解析医疗影像中的dicom文件

一、DICOM文件概述

我们先了解一下DICOM文件是什么,干嘛用的,以及DICOM内部有哪些信息,然后再谈如何去解析这些信息并转换成java对象。

医学影像学概览 医学影像学

这一学科致力于利用X射线、电磁场、超声波等多种介质与人体相互作用原理,将人体内部结构和密度以图像形式生动展现出来。这些可视化的信息为诊断医师提供了关键的决策依据,进而对个体健康状况进行专业评估。医学影像学的研究内容广泛,既包括构建及优化医学成像系统的硬件技术,也涵盖医学图像处理的软件算法。

DICOM标准解读 DICOM(Digital Imaging and Communications in Medicine)

作为国际公认的医学数字成像与通信标准(ISO 12052),在放射医学、心血管造影及多种放射诊疗设备(如X光、CT、PET、超声、MRI等)领域中广泛应用,并逐步渗透到眼科、牙科等其他医疗分支。DICOM格式文件是存储患者受保护健康信息(PHI)的重要载体,涵盖了姓名、性别、年龄等个人数据,以及图像采集设备信息、医疗背景资料等重要参数。目前采用的标准版本为DICOM3.0,每一张图像都蕴含丰富信息,主要分为四个类别:患者信息、检查研究信息、序列信息和图像信息,每一个信息单元通过独特的双字节十六进制标签(Group, Element)标识,如(0010,0010)即代表患者的姓名。
为了方便开发人员高效地处理DICOM数据,业界涌现了一批优秀的第三方库,如基于C++的DCMTK、基于Java的dcm4che以及Python语言环境下的pydicom。它们为开发者屏蔽了底层解析复杂性,极大地提高了项目开发效率。
当前,CT、MRI、超声等先进成像技术通过精准聚焦的射线束、探测器围绕人体特定区域进行连续断层扫描,从而生成多层面图像,经由三维重建技术可叠加成三维图像,而每一层面图像及其相关头部信息均可储存在DICOM文件中。DICOM文件的结构严谨,通常由文件头和数据集合两部分构成,其中文件头包含了识别文件属性的关键信息,且每个DICOM文件必备一个文件头。

深入剖析DICOM内部信息(DICOM Tag与VR)

DICOM数据集构成了DICOM文件的核心部分,其由一系列按特定顺序排列的DICOM数据元素组成。每个数据元素最小单位称为数据元,根据TAG值从小至大排序。数据元由四部分构成:

  • TAG编号: 由4个字节表示,包含2字节组号和2字节元素号,例如"0010 0040"对应患者性别信息,不同组号分别指示设备通讯信息、特性参数、患者信息和图像信息参数等内容。

  • 值表示(VR, Value Representation): 两个字节定义了该数据元的数据类型,如LO(长字符串)、IS(整型字符串)、DA(日期)等共27种不同类型。

  • 值长度(Value Length): 记录该数据项的具体长度。

  • 值域(Value): 实际存储的数据值。

数据元素按照信息类别可分为四大类:患者、检查研究、序列和图像 ,形成一种患者可进行多次检查,每次检查涉及多个部位系列,每个部位系列关联一张或多张影像图像的层级结构。

1.常见的TAG

(1) Patient Tag(患者)

Group Element Tag Description 中文解释 数据类型(VR)
10 10 Patient’s Name 患者姓名 PN
10 20 Patient ID 患者ID LO
10 30 Patient’s Birth Date 患者出生日期 DA
10 32 Patient’s Birth Time 患者出生时间 TM
10 40 Patient’s Sex 患者性别 CS
10 1030 Patient’s Weight 患者体重 DS
10 21C0 Pregnancy Status 怀孕状态 US

(2) Study Tag(患者)

