返回系列

2D 与 MPR 渲染:从体数据切出可看的图像

复盘 DicomVision 中 2D Stack、窗宽窗位、仿射变换、MPR 三正交面和 scipy 重采样的实现思路。

问题引入

2D 显示看起来最简单:选一张切片,显示出来。但医学影像的 2D 渲染并不是把数组直接贴到页面上。它至少要处理 HU 转换、窗宽窗位、灰度方向、缩放、平移、旋转、翻转、测量叠加和视口尺寸。MPR 又在 2D 之上增加了一层复杂度:图像不再来自原始切片,而是从三维 volume 中沿任意平面重采样得到。

DicomVision 的后端渲染思路是把这两件事统一到视口模型里。Stack、MPR、3D 都有 ViewRecord,用户操作都变成 view operation。不同视图的差异体现在像素来源和渲染路径,而不是前端自己维护一套独立算法。

背景概念

Stack 是最传统的阅读方式:当前 index 指向某个 Instance,滚轮切换 index,窗宽窗位决定显示范围。图像坐标是二维像素网格,屏幕坐标是视口画布。缩放、平移、旋转和翻转可以用一个 image-to-canvas 的仿射矩阵表示。

MPR 是 Multi-Planar Reconstruction。它把连续切片看作一个三维体,然后从体里切出 axial、coronal、sagittal 三个正交面。Axial 通常接近原始横断面,coronal 是冠状面,sagittal 是矢状面。MPR 的关键不是“换个方向显示数组”,而是按真实 spacing 重采样。如果 z 方向切片厚度是 2.5 mm,而 x/y 像素间距是 0.7 mm,直接按数组索引切面会导致比例失真。

项目实现

2D Stack 的渲染链路是:根据 view.current_index 找到 Instance,读取 dicom_cache 中的 source_pixels,选择视口窗宽窗位,使用 np.clip 把目标值域裁剪到显示范围,归一化为 uint8,再把图像按 ViewportTransformer 的仿射矩阵映射到 canvas 大小。测量层和方向标识等 overlay 也要使用同一套变换,避免图像和标注错位。

ViewportTransformer.build_image_to_canvas_transform 会构造 3x3 矩阵:先把图像中心平移到原点,再缩放和翻转,再旋转,最后平移到 canvas 中心并加上用户拖拽偏移。应用到 NumPy 数组时,项目使用 scipy.ndimage.affine_transform。这个函数接受的是输出像素到输入像素的反向映射,所以代码会先求逆矩阵。它还使用 row/col,也就是 y/x 顺序,因此需要把矩阵从 x/y 坐标转为数组坐标。

MPR 的路径更重一些。后端先构造 volume 和几何信息,然后每个 MPR 视口根据自己的平面定义取样。三视图通过共享的 ViewGroupRecord 保持联动:一个视口里的十字线移动,会改变共享 cursor,再广播到另外两个视口。这样 axial、coronal、sagittal 不是三个孤立图像,而是同一个三维位置的三个观察方向。

关键函数表

函数或模块作用关键注意点
ViewportTransformer.calculate_contain_zoom计算初始适配缩放要考虑像素宽高比
build_image_to_canvas_transform构造 image-to-canvas 矩阵平移、缩放、翻转、旋转的顺序不能乱
AffineTransform.inverse_components给 SciPy 提供反向映射affine_transform 使用 output-to-input
scipy.ndimage.affine_transform重采样二维图像或切面数组坐标是 row/col,不是 x/y
Pillow.Image.fromarray把 uint8 数组转成可编码图像输出前必须确认 dtype 和范围
services/mpr/*MPR 几何、平面、光标、重采样必须使用 spacing 和方向信息

自绘图表

Instance 当前切片 Pixel HU / float32 Window clip 到 0-255 Affine 缩放平移旋转 Overlay 测量和标识 PNG 推送 2D Stack 渲染不是直接显示 pixel_array 每一步都必须和当前视口状态一致,否则图像、测量和交互会错位。
Stack 渲染把医学像素转换为当前视口可显示的图像帧。
同一个体数据上的三个正交切面 Axial Sagittal Coronal 十字线移动的是三维 cursor,三个视口只是同一位置的不同投影。
MPR 的核心是共享三维位置和几何,而不是三张孤立的二维图。

小结

2D 和 MPR 的共同点是都要把医学空间的数据变成当前视口的二维图像。区别在于 Stack 的像素直接来自某个 Instance,MPR 的像素来自 volume 重采样。只要牢记“数组坐标、物理坐标、屏幕坐标是三件事”,渲染路径里的矩阵、spacing 和重采样就会变得容易理解。