DICOM医学图像处理:Deconstructed PACS之Orthanc,Modification & Anonymization

背景:

上篇博文为引子,介绍了一款神奇的开源PACS系统——Orthanc。本篇开始解读官方Cookbook中的相关内容,对于简单的浏览、访问和上传请阅读前篇博文。在常规的PACS系统中还未出现对于DCM图像的修改和匿名化操作,因此此次重点介绍Orthanc利用REST API实现对DCM医学图像的修改(modification)和匿名化(anonymization)。对于官方Cookbook中的实例进行示范和调试,通过Orthanc源码分析确保示例在本机良好运行。注意:官方Cookbook中的示例在Windows下会有错误,详情见博文。

Orthanc介绍:

取名为Orthanc源自于J.R.R. Tolkien’s(托尔金)的小说。Orthanc是艾辛格(Isengard)要塞中的黑塔,初建于第二世纪,用于储存收纳南方王国的真知晶石——palantíri,一种圆形且能够看见远方的石头,透过palantíri可以跟远方使用palantíri的人进行交流。Orthanc Server正是取palantíri的此层含义,设计出一种可在整个医院DICOM拓扑网络中便捷、透明以及可编程访问医学图像的系统(可参照wiki百科的介绍:http://en.wikipedia.org/wiki/Isengard)。

另外,Orthanc中同时包含了“RTH“,即Radiotherapy。其实Orthanc本身源自于法国de Liège大学中心医院(Centre Hospitalier Universitaire)对于放射治疗服务的研究。

Orthanc之Modification & Anonymization

Anonymization:

Orthanc从0.5.0版本之后引入了对DICOM资源的匿名化操作,可对患者(patients)、检查(studies)、序列(series)和图像(instances)多个级别进行匿名化处理。为了方便示范,此处以instances级别为例进行介绍:

1)按照上篇博文上传两幅测试图像到Orthanc Server,如下图所示:

2)利用curl命令行查看一下上述两个instances,获取ID号,结果如下:

curl http://localhost:8042/instances

此处获取的instance ID号为:c77324ec-f5e76fc5-c96846bf-2ed4097d-86f9e79c

3)按照Orthanc官方Cookbook的说明,进行匿名化操作

输入指令:curl http://localhost:8042/instances/c77324ec-f5e76fc5-c96846bf-2ed4097d-86f9e79c/anonymize -X POST -d ‘{}‘ > c:\orthanc-anonymize.dcm

但是并未获得如期结果,打开c盘发现orthanc-anonymization.dcm文件大小为0KB。开启curl的verbose模式,

curl –v http://localhost:8042/instances/c77324ec-f5e76fc5-c96846bf-2ed4097d-86f9e79c/anonymize -X POST -d ‘{}‘ > c:\orthanc-anonymize.dcm

查看输出信息如下:

发现Orthanc Server中HTTP 服务返回值为404。

通过自己查看官方Cookbook给出的指令,除了对应instance的UID不同外,并未找到问题,暂且跳过,尝试一下Modification。

Modification:

1)上传DCM文件到Orthanc Server,同Anonymization中相同;

2)利用curl http://localhost:8042/instances获取指定instance的ID号;

3)参照官方Cookbook进行Modification处理

输入如下指令,此次为了方便,直接使用curl的verbose模式:

curl –v http://localhost:8042/instances/c77324ec-f5e76fc5-c96846bf-2ed4097d-86f9e79c/modify –X POST –d ‘{"Replace”:{"PatientName”:"hello”,"PatientID”:"world”}}‘
>c:\orthanc-modify.dcm

输出结果如下:

目前为止,参照Orthanc官方的Cookbook说明,给出的两个示例都在本机无法顺利完成。因此猜测官网中给出的代码有可能是Linux系统的,在Windows系统下应该做出适当的调整,但是该怎样调整呢?该调整哪一部分呢?请继续往下看……

调试Orthanc官方Cookbook示例:

0)Orthanc REST API资料查询:

在调试之前,我们先要搞清楚出现问题的大致原因,排除一些常见的错误,比如软件版本错误、指令拼写错误等等。上面介绍的Anonymization和Modification都是利用了Orthanc的REST API功能,那么Orthanc各版本对于REST API的支持程度如何呢?我们查看一下官方的说明,如下图所示:

从上图可以看出,Orthanc从0.5.0版本之后就支持多级别的修改和匿名化操作,如上一篇博文所述,我本机安装的是最新的0.8.5版本。因此可以确保软件版本无误,另外在上述示例仿真过程中我们也对比排查了指令拼写错误。

接下来使出我们的杀手锏吧:“启动C:\Orthanc-0.8.5\Orthanc.sln工程,进入调试模式,查看Orthanc的源码”

打开Orthanc.sln解决方案,右键Orthanc工程开启调试模式,在关于REST API的几个核心类中插入断点,初次尝试插入的断点如下:

