[医疗信息化][DICOM教程]1.使用Java的DICOM基础-理解DICOM文件-DICOM Basics using Java – Making Sense of the DICOM File
使用Java的DICOM基础-理解DICOM文件
内容
-
介绍
-
DICOM文件内部的外观
-
这些术语在DICOM中到底意味着什么-SCU,SCP,SOP和IOD?
-
PixelMed Java DICOM工具包-快速概述
-
在我们开始之前…
-
列出要控制台的DICOM文件的各种标签
-
将所有DICOM文件的标签列出到控制台
-
使用DICOM测试工具
-
结论
介绍
这是
我有关DICOM标准的系列文章的
一部分。在我们开始本教程之前,请快速浏览一下我之前的文章
“ DICOM标准简介”
,以简短,快速地介绍该标准。请注意,本教程假定您知道Java(或任何等效的面向对象的语言,如C#或C ++)。
我以为我将以DICOM文件格式的非常高级的介绍开始有关DICOM的编程教程系列。请方便地使用
此链接
的
官方DICOM标准文档,
因为我在本教程中介绍的许多内容都在此处进行了详细说明。
正如您在我的入门DICOM教程中所回忆的那样,DICOM文件通常使用
标签
的概念(将在后面进行解释)像其他图像格式(例如TIFF)一样,在同一文件中包含图像数据和有关患者的数据。但是,以
DICOM词典
的形式存储在DICOM文件中的信息比其他标准结构化和多样化得多。该词典
(请参阅本文档的第23页,
以了解其全面程度),其中包含数千个标签,可帮助我们对信息进行编码,例如何时何地拍摄图像,其所属人物,转介医师甚至诊断信息以及核心图像数据。这样可以最大程度地减少将某些关键医疗信息分配给错误患者的风险。
大多数DICOM文件(有时带有’.dcm’扩展名-参见本文的脚注)通常包含图像数据,有时甚至可能包含多个图像(或在DICOM中通常称为
“帧”
)以启用以下功能:称为
电影循环
,使DICOM观看者可以将整个图像序列可视化为电影。但是,DICOM文件并不一定像大多数人所想的那样是关于图像的,它还可以用于存储其他信息,例如报告,ECG信号甚至音频(我们将在后面介绍)。
“写作是大自然的一种让您知道自己的想法多么草率的方式”〜理查德·金登(Richard Guindon)
在以下各节中,我们将首先了解DICOM文件的基本结构(和语法),该文件使可能在不同操作系统和设备上运行的应用程序可以轻松地相互交换图像和与图像有关的信息。然后,我们将使用免费提供但功能非常强大的DICOM工具箱,称为
PixelMed Java DICOM工具箱。
来解析实际的DICOM文件,并查看其中的内容与我们所涵盖的概念有关,从而有助于加深我们对所涵盖概念的理解。请注意,DICOM文件比我在这里讨论的要多得多,但是对于像我这样的大多数使用工具箱或库来处理DICOM文件的开发人员来说,本教程和后面的其他信息应包括足以快速启动并运行。但是,如果您有兴趣从头开始编写自己的DICOM解析器,则应
详细阅读本文档。
DICOM文件内部的外观
每个DICOM文件都由三个主要部分组成,现在我们来看一下每个部分在整体方案中所扮演的角色。
第一部分,
文件头
,由128字节的文件前导码和4字节的前缀组成。这种方法在您可能已经看到/使用过的许多其他图像标准(例如TIFF)中非常普遍。4字节前缀由大写字符“ DICM”组成(注意,它不是“ DICOM”,而是“ DICM”)。该标准不关心前导码应如何构造以及应在其中存储什么。根据我的理解,使用文件序言是为了确保处理应用程序像处理其他几种现有图像文件格式一样处理DICOM文件的兼容性或一致性。该标准并不关心您在其中存储的内容或使用方式。
因此,从理论上讲,如果您选择解析DICOM文件,则您的应用程序可以完全跳过此数据。
在查看DICOM文件的下一部分之前,需要先谈谈
传输语法
的概念及其在DICOM标准中的作用。如果您从我以前的教程中回想起,即使设备运行在不同的操作系统上,DICOM标准也使设备之间能够相互传输信息。不同的操作系统和设备在存储二进制数据时遵循不同的格式来存储数据,例如
字节顺序
。由于需要大量网络来交换由CT或MR等扫描方式生成的大图像,因此该标准还规定在必要时/在使用压缩时交换图像数据。在
我的DICOM入门教程中
,我们还了解了隐式和显式
VR编码
也是如此。必须首先理解并达成所有三个标准(VR编码的类型,字节排序的类型和所使用的压缩),以确保两个交换信息的DICOM系统在它们之间的任何通信期间都能相互理解。传输语法是一组编码规则,可通过使用UID来帮助指定此条件,我们
在DICOM入门教程中
也了解到。例如,隐式VR小端(由UID值1.2.840.10008.1.2表示),显式VR小端(值-1.2.840.10008.1.2.1),显式VR大端(值-1.2.840.10008 .1.2.2)和JPEG无损(值-1.2.840.10008.1.2.4.57)是DICOM处理可用的一些传输语法。在我的DICOM系列的后续教程中讨论图像像素数据处理时,我将更详细地介绍这些内容。
既然我们已经了解了传输语法的简要含义(还有更多内容,包括
演示上下文
,我将在以后的教程中介绍),让我们看一下文件的下一部分,即
文件元信息标头
。本节紧随文件头之后,由一个数据集组成,该数据集由一系列标记信息(称为
“ Dicom元素”
)组成,该序列指定诸如传输语法(如上所解释)之类的详细信息以及有关设备或实现的其他信息创建此文件的对象以及为谁创建此信息的对象(接收应用程序)。有关详细信息,请参见
本文档的第32页
。
本节之后的内容是DICOM文件的第三部分也是最后一部分,它是
数据对象
。DICOM文件的此部分也以数据集的形式指定,该数据集由一系列标签组成,这些标签又可以嵌套并自身携带其他子标签。这些标签有助于携带有关SOP实例的信息(请参阅
我的入门教程
对于SOP意味着什么),例如研究,系列,其所属的患者以及其他有关图像的细节,例如图像像素数据,扫描位置数据等。研究,系列和患者信息通常用于在大多数PACS系统中为图像建立索引,以便更快地检索数据。我的下面的插图有望提供整个文件结构的概要,还显示了各个DICOM元素(每个元素包括标签和相关信息)如何成为整个结构的一部分。
例如,在上面的插图中所示的数据对象部分中的三个DICOM元素的第一个中,“(0008,0070)”表示属于
组号0008
的标签
,其属性号为0070
,“ LO”表示数据类型或
值表示(VR)
,DICOM称之为它(LO表示
长字符串
数据类型),“ PHILIPS”是标记的实际值,“#8”有助于指定值8的长度(请注意,DICOM始终使用偶数个字符对文本进行编码,因此即使
PHILIPS
值只有7个字符长,也会使用一个额外的填充字符-稍后将对此进行详细说明,1表示
值的多重性
此处(可以重复一些数据),“制造商”是DICOM词典中指定的实际标签名称。组合的组和属性编号,VR,值,值多重性和标签名称称为
DICOM元素
。自DICOM字典以来
(请参阅第23页及以后)
隐式定义与每个标签关联的VR,VR是多余的,有时会被省略。尽管如此,一种常见的做法和建议是在将DICOM对象序列化为文件或在网络上交换DICOM信息时明确指定VR。当我在以后的教程中更详细地讨论传输语法时,将更详细地介绍隐式传输语法(省略VR)和显式传输语法(其中VR与标记一起指定)。现在,我们可以更好地了解什么是
IOD(信息对象定义)
。让我们继续。
这些术语在DICOM中到底意味着什么-SCU,SCP,SOP和IOD?
DICOM定义了服务和服务使用或对其进行操作的数据的概念。服务的示例可以是CT存储服务,它负责将从CT模态生成的图像存储到PACS服务器。服务分为两部分,服务的使用者也称为
服务类用户
或
SCU
,服务的提供者也称为
服务类提供者
或
SCP
。例如,在CT存储操作中,生成图像的模态用作
C存储SCU
并将要存储的数据传输到PACS服务器播放的C-Store SCP。在DICOM标准中,服务类和那些服务所涉及的对象的组合称为
服务对象对
或
SOP
。SOP的抽象定义称为
SOP类
,它们由唯一的标识符(称为
UID
)定义,我稍后将介绍(请参阅此链接以获取SOP列表)。因此,由1.2.840.10008.5.1.4.1.1.2的SOP类UID标识的SOP CT图像存储可帮助识别这是CT图像存储操作。在所涉及的机器之间进行此操作期间,会交换命令(称为
DIMSE
(我将在后面介绍)以及一些数据,其中包括图像像素信息以及其他识别信息,例如患者,研究,系列和设备信息。这些操作的具体细节一起被称为
SOP实例
。这些SOP实例中的每个实例也由唯一标识符标识,但由负责传输它们的应用程序生成。这些标识符称为
SOP实例UID
。此SOP涉及的实际数据由
IOD(信息对象定义)
定义,该
IOD
指定了要成功完成处理需要呈现哪些DICOM模块(模块本质上是DICOM元素组)。
IOD对象本身分为称为
信息实体
(缩写为IE)的子组,而信息实体又分为
信息模块的
小组。信息模块由我们已经看到的一系列
DICOM元素组成
。DICOM定义了有关强制性模块,有条件性模块和可选模块的规则。IOD本身分为
归一化IOD
和
复合IOD
。标准化IOD仅表示与一个实体有关的数据,而复合IOD表示来自彼此相关的各种实体的混合物的数据,如下图所示。总之,这本质上是DICOM信息模型的整体结构。综上所述,您现在将看到,到目前为止,我们处理的任何DICOM文件实际上都是IOD(信息的序列化版本)的实例,该IOD还在任何成像工作流程中在两台机器之间传输。在我们正在研究的情况下,该操作有助于将CT模态生成的图像存储到PACS服务器上。DICOM IOD和编码要比这里介绍的更多,但是在讨论创建DICOM文件和目录时,我们将处理这些领域。我不想让你无聊
PixelMed Java DICOM工具包-快速概述
为了说明我计划在本教程系列中涵盖的DICOM的许多方面,我将使用一个称为
PixelMed Java DICOM Toolkit
的免费可用且功能强大的DICOM工具
包
。这是一个完全独立的DICOM工具包,为DICOM文件和目录处理,图像查看以及与DICOM网络相关的操作提供功能。该工具包对于商业或非营利性用途都是完全免费的。它有
充分的文档记录
,还
为用户
提供了一个
小型讨论论坛和一个邮件列表
。该工具包中包含的功能列表非常全面。请记住,在我的教程中使用此工具包绝不表示我对实现生产应用程序的官方认可。每种情况都是独特的,只有您最终处于最佳位置才能做出决定。本文也不是该工具包的教程,我在这里的重点只是将DICOM理论与实际(尽管简单)的实现方式联系起来。因此,如果您的目标是学习如何使用PixelMed库,我鼓励您访问
其网站
或查看
讨论论坛
或
StackOverflow讨论页面
以寻求帮助。
在我们开始之前…
与之前的编程示例非常相似,我将使用最简单的最少代码和方法来帮助说明本教程中介绍的概念。这意味着我在这里编写的代码最适合于简单地显示我试图解释的概念,并且不一定是在现实生活中和生产应用程序中部署的最有效的代码。
首先,您需要在计算机上配置一些东西,包括Java开发环境以及PixelMed工具包,然后才能运行示例(如果您想自己尝试一下)。
-
从此处
下载并安装Eclipse Java IDE (或使用您喜欢的任何其他IDE) -
从
此处
下载PixelMed工具包库 -
确保Java项目的类路径中包含PixelMed.jar库(某些示例可能需要其他
运行时依赖项,
例如可以在PixelMed软件下载中找到的JAI Image IO工具。寻找一个名为pixelmedjavadicom_dependencyrelease.YYYYMMDD.tar的tar压缩文件。 .bz2或类似名称) -
您可以
在GitHub上找到
本教程中使用的源代码 -
您可以下载更多的DICOM图像
从这个网站
,如果你想和
列出要控制台的DICOM文件的各种标签
使用PixelMed工具包读取和提取DICOM标签信息非常简单。此操作仅需要两个类,即
AttributeList
和
Attribute
。该
AttributeList中的
类提供了读取(和写入)整个DICOM对象从文件或流的属性列表的方法。此类的构造函数采用您要处理的DICOM文件的路径。另一方面,
Attribute
类负责读取和写入DICOM属性。下面显示了使用这些类来显示我们感兴趣的DICOM元素。
package com.saravanansubramanian.dicom.pixelmedtutorial; import com.pixelmed.dicom.Attribute; import com.pixelmed.dicom.AttributeList; import com.pixelmed.dicom.AttributeTag; import com.pixelmed.dicom.TagFromName; public class DumpDicomTagsToConsole { private static AttributeList list = new AttributeList(); public static void main(String[] args) { String dicomFile = "D:\\JavaProjects\\Sample Images\\MR-MONO2-16-head"; try { list.read(dicomFile); System.out.println("Study Instance UID:" + getTagInformation(TagFromName.StudyInstanceUID)); System.out.println("Series Instance UID:" + getTagInformation(TagFromName.SeriesInstanceUID)); System.out.println("SOP Class UID:" + getTagInformation(TagFromName.SOPClassUID)); System.out.println("SOP Instance UID:" + getTagInformation(TagFromName.SOPInstanceUID)); System.out.println("Transfer Syntax UID:" + getTagInformation(TagFromName.TransferSyntaxUID)); } catch (Exception e) { e.printStackTrace(); //in real life, do something about this exception } } private static String getTagInformation(AttributeTag attrTag) { return Attribute.getDelimitedStringValuesOrEmptyString(list, attrTag); } }
运行以上代码的输出如下所示:
Study Instance UID:1.2.840.113619.2.1.3352.2053053415.834484316
Series Instance UID:1.2.840.113619.2.1.3352.1136944889.4.834485379
SOP Class UID:1.2.840.10008.5.1.4.1.1.4
SOP Instance UID:1.2.840.113619.2.1.3352.1015047400.4.3.834485381
Transfer Syntax UID:1.2.840.10008.1.2
将所有DICOM文件的标签列出到控制台
列出DICOM文件的全部属性很好,但是有时候,您只是想只显示DICOM文件中包含的特定标签,包括组和属性号,值表示(VR),值,值长度,值多重性和我在本教程前面介绍的标签名称信息。通过使用PixelMed库中包含的
AttributeList
类的
toString
方法,可以轻松实现此过程。该操作的代码说明如下所示。
package com.saravanansubramanian.dicom.pixelmedtutorial; import com.pixelmed.dicom.AttributeList; public class DumpDicomFileContentsToConsole { public static void main(String[] args) { String dicomFile = "D:\\JavaProjects\\Sample Images\\MR-MONO2-16-head"; try { AttributeList list = new AttributeList(); list.read(dicomFile); System.out.println(list.toString()); } catch (Exception e) { e.printStackTrace(); } } }
运行以上代码的输出如下所示:
(0x0002,0x0000) FileMetaInformationGroupLength VR=<UL> VL=<0x4> [0xba]
(0x0002,0x0001) FileMetaInformationVersion VR=<OB> VL=<0x2> []
(0x0002,0x0010) TransferSyntaxUID VR=<UI> VL=<0x12> <1.2.840.10008.1.2 >
(0x0002,0x0012) ImplementationClassUID VR=<UI> VL=<0x18> <1.2.276.0.7230010.3.1.2 >
(0x0002,0x0013) ImplementationVersionName VR=<SH> VL=<0x10> <OFFIS-DCMTK-301 >
(0x0008,0x0000) VR=<UN> VL=<0x4> [B@681a9515
(0x0008,0x0008) ImageType VR=<CS> VL=<0x10> <ORIGINAL\PRIMARY>
(0x0008,0x0016) SOPClassUID VR=<UI> VL=<0x1a> <1.2.840.10008.5.1.4.1.1.4 >
(0x0008,0x0018) SOPInstanceUID VR=<UI> VL=<0x30> <1.2.840.113619.2.1.3352.1015047400.4.3.834485381>
(0x0008,0x0020) StudyDate VR=<DA> VL=<0xa> <1996.06.11>
(0x0008,0x0021) SeriesDate VR=<DA> VL=<0xa> <1996.06.11>
(0x0008,0x0023) ContentDate VR=<DA> VL=<0xa> <1996.06.11>
(0x0008,0x0030) StudyTime VR=<TM> VL=<0x8> <09:11:56>
(0x0008,0x0031) SeriesTime VR=<TM> VL=<0x8> <09:29:39>
(0x0008,0x0033) ContentTime VR=<TM> VL=<0x8> <09:29:41>
(0x0008,0x0060) Modality VR=<CS> VL=<0x2> <MR>
(0x0008,0x0070) Manufacturer VR=<LO> VL=<0x12> <GE MEDICAL SYSTEMS>
(0x0008,0x0080) InstitutionName VR=<LO> VL=<0x20> <PALO ALTO MEDICAL FOUNDATION MRI>
(0x0008,0x0090) ReferringPhysicianName VR=<PN> VL=<0xa> <Anonymized>
(0x0008,0x1010) StationName VR=<SH> VL=<0x8> <MROCOC0 >
(0x0008,0x1030) StudyDescription VR=<LO> VL=<0x4> <HEAD>
(0x0008,0x103e) SeriesDescription VR=<LO> VL=<0x6> <FLAIR >
(0x0008,0x1060) NameOfPhysiciansReadingStudy VR=<PN> VL=<0xa> <Anonymized>
(0x0008,0x1070) OperatorsName VR=<PN> VL=<0xa> <Anonymized>
(0x0008,0x1090) ManufacturerModelName VR=<LO> VL=<0xe> <GENESIS_SIGNA >
(0x0010,0x0000) VR=<UN> VL=<0x4> [B@681a9515
(0x0010,0x0010) PatientName VR=<PN> VL=<0xa> <Anonymized>
(0x0018,0x0000) VR=<UN> VL=<0x4> [B@681a9515
(0x0018,0x0010) ContrastBolusAgent VR=<LO> VL=<0x4> <NONE>
(0x0018,0x0020) ScanningSequence VR=<CS> VL=<0x2> <RM>
(0x0018,0x0021) SequenceVariant VR=<CS> VL=<0x4> <NONE>
(0x0018,0x0022) ScanOptions VR=<CS> VL=<0x16> <GRAPH_GEMS\FC\EDR_GEMS>
(0x0018,0x0023) MRAcquisitionType VR=<CS> VL=<0x2> <2D>
(0x0018,0x0024) SequenceName VR=<SH> VL=<0x6> <flair >
(0x0018,0x0025) AngioFlag VR=<CS> VL=<0x2> <Y >
(0x0018,0x0050) SliceThickness VR=<DS> VL=<0x8> <5.000000>
(0x0018,0x0080) RepetitionTime VR=<DS> VL=<0xc> <10002.000000>
(0x0018,0x0081) EchoTime VR=<DS> VL=<0xa> <159.500000>
(0x0018,0x0083) NumberOfAverages VR=<DS> VL=<0x8> <1.000000>
(0x0018,0x0084) ImagingFrequency VR=<DS> VL=<0xe> <63.8609400000 >
(0x0018,0x0085) ImagedNucleus VR=<SH> VL=<0x2> <H1>
(0x0018,0x0086) EchoNumbers VR=<IS> VL=<0x2> <1 >
(0x0018,0x0087) MagneticFieldStrength VR=<DS> VL=<0x6> <15000 >
(0x0018,0x0088) SpacingBetweenSlices VR=<DS> VL=<0x8> <6.000000>
(0x0018,0x0091) EchoTrainLength VR=<IS> VL=<0x2> <0 >
(0x0018,0x0093) PercentSampling VR=<DS> VL=<0xa> <100.000000>
(0x0018,0x0094) PercentPhaseFieldOfView VR=<DS> VL=<0xa> <100.000000>
(0x0018,0x0095) PixelBandwidth VR=<DS> VL=<0xa> <167.187500>
(0x0018,0x1020) SoftwareVersions VR=<LO> VL=<0x2> <04>
(0x0018,0x1050) SpatialResolution VR=<DS> VL=<0x12> <1.145833\0.859375 >
(0x0018,0x1088) HeartRate VR=<IS> VL=<0x2> <0 >
(0x0018,0x1090) CardiacNumberOfImages VR=<IS> VL=<0x2> <0 >
(0x0018,0x1094) TriggerWindow VR=<IS> VL=<0x2> <0 >
(0x0018,0x1100) ReconstructionDiameter VR=<DS> VL=<0xe> <220.0000000000>
(0x0018,0x1250) ReceiveCoilName VR=<SH> VL=<0x4> <HEAD>
(0x0018,0x1251) TransmitCoilName VR=<SH> VL=<0x4> <HEAD>
(0x0018,0x1312) InPlanePhaseEncodingDirection VR=<CS> VL=<0x4> <COL >
(0x0018,0x1314) FlipAngle VR=<DS> VL=<0x2> <90>
(0x0018,0x1315) VariableFlipAngleFlag VR=<CS> VL=<0x2> <N >
(0x0018,0x1316) SAR VR=<DS> VL=<0x8> <0.015446>
(0x0018,0x5100) PatientPosition VR=<CS> VL=<0x4> <HFS >
(0x0020,0x0000) VR=<UN> VL=<0x4> [B@681a9515
(0x0020,0x000d) StudyInstanceUID VR=<UI> VL=<0x2c> <1.2.840.113619.2.1.3352.2053053415.834484316>
(0x0020,0x000e) SeriesInstanceUID VR=<UI> VL=<0x2e> <1.2.840.113619.2.1.3352.1136944889.4.834485379>
(0x0020,0x0011) SeriesNumber VR=<IS> VL=<0x2> <4 >
(0x0020,0x0012) AcquisitionNumber VR=<IS> VL=<0x2> <1 >
(0x0020,0x0013) InstanceNumber VR=<IS> VL=<0x2> <3 >
(0x0020,0x0032) ImagePositionPatient VR=<DS> VL=<0x24> <-110.000000\ -109.800003\-47.500000 >
(0x0020,0x1040) PositionReferenceIndicator VR=<LO> VL=<0x2> <NA>
(0x0020,0x1041) SliceLocation VR=<DS> VL=<0xe> <-47.5000000000>
(0x0028,0x0000) VR=<UN> VL=<0x4> [B@681a9515
(0x0028,0x0002) SamplesPerPixel VR=<US> VL=<0x2> [0x1]
(0x0028,0x0004) PhotometricInterpretation VR=<CS> VL=<0xc> <MONOCHROME2 >
(0x0028,0x0010) Rows VR=<US> VL=<0x2> [0x100]
(0x0028,0x0011) Columns VR=<US> VL=<0x2> [0x100]
(0x0028,0x0030) PixelSpacing VR=<DS> VL=<0x12> <0.859375\0.859375 >
(0x0028,0x0100) BitsAllocated VR=<US> VL=<0x2> [0x10]
(0x0028,0x0101) BitsStored VR=<US> VL=<0x2> [0x10]
(0x0028,0x0102) HighBit VR=<US> VL=<0x2> [0xf]
(0x0028,0x0103) PixelRepresentation VR=<US> VL=<0x2> [0x1]
(0x7fe0,0x0000) VR=<UN> VL=<0x4> [B@681a9515
(0x7fe0,0x0010) PixelData VR=<OW> VL=<0x20000>
使用DICOM测试工具
如果您想深入研究这一领域,可以使用许多有用的DICOM测试工具之一。我过去使用过并喜欢的一个是
DCMTK
。该工具包附带许多独立的测试实用程序,可帮助您通过命令行界面测试与DICOM处理相关的各个方面。在示例DICOM文件上运行
dcmdump
命令将导致输出,如下所示。
c:\DicomTestingTools\DCMTK-3.6.3-Win64-Dynamic\bin>dcmdump.exe sampledicomfile.dcm
# Dicom-File-Format
# Dicom-Meta-Information-Header
# Used TransferSyntax: Little Endian Explicit
(0002,0000) UL 184 # 4, 1 FileMetaInformationGroupLength
(0002,0001) OB 00\01 # 2, 1 FileMetaInformationVersion
(0002,0002) UI =OphthalmicPhotography8BitImageStorage # 32, 1 MediaStorageSOPClassUID
(0002,0003) UI [2.25.101703809854595919801950834747690813074] # 44, 1 MediaStorageSOPInstanceUID
(0002,0010) UI =LittleEndianExplicit # 20, 1 TransferSyntaxUID
(0002,0012) UI [1.3.6.1.4.1.30071.8] # 20, 1 ImplementationClassUID
(0002,0013) SH [fo-dicom 4.0.1] # 14, 1 ImplementationVersionName
# Dicom-Data-Set
# Used TransferSyntax: Little Endian Explicit
(0008,0008) CS [ORIGINAL\PRIMARY] # 16, 2 ImageType
(0008,0016) UI =OphthalmicPhotography8BitImageStorage # 32, 1 SOPClassUID
(0008,0018) UI [2.25.101703809854595919801950834747690813074] # 44, 1 SOPInstanceUID
(0008,0020) DA [20200501] # 8, 1 StudyDate
(0008,002a) DT [20200501071652] # 14, 1 AcquisitionDateTime
(0008,0030) TM [071652] # 6, 1 StudyTime
(0008,0050) SH [125] # 4, 1 AccessionNumber
(0008,0060) CS [US] # 2, 1 Modality
(0008,0061) CS [OP] # 2, 1 ModalitiesInStudy
(0008,0070) LO [Medicolle] # 10, 1 Manufacturer
(0008,0080) LO [Test Hospital] # 14, 1 InstitutionName
(0008,0081) ST (no value available) # 0, 0 InstitutionAddress
(0008,0090) PN [Fergusson^^^Dr] # 14, 1 ReferringPhysicianName
(0008,1010) SH [AdvancedCapture] # 16, 1 StationName
(0008,1030) LO (no value available) # 0, 0 StudyDescription
(0008,1090) LO [AdvancedCapture] # 16, 1 ManufacturerModelName
(0010,0010) PN [Bowen^William^^Dr] # 18, 1 PatientName
(0010,0020) LO [PAT004] # 6, 1 PatientID
(0010,0030) DA [19560807] # 8, 1 PatientBirthDate
(0010,0040) CS [M] # 2, 1 PatientSex
(0010,1030) DS (no value available) # 0, 0 PatientWeight
(0018,1000) LO (no value available) # 0, 0 DeviceSerialNumber
(0018,1020) LO [AdvancedCapture 3.0.7425.23917] # 30, 1 SoftwareVersions
(0018,106a) CS [NO TRIGGER] # 10, 1 SynchronizationTrigger
(0018,1800) CS [N] # 2, 1 AcquisitionTimeSynchronized
(0020,000d) UI [1.2.826.0.1.3680043.11.106] # 26, 1 StudyInstanceUID
(0020,000e) UI [2.25.103253814587692535336175589446854494919] # 44, 1 SeriesInstanceUID
(0020,0010) SH [1] # 2, 1 StudyID
(0020,0011) IS [1] # 2, 1 SeriesNumber
(0020,0013) IS [1] # 2, 1 InstanceNumber
(0020,0020) CS [F\A] # 4, 2 PatientOrientation
(0020,0062) CS [R] # 2, 1 ImageLaterality
(0020,0200) UI =UniversalCoordinatedTimeSynchronizationFrameOfReference # 20, 1 SynchronizationFrameOfReferenceUID
(0020,1206) IS [1] # 2, 1 NumberOfStudyRelatedSeries
(0020,1208) IS [1] # 2, 1 NumberOfStudyRelatedInstances
(0020,1209) IS [1] # 2, 1 NumberOfSeriesRelatedInstances
(0022,000a) FL 0 # 4, 1 EmmetropicMagnification
(0022,000c) FL 0 # 4, 1 HorizontalFieldOfView
(0022,000e) FL 0 # 4, 1 DegreeOfDilation
(0022,0015) SQ (Sequence with undefined length #=1) # u/l, 1 AcquisitionDeviceTypeCodeSequence
(fffe,e000) na (Item with undefined length #=1) # u/l, 1 Item
(0008,0104) LO (no value available) # 0, 0 CodeMeaning
(fffe,e00d) na (ItemDelimitationItem) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem) # 0, 0 SequenceDelimitationItem
(0022,0016) SQ (Sequence with undefined length #=1) # u/l, 1 IlluminationTypeCodeSequence
(fffe,e000) na (Item with undefined length #=0) # u/l, 1 Item
(fffe,e00d) na (ItemDelimitationItem) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem) # 0, 0 SequenceDelimitationItem
(0022,0019) SQ (Sequence with undefined length #=1) # u/l, 1 LensesCodeSequence
(fffe,e000) na (Item with undefined length #=0) # u/l, 1 Item
(fffe,e00d) na (ItemDelimitationItem) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem) # 0, 0 SequenceDelimitationItem
(0028,0002) US 3 # 2, 1 SamplesPerPixel
(0028,0004) CS [RGB] # 4, 1 PhotometricInterpretation
(0028,0006) US 0 # 2, 1 PlanarConfiguration
(0028,0008) IS [1] # 2, 1 NumberOfFrames
(0028,0010) US 1920 # 2, 1 Rows
(0028,0011) US 1920 # 2, 1 Columns
(0028,0100) US 8 # 2, 1 BitsAllocated
(0028,0101) US 8 # 2, 1 BitsStored
(0028,0102) US 7 # 2, 1 HighBit
(0028,0103) US 0 # 2, 1 PixelRepresentation
(0040,0555) SQ (Sequence with undefined length #=1) # u/l, 1 AcquisitionContextSequence
(fffe,e000) na (Item with undefined length #=0) # u/l, 1 Item
(fffe,e00d) na (ItemDelimitationItem) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem) # 0, 0 SequenceDelimitationItem
(7fe0,0010) OB 00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00... # 11059200, 1 PixelData
c:\DicomTestingTools\DCMTK-3.6.3-Win64-Dynamic\bin>
结论
这就对了。这就是对DICOM文件进行一些基本处理的全部。希望本入门教程可以帮助您了解DICOM文件中包含的内容。我没有涉及如何提取存储在我们处理过的DICOM文件中的图像像素数据。我希望在不久的将来在单独的教程中进行介绍。但是,在我的下一个DICOM教程中,我将向您展示如何使用图像以及一些相关信息从头开始编码/创建DICOM文件。如果您对本教程有任何疑问或意见,请随时给我发送电子邮件。请注意,由于工作和其他承诺,我可能不会立即与您联系。
脚注:
DICOM标准将包含在其中的文件名/标识符限制为8个字符(仅大写字母字符和数字),以符合传统/历史要求。它还指出,不得从这些名称推断/提取任何信息。当文件名存储为CD或DVD等媒体的一部分时,文件扩展名通常没有.dcm扩展名。我使用更长的名称来防止这些细节现在分散注意力,但是我仍然想提及标准在这里声明的内容,以免造成混淆。
DICOM Basics using Java – Making Sense of the DICOM File
CONTENTS
-
Introduction
-
A Look inside the DICOM File
-
What do these terms really mean in DICOM – SCU, SCP, SOP and IOD?
-
The PixelMed Java DICOM Toolkit – Quick Overview
-
Before We Get Started…
-
Listing Various Tags of a DICOM file to Console
-
Listing All Tags of a DICOM file to Console
-
Using DICOM Testing Tools
-
Conclusion
Introduction
This is part of
my series of articles on the DICOM standard
. Before we get started on this tutorial, have a quick look at my earlier article titled
“Introduction to the DICOM Standard”
for a short and quick introduction to the standard. Please note that this tutorial assumes that you know Java (or any equivalent object-oriented language such as C# or C++).
I thought I will start this programming tutorial series on DICOM with an very high-level introduction to the DICOM file format. Please keep the
official DICOM standard document at this link
handy as many things I cover in this tutorial are explained in more detail there.
As you recall from my introductory DICOM tutorial, a DICOM file often contains image data and data about the patient in the same file using the concept of
tags
(to be explained later) like other image formats such as TIFF. However, the information stored in the DICOM file is far more structured and diverse than other standards in the form of a
DICOM Dictionary
. This dictionary
(see page 23 of this document)
to get an idea how comprehensive it is) which contains several thousand of these tags helps us encode information such as when and where the image was taken, who it belongs to, referring physician and even diagnosis information along with core image data. This minimizes the risk of attributing some critical healthcare information to a wrong patient.
Most DICOM files (sometimes seen with a ‘.dcm’ extension – see footnote of this article) usually contain image data, and may sometimes even contain multiple images (or
“frames”
as they are often referred to in DICOM) to enable what is called a
cine-loop
which allows a DICOM viewer to visualize the entire sequence of images as a movie. However, DICOM files don’t necessarily have to be about images as most people assume and may be used to store other information such as reports, ECG signals and even audio (which we will cover later) as well.
“Writing is nature’s way of letting you know how sloppy your thinking is” ~ Richard Guindon
In the sections that follow below, we will first understand the basic structure (and syntax) of the DICOM file which permits applications that may be running on different operating systems and devices to exchange image and image-related information with one another easily. We will then use a freely available but extremely powerful DICOM toolkit called
PixelMed Java DICOM toolkit
to parse an actual DICOM file and see the content inside it relating back to the concepts we covered to help reinforce our understanding of the concepts covered. Please note that there is far more to the DICOM file than what I cover here, but for most developers like me who deal with processing of DICOM files using a toolkit or library, the information provided in this tutorial and others that follow should be more than sufficient to be up and running quickly. However, if you are interested in writing your own DICOM parser from scratch, you should
read this document in detail.
A Look inside the DICOM File
Every DICOM file consists of three major parts, and we will now look at the role that each part plays in the overall scheme of things.
The first part, the
file header
, consists of a 128-byte file preamble followed by a 4-byte prefix. This approach is very common in many other image standards such as TIFF that you may have already seen/used. The 4-byte prefix consists of the uppercase characters ‘DICM’ (note, it is not “DICOM”, but “DICM”). The standard does not care how the preamble should be structured and what should be stored in it. The use of the file preamble from my understanding is to simply ensure compatibility or consistency for the processing application to deal with DICOM files just like several other existing image file formats. The standard does not care about what you store in it or how you use it.
So, in theory, your application could completely skip over this data when parsing the DICOM file if you chose to.
Before we look at the next part of the DICOM file, something needs to be said about the concept of
transfer syntax
and its role in the DICOM standard. If you recall from my earlier tutorial, the DICOM standard enables devices to transfer information with each other even if they are running on different operating systems. Different operating systems and devices follow different formats for storing data such as
byte ordering
when they store binary data. Due to heavy network requirements for exchange of large imagery generated from the scanning modalities such as CT or MR, the standard also has provisions to exchange image data using compression when/if necessary. In
my introductory tutorial on DICOM
, we also learnt about implicit and explicit
VR encoding
as well. All three criteria (the type of VR encoding, the type of byte ordering and the compression utilized) must first be understood and agreed upon to ensure that the two DICOM systems exchanging information understand each other during any communication between them. The transfer syntax is a set of encoding rules that helps specify this criteria through the use of UIDs which we also learnt about in
my introductory tutorial on DICOM
. For example, Implicit VR Little-endian (indicated by an UID value of 1.2.840.10008.1.2), Explicit VR Little-endian (value – 1.2.840.10008.1.2.1), Explicit VR Big-endian (value – 1.2.840.10008.1.2.2) and JPEG Lossless (value – 1.2.840.10008.1.2.4.57) are some of the transfer syntaxes available for DICOM processing. I will cover these in more detail when discussing image pixel data processing in a future tutorial in my DICOM series.
Now that we understand what transfer syntax is in brief (there is more to this including
presentation context
which I will cover in a later tutorial), let us look at the next part of the file namely the
file meta-information header
. This section follows immediately after the file header and consists of a data set consisting of a sequence of tagged information (called
“Dicom Elements”
) which specifies details such as the transfer syntax (explained above) as well as other information regarding the device or implementation that created this file and for whom this information was created for (the receiving application). See
Page 32 of this document
for detailed information.
Following this section is the third and last part of the DICOM file and is the
data object
. This part of the DICOM file is also specified in the form of a data set consisting of a series of tags which may in turn be nested and carry additional child tags themselves. These tags help carry information about the SOP instance (see
my introductory tutorial
for what SOP means) such as the study, the series, the patient that it belongs to as well as other details regarding the image such as image pixel data, scan position data, etc. The study, series and patient information is often used to index the image in most PACS systems for faster retrieval of data. My illustration below should hopefully provide a synopsis of the overall file structure also showing how individual DICOM elements (each element includes the tag and the associated information) are part of the whole structure.
For example, in the first of the three DICOM elements in the data object section shown in my illustration above, ‘(0008, 0070)’ indicates a tag belonging to
group number of 0008 with an attribute number of 0070
, the ‘LO’ indicates the data type or the
Value Representation (VR)
as DICOM calls it (LO refers to the
Long String
data type), ‘PHILIPS’ is the actual value of the tag, ‘#8’ helps specify the length of the value of 8 (please note that DICOM always encodes data using an even number of characters for text so an extra padding character is used even though the value
PHILIPS
is only 7 characters long – more on this later), 1 represents the
value multiplicity
here (some data can be repeated), and ‘Manufacturer’ is the actual tag name as specified within the DICOM dictionary. The group and attribute number, the VR, the value, the value multiplicity and the tag name combined are referred to as an
DICOM Element
. Since the DICOM dictionary
(see page 23 and onwards)
implicitly defines the VR associated with each tag, the VR is redundant and is sometimes omitted. Despite this, a common practice and recommendation is to explicitly specify the VR when serializing DICOM objects into files or when exchanging DICOM information across the network. When I discuss the transfer syntax in more detail in a later tutorial, I will cover both the implicit transfer syntax (where VR is omitted) and the explicit transfer syntax (where the VR is specified along with the tag) in more detail. Now, we are in a better position to understand what an
IOD (Information Object Definition)
is. Let us proceed.
What do these terms really mean in DICOM – SCU, SCP, SOP and IOD?
DICOM defines the concepts of services and data that the services use or act upon. An example of a service may be a CT Store service which is responsible for storing an image generated from a CT modality to a PACS server. There are two parts to the service, the consumer of the service also known as a
Service Class User
or
SCU
, and the provider of the service also known as a
Service Class Provider
or
SCP
. In the CT Store operation for instance, the modality that generates the image acts as a
C-Store SCU
and transmits the data for storage to the C-Store SCP which is played by the PACS server. In the DICOM standard, the combination of the service classes and the objects that are involved with those services are known as
Service Object Pairs
or
SOPs
. The abstract definition of an SOP is called a
SOP Class
, and these are defined by unique identifiers (called
UIDs
) which I cover soon (see this link for a list of SOPs). So, the SOP CT Image Storage which is identified by a SOP Class UID of 1.2.840.10008.5.1.4.1.1.2 helps identify that this is a CT Image Storage operation. During this operation between the machines involved, there is an exchange of commands (called
DIMSEs
which I cover later) as well as some data which includes the image pixel information along with other identifying information such as patient, study, series and equipment information. Together, these concrete details for that operation are known as a
SOP Instance
. Each of these SOP instances are also identified by an unique identifier but are generated by the application responsible for transmitting them. These identifiers are called
SOP Instance UIDs
. The actual data involved with this SOP is defined by an
IOD (Information Object Definition)
specifying what DICOM modules (modules are essentially groups of DICOM elements) need to be present for successful completion of processing.
The IOD objects are themselves broken into sub groups called
Information Entities
(abbreviated to IE), and the Information Entities are in return broken into small groups of
Information Modules
. The Information Modules comprise of a series of
DICOM elements
which we have already seen. DICOM defines rules on what modules are mandatory, what are conditionally present as well as what are optional. The IODs themselves are classified into
Normalized IODs
and
Composite IODs
. Normalized IODs represent data pertaining to one entity only whereas Composite IODs represents data from a mixture of various entities that are related to one another as shown in my illustration below. This is essentially the grand structure of DICOM information model in summary. Putting this all together you will now see that any DICOM file that we have dealt so far is really an instance of an IOD (a serialized version of information) that is also transmitted between two machines during any imaging workflow. And in the case we were looking at, the operation to help store an image generated by a CT modality onto a PACS server. There is more to DICOM IODs and encoding than what is covered here, but we will deal with those areas when discussing creating DICOM files and directories. I didn’t want to bore you to death, but this is all you need to know for now.
The PixelMed Java DICOM Toolkit – Quick Overview
For the purposes of illustrating many aspects of DICOM that I plan to cover in this tutorial series, I will be using a freely available and powerful DICOM toolkit called
PixelMed Java DICOM Toolkit
. This is a completely stand-alone DICOM toolkit that provides functionality for DICOM file and directory processing, image viewing as well as DICOM networking-related operations. This toolkit is completely free for both commercial or non-profit use. It is
well documented
and also has a
small discussion forum and mailing list for users
. The list of features contained within this toolkit is quite comprehensive. Please keep in mind that the use of this toolkit in my tutorial does not in anyway imply my official endorsement of it for implementing a production application. Every situation is unique, and only you are ultimately in the best position to decide that. This article is also not meant to be a tutorial on this toolkit, and my focus here is simply to tie DICOM theory to what a practical (although simple) implementation might look like. So, if your goal is to learn how to use the PixelMed library, I would encourage you to visit
its website
or check out the
discussion forum
or
StackOverflow discussion pages
for any assistance.
Before We Get Started…
Much like my previous programming examples, I will use the most bare minimum code and approach to help illustrate the concepts that I cover in this tutorial. This means that the code I write here is best suited to simply show the concept that I am trying to explain and is not necessarily the most efficient code to deploy in real life and in your production application.
To get started, you will need to configure a few things on your machine including a Java development environment as well as the PixelMed toolkit before you can run the example if you want to try this out yourself.
-
Download and install the Eclipse Java IDE
from here
(or use any other IDE you prefer) -
Download the PixelMed toolkit library from
here
-
Ensure that the PixelMed.jar library is included in your Java project’s class path (some examples may require additonal
runtime dependencies
such as JAI Image IO Tools that can be found on PixelMed software download. Look for a tar compressed file called pixelmedjavadicom_dependencyrelease.YYYYMMDD.tar.bz2 or something similar) -
You can find the source code used in this tutorial
on GitHub
-
You can download more DICOM images
from this site
if you want as well
Listing Various Tags of a DICOM file to Console
To read and extract the DICOM tag information using the PixelMed toolkit is pretty straightforward. There are only two classes needed for this operation namely
AttributeList
and
Attribute
. The
AttributeList
class provides methods for reading (and writing) entire DICOM objects as a list of attributes from files or from streams. The constructor of this class takes the path to the DICOM file you want to process. The
Attribute
class on the other hand deals with reading and writing DICOM attributes. The use of these classes to display the DICOM elements that are of interest to us is shown below.
package com.saravanansubramanian.dicom.pixelmedtutorial; import com.pixelmed.dicom.Attribute; import com.pixelmed.dicom.AttributeList; import com.pixelmed.dicom.AttributeTag; import com.pixelmed.dicom.TagFromName; public class DumpDicomTagsToConsole { private static AttributeList list = new AttributeList(); public static void main(String[] args) { String dicomFile = "D:\\JavaProjects\\Sample Images\\MR-MONO2-16-head"; try { list.read(dicomFile); System.out.println("Study Instance UID:" + getTagInformation(TagFromName.StudyInstanceUID)); System.out.println("Series Instance UID:" + getTagInformation(TagFromName.SeriesInstanceUID)); System.out.println("SOP Class UID:" + getTagInformation(TagFromName.SOPClassUID)); System.out.println("SOP Instance UID:" + getTagInformation(TagFromName.SOPInstanceUID)); System.out.println("Transfer Syntax UID:" + getTagInformation(TagFromName.TransferSyntaxUID)); } catch (Exception e) { e.printStackTrace(); //in real life, do something about this exception } } private static String getTagInformation(AttributeTag attrTag) { return Attribute.getDelimitedStringValuesOrEmptyString(list, attrTag); } }
Output of running the code above is shown below:
Study Instance UID:1.2.840.113619.2.1.3352.2053053415.834484316
Series Instance UID:1.2.840.113619.2.1.3352.1136944889.4.834485379
SOP Class UID:1.2.840.10008.5.1.4.1.1.4
SOP Instance UID:1.2.840.113619.2.1.3352.1015047400.4.3.834485381
Transfer Syntax UID:1.2.840.10008.1.2
Listing All Tags of a DICOM file to Console
Listing the DICOM file’s entire attributes is fine, but sometimes, you simply want to selectively display only specific tags contained in the DICOM file including the group and attribute number, the value representation (VR), the value, the value length, value multiplicity and the tag name information which I described earlier in this tutorial. This process is easily achieved by using the
toString
method of
AttributeList
class contained in the PixelMed library. Code illustration of the operation is show below.
package com.saravanansubramanian.dicom.pixelmedtutorial; import com.pixelmed.dicom.AttributeList; public class DumpDicomFileContentsToConsole { public static void main(String[] args) { String dicomFile = "D:\\JavaProjects\\Sample Images\\MR-MONO2-16-head"; try { AttributeList list = new AttributeList(); list.read(dicomFile); System.out.println(list.toString()); } catch (Exception e) { e.printStackTrace(); } } }
Output of running the code above is shown below:
(0x0002,0x0000) FileMetaInformationGroupLength VR=<UL> VL=<0x4> [0xba]
(0x0002,0x0001) FileMetaInformationVersion VR=<OB> VL=<0x2> []
(0x0002,0x0010) TransferSyntaxUID VR=<UI> VL=<0x12> <1.2.840.10008.1.2 >
(0x0002,0x0012) ImplementationClassUID VR=<UI> VL=<0x18> <1.2.276.0.7230010.3.1.2 >
(0x0002,0x0013) ImplementationVersionName VR=<SH> VL=<0x10> <OFFIS-DCMTK-301 >
(0x0008,0x0000) VR=<UN> VL=<0x4> [B@681a9515
(0x0008,0x0008) ImageType VR=<CS> VL=<0x10> <ORIGINAL\PRIMARY>
(0x0008,0x0016) SOPClassUID VR=<UI> VL=<0x1a> <1.2.840.10008.5.1.4.1.1.4 >
(0x0008,0x0018) SOPInstanceUID VR=<UI> VL=<0x30> <1.2.840.113619.2.1.3352.1015047400.4.3.834485381>
(0x0008,0x0020) StudyDate VR=<DA> VL=<0xa> <1996.06.11>
(0x0008,0x0021) SeriesDate VR=<DA> VL=<0xa> <1996.06.11>
(0x0008,0x0023) ContentDate VR=<DA> VL=<0xa> <1996.06.11>
(0x0008,0x0030) StudyTime VR=<TM> VL=<0x8> <09:11:56>
(0x0008,0x0031) SeriesTime VR=<TM> VL=<0x8> <09:29:39>
(0x0008,0x0033) ContentTime VR=<TM> VL=<0x8> <09:29:41>
(0x0008,0x0060) Modality VR=<CS> VL=<0x2> <MR>
(0x0008,0x0070) Manufacturer VR=<LO> VL=<0x12> <GE MEDICAL SYSTEMS>
(0x0008,0x0080) InstitutionName VR=<LO> VL=<0x20> <PALO ALTO MEDICAL FOUNDATION MRI>
(0x0008,0x0090) ReferringPhysicianName VR=<PN> VL=<0xa> <Anonymized>
(0x0008,0x1010) StationName VR=<SH> VL=<0x8> <MROCOC0 >
(0x0008,0x1030) StudyDescription VR=<LO> VL=<0x4> <HEAD>
(0x0008,0x103e) SeriesDescription VR=<LO> VL=<0x6> <FLAIR >
(0x0008,0x1060) NameOfPhysiciansReadingStudy VR=<PN> VL=<0xa> <Anonymized>
(0x0008,0x1070) OperatorsName VR=<PN> VL=<0xa> <Anonymized>
(0x0008,0x1090) ManufacturerModelName VR=<LO> VL=<0xe> <GENESIS_SIGNA >
(0x0010,0x0000) VR=<UN> VL=<0x4> [B@681a9515
(0x0010,0x0010) PatientName VR=<PN> VL=<0xa> <Anonymized>
(0x0018,0x0000) VR=<UN> VL=<0x4> [B@681a9515
(0x0018,0x0010) ContrastBolusAgent VR=<LO> VL=<0x4> <NONE>
(0x0018,0x0020) ScanningSequence VR=<CS> VL=<0x2> <RM>
(0x0018,0x0021) SequenceVariant VR=<CS> VL=<0x4> <NONE>
(0x0018,0x0022) ScanOptions VR=<CS> VL=<0x16> <GRAPH_GEMS\FC\EDR_GEMS>
(0x0018,0x0023) MRAcquisitionType VR=<CS> VL=<0x2> <2D>
(0x0018,0x0024) SequenceName VR=<SH> VL=<0x6> <flair >
(0x0018,0x0025) AngioFlag VR=<CS> VL=<0x2> <Y >
(0x0018,0x0050) SliceThickness VR=<DS> VL=<0x8> <5.000000>
(0x0018,0x0080) RepetitionTime VR=<DS> VL=<0xc> <10002.000000>
(0x0018,0x0081) EchoTime VR=<DS> VL=<0xa> <159.500000>
(0x0018,0x0083) NumberOfAverages VR=<DS> VL=<0x8> <1.000000>
(0x0018,0x0084) ImagingFrequency VR=<DS> VL=<0xe> <63.8609400000 >
(0x0018,0x0085) ImagedNucleus VR=<SH> VL=<0x2> <H1>
(0x0018,0x0086) EchoNumbers VR=<IS> VL=<0x2> <1 >
(0x0018,0x0087) MagneticFieldStrength VR=<DS> VL=<0x6> <15000 >
(0x0018,0x0088) SpacingBetweenSlices VR=<DS> VL=<0x8> <6.000000>
(0x0018,0x0091) EchoTrainLength VR=<IS> VL=<0x2> <0 >
(0x0018,0x0093) PercentSampling VR=<DS> VL=<0xa> <100.000000>
(0x0018,0x0094) PercentPhaseFieldOfView VR=<DS> VL=<0xa> <100.000000>
(0x0018,0x0095) PixelBandwidth VR=<DS> VL=<0xa> <167.187500>
(0x0018,0x1020) SoftwareVersions VR=<LO> VL=<0x2> <04>
(0x0018,0x1050) SpatialResolution VR=<DS> VL=<0x12> <1.145833\0.859375 >
(0x0018,0x1088) HeartRate VR=<IS> VL=<0x2> <0 >
(0x0018,0x1090) CardiacNumberOfImages VR=<IS> VL=<0x2> <0 >
(0x0018,0x1094) TriggerWindow VR=<IS> VL=<0x2> <0 >
(0x0018,0x1100) ReconstructionDiameter VR=<DS> VL=<0xe> <220.0000000000>
(0x0018,0x1250) ReceiveCoilName VR=<SH> VL=<0x4> <HEAD>
(0x0018,0x1251) TransmitCoilName VR=<SH> VL=<0x4> <HEAD>
(0x0018,0x1312) InPlanePhaseEncodingDirection VR=<CS> VL=<0x4> <COL >
(0x0018,0x1314) FlipAngle VR=<DS> VL=<0x2> <90>
(0x0018,0x1315) VariableFlipAngleFlag VR=<CS> VL=<0x2> <N >
(0x0018,0x1316) SAR VR=<DS> VL=<0x8> <0.015446>
(0x0018,0x5100) PatientPosition VR=<CS> VL=<0x4> <HFS >
(0x0020,0x0000) VR=<UN> VL=<0x4> [B@681a9515
(0x0020,0x000d) StudyInstanceUID VR=<UI> VL=<0x2c> <1.2.840.113619.2.1.3352.2053053415.834484316>
(0x0020,0x000e) SeriesInstanceUID VR=<UI> VL=<0x2e> <1.2.840.113619.2.1.3352.1136944889.4.834485379>
(0x0020,0x0011) SeriesNumber VR=<IS> VL=<0x2> <4 >
(0x0020,0x0012) AcquisitionNumber VR=<IS> VL=<0x2> <1 >
(0x0020,0x0013) InstanceNumber VR=<IS> VL=<0x2> <3 >
(0x0020,0x0032) ImagePositionPatient VR=<DS> VL=<0x24> <-110.000000\ -109.800003\-47.500000 >
(0x0020,0x1040) PositionReferenceIndicator VR=<LO> VL=<0x2> <NA>
(0x0020,0x1041) SliceLocation VR=<DS> VL=<0xe> <-47.5000000000>
(0x0028,0x0000) VR=<UN> VL=<0x4> [B@681a9515
(0x0028,0x0002) SamplesPerPixel VR=<US> VL=<0x2> [0x1]
(0x0028,0x0004) PhotometricInterpretation VR=<CS> VL=<0xc> <MONOCHROME2 >
(0x0028,0x0010) Rows VR=<US> VL=<0x2> [0x100]
(0x0028,0x0011) Columns VR=<US> VL=<0x2> [0x100]
(0x0028,0x0030) PixelSpacing VR=<DS> VL=<0x12> <0.859375\0.859375 >
(0x0028,0x0100) BitsAllocated VR=<US> VL=<0x2> [0x10]
(0x0028,0x0101) BitsStored VR=<US> VL=<0x2> [0x10]
(0x0028,0x0102) HighBit VR=<US> VL=<0x2> [0xf]
(0x0028,0x0103) PixelRepresentation VR=<US> VL=<0x2> [0x1]
(0x7fe0,0x0000) VR=<UN> VL=<0x4> [B@681a9515
(0x7fe0,0x0010) PixelData VR=<OW> VL=<0x20000>
Using DICOM Testing Tools
If you want to dig deeper into this area, you can use one of the many useful DICOM testing tools out there. The one that I have used in the past and have liked is
DCMTK
. The toolkit comes with many standalone testing utilities that help you test various aspects related to DICOM processing through a command line interface. Running the
dcmdump
command on a sample DICOM file results in an output as seen below.
c:\DicomTestingTools\DCMTK-3.6.3-Win64-Dynamic\bin>dcmdump.exe sampledicomfile.dcm
# Dicom-File-Format
# Dicom-Meta-Information-Header
# Used TransferSyntax: Little Endian Explicit
(0002,0000) UL 184 # 4, 1 FileMetaInformationGroupLength
(0002,0001) OB 00\01 # 2, 1 FileMetaInformationVersion
(0002,0002) UI =OphthalmicPhotography8BitImageStorage # 32, 1 MediaStorageSOPClassUID
(0002,0003) UI [2.25.101703809854595919801950834747690813074] # 44, 1 MediaStorageSOPInstanceUID
(0002,0010) UI =LittleEndianExplicit # 20, 1 TransferSyntaxUID
(0002,0012) UI [1.3.6.1.4.1.30071.8] # 20, 1 ImplementationClassUID
(0002,0013) SH [fo-dicom 4.0.1] # 14, 1 ImplementationVersionName
# Dicom-Data-Set
# Used TransferSyntax: Little Endian Explicit
(0008,0008) CS [ORIGINAL\PRIMARY] # 16, 2 ImageType
(0008,0016) UI =OphthalmicPhotography8BitImageStorage # 32, 1 SOPClassUID
(0008,0018) UI [2.25.101703809854595919801950834747690813074] # 44, 1 SOPInstanceUID
(0008,0020) DA [20200501] # 8, 1 StudyDate
(0008,002a) DT [20200501071652] # 14, 1 AcquisitionDateTime
(0008,0030) TM [071652] # 6, 1 StudyTime
(0008,0050) SH [125] # 4, 1 AccessionNumber
(0008,0060) CS [US] # 2, 1 Modality
(0008,0061) CS [OP] # 2, 1 ModalitiesInStudy
(0008,0070) LO [Medicolle] # 10, 1 Manufacturer
(0008,0080) LO [Test Hospital] # 14, 1 InstitutionName
(0008,0081) ST (no value available) # 0, 0 InstitutionAddress
(0008,0090) PN [Fergusson^^^Dr] # 14, 1 ReferringPhysicianName
(0008,1010) SH [AdvancedCapture] # 16, 1 StationName
(0008,1030) LO (no value available) # 0, 0 StudyDescription
(0008,1090) LO [AdvancedCapture] # 16, 1 ManufacturerModelName
(0010,0010) PN [Bowen^William^^Dr] # 18, 1 PatientName
(0010,0020) LO [PAT004] # 6, 1 PatientID
(0010,0030) DA [19560807] # 8, 1 PatientBirthDate
(0010,0040) CS [M] # 2, 1 PatientSex
(0010,1030) DS (no value available) # 0, 0 PatientWeight
(0018,1000) LO (no value available) # 0, 0 DeviceSerialNumber
(0018,1020) LO [AdvancedCapture 3.0.7425.23917] # 30, 1 SoftwareVersions
(0018,106a) CS [NO TRIGGER] # 10, 1 SynchronizationTrigger
(0018,1800) CS [N] # 2, 1 AcquisitionTimeSynchronized
(0020,000d) UI [1.2.826.0.1.3680043.11.106] # 26, 1 StudyInstanceUID
(0020,000e) UI [2.25.103253814587692535336175589446854494919] # 44, 1 SeriesInstanceUID
(0020,0010) SH [1] # 2, 1 StudyID
(0020,0011) IS [1] # 2, 1 SeriesNumber
(0020,0013) IS [1] # 2, 1 InstanceNumber
(0020,0020) CS [F\A] # 4, 2 PatientOrientation
(0020,0062) CS [R] # 2, 1 ImageLaterality
(0020,0200) UI =UniversalCoordinatedTimeSynchronizationFrameOfReference # 20, 1 SynchronizationFrameOfReferenceUID
(0020,1206) IS [1] # 2, 1 NumberOfStudyRelatedSeries
(0020,1208) IS [1] # 2, 1 NumberOfStudyRelatedInstances
(0020,1209) IS [1] # 2, 1 NumberOfSeriesRelatedInstances
(0022,000a) FL 0 # 4, 1 EmmetropicMagnification
(0022,000c) FL 0 # 4, 1 HorizontalFieldOfView
(0022,000e) FL 0 # 4, 1 DegreeOfDilation
(0022,0015) SQ (Sequence with undefined length #=1) # u/l, 1 AcquisitionDeviceTypeCodeSequence
(fffe,e000) na (Item with undefined length #=1) # u/l, 1 Item
(0008,0104) LO (no value available) # 0, 0 CodeMeaning
(fffe,e00d) na (ItemDelimitationItem) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem) # 0, 0 SequenceDelimitationItem
(0022,0016) SQ (Sequence with undefined length #=1) # u/l, 1 IlluminationTypeCodeSequence
(fffe,e000) na (Item with undefined length #=0) # u/l, 1 Item
(fffe,e00d) na (ItemDelimitationItem) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem) # 0, 0 SequenceDelimitationItem
(0022,0019) SQ (Sequence with undefined length #=1) # u/l, 1 LensesCodeSequence
(fffe,e000) na (Item with undefined length #=0) # u/l, 1 Item
(fffe,e00d) na (ItemDelimitationItem) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem) # 0, 0 SequenceDelimitationItem
(0028,0002) US 3 # 2, 1 SamplesPerPixel
(0028,0004) CS [RGB] # 4, 1 PhotometricInterpretation
(0028,0006) US 0 # 2, 1 PlanarConfiguration
(0028,0008) IS [1] # 2, 1 NumberOfFrames
(0028,0010) US 1920 # 2, 1 Rows
(0028,0011) US 1920 # 2, 1 Columns
(0028,0100) US 8 # 2, 1 BitsAllocated
(0028,0101) US 8 # 2, 1 BitsStored
(0028,0102) US 7 # 2, 1 HighBit
(0028,0103) US 0 # 2, 1 PixelRepresentation
(0040,0555) SQ (Sequence with undefined length #=1) # u/l, 1 AcquisitionContextSequence
(fffe,e000) na (Item with undefined length #=0) # u/l, 1 Item
(fffe,e00d) na (ItemDelimitationItem) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem) # 0, 0 SequenceDelimitationItem
(7fe0,0010) OB 00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00... # 11059200, 1 PixelData
c:\DicomTestingTools\DCMTK-3.6.3-Win64-Dynamic\bin>
Conclusion
That is it. This is all there is to performing some rudimentary processing of a DICOM file. I hope this introductory tutorial helped you in understanding what is contained within a DICOM file. I didn’t touch on how to extract the image pixel data that is stored inside the DICOM file that we processed. I hope to cover that in a separate tutorial in the near future. But in my next DICOM tutorial I will show you how to encode/create a DICOM file from scratch using an image as well as some associated information. If you have any questions or comments regarding this tutorial, please feel free to send me an email. Please note that I may not get back to you right away due to work and other commitments.
Footnote:
The DICOM standard restricts the file names/identifiers contained within to 8 characters (either uppercase alphabetic characters and numbers only) to keep in conformity with legacy/historical requirements. It also states that no information must be inferred/extracted from these names. The file names usually don’t have a .dcm extension when they are stored as part of a media such as CD or DVD. I use longer names to keep these details from being a distraction right now, but I still want to mention what the standard states here so that no confusion arises as a result.