Group Element Tag Description 中文解释 数据类型(VR)
8 50 Accession Number:A RIS generated number that identifies order for the Study. 检查号:RIS的生成序号,用以标识做检查的次序 SH
20 10 Study ID 检查ID SH
20 000D Study Instance UID:Unique identifier for the Study. 检查实例号:不同检查的唯一标识号 UI
8 20 Study Date:Date the Study started. 检查日期:检查开始的日期 DA
8 30 Study Time:Time the Study started. 检查时间:检查开始的时间 TM
8 61 Modalities in Study 一个检查中含有的不同检查类型 CS
8 15 Body Part Examined 检查的部位 CS
8 1030 Study Description 检查的描述 LO
10 1010 Patient’s Age 做检查时刻的患者年龄,而不是此刻患者的真实年龄 AS

(3) Series Tag(序列)

Group Element Tag Description 中文解释 数据类型(VR)
20 11 Series Number:A number that identifies this Series. 序列号:识别不同检查的号码 IS
20 000E Series Instance UID:Unique identifier for the Series. 序列实例号:不同序列的唯一标识号 UI
8 60 Modality 检查模态(MRI/CT/CR/DR) CS
8 103E Series Description 检查描述和说明 LO
8 21 Series Date 检查日期 DA
8 31 Series Time 检查时间 TM
20 32 Image Position (Patient):
The x,y and z coordinates of the upper left hand corner of the image,in mm.
图像位置:
图像的左上角在空间坐标系中的x.y.z坐标,单位是毫米。如果在检查中,则指该序列中第一张影像左上角的坐标。
DS
20 37 Image Orientation (Patient):The direction cosines of the first row and the first column with respect to the patient. 图像方位 DS
18 50 Slice Thickness:Nominal slice thickness,in mm. 层厚 DS
20 1041 Slice Location:Relative position of exposure expressed in mm. 实际的相对位置,单位为mm DS
18 23 MR Acquisition CS
18 15 Body Part Examined 身体部位 CS

(4) Image Tag(图像)

Group Element Tag Description 中文解释 数据类型(VR)
8 8 Image Type:Image identification characteristics. CS
8 18 SOP Instance UID SOP实例UID
8 23 Content Date:
The date the image pixel data creation started.
影像拍摄日期 DA
8 33 Content Time 影像拍摄时间 TM
20 13 Image/Instance Number:A number that identifies this image. 图像码:识别图像的号码 IS
28 2 Samples Per Pixel:Number of samples (planes) in this image. 图像采样率 US
28 4 Photometric Interpretation:
Specifies the intended interpretation of the pixel data.
光度计解释:
对于CT图像,用两个枚举值MONOCHROME1,MONOCHROME2 用来判断图像是否是彩色的;
MONOCHROME 1/2是灰度图,RGB则是真彩色图
CS
28 10 Rows : Number of rows in the image. 图像的总行数,行分辨率 US
28 11 Columns : Number of columns in the image. 图像的总列数,列分辨率 US
28 30 Pixel Spacing:
Physical distance in the patient between the center of each pixel.
像素间距:
像素中心之间的物理间距
DS
28 100 Bits Allocated:
Number of bits allocated for each pixel sample.Each sample shall have the same number of bits allocated.
分配的位数:
存储每一个像素值时分配的位数,每一个样本该值相同
US
28 101 Bits Stored:
Number of bits stored for each pixel sample.Each sample shall have the same number of bits stored.
存储的位数:有12到16列举值
存储每一个像素用的位数,每一个样本该值相同
US
28 102 High Bit:
Most significant bit for pixel sample data.
Each sample shall have the same high bit.
高位 US
28 103 Pixel Representation:
Data representation of the pixel samples.
Each sample shall have the same pixel representation.
Enum:0000H=unsigned integer,0001H=2’ s complement.
像素数据的表现类型:
一个枚举值,分别为十六进制数0000和0001.
0000H = 无符号整型,
0001H = 2的补码
US
28 1050 Window Center 窗位 DS
28 1051 Window Width 窗宽 DS
28 1052 Rescale Intercept:
The value b in relationship between stored values(SV) and the output units.
Output units = m*SV + b.
Required if Modality LUT Sequence(0028, 0030) is not present.
截距:
如果表明不同模态的LUT颜色对应表不存在时,则使用方程:
Units = m*SV + b,计算真实的像素值到呈现像素值,其中截距为表达式中的b
DS
28 1053 Rescale Slope:
m in the equation specified by Rescale Intercept(0028, 1052).
Required if Rescale Intercept is present.
斜率:
该值为表达式中的m
DS
28 1054 Rescale Type:
Specifies the output units of Rescale Slope (0028,1053) and Rescale Intercept (0028,1052).
Enum: US=Unspecified Requried if Photometric Interpretation is MONOCHROME2, and Bits Stored is greater than 1.
This specifies an identity Modality LUT transformation.
输出值的单位:
该值是一个枚举值
LO

