背景:

Orthanc是博主发现的一个很完美的DICOM和HTTP服务端开源软件,前几篇分别介绍了Orthanc的基本使用。Orthanc从0.8.0版本之后给出了Plugin SDK,通过该SDK可以利用Orthanc内建的REST API实现WADO服务,下面就参照官网给出的说明介绍一下如何使用SDK实现WADO服务,并且对官网的实例进行更新,采用最新的方式直接实现WADO服务。

官方说明中文翻译:

1)简介

DICOM标准定义了文件格式以及医学影像网络传输协议。WADO,即Web Access to DICOM Persistent Objects,是DICOM3.0标准中制定的一种基于网络web服务访问医学图像的协议(具体在DICOM3.0第18部分)。通过WADO协议,专业的医生可以使用常见的浏览器(目前Orthanc貌似不支持IE)预览和下载医学图像。

本博文所附代码给出了一个实现简单WADO服务的示例。该示例可以返回原始的DICOM图像,或者JPEG格式图像;并可以作为Orthanc的插件来运行。借助于Orthanc的框架,可以通过简单的几行代码实现WADO服务。

2)背景:

Orthanc是一款开源的、轻型的、独立的,并支持脚本化的DICOM服务端。Orthanc主要用来精简临床就医流程和简化医学图像的管理。另外通过兼容常见的JSON、PNG格式和RESTful API,使得DICOM标准在计算机图像领域得到更广泛的应用。Orthanc隐藏了DICOM文件格式和DICOM协议的复杂性,因此医院普通的网络管理人员以及专业的医学图像自动分析软件开发人员都可以使用。Orthanc可以作为一个健壮的医学影像处理中心,为各个医院提供服务。

从0.8.0(2014 7月份发布)开始,Orthanc给外部开发人员 提供了插件开发SDK。利用SDK开发的动态库形式的插件可以被导入到Orthanc服务中,插件通过注册回调函数来响应浏览器的HTTP请求。回调函数反过来可以访问Orthanc数据库提取目标DICOM文件的信息。Orthanc Plugin SDK以C语言头文件形式给出,链接如下:https://code.google.com/p/orthanc/source/browse/Plugins/OrthancCPlugin/OrthancCPlugin.h?name=Orthanc-0.8.0,说明文档:http://www.codeproject.com/KB/webservices/797118/OrthancPluginDocumentation.zip

3)DICOM 和 WADO

本小节只概括介绍WADO协议,详细介绍参见DICOM3.0标准的第18部分。

DICOM协议里规定了如下标准:一个患者(Patient)可以做多次检查(Studies)。每一个检查(Study)包含一系列医学图像,即序列(Series)。举个例子:标准的PET-CT检查(Study)会包含两组序列,CT 序列和PET 序列。序列中通常对应人体的二维/三维/四维影像。每种影像会被分割成多个文件存储,即Instance(也就是我们看到的包含单幅图像的单个后缀为DCM的文件)。

通常,一个DICOM实例(Instance)可以看做是二维图像与存储了患者元信息(人口统计学信息,如姓名、年龄、身高、体重等等)结构的组合。患者元信息通常是包含了DICOM标签对应值的数组。每个标签由两个十六进制数表示。非常重要的是,每一级的检查(Study)、序列(Series)和图像(Instance)要求全局唯一。

例如(0x0020,0x000d)代表的是Study Instance UID,定义检查(Study)的唯一性;

…………

一个WADO请求就是一个简单的HTTP GET请求,请求中包含了Study、Series和Instance标识符。例如:

http://localhost/wado?
studyUID=1.2.840.113845.11.1000000001951524609.20121203131451.1457891&
seriesUID=1.2.840.113619.2.278.3.262930758.589.1354512768.115&
objectUID=1.2.840.113619.2.278.3.262930758.589.1354512768.116.1&
requestType=WADO

该WADO请求的响应会是与studyUID/seriesUID/objectUID对应的DICOM图像的JPEG格式。如果希望直接获取DICOM格式文件,应在WADO请求中添加contentType=application%2Fdicom,如下所示:

http://localhost/wado?
studyUID=1.2.840.113845.11.1000000001951524609.20121203131451.1457891&
seriesUID=1.2.840.113619.2.278.3.262930758.589.1354512768.115&
objectUID=1.2.840.113619.2.278.3.262930758.589.1354512768.116.1&
contentType=application%2Fdicom&
requestType=WADO
    

官方说明就简单的翻译到这里,下面采用结合具体事例的方式来进行。

Orthanc WADO Plugin的编辑及使用:

