背景介绍:

近期项目需求,须要使用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无损压缩。例如以下图所看到的,

DICOM医学图像处理:开源库mDCM与DCMTK的比較分析(一),JPEG无损压缩DCM图像-LMLPHP

利用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,例如以下图:

DICOM医学图像处理:开源库mDCM与DCMTK的比較分析(一),JPEG无损压缩DCM图像-LMLPHP

   推測:直接调用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打开该文件时,出现错误,例如以下图所看到的:

DICOM医学图像处理:开源库mDCM与DCMTK的比較分析(一),JPEG无损压缩DCM图像-LMLPHP

利用DCMTK开源库的工具包dcmdump.exe查看利用mDCM压缩后的文件outfile.dcm,输出例如以下错误提示:

DICOM医学图像处理:开源库mDCM与DCMTK的比較分析(一),JPEG无损压缩DCM图像-LMLPHP

警告(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)元素的解析方式为

DICOM医学图像处理:开源库mDCM与DCMTK的比較分析(一),JPEG无损压缩DCM图像-LMLPHP

可是在读取dataset时,将55 4c 04 00所有当成了长度来读取,因此推測是将原本为ExplicitUL格式的元素当做了ImplicitVR格式来读取了,文件流的指针_streamPosition直接从0x0000000000000160直接跳转到了0x0000000000044db5,例如以下图所看到的:

DICOM医学图像处理:开源库mDCM与DCMTK的比較分析(一),JPEG无损压缩DCM图像-LMLPHP

因此尝试在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文件存储格式的具体介绍,当中对于传输语义的介绍例如以下:

因此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中。(基本调用流程例如以下图。

DICOM医学图像处理:开源库mDCM与DCMTK的比較分析(一),JPEG无损压缩DCM图像-LMLPHP

作者:[email protected]

时间:2014-08-11

04-29 20:59