2. 医学影像数据格式与读取

在医学影像分割算法开发中,数据读取与空间信息解析是整条流水线的起点。如果对格式、几何变换和灰度映射理解不到位,后续的预处理、训练和推理都会出现难以定位的错误。本节围绕三类常见格式(DICOM、NIfTI、MHA)以及四个关键概念(窗宽窗位、方向矩阵、体素间距、空间坐标系统)展开,目标是让读者能够正确读取数据、理解其空间语义,并写出可复现的读取代码。

一、三种格式的定位与差异

DICOM(Digital Imaging and Communications in Medicine)是临床设备输出的原始格式,通常一个序列由多张切片文件组成,每张文件包含像素数据和大量元数据标签(如患者信息、层厚、层间距、图像方向、窗宽窗位等)。DICOM 的优势是信息完整、与临床系统兼容,缺点是读取时需要按序列重组,且不同厂商的私有标签可能带来兼容性问题。

NIfTI(Neuroimaging Informatics Technology Initiative)常见于神经影像和科研数据集,通常以单个 .nii 或 .nii.gz 文件存储三维或四维体数据,头信息中包含体素间距、方向矩阵、仿射变换等几何信息。NIfTI 结构简洁,适合作为训练数据的中间格式。

MHA(MetaImage)是 ITK 生态中常用的格式,支持多通道、各向异性体素和多种数据类型,头信息以文本形式描述维度、间距、方向等。MHA 在医学图像分割挑战赛(如 MSD、BTCV)中较为常见。

三者的核心差异在于:DICOM 以切片为单位、元数据分散;NIfTI 与 MHA 以体数据为单位、几何信息集中。实际项目中,常将 DICOM 序列转换为 NIfTI 或 MHA 后再进入训练流程。

二、窗宽窗位:把 HU 值映射到可视灰度

CT 图像的像素值通常以 HU(Hounsfield Unit)表示,范围可达 -1024 到 3000 以上。人眼无法直接分辨如此宽的动态范围,因此需要通过窗宽(Window Width, WW)和窗位(Window Center, WC)将感兴趣区间的 HU 值线性映射到 0–255 的显示灰度。

映射公式为:

lower = WC - WW / 2
upper = WC + WW / 2
gray = clip((HU - lower) / (upper - lower), 0, 1) * 255

例如,观察腹部软组织常用 WW=400、WC=40,此时映射区间为 [-160, 240];观察肺窗常用 WW=1500、WC=-600,映射区间为 [-1350, 150]。在分割任务中,窗宽窗位不仅影响可视化,也常作为预处理手段:将 HU 裁剪到特定区间后再归一化,可以突出目标器官的对比度。需要注意的是,窗宽窗位是显示参数,不应直接改变原始 HU 值;若在训练前做裁剪,应记录所用区间以保证推理时一致。

三、体素间距、方向矩阵与空间坐标系统

体素间距(spacing)描述相邻体素在物理空间中的实际距离,单位通常为毫米。例如 spacing=(0.7, 0.7, 3.0) 表示层内分辨率为 0.7mm×0.7mm,层间距为 3.0mm。各向异性间距会导致分割网络在不同方向上感受野不一致,因此常通过重采样将数据统一到各向同性间距(如 1mm×1mm×1mm)。

方向矩阵(direction matrix)描述体素坐标轴相对于世界坐标系的朝向。在 NIfTI 中,方向信息与体素间距共同构成仿射矩阵,将体素索引 (i, j, k) 映射到世界坐标 (x, y, z):

[x]   [d11*s1  d12*s2  d13*s3  o1] [i]
[y] = [d21*s1  d22*s2  d23*s3  o2] [j]
[z]   [d31*s1  d32*s2  d33*s3  o3] [k]
[1]   [  0       0       0      1] [1]

其中 s 为体素间距,d 为方向余弦,o 为原点偏移。方向矩阵决定了图像在解剖学上的朝向(如 LPS 或 RAS)。如果忽略方向矩阵,直接把不同来源的数据按数组索引对齐,可能导致左右、前后、上下翻转,进而使分割结果在空间上错位。

