DICOM 基础:从一个 .dcm 文件理解医学影像
解释 DICOM 的层级、Tag、PixelData、窗宽窗位、HU,以及 CT、PET、超声在数据形态上的差异。
问题引入
第一次接触 DICOM 时,很容易把 .dcm 当作一种医学图片格式。这个理解只对了一小半。DICOM 文件当然可以携带像素,但它更重要的是携带医学语义:这是哪位患者、哪次检查、哪组序列、哪张图像、来自什么设备、像素间距是多少、切片位置在哪里、默认窗宽窗位是什么。一个 Viewer 如果只把 PixelData 当图片显示,它很快会在序列组织、测量、MPR、PACS、导出上遇到问题。
DicomVision 的很多实现都围绕这个事实展开:先尊重 DICOM 的层级和元数据,再处理像素。因为 Viewer 打开的通常不是一张图,而是一组可以构成空间关系的实例。CT 的三维重建、MPR 的正交切面、测量中的毫米单位,全都依赖这些元数据。
背景概念
DICOM 最常见的层级是 Patient、Study、Series、Instance。Patient 是患者,Study 是一次检查,Series 是检查中的一组影像,Instance 是一张图像或一个标准对象。Viewer 通常按 Series 打开,是因为同一个 Series 内的图像通常共享 modality、扫描参数和几何关系,可以被当成一个连续序列处理。
常见字段可以分成几类:
| 类别 | 典型 Tag | 工程意义 |
|---|---|---|
| 身份与归档 | PatientID、StudyInstanceUID、SeriesInstanceUID、SOPInstanceUID | 分组、检索、导出、引用原始对象 |
| 图像尺寸 | Rows、Columns、BitsStored、PixelRepresentation | 决定像素数组 shape 和 dtype 解释 |
| 空间几何 | PixelSpacing、SliceThickness、ImagePositionPatient、ImageOrientationPatient | 距离测量、MPR、3D 重建 |
| 显示建议 | WindowCenter、WindowWidth、PhotometricInterpretation | 默认窗宽窗位、灰度方向 |
| 像素数据 | PixelData、TransferSyntaxUID | 解码实际影像像素 |
| 设备与类型 | Modality、Manufacturer、SeriesDescription | 区分 CT、MR、PT、US、SR、PR 等对象 |
CT 图像的像素通常要通过 RescaleSlope 和 RescaleIntercept 转成 HU,也就是 Hounsfield Unit。空气接近 -1000 HU,水接近 0 HU,骨骼可以到几百甚至更高。窗宽窗位不是简单调亮度,而是选择一段 HU 范围映射到 0 到 255 灰度。肺窗、骨窗、纵隔窗本质上是在看不同组织的 HU 区间。
项目实现
DicomVision 加载文件时不会立刻把所有像素读进内存。它先读取 header,判断是否是可显示的普通像素影像,再根据 SeriesInstanceUID、目录分区和 4D phase 信息分组。这样做的原因是很现实的:一个 CT 序列可能有几百张图,如果扫描阶段就解码每张 PixelData,会让打开目录的等待时间和内存占用都显著上升。
当用户真正打开某个序列并需要渲染时,后端才通过 dataset.pixel_array 解码像素。CT 像素会转成 float32,再应用 slope 和 intercept。对于 PhotometricInterpretation == "MONOCHROME1" 的图像,项目会反相,保证后续渲染路径仍然遵循“数值越大显示越亮”的模型。
CT、PET、超声在 Viewer 中的困难不同。CT 切片更规则,适合堆成 (z, y, x) 体数据。PET 通常涉及 SUV、衰减校正、彩色 LUT 和 PET/CT 融合。超声常见多帧、彩色、多普勒、私有标签和非规则采集,不能简单套用 CT 的 MPR 思路。DicomVision 当前最成熟的主线是 CT 类体数据,其他 modality 可以显示一部分,但需要按数据特点逐步增强。
关键函数表
| 概念 | 相关字段或函数 | 在 Viewer 中的作用 |
|---|---|---|
| 序列归属 | SeriesInstanceUID | 把多个 Instance 归入同一可打开序列 |
| 唯一实例 | SOPInstanceUID | 缓存、导出和标准对象引用 |
| 像素解码 | Dataset.pixel_array | 从 DICOM 编码中得到 NumPy 数组 |
| HU 转换 | RescaleSlope、RescaleIntercept | 把原始像素值转为物理/近物理值 |
| 灰度方向 | PhotometricInterpretation | 处理 MONOCHROME1 的反相 |
| 物理距离 | PixelSpacing | 把像素距离转换为毫米 |
| 切片姿态 | ImagePositionPatient、ImageOrientationPatient | 建立体数据几何和 MPR 坐标 |
自绘图表
<rect x="390" y="130" width="200" height="60" rx="14" fill="var(--surface)" stroke="var(--line)" />
<text x="490" y="166" text-anchor="middle" fill="currentColor" font-weight="800">Study</text>
<rect x="170" y="250" width="180" height="60" rx="14" fill="var(--surface)" stroke="var(--line)" />
<text x="260" y="286" text-anchor="middle" fill="currentColor" font-weight="800">Series A</text>
<rect x="400" y="250" width="180" height="60" rx="14" fill="var(--surface)" stroke="var(--line)" />
<text x="490" y="286" text-anchor="middle" fill="currentColor" font-weight="800">Series B</text>
<rect x="630" y="250" width="180" height="60" rx="14" fill="var(--surface)" stroke="var(--line)" />
<text x="720" y="286" text-anchor="middle" fill="currentColor" font-weight="800">Series C</text>
<text x="260" y="350" text-anchor="middle" fill="var(--muted)" font-size="14">Instance 1..N</text>
<text x="490" y="350" text-anchor="middle" fill="var(--muted)" font-size="14">Instance 1..N</text>
<text x="720" y="350" text-anchor="middle" fill="var(--muted)" font-size="14">Instance 1..N</text>
<line x1="490" y1="90" x2="490" y2="130" stroke="var(--accent)" stroke-width="3" marker-end="url(#arrow-dicom-tree)" />
<line x1="490" y1="190" x2="260" y2="250" stroke="var(--accent)" stroke-width="3" marker-end="url(#arrow-dicom-tree)" />
<line x1="490" y1="190" x2="490" y2="250" stroke="var(--accent)" stroke-width="3" marker-end="url(#arrow-dicom-tree)" />
<line x1="490" y1="190" x2="720" y2="250" stroke="var(--accent)" stroke-width="3" marker-end="url(#arrow-dicom-tree)" />
<text x="490" y="222" text-anchor="middle" fill="var(--muted)" font-size="14">Viewer 通常按 Series 打开</text>
| Modality | 数据特点 | Viewer 中的典型难点 |
|---|---|---|
| CT | 切片规则,HU 语义明确,适合 MPR 和 3D | 几何排序、spacing、窗宽窗位、体数据内存 |
| PET | 常见 SUV、彩色 LUT、与 CT 融合 | 标准化摄取值、配准、双模态融合显示 |
| US | 多帧、彩色、多普勒、私有标签较多 | 多帧播放、彩色解释、设备私有差异 |
小结
理解 DICOM 的重点不是背 Tag,而是知道哪些 Tag 会改变工程实现。SeriesInstanceUID 决定分组,PixelSpacing 决定测量,ImagePositionPatient 和 ImageOrientationPatient 决定 MPR,WindowCenter 和 WindowWidth 决定默认显示,PixelData 决定真正的内存压力。后面的 pydicom、NumPy、MPR、VTK 都是在这些 DICOM 事实上继续往下走。