首页 > 代码库 > DICOM医学图像处理:开源库mDCM与DCMTK的比较分析(一),JPEG无损压缩DCM图像(续)

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

背景:

        上周通过单步调试,找出了开源库mDCM与DCMTK在对DICOM图像进行JPEG无损压缩时的细小区别,并顺利实现了在C++和C#环境下对DICOM图像的压缩。但是问题接踵而至啊,随着项目的深入,发现在单独的测试工程中可以实现的mDCM版本,在嵌入到项目整体中后,却意外地出现了错误,并未顺利实现DICOM图像的JPEG无损压缩。因此需要继续详细对比分析mDCM与DCMTK两者,期望寻找原因。

问题分析:

        开启项目的日志功能后,得到的信息反馈为:

No registered codec for transfer syntax! 在 Dicom.Data.DcmDataset.ChangeTransferSyntax(DicomTransferSyntax newTransferSyntax, DcmCodecParameters parameters),在…………………………处。

        从日志得到的反馈来看,应该是JPEG的编码器注册失败。而编码器部分包含在mDCM开源库的Dicom.Codec64.dll程序集中。因此单步调试进入,查看工程是否顺利加载了Dicom.Codec64.dll模块

        首先单步进入的是上周测试用的独立工程JpegLossLess,通过在Program.cs中调用Dicom.Codec.DicomCodec.RegisterCodecs();使得程序进入到DicomCodec.cs文件,程序运行到静态类DicomCodec的静态方法RegisterCodecs内。

clip_image002

        如上图所示,方法RegisterCodecs内部通过C#的程序集的动态加载和反射技术,顺利识别了工程引用中添加的Dicom.dl程序集Dicom.Codec64.dll程序集

接下来单步调试到整体工程中,程序从主框架转移到我们手动添加的调用Dicom.Codec.DicomCodec.RegisterCodecs();函数处,如下图所示:

image

         经过几次的调试发现,使用RegisterCodecs函数并未顺利的注册JPEG编码器,识别出的17个程序集中只有Dicom.dll模块。通过浏览DicomCodec.cs文件源码发现,RegisterCodecs函数是静态类DicomCodec的静态函数,该函数实现的是自动注册JPEG所有编码器。继续浏览发现,静态类DicomCodec还有类似的其它函数,如public static void RegisterCodec(DicomTransferSyntax ts, Type type);public static void RegisterExternalCodecs(string path, string pattern);两个函数,分别是注册指定传输语义的解码器和注册指定路径下的程序集中的解码器。由于我们利用RegisterCodecs函数并未实现自动加载JPEG解码器的功能,而且工程中已经添加引用了DicomCodec64.dll程序集,并且在调试时刻VS2012的模块窗口已经显示顺利加载了DicomCodec64.dll程序集。所以此时决定尝试手动加载DicomCodec64.dll程序集,即用下面的代码替换原本的Dicom.Codec.DicomCodec.RegisterCodecs();语句,

       string path = System.IO.Directory.GetCurrentDirectory();
       string pattern = "Dicom.Codec64.dll";
       DicomCodec.RegisterExternalCodecs(path, pattern);

        此刻单步调试可以看到,已经成功的实现了DicomCodec64.dll程序集中JPEG解码器的注册,完成了将DICOM图像JPEG压缩的功能与整体工程的整合。

学习总结:

1)GetReferencedAssemblies函数能否返回工程中的所有引用程序集?

        通过对比上述的自动手动的注册代码,发现两者的最终都是利用的GetExportedTypes函数来完成注册,具体代码都是Type[] types = asm.GetExportedTypes();来提取相应的解码器,唯一不同的是自动注册中是利用AssemblyName[] referenced = main.GetReferencedAssemblies();提取该模块的引用程序集,而手动注册是利用的Assembly.LoadFile函数加载手动指定的程序集文件,难道是GetReferencedAssemblies函数出现了问题?GetReferencedAssemblies函数到底能不能返回我们工程中所有的引用程序集呢?

        在MSDN搜索一下GetReferencedAssemblies函数的功能,描述为:Gets the AssemblyName objects for all the assemblies referenced by this assembly.

乍一看,好像该函数是可以返回我们工程中所有加载的程序集的名称。但是仔细分析一下,描述中提到的是"this assembly”,此处this 应该指的是调用RetReferencedAssemblies函数的程序集,因此该函数应该获得的是当前模块所引用的所有程序集,而并不是我们起初认为的整个工程的引用程序集。经过漫长的搜索,终于在一篇stackoverflow的博文(http://stackoverflow.com/questions/3971793/what-when-assembly-getreferencedassemblies-returns-exe-dependency)中找到了对“提取工程所有依赖程序集”的相关说明,文中作者不仅给出了实现的方法,而且给出了为什么GetReferencedAssemblies函数没有返回工程所有引用程序集的原因(http://msdn.microsoft.com/en-us/magazine/cc163641.aspx)。此处简单的对其归纳一下,并借用一下原作者的图:

        如下图所示,假设我们在模块A中调用了GetReferencedAssemblies函数,那么按照MSDN中对应的解释,函数应该返回this——即A所引用(更确切的说是直接应用)的程序集B、C、D。然而如下图左所示,程序集C和D又分别引用了其他的程序集,所以此处我们并未直接获取到整个工程中所有的程序集。因此自动加载的时候并未顺利的返回我们需要的Dicom.Codec64程序集。

        说到这里,我想提取工程所有引用程序集的方法已经呼之欲出了,最简单的就是我们可以对GetReferencedAssemblies的首次返回值进行递归调用,那么自然而然就可以得到所有的引用程序集A-J。但是博文中作者是按照上图右中的方式来提取所有引用程序集的,因为递归会影响程序的性能,尤其是程序模块众多的时候。简言之,就是利用算法导论中的“前序遍历”来提取所有的引用程序集,具体代码可以从给出的参考博文下载。

2)C#的静态类与Singleton设计模式

        在对比mDCM与DCMTK两个开源库对于JPEG解码器注册的源代码后,发现在用C++完成的DCMTK开源库中,使用的是Singleton设计模式的DcmCodecList类来完成JPEG各种解码器注册的,而用C#编写的mDCM开源库使用的是C#的静态类public static DicomCodec。这两种方式可以实现相同的功能,由于刚开始从C++转向C#,对于这两者的区别不是很清楚,因此搜索了一下,仅摘取部分重要片段贴在博文中,便于以后查阅。

【摘要1】:http://bbs.csdn.net/topics/370008452

除了跨程序集的边界问题,static 类和模仿 GoF C++ 版的单件没有本质的区别。我感兴趣的讨论在于这两者在满足同样的动机的情况下,是否达成了同样的效果,我个人的看法是,静态类有简单和优雅的一面。事实上,在Java和C#方面,GoF的设计模式本身有问题,这就是经典的Double Lock Check问题(看 CLR via C#)。

粗略地说,在C# 4中,这些模式消失了:单件(静态类)、策略(委托和Lambda)、观察者(事件)、装饰(扩展方法)、工厂(部分靠反射实现)、代理(表达式树和动态类)、迭代器(yield return语法),等等,如果你按照GoF的实现来做这些,你反而舍近求远了。

最后,不光是 singleton,我对设计模式一个普遍的看法是,随着编程语言的进步,所有设计模式的实现都将消亡,而思想保存了下来。设计模式的本质也可以说是为了修饰语言的缺陷,一种优雅的语言,不需要设计模式(这个观点是我一个大学同学提出的,他也是一位 Ruby 社区的专家)。

【摘要2】:http://www.cnblogs.com/utopia/archive/2010/03/02/1676390.html

静态类的语义是全局唯一代码段,而单件的语义是全局唯一对象实例;

语义上是完全不同地,不能说起修饰都是“全局唯一”就放一块比较;

如果是这样那么所有public修饰的东西我们不是都得比较一翻了;

另外:如果要研究对象设计,那么请先抛开代码。对象设计是本身哲学性和世界观的表达。

如何把现实的东西用概念还原表达出来,才是对象设计的实质。而代码则是体现你头脑里那个概念模型的工具。

【摘要3】:http://blog.csdn.net/lyrebing/article/details/1902235

单例模式的目的是为了在程序中提供类的唯一实例,而且仅提供唯一的访问点。静态不需要实例,仅提供一个全局功能。使用单例可以继承,实现接口,而静态类不能。静态方法不能访问类中的实例字段,因为静态方法不是通过实例来访问的。而单例中的方法却可以访问那个唯一实例中的实例字段。静态方法在执行后,会释放掉它所创建的所有对象。而单例中的方法却可以保留。静态字段仅是提供全局的功能,大家共享同一内存位置。访问单例中的字段是类的唯一实例中的字段,大家只能访问这个实例的字段。

 

作者:zssure@163.com

时间:2014-08-17