2.VR数据类型

VR是DICOM标准中用来描述数据类型的,总共有27个值。

27种数据类型

数据类型(VR) 含义 允许字符 数据长度
CS - Code String代码字符串 开头结尾可以有没有意义的空格的字符串,比如 “CD123_4" 大写字母,0-9,空格以及下划线字符 最多16个字符
SH - Short String短字符串 短字符串,比如:电话号码, ID 等 最多16个字符
LO - Long String长字符串 一个字符串,可能在开头、结尾填有空格。比如 “Introduction to DICOM” 最多64个字符
ST - Short Text短文本 可能包含一个或多个段落的字符串 最多1024个字符
LT - Long Text长文本 可能包含一个或多个段落的字符串,与 LO 相同,但可以更长 最多10240个字符
UT - Unlimited Text无限制文本 包含一个或多个段落的字符串,与 LT 类似 最多(232 -2)个字符
AE - Application Entity应用实体 标识一个设备的名称的字符串,开头和结尾可以有无意义的字符。比如 “MyPCO 1” 最多16个字符
PN - Person Name病人姓名 有插入符号 (^) 作为姓名分隔符的病人姓名。比如“SMITH^JOHN” “Morrison Jones Susan^^^Ph.D,Chief Executive Officer” 最多64个字符
UI - Unique Identifier(UID)唯一标识符 一个用作唯一标识各类项目的包含UID的字符串。比如 “1.2.840.10008.1.1” 0-9和半角句号 (.) 最多64个字符
DA - Date日期 格式为 YYYYMMDD 的字符串;YYYY 代表年;MM 代表月;DD 代表日。比如 “20050822” 表示 2005 年 8 月 22 日 0-9 8个字符
TM - Time
时间
格式为 HHMMSS.FRAC 的字符串。
HH 表示小时(范围"00"-“23”);
MM 表示分钟 (范围"00"-“59”);
而 FRAC 包含秒的小数部分,即百万分之一秒, 比如 “183200.00” 表示下午 6:32
0-9和半角句号 (.) 最多16个字符
DT - Date Time
日期时间
格式为 YYYYMMDDHHMMSS.FFFFFF,串联的日期时间字符串。
字符串的各部分从左至右是:年-YYYY;月-MM;日-DD;小时-HH;分钟-MM;秒-SS;秒的小数-FFFFFF。
比如 “20050812183000.00” 表示 2005 年 8 月 12 日 下午 18 点 30 分 00 秒
0-9,加号,减号和半角句号 最多26个字符
AS - Age String
年龄字符串
符合以下格式的字符串:nnnD,nnnW,nnnM,nnnY;其中 nnn 对于 D 来说表示天数,对于 W 来说表示周数,对于 M 来说表示月数,对于 Y 来说表示岁数。比如 “018M” 表示他的年龄是 18 个月 0-9,D,W,M,Y 4个字符
IS - Integer String整型字符串 表示一个整型数字的字符串,比如 “-1234567” 0-9,加号 (+),减号 (-) 最多12个字符
DS - Decimal String小数字符串 表示定点小数和浮点小数,比如 “12345.67”, “-5.0e3” 0-9, 加号 (+), 减号 (-), 最多16个字符 E, e 和半角句号(.) 最多16个字符
SS - Signed Short有符号短型 符号型二进制整数,长度 16 bits 2个字符
US - Unsigned Short无符号短型 无符号二进制整数,长度 16 bits 2个字符
SL - Signed Long有符号长型 有符号二进制整数 4个字符
UL - Unsigned Long无符号长型 无符号二进制长整数,长度 32 bits 4个字符
AT - Attribute Tag属性标签 16 bits 无符号整数的有序对,数据元素的标签 4个字符
FL - Floating Single单精度浮点型 单精度二进制浮点数 4个字符
FD - Floating Point Double双精度二进制浮点型 双精度二进制浮点数 8个字符
OB - Other Byte String其它字节字符串 字节的字符串("其它"表示没有在VR中定义的内容)
OW - Other Word String其它单词字符串 16 bits(2字节) 单词字符串
OF - Other Float String其它浮点字符串 32 bits(4个字节) 浮点单词字符串
SQ - Sequence Items条目序列 条目的序列
UN - Unknown未知 字节的字符串,其中内容的编码方式是未知的

