前段时间由于一项TASK的需求,要根据原有的一组DICOM文件的信息将一组另外生成的二维图像数据重新写成DICOM文件格式以作后续使用,我采取了通过MATLAB写文件的方式把这个二维图像数据写到原DICOM文件中存储图像数据的部分。这里主要想跟大家分享一下我尝试这个操作的方法和流程。
为了实现这个操作,第一步就是要去获取DICOM的二进制文件中图像数据存储的起始位置,那么如何去获取,这就需要对DICOM文件格式的结构有比较清晰的把握。通过查阅相关资料(这里非常感谢zssure提供的DICOM3.0标准的详细说明),可以了解到DICOM文件主要分为文件头和数据集两部分,那么在这里根据相关资料中的内容我们可以对DICOM文件格式的这样两部分作一个简要的介绍。
1、文件头,它主要包括两个部分,第一部分是导言(Preamble),一般是由128个字节的00h所填充,紧接着第二部分是文件前缀,内容是“44 49 43 4D”这样四个字节代表ASCII码对应的大写字母字符串“DICM”;
2、数据集,它是由数据元素按照数据标签(Tag)的升序排列而成,主要用来存储具体信息 ,包括传输语法,病人姓名,文件元信息版本,媒体存储SOP类UID,传输句法UID,以及图像的属性和数据内容方面的信息等等。而数据元素主要是由四个成分组成:标签(Tag),值表示类型(VR Value Representation),值长度(Value Length),值域(Value Field)。
下面我们给出一个例子,如下表所示。
这里我同时截取了用UltraEdit打开DICOM文件时看到的十六进制数排列图
通过上表与上图的对照,我们可以清楚地看到DICOM文件头部分数据的排列,而接着从“02 00 00 00”对应的数据标签(0002 0000)开始以数据元素为单位进行各类信息的顺序存储。那么文件整体结构即可用paper里的图表示,见下图:
据此,可初步估计我所需要知道的图像数据应当存储于某一个数据元素单元的数据值中。然后,通过继续查阅资料,搜索到了标签为(7FE0 0010)的数据元素单元正是包含了DICOM图像显示所需要的数据。这里我给出了这个数据元素的一个实例,如下图所示。
对于该数据元素,我们将它对应到之前所述的四个组成成分[Tag VR VL VF],可以得出(7FE0,0010)的四个字节对应Tag,(4F 57 00 00)的四个字节对应VR为OW类型,数据长度VL为(0001 2000)=73728个字节(PS:这里注意数据的存放方式是Little-Endian,即小端模式),作为验证,你可以check一下这个字节数应当等于图像行数×图像列数×2(PS:其中,标签(0028,0010)对应的是图像行数,(0028,011)对应的是图像列数,(0028,0100)对应的是分配位数,这个位数通常为16bits,即用2个字节存储一个像素的数据),另一方面你也可以check一下这个字节数同时也应当等于从该单元内数据值开始的地址(如这里从地址2140h开始)到文件末尾地址所存储的字节数。
OK,在找到图像数据元素的存储位置之后,剩下的就是写数据的工作了。起初可能由于解读DICOM文件格式的惯性思维,还想用存有图像数据的mat文件直接写入DICOM文件,但是发现mat文件的格式不是那么容易找到相关资料解析的,于是还是采用matlab中的文件写函数将数据直接写入到DICOM文件中的图像数据存储位置进行替换。
总体来说呢,在matlab中实现这部分操作还是蛮简单的,只需要几行code就可以实现,这里我把关键的一部分列在这里供大家参考。
其中,idx即是找到图像数据元素单元对应标签(7FE0,0010)的位置,然后从‘E0’开始之后的第12个字节处即为图像数据存储的首地址,亦即图像数据在这个DICOM二进制文件中的offset,从此处将已经生成的图像数据写入到原DICOM文件的相应部分即可。
这里能够很快完成这部分尝试性的操作还要非常感谢zssure同志的建议和鼓励,在此写下自己在这个过程中所了解到的一些知识以及解决问题过程中的一些经验来与大家分享,希望可以帮助到有可能会有类似操作需求的人们,可能算是小众群体吧,嘿嘿~
另外呢,将自己生成的图像数据重新写成DICOM文件还有另一种方法就是直接利用matlab中的dicomwrite函数,但很多时候利用这个函数将已有的dicominfo写入到新的dicom文件中时会丢失或者改写一些信息,尤其是private parameter的部分,我曾经通过改写matlab的内部函数解决过其中的一些参数写入不全的问题,如果有对此操作感兴趣的同志也可以给我留言啦。