上一篇博文简单翻译了Orthanc官网给出的CodeProject上“利用Orthanc Plugin SDK开发WADO插件”的博文,其中提到了Orthanc从0.8.0版本之后支持快速查询,而原本的WADO请求需要是直接借助于Orthanc内部的REST API逐级定位。那么为什么之前的Orthanc必须要逐级来定位WADO请求的Instance呢?新版本中又是如何进行改进的呢?此篇博文通过分析Orthanc内嵌的sqlite数据库,来剖析Orthanc的RESTful API机制,以及WADO服务的实现。
Orthanc UUID与DICOM UID: 1)Orthanc Plugin SDK模拟实现WADO Server上一篇博文中提到的LocateStudy、LocateSerIEs、LocateInstanc函数都不是直接查询WADO请求传入的各级UID(StudyUID、SerIEsUID、InstanceUID),而是通过内部构建出等同的RESTful API来实现。举个例子,测试DCM文件名为test1.dcm,其对应的三级UID分别是:
StudyUID=1.3.6.1.4.1.30071.6.176694098609799.4240639413125000,
SerIEsUID=1.3.6.1.4.1.30071.6.176694098609799.4240639413125000.1,
InstanceUID(即SOP Instance UID)=2.16.840.114421.81623.9430067258.9493139258,正常的WADO协议规定的请求连接为:
http://localhost:8042/wado?requestType=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
按照常规方式来实现的话,应该是直接利用SQL语句在指定的数据库中直接搜索WADO Request中的三级UID,而在Orthanc Plugin SDK实现的WADO插件中,却是分级进行,详细流程如下:
Study级别:第一,LocateStudy函数中构建http://localhost:8042/studies请求,利用内置的REST API服务获得当前数据中所有的studIEs的UUID(后面会讲到该UUID与DICOM UID之间的转换关系);第二,LocateStudy中的每一个studyUUID,构造http://localhost:8042/studies/XXXX-XXXX-XXXX-XXXX,通过对比返回JsON数据中study["MainDicomTags"]["StudyInstanceUID"]标签值与WADO中的studyUID,实现定位Study的功能;
SerIEs级别:与Study相同,先构造http://localhost:8042/series获取全部serIEsUUID,然后针对每个serIEsUUID构造http://localhost:8042/series/XXXX-XXXX-XXXX-XXXX,对比返回值中的serIEs["MainDicomTags"]["SerIEsInstanceUID"]与serIEsUID,实现定位SerIEs的功能;
Instance级别:先构造http://localhost:8042/instances获取全部instanceUUID,然后对每个instanceUUID构造http://localhost:8042/instances/XXXX-XXXX-XXXX-XXXX对比返回值中的instance["MainDicomTags"]["SOPInstanceUID"]与WADO请求中的objectUID,实现最终定位图像的目的。
2)Orthanc UUID与DICOM UID上面的实现是不是很繁琐啊,哈哈。好在官方Plugin SDK说明博文中给出了最新版的定位方式,具体的实现可参见我上一篇博文(http://www.jb51.cc/article/p-vndxmzzm-pn.html)。那么为何Orthanc起初需要如此繁琐的定位图像呢?这里我们先简单的分析一下Orthanc内部是如何来标记文件的唯一性的,后续章节再详细分析之前Orthanc模拟WADO服务为何如此繁琐。
在Orthanc源码中有这样一个类DicomInstanceHasher(定义在DicomInstanceHasher.h,实现在DicomInstanceHasher.cpp),其注释中如此描述:
/** * This class implements the hashing mechanism that is used to * convert DICOM unique IDentifIErs to Orthanc IDentifIErs. Any * Orthanc IDentifIEr for a DICOM resource corresponds to the SHA-1 * hash of the DICOM IDentifIErs.* \note SHA-1 hash is used because it is less sensitive to * collision attacks than MD5. <a * href="http://en.wikipedia.org/wiki/SHA-256#Comparison_of_SHA_functions">[Reference]</a> **/
从描述中我们可以知道Orthanc内部时利用SHA1(百度百科:维基百科:)算法来计算出DCM文件的唯一标识的,具体计算过程为:
PatIEntID对应的UUID:即向SHA1计算函数中直接输入【PatIEntID】,获得SHA1值
StudyUID对应的UUID:向SHA1计算函数中输入【PatIEntID+”|"+StudyUID】,获得SHA1值
SerIEsUID对应的UUID:向SHA1计算函数中输入【PatIEntID+”|"+StudyUID+”|"+SerIEsUID】,获得SHA1值
InstanceUID对应的UUID:向SHA1计算函数中输入【PatIEntID+”|"+StudyUID+”|"+SerIEsUID+”|"+InstanceUID】,获得SHA1值
这就是OrthancUUID与DICOM UID之间的转换关系,下一节讲解数据库时再给出真实的示例。
Orthanc sqlite介绍: 1)Orthanc sqlite数据库列表介绍:Orthanc采用了sqlite嵌入式数据库,对数据库的 *** 作在工程代码中集成,因此在使用过程中并未能感觉到数据库的管理,这也支撑了Orthanc主打的轻型、便捷、网络化优点。下面简单介绍一下Orthanc sqlite数据表的逻辑:
sqlite的数据库文件默认存储位置为:C:\Orthanc\OrthancStoragef\index(其真实后缀为db3)。用sqlite可视化工具打开index文件,可以看到如下几张表:
从表名称中可以推断出各表大致的用途:例如Attachedfiles是添加文件的记录、Changes可能为修改 *** 作(删除、匿名化等)、DicomIDentifIErs为DICOM文件标示符(各级UID)、ExportedResources可能为导出或上传 *** 作、GlobalPropertIEs应该是全局属性、MainDicomTags应该是Orthanc返回给REST API *** 作的JsON格式数据、Metadata是数据体、Resources应该是文件体标记(PatIEntRecyclingOrder暂时不清楚,请看下文分析)。
2)Orthanc主要数据 *** 作类介绍:Orthanc源码中有DatabaseWrapper类,其中有如下注释:
/** * This class manages an instance of the Orthanc sqlite database. It * translates low-level requests into sql statements. Mutual * exclusion MUST be implemented at a higher level. **/
说明该类是Orthanc *** 作sqlite数据库的封装类,具体的涉及到sqlite数据库底层的 *** 作都由DatabaseWrapper来完成。与上节看到的index中的表对比,将DatabaseWrapper类主要函数分类:
数据表 | DatabaseWrapper *** 作函数 |
Attachedfiles | AddAttachment DeleteAttachment LookupAttachment ListAvailableAttachments |
Resources | CreateResource DeleteResource GetResourceType GetResourceCount LookupResource |
Metadata | DeleteMetadata GetAllMetadata GetMetadata GetMetadataAsInteger LookupMetadata SetMetadata |
另外还会看到众多获取各表字段的函数,例如GetPublicID、GetChildrenPublicID等等。
Orthanc中sqlite实例测试:在大致了解了Orthanc中sqlite数据库的基本结构后,进行一下实例测试。如博文(http://www.jb51.cc/article/p-vndxmzzm-pn.html)所述,向Orthanc中添加数据有多种方式,命令行工具,REST API,以及网页。下面我们对Orthanc自带的Explorer和DCMTK工具包storescu.exe进行真实数据上传测试。
sqlite数据写入逻辑实例测试 1)Explorer中Drag & Drop测试:先打开Orthanc的浏览界面:http://localhost:8042/app/explorer.html#upload
拖拽任意图像到浏览器内,单击【Start the upload】,直到出现绿色'【Done】,表明上传成功。
数据库变化如下:
上述利用Orthanc内嵌的Explorer成功上传并写入数据库。此次使用storescu.exe,把Orthanc当做Dicom Server查看数据写入情况,写入指令如下:
storescu.exe -d localhost 4242 -aet ZSSURE -aec ORTHANC c:\test2.dcm
完成后数据库变化如下:
上面利用两种方式来完成了添加数据到Orthanc内嵌sqlite数据库(还有REST API第三种方式,参见之前博文:,由于原理与Explorer中类同就不单独介绍了),并且观察到了数据库的真实变化,但是具体的字段含义此刻可能还不是很清楚,让我们利用REST API来读取数据库并尝试分析下其中的含义。
1)PatIEnts:curl http://localhost:8042/patients
返回结果如上图所示,通过对比上一节中观察到的数据库变化发现:返回的两个PatIEnt UUID分别记录在Resources表中PublicID列的第4与8行,其对应的internalID分别为44和48。因此我们可以推断出Resources中应该是我们上传文件的记录,下面来验证一下我们的猜想。
根据上一节分析指导此处的publicID应该是DICOM UID对应的UUID,即SHA1计算值。打开在线计算SHA1网站:http://www.seacha.com/tools/sha1.html。按照上一节分析输入test1.dcm的各级UID,计算结果如下所示:
从图中我们可以看出在Resources表中的前四条记录按照级别深度分别存储的是InstanceUUID、SerIEsUUID、StudyUUID、PatIEntUUID,这些UUID是由DICOM 各级UID进行SHA1计算所得。有兴趣的话可以验证一下后四条记录,自然也是相同的含义。至此我们搞清楚了Resources表的意义,是用于存储DICOM图像的UUID
2)StudIEs:curl http://localhost:8042/studies
返回结果为,
即上述分析的Resources表中的每组的第三条记录,也就是表中的43和47行。
3)SerIEs:curl http://localhost:8042/series
返回结果为,
Resources表中每组记录的第二条,表中的42和46行。
4)Instances:curl http://localhost:8042/instances
返回结果为,
Resources表中每组记录的第一条,表中的41和45行。
5)查看每个PatIEnt内容:curl http://localhost:8042/patients/64d6f8a0-ea0ffdb2-a14d1488-4fa7879c-2d9758d8
对比前面数据库的分析,发现大多数字段都可以直接在数据库中看到对应的值,如下图所示:
因为查看Study和SerIEs级别的内容与查看PatIEnt级别类似,就不啰嗦了,直接看一下具体Instance(即DICOM文件)的查询结果,输入指令:
curl http://localhost:8042/instances/064123d1-803dde30-f81071dc-cb2aad3b-bd246b7b
上述结果在数据库中都可以直接找到,如下图所示:
至此我们看到了熟悉的【SOP Instance UID】,原来存储在DicomIDentifIErs表中。
从上述的多次实例测试我们也大致猜出来Orthanc sqlite数据库中各表的作用,Resources表中是利用SHA1来计算出UUID唯一标识我们的DCM文件;DicomIDentifIErs表记录的是对应DCM文件的各级DICOM UID,想必这也是WADO协议中需要定位文件的必要参数;MainDicomTags表存储的是对应DCM文件的主要几种Tag,包括Group号、Element号,以及值域数据。各个表之间的关联是通过Resources表中的internalID来完成的,internalID是大多数表的主键(PK)。
到这里本文就可以结束了,已经达到了剖析Orthanc sqlite的目的,但是还并未清晰的看出REST API与WADO的区别。为此,也为了更好的了解Orthanc的 *** 作流程,再补充一节,通过单步调试来深入分析一下Orthanc的实现机制,达到深入剖析的境界。
Orthanc sqlite总结:
前一篇博文中对Orthanc官方给出的Plugin SDK开发文档进行了简短的翻译,文档中指出在0.8.0版本之前,Orthanc是利用内建的RESTful API来模拟是实现WADO服务的,并非是直接响应浏览器发送过来的WADO请求。前文中已经介绍了如何具体编译和安装官方WadoPlugin.dll,这里在剖析sqlite的基础上采用单步调试的方式查看一下早期Orthanc是如何利用RESTful API来模拟实现WADO服务的。
RESTful API模拟WADO官网给出的利用内建RESTful API仿真WADO的代码在WadoPlugin.cpp中的Wado函数内,其中最主要的是LocateStudy、LocateSerIEs和LocateInstance三个定位函数。下图是LocateStudy级别的单步调试结果:
从上图可以看出在LocateStudy函数内部,首先是利用DatabaseWrapper.cpp中的GetAllPublicID函数从sqlite数据库的Resources表中提取出全部的publicID,如我们上面分析,每一个上传的文件都有唯一对应的UUID格式的publicID。
随后,在LocateStudy函数内部,对前面返回的所有publicID进行循环遍历,针对每一个/studIEs/{publicID}进行资源定位,用到的函数是LookupResource(同样在DatabaseWrapper.cpp中)。通过下图中可以看出该函数从Resources表中根据publicID查询出internalID和resourceType两个字段。查看LookupResource函数参数type的类型ResourceType定义可知:Resources表中第二列字段存储的是publicID对应的资源级别,该级别按照DICOM3.0标准划分为PatIEnt(=1)、Study(=2)、SerIEs(=3)、Instance(=4)四级,如Enumeration.h中定义所示:
enum ResourceType{ResourceType_PatIEnt = 1,ResourceType_Study = 2,ResourceType_SerIEs = 3,ResourceType_Instance = 4};
下面直接贴出调试的截图:
从截图中可以看出Orthanc中响应WADO请求的大致数据库检索流程,首先是在Resources表中查询所有的publicID(因为初次查询无法利用WADO请求中的studyID/serIEsID/objectID计算出任何有效UUID);然后构造/studIEs/{ID}形式的uri,利用RESTful API机制查询组合出各个级别的publicID,其各级之间的关系由表Resources中的parentID字段标明,而唯一性由主键internalID来决定。这也就是上述多次发起RESTful API查询数据库的主要原因;待获得了各级publicID和internalID后,就是从DicomIDentifIErs表、MainDicomTags表和Metadata表中提取DICOM文件关键信息 *** 作;最后自然就是将查询到的结果图像返回到浏览器端(可以DICOM格式或JPEG缩略图形式返回)。
【注】:在表Metadata中记录的type由Enumerations.h文件给出定义,如下:
enum MetadataType{MetadataType_Instance_IndexInSerIEs = 1,MetadataType_Instance_ReceptionDate = 2,MetadataType_Instance_RemoteAet = 3,MetadataType_SerIEs_ExpectednumberOfInstances = 4,MetadataType_ModifIEdFrom = 5,MetadataType_AnonymizedFrom = 6,MetadataType_LastUpdate = 7,// Make sure that the value "65535" can be stored into this enumerationMetadataType_StartUser = 1024,MetadataType_EndUser = 65535};
可以发现其中有RemoteAet类型,因此猜测可能跟DICOM 协议有关,用于记录上传端的AE Title,通过输入指令验证如下:
指令:storescu.exe -d localhost 4242 -aet ZSSURE -aec ORTHANC c:\Slice_0010.dcm
测试结果:
在分析了原有的效率较低的WadoPlugin查询方式后,我们按照同样的方式单步调试,查看新的Orthanc PluginSDK的查询过程。具体截图如下:
上述系列截图可以看出新的Orthanc Plugin SDK通过三步可以轻松从sqlite数据库中读取指定Instance的publicID(即上文说的UUID);获得了InstanceUUID后构造/instances/{ID}类型的RESTful API uri来直接获取Orthanc数据库中的文件信息。如是减少了循环查询数据库的次数,提升了效率。仔细分析下来可以发现之所以原本的PluginSDK需要查询多次数据库是因为Orthanc中将DICOM文件及相关信息按照不同级别将信息分类存储,因此提取时需要分别定位然后将查询结果组合。另外打开Orthanc的Storage目录可以发现对于每个DCM文件Orthanc采用了publicID的两级目录方式来存储:第一级目录是文件的MD5值中的第一部分的前2个字节;第二级是后两个字节。如下图所示:
至此可以清楚地了解了Orthanc底层sqlite数据库的结构及相关 *** 作,为了兼容RESTful API和DICOM3.0标准,数据库的逻辑设计是很精妙的,后续可深入研究一下。
fo-dicom搭建简单的DICOM Server服务器
总结作者:zssure@163.com
时间:2014-12-10
以上是内存溢出为你收集整理的DICOM医学图像处理:深入剖析Orthanc的SQLite,了解WADO & RESTful API全部内容,希望文章能够帮你解决DICOM医学图像处理:深入剖析Orthanc的SQLite,了解WADO & RESTful API所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)