DICOM医学图像处理:Orthanc Plugin SDK实现WADO服务

背景:

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级,代码如下:

[cpp] view plain copy

print?

  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级,代码如下:

[cpp] view plain copy

print?

  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级,代码如下:

[cpp] view plain copy

print?

  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如此繁琐了,修改后的代码如下:

[cpp] view plain copy

print?

  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()函数,因此需要手动添加公有函数:

[cpp] view plain copy

print?

  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的开发就讲解完成了。

时间: 2024-08-15 12:31:09

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

DICOM医学图像处理:深入剖析Orthanc的SQLite,了解WADO &amp; 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在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医学图像处理: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医学图像处理:开源库mDCM与DCMTK的比较分析(一),JPEG无损压缩DCM图像(续)

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

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与fo-dicom保存文件的不同设计模式之“同步VS异步”+“单线程VS多线程”

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