上面TAG数据均可通过dcm查看工具查看,也能修改:

在这里插入图片描述

二、java解析DICOM的TAG

导入dcm4che3到maven中

   <dependency>
            <groupId>org.dcm4che</groupId>
            <artifactId>dcm4che-core</artifactId>
            <version>3.3.8</version>
        </dependency>

然后上代码:

package com.wanputech.film.util;

import com.alibaba.fastjson.JSON;
import com.wanputech.film.domain.dto.DicomInfo;
import org.apache.commons.lang.StringUtils;
import org.dcm4che3.data.Attributes;
import org.dcm4che3.data.Tag;
import org.dcm4che3.data.VR;
import org.dcm4che3.io.DicomInputStream;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * 解析获取dcm文件信息
 *
 * @author dengjiajin
 * @since 2024/1/10
 */
public class RadiologyParserUtil {

    public static void main(String[] args) throws IOException {
        String dcmPath = "D:\\radiology1\\11151\\1609000006C7_0001_0001.dcm";
        DicomInfo dicomInfo = getDicomInfo(dcmPath);
        System.out.println(JSON.toJSONString(dicomInfo));
    }

    /**
     * 解析获取dcm文件信息
     * @param dcmPath
     * @return
     */
    public static DicomInfo getDicomInfo(String dcmPath) throws IOException {
        File file = new File(dcmPath);
        DicomInputStream dis = new DicomInputStream(file);
        Attributes attrs = dis.readDataset(-1, -1);
        dis.close();
        String specificCharset = attrs.getString(Tag.SpecificCharacterSet);
        if (specificCharset.equals("ISO_IR 192") || specificCharset.equals("GB18030")) {
            // 设置系统属性以影响dcm4che库内部的字符集转换
            System.setProperty("dcm4che.charset", "GB18030");
        } else if (specificCharset.equals("ISO_IR 58") || specificCharset.equals("GBK")) {
            System.setProperty("dcm4che.charset", "GBK");
        }

        String patientName = attrs.getString(Tag.PatientName);
        String patientID = attrs.getString(Tag.PatientID);
        String modality = attrs.getString(Tag.Modality);
        String studyDate = attrs.getString(Tag.StudyDate);
        String studyTime = attrs.getString(Tag.StudyTime);
        String studyInstanceUid = attrs.getString(Tag.StudyInstanceUID);
        String bodyPartExaminedStr = new String(attrs.getBytes(Tag.BodyPartExamined)
                , Charset.forName(attrs.getString(Tag.SpecificCharacterSet)));

        DicomInfo dicomInfo = new DicomInfo();
        dicomInfo.setPatientName(patientName);
        dicomInfo.setPatientId(patientID);
        dicomInfo.setModality(modality);
        dicomInfo.setStudyDateStr(studyDate);
        dicomInfo.setStudyTimeStr(studyTime);
        studyDateTimeStrConverseToDate(dicomInfo);
        dicomInfo.setBodyPartExamined(bodyPartExaminedStr);
        dicomInfo.setAge(parseAge(attrs.getString(Tag.PatientAge)));
        dicomInfo.setSex(parseSex(attrs.getString(Tag.PatientSex)));
        dicomInfo.setStudyInstanceUid(studyInstanceUid);

        return dicomInfo;

    }