1)Anonymization源码调试:

从新按照上一节中的步骤,首先利用Orthanc Explorer向调试模式下的Orthanc Server添加DICOM文件,此时直接按F5跳过调试,因为图像加载我们并未遇到问题。(此步操作必须重新添加,因为调试模式下数据的存储目录是C:\Orthanc-0.8.5\OrthancStorage,与二进制安装包默认的C:OrthancStorage不同,上一节中我们添加的图像在调试状态下是看不到的

输入指令查看ID:

curl http://localhost:8042/instances

开始输入上一节的Anonymization指令:

curl -v http://localhost:8042/instances/c77324ec-f5e76fc5-c96846bf-2ed4097d-86f9e79c/anonymize -X POST -d ‘{}‘ > c:\orthanc-anonymize.dcm

Orthanc工程首次停在了断点RestApi.cpp中的Visit函数内,如下图所示:

利用F11单步调试,随后进入到解析Anonymization请求操作的函数ParseAnonymizationRequest内部,可以看到在该函数内给出了一个示例与我们输入的格式相同

继续单步调试,最后发现在解析Json格式的AnonymizationRequest指令,即我们输入的‘{}’,json_reader.cpp中的readToken返回值为tokenError

至此想必我们找到了Anonymization指令出错的原因的,应该是我们输入的Json格式不正确,虽然我们是按照Orthanc官方的Cookbook来进行的。为了确定我们的Json格式是否有误,在在线Json格式检查网站(http://www.bejson.com/)测试一下,结果为:

按照提示,去掉指令中Json部分的单引号,输入:

curl -v http://localhost:8042/instances/c77324ec-f5e76fc5-c96846bf-2ed4097d-86f9e79c/anonymize -X POST -d {} > c:\orthanc-anonymize.dcm

curl调试输出的结果为:

利用DICOM看图软件,打开可以发现orthanc-anonymize.dcm文件中的患者姓名已经被隐藏掉了。

【总结】:正确的指令应该是外边不添加单引号,如下所示:

curl -v http://localhost:8042/instances/c77324ec-f5e76fc5-c96846bf-2ed4097d-86f9e79c/anonymize -X POST -d {} > c:\orthanc-anonymize.dcm

2)Modification源码调试:

既然已经找到了Anonymization中代码出错的问题,让我们去掉Modification中的单引号,尝试一下:

输入指令:

curl -v http://localhost:8042/instances/c77324ec-f5e76fc5-c96846bf-2ed4097d-86f9e79c/modify -X POST -d {"Replace":{"PatientName":"hello","PatientID":"world"}} >c:\orthanc-modify.dcm

令我们失望的是依然看到了HTTP Server返回的404错误。

由于Modification指令中单引号内部还存在着更多的双引号,所以死让我们继续调试,根据上次的经验,有可能还是Json格式输入输入错误,然我们只保留json_reader.cpp中的readToken断点,直接查看一下解析结果是否是tokenError?

重新输入指令,进入到readToken内部,发现函数依然返回tokenError,结果如下图所示:

利用VS自带的查看工具,我们可以看到readToken函数解析的字符串current_,其真实内容是:{Replace:{PatientName:hello,PatientID:world}},其中并未看到我们输入的双引号,将{Replace:{PatientName:hello,PatientID:world}}输入到http://www.bejson.com/中,也得到了错误的结果提示:

这说明我们在命令行中输入的Json指令中的双引号,在被VS读入过程中被忽略了。

那么我们利用转义字符将双引号传递进入,输入指令:

curl -v http://localhost:8042/instances/c77324ec-f5e76fc5-c96846bf-2ed4097d-86f9e79c/modify -X POST -d {\"Replace\":{\"PatientName\":\"hello\",\"PatientID\":\"world\"}} >c:\orthanc-modify.dcm

查看结果:

最终运行输出为,

在DCM看图软件中打开orthanc-modify.dcm,可以看到PatientID和PatientName已经修改。

至此,关于Orthanc Modification和Anonymization的调试已经顺利完成,可以松一口气啦,还是调试源代码给力啊^_^。

知识点补充:

Orthanc作为开源项目,获取其源码自然是轻而易举。虽然号称轻型Server系统,但是代码量还是足以吓退大多数人的。初次面对海量的代码还是很难进行调试的。下面以上述调试Orthanc官方Cookbook中的Anonymization和Modification示例过程为例,讲解一下如何开始调试开源项目:

1)如何调试Orthanc中的Http Server

之所以上一节最开始将断点设置在了RestXXX.cpp文件中,是因为本博文中的介绍的Modification和Anonymization使用的是Orthanc提供的REST API服务,因此首先推测错误应该出现在REST API的相关实现函数中。这种方法可以顺利的解决我们上面遇到的问题,并且如你所见我们已经实现了问题的排查。

