標籤:dcmtk worklist findscu 移動醫學 dicom
背景:
DICOM3.0協議中有介紹關於worklist的部分。簡而言之,worklist可以看做是放射科裝置從醫院RIS系統中自動讀取患者資訊的一種“通訊協定”,可以指儲存在RIS系統中的患者資料庫,主要包括患者的基本資料(如年齡、性別、身高、體重、出生年月等),這與DCM檔案資訊頭MetaInfo中的多數欄位重合。因此從RIS系統中自動擷取worklist是醫院資訊化的必要組成部分。下面簡單的給出幾個映像,形象的描述一下worklist的作用。
worklist的執行個體學習:
在簡單的瞭解了worklist的作用後,下面我們利用DCMTK提供的工具包(wlmscpfs.exe和findscu.exe)來真實類比一下該情境,從而更深刻的學習worklist的功能。
worklist簡單的看做一種“通訊”,那麼自然就存在著通訊的兩端,暫且稱作“服務端”和“用戶端”。這裡我們用wlmscpfs.exe來作為worklist通訊的服務端,即等待外部存取的終端;用findscu.exe來作為服務端,用來發起worklist訪問。
首先簡單的介紹一下工具包的指令及使用方式。wlmscpfs.exe是類似於DOS時代的命令,通過設定參數可以實現不同的目的。利用Win+R鍵開啟作業系統的運行視窗,輸入cmd後進入到命令提示行視窗。然後進入到DCMTK編譯後的bin檔案夾(我本機地址是C:\Program Files (x86)\DCMTK\bin)目錄,此時直接輸入wlmscpfs.exe就可以看到關於該命令工具的各種說明。
>cd C:\Program Files (x86)\DCMTK\bin
>wlmscpfs.exe
(注,此處如果為了省事,可以將bin檔案夾路徑添加到windows的環境變數中,如是就可以在任何目錄下使用bin下的各種工具了)
由看到,wlmscpfs.exe工具至少需要給出port一個參數,即開啟worklist服務的本機連接埠號碼。另外還需要輸入worklist資料庫檔案的地址,用來供用戶端查詢、訪問使用。下面我們正式開啟worklist的服務端程式,至於開啟全過程,可以跟大家推薦CSDN一位博主的精品文章(http://blog.csdn.net/pachleng/article/details/5800513),博文中給出了具體的操作步驟,這個執行個體是對DCMTK論壇中的補充和更新。大家可以動手試一下。
第一步:建立各級目錄
以我的電腦為例,我在D盤建立了DCMWorklist檔案夾,然後建立了兩個子檔案夾wlistdb和wlistqry。
第二步:準備worklist資料庫檔案,開啟worklist服務端服務。
然後將worklist的資料庫檔案拷貝到wlistdb目錄下,此處參見冷哥博文,記得建立OFFIS子目錄。至於worklist資料庫檔案通常在dcmtk庫的源碼中已經給出了,預設目錄是dcmtk-3.6.0\dcmtk-3.6.0\dcmwlm\datawlistdb(我用的是3.6.0版本)。但是源碼中的檔案通常是.dump副檔名的檔案,也就是我們常見的文字檔(用記事本或者Notepad++等工具雙擊即可開啟)。通過利用dcmtk工具包中的dump2dcm.exe可以將.dump檔案轉換成.wl檔案,轉換後的.wl檔案就是worklist資料庫檔案。
>dcmp2dcm.exe .\dcmtk-3.6.0\dcmtk-3.6.0\dcmwlm\data\wklistdb\wlist1.dump d:\DCMWorklist\wlistdb\OFFIS\wlist.wl
>……
有了worklist資料庫檔案後,我們就可以開啟worklist服務了,利用的工具就是前文提到的wlmscpfs.exe(從工具名稱中的SCP就可以看出這應該是服務端開啟服務的)。
>wlmscpfs.exe –d –dfr –dfp d:\DCMWorklist\wlistdb 104 (註:其中的-d是為了方便我們觀察工具運行過程而開啟的調試開關)
>……
第三步,準備查詢檔案,開啟worklist查詢。
服務端已經準備就緒,下面就是該發起worklist查詢服務啦。利用的工具是findscu.exe(從工具名稱中的SCU同樣可以猜測出這是用戶端)。dcmtk源碼包中同樣給我們提供了查詢worklist的查詢檔案,預設目錄是dcmtk-3.6.0\dcmtk-3.6.0\dcmwlm\data\wlistqry,開啟後可以看到裡面以.dump格式存在的檔案,再次利用dump2dcm.exe將.dump轉換為.wl檔案。
>dcmp2dcm.exe .\dcmtk-3.6.0\dcmtk-3.6.0\dcmwlm\data\wklistqrt\wlistqry1.dump d:\DCMWorklist\wlistqry\wlistqry.wl
然後我們利用findscu發起查詢請求,
>findscu.exe –d 127.0.0.1 104 d:\DCMWorklist\wlistqry\wlistqry.wl –aec OFFIS
其中aec代表的是被請求或者說被呼叫的應用端的名稱,即我們wlistdb檔案夾內的OFFIS子檔案夾。
通過以上三步,我們就簡單的利用dcmtk提供的wlmscpf.exe和findscu.exe工具包以及相應的wklistdb資料庫檔案和wklistqry資料庫查詢檔案類比了worklist服務開啟及查詢的整個流程。是不是很簡單,很容易上手。
實際結果分析:
上一部分中提到了在使用wlmscpfs.exe和findscu.exe工具包的時候開啟了-d偵錯模式,目的就是為了方便我們跟蹤整個通訊的流程。另外為了方便查看,我們利用重新導向技術,將wlmscpfs.exe和findscu.exe工具包的調試資訊輸出到txt檔案,方便我們事後進行再次對比查看。下面將兩個工具包的調試資訊用Notopad++開啟,對比分析一下,見:
從調試資訊可以清晰的看到wlmscpfs.exe與findscu.exe之間的通訊流程,該流程在DICOM3.0標準的第四部分(Service Class Specifications )和第八部分(Network Communication Support for Message Exchange)都有詳細的介紹,上述的重新導向產生的文字文件就是學習DICOM3.0第四、八部分最好的執行個體。因為dcmtk是開源的,所以這方便我們分析wlmscpfs.exe和findscu.exe兩個工具包的源碼。具體分析見下一節。
wlmscpfs.exe和findscu.exe工具包源碼分析:
|
wlmscpfs.exe |
findscu.exe |
C/S |
worklist服務端 |
worklist用戶端 |
源碼檔案 |
wlmscpfs.cc wlcefs.cc wlmactmg.cc |
findscu.cc |
內建函式 |
1)ConnectToDataSource();//開啟串連 2)WlmActivityManager(); //函數內部利用WSAStartup()啟動了Windows通訊端服務 3)StartProvidingService(); //啟動worklist管理服務,位於wlmactmg.cc檔案。函數內部調用(a)(b)兩個函數。 (a)ASC_initializeNetwork()函數 ASC_initializeNetwork函數調用了DICOM協議封裝的TCP協議函數DUL_InitializeNetwork(該函數內部就會出現我們在通訊端編程中常見的socket、setsockopt、bind和listen函數) (b)WaitForAssociation();函數 WaitForAssociation函數調用了DICOM協議封裝的TCP/IP協議函數receiveTransportConnectionTCP(該函數內部利用的是select連接埠模式,會出現通訊端編程中常見的select、accept函數) 4)disconnectfromDataSource(); //中斷連線的函數。 |
1)WSAStartup();//初始化通訊端服務 2)DcmFindSCU::initializeNetWork(); //函數內部調用的也是ASC_initializeNetwork函數。 3)DcmFindSCU::performQuery(); //同樣該函數內部封裝了很多以DUL開頭的協議操作函數。DUL是DICOM Upper Layer 的縮寫。 4)WSACleanup(); |
從上述分析中我們基本可以看出,worklist的通訊是建立在TCP/IP這一現有協議之上的,可以說是對協議的二次封裝。與我們平時進行通訊端編程的基本流程相似,搞清楚了這一點,對於分析工具包、學習工具包的使用都會有很大的協助。
worklist資料庫檔案或查詢檔案(*.wl)的產生:1)問題提出:
參照冷哥博文(http://blog.csdn.net/pachleng/article/details/5800513、http://blog.csdn.net/pachleng/article/details/5827232)中的說明,我們可以很容易的利用DCMTK的工具包學習worklist操作。但是在實際應用模仿過程中,有的人可能會好奇為什麼要把wklist.wl和wklistqry.wl檔案分別放在不同目錄?為什麼同為以.wl為副檔名的檔案,一個就可以作為worklist的資料庫檔案放在服務端,而另一個就是客戶單的查詢檔案?如果想發起自己的查詢,即C-FIND請求,我們怎麼手動產生wklistqry.wl檔案?
針對上述問題,在dcmtk的論壇中也曾經有人提到過,參見(http://forum.dcmtk.org/viewtopic.php?f=1&t=1475&hilit=wlmscpfs.exe%23p5016)。利用UltraEdit工具將wklist.wl和wklistqry.wl檔案同時開啟,對比如下:
從中可以看出,作為worklist用戶端資料庫檔案的wklist.wl中是將患者真實資訊以符合DICOM3.0標準欄位的形式儲存,而用戶端發起查詢請求的wklistqry.wl檔案內部只是簡單的需要查詢的空欄位,即各個欄位的值域都為空白。參考博文中給出了利用dump2dcm.exe工具包將.dump檔案轉換成wklistqry.wl檔案(dump可以認為是普通的文字檔,可以利用記事本等工具進行直接編輯)。如所示:
例如作為測試用的wlist-2.dump檔案中的患者ID為123456,產生後的wlist-2.wl檔案中的(0010,0020)欄位也是123456。
2)實際測試:
從dump2dcm.exe工具包的說明我們就可以知道,.wl檔案其實就是dcm檔案,只是該類檔案中並不存在真實的像素資訊。通常只包含資訊頭部分,主要指的是患者的各項資訊。因此想利用dcmtk的庫函數直接擷取.wl檔案,其實就是手動構造dcm檔案的過程。參見http://support.dcmtk.org/docs/mod_dcmdata.html#Examples中的第二個例子,我們可以手動產生以“.wl”為尾碼的dcm檔案。具體的代碼如下:
#include <stdio.h>#include <tchar.h>#include "dcmtk/config/osconfig.h"#include "dcmtk/dcmdata/dctk.h"#include "dcmtk/dcmdata/dcpxitem.h"#include "dcmtk/dcmjpeg/djdecode.h"#include "dcmtk/dcmjpeg/djencode.h"#include "dcmtk/dcmjpeg/djcodece.h"#include "dcmtk/dcmjpeg/djrplol.h"using namespace std;int main(){char uid[100];DcmFileFormat fileformat;DcmDataset *dataset = fileformat.getDataset();/************************************************利用下列語句可以產生worklist的資料庫檔案,即*不含有影像資訊的dcm檔案*************************************************/dataset->putAndInsertString(DCM_SOPClassUID, UID_SecondaryCaptureImageStorage);dataset->putAndInsertString(DCM_SOPInstanceUID, dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT));dataset->putAndInsertString(DCM_PatientName, "Doe^John");OFCondition status = fileformat.saveFile("D:\\DcmWorklist\\worklist\\test.wl", EXS_LittleEndianExplicit);if (status.bad())cerr << "Error: cannot write DICOM file (" << status.text() << ")" << endl;return 0;}
既然服務端需要的worklist資料庫檔案和用戶端需要的查詢檔案都是.wl檔案,唯一的區別就是一個有值域,一個沒有。因此利用上述代碼我們既可以產生worklist資料庫檔案,也可以產生worklist查詢檔案。
3)測試結果:
查看findscu.exe工具包,我們可以找到-k選項,也就是在提供了查詢檔案wklistqry.wl的同時也可以指定限定的查詢欄位,例如
>findscu 127.0.0.1 104 -v -k 0010,0020="123456" -aec OFFIS wlistqry.wl
如果不添加-k 0010,0020=“123456”限定選項,查詢結果如前文中重新導向的結果相同,而添加了限定欄位後,我們能夠查詢到的就只有資料庫端中滿足PatientID欄位為123456的患者資料。具體結果如下:
從中我們可以看到服務端給我們的反饋是PatientID為123456的患者資訊,所返回的資訊都是wlistqry.wl檔案中要求的欄位,其中通過-k 0010,0020=“123456”限定項來限定了查詢的結果,在服務端的反饋是兩個患者中表明有一個匹配的worklist資料庫檔案,如中黃色地區所示。
猜想:既然我們可以利用dcmtk自由產生用戶端的.wl查詢檔案,而-k 0010,0020=”123456”就是對該查詢檔案的補充,那麼是不是如果我們直接把123456寫入到wlistqry.wl中的(0010,0020)欄位的值域,而直接利用修改後的查詢檔案也會得到相同的結果呢?此處利用自己的代碼將0010,0020欄位的值域填充為123456
#include <stdio.h>#include <tchar.h>#include "dcmtk/config/osconfig.h"#include "dcmtk/dcmdata/dctk.h"#include "dcmtk/dcmdata/dcpxitem.h"#include "dcmtk/dcmjpeg/djdecode.h"#include "dcmtk/dcmjpeg/djencode.h"#include "dcmtk/dcmjpeg/djcodece.h"#include "dcmtk/dcmjpeg/djrplol.h"using namespace std;int main(){char uid[100];DcmFileFormat fileformat;DcmDataset *dataset = fileformat.getDataset();/***********************************************【猜測一】:*利用下列語句可以產生worklist的查詢檔案*即,*各個欄位資料都為空白的dcm檔案************************************************/dataset->putAndInsertString(DCM_SOPClassUID, UID_SecondaryCaptureImageStorage);dataset->putAndInsertString(DCM_SOPInstanceUID, dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT));dataset->putAndInsertString(DCM_ImplementationVersionName,"OFFIS_DCMTK_361");dataset->putAndInsertString(DCM_SpecificCharacterSet,"");dataset->putAndInsertString(DCM_PatientName, "");dataset->putAndInsertString(DCM_PatientID,"123456");dataset->putAndInsertString(DCM_PatientBirthDate,"");dataset->putAndInsertString(DCM_PatientSex,"");OFCondition status = fileformat.saveFile("D:\\DcmWorklist\\worklist\\testqry.wl", EXS_LittleEndianExplicit);if (status.bad())cerr << "Error: cannot write DICOM file (" << status.text() << ")" << endl;return 0;}
然後利用
>findscu 127.0.0.1 104 -v -aec OFFIS testqry.wl 指令直接發起查詢。
查詢結果反饋如所示:
證明了我們的猜想,通過寫入wlistqry.wl的相關欄位的值域,就等同於在findscu.exe指令中添加-k XXXX,XXXX限定選項。
至此我們詳細的介紹了如何類比worklist的雙端服務,如何開啟服務端服務、發起用戶端查詢,關鍵是對如何利用dcmtk的庫函數來產生自訂的查詢端.wl檔案進行了補充設執行個體測試。
(完)
[email protected]
時間:2014-08-23
DICOM醫學影像處理:基於DCMTK工具包學習和分析worklist