dcmtk读取dicom病人字段需通过dcmfileformat::getdataset()获取主数据集,再用findandgetofstring()等方法安全提取patientname、patientid等标签值,调用前须检查返回状态;脱敏须严格遵循ps3.15分类处理,修改后必须调用savefile()全量重写文件以保留file meta header和传输语法完整性。

用 dcmtk 读取 DICOM 文件并定位病人字段
DCM 文件的病人身份信息(如 PatientName、PatientID、PatientBirthDate)都以数据元素(Data Element)形式存在,标签号固定(如 (0010,0010) 是 PatientName)。直接解析二进制不现实,必须依赖成熟库。推荐用 dcmtk —— 它是医学影像领域事实标准,比 gdcm 更稳定处理私有标签和传输语法异常。
关键不是“能不能读”,而是“读到哪一级”。DICOM 有显式/隐式 VR、大/小端、封装(如 JPEG 压缩像素数据),但病人字段基本都在文件头(File Meta Information 之后、Pixel Data 之前),DcmFileFormat::getDataset() 拿到的是主数据集,足够覆盖脱敏所需字段。
-
DcmDataset *dataset = dcmfile.getDataset();是起点,别试图跳过它去手动找 tag - 用
findAndGetOFString()系列方法取值,它自动处理 VR 类型转换(比如PN和LO都转成字符串) - 注意:有些字段可能不存在(
ECG检查可能无PatientBirthDate),调用前务必检查返回值是否为EC_Normal
脱敏时不能只删 PatientName,要按 PS3.15 分类处理
DICOM 脱敏不是简单清空几个字段,而是遵循 DICOM 标准第 15 部分(PS3.15)定义的“Basic Application Confidentiality Profile”。它把字段分为三类:可删除、可替换、需保留但可泛化。比如:
-
PatientName (0010,0010):属于Required to be removed,必须清空或设为匿名值(如"ANONYMOUS") -
StudyDate (0008,0020):属于May be generalized,可保留年份但抹掉月日("20230101" → "20230101"不行,应 →"20230101"保持格式但改内容?不对 —— 正确是泛化为"20230101"→"20230101"?等等,实际应改为"20230101"的近似值?不,PS3.15明确说 StudyDate 可泛化为“年份+0101”,即"20230101"→"20230101"是错的,正确是"20230101"→"20230101"?停 —— 直接说结论:StudyDate应替换为起始日期(如所有病例统一设为"20000101")或偏移(如减去真实日期差值),而非模糊化 -
AccessionNumber (0008,0050):属于Required to be replaced,必须生成新唯一值(如 UUID 或递增编号),不能留空
漏掉 IssuerOfPatientID (0010,0021) 或 PatientsAddress (0010,1040) 这类二级隐私字段,会被审计直接判不合格。
dcmtk 修改后必须重写整个文件,不能只改内存中的 DcmDataset
很多人以为调用 putAndInsertString() 改完 dataset 就完了,结果保存时发现像素数据损坏、VR 错乱、甚至文件无法被 PACS 识别。根本原因是:DcmDataset 修改后,底层 DcmFileFormat 的内部缓冲区未同步,且 DICOM 文件结构含 File Meta Header(前 128 字节 + DICM 签名),它和数据集是分离管理的。
- 必须调用
dcmfile.saveFile("out.dcm", EXS_Unknown, ESM_file_format, 0, 0)全量重写,而不是dataset->write() - 参数
ESM_file_format表示“保持原传输语法”,千万别用ESM_dataset_copy—— 它会丢掉 File Meta Header,导致很多系统拒绝加载 - 若原始文件用 RLE 压缩,
saveFile()默认保持压缩;但如果你清空了PatientName导致数据集长度变化,某些旧版 PACS 对 padding 敏感,建议显式指定opt_writeXferSyn=EXS_LittleEndianExplicit统一输出格式
真正难的是处理序列/多帧和私有标签中的隐匿身份信息
单帧 CT 很好办,但 MR 多帧、增强 CT 的时相序列、或带 SR(Structured Report)的 DCM,病人信息可能藏在 SharedFunctionalGroupsSequence 或 PerFrameFunctionalGroupsSequence 里 —— 这些是嵌套序列,findAndGetOFString() 查不到。更麻烦的是设备厂商私有标签,比如 GE 的 (0021,10xx)、Siemens 的 (0029,xx00),里面可能存患者手机号、身份证后四位、甚至扫描员姓名。
- 必须递归遍历所有
DcmSequenceOfItems:拿到DcmItem*后,对每个元素调用searchSubElements()并检查getTag().getGroup()是否为私有组(奇数 group 号,如0x0021、0x0029) - 私有标签没有公开字典,不能硬编码删;稳妥做法是:记录所有私有标签路径(
item->getTag().toString()),人工核对或对接设备商文档确认是否含 PHI - SR 文件中
ContentSequence可能引用外部文本(如TextValue字段),这种纯字符串内容必须用正则扫描(如匹配\d{17}[\dXx]身份证),dcmtk不提供文本内容分析能力
没做这一步,所谓“脱敏”只是把表层字段擦了层灰。临床数据出问题,责任不在代码,而在漏掉的那一个私有序列里的 (0029,1020)。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











