2. 医学影像数据格式与解析
在医学影像 AI 训练流水线中,数据格式解析是第一个真正接触原始数据的环节。它决定了后续预处理、标注对齐、增强和模型输入的正确性。医学影像领域存在多种格式,其中 DICOM 是临床采集与归档的事实标准,NIfTI 是神经影像研究的主流格式,NRRD 则常见于影像组学和部分开源工具链。理解它们的元数据组织方式、像素数据布局以及常用解析库的读写行为,是构建稳定数据管线的必要基础。
三种格式的核心差异
DICOM 以“单次检查、单序列、单帧”为基本单位,一个 CT 或 MR 序列通常由几十到几百个独立文件组成,每个文件都携带完整的患者信息、设备参数、采集几何和像素数据。它的元数据以标签(Tag)形式组织,例如 (0028,0010) 表示行数,(0028,0011) 表示列数,(0020,0032) 表示图像位置,(0028,1052) 表示截距,(0028,1053) 表示斜率。像素值需要经过 HU = pixel * RescaleSlope + RescaleIntercept 转换才能得到具有物理意义的 CT 值。
NIfTI 将整个三维或四维体数据存放在单个文件中,元数据集中在头部(Header),包括维度、体素尺寸、数据类型、仿射矩阵等。它的优势是读写简单、适合体数据整体处理,但缺少 DICOM 那样丰富的采集上下文。NRRD 同样以单文件存储体数据,头部为可读文本,支持多种编码方式,在 3D Slicer 和 PyRadiomics 等工具中广泛使用。
DICOM 解析实战:从序列到体数据
使用 Python 生态中最常用的 pydicom 库读取单个 DICOM 文件非常直接:
import pydicom
import numpy as np
ds = pydicom.dcmread("IM-0001.dcm")
rows = ds.Rows
cols = ds.Columns
pixel = ds.pixel_array.astype(np.float32)
hu = pixel * float(ds.RescaleSlope) + float(ds.RescaleIntercept)
print(rows, cols, hu.min(), hu.max())
但实际训练中面对的是一个序列,需要把多个切片按空间位置排序后堆叠成三维体数据。关键步骤是读取每个文件的 ImagePositionPatient 和 ImageOrientationPatient,计算切片沿法向的投影位置,再按该位置升序排列。如果忽略这一步,直接按文件名排序,在部分设备导出的数据中会出现层序错乱,导致三维重建和病灶定位出现严重偏差。
另一个常见问题是像素间距不一致。DICOM 中的 PixelSpacing 给出层内两个方向的实际物理尺寸,SliceThickness 或相邻层位置差给出层间间距。当层间距远大于层内间距时,体数据在 Z 方向被“拉长”,直接输入各向同性假设的模型会产生几何畸变。工程上通常先重采样到统一间距,例如将 0.7 mm × 0.7 mm × 5 mm 重采样为 1 mm × 1 mm × 1 mm,再进入后续流程。
NIfTI 与 NRRD 的读写要点
NIfTI 常用 nibabel 读写。读取时除了拿到 dataobj,还要关注 header.get_zooms() 和 affine。仿射矩阵定义了体素索引到世界坐标的映射,做多模态配准、标注坐标转换时必须使用它,而不能假设原点在图像角点。
import nibabel as nib
img = nib.load("volume.nii.gz")
data = img.get_fdata() # float64,已应用 scl_slope/inter
zooms = img.header.get_zooms() # 体素物理尺寸
affine = img.affine # 4x4 仿射矩阵
print(data.shape, zooms)
NRRD 可以用 pynrrd 读取,其头部字段 space directions 与 NIfTI 的仿射矩阵作用类似,但表达方式不同。跨格式转换时,最容易出错的地方是轴顺序和方向约定:NIfTI 常用 RAS 约定,而部分 DICOM 和 NRRD 数据可能使用 LPS 或 LAS。若不做方向统一,左右侧结构可能被镜像,这在脑部或肺部病灶分析中是不可接受的错误。
方向一致性检查:在把 DICOM 序列转为 NIfTI 后,建议用 nib.aff2axcodes(img.affine) 查看轴方向编码,确认与预期一致。若出现左右翻转,需要在仿射矩阵或数据轴上做显式修正,而不是靠模型“自己学”。
元数据与像素数据的分离管理
在流水线设计中,建议把元数据和像素数据分开管理。元数据表可以包含:患者/检查匿名 ID、序列描述、层厚、像素间距、图像方向、重采样后间距、窗宽窗位、数据来源格式等字段。像素数据则统一转换为 NumPy 数组或内存映射文件,按固定命名规则存放。这样做的直接好处是:预处理参数可以按序列批量配置,数据增强不会误改几何信息,训练时的数据加载也不必反复解析原始格式。
| 格式 | 典型解析库 | 元数据关键字段 | 像素数据注意点 |
|---|---|---|---|
| DICOM | pydicom | Rows/Columns、PixelSpacing、ImagePositionPatient、RescaleSlope/Intercept | 需按位置排序、应用 HU 转换、注意层间距 |
| NIfTI | nibabel | dim、pixdim、affine、scl_slope/inter | get_fdata 已应用缩放,注意轴方向 RAS/LAS |
| NRRD | pynrrd | dimension、sizes、space directions、origin、encoding | 按 encoding 解码,注意 space directions 与仿射的对应 |
一个可复用的解析检查清单
在把任何一批医学影像数据接入训练流水线之前,建议按以下顺序做一次解析检查:
- 确认格式与解析库版本,读取一个样本文件,打印形状、数据类型和值范围。
- 对 DICOM 序列,检查切片数量、层间距是否均匀、是否存在缺失层或重复层。
- 对 NIfTI/NRRD,检查仿射矩阵或 space directions,确认体素尺寸和方向编码。
- 验证像素值转换:CT 的 HU 范围是否合理(如空气约 -1000,水约 0,骨密度可到 1000 以上)。
- 检查方向一致性,必要时统一到 RAS 或 LPS,并记录转换关系。
- 将元数据写入结构化表,像素数据转为统一数组格式,完成一次端到端读取测试。
完成这些检查后,数据解析环节才算真正稳定。后续的预处理、增强和模型训练都建立在这一步的正确性之上,任何方向、间距或值域上的疏漏都会在训练结果中被放大。