该示例代码依赖于下面四部分:Orthanc Plugin SDK(0.8.0版本之后);CImg Library(用于将DICOM图像转换成PNG格式);JsonCpp库,用于解析Orthanc服务返回的Json格式的文件;CMake,用于编译源码。

1)Orthanc WADO Plugin的编译:

下载官方说明中的源码(http://www.codeproject.com/KB/webservices/797118/WadoPluginSources.zip),解压后按照README.txt中的说明编译安装Orthanc WADO Plugin:

第一步:进入cmd命令行模式,创建编译目录,输入指令:mkdir Build

第二步:进入Build目录

第三步:启动Cmake,开始编译,输入cmake ..\WadoPluginSources(注:这里..意思是返回WadoPluginSources源码中CmakeList.txt所在的目录,README.txt中的写法是错误的

第四步:打开Build目录下的WadoPlugin.sln工程,利用VS进行编译,会在Build\Debug目录下看到WadoPlugin.dll,说明WADO插件生成成功。

2)Orthanc WADO Plugin的安装:

源码包中README.txt给出的安装说明有误,应该将WadoPlugin.dll全路径名添加到Configuration.json文件中Plugin对应的字段内,如下图所示:

注意:在Windows系统中输入的WadoPlugin.dll的路径应该使用上图中的【/】,或者输入"c:\\WadoPluginSources\\Build\\Debug\\WadoPlugin.dll”。否则会出现错误。

3)Orthanc WADO Plugin启动:

修改完Configuration.json文件后,准到Orthanc.exe所在目录,例如我本机为:C:\Orthanc-0.8.5\Debug>Orthanc.exe ../../Orthanc/Configuration.json。【注意:后面跟的是添加了WadoPlugin.dll的Configuration.json的路径,如果输入Orthanc.exe --config=Configuration.json,是生成默认Configuration.json的结果,而并不会启动WadoPlugin服务)。

4)实例测试:

按照前几篇博文方式,上传两幅测试图像,结果如下:

其中已知test1的各级UID为:

StudyInstanceUID=1.3.6.1.4.1.30071.6.176694098609799.4240639413125000;

SeriesInstanceUID=1.3.6.1.4.1.30071.6.176694098609799.4240639413125000.1;

SOPInstanceUID=2.16.840.114421.81623.9430067258.9493139258;

构造WADO请求,查询test1图像,请求连接为:http://localhost:8042/wado?studyUID=1.3.6.1.4.1.30071.6.176694098609799.4240639413125000&seriesUID=1.3.6.1.4.1.30071.6.176694098609799.4240639413125000.1&objectUID=2.16.840.114421.81623.9430067258.9493139258&requestType=WADO

浏览器结果如下所示,与test1.dcm原文件相同。

新版Orthanc WADO Plugin:

1)官方说明:

