DICOM 图像查看器
一个简单的 DICOM 3.0 文件格式(C#)图像查看器。文件应包含原始像素数据,未压缩。还提供窗位功能。
引言
DICOM 是 Digital Imaging and COmmunication in Medicine(医学数字成像和通信)的缩写。DICOM 标准解决了不同成像设备之间的基本连接性,以及医学成像部门的工作流程。DICOM 标准由美国国家电气制造商协会(NEMA)创建,还涵盖了医学图像的分发和查看。该标准包含二十个部分(截至 2013 年),可在 NEMA 网站免费获取:http://medical.nema.org。在标准的内部也包含图像文件格式的详细规范。在本文中,我们将介绍一个 DICOM 图像查看器。我们还将演示如何通过窗位修改显示图像的亮度和对比度。
DICOM 图像文件格式
现在我们简要介绍一下 DICOM 图像文件格式。与所有其他图像文件格式一样,DICOM 文件由头部和像素数据组成。头部包含患者姓名和其他患者详细信息以及图像详细信息。图像详细信息中重要的是图像尺寸——宽度和高度,以及每像素位数。所有这些详细信息都隐藏在 DICOM 文件中,以标签和值的形式存在。
在我们深入研究标签和值之前,先简要介绍一下 DICOM 本身和相关术语。在下面的内容中,我们将只解释与 DICOM 文件相关的术语和概念。特别是,我们不讨论 DICOM 标准的通信和网络方面。
DICOM 中的一切都是一个对象——医疗设备、患者等。对象,如同面向对象编程一样,以属性为特征。DICOM 对象根据 IODs(信息对象定义)进行标准化。IOD 是描述数据对象的属性集合。换句话说,IOD 是对一类相似现实世界对象的抽象,它定义了与该类相关的性质和属性。DICOM 还对最常用的属性进行了标准化,这些属性列在 DICOM **数据字典**(标准第 6 部分)中。如果应用程序在标准列表中找不到所需的属性名称,它可以添加自己的私有条目,称为私有标签;因此,DICOM 中可能存在专有属性。
属性的示例包括 Study Date、Patient Name、Modality、Transfer Syntax UID 等。正如所见,这些属性需要不同的数据类型才能正确表示。在 DICOM 中,这种“数据类型”称为**值表示**(VR)。定义了 27 种这样的 VR,它们是 AE、AS、AT、CS、DA、DS、DT、FL、FD、IS、LO、LT、OB、OF、OW、PN、SH、SL、SQ、SS、ST、TM、UI、UL、UN、US 和 UT。例如,DT 表示日期时间,一个串联的日期时间字符串,格式为 YYYYMMDDHHMMSS.FFFFFF&ZZXX。关于这些 VR 的详细解释,请参阅标准(2011 版)第 5 部分(第 6.2 节)。VR 的一个重要特性是其长度,它应始终为偶数。
属性的特征是其标签、VR、VM(值多重性)和值。**标签**是一个 4 字节值,唯一标识该属性。标签分为两部分,组标签和元素标签,每部分长度为 2 字节。例如,标签 0010 0020(十六进制)表示患者 ID,VR 为 LO(长字符串)。在此示例中,0010(十六进制)是**组标签**,0020(十六进制)是**元素标签**。DICOM 数据字典提供了所有标准化组和元素标签的列表。
了解标签是否为强制性也很重要。标准(2011 版)第 5 部分第 7.4 节给出了数据元素类型,定义了五种类别——Type 1、Type 1C、Type 2、Type 2C 和 Type 3。如果您的应用程序处理,例如,数字 X 射线,那么请参阅标准(2011 版)第 3 部分,表 A.26-1,以识别此项的强制性和非强制性标签。例如,从该表中,再次参考 C.7.1.1 获取与患者对应的详细信息。对 A.26-1 表中的所有条目重复此操作。其他模态也类似。
另一个重要概念是**传输语法**。简单来说,它告诉一个设备是否可以接收另一个设备发送的数据。每个设备都附带自己的 DICOM 兼容性声明,其中列出了设备可接受的所有传输语法。传输语法说明了传输的数据和消息是如何编码的。DICOM 标准的第 5 部分(第 10 节)将传输语法定义为一组编码规则,这些规则允许应用程序实体无歧义地协商它们能够支持的编码技术(例如,数据元素结构、字节顺序、压缩),从而允许这些应用程序实体进行通信。(这里还有一个术语——**应用程序实体**是 DICOM 设备或程序的名称,用于唯一标识它。)非压缩图像的传输语法是:
- Implicit VR Little Endian,UID 为 1.2.840.10008.1.2
- Explicit VR Little Endian,UID 为 1.2.840.10008.1.2.1
- Explicit VR Big Endian,UID 为 1.2.840.10008.1.2.2
使用 JPEG 有损或无损压缩技术压缩的图像具有自己的传输语法 UID。查看器应能够识别传输语法并相应地解码图像数据;或者在无法处理时显示适当的错误消息。
关于 DICOM 文件的更多要点
- 它是一个二进制文件,这意味着像 Notepad 或 Notepad++ 这样的基于 ASCII 字符的文本编辑器无法正确显示它。
- DICOM 文件可能以 Little Endian 或 Big Endian 字节序编码。
- DICOM 文件中的元素始终按标签的升序排列。
- 私有标签始终为奇数。
有了这些背景,现在是时候深入研究 DICOM 文件格式了。DICOM 文件包含以下部分:
- 序言:包含 128 字节,后跟,
- 前缀:包含字符 'D'、'I'、'C'、'M',后跟,
- 文件元头:其中包含媒体 SOP 类 UID、媒体 SOP 实例 UID 和传输语法 UID 等。默认情况下,这些都以显式 VR、小端格式编码。数据应根据 VR 类型进行读取和解释。
- 数据集:包含多个 DICOM 元素,以标签和值作为特征。
DICOM 图像读取器主要功能是根据传输语法读取不同的标签,然后相应地使用这些值。图像查看器需要读取图像属性——图像宽度、高度、每像素位数以及实际像素数据。本文提出的查看器可用于查看具有非压缩传输语法的 DICOM 图像。我们尝试读取旧的(DICOM 标准 3.0 版本之前)DICOM 文件。
DICOM 图像查看器代码
市面上有许多免费的 DICOM 图像查看器。但是,我们找不到任何用 C# 实现的查看器。ImageJ 是一个免费的基于 Java 的查看器(带源代码),能够显示包括 DICOM 在内的多种格式的图像。我们的目的是在 C# 中模拟 ImageJ 代码,创建一个简洁的 DICOM 文件查看器。
此查看器的功能包括:
- 打开具有显式 VR 和隐式 VR 传输语法的 DICOM 文件
- 读取图像位深度为 8 或 16 位的 DICOM 文件。还读取位深度为 8 像素、每像素 3 字节的 RGB DICOM 文件——这些图像来自超声模态。
- 读取只包含一个图像的 DICOM 文件
- 读取 DICONDE 文件(DICONDE 文件是包含 NDE - 非破坏性评估 - 标签的 DICOM 文件)
- 读取旧的 DICOM 文件——其中一些没有序言和前缀。它们在开头只包含字符串 1.2.840.10008。
- 显示 DICOM 文件中的标签
- 通过“缩放到适合”功能,使用户能够完整查看 DICOM 图像
- 使用户能够将 DICOM 图像另存为 PNG
此查看器不旨在:
- 检查所有必需的标签是否存在
- 打开除显式和隐式 VR 以外的 VR 文件——特别是,不打开 JPEG 压缩的有损和无损文件
- 读取一系列图像
尽管 DICOM 图像经常将像素数据存储为 JPEG 压缩格式,但我们没有在此应用程序中包含 JPEG 解压缩,因为它会将焦点转移到其他地方。
代码是用 C# 编写的,并在 Visual Studio 2019 上构建。软件本身组织成一组文件,如下所示:
- DicomDictionary.cs,包含 DICOM 数据字典,如标准第 6 部分所示
class DicomDictionary { public Dictionary<string, string> dict = new Dictionary<string,string>() { {"20002", "UIMedia Storage SOP Class UID"}, {"20003", "UIMedia Storage SOP Inst UID"}, {"20010", "UITransfer Syntax UID"}, {"20012", "UIImplementation Class UID"}, {"20013", "SHImplementation Version Name"}, ... {"FFFEE000", "DLItem"}, {"FFFEE00D", "DLItem Delimitation Item"}, {"FFFEE0DD", "DLSequence Delimitation Item"} }; }
- DicomDecoder.cs,其主要功能是解析 DICOM 文件并将必要属性适当地存储起来。其中一个重要的方法旨在获取下一个标签
int GetNextTag() { int groupWord = GetShort(); if (groupWord == 0x0800 && bigEndianTransferSyntax) { littleEndian = false; groupWord = 0x0008; } int elementWord = GetShort(); int tag = groupWord << 16 | elementWord; elementLength = GetLength(); // "Undefined" element length. // This is a sort of bracket that encloses a sequence of elements. if (elementLength == -1) { elementLength = 0; inSequence = true; } return tag; }
- ImagePanelControl.cs,重用了我们撰写的早期文章中的代码。此图像面板内置图像滚动功能,以防图像尺寸大于显示区域。
- WindowLevelGraphControl.cs,主要负责在屏幕上显示图表控件。下面将对窗位进行解释。
主窗体有四个按钮——用于打开 DICOM 文件、查看标签、另存为 PNG 文件以及重置为原始窗位值。如果用户想查看标签及其值,则会出现以下屏幕,列出文件中存在的不同标签。
窗位和窗宽
图像显示时,其亮度和对比度是其特征。当您将每个像素的灰度值增加一个单位时,您实际上将图像的亮度增加了单位。降低亮度也是如此。整体亮度低的图像看起来是暗的;而整体亮度高的图像看起来是亮的。对比度是图像中高低值之间差异的度量。如果两个相邻像素的灰度值差异很大,则它们之间的对比度被认为是高的;反之,如果两个相邻像素的灰度值差异很小,则它们之间的对比度被认为是低的。表示亮度和对比度的另一种方法是通过窗位和窗宽。简单来说,窗宽是显示的最亮像素值和最暗像素值之间的差异。而窗位(也称为窗中心)是最亮和最暗像素值之间的中间值。理解这些很简单,如果注意到涉及四个值:
- 图像最小值,即任何特定图像中所有灰度值中的最小值
- 图像最大值,即图像中所有灰度值中的最大值
- 窗最小值,即显示为屏幕上零强度(暗)的下阈值
- 窗最大值,即显示为屏幕上最高强度(亮)的上限值
上面列出的前两个值取决于图像,而接下来的两个值取决于用户的设置。所有灰度强度小于窗最小值的图像像素显示为暗(零强度),而所有灰度强度大于窗最大值的图像像素显示为亮(最大强度,通常为 255)。在窗最小值和最大值之间,映射函数(线性的或非线性的)将图像灰度值映射到显示的输出值。在本文中,我们仅限于线性映射,如下图所示(针对 16 位图像)。
也可以从另一方面来看。可以调整图像的亮度和对比度以突出感兴趣的特征。这称为图像的“窗化”。对图像进行窗化时,会调整显示的灰度。本质上,窗最小值和窗最大值被操纵,以便更好地查看图像特征。窗位是窗最小值和窗最大值之间的中间值;换句话说,它是中心值,因此也称为窗中心。该数字越大,图像看起来越暗;反之亦然。窗宽是窗最大值和窗最小值之间的差值。差值越大,对比度越高。例如,考虑一个 16 位图像,用户可能希望关注强度在 40000 到 50000 之间的像素;在这种情况下,窗位成为中点值 45000,窗宽成为差值 10000。在此应用程序中,屏幕左侧的矩形窗口显示了从输入到输出的像素映射。下图显示了屏幕的一部分,仅显示了窗口/级别部分。窗宽由紫色线的长度表示。窗位由标记位置指示。
当窗最小值或最大值超出原始图像范围时,表示窗宽的线将变为虚线样式,如下图所示
要更改图像上的窗位和窗宽,只需在图像上右键拖动(单击并移动鼠标右键)。在垂直方向上右键拖动以修改窗位。在水平方向上右键拖动以修改窗宽。您可以将图像另存为 PNG,并保留当前的窗位设置。
已知问题
打开图像并按下 Alt 键时,图像会消失。但是,在强制重绘后(例如,通过最小化并恢复查看器窗口)它会再次出现。
闭包
本文介绍了一个简单的 DICOM 文件显示应用程序。简要解释了 DICOM 术语,然后简要解释了 DICOM 文件格式。该应用程序深受 ImageJ 的启发。此处显示的查看器可用于查看具有显式和隐式 VR 传输语法的文件,而不适用于包含压缩图像数据的。您还可以通过在图像上右键拖动来修改图像的窗宽和窗位。
修订历史
- 2009 年 4 月,初版
- 2010 年 4 月,扩展以包含窗/级别
- 2011 年 1 月,扩展以包含 RGB 彩色和有符号 16 位图像
- 2013 年 8 月,扩展以包含旧的 DICOM 文件;并进行了一些错误修复
- 2020 年 10 月,修复了几个错误。
致谢
作者感谢 Guruprasad Bhat 和 Bhanuprakash P 就 DICOM 的各个方面进行的有益讨论。
作者还感谢以下 CodeProject 用户提供有用的反馈,以对软件进行更新:
- Guiseppe Marchi,http://www.peppedotnet.it,http://www.sharepointcommunity.it,他为我们提供了超声图像样本
- Vignesh Babu M,他为我们提供了超声图像样本
- Zhang,他指出了关于有符号 16 位图像的一个错误
- Matius Montroull,他指出了有符号图像的一个问题
- Mark Sedrak,他指出了在遇到空字符串时的一个异常