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

背景:

上一篇博文DICOM:DICOM万能编辑工具之Sante DICOM Editor介绍了DICOM万能编辑工具,在日常使用过程中发现,“只要Sante DICOM Editor打不开的数据,基本可以判定此DICOM文件格式错误(准确率达99.9999%^_^)”。在感叹Sante DICOM Editor神器牛掰的同时,想了解一下其底层是如何实现的。通过日常使用以及阅读软件帮助手册推断其底层依赖库很可能是dcmtk,就如同本人使用dcmtk、fo-dicom、dcm4che3等诸多DICOM开源库遇到的兼容性问题类似,——dcmtk兼容性最强,fo-dicom次之,dcm4che3最差

问题:

本篇通过对比dcmtk3.6与dcm4che3.x解析同一特殊dicom文件包含非标准VR的元素)分析dcmtk、dcm4che以及fo-dicom数据加载的兼容性问题。
特殊的dicom文件内容如下:
28 00 20 01 20 20 02 00 30 F8,具体描述如下:

使用dcmtk与fo-dicom加载数据时都未出现错误,例如dcmtk加载数据时的提示如下:

由此可以看出dcmtk已经顺利识别出了非标准VR的元素(0028,0120),并成功加载。
虽然使用fo-dicom加载数据没有出现错误,但是对于上述非标准VR的元素(0028,0120)后的元素未顺利加载,如下图所示:

而dcm4che3加载过程中直接弹出了错误,如下所示:

问题分析:

出现该问题的原因是dcm4che3和fo-dicom在解析0028,0120元素时,对于20 20的非标准VR无法识别。下文中将通过分析dcm4che3与dcmtk的源码来定位问题的具体位置并给出解决方案(此处暂时只对比分析了dcm4che3.3.8最新版与dmctk3.6的源码,对于fo-dicom的源码分析待后续整理完成后再补充)

1. dcmtk3.6源码:

使用dcmtk编写本次数据加载测试工程,简单的示例代码如下:

<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>

单步调试,可以知道dcmtk加载dicom文件的流程如下:

  1. 创建DcmMetaInfo、DcmDataset元素
  2. 分别加载DcmMetaInfo、DcmDataset元素
  3. 使用DcmItem中的readGroupLength、readTagAndLength、readSubElement逐步加载DcmMetaInfo、DcmDataset的各个子元素。

在DcmItem类中对于非标准VR元素有相应的警告提示信息,

<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>

在警告后,对于非标准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标准PS5的7.1.2有对于非标准VR的相关描述,如下:

2. dcm4che3.3.8源码:

再对比dcm4che3.3.8的源码,单步调试发现,对于0028,0120VR=20,20,被dcmtk直接标记为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标准对于VR=UN(unknown)类型的标签约束来定义的,即,其VR字段应该是四个字节。然而此处0028,0120VR=20,20后的Value Length只有两个字节02 00。因此导致dcm4che3在加载0028,0120元素时,将其长度错误地解析为4163895298,即十六进制的F8 30 00 02,如下图所示:

解决方案:

至此我们找到了dcm4che3错误解析0028,0120VR=20,20非标准VR元素的原因。对于这种非标准VR不能统一当做VR.UN类型进行处理,而应该根据其后续的Value Length的具体长度为2或者4来进行分类处理关于该问题后续博文会继续深入剖析,请注意),需要修改的地方有两处:

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>

测试文件下载:

本文中使用的测试数据已经上传到了我Github的CSDN仓库中,可自行下载,为了保护患者隐私已经进行了匿名化处理。
Download Non-standard VR test dcm file

后续博文介绍:

1. 由dcm4che3.x库看Java流操作之”流的拷贝”
2. Eclipse自动编译dcm4che3.x源码
3. DICOM三大开源库对比分析之“数据加载”(续)





作者:zssure@163.com
时间:2015-09-05




 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/435946.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

wince Hive注册表实现机制