官方说明中有这样一段:(http://www.codeproject.com/Articles/797118/Implementing-a-WADO-Server-using-Orthanc)

当从WADO HTTP请求中解析出study/series/instance标识符后,需要在Orthance数据库中进行查询。为了实现查询定位,官方博文中给出的代码实例直接借用了Orthanc内建的RESTful API服务(而不是直接响应WADO HTTP 请求)。

首先需要定位study级,代码如下:

  1. static bool LocateStudy(Json::Value& study,
  2. const std::string& studyUID)
  3. {
  4. // Retrieve the list of the studies that are stored in Orthanc
  5. Json::Value listOfStudies;
  6. if (!OrthancContext::GetInstance().RestApiDoGet(listOfStudies, "/studies"))
  7. {
  8. return false;
  9. }
  10. // Retrieve information about each of these studies
  11. for (Json::Value::ArrayIndex i = 0; i < listOfStudies.size(); i++)
  12. {
  13. std::string studyUri = "/studies/" + listOfStudies[i].asString();
  14. if (OrthancContext::GetInstance().RestApiDoGet(study, studyUri))
  15. {
  16. // If the "StudyInstanceUID" of this study matches, we are done
  17. if (study["MainDicomTags"]["StudyInstanceUID"].asString() == studyUID)
  18. {
  19. return true;
  20. }
  21. }
  22. }
  23. return false;
  24. }

LocateStudy函数内部先构造出/studies形式的RESTful API的uri请求,查询出Orthanc中的所有study的UUID,然后再循环遍历每一个获得的studyUUID,构造出/studies/{id}形式的RESTful API请求,逐个对比返回JSON结果中的StudyInstanceUID标签,实现study定位;

其次定位sereis级,代码如下:

  1. static bool LocateSeries(Json::Value& series,
  2. const Json::Value& parentStudy,
  3. const std::string& seriesUID)
  4. {
  5. // Loop over the child series of the located study
  6. const Json::Value& listOfSeries = parentStudy["Series"];
  7. for (Json::Value::ArrayIndex j = 0; j < listOfSeries.size(); j++)
  8. {
  9. std::string seriesUri = "/series/" + listOfSeries[j].asString();
  10. // If the "SeriesInstanceUID" of this series matches, we are done
  11. if (OrthancContext::GetInstance().RestApiDoGet(series, seriesUri) &&
  12. series["MainDicomTags"]["SeriesInstanceUID"].asString() == seriesUID)
  13. {
  14. return true;
  15. }
  16. }
  17. return false;
  18. }

与study级类同;

最后是Instance级,代码如下:

  1. static bool LocateInstance(Json::Value& instance,
  2. const Json::Value& parentSeries,
  3. const std::string& objectUID)
  4. {
  5. // Loop over the child instances of the located series
  6. const Json::Value& listOfInstances = parentSeries["Instances"];
  7. for (Json::Value::ArrayIndex k = 0; k < listOfInstances.size(); k++)
  8. {
  9. std::string instanceUri = "/instances/" + listOfInstances[k].asString();
  10. // If the "SOPInstanceUID" of this series matches "objectUID", we are done
  11. if (OrthancContext::GetInstance().RestApiDoGet(instance, instanceUri) &&
  12. instance["MainDicomTags"]["SOPInstanceUID"].asString() == objectUID)
  13. {
  14. return true;
  15. }
  16. }
  17. return false;
  18. }

上述定位流程复杂,从0.8.0版本之后Orthanc提供了直接访问数据库中DICOM索引的函数,OrthancPluginLookupPatient(),OrthancPluginLookupStudy(), OrthancPluginLookupStudyWithAccessionNumber(), OrthancPluginLookupSeries()and OrthancPluginLookupInstance()。利用该类函数就需不要先定位study、再定位series、最后定位instance如此繁琐了,修改后的代码如下:

  1. //2014-12-07:zssure
  2. //利用新的Orthanc插件的接口,直接定位Instance
  3. //http://www.codeproject.com/Articles/797118/Implementing-a-WADO-Server-using-Orthanc
  4. static bool LocateInstance(Json::Value& instance, const std::string& objectUID)
  5. {
  6. char* instanceId = OrthancPluginLookupInstance
  7. (OrthancContext::GetInstance().GetContext(), objectUID.c_str());
  8. if (instanceId == NULL)
  9. {
  10. return false;
  11. }
  12. std::string instanceUri = "/instances/" + std::string(instanceId);
  13. OrthancPluginFreeString(OrthancContext::GetInstance().GetContext(), instanceId);
  14. return (OrthancContext::GetInstance().RestApiDoGet(instance, instanceUri) &&
  15. instance["MainDicomTags"]["SOPInstanceUID"].asString() == objectUID);
  16. }
  17. //zssure:end

2)新版Wado Plugin修改:

按照官方的说明Orthanc Plugin SDK是以C头文件格式给出,因此直接利用Orthanc-0.8.5中的OrthancCPlugin.h文件替换掉WadoPluginSources中的OrthancCPlugin.h后,发现WadoPlugin.cpp中我们新添加的LocateInstance函数中的GetContext()无法识别:

打开OrthancContext.h文件发现,文件中并不存在GetContext()函数,因此需要手动添加公有函数:

  1. public:
  2. OrthancPluginContext* GetContext()
  3. {
  4. return context_;
  5. }

修改完成后可以识别GetContext()函数了,但是编译后出现如下错误:

将#include "../../Resources/ThirdParty/VisualStudio/stdint.h"代码修改为#include "stdint.h"后即可消除上述错误。重新生成后可获得新版WadoPlugin.dll插件。重新输入WADO Request,得到测试结果如下:

至此Orthanc WADO Plugin的开发就讲解完成了。

