背景:

上一篇博文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

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

  1. DICOM:DICOM开源库多线程分析之“ThreadPoolQueue in fo-dicom”

    背景: 上篇博文介绍了dcm4chee中使用的Leader/Follower线程池模型,主要目的是节省上下文切换,提高运行效率.本博文同属[DICOM开源库多线程分析]系列,着重介绍fo-dicom中 ...

  2. Android下拉刷新开源库对比(转)

    安卓下拉刷新开源库对比 作者:desmond1121 目前仅比对github上star数>1500的下拉刷新开源库,在比较完成之后可能会加入其它有代表性的库. Repo Repo Owner S ...

  3. 转: 三大WEB服务器对比分析(apache ,lighttpd,nginx) (2008年的旧文,仅供参考之用)...

    from:  http://www.blogjava.net/daniel-tu/archive/2008/12/29/248883.html 三大WEB服务器对比分析(apache ,lighttp ...

  4. 【报告分享】零售行业三大平台之对比分析-阿里VS京东VS拼多多:分级、竞争、进化.pdf...

    今天给大家分享招商证券于2020年5月份发布的深度报告<零售行业三大平台之对比分析-阿里VS京东VS拼多多:分级.竞争.进化.pdf>,报告共包含如下三大部分: 1.复盘三大平台的崛起之路 ...

  5. 移动三大平台和三大开发模式对比分析

    一:移动三大平台及其对比分析: 1)移动三大平台 2)移动三大平台对比分析 二:三大开发模式及其对比分析: 1)三大开发模式 2)三大开发模式对比分析

  6. 三大WEB server 对比分析(apache ,lighttpd,nginx)

    三大WEB服务器对比分析(apache ,lighttpd,nginx) 一.软件介绍(apache  lighttpd  nginx) 1. lighttpd Lighttpd是一个具有非常低的内存 ...

  7. cbitmap 从内存中加载jpg_Pytorch数据加载的分析

    Pytorch数据加载的效率一直让人头痛,此前我介绍过两个方法,实际使用后数据加载的速度还是不够快,我陆续做了一些尝试,这里做个简单的总结和分析. 1.定位问题 在优化数据加载前,应该先确定是否需要优 ...

  8. 借由ARM CORTEX-M芯片分析C程序加载和存储模型

    https://zhuanlan.zhihu.com/p/22048373 写文章 借由ARM CORTEX-M芯片分析C程序加载和存储模型 王小军 1 年前 阿军最近在忙着血氧手环嵌入式系统的技术预 ...

  9. R包库安装及数据加载:一次安装多个R包、一次加载多个R包

    R包库安装及数据加载:一次安装多个R包.一次加载多个R包 目录 R包库安装及数据加载 R包安装 一次安装多个R包 加载需要的R包

最新文章

  1. [zz] 几种类间关系:继承、实现、依赖、关联、聚合、组合及UML实现图
  2. cocos中添加显示文字的三种方式(CCLabelTTF 、CCLabelBMFont 和CCLabelAtlas)
  3. Eureka出现Root name ‘timestamp‘ does not match expected (‘instance‘) for type xxx的错误,如何解决?
  4. es springboot 不设置id_springboot整合ES_文档ID删除
  5. Linux怎么删除虚拟硬盘,2017.05.10 qemu-nbd 全自动挂载/卸载 虚拟硬盘中所有可用分区 的 脚本...
  6. Android无法生成R文件的终极解决办法
  7. C#经典算法实践,回顾往生,更是致敬《算法导论》
  8. 高手也不好当,压力更大
  9. 软件测试面试题【2021模拟面试整理版(含答案)】
  10. ADV-234-字符串跳步
  11. Altium Designer入门
  12. Squid 设置网站访问白名单
  13. 积分域为椭球的三重积分的求解方式----广义的极坐标变换
  14. Python基本语法,让我们轻松入门学习Python!
  15. LeetCode 第 993 题:二叉树的堂兄弟结点
  16. 每天学习10句英语-第九天
  17. 轻松理解MySQL的MVCC机制
  18. Python 引入上级目录
  19. console连接h3c s5500_H3C S3100交换机Console口登录方式配置
  20. awk BEGIN、END 很明白也很明了

热门文章

  1. 七夕秀恩爱新姿势!这波操作我给十分!
  2. android与php使用base64加密的字符串结果不一样解决方法
  3. 數據庫ORACLE轉MYSQL存儲過程遇到的坑~(總結)
  4. Java并发编程笔记之Semaphore信号量源码分析
  5. 分辨率到底是个什么概念?它和DPI之间是什么关系?
  6. 网络攻防 第四周学习总结
  7. 在SharePoint 2010中创建网站的权限级别
  8. 784. Letter Case Permutation
  9. java收发邮寄_JavaMail收发邮件的一般流程与主要方法
  10. java自定义菜单跳转页面_微信公众号开发 自定义菜单跳转页面并获取用户信息实例详解...