【题外话】
其实早就想开始重新写博客,但总是有各种各样的理由“说服”自己偷懒,这次在一家小公司实习,要做Dicom文件的解析,觉得有必要记录一些东西,也是本博第一篇文章了。
【文章索引】
个人感觉,要想解析一个文件,虽然可能会有各种各样的类库去做,但还是很有必要去了解下最起码文件的结构,这样在使用类库的时候思路也会更清晰一些。
总体来说,DICOM的文件格式还是很清晰的,简单看其实可以分为三部分:
- 第一部分是文件开始固定的128字节为DICOM文件的引言(Preamble),不过由于可以不写,所以基本上所有的DICOM文件前128字节均为置0;
- 第二部分为跟在之后的4字节文件标识,即“DICM”四个字节,如果不是则表示不是DICOM文件;
- 第三部分为数据集(DataSet),数据集中包含若干个数据元素(DataElement),数据集中包含了文件的元数据(File Meta Data Element)、患者及检查的数据、私有数据(Private Data Element)以及图像、覆盖层等数据(Encoding of Pixel, Overlay and Waveform Data)。
对于每一个数据元素,其又包含如下图所示的四部分:
- 数据元素标识 Tag(2字节UInt16分组号和2字节UInt16元素号);
- 数据表现类型 Value Representation(2个单字节Char,有些情况之后会预留两字节的置0,如上图的0x20 0x00 0x01 0x00);
- 数据长度 Value Length(2字节UInt16,有些情况是4字节UInt32,如上图的0x20 0x00 0x01 0x00);
- 数据内容 Value Field(长度为Value Length,如果VL=0xFFFFFFFF,则需要一直读到截止符)。
数据内容的存储与表现格式与VR是关联的(参见标准第五部分第24页),但比较恶心的是,VR不是一定存在,也就是可能有隐式的情况(需要根据元素标识进行判断),此外VR的属性还可能是UN(Unknown)等等等等。当然除非你要自己写解析,否则了解到这就可以了。
.NET平台下其实有好多好用的DICOM解析库,之前有一个叫mDCM的类库(https://github.com/rcd/mdcm),能读取大部分DICOM文件(手头有几张色深16位的片子解析有偏差,还有几张测试软件的片子无法读取,再就是无法支持32位色片子等等),作者也已经对类库停止了更新。不过好在之后又有了fo-dicom(https://github.com/rcd/fo-dicom),用起来感觉跟mDCM是一样的(代码很多也是一样的),而且上述的问题都解决了,起码我手头几个医院的片子都能正常解析。
要获取的fo-dicom的话,需要去上述提供的github链接上下载代码,可以直接点击上方工具栏的ZIP打包下载所有代码,然后在本地打开编译一下。需要说明的是,项目是基于.NET 4.0的,由于这个项目包含了C++的代码,所以如果用VS2012打开的话需要更新为VC++2012编译。其实如果不需要Jpeg解析,只需要编译Dicom项目即可。
fo-dicom对DICOM文件格式进行了高度封装,首先一个DICOM文件是一个DicomFile类(using Dicom),你可以这样从文件中创建,比如这样:
DicomFile dcmFile = DicomFile.Open(filePath);
或者这样从流中创建,比如这样:
DicomFile dcmFile = DicomFile.Open(stream);
如果要获取DICOM文件中的数据元素(Data Element),需要从DICOM的数据中获取,与mDCM不同的是,fo-dicom提供了泛型的方法,而不是针对每个类型提供了一个方法。同时,与mDCM一样的是,fo-dicom对所有的数据元素标识(Tag)都进行了封装,你无需考虑Tag到底是多少,而只需要给出你需要什么就可以了,DicomTag类下提供了几千个封装好的静态类可供选择,比如这样:
DicomDataset dcmDataSet = dcmFile.Dataset;
String patientName = dcmDataSet.Get<String>(DicomTag.PatientName);
如果要获取图像信息的话需要创建DicomImage类(using Dicom.Imaging),比如这样:
DicomImage dcmImage = new DicomImage(dcmDataSet);//可以增加第二个参数来指定获取第几帧 Image image = dcmImage.RenderImage();
fo-dicom另一点比mDCM好的地方在于,其将窗位(WindowCenter)和窗宽(WindowWidth)也放到了DicomImage类中,可以直接修改DicomImage类中的这两个参数来获取和设置窗位和窗宽,调整完直接RenderImage即可,而无需调整DataSet中的参数再重新创建DicomImage类(不但简化了操作而且提交了效率),比如这样:
dcmImage.WindowCenter = 300; dcmImage.WindowWidth = 1500;
1、在某个WindowCenter和WindowWidth程序提示数组越界,程序崩溃。
修改Dicom/Imaging/LUT/OutputLUT.cs中的索引器(public int this[int value],如下图),将注释掉的四行屏蔽注释即可(判断value < 0 和 > 255的部分)。不知道作者的因为调试程序还是其他原因,为什么将这四行注释掉,mDCM的这个文件也是如此,实际测试中对于某些WindowCenter和WindowWidth,在这个地方可能会导致数组越界。
2、调整Overlays层的绘制颜色。
修改Dicom/Imaging/DicomImage.cs中的常量OverylayColor;
3、在输出图片时直接输出旋转后的影像。
修改Dicom/Imaging/DicomImage.cs中RenderImage方法,在创建完ImageGraphic的对象graphic之后执行graphic.Rotate(angle)方法,其中angle如果大于零表示向右旋转的度数,如果小于零表示向左的度数,所以你可以在DicomImage中创建个属性设置好旋转的度数或者直接在RenderImage方法传入旋转的度数。例如改成下图这样:
1、Dicom格式文件解析器:http://www.cnblogs.com/assassinx/archive/2013/01/09/dicomViewer.html
2、《DICOM标准医学图像文件解析及工具软件的研制》:http://wenku.baidu.com/view/7511df2c2af90242a895e524.html
3、The DICOM Standard:http://medical.nema.org/standard.html