转自:https://www.cnblogs.com/mfrbuaa/p/4004114.html 有修订
背景介绍:
近期项目需求,需要使用C#进行最新的UI和相关DICOM3.0医学图像模块的开发。在C++语言下,我使用的是应用最广泛的DCMTK开源库,在本专栏的起初阶段的大多数博文都是对DCMTK开源库的介绍和学习。眼下因为项目需要,现开始对mDCM开源库继续学习分析,因此本专栏接下来的文章大多以mDCM开源库为例进行医学图像的解说,DCMTK因为是C++语言开发的,所以作为我学习和剖析mDCM开源库的原始根据,我们并未放弃对DCMTK开源库的学习,而是通过更加细致的研读和分析DCMTK的C++源代码,从而更好更迅速的切换到C#语言环境下的医学图像处理。
DCMTK、mDCM(fo-dicom)的关系:
DCMTK的官网上有具体的说明文档,对该开源库的各个类,以及类之间的依赖关系进行了清晰的阐述。是学习DICOM3.0医学最新标准不可或缺的资源。其官网网址是:http://www.dcmtk.org/,活跃的开发人员论坛地址是:http://forum.dcmtk.org/index.php。
mDCM眼下了解是从DCMTK开源库转过来的,或者说是该开源项目的另一个分支,是用C#语言对C++版本号的医学图像开源库的再次组织和封装,其项目托管在GitHub上的官方网址是:https://github.com/rcd/mdcm。此处就需要提到fo-dicom了,该开源库是mDCM的升级版本号,里面添加了几大特性,详情可參见GitHub网址:https://github.com/rcd/fo-dicom。
大致上这三者的关系就是如此,更说明了我们依旧要以DCMTK开源库为根据,来高速学习和剖析mDCM(fo-dicom)开源库,要非常好的借助于DCMTK开源库丰富而具体的说明文档,以及活跃的开发人员论坛。以下我们就通过对DCM图像进行无损压缩这一任务来对照学习一下mDCM与DCMTK开源库的不同。
DCMTK与mDCM对DCM图像进行JPEG无损压缩的对照学习:
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, ¶ms); // 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
(详细的project配置如前一篇博文所述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(); //Dicom.Codec.JpegLs.DcmJpegLsCodec.Register(); string fName = string.Format("d:\\dcm\\test.dcm"); DicomFileFormat ff = new DicomFileFormat(); ff.Load(fName, DicomReadOptions.Default | DicomReadOptions.DeferLoadingPixelData);//ff.Load(fName, DicomReadOptions.None); DcmPixelData pixels = new DcmPixelData(ff.Dataset);DcmJpegLsParameters JpegParameters = new DcmJpegLsParameters(); ff.FileMetaInfo.TransferSyntax = DicomTransferSyntax.JPEGProcess14SV1;ff.Dataset.ChangeTransferSyntax(DicomTransferSyntax.JPEGProcess14SV1, JpegParameters);string OutFile = string.Format(@"d:\dcm\outfile.dcm"); ff.Save(OutFile, DicomWriteOptions.Default); } } }
project顺利编译成功,执行调试后,也相同出现了大小为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的project来读取我们利用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#project中加入手动改动文件元信息中传输语义的语句,
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); } } }
project编译后,可以顺利完成压缩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中。(基本调用流程例如以下图。 |