日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【转】DICOM:DICOM三大开源库对比分析之“数据加载”

發(fā)布時間:2023/12/10 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【转】DICOM:DICOM三大开源库对比分析之“数据加载” 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

背景:

上一篇博文DICOM:DICOM萬能編輯工具之Sante DICOM Editor介紹了DICOM萬能編輯工具,在日常使用過程中發(fā)現(xiàn),“只要Sante DICOM Editor打不開的數(shù)據(jù),基本可以判定此DICOM文件格式錯誤(準(zhǔn)確率達(dá)99.9999%^_^)”。在感嘆Sante DICOM Editor神器牛掰的同時,想了解一下其底層是如何實現(xiàn)的。通過日常使用以及閱讀軟件幫助手冊推斷其底層依賴庫很可能是dcmtk,就如同本人使用dcmtk、fo-dicom、dcm4che3等諸多DICOM開源庫遇到的兼容性問題類似,——dcmtk兼容性最強,fo-dicom次之,dcm4che3最差

問題:

本篇通過對比dcmtk3.6與dcm4che3.x解析同一特殊dicom文件包含非標(biāo)準(zhǔn)VR的元素)分析dcmtk、dcm4che以及fo-dicom數(shù)據(jù)加載的兼容性問題。
特殊的dicom文件內(nèi)容如下:
28 00 20 01?20 20?02 00?30 F8,具體描述如下:

使用dcmtk與fo-dicom加載數(shù)據(jù)時都未出現(xiàn)錯誤,例如dcmtk加載數(shù)據(jù)時的提示如下:

由此可以看出dcmtk已經(jīng)順利識別出了非標(biāo)準(zhǔn)VR的元素(0028,0120),并成功加載。
雖然使用fo-dicom加載數(shù)據(jù)沒有出現(xiàn)錯誤,但是對于上述非標(biāo)準(zhǔn)VR的元素(0028,0120)后的元素未順利加載,如下圖所示:

而dcm4che3加載過程中直接彈出了錯誤,如下所示:

問題分析:

出現(xiàn)該問題的原因是dcm4che3和fo-dicom在解析0028,0120元素時,對于20 20的非標(biāo)準(zhǔn)VR無法識別。下文中將通過分析dcm4che3與dcmtk的源碼來定位問題的具體位置并給出解決方案(此處暫時只對比分析了dcm4che3.3.8最新版與dmctk3.6的源碼,對于fo-dicom的源碼分析待后續(xù)整理完成后再補充)

1. dcmtk3.6源碼:

使用dcmtk編寫本次數(shù)據(jù)加載測試工程,簡單的示例代碼如下:

<span style="color:#000000"><code class="language-C"><span style="color:#abb2bf">int</span> main() {OFLog::configure(OFLogger::TRACE_LOG_LEVEL);<span style="color:#abb2bf">char</span>* ifname = <span style="color:#abb2bf">"c:\\1.dcm"</span>;E_FileReadMode readMode = <span style="color:#abb2bf"><em>/*ERM_fileOnly*/</em></span>ERM_autoDetect;E_TransferSyntax xfer = EXS_Unknown;Uint32 maxReadLength = DCM_MaxReadLength;<span style="color:#abb2bf">bool</span> loadIntoMemory = <span style="color:#abb2bf">true</span>;DcmFileFormat dfile;DcmObject *dset = &dfile;<span style="color:#abb2bf">if</span> (readMode == ERM_dataset) dset = dfile.getDataset();OFCondition cond = dfile.loadFile(ifname, xfer, EGL_noChange, maxReadLength, readMode);<span style="color:#abb2bf">if</span> (cond.bad()){<span style="color:#abb2bf">return</span> <span style="color:#abb2bf">1</span>;}<span style="color:#abb2bf">return</span> <span style="color:#abb2bf">0</span>; }</code></span>

單步調(diào)試,可以知道dcmtk加載dicom文件的流程如下:

  • 創(chuàng)建DcmMetaInfo、DcmDataset元素
  • 分別加載DcmMetaInfo、DcmDataset元素
  • 使用DcmItem中的readGroupLength、readTagAndLength、readSubElement逐步加載DcmMetaInfo、DcmDataset的各個子元素。
  • 在DcmItem類中對于非標(biāo)準(zhǔn)VR元素有相應(yīng)的警告提示信息,

    <span style="color:#000000"><code class="language-C">/* <span style="color:#abb2bf">if</span> the VR which was read is not a standard VR, print a <span style="color:#abb2bf">warning</span> */<span style="color:#abb2bf">if</span> (!vr.isStandard()){OFOStringStream oss;oss << <span style="color:#abb2bf">"DcmItem: Non-standard VR '"</span><< ((OFstatic_cast(unsigned char, vrstr[<span style="color:#abb2bf">0</span>]) < <span style="color:#abb2bf">32</span>) ? <span style="color:#abb2bf">' '</span> : vrstr[<span style="color:#abb2bf">0</span>])<< ((OFstatic_cast(unsigned char, vrstr[<span style="color:#abb2bf">1</span>]) < <span style="color:#abb2bf">32</span>) ? <span style="color:#abb2bf">' '</span> : vrstr[<span style="color:#abb2bf">1</span>]) << <span style="color:#abb2bf">"' ("</span><< STD_NAMESPACE hex << STD_NAMESPACE setfill(<span style="color:#abb2bf">'0'</span>)<< STD_NAMESPACE setw(<span style="color:#abb2bf">2</span>) << OFstatic_cast(unsigned int, vrstr[<span style="color:#abb2bf">0</span>] & <span style="color:#abb2bf">0xff</span>) << <span style="color:#abb2bf">"\\"</span><< STD_NAMESPACE setw(<span style="color:#abb2bf">2</span>) << OFstatic_cast(unsigned int, vrstr[<span style="color:#abb2bf">1</span>] & <span style="color:#abb2bf">0xff</span>)<< <span style="color:#abb2bf">") encountered while parsing element "</span> << newTag << OFStringStream_ends;OFSTRINGSTREAM_GETSTR(oss, tmpString)/* encoding of this data element might be wrong, <span style="color:#abb2bf">try</span> to correct it */<span style="color:#abb2bf">if</span> (dcmAcceptUnexpectedImplicitEncoding.get()){DCMDATA_WARN(tmpString << <span style="color:#abb2bf">", trying again with Implicit VR Little Endian"</span>);/* put back read bytes to input stream <span style="color:#abb2bf">...</span> */inStream.putback();bytesRead = <span style="color:#abb2bf">0</span>;/* <span style="color:#abb2bf">...</span> and retry with Implicit VR Little Endian transfer syntax */<span style="color:#abb2bf">return</span> readTagAndLength(inStream, EXS_LittleEndianImplicit, tag, length, bytesRead);} <span style="color:#abb2bf">else</span> {DCMDATA_WARN(tmpString << <span style="color:#abb2bf">", assuming "</span> << (vr.usesExtendedLengthEncoding() ? <span style="color:#abb2bf">"4"</span> : <span style="color:#abb2bf">"2"</span>)<< <span style="color:#abb2bf">" byte length field"</span>);}OFSTRINGSTREAM_FREESTR(tmpString)}/* set the VR which was read <span style="color:#abb2bf">in</span> the above created tag object. */newTag.setVR(vr);/* increase counter by <span style="color:#abb2bf">2</span> */bytesRead += <span style="color:#abb2bf">2</span>;</code></span>

    在警告后,對于非標(biāo)準(zhǔn)VR元素的處理過程如下:

    <span style="color:#000000"><code class="language-C"> <span style="color:#abb2bf"><em>/* read the value in the length field. In some cases, it is 4 bytes wide, in other */</em></span><span style="color:#abb2bf"><em>/* cases only 2 bytes (see DICOM standard part 5, section 7.1.1) */</em></span><span style="color:#abb2bf">if</span> (xferSyn.isImplicitVR() || nxtobj == EVR_na) <span style="color:#abb2bf"><em>//note that delimitation items don't have a VR</em></span>{inStream.read(&valueLength, <span style="color:#abb2bf">4</span>); <span style="color:#abb2bf"><em>//length field is 4 bytes wide</em></span>swapIfNecessary(gLocalByteOrder, byteOrder, &valueLength, <span style="color:#abb2bf">4</span>, <span style="color:#abb2bf">4</span>);bytesRead += <span style="color:#abb2bf">4</span>;} <span style="color:#abb2bf">else</span> { <span style="color:#abb2bf"><em>//the transfer syntax is explicit VR</em></span>DcmVR vr(newTag.getEVR());<span style="color:#abb2bf">if</span> (vr.usesExtendedLengthEncoding()){Uint16 reserved;inStream.read(&reserved, <span style="color:#abb2bf">2</span>); <span style="color:#abb2bf"><em>// 2 reserved bytes</em></span>inStream.read(&valueLength, <span style="color:#abb2bf">4</span>); <span style="color:#abb2bf"><em>// length field is 4 bytes wide</em></span>swapIfNecessary(gLocalByteOrder, byteOrder, &valueLength, <span style="color:#abb2bf">4</span>, <span style="color:#abb2bf">4</span>);bytesRead += <span style="color:#abb2bf">6</span>;} <span style="color:#abb2bf">else</span> {Uint16 tmpValueLength;inStream.read(&tmpValueLength, <span style="color:#abb2bf">2</span>); <span style="color:#abb2bf"><em>// length field is 2 bytes wide</em></span>swapIfNecessary(gLocalByteOrder, byteOrder, &tmpValueLength, <span style="color:#abb2bf">2</span>, <span style="color:#abb2bf">2</span>);bytesRead += <span style="color:#abb2bf">2</span>;valueLength = tmpValueLength;}}</code></span>

    由上述代碼可知,0028,0120VR=20,20,被dcmtk解析為?EVR_UNKNOWN2B類型,如同代碼注釋中所描述:

    /// used internally for elements with unknown VR with 2-byte length field in explicit VR
    EVR_UNKNOWN2B

    DICOM標(biāo)準(zhǔn)PS5的7.1.2有對于非標(biāo)準(zhǔn)VR的相關(guān)描述,如下:

    2. dcm4che3.3.8源碼:

    再對比dcm4che3.3.8的源碼,單步調(diào)試發(fā)現(xiàn),對于0028,0120VR=20,20,被dcmtk直接標(biāo)記為UN類型,

    <span style="color:#000000"><code class="language-Java"><span style="color:#abb2bf">public</span> <span style="color:#abb2bf">static</span> VR <span style="color:#abb2bf">valueOf</span>(<span style="color:#abb2bf">int</span> code) {<span style="color:#abb2bf">try</span> {VR vr = VALUE_OF[indexOf(code)];<span style="color:#abb2bf">if</span> (vr != <span style="color:#abb2bf">null</span>)<span style="color:#abb2bf">return</span> vr;} <span style="color:#abb2bf">catch</span> (IndexOutOfBoundsException e) {}LOG.warn(<span style="color:#abb2bf">"Unrecogniced VR code: {0}H - treat as UN"</span>,Integer.toHexString(code));<span style="color:#abb2bf">return</span> UN;}</code></span>

    并且在dcm4che3中對于UN類型定義為

    此處UN類型是參照上述截圖中DICOM3.0標(biāo)準(zhǔn)對于VR=UN(unknown)類型的標(biāo)簽約束來定義的,即,其VR字段應(yīng)該是四個字節(jié)。然而此處0028,0120VR=20,20后的Value Length只有兩個字節(jié)02 00。因此導(dǎo)致dcm4che3在加載0028,0120元素時,將其長度錯誤地解析為4163895298,即十六進(jìn)制的F8 30 00 02,如下圖所示:

    解決方案:

    至此我們找到了dcm4che3錯誤解析0028,0120VR=20,20非標(biāo)準(zhǔn)VR元素的原因。對于這種非標(biāo)準(zhǔn)VR不能統(tǒng)一當(dāng)做VR.UN類型進(jìn)行處理,而應(yīng)該根據(jù)其后續(xù)的Value Length的具體長度為2或者4來進(jìn)行分類處理關(guān)于該問題后續(xù)博文會繼續(xù)深入剖析,請注意),需要修改的地方有兩處:

    1. 正確解析Non-standard VR:

    <span style="color:#000000"><code class="language-Java"><span style="color:#abb2bf"><em>//VR.java,Line 110</em></span> <span style="color:#abb2bf">public</span> <span style="color:#abb2bf">static</span> VR <span style="color:#abb2bf">valueOf</span>(<span style="color:#abb2bf">int</span> code) {<span style="color:#abb2bf">try</span> {VR vr = VALUE_OF[indexOf(code)];<span style="color:#abb2bf">if</span> (vr != <span style="color:#abb2bf">null</span>)<span style="color:#abb2bf">return</span> vr;} <span style="color:#abb2bf">catch</span> (IndexOutOfBoundsException e) {}LOG.warn(<span style="color:#abb2bf">"Unrecogniced VR code: {0}H - treat as UN"</span>,Integer.toHexString(code));<span style="color:#abb2bf"><em>//return UN;</em></span>LOG.warn(<span style="color:#abb2bf">"zssure:to solve non-standard VR,Unrecogniced VR code: {0}H - treat as UN"</span>,Integer.toHexString(code));<span style="color:#abb2bf">return</span> <span style="color:#abb2bf">null</span>;<span style="color:#abb2bf"><em>//zssure:to solve non-standard VR</em></span>} </code></span>

    2. 正確讀取Non-standard VR的VL:

    <span style="color:#000000"><code class="language-Java"><span style="color:#abb2bf"><em>//DicomInputStream.java Line 386</em></span><span style="color:#abb2bf">public</span> <span style="color:#abb2bf">int</span> <span style="color:#abb2bf">readHeader</span>() <span style="color:#abb2bf">throws</span> IOException {<span style="color:#abb2bf">byte</span>[] buf = buffer;tagPos = pos; readFully(buf, <span style="color:#abb2bf">0</span>, <span style="color:#abb2bf">8</span>);<span style="color:#abb2bf">switch</span>(tag = ByteUtils.bytesToTag(buf, <span style="color:#abb2bf">0</span>, bigEndian)) {<span style="color:#abb2bf">case</span> Tag.Item:<span style="color:#abb2bf">case</span> Tag.ItemDelimitationItem:<span style="color:#abb2bf">case</span> Tag.SequenceDelimitationItem:vr = <span style="color:#abb2bf">null</span>;<span style="color:#abb2bf">break</span>;<span style="color:#abb2bf">default</span>:<span style="color:#abb2bf">if</span> (explicitVR) {vr = VR.valueOf(ByteUtils.bytesToVR(buf, <span style="color:#abb2bf">4</span>));<span style="color:#abb2bf"><em>//zssure:to solve non-standard VR</em></span><span style="color:#abb2bf"><em>//referred:dcmtk/dcitem.cc/readTagAndLength,Line 970</em></span><span style="color:#abb2bf">if</span>(vr == <span style="color:#abb2bf">null</span>){length = ByteUtils.bytesToUShort(buf, <span style="color:#abb2bf">6</span>, bigEndian);<span style="color:#abb2bf">return</span> tag; }<span style="color:#abb2bf"><em>//zssure:end</em></span><span style="color:#abb2bf">if</span> (vr.headerLength() == <span style="color:#abb2bf">8</span>) {length = ByteUtils.bytesToUShort(buf, <span style="color:#abb2bf">6</span>, bigEndian);<span style="color:#abb2bf">return</span> tag;}readFully(buf, <span style="color:#abb2bf">4</span>, <span style="color:#abb2bf">4</span>);} <span style="color:#abb2bf">else</span> {vr = VR.UN;}}length = ByteUtils.bytesToInt(buf, <span style="color:#abb2bf">4</span>, bigEndian);<span style="color:#abb2bf">return</span> tag;}</code></span>

    測試文件下載:

    本文中使用的測試數(shù)據(jù)已經(jīng)上傳到了我Github的CSDN倉庫中,可自行下載,為了保護(hù)患者隱私已經(jīng)進(jìn)行了匿名化處理。
    Download Non-standard VR test dcm file

    后續(xù)博文介紹:

    1. 由dcm4che3.x庫看Java流操作之”流的拷貝”
    2. Eclipse自動編譯dcm4che3.x源碼
    3. DICOM三大開源庫對比分析之“數(shù)據(jù)加載”(續(xù))





    作者:zssure@163.com
    時間:2015-09-05




    ?

    總結(jié)

    以上是生活随笔為你收集整理的【转】DICOM:DICOM三大开源库对比分析之“数据加载”的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。