標籤:wado dicom restful api orthanc dcmtk
背景:
上一篇博文簡單翻譯了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://blog.csdn.net/zssureqh/article/details/41836885)。那麼為何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://blog.csdn.net/zssureqh/article/details/41836885)所述,向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】,表明上傳成功。
資料庫變化如下:
2)storescu.exe測試:
上述利用Orthanc內嵌的Explorer成功上傳並寫入資料庫。此次使用storescu.exe,把Orthanc當做Dicom Server查看資料寫入情況,寫入指令如下:
storescu.exe -d localhost 4242 -aet ZSSURE -aec ORTHANC c:\test2.dcm
完成後資料庫變化如下:
SQLite查詢邏輯測試:
上面利用兩種方式來完成了添加資料到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
對比前面資料庫的分析,發現大多數欄位都可以直接在資料庫中看到對應的值,如所示:
6)查看具體Instance內容
因為查看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
測試結果:
直接實現WADO
在分析了原有的效率較低的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伺服器
[email protected]
時間:2014-12-10
DICOM醫學影像處理:深入剖析Orthanc的SQLite,瞭解WADO & RESTful API