但是既然调试进入了Orthanc的源码内,就顺道儿搞清楚Orthanc中Http Server的整体流程,也顺便找找究竟是在哪一个地方将curl指令中的双引号忽略的。

再一次输入指令,进入调试状态。断点第一次停在RestApi.cpp中的Visit函数处,在前一节中我们利用“单步调试”顺利的逐步进入到了JSON的解析函数readToken内,“单步调试”是向前追溯的最佳手段,也是VS提供给我们的最直观最有利的错误排查工具。但是为了要了解整个流程,我们此处需要“回溯”,那么VS提供给我们逆流而上的工具是什么呢?那就是“查找所有引用”,如下图所示:

我们可以由RestApi.cpp 转到上一级,即RestApiHierarchy.cpp中的LookupResource函数,如此迭代下去相信可以回溯到整个流程的最顶端。这种方法虽然笨拙了一些,但是比较实用。

想必在Windows编程环境下大家还是更喜欢“可视化”的直观操作,即所见即所得。那么单击菜单栏中的“调试”,选择“窗口”中的“调用堆栈”,可以直观的看到在RestApi.cpp中的Visit函数之前的各种函数调用顺序,如下图所示:

至此我们可以看到整个Http Server服务的起始端是mongoose.cpp中的worker_thread函数(在“调用堆栈”中相关函数右键选择“转到源码”可以查看函数的具体位置)。由上图中“调用堆栈”的顺序我们已经可以清晰的看到Orthanc Http Server的整个调用流程。那么我们在worker_thread函数中插入断点,重新输入curl指令,查看一下在整个流程的起始状态下,Orthanc Http Server接收到的数据体是什么格式?如下图所示:

在process_new_connection函数内部读取curl发出的POST请求指令时,连接缓存buf的内容中已经不存在双引号了,因此可以确定curl的-d参数在发送JSON格式数据到Orthanc的Http Server时忽略了双引号。后续可以研究一下curl在windows环境下的使用,尤其是-d参数的设置。

2)如何调试Orthanc中的DICOM Server

由于DICOM Server比较熟悉,先验知识比较多,因此调试起来比HTTP Server容易。在Orthanc的main函数中我们可以看到httpServer.Start()和dicomServer.Start(),dicomServer.Start()就是DICOM Server的入口点,由于Orthanc是基于DCMTK开发的,因此后续的流程应该是交由DCMTK来完成的,更详细的细节可参考本专栏前面的DCMTK网络系列文章。另外预告一下下一篇博文会详细介绍利用fo-dicom开源库打造一个简单的DICOM
Server服务端,那时再详细的对比分析一下Orthanc中的DICOM Server,敬请期待。

参考资料:

https://code.google.com/p/orthanc/wiki/OrthancCookbook,Cookbook官网链接

https://code.google.com/p/orthanc/wiki/Anonymization,Orthanc的Modification和Anonymization介绍

https://docs.google.com/spreadsheet/pub?key=0Ao5aRMxCX2hldEJadzVUaWFmNW5QTWhrYTI3UHMzdXc&single=true&gid=8&output=html,REST
API支持图谱

https://docs.google.com/spreadsheet/pub?key=0Ao5aRMxCX2hldEJadzVUaWFmNW5QTWhrYTI3UHMzdXc&single=true&gid=0&output=html,REST
API支持图谱

后续专栏博文预告:

fo-dicom搭建简单的DICOM Server

作者:[email protected]

时间:2014-11-29

时间: 2024-10-11 22:54:23

DICOM医学图像处理:Deconstructed PACS之Orthanc,Modification & Anonymization的相关文章

DICOM医学图像处理:深入剖析Orthanc的SQLite,了解WADO & RESTful API

背景: 上一篇博文简单翻译了Orthanc官网给出的CodeProject上"利用Orthanc Plugin SDK开发WADO插件"的博文,其中提到了Orthanc从0.8.0版本之后支持快速查询,而原本的WADO请求需要是直接借助于Orthanc内部的REST API逐级定位.那么为什么之前的Orthanc必须要逐级来定位WADO请求的Instance呢?新版本中又是如何进行改进的呢?此篇博文通过分析Orthanc内嵌的SQLite数据库,来剖析Orthanc的RESTful A

DICOM医学图像处理:DCMTK的wiki资料学习之PACS调试

背景: 前段时间着重从dcmtk和fo-dicom(mDCM)源码角度进行剖析,期望加深对DICOM协议的理解.知其然,知其所以然.如果"所以然"很不好懂,那我们还是先多多"知其然"吧.搞清楚原理的目的不也是为了更好的运用于实践么?所以理论和实践应该彼此交错进行,理论搞不动了就搞搞应用,应用久了就钻研钻研理论. 以前上DCMTK官网仅仅是浏览关于开源库中各个类的设计模式.依赖关系.最近在打开DCMTK官网的wiki时,才发现OFFIS对DCMTK的介绍是如此的详细.

