3. DICOM协议与元数据工程

在医学影像数据管线中,DICOM 文件既是像素数据的载体,也是临床语义的容器。一个 CT 序列能否被正确重建、一个检查能否被准确归入某位患者、一次科研检索能否命中目标人群,几乎都取决于对 DICOM 信息模型与元数据的理解深度。本节从信息模型出发,逐层展开 SOP 类、传输语法、私有标签、去标识化标准与元数据抽取入库的工程实现。

一、DICOM 信息模型:从患者到像素的四级结构

DICOM 采用面向对象的信息模型,其核心是“实体—关系”结构。最上层是 Patient(患者),向下依次为 Study(检查)、Series(序列)、Instance(实例,即单个 SOP 实例)。四者通过唯一标识符(UID)串联:PatientID 标识患者,StudyInstanceUID 标识一次检查,SeriesInstanceUID 标识一个序列,SOPInstanceUID 标识一个实例。这种层级关系决定了数据入库时的主键设计——若把 SOPInstanceUID 当作唯一主键而忽略 Series 与 Study 的归属,后续按检查或序列聚合时就会出现重复或错配。

每个层级都携带一组属性。例如 Study 层有 StudyDate、StudyDescription、AccessionNumber;Series 层有 Modality、SeriesNumber、BodyPartExamined;Instance 层有 InstanceNumber、SOPClassUID、Rows、Columns。工程上常说的“元数据抽取”,本质就是按这四级结构把属性映射到关系表或宽表中。

DICOM 信息模型与元数据抽取映射 Patient PatientID / Name / BirthDate Study StudyInstanceUID / Date / Accession Series SeriesInstanceUID / Modality / Number Instance (SOP) SOPInstanceUID / SOPClassUID / Rows 元数据抽取映射示例(关系表字段) 层级 DICOM 标签 目标字段 类型 Patient (0010,0020) PatientID patient_id VARCHAR(64) Study (0020,000D) StudyInstanceUID study_uid CHAR(64) Series (0008,0060) Modality modality CHAR(16)

二、SOP 类与传输语法:决定“怎么读”

SOP 类(Service-Object Pair Class)定义了“对象是什么”以及“能对它做什么”。例如 CT 图像存储的 SOP Class UID 为 1.2.840.10008.5.1.4.1.1.2,MR 为 1.2.840.10008.5.1.4.1.1.4,而增强型 CT 为 1.2.840.10008.5.1.4.1.1.2.1。解析时若只按 Modality 判断,会把增强型与普通型混为一谈,导致多帧数据被误当作单帧处理。

传输语法(Transfer Syntax)决定字节序、VR 编码方式与压缩算法。常见的有隐式 VR 小端(1.2.840.10008.1.2)、显式 VR 小端(1.2.840.10008.1.2.1)、显式 VR 大端(1.2.840.10008.1.2.2),以及 JPEG Baseline、JPEG Lossless、JPEG 2000 等压缩语法。工程上必须显式读取 (0002,0010) TransferSyntaxUID,再据此选择解码器;若用隐式 VR 解析器去读显式 VR 文件,标签长度会错位,后续所有属性都会偏移。

三、私有标签:厂商扩展的陷阱

私有标签位于奇数 Group 号中,由厂商自行定义,如西门子的 (0019,xx)、GE 的 (0009,xx)、飞利浦的 (2001,xx)。它们常携带重建参数、剂量信息、序列描述等关键字段,但缺乏统一语义。处理原则是:先通过 Private Creator 标签(如 (0019,0010))确认厂商,再按厂商字典解析;对无法识别的私有标签,保留原始字节并记录来源,不要强行猜测含义。入库时建议单独建一张私有标签表,以 SOPInstanceUID + 标签路径为键,避免污染主表结构。

四、去标识化:DICOM PS3.15 的工程落地

PS3.15 定义了去标识化配置文件与保密选项。基础配置文件要求移除或替换患者姓名、出生日期、病历号、机构名称、医生姓名等 30 余类属性,同时保留 StudyInstanceUID 等用于关联的 UID(或按需重新生成)。工程实现通常分三步:第一,按标签清单执行“清除/替换/保留”动作;第二,对像素数据中的烧录注释(Burned-in Annotation)进行检测,必要时用 (0028,0301) 标记;第三,对私有标签中的身份信息做二次扫描。

注意:仅替换 PatientName 并不等于完成去标识化。检查描述、序列描述、协议名称、附加患者历史等自由文本字段同样可能包含身份线索,必须纳入清洗范围。

五、元数据抽取入库:从文件到表

抽取流程可概括为“扫描—解析—映射—写入”。以 Python 为例,使用 pydicom 读取文件后,按四级结构提取字段:

import pydicom
ds = pydicom.dcmread(path, stop_before_pixels=True)
record = {
    "patient_id": ds.get("PatientID"),
    "study_uid": ds.get("StudyInstanceUID"),
    "series_uid": ds.get("SeriesInstanceUID"),
    "sop_uid": ds.get("SOPInstanceUID"),
    "modality": ds.get("Modality"),
    "study_date": ds.get("StudyDate"),
    "series_number": ds.get("SeriesNumber"),
    "instance_number": ds.get("InstanceNumber"),
    "rows": ds.get("Rows"),
    "columns": ds.get("Columns"),
    "transfer_syntax": ds.file_meta.TransferSyntaxUID,
}

写入时建议以 SOPInstanceUID 为唯一键做 UPSERT,同时建立 study_uid、series_uid、patient_id 的索引。对于多帧增强型对象,还需额外抽取 NumberOfFrames、FrameIncrementPointer 等字段,否则后续按帧检索会缺失粒度。

入库校验建议:统计每个 Study 下的 Series 数量、每个 Series 下的 Instance 数量,与原始目录结构比对;若出现 Instance 数为 0 或 Series 归属为空,通常意味着 UID 解析失败或文件损坏。

综上,DICOM 元数据工程的核心不是“读文件”,而是建立一套从信息模型到关系表的稳定映射,并在 SOP 类、传输语法、私有标签与去标识化四个维度上保持一致性。只有这四层都处理正确,后续的影像重建、检索与科研分析才有可靠的数据基础。