空间坐标系统是上述概念的共同基础。医学影像中常用的世界坐标系有 RAS(x 向右、y 向前、z 向上)和 LPS(x 向左、y 向后、z 向上)。DICOM 通常使用 LPS,NIfTI 标准使用 RAS。在读取和保存数据时,必须明确当前使用的坐标系,并在格式转换时进行相应翻转。例如,从 DICOM 转 NIfTI 时,若不做坐标变换,x 和 y 方向可能需要取反。

医学影像读取与空间信息解析 输入格式 DICOM 序列 NIfTI (.nii/.nii.gz) MHA / MHD 空间信息解析 体素间距 spacing (sx, sy, sz) 方向矩阵 direction (3×3) 原点 origin (ox, oy, oz) 统一体数据 仿射矩阵 (4×4) 世界坐标 (RAS/LPS) 重采样后体素 窗宽窗位映射:HU → 显示灰度 -1024 -160 40 240 3000 映射区间 [WC-WW/2, WC+WW/2] = [-160, 240] 0 255 腹部软组织:WW=400, WC=40 肺窗:WW=1500, WC=-600 骨窗:WW=2000, WC=400

四、实际读取流程与代码示例

以 Python 生态为例,DICOM 常用 pydicom 或 SimpleITK 读取,NIfTI 常用 nibabel,MHA 常用 SimpleITK。下面给出一个统一读取并提取空间信息的示例:

import SimpleITK as sitk
import numpy as np

# 读取 NIfTI 或 MHA
img = sitk.ReadImage("case_001.nii.gz")
arr = sitk.GetArrayFromImage(img)          # 形状 (z, y, x)
spacing = img.GetSpacing()                  # (sx, sy, sz)
origin = img.GetOrigin()                    # (ox, oy, oz)
direction = img.GetDirection()              # 9 元素方向余弦

# 读取 DICOM 序列
reader = sitk.ImageSeriesReader()
dicom_names = reader.GetGDCMSeriesFileNames("dicom_dir")
reader.SetFileNames(dicom_names)
dcm_img = reader.Execute()
dcm_arr = sitk.GetArrayFromImage(dcm_img)

# 窗宽窗位映射(以 CT 为例)
def apply_window(hu_arr, ww, wc):
    lower = wc - ww / 2
    upper = wc + ww / 2
    gray = np.clip((hu_arr - lower) / (upper - lower), 0, 1)
    return (gray * 255).astype(np.uint8)

windowed = apply_window(dcm_arr, ww=400, wc=40)

读取后应检查三件事:数组形状是否与预期一致(注意 SimpleITK 返回的是 z,y,x 顺序,而 nibabel 返回 x,y,z);spacing 是否合理(如出现 0 或负值说明元数据异常);direction 是否为单位矩阵或接近单位矩阵(若明显偏离,说明存在旋转或翻转)。

五、常见问题与排查建议

  • 方向翻转:DICOM 转 NIfTI 后左右颠倒,通常是 RAS/LPS 坐标系未转换。可用 nibabel 的 affine 或 SimpleITK 的 DICOMOrient 统一到 RAS。
  • 层间距不一致:部分 DICOM 序列层间距在标签中缺失,需从 ImagePositionPatient 计算相邻切片距离。
  • 窗宽窗位误用:训练时若对 HU 做了裁剪,推理时必须使用相同区间,否则灰度分布偏移会导致分割性能下降。
  • 重采样插值:将各向异性数据重采样到各向同性时,图像用线性插值,标签必须用最近邻插值,避免引入不存在的类别。
建议在数据加载阶段统一将图像方向调整为 RAS,并记录原始 spacing、origin、direction 到元数据文件,便于后续逆变换回原始空间进行评估或可视化。

掌握上述格式解析与空间信息处理,是构建可靠医学影像分割训练集的基础。只有在读取阶段保证几何与灰度语义正确,后续的网络训练和结果评估才具有临床意义。