DICOM医学图像处理:storescp.exe与storescu.exe源码剖析,学习C-STORE请求

背景: 上一篇专栏博文中针对PACS终端(或设备终端,如CT设备)与RIS系统之间worklist查询进行了介绍,并着重对比分析了DICOM3.0中各部分对DICOM网络通讯服务的定义.此次通过结合早些时间的博文DICOM医学图像处理:基于DCMTK工具包学习和分析worklist,对DCMTK开源库中提供的storescp.exe和storescu.exe工具的源码进行剖析,从底层深入了解C-STORE服务的触发及响应. 分析思路: storescp.exe和storescu.exe分别充当着

DICOM医学图像处理:DICOM存储操作之“多幅BMP图像数据存入DCM文件”

背景: 本专栏"DICOM医学图像处理"受众较窄,起初只想作为自己学习积累和工作经验的简单整理.前几天无聊浏览了一下,发现阅读量两极化严重,主要集中在"关于BMP(JPG)与DCM格式转换"和"DICOM 通讯协议",尤其是许久前的第一篇博文DCMTK开源库的学习笔记1:将DCM文件保存成BMP文件或数据流(即数组).因此在2014年底前打算写几篇关于DCM格式转换的文章,此次主要聚焦"如何将BMP.JPG等常规图像保存成DCM文件&q

DICOM医学图像处理:DICOM网络传输

背景: 专栏取名为DICOM医学图像处理原因是:博主是从医学图像处理算法研究时开始接触DICOM协议的.当初认识有局限性,认为DICOM只是一个简单的文件格式约定,简而言之,我当时认为DICOM协议就是扩展名为DCM文件的格式说明.其实不然,随着对医疗行业的深入,对DICOM协议也有了更全面的认识.而今才发现DCM文件只是DICOM协议一部分中的一小节,仅仅是整个协议中的一个数据结构,而DICOM协议更多的是关于医疗行业各种服务及相关流程的约定,因此其实DICOM协议中最主要的是信息流,是对医院

DICOM医学图像处理:DCMTK在VS2012中的配置

背景: 最近由于项目需要,将原本的开发IDE环境由VS2008升级到了VS2012.本以为编译完成后的DCMTK开源库可以直接从VS2008移植到VS2012.但是通过项目属性添加完包含目录和依赖库后,编译会出现大量的链接错误(大多是跟dcmdata.lib.oflog.lib有关). 解决方法: 重新按照原本的博客前辈柳北风儿(大神目前已经博客转移到网易:http://blog.163.com/[email protected]/),利用CMake工具,选择VS2012本地编译器对DCMTK3

DICOM医学图像处理:DICOM存储操作之 “多幅JPG图像数据存入DCM文件”

背景: 续上篇,继续介绍如何将多幅JPG图像数据存入DCM文件.即将有损压缩数据直接写入DCM文件,存储为Multi-frame形式. 多幅JPG图像数据存入DCM文件: 为了避免引起歧义,这里着重说明一下.本博文的描述的场景是:假设我们手中有多张JPG文件,想把JPG文件写入DCM文件,即单个DCM文件包含多幅图像信息的Multi-Frame形式.该问题之前与CSDN博友y317215133y也讨论过,当时我在OFFIS论坛中找到了一个帖子直接给了y317215133y答复.今天重新梳理了一下

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

背景: 上周通过单步调试,找出了开源库mDCM与DCMTK在对DICOM图像进行JPEG无损压缩时的细小区别,并顺利实现了在C++和C#环境下对DICOM图像的压缩.但是问题接踵而至啊,随着项目的深入,发现在单独的测试工程中可以实现的mDCM版本,在嵌入到项目整体中后,却意外地出现了错误,并未顺利实现DICOM图像的JPEG无损压缩.因此需要继续详细对比分析mDCM与DCMTK两者,期望寻找原因. 问题分析: 开启项目的日志功能后,得到的信息反馈为: No registered codec fo

DICOM医学图像处理:Dcmtk与fo-dicom保存文件的不同设计模式之“同步VS异步”+“单线程VS多线程”

一.背景: 最近一直在做DCM相关的编程工作,以前项目使用C++居多,所以使用DCMTK开源库,而目前团队使用C#居多,所以需要转向使用fo-dicom库,由于前一篇专栏文章DICOM医学图像处理:利用fo-dicom发送C-Find查询Worklist在调试过程中需要对DIMSE信息进行手动保存,偶然间发现了dcmtk开源库与fo-dicom开源库在保存dcm文件时使用的方式差异很大,因此决定研究一下,期望通过对比分析来看一下孰优孰劣. 二.dcmtk与fo-dicom保存文件函数的源码剖析: