4. DICOM标签体系与常用属性实战

DICOM 文件本质上是一棵由“标签(Tag)”构成的属性树。每个标签由组号(Group)和元素号(Element)唯一确定,例如 (0010,0010) 表示患者姓名。理解标签体系的关键,不是背下所有标签,而是掌握四层信息结构:患者信息 → 检查信息 → 序列信息 → 图像信息。这四层恰好对应临床上一个患者做一次检查、产生多个序列、每个序列包含多张图像的层级关系。本节围绕这四层,编写脚本提取关键标签并做校验。

一、四层标签的层级关系

DICOM 的层级不是靠目录结构,而是靠标签之间的引用关系建立的。患者层用 PatientID 标识唯一患者;检查层用 StudyInstanceUID 标识一次检查,并通过 PatientID 关联到患者;序列层用 SeriesInstanceUID 标识一个序列,通过 StudyInstanceUID 关联到检查;图像层用 SOPInstanceUID 标识单张图像,通过 SeriesInstanceUID 关联到序列。这条 UID 链是数据工程中做归组、去重、索引的核心依据。

DICOM 四层标签结构与 UID 关联链 患者层 Patient (0010,0010) PatientName (0010,0020) PatientID (0010,0030) BirthDate 检查层 Study (0020,000D) StudyInstanceUID (0008,0020) StudyDate (0008,1030) StudyDesc 序列层 Series (0020,000E) SeriesInstanceUID (0008,0060) Modality (0020,0011) SeriesNumber 图像层 Instance (0008,0018) SOPInstanceUID (0020,0013) InstanceNumber (0028,0010) Rows PatientID 关联 StudyInstanceUID 关联 SeriesInstanceUID 关联

二、用 pydicom 提取关键标签

下面脚本读取一个 DICOM 文件,按四层结构提取关键标签,并输出为字典。示例中的文件路径和标签值均为教学假设值,实际运行时以真实文件为准。

import pydicom

def extract_key_tags(path):
    ds = pydicom.dcmread(path, stop_before_pixels=True)
    return {
        # 患者层
        "PatientName":   ds.get("PatientName", ""),
        "PatientID":     ds.get("PatientID", ""),
        "PatientBirthDate": ds.get("PatientBirthDate", ""),
        # 检查层
        "StudyInstanceUID": ds.get("StudyInstanceUID", ""),
        "StudyDate":     ds.get("StudyDate", ""),
        "StudyDescription": ds.get("StudyDescription", ""),
        # 序列层
        "SeriesInstanceUID": ds.get("SeriesInstanceUID", ""),
        "Modality":      ds.get("Modality", ""),
        "SeriesNumber":  ds.get("SeriesNumber", ""),
        # 图像层
        "SOPInstanceUID": ds.get("SOPInstanceUID", ""),
        "InstanceNumber": ds.get("InstanceNumber", ""),
        "Rows": ds.get("Rows", ""), "Columns": ds.get("Columns", ""),
    }

info = extract_key_tags("sample.dcm")
for k, v in info.items():
    print(f"{k:20s} = {v}")

使用 stop_before_pixels=True 可以跳过像素数据,只读元数据,速度更快,适合批量扫描。输出示例(假设值):

PatientName          = ZHANG^SAN
PatientID            = P20240001
StudyInstanceUID     = 1.2.840.113619.2.55.3.604688.100.1700000001
Modality             = CT
SeriesNumber         = 3
InstanceNumber       = 45
Rows                 = 512
Columns              = 512

三、关键标签校验规则

提取之后必须校验,否则脏数据会污染后续索引。校验分三类:存在性、格式、一致性。

  • 存在性:PatientID、StudyInstanceUID、SeriesInstanceUID、SOPInstanceUID 为必填,缺失则文件不可入库。
  • 格式:UID 必须符合 DICOM 规范,只含数字和点,长度不超过 64 字节,且不含前导零。可用正则 ^[0-9]+(\.[0-9]+)*$ 校验。
  • 一致性:同一序列内所有图像的 SeriesInstanceUID 必须相同,Modality 必须一致;同一检查内 StudyInstanceUID 必须相同。

校验脚本示例:

import re

UID_RE = re.compile(r"^[0-9]+(\.[0-9]+)*$")

def validate(info):
    errors = []
    for key in ["PatientID", "StudyInstanceUID",
                "SeriesInstanceUID", "SOPInstanceUID"]:
        if not info.get(key):
            errors.append(f"缺失必填标签: {key}")
    for key in ["StudyInstanceUID", "SeriesInstanceUID", "SOPInstanceUID"]:
        uid = info.get(key, "")
        if uid and (not UID_RE.match(uid) or len(uid) > 64):
            errors.append(f"UID 格式非法: {key}={uid}")
    return errors

errs = validate(info)
print("校验通过" if not errs else "\n".join(errs))

四、批量校验与一致性检查

单文件校验只能发现格式问题,一致性必须跨文件比较。思路是:先按 SeriesInstanceUID 分组,再检查每组内 Modality 是否唯一、InstanceNumber 是否重复。

from collections import defaultdict

def check_series(files):
    groups = defaultdict(list)
    for f in files:
        info = extract_key_tags(f)
        groups[info["SeriesInstanceUID"]].append(info)
    report = []
    for uid, items in groups.items():
        mods = {i["Modality"] for i in items}
        nums = [i["InstanceNumber"] for i in items]
        if len(mods) > 1:
            report.append(f"{uid}: Modality 不一致 {mods}")
        if len(nums) != len(set(nums)):
            report.append(f"{uid}: InstanceNumber 重复")
    return report
校验时优先检查 UID 链是否完整。UID 链断裂(如 SeriesInstanceUID 为空)会导致整个序列无法归组,比单个字段格式错误影响更大。
不要用 PatientName 作为患者唯一标识。姓名可能重复、拼写不一致或含脱敏字符,唯一标识应使用 PatientID 或 PatientID 与 IssuerOfPatientID 的组合。

五、常见问题与判断依据

现象可能原因判断依据
同一序列被拆成多组SeriesInstanceUID 不一致分组后组数大于预期
图像顺序错乱InstanceNumber 缺失或重复排序后编号不连续
入库失败必填 UID 为空存在性校验报错
模态统计异常Modality 大小写或空格去重后出现 "CT" 与 "ct"

实际工程中,建议把上述提取与校验封装成一个函数,对每个文件返回 (info, errors),再汇总成 CSV 或数据库表。这样既能保留原始标签,又能记录校验结果,便于后续排障和复盘。掌握四层标签结构与 UID 关联链,是 DICOM 数据工程中最基础也最关键的一步。