    private static String parseSex(String sex) {
        if ("F".equals(sex)){
            return "1";
        }else {
            return "0";
        }
    }

    private static String parseAge(String n) {
        Integer patientAge = null;
        Pattern pattern = Pattern.compile("(\\d+)Y");
        Matcher matcher = pattern.matcher(n);
        if (matcher.find()) {
            patientAge = Integer.valueOf(matcher.group(1));
        }
        Pattern pattern2 = Pattern.compile("(\\d+)M");
        Matcher matcher2 = pattern2.matcher(n);
        if (matcher2.find()) {
            patientAge = Integer.valueOf(matcher2.group(1));
            patientAge = patientAge / 12;
        }
        Pattern pattern3 = Pattern.compile("(\\d+)D");
        Matcher matcher3 = pattern3.matcher(n);
        if (matcher3.find()) {
            patientAge = Integer.valueOf(matcher3.group(1));
            patientAge = patientAge / 365;
        }
        return patientAge.toString();
    }

    private static void studyDateTimeStrConverseToDate(DicomInfo dicomInfo) {
        String studyDateStr = dicomInfo.getStudyDateStr();
        String studyTimeStr = dicomInfo.getStudyTimeStr();
        if (studyDateStr != null && studyTimeStr != null) 
            String dateTimeStr = studyDateStr + " " + studyTimeStr.substring(0, 6); // 去掉微秒部分
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd HHmmss");

            LocalDateTime localDateTime = LocalDateTime.parse(dateTimeStr, formatter);

            ZoneId zoneId = ZoneId.systemDefault(); // 或者指定时区 TimeZone.getDefault().toZoneId()
            ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);
            Date studyTime = Date.from(zonedDateTime.toInstant());
            dicomInfo.setStudyTime(studyTime);
        }
    }

}

解析的代码主要是getDicomInfo这个方法。

这段Java代码主要功能是从DICOM文件中读取相关信息,并将其保存在一个自定义的DicomInfo对象中。

  1. 首先,通过File对象创建一个DicomInputStream,然后使用readDataset方法读取DICOM文件中的数据集,并将其保存在Attributes对象中。

  2. 根据DICOM文件中的SpecificCharacterSet属性值,设置系统属性dcm4che.charset,以影响dcm4che库内部的字符集转换。

  3. 从Attributes对象中获取并保存一些重要的DICOM属性值,如PatientName、PatientID、Modality、StudyDate、StudyTime、StudyInstanceUID和BodyPartExamined等。

  4. 创建一个DicomInfo对象,并将之前获取的DICOM属性值设置到该对象中。调用studyDateTimeStrConverseToDate方法对studyDateTimeStr进行日期时间转换,并将结果保存在DicomInfo对象中。

  5. 调用parseAge和parseSex方法分别解析PatientAge和PatientSex属性值,并将结果保存在DicomInfo对象中。

最终,这段代码将包含DICOM文件信息的DicomInfo对象返回或进行后续处理。

dcm返回的age格式是40Y,08M,24D,这种格式最后的字母分别代表Y(年),M(月),D(天),这里需要特殊处理。

领取dcm4che包、查看dcm文件工具、dcm文件模版。扫码添加我的公众号。


如果您有什么问题、或领取dcm4che包、查看dcm文件工具、dcm文件模版。扫码添加我的公众号留言或添加我的微信留言即可。届时也会分享一些日常工作遇到的难题。

公众号:

在这里插入图片描述

微信号:11141004

posted @ 2024-03-08 16:59  星记事  阅读(711)  评论(0编辑  收藏  举报