DICOM医学图像处理:Orthanc Plugin SDK实现WADO服务的更多相关文章

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

    背景: 上一篇博文简单翻译了Orthanc官网给出的CodeProject上“利用Orthanc Plugin SDK开发WADO插件”的博文,其中提到了Orthanc从0.8.0版本之后支持快速查询 ...

  2. DICOM医学图像处理:DIMSE消息发送与接收“大同小异”之DCMTK fo-dicom mDCM

    背景: 从DICOM网络传输一文开始,相继介绍了C-ECHO.C-FIND.C-STORE.C-MOVE等DIMSE-C服务的简单实现,博文中的代码给出的实例都是基于fo-dicom库来实现的,原因只 ...

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

    转载:http://blog.csdn.net/zssureqh/article/details/39213817 背景: 上一篇专栏博文中针对PACS终端(或设备终端,如CT设备)与RIS系统之间w ...

  4. [转]DICOM医学图像处理:Deconstructed PACS之Orthanc

    转载:http://blog.csdn.net/zssureqh/article/details/41424027 背景: 此篇博文介绍一个开源的.基于WEB的DICOM Server软件.该开源软件 ...

  5. DICOM医学图像处理:Deconstructed PACS之Orthanc

    背景: 此篇博文介绍一个开源的.基于WEB的DICOM Server软件.该开源软件完全使用C++编写,不依赖于第三方数据库(内置了SQLite数据库)或其他框架,支持RESTful API设计模式. ...

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

    背景: 上篇博文为引子,介绍了一款神奇的开源PACS系统——Orthanc.本篇开始解读官方Cookbook中的相关内容,对于简单的浏览.访问和上传请阅读前篇博文.在常规的PACS系统中还未出现对于D ...

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

    背景介绍: 近期项目需求,须要使用C#进行最新的UI和相关DICOM3.0医学图像模块的开发.在C++语言下,我使用的是应用最广泛的DCMTK开源库,在本专栏的起初阶段的大多数博文都是对DCMTK开源 ...

  8. DICOM医学图像处理:WEB PACS初谈

    背景: 周末看到了一篇原公司同事的文章,讲的是关于新的互联网形势下的PACS系统.正好上一篇专栏文章也提到了有想搭建一个worklist服务器的冲动,所以就翻箱倒柜将原本学生时代做课题时搭建的简易We ...

  9. DICOM医学图像处理:WEB PACS初谈四,PHP DICOM Class

    背景: 预告了好久的几篇专栏博文一直没有整理好,主要原因是早前希望搭建的WML服务器计划遇到了问题.起初以为参照DCMTK的官方文档wwwapp.txt结合前两天搭建的WAMP服务器可以顺利的实现WM ...

随机推荐

  1. HDU 2460 Network 边双连通分量 缩点

    题意: 给出一个无向连通图,有\(m\)次操作,每次在\(u, v\)之间加一条边,并输出此时图中桥的个数. 分析: 先找出边双连通分量然后缩点得到一棵树,树上的每条边都输原图中的桥,因此此时桥的个数 ...

  2. 简述 yield和yield from关键字

    1.可迭代对象与迭代器的区别 可迭代对象:指的是具备可迭代的能力,即enumerable.  在Python中指的是可以通过for-in 语句去逐个访问元素的一些对象,比如元组tuple,列表list ...

  3. Python面试题(练习一)

    1.Python的可变类型和不可变类型? 可变类型:list.dict(列表和字典) 不可变类型:数字.字符串.元组 2.求结果: v = dict.fromkeys(['k1','k2'],[]) ...

  4. 九度oj 题目1356:孩子们的游戏(圆圈中最后剩下的数)

    题目描述: 每年六一儿童节,JOBDU都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此.HF作为JOBDU的资深元老,自然也准备了一些小游戏.其中,有个游戏是这样的:首先,让小朋友们围成一个大圈. ...

  5. sysctl内核参数解析

    sysctl内核参数解析 kernel.参数 kernel.shmall = 2097152    ## 1> 表示所有内存大小.可以分配的所有共享内存段的总和最大值.(以页为单位) ## 2& ...

  6. 关于iOS 7的几个开源项目

    MBSwitch   MBSwitch是一个体现了iOS 7扁平化设计风格的UISwitch,支持iOS 7以下系统.允许使用者进行颜色的深度自定义,你可以定义边框的颜色,开/关的颜色以及按钮的颜色. ...

  7. 【bzoj3252】攻略 贪心+DFS序+线段树

    题目描述 题目简述:树版[k取方格数] 众所周知,桂木桂马是攻略之神,开启攻略之神模式后,他可以同时攻略k部游戏. 今天他得到了一款新游戏<XX半岛>,这款游戏有n个场景(scene),某 ...

  8. Json操作(汇总)

    利用:com.fasterxml.jackson 原文地址:https://blog.csdn.net/joyous/article/details/9448461 说明:Map转化为Json:创建J ...

  9. 详解keepalived配置和使用

    标签:keepalived 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://lanlian.blog.51cto.com/6790 ...

  10. ajax提交数据服务端返回报错

    报错如下: if response.get('X-Frame-Options') is not None:AttributeError: 'str' object has no attribute ' ...