WinCE下的注册表可以分为两种&#xff0c;一种是RAM based&#xff0c;另外一种是HIVE based了.这要根据你在PB里添加的是哪种配置. 用PB向导新建的WinCE工程文件默认会用RAM based. Hive 注册表可以实现掉电不丢失数据.那它是如何实现这种机制的呢. RAM based 注册表因为是把信…

html中点击照片时放大缩小,基于jquery实现一张图片点击鼠标放大再点缩小

. 代码如下:var isopen false;var newImg;var w 200; //将图片宽度200var h 200; // 将图片高度 200$(document).ready(function(){$("img").bind("click", function(){newImg this;if (!isopen){isopen true;$(this).width($(this).width() w);$(th…

css入门之head区设置

收藏夹小图标 如果你将本站加入收藏夹&#xff0c;可以看到在收藏夹网址之前的IE图标变成了本站特别的图标。要实现这样效果很简单&#xff0c;首先制作一个16x16的icon图标&#xff0c;命名为favicon.ico&#xff0c;放在根目录下。然后将下面的代码嵌入head区&#xff1a; &l…

初识WINCE的HIVE注册表

要点&#xff1a;1、WINCE5.0的注册表共分为两类&#xff0d;&#xff0d;RAM based及HIVE based&#xff1b;2、RAM based注册表特点是在内核启动时&#xff0c;被释放到内存&#xff0c;用户可以修改&#xff0c;但由于是基于内存的方式&#xff0c;所以在系统掉电后&#xf…

【转】Dicom基础知识

DICOM 文件可以大致分为两部分&#xff1a; 一部分&#xff1a;与 图像 相关 的 元 信息 &#xff0c;包括患者信息&#xff0c;检查信息&#xff0c;序列信息&#xff0c;图像信息等等。 另一部分&#xff1a;图像的像素数据。 在解析DICOM文件中的像素数据的时候&#xff0c;…

04751计算机网络安全讲解,【19份】04751计算机网络安全自考试卷_历年真题自考答案及解析_湖南080901计算机科学与技术(原B080702计算机及应用)专业-自考生资料网...

1、资料如何使用本商城提供资料为WORD版&#xff0c;可打印成纸质版&#xff0c;结合备考习惯&#xff0c;营造考试氛围。支持手机查看&#xff0c;随时随地&#xff0c;高效学习。WORD文档也可直接用于电脑端学习&#xff0c;快速浏览&#xff0c;永久使用。2、文档无法编辑&a…

湖北省汉十高速公路项目接近尾声,所想所感真的值得写写

湖北省汉十高速公路的项目是我接的第一个B/S结构的软件项目&#xff0c;总体感觉吧&#xff1a;有点粗糙。虽然基本功能已经实现。想来不经意的来了一个公司竟然还能做一个这样的项目。前前后后2个月的开发周期&#xff0c;也让我感到做此类项目&#xff0c;需要你的技术点很多…

WINCE恢复默认HIVE注册表的方法

当Wince使用了HIVE注册表后&#xff0c;每次用户的注册表改动将得到保存&#xff0c;但是在某些应用场合需要将注册表还原成为出厂的默认设置&#xff0c;通常要求能够在AP中通过点击一个按钮来实现这种clean boot。使用我前面的文章的方法配置的HIVE系统注册表和HIVE用户注册表…

【转】DICOM协议新手入门资料-DICOM协议详细解释!!

转自&#xff1a;https://blog.csdn.net/zhuwei0710/article/details/82620036 数字影像传输标准协议的初衷&#xff0c;是为了在不同厂商生产的数字影像设备之间实现影像及其附属信息的调用。这个标准的最初版本是所谓ACR&#xff08;美国放射学会&#xff09;-NEMA&#xff…

博士期间要注意的几个问题

博士期间要注意的几个问题 读博士是一件非常艰苦和漫长的过程&#xff0c;相信在经历了博士期间的洗礼&#xff0c;一个人的思想会得到升华&#xff0c;视野会得到拓宽&#xff0c;人生会更加充实和丰富&#xff0c;为以后的人生打好了坚实的基础&#xff01; 在博士期间&#…

html设置页面编码gbk,GBK及UTF-8网页编码定义与应用

网页编码英文译为web page encoding&#xff0c;是在网页中指定其特定的字符编码格式的库。GBK是国家标准GB2312基础上扩容后兼容GB2312的标准。GBK的文字编码是用双字节来表示的&#xff0c;即不论中、英文字符均使用双字节来表示&#xff0c;为了区分中文&#xff0c;将其最高…

VxWorks常用的命令

1&#xff0e;与任务相关的命令 sp function,[arg1],...,[arg9] &#xff0d;启动任务&#xff0c;最多接受9个参数&#xff0c;默认的优先级100、堆栈20000字节 period n,function,[arg1],...,[arg8] &#xff0d;创建一个周期调用function的任务&#xff0c;周期为n秒&#x…

【转】Wireshark网络抓包(三)——网络协议

转自&#xff1a;https://www.cnblogs.com/strick/p/6262284.html 一、ARP协议 ARP&#xff08;Address Resolution Protocol&#xff09;地址解析协议&#xff0c;将IP地址解析成MAC地址。 IP地址在OSI模型第三层&#xff0c;MAC地址在OSI第二层&#xff0c;彼此不直接通信…

jquery技巧总结-转载

jquery技巧总结一、简介1.1、概述随着WEB2.0及ajax思想在互联网上的快速发展传播&#xff0c;陆续出现了一些优 秀的Js框架&#xff0c;其中比较著名的有Prototype、YUI、jQuery、mootools、Bindows以及国内的JSVM框架等&#xff0c;通过将这些JS 框架应用到我们的项目中能够使…

川农计算机网络题库,川农网院20秋《计算机网络》期末机考

《计算机网络》期末机考, j9 g5 q- D# p w1.[单选题] 般在10M或100M局域网中&#xff0c;至少要用双绞线多少蕊( ) p2 N: n5 g d A.6) p. a5 B& d2 Z7 l B.104 ^5 l$ 0 C" j& L f# \& " \1 Y C.8, |6 j: y) F2 }; }$ kD.43 o I U$ S0 ) c/ |;…

Vxworks系统学习之一----任务

1.任务队列 vxworks维护4个队列&#xff1a; tick队列 ready队列 active队列 pend队列 一&#xff0c;tick队列 当执行taskDelay时&#xff0c;任务会被延长一段时间执行&#xff0c;此时&#xff0c;任务就会被加入到tick队列中&#xff0c;任务处于Delay状态&#xff…

【转】Wireshark网络抓包(四)——工具

转自&#xff1a;https://www.cnblogs.com/strick/p/6344486.html 一、基本信息统计工具 1&#xff09;捕获文件属性&#xff08;Summary&#xff09; 1. File&#xff1a;了解抓包文件的各种属性&#xff0c;例如抓包文件的名称、路径、文件所含数据包的规模等信息 2. Tim…

silverlight + wcf(json格式) + sqlserver存储过程分页

silverlight并没有提供现成的分页控件&#xff0c;百度了一圈&#xff0c;也没有发现aspx中好用的类似AspNetPager成熟控件&#xff0c;网上现有的一些分页代码&#xff0c;很多也是基于1.0版本的&#xff0c;silverlight2.0的并不多&#xff0c;自个儿琢磨了一下&#xff0c;发…

什么是指利用计算机和现代,现代计算机一般指什么计算机?

现代计算机一般指通用数字电子计算机&#xff0c;它是当今世界电子计算机行业中的主流&#xff0c;其内部处理的是一种称为符号信号或数字信号的电信号&#xff1b;它的主要特点是“离散”&#xff0c;在相邻的两个符号之间不可能有第三种符号存在。电子计算机分为模拟式电子计…

程序员成长之路

程序员的成长经历往往很相似&#xff0c;大部分的人走过了最前面相同的一段路&#xff0c;而有的人则走得更远。总结自己这些年来的历程&#xff0c;这也许能让年轻的程序员少走一些弯路&#xff0c;成长得更快&#xff1b;或许更好一些&#xff0c;能让大家从中得到一些启发&a…