最近项目需求,需要使用C#进行最新的UI和相关DICOM3.0医学图像模块的开发。在C++语言下,我使用的是应用最广泛的DCMTK开源库,在本专栏的起初阶段的大多数博文都是对DCMTK开源库的介绍和学习。目前由于项目需要,现开始对mDCM开源库继续学习分析,因此本专栏接下来的文章会大多以mDCM开源库为例进行医学图像的讲解,DCMTK由于是C++语言开发的,所以作为我学习和剖析mDCM开源库的原始依据,我们并未放弃对DCMTK开源库的学习,而是通过更加仔细的研读和分析DCMTK的C++源码,从而更好的切更迅速的切换到C#语言环境下的医学图像处理。
DCMTK的官网上有详细的说明文档,对该开源库的各个类,以及类之间的依赖关系进行了清晰的阐述。是学习DICOM3.0医学最新标准不可或缺的资源。其官网网址是:http://www.dcmtk.org/,活跃的开发者论坛地址是:http://forum.dcmtk.org/index.php。
mDCM目前了解是从DCMTK开源库转过来的,或者说是该开源项目的另一个分支,是对用C#语言对C++版本的医学图像开源库的再次组织和封装,其项目托管在GitHub上的官方网址是:https://github.com/ignacioinnovo/mdcm。此处就需要提到fo-dicom了,该开源库是mDCM的升级版本,里面增加了几大特性,详情可参见GitHub网址:https://github.com/fo-dicom/fo-dicom。
大致上这三者的关系就是如此,所以更说明了我们依然要以DCMTK开源库为依据,来快速学习和剖析mDCM(fo-dicom)开源库,要很好的借助于DCMTK开源库丰富而详细的说明文档,以及活跃的开发者论坛。下面我们就通过对DCM图像进行无损压缩这一任务来对比学习一下mDCM与DCMTK开源库的不同。
DCMTK的说明文档中对于dcmjpeg包的介绍中,就直接给出了一个利用JPEG无损压缩的实例。具体代码如下:
/***************************************************************************** dcmjpeg程序包 dcmjpeg提供了一个压缩/解压缩库以及可用工具。该模块包含一些类,可将DICOM图像对象在非压缩和JPEG压缩表示(传输协议)之间转换。无失真和有失真JPEG处理都被支持。这个模块实现了一族codec(编码解码器,由DcmCodec类派生而来),可以将这些codec在codec list中注册,codec list是由dcmdata模块保存的。 主要接口类: --DJEncoderRegistration: 一个singleton(孤立)类,为所有支持的JPEG处理注册编码器。在djencode.h中定义。 --DJDecoderRegistration: 一个singleton(孤立)类,为所有支持的JPEG处理注册解码器。在djdecode.h中定义。 --DJCodecEncoder: JPEG编码器的一个抽象codec类。This abstract class contains most of the application logic needed for a dcmdata codec object that implements a JPEG encoder using the DJEncoder interface to the underlying JPEG implementation. This class only supports compression, it neither implements decoding nor transcoding. 在djcodece.h中定义。 --DJCodecDecoder: JPEG解码器的一个抽象codec类。This abstract class contains most of the application logic needed for a dcmdata codec object that implements a JPEG decoder using the DJDecoder interface to the underlying JPEG implementation. This class only supports decompression, it neither implements encoding nor transcoding. 工具: dcmcjpeg: Encode DICOM file to JPEG transfer syntax dcmdjpeg: Decode JPEG-compressed DICOM file dcmj2pnm: Convert DICOM images to PGM, PPM, BMP, TIFF or JPEG dcmmkdir: Create a DICOMDIR file 举例: --用无失真JPEG压缩一幅DICOM图像文件。 *****************************************************************************/ DJEncoderRegistration::registerCodecs(); // register JPEG codecs DcmFileFormat fileformat; if (fileformat.loadFile("test.dcm").good()) { DcmDataset *dataset = fileformat.getDataset(); DcmItem *metaInfo = fileformat.getMetaInfo(); DJ_RPLossless params; // codec parameters, we use the defaults // this causes the lossless JPEG version of the dataset to be created dataset->chooseRepresentation(EXS_JPEGProcess14SV1TransferSyntax, params); // check if everything went well if (dataset->canWriteXfer(EXS_JPEGProcess14SV1TransferSyntax)) { // force the meta-header UIDs to be re-generated when storing the file // since the UIDs in the data set may have changed delete metaInfo->remove(DCM_MediaStorageSOPClassUID); delete metaInfo->remove(DCM_MediaStorageSOPInstanceUID); // store in lossless JPEG format fileformat.saveFile("test_jpeg.dcm", EXS_JPEGProcess14SV1TransferSyntax); } } DJEncoderRegistration::cleanup(); // deregister JPEG codecs
(具体的工程配置如前一篇博文所述http://blog.csdn.net/zssureqh/article/details/38460445,在此就不在重复介绍了)
通过这段代码可以顺利实现对DCM图像的JPEG无损压缩。如下图所示,
利用Sante DICOM Editor专业DCM图像浏览编辑器打开压缩前后的图像,发现图像质量没有差别,压缩前实际大小为4572K,压缩后为1774K,压缩效果良好。
设想:既然mDCM开源库就是对DCMTK开源库的封装,那么两个开源库中应该会有相对应的功能相同或类似的函数。有DCMTK实例中的代码可知,示例中只调用了DcmFileFormat的loadFile、saveFile和DcmDataset的chooseRepresentation和canWriteXfer四个函数,而且从函数名称上看,就知道实际达到压缩效果的应该是DcmDataset的chooseRepresentation和canWriteXfer的两个函数,那么接下来我们看看mDCM开源库下的DcmDataset是否有相对应的函数呢?
通过VS2012的对象浏览器可以看到,mDCM开源库下的Dicom.Data命名空间中DcmDataset类中的确拥有一个类似的函数ChangeTransferSyntax,如下图:
猜测:直接调用mDCM的Load、ChangeTransferSyntax和Save三个函数,应该可以实现与DCMTK相同的效果,即完成对DCM的JPEG无损压缩。
具体代码如下,
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Dicom; using Dicom.Data; using Dicom.Codec; using Dicom.Codec.JpegLs; namespace JpegLossLess { class Program { static void Main(string[] args) { DicomCodec.RegisterCodecs(); string fName = string.Format("d:\\dcm\\test.dcm"); DicomFileFormat ff = new DicomFileFormat(); ff.Load(fName, DicomReadOptions.Default | DicomReadOptions.DeferLoadingPixelData); DcmJpegLsParameters JpegParameters = new DcmJpegLsParameters(); ff.Dataset.ChangeTransferSyntax(DicomTransferSyntax.JPEGProcess14SV1, JpegParameters); string OutFile = string.Format(@"d:\dcm\outfile.dcm"); ff.Save(OutFile, DicomWriteOptions.Default); } } }
工程顺利编译成功,运行调试后,也同样出现了大小为1774K的文件,但是利用Sante DICOM Editor打开该文件时,出现错误,如下图所示:
利用DCMTK开源库的工具包dcmdump.exe查看利用mDCM压缩后的文件outfile.dcm,输出如下错误提示:
警告(Warning)提示(0008,0000)数据元素的数值有误,错误(Error)是出现了无法识别的标签和数据(f752,0e57),经过查看DICOM3.0标准,并未发现有(f752,0e57)该标签,利用UltraEdit打开DCMTK压缩后的文件test_jpeg.dcm和mDCM压缩的文件outfile.dcm,通过查找功能发现,(f752,0e57)字段实际上是标准的JPEG无损压缩后的(7fe0,0010)字段的Value Field内容(如下图所示),因此猜测应该是mDCM压缩后的文件头中某个字段写入有误,导致在读取数据体的时候并未按照原本的DICOM3.0标准去读取。
利用DCMTK的工程来读取我们利用mDCM压缩后的文件outfile.dcm,结果单步调试进入后,利用Load函数读取Jpeg压缩后的图像时,metainfo部分是没有问题的。但是当读取到dataset时,对于(0008,0000)元素的读取有误,正确的(0008,0000)元素的解析方式为
元素标签,即(group,element)为:08 00 00 00——(0008,0000)
元素类型,即VR为:55 4C——UL
元素长度,即VL为:04 00——0004(长度为4)
元素值域,即Value Field:B8 00 00 00——00000008(值为184)
但是在读取dataset时,将55 4c 04 00全部当成了长度来读取,因此猜测是将原本为ExplicitUL格式的元素当做了ImplicitVR格式来读取了,文件流的指针_streamPosition直接从0x0000000000000160直接跳转到了0x0000000000044db5,如下图所示:
因此尝试在mDCM的c#工程中添加手动修改文件元信息中传输语义的语句,
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Dicom; using Dicom.Data; using Dicom.Codec; using Dicom.Codec.JpegLs; namespace JpegLossLess { class Program { static void Main(string[] args) { /**************************************************** * 对比C++中DCMTK对于DICOM进行JPEG无损压缩,来学习C# * 中Dicom库的使用 * 2014-08-06 * zssure ****************************************************/ DicomCodec.RegisterCodecs(); string fName = string.Format(@"d:\dcm\test.dcm"); DicomFileFormat ff = new DicomFileFormat(); ff.Load(fName, DicomReadOptions.Default | DicomReadOptions.DeferLoadingPixelData); DcmJpegLsParameters JpegParameters = new DcmJpegLsParameters(); ff.FileMetaInfo.TransferSyntax = DicomTransferSyntax.JPEGProcess14SV1; ff.Dataset.ChangeTransferSyntax(DicomTransferSyntax.JPEGProcess14SV1, JpegParameters); string OutFile = string.Format(@"d:\dcm\outfileJpeg22.dcm"); ff.Save(OutFile, DicomWriteOptions.Default); } } }
工程编译后,能够顺利完成压缩DCM的功能,至此利用mDCM对DICOM图像进行JPEG无损压缩的目的已经实现。
DICOM3.0标准的第10部分中,有对于dcm文件存储格式的详细介绍,其中对于传输语义的介绍如下:
1)Except for the 128 byte preamble and the 4 byte prefix, the File Meta Information shall be encoded using theExplicit VR Little Endian Transfer Syntax (UID=1.2.840.10008.1.2.1) as defined in DICOM PS 3.5. Values of each File Meta Element shall be padded when necessary to achieve an even length, as specified in PS 3.5 by their corresponding Value Representation. The Unknown (UN) Value Representation shall not be used in the File Meta Information. For compatibility with future versions of this Standard, any Tag (0002,xxxx) not defined in Table 7.1-1 shall be ignored. Values of all Tags (0002,xxxx) are reserved for use bythis Standard and later versions of DICOM. Data Elements with a group of 0002 shall not be used in datasets other than within the File Meta Information
2)The Transfer Syntax used to encode the DataSet cannot be changed within the Data Set; i.e., the Transfer Syntax UID Data Element may not occur anywhere within the Data Set, e.g., nested within a Sequence Item.
因此DCM文件元信息中的标签(0002,0010),即传输语义,对于DCM文件的数据体Dataset的读取起到关键的作用。通过此次的mDCM开源库与DCMTK开源库的比较发现,两者虽然大多的函数都相同,且名称和功能都类似,但是对于细节部分应该注意。
现在对两个开源库对DCM文件的JPEG无损压缩功能所需要调用的函数进行一个对比分析,以找到两者之间的差别所在,具体分析如下表
mDCM |
DCMTK |
1) DicomFileFormat.Load,打开文件(也是通过文件流的方式一一读取DCM文件的各个信息到内存中) 2) DicomFileFormat.Dataset.ChangeTransferSyntax,该函数与DCMTK中的chooseRepresentation函数类似,在参数中都需要指出新的传输语义,函数内部会根据新的传输语义来修改数据体的存储方式。该函数主要完成的功能是: 比较新旧传输语义、根据新旧语义决定数据体是否解压缩或压缩(Dicom.Codec.Encode或者Dicom.Codec.Decode)。 3) DicomFileFormat.Save,存储文件,但是该函数中并不需要填写新的传输语义 【注】:这一点与DCMTK中的saveFile函数不同。这也就是上个周C#版本的mDCM实现对DCM数据的JPEG无损压缩后无法顺利读取的原因。因为数据体存储格式不是按照文件元信息中指定的传输语义存储的,或者说文件元信息中的传输语义没有修改为JPEG无损压缩的方式。 |
1) DicomFileFormat::loadFile,导入文件,主要是DcmMetaInfo和DcmDataset两部分; 2) Dataset::chooseReresentation,参数中会出现新旧传输语义TransferSyntax,函数根据新的语义对相应数据(主要是像素数据)进行处理,会调用DcmPixelData::canChooseRepresentation、DcmPixelData::chooseRepresentation 3) Dataset::canWriteXfer,参数中是新修改后的传输语义。 4) DcmFileFormat::saveFile,参数中需要指出修改后的传输语义。 ——》随后会调用dcfilefo.cc文件中的validateMetaInfo函数(该函数中也需要指定新的传输语义)。 ——》对文件元信息的各个元素分别调用DcmMetaInfo::search和chekMetaHeaderValue两个函数(在该函数内,会检测各个元信息元素是否存在,不存在会新建之并插入,其参数中就需要指出新的传输语义) ——》DcmElement::putString将新的传输协议写入到MetaInfo中。(基本调用流程如下图。 |
时间:2014-08-11