首页 > 程序开发 > 综合编程 > 其他综合 >

DICOM:由fo-dicom库解析DICOM文件引申出来

2016-06-27

近期收到不少博友的邮件或私信,有刚入门者,来信咨询DICOM协议基础知识的;也有同行咨询如何开发DICOM服务器,如何选用dcmtk、dcm4che、fo-dicom等开源库;还有一部分是医疗从业者,大多在医院放射科或相关科室工作,希望实现与设备的交互

题记:

近期收到不少博友的邮件或私信,有刚入门者,来信咨询DICOM协议基础知识的;也有同行咨询如何开发DICOM服务器,如何选用dcmtk、dcm4che、fo-dicom等开源库;还有一部分是医疗从业者,大多在医院放射科或相关科室工作,希望实现与设备的交互,查询并获取数据,最好能有UI界面的开源测试软件。
希望能够解答这些问题也正是我坚持写博客,并开设DICOM技术专栏的初衷。怎奈DICOM是一个标准自身比较复杂、且行业专业度较高的协议,即使写了诸多系列的博文,依然未整理出一篇理想的科普贴——理想状态是希望通过这一篇科普贴就能够让任何人都了解并清楚DICOM协议是什么?能做什么?怎么做?——截至目前,只能给出如下建议:按照博文中的分类依次阅读并学习“介绍篇”、“数据篇”和“协议篇”,直至最后的“应用篇”,如此下来应该可以形成一个整体的认识,不过这可能需要花费一些时间,任何知识的汲取都是需要时间来消化和吸收的,静下心来阅读想必你能够读懂我的博文。

背景:

近期由于收到博友咨询fo-dicom的兼容性和扩展性和自身Github的fo-dicom仓库更新等原因,发现fo-dicom官方主版本已经更新到了3.X系列,貌似完善了不少东西,不过还没有时间仔细研究。以后再抽时间试用并介绍吧。本篇博文由之前自己的fo-dicom版本库在解析不规范DICOM文件时弹出的【错误】:Requested xxxx bytes past end of file…引申,先给出【解决方案】,随后对DICOM文件的解析畅想一下,希望对DICOM文件能够做到高智能化、高鲁棒性的解析。

fo-dicom解析不规范DICOM文件:

1. 不规范DICOM文件

此次遇到的不规范DICOM文件,依然是由于Transfer Syntax(0x0002,0x0010)元素导致的。
Transfer Sytanx在DICOM标准中占有重要的一席之地,既作为必要元素写入到DCM文件元信息(MetaInformation)中,又是DICOM网络服务中双方数据传输的前提。之前也遇到过GE私有的Transfer Syntax
这里需要指出的是TransferSyntax元素,即(0x0002,0x0010),作为DICOM文件头信息(File Meta Information)的必要字段被归类到group=0002组中,其常见的数值如下图所示:
这里写图片描述

由上图所示Transfer Syntax的数值表达了两种含义,即编码字节序+编码组成格式
举个例子,当Transfer Syntax=1.2.840.10008.1.2.1时,含义为Explicit VR Little Endian,即①编码字节序采用小端序(大端序与小端序即数据在内存中存储与对应的顺序,网络上有很多资料,大家可自行搜索)②编码组成格式采用显式VR,如下图所示元素Element中包括Tag+VR+VL+Value<喎"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPqOsz+C3tMjnufu4xM6q0v7KvVZSo6i8tEltcGxpY2l0IFZSo6mjrMTHw7TUqsvYRWxlbWVudNbQsPy6rDxzdHJvbmc+VGFnK1ZMK1ZhbHVlPC9zdHJvbmc+o6zKobX0wctWUqOoxMfDtNPQyMu/ycTcu+HOyqOsyqG19MHLVlLEx8jnus694s72uvPD5rXEyv2+3cTYo7/G5Mq1RElDT02x6te8tcRUYWe2vNPQucy2qLXEyv3WtcDg0M2jrNL+yr1WUqOsvLRJbXBsaWNpdFZSxKPKvc/Co6y/ydLUzai5/bLp0a9EaWNvbVRhZ7XE19a15M7EvP7AtMXQsfBWUsDg0M2hozxpbWcgYWx0PQ=="这里写图片描述" src="http://www.2cto.com/uploadfile/Collfiles/20160627/20160627091943595.png" title="\" />

通过上面了解了Transfer Syntax的含义,还有一点需要注意——Transfer Syntax“作用域”,即其能够影响的范围:DICOM协议规定包含头信息(File Meta Information)的文件,头信息(即group=0002)的所有元素默认采用Explicit VR Little Endian存储,数据体Dataset(即group>0002的分组)元素如何存储则由头信息File Meta Information中的Transfer Syntax来决定。
今天遇到的不规范文件的情况是:File Meta Information中的Transfer Syntax=1.2.840.10008.1.2,即Implicit VR Little Endian,但是Dataset数据体存储的方式采用的是1.2.840.10008.1.2.1,即Explicit VR Little Endian,构成是Tag+VR+VL+Value。因此fo-dicom在解析时刻抛出了异常信息font color=red>【错误】:Requested xxxx bytes past end of file…。

2. 解决方案

网络上对于这个错误有多种解决方案,例如https://groups.google.com/forum/https://groups.google.com/forum/,想必上述方案的作者遇到的问题跟本文的略有不同,因此并不能解决本文遇到的不规范文件。
通过跟踪fo-dicom代码(这里的fo-dicom版本是基于1.0.38分支fork而来,目前由我自己维护,详细的仓库连接为zssurethu\fo-dicom,所以不知道最新的fo-dicom3.x系列是否能够兼容本文提到的不规范文档,有兴趣的朋友可以亲测一下),发现DicomFileReader中来获取TransferSyntax的数值,并记录编码字节序+编码组成格式,详细代码如下:

c# hljs avrasm">                    // test for explicit VR
                    var vrt = Encoding.UTF8.GetBytes(tag.DictionaryEntry.ValueRepresentations[0].Code);
                    var vrs = _source.GetBytes(2);

                    if (vrt[0] != vrs[0] || vrt[1] != vrs[1]) {
                        // implicit VR
                        if (_syntax.Endian == Endian.Little)
                            _syntax = DicomTransferSyntax.ImplicitVRLittleEndian;
                        else
                            _syntax = DicomTransferSyntax.ImplicitVRBigEndian;
                    }

                    _source.Rewind();
                } while (_fileFormat == DicomFileFormat.Unknown);

                if (_fileFormat == DicomFileFormat.Unknown)
                    throw new DicomReaderException("Attempted to read invalid DICOM file");

                var obs = new DicomReaderCallbackObserver();
                if (_fileFormat != DicomFileFormat.DICOM3) {
                    obs.Add(DicomTag.RecognitionCodeRETIRED, (object sender, DicomReaderEventArgs ea) => {
                        try {
                            string code = Encoding.UTF8.GetString(ea.Data.Data, 0, ea.Data.Data.Length);
                            if (code == "ACR-NEMA 1.0")
                                _fileFormat = DicomFileFormat.ACRNEMA1;
                            else if (code == "ACR-NEMA 2.0")
                                _fileFormat = DicomFileFormat.ACRNEMA2;
                        } catch {
                        }
                    });
                }
                obs.Add(DicomTag.TransferSyntaxUID, (object sender, DicomReaderEventArgs ea) => {
                    try {
                        string uid = Encoding.UTF8.GetString(ea.Data.Data, 0, ea.Data.Data.Length);
                        _syntax = DicomTransferSyntax.Parse(uid);
                    } catch {
                    }
                });

                _source.Endian = _syntax.Endian;
                _reader.IsExplicitVR = _syntax.IsExplicitVR;

此后DicomReader的ParseDataset函数会严格按照上述代码获得的_syntax来逐个解析Dataset中的各个元素。继续单步调试发现,之所以抛除上述异常时因为TransferSyntax表明的元素编码组成为ImplicitVR,即DicomReader的IsExplicitVR=false,而Dataset中是按照ExplicitVR存储的,由于IsExplicitVR被错误的设置为了false,导致将VR解析成了VL,这与弹出的异常信息相一致。
因此在ParseDataset函数内部,当_state == ParseState.Tag且标签group>0002的时刻,需要添加对Transfer Syntax的自动校验,以确保File Meta Information的Transfer Syntax与Dataset真实存储相一致。详细代码参见我的github主页中的fo-dicom仓库

引申思考:

通过上述错误,以及之前博文遇到的GE私有Transfer Syntax,是否可以运用技术手段(简单的状态机,或者更高端的机器学习)给出一种自动且高鲁棒性的解析任意格式的DICOM文件方法。考虑到效率原因,只有在常见的dicom库(dcmtk、dcm4che以及fo-dicom等)解析出现异常时调用。抽空尝试写一下,以能够解析任意DICOM文件为终极目的,如果大家有啥想法欢迎邮件或私信交流。

热点推荐