4. DICOM标签体系与常用属性实战
DICOM 文件本质上是一棵由“标签(Tag)”构成的属性树。每个标签由组号(Group)和元素号(Element)唯一确定,例如 (0010,0010) 表示患者姓名。理解标签体系的关键,不是背下所有标签,而是掌握四层信息结构:患者信息 → 检查信息 → 序列信息 → 图像信息。这四层恰好对应临床上一个患者做一次检查、产生多个序列、每个序列包含多张图像的层级关系。本节围绕这四层,编写脚本提取关键标签并做校验。
一、四层标签的层级关系
DICOM 的层级不是靠目录结构,而是靠标签之间的引用关系建立的。患者层用 PatientID 标识唯一患者;检查层用 StudyInstanceUID 标识一次检查,并通过 PatientID 关联到患者;序列层用 SeriesInstanceUID 标识一个序列,通过 StudyInstanceUID 关联到检查;图像层用 SOPInstanceUID 标识单张图像,通过 SeriesInstanceUID 关联到序列。这条 UID 链是数据工程中做归组、去重、索引的核心依据。
二、用 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
PatientName 作为患者唯一标识。姓名可能重复、拼写不一致或含脱敏字符,唯一标识应使用 PatientID 或 PatientID 与 IssuerOfPatientID 的组合。五、常见问题与判断依据
| 现象 | 可能原因 | 判断依据 |
|---|---|---|
| 同一序列被拆成多组 | SeriesInstanceUID 不一致 | 分组后组数大于预期 |
| 图像顺序错乱 | InstanceNumber 缺失或重复 | 排序后编号不连续 |
| 入库失败 | 必填 UID 为空 | 存在性校验报错 |
| 模态统计异常 | Modality 大小写或空格 | 去重后出现 "CT" 与 "ct" |
实际工程中,建议把上述提取与校验封装成一个函数,对每个文件返回 (info, errors),再汇总成 CSV 或数据库表。这样既能保留原始标签,又能记录校验结果,便于后续排障和复盘。掌握四层标签结构与 UID 关联链,是 DICOM 数据工程中最基础也最关键的一步。