【转】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,一经查实,立即删除!

相关文章

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…

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

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

【转】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;彼此不直接通信…

【转】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;在相邻的两个符号之间不可能有第三种符号存在。电子计算机分为模拟式电子计…

【转】VS中常用图标提示含义

转自&#xff1a;https://www.cnblogs.com/zhjason/articles/14044190.html 有增删 “类视图”和“对象浏览器”图标 “类视图”和“对象浏览器”显示表示代码实体的图标&#xff0c;如命名空间、类、函数和变量 。 下表展示和描述了图标。 图标描述图标描述图标描述图标描述…

二层和三层转发

二层转发的机制是什么?学习线程和报文转发线程。二层只跟MAC地址有关 与IP无关 所以在二层做IP&#xff0d;MAC绑定是无效的。 三层以太网交换机的转发机制主要分为两个部分&#xff1a; 二层转发和三层交换。      先讲二层转发流程。      1、 MAC地址介绍   MA…

【转】胶片曝光时的排版!!!!!!!

这个本来没啥 不是什么算法 绝技。 都不值得一提。 其实这个是医学影像胶片曝光时排版的一个逻辑。 dicom标准第三部分 主要是讲IOD定义 在第166页有这样的描述&#xff1a; 表C.13.5-1图象盒象素描述组件 属性名称 标记       说明 图象位置 (2020,0010)  …

计算机课数学,这4个专业,对数学要求很高,数学不好的慎选!

建筑学专业这个专业就是典型数学课程比较多的专业&#xff0c;如果数学成绩不好&#xff0c;真心建议千万不要选择这个专业&#xff0c;因为学习的课程与大多与数学有关&#xff0c;而且难度较大。像代数&#xff0c;微积分&#xff0c;线性规划&#xff0c;统计学等课程都是在…

架构师之路(5)---IoC框架

1 IoC理论的背景 我们都知道在面向对象的应用中&#xff0c;软件系统都是由N个对象组成的&#xff0c;它们通过彼此的合作&#xff0c;最终实现业务逻辑。 图1&#xff1a;耦合在一起的对象 如果我们打开机械式手表的后盖&#xff0c;就会看到与上面类似的情形&#xff0c;各…

【转】数据库软考笔记(一)第一章 计算机硬件基础知识笔记总结

转自&#xff1a;https://blog.csdn.net/qq_36411874/article/details/115057569 目录 运算器&#xff1a;ALU、AC、DR、PSW 控制器&#xff1a;IR、PC、AR、ID 寄存器组&#xff08;了解即可&#xff09; 存储器 存储器与总线&#xff08;了解&#xff0c;考分类&#xf…

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

转自&#xff1a;https://www.cnblogs.com/mfrbuaa/p/4004114.html 有修订 背景介绍&#xff1a; 近期项目需求&#xff0c;需要使用C#进行最新的UI和相关DICOM3.0医学图像模块的开发。在C语言下&#xff0c;我使用的是应用最广泛的DCMTK开源库&#xff0c;在本专栏的起初阶段…

xfire客户端对返回list很挑剔,所以需要使用泛型。

casldap异常分析 | xfirewebservice(服务器配置篇) 2008-12-31xfirewebservice(客户端调用篇) 服务接口&#xff0c;就是用来调用的&#xff0c;所以客户端显得尤为重要&#xff0c;xfire客户端对返回list很挑剔&#xff0c;所以需要使用泛型。 如何建立webservice client 步骤…

在虚拟机上运行vxWorks

Vxworks是一个嵌入式系统&#xff0c;主要运行在arm、ppc、mips等嵌入式处理器上&#xff0c;它同样可以运行在X86处理器上。风河公司开发的tornado开发环境就 包括了pentium版本&#xff0c;并且发布了相应的bsp。因此我们可在vmware虚拟机上运行vxworks&#xff0c;利用虚拟机…

使用SDL打造游戏世界之入门篇 - 7

打开和显示图片在这个例子里我们将学习如何使用SDL程序中打开和显示图片。示例程序将绘制一个漂亮的背景&#xff0c;上面显示一个正方形图案&#xff0c;并可以使用键盘的方向键移动它。如果比较熟悉“推箱子”这个游戏&#xff0c;可以看出这个程序实际就是推箱子游戏的基础。…

【转】GPS从入门到放弃(一) --- GPS基础原理

转自&#xff1a;https://blog.csdn.net/tyst08/article/details/100529424 GPS从入门到放弃&#xff08;一&#xff09; — GPS基础原理 GPS&#xff0c;全称Global Positioning System&#xff0c;已经广泛应用于我们的日常生活中了&#xff0c;现在的智能手机 里都会自带G…

【转】10个推荐的 PACS/DICOM Server开源项目

10个推荐的 PACS/DICOM Server开源项目 转自&#xff1a;https://zhuanlan.zhihu.com/p/65437040 soolaugust 喜欢我的分享可以关注公众号&#xff1a;雨夜随笔 35 人赞同了该文章 本文仅授权给公众号&#xff1a;雨夜随笔&#xff0c; 欢迎关注 本文翻译自&#xff1a;To…

字节序、位序

字节序 字节序&#xff0c;又称端序、尾序&#xff0c;英文单词为Endian&#xff0c;该单词来源于于乔纳森斯威夫特的小说《格列佛游记》&#xff0c;小说中的小人国因为吃鸡蛋的问题而内战&#xff0c;战争开始是由于以下的原因&#xff1a;我们大家都认为&#xff0c;吃鸡蛋前…