import numpy as np
import matplotlib.pyplot as plt
from spacetransformer.core import Space
# 创建一个标准的规范采样网格
standard_space = Space(
shape=(1, 10, 10), # 三维网格,第一维度为1用于2D可视化
origin=(0.0, 0.0, 0.0), # 物理原点
spacing=(1.0, 1.0, 1.0), # 各向同性1mm采样
x_orientation=(1.0, 0.0, 0.0), # X轴方向
y_orientation=(0.0, 1.0, 0.0), # Y轴方向
z_orientation=(0.0, 0.0, 1.0) # Z轴方向
)
# 展示标准规范采样网格Space 概念详解
Space 概念:完整的 3D 图像几何描述
每一幅医学图像都对应物理世界中的一个规范采样网格。SpaceTransformer 通过 Space 对象的六个要素来完整描述这个网格:形状、原点、间距与三个方向向量。
为了直观展示 Space 变换效果,我们实现 2D 网格可视化:
def visualize_sampling_grid(space, color='blue', show_axes=True, label_suffix=''):
"""
可视化采样网格的2D投影(绘制所有采样点)
Args:
space: Space 对象
color: 绘制颜色
show_axes: 是否显示坐标轴
label_suffix: 标签后缀,用于区分不同的网格
"""
# 提取 Y-Z 平面信息(忽略 X 维)
shape_yz = space.shape[1:3] # (height, width)
# 创建所有索引点的网格
y_indices, z_indices = np.meshgrid(
np.arange(shape_yz[0]),
np.arange(shape_yz[1]),
indexing='ij'
)
# 构建 3D 索引点(X 维设为 0)
index_points = np.stack([
np.zeros_like(y_indices.flatten()), # X=0
y_indices.flatten(), # Y索引
z_indices.flatten() # Z索引
], axis=1)
# 转换为世界坐标
world_points = space.to_world_transform.apply_point(index_points)
# 绘制采样点
plt.scatter(world_points[:, 2], world_points[:, 1],
c=color, alpha=0.4, s=20, label=f'Sampling Grid{label_suffix}')
if show_axes:
# 显示原点和坐标轴方向
origin_world = space.to_world_transform.apply_point([[0, 0, 0]])[0]
plt.plot(origin_world[2], origin_world[1], 'o',
color=color, markersize=8, alpha=0.8)
# 计算坐标轴长度(适当拉长)
axis_length = 1
# Y 轴方向(图中为垂直)
y_axis_end = origin_world[1:3] + np.array(space.y_orientation[1:3]) * axis_length
plt.arrow(origin_world[2], origin_world[1],
y_axis_end[1] - origin_world[2], y_axis_end[0] - origin_world[1],
head_width=axis_length*0.1, head_length=axis_length*0.1,
fc=color, ec=color, alpha=0.7)
# Y轴文字标注(偏移避免被箭头遮挡)
y_text_offset = np.array(space.y_orientation[1:3]) * axis_length * 0.3
plt.text(y_axis_end[1] + y_text_offset[1], y_axis_end[0] + y_text_offset[0], 'Y',
fontsize=12, color=color, ha='center', va='center', weight='bold')
# Z 轴方向(图中为水平)
z_axis_end = origin_world[1:3] + np.array(space.z_orientation[1:3]) * axis_length
plt.arrow(origin_world[2], origin_world[1],
z_axis_end[1] - origin_world[2], z_axis_end[0] - origin_world[1],
head_width=axis_length*0.1, head_length=axis_length*0.1,
fc=color, ec=color, alpha=0.7)
# Z轴文字标注(偏移避免被箭头遮挡)
z_text_offset = np.array(space.z_orientation[1:3]) * axis_length * 0.3
plt.text(z_axis_end[1] + z_text_offset[1], z_axis_end[0] + z_text_offset[0], 'Z',
fontsize=12, color=color, ha='center', va='center', weight='bold')
def setup_plot(figsize=(10, 8)):
"""设置绘图通用参数"""
plt.figure(figsize=figsize)
def finalize_plot(title):
"""完成绘图的通用设置"""
plt.xlabel('Z (mm)')
plt.ylabel('Y (mm)')
plt.title(title)
plt.grid(True, alpha=0.3)
plt.axis('equal')
plt.legend()
plt.tight_layout()
plt.show()
# 可视化标准网格
setup_plot()
visualize_sampling_grid(standard_space, color='blue')
finalize_plot('Standard Sampling Grid (10x10, 1mm resolution)')空间变换操作演示
形状变换(Resize)
# 创建不同分辨率的采样网格
high_res_space = standard_space.apply_shape((1, 20, 20))
low_res_space = standard_space.apply_shape((1, 5, 5))
setup_plot()
# 低分辨率对比
visualize_sampling_grid(standard_space, color='blue', label_suffix=' (Original)')
visualize_sampling_grid(low_res_space, color='red', label_suffix=' (5x5)')
finalize_plot('Low Resolution Transform (10x10 → 5x5)')翻转变换
# 创建翻转的采样网格
flipped_space = standard_space.apply_flip(axis=1) # 沿Y轴翻转
# 可视化翻转变换的overlay效果
setup_plot()
visualize_sampling_grid(standard_space, color='blue', label_suffix=' (Original)')
visualize_sampling_grid(flipped_space, color='red', label_suffix=' (Flipped Y-axis)')
finalize_plot('Flip Transform (Y-axis)')轴交换变换
# 创建轴交换的采样网格(使用非正方形网格以增强对比)
rect_space = Space(
shape=(1, 6, 12), # 矩形网格
origin=(0.0, 0.0, 0.0),
spacing=(1.0, 1.0, 1.0),
x_orientation=(1.0, 0.0, 0.0),
y_orientation=(0.0, 1.0, 0.0),
z_orientation=(0.0, 0.0, 1.0)
)
swapped_space = rect_space.apply_swap(1, 2) # 交换 Y 轴与 Z 轴
# 可视化轴交换变换的overlay效果
setup_plot()
visualize_sampling_grid(rect_space, color='blue', label_suffix=' (Original 6x12)')
visualize_sampling_grid(swapped_space, color='red', label_suffix=' (Swapped Y↔Z 12x6)')
finalize_plot('Axis Swap Transform (Y ↔ Z)')旋转变换
# 创建旋转的采样网格
rotated_space = standard_space.apply_rotate(axis=0, angle=30, unit='degree', center='center')
# 可视化旋转变换的overlay效果
setup_plot()
visualize_sampling_grid(standard_space, color='blue', label_suffix=' (Original)')
visualize_sampling_grid(rotated_space, color='red', label_suffix=' (Rotated 30°)')
finalize_plot('Rotation Transform (around X-axis 30°)')边界框裁剪
# 定义裁剪区域并应用变换
crop_bbox = np.array([[0, 1], [2, 8], [2, 8]]) # X, Y, Z范围
cropped_space = standard_space.apply_bbox(crop_bbox)
# 可视化裁剪变换的overlay效果
setup_plot()
visualize_sampling_grid(standard_space, color='blue', label_suffix=' (Original)')
visualize_sampling_grid(cropped_space, color='red', label_suffix=' (Cropped ROI)')
finalize_plot('Bounding Box Crop (6x6 ROI)')复杂变换链
# 演示复杂变换链的抽象规划
complex_target_space = (standard_space
.apply_bbox(np.array([[0, 1], [2, 8], [2, 8]])) # 裁剪到中心区域
.apply_shape((1, 12, 12)) # 重采样到12×12
.apply_rotate(axis=0, angle=45, unit='degree') # 旋转45度
)
# 可视化复杂变换链的 overlay 效果
setup_plot()
visualize_sampling_grid(standard_space, color='blue', label_suffix=' (Original)')
visualize_sampling_grid(complex_target_space, color='red', label_suffix=' (Crop→Resample→Rotate)')
finalize_plot('Complex Transform Chain (Crop → Resample → Rotate)')浮点边界框采样(float-point bbox sampling)
float_bbox_space = standard_space.apply_float_bbox(np.array([[0, 1], [2.5, 5.5], [2.5, 5.5]]), (12, 12, 12))
setup_plot()
visualize_sampling_grid(standard_space, color='blue', label_suffix=' (Original)')
visualize_sampling_grid(float_bbox_space, color='red', label_suffix=' (Float Bounding Box)')
finalize_plot('Float Bounding Box Sampling (6x6 ROI)')Space 抽象框架的核心优势
设计理念:Space 中心 vs Transform 中心
SpaceTransformer 采用“Space 中心”的设计理念,区别于 torchvision 等库的“Transform 中心”模式。该选择基于医学图像处理的本质:对象的坐标以世界坐标系为准。
Space 中心设计:每个数据对象(图像、点集、掩膜)都绑定唯一空间描述符。当两个对象需要对齐时,通过比较它们的 Space 属性自动生成精确变换关系。
Transform 中心的局限:变换本质相对,缺乏绝对基准,导致多对象空间关系难维护,容易产生累积误差。
技术实现:规划与执行分离
Space 类实现了空间变换的“规划阶段”与“执行阶段”完全解耦:
规划阶段:通过链式调用构建复杂变换序列,所有操作在抽象几何空间进行,无需触碰像素数据。
执行阶段:warp_image 分析完整变换链,自动选择最优插值路径,以单次采样完成全部变换。
实际收益
几何精度:变换顺序不影响结果。“旋转→缩放”与“缩放→旋转”在 Space 层面等价,避免传统方法受采样边界影响的信息丢失。
内存效率:消除多步骤变换中的中间缓存;传统每步都需要完整图像拷贝,而 Space 仅在最终执行时分配目标内存一次。
架构简洁:库中的 warp_xxx 接口处理所有类型变换,无需针对图像/点集/掩膜分别维护复杂逻辑。
接口易用:相比 SimpleITK 需手工配置采样参数,SpaceTransformer 提供语义化接口,同时保留高级用户的底层控制力。