標籤:dicom 影像處理 mdcm dcmtk
背景:
上周通過單步調試,找出了開源庫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內。
如所示,方法RegisterCodecs內部通過C#的程式集的動態載入和反射技術,順利識別了工程引用中添加的Dicom.dl程式集和Dicom.Codec64.dll程式集。
接下來單步調試到整體工程中,程式從主架構轉移到我們手動添加的調用Dicom.Codec.DicomCodec.RegisterCodecs();函數處,如所示:
經過幾次的調試發現,使用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
單例模式的目的是為了在程式中提供類的唯一執行個體,而且僅提供唯一的訪問點。靜態不需要執行個體,僅提供一個全域功能。使用單例可以繼承,實現介面,而靜態類不能。靜態方法不能訪問類中的執行個體欄位,因為靜態方法不是通過執行個體來訪問的。而單例中的方法卻可以訪問那個唯一執行個體中的執行個體欄位。靜態方法在執行後,會釋放掉它所建立的所有對象。而單例中的方法卻可以保留。靜態欄位僅是提供全域的功能,大家共用同一記憶體位置。訪問單例中的欄位是類的唯一執行個體中的欄位,大家只能訪問這個執行個體的欄位。
[email protected]
時間:2014-08-17