NumPy 影像计算:shape、axis、dtype 与体数据
用 DicomVision 的后端处理链路解释 NumPy 在医学影像中的核心角色:数组形状、轴顺序、窗宽窗位、dtype 和内存。
问题引入
医学影像后端离不开 NumPy。pydicom 负责把 DICOM 像素解码出来,但真正的计算从 NumPy 数组开始:切片要排序和堆叠,灰度要裁剪和归一化,MPR 要重排轴和重采样,测量要做向量和距离计算,VTK 也需要从连续内存的数组接收体数据。
如果不理解 shape、axis 和 dtype,影像代码很容易出现三类错误:图像方向反了,MPR 切面比例错了,内存突然爆了。DicomVision 中大量看似细小的 np.asarray、np.stack、np.flip、np.clip,其实都在处理这些基础问题。
背景概念
DICOM 单张切片解码后通常是二维数组,shape 为 (rows, columns),也就是 (y, x)。多张切片组成体数据后,后端常用 (z, y, x) 表示,其中 z 是切片方向,y 是行,x 是列。这个顺序适合按照切片索引取图,也适合把 axial/coronal/sagittal 的关系说清楚。
但是数组坐标不等于物理坐标。数组的 x/y/z 是索引,单位是像素或切片序号;物理坐标要乘上 spacing,还要考虑 DICOM 中的方向矩阵。PixelSpacing 决定行列方向的毫米间距,切片间距可以来自相邻切片位置,也可以退化使用 SliceThickness。MPR、测量和 3D 都不能只看数组索引。
dtype 同样关键。CT 原始像素可能是 int16,应用 slope/intercept 后通常转为 float32。显示图像最终要转成 uint8。3D 渲染为了给 VTK,常会保持 float32 并保证连续内存。一个 512x512x500 的 volume,如果是 int16,大约 250 MB;如果转 float32,就是约 500 MB。再加缓存、重采样和 VTK session,低配机器很快到内存边界。
项目实现
项目在 DicomCache 中把 dataset.pixel_array 转为 NumPy 数组。如果是灰度图,会转成 float32,再应用 slope/intercept;如果是彩色图,会处理 YBR 到 RGB 的转换,并把值规整到 uint8。对于 MONOCHROME1,项目通过取负值统一灰度方向。
窗宽窗位的核心计算可以简化为:
low = center - width / 2
high = center + width / 2
display = np.clip(pixels, low, high)
display = (display - low) / (high - low) * 255
display = display.astype(np.uint8)
这段逻辑的意义是把医学值域映射到显示值域。np.clip 不改变原始 Dataset,它只服务于当前视口显示。MPR 和 3D 则会进一步把多个切片通过 np.stack 组织成 volume。轴方向不一致时,项目会用 np.transpose、np.flip 和方向信息把数据变成可稳定重采样的体。
关键函数表
| NumPy 函数 | 在影像中的用途 | 风险点 |
|---|---|---|
np.asarray | 把 pydicom 或 PIL 结果变成数组 | 可能共享内存,修改前要确认语义 |
np.stack | 多张切片堆成 volume | 切片 shape 必须一致 |
np.transpose | 调整 axis 顺序 | 很容易把 x/y/z 语义写反 |
np.flip | 修正方向或显示翻转 | 要区分显示翻转和物理方向修正 |
np.clip | 窗宽窗位裁剪、颜色范围限制 | 裁剪只用于显示,不代表修改原始像素 |
np.ascontiguousarray | 给 VTK 或编码路径提供连续内存 | 会复制数据,注意内存峰值 |
np.linalg.norm | 距离和向量长度 | 要使用物理坐标而不是屏幕坐标 |
np.cross / np.dot | 方向向量、角度、平面计算 | 方向向量必须归一化 |
自绘图表
| 数据 | shape 示例 | dtype | 约占内存 |
|---|---|---|---|
| 单张 CT 原始切片 | 512 x 512 | int16 | 0.5 MB |
| 500 张 CT 原始体 | 500 x 512 x 512 | int16 | 250 MB |
| 500 张 CT 计算体 | 500 x 512 x 512 | float32 | 500 MB |
| 500 张显示缓存 | 500 x 512 x 512 | uint8 | 125 MB |
| RGB 彩色显示体 | 500 x 512 x 512 x 3 | uint8 | 375 MB |
小结
NumPy 在 DicomVision 中不是辅助库,而是影像计算的共同语言。pydicom 解码出来的是数组,MPR 重采样操作的是数组,测量统计依赖数组,VTK 也从数组接收 volume。写医学影像代码时,最该先确认的不是某个算法名,而是当前数组的 shape、axis、dtype、spacing 和方向语义。