3. DICOM数据元素与编码实战

在PACS工程实践中,DICOM文件并不是“一张图片加一些文字”,而是一棵由数据元素(Data Element)逐层嵌套构成的编码树。要真正读懂一台CT、MR或超声设备导出的DICOM文件,必须掌握四个关键层面:数据元素的三元组结构、VR(Value Representation)类型、传输语法(Transfer Syntax)以及字符集(Specific Character Set)。本节以真实DICOM文件解析为主线,把抽象标准落到可操作的字段与字节上。

一、数据元素:DICOM的最小信息单元

每个数据元素由三部分构成:标签(Tag)、值表示(VR)和值长度(Value Length),随后是值域(Value Field)。标签是4字节无符号数,写成 (组号,元素号) 的十六进制形式,例如患者姓名是 (0010,0010),像素数据是 (7FE0,0010)。组号为偶数的标签属于标准标签,奇数组号通常为私有标签。

数据元素按标签升序排列,形成一条“扁平序列”,但通过SQ(Sequence)类型的元素可以嵌套子数据集,从而表达“一个检查包含多个序列、一个序列包含多帧图像”的层级关系。解析时若只做线性扫描而不处理SQ嵌套,就会漏掉序列内部的采集参数。

二、VR类型:决定值如何被解释

VR规定了值域的编码方式。常见VR包括:PN(患者姓名,如“ZHANG^SAN”)、DA(日期,YYYYMMDD)、TM(时间)、UI(唯一标识符,如SOP Instance UID)、US/SS(无符号/有符号短整型)、UL(无符号长整型)、OB/OW(字节/字流,像素数据常用)、SQ(序列)、CS(代码字符串)、DS/IS(十进制/整数字符串)。

VR的编码方式分两种:显式VR下,VR以两个大写字母直接写在标签之后;隐式VR下,VR不写出,由字典根据标签查表推断。这正是传输语法要解决的问题。

三、传输语法:字节序、VR模式与压缩

传输语法(0002,0010)是一个UID,它同时约定三件事:字节序(小端/大端)、VR模式(显式/隐式)、像素数据是否压缩。工程中最常见的几种:

  • 1.2.840.10008.1.2 —— 隐式VR小端(Implicit VR Little Endian),兼容性最好;
  • 1.2.840.10008.1.2.1 —— 显式VR小端(Explicit VR Little Endian),现代设备默认;
  • 1.2.840.10008.1.2.2 —— 显式VR大端,较少见;
  • 1.2.840.10008.1.2.4.50 —— JPEG基线压缩;
  • 1.2.840.10008.1.2.4.70 —— JPEG无损;
  • 1.2.840.10008.1.2.4.90/91 —— JPEG 2000无损/有损。

解析时若把隐式VR文件按显式VR读取,会把长度字段误读成VR字符,导致后续全部错位。因此先读文件头(0002组)确定传输语法,再切换解析模式,是解析器的第一条铁律。

四、字符集:中文姓名不乱码的关键

字符集由 (0008,0005) Specific Character Set 指定。默认ISO-IR 6(ASCII)只能表示英文;中文常用 ISO 2022 IR 6 与 ISO 2022 IR 87(GB2312)、ISO 2022 IR 149(韩文)、GB18030 或 UTF-8。若解析器忽略该字段,用UTF-8去解GB2312字节,患者姓名就会变成乱码。正确做法是:先读 (0008,0005),再按声明的字符集解码PN、LO、SH等文本VR。

DICOM文件解析链路:从字节到可读属性 文件头 0002组 (0002,0010) Transfer Syntax UID 判定传输语法 字节序 / VR模式 / 压缩 显式 or 隐式 数据元素序列(按Tag升序) Tag + VR + Length + Value SQ 嵌套子数据集 单个数据元素结构(显式VR小端示例) Tag 4字节 (0010,0010) VR 2字节 "PN" Length 2字节 值长度 Value Field ZHANG^SAN(PN,按字符集解码) 患者/检查层属性 (0010,0010) PN 患者姓名 (0010,0020) LO 患者ID (0008,0020) DA 检查日期 (0008,0060) CS 模态 CT/MR (0008,0005) CS 字符集 (0020,000D) UI 检查UID 图像层属性 (0028,0010) US 行数 Rows (0028,0011) US 列数 Columns (0028,0100) US 位深 (0028,1050) DS 窗位 (0028,1051) DS 窗宽 (7FE0,0010) OW 像素数据 解析结果判读 姓名可读 → 字符集正确 UID唯一 → 归档不冲突 Rows×Cols×位深 → 帧大小 窗宽窗位 → 显示映射 传输语法 → 解码方式 SQ嵌套 → 多序列结构

五、实战:用工具解析真实DICOM文件

命令行下最直接的工具是 dcmdump(DCMTK)与 gdcminfo(GDCM)。以一份CT文件为例,先看文件头与传输语法:

$ dcmdump +P 0002,0010 ct_chest.dcm
(0002,0010) UI = 1.2.840.10008.1.2.1   # Explicit VR Little Endian

$ dcmdump +P 0008,0005 ct_chest.dcm
(0008,0005) CS = ISO_IR 100            # Latin-1,中文需另设

$ dcmdump +P 0010,0010 ct_chest.dcm
(0010,0010) PN = ZHANG^SAN

若文件声明为 ISO 2022 IR 87,则姓名可能以GB2312字节存储,此时用Python的pydicom读取需显式指定编码:

import pydicom
ds = pydicom.dcmread("ct_chest.dcm")
print(ds.SpecificCharacterSet)      # ['ISO 2022 IR 87']
print(ds.PatientName)               # 正确解码为中文
print(ds.file_meta.TransferSyntaxUID)
print(ds.Rows, ds.Columns, ds.BitsAllocated)
print(ds.WindowCenter, ds.WindowWidth)

提取关键属性时,建议按“患者—检查—序列—图像”四级组织:患者层取姓名、ID、出生日期;检查层取检查UID、检查日期、模态;序列层取序列UID、序列号、层厚;图像层取SOP Instance UID、行列数、位深、窗宽窗位、像素数据。这样既符合DICOM信息模型,也便于后续入库与检索。

解析前先读 (0002,0010) 确定传输语法,再读 (0008,0005) 确定字符集,最后按标签升序遍历并处理SQ嵌套。这三步顺序不能颠倒,否则极易出现错位或乱码。
隐式VR文件不写VR字段,必须依赖数据字典推断;若字典缺失私有标签,解析器应跳过而非猜测长度,否则会破坏后续所有元素的偏移。

六、结果如何判断

一份解析成功的DICOM文件,应满足:患者姓名可读且与设备界面一致;SOP Instance UID全局唯一;Rows×Columns×BitsAllocated/8 与像素数据长度吻合(多帧需再乘帧数);窗宽窗位为合理数值(如肺窗W=1500、L=-600);传输语法与像素数据实际编码一致。若姓名乱码,先查字符集;若像素长度不符,先查传输语法与是否压缩;若属性缺失,检查是否因SQ未展开而漏读。掌握这条链路,就掌握了PACS数据接入的第一道关口。