DICOM医学图像处理:基于DCMTK工具包学习和分析worklist
背景:
? ? ? ? DICOM3.0協(xié)議中有介紹關(guān)于worklist的部分。簡而言之,worklist可以看做是放射科設(shè)備從醫(yī)院RIS系統(tǒng)中自動(dòng)讀取患者信息的一種“通信協(xié)議”,可以指存儲(chǔ)在RIS系統(tǒng)中的患者數(shù)據(jù)庫,主要包括患者的基本信息(如年齡、性別、身高、體重、出生年月等),這與DCM文件信息頭MetaInfo中的多數(shù)字段重合。因此從RIS系統(tǒng)中自動(dòng)獲取worklist是醫(yī)院信息化的必要組成部分。下面簡單的給出幾個(gè)圖像,形象的描述一下worklist的作用。
worklist的實(shí)例學(xué)習(xí):
? ? ? ? 在簡單的了解了worklist的作用后,下面我們利用DCMTK提供的工具包(wlmscpfs.exe和findscu.exe)來真實(shí)模擬一下該場(chǎng)景,從而更深刻的學(xué)習(xí)worklist的功能。
worklist簡單的看做一種“通訊”,那么自然就存在著通訊的兩端,暫且稱作“服務(wù)端”和“客戶端”。這里我們用wlmscpfs.exe來作為worklist通訊的服務(wù)端,即等待外部訪問的終端;用findscu.exe來作為服務(wù)端,用來發(fā)起worklist訪問。
首先簡單的介紹一下工具包的指令及使用方式。wlmscpfs.exe是類似于DOS時(shí)代的命令,通過設(shè)定參數(shù)可以實(shí)現(xiàn)不同的目的。利用Win+R鍵開啟操作系統(tǒng)的運(yùn)行窗口,輸入cmd后進(jìn)入到命令提示行窗口。然后進(jìn)入到DCMTK編譯后的bin文件夾(我本機(jī)地址是C:\Program Files (x86)\DCMTK\bin)目錄,此時(shí)直接輸入wlmscpfs.exe就可以看到關(guān)于該命令工具的各種說明。
>cd C:\Program Files (x86)\DCMTK\bin
>wlmscpfs.exe
(注,此處如果為了省事,可以將bin文件夾路徑添加到windows的環(huán)境變量中,如是就可以在任何目錄下使用bin下的各種工具了)
? ? ? ? 由上圖看到,wlmscpfs.exe工具至少需要給出port一個(gè)參數(shù),即開啟worklist服務(wù)的本機(jī)端口號(hào)。另外還需要輸入worklist數(shù)據(jù)庫文件的地址,用來供客戶端查詢、訪問使用。下面我們正式開啟worklist的服務(wù)端程序,至于開啟全過程,可以跟大家推薦CSDN一位博主的精品文章(http://blog.csdn.net/pachleng/article/details/5800513),博文中給出了具體的操作步驟,這個(gè)實(shí)例是對(duì)DCMTK論壇中的補(bǔ)充和更新。大家可以動(dòng)手試一下。
第一步:建立各級(jí)目錄
? ? ? ? 以我的電腦為例,我在D盤創(chuàng)建了DCMWorklist文件夾,然后建立了兩個(gè)子文件夾wlistdb和wlistqry。
第二步:準(zhǔn)備worklist數(shù)據(jù)庫文件,開啟worklist服務(wù)端服務(wù)。
? ? ? ? 然后將worklist的數(shù)據(jù)庫文件拷貝到wlistdb目錄下,此處參見冷哥博文,記得建立OFFIS子目錄。至于worklist數(shù)據(jù)庫文件通常在dcmtk庫的源碼中已經(jīng)給出了,默認(rèn)目錄是dcmtk-3.6.0\dcmtk-3.6.0\dcmwlm\datawlistdb(我用的是3.6.0版本)。但是源碼中的文件通常是.dump擴(kuò)展名的文件,也就是我們常見的文本文件(用記事本或者Notepad++等工具雙擊即可打開)。通過利用dcmtk工具包中的dump2dcm.exe可以將.dump文件轉(zhuǎn)換成.wl文件,轉(zhuǎn)換后的.wl文件就是worklist數(shù)據(jù)庫文件。
>dcmp2dcm.exe .\dcmtk-3.6.0\dcmtk-3.6.0\dcmwlm\data\wklistdb\wlist1.dump d:\DCMWorklist\wlistdb\OFFIS\wlist.wl
>……
? ? ? ? 有了worklist數(shù)據(jù)庫文件后,我們就可以開啟worklist服務(wù)了,利用的工具就是前文提到的wlmscpfs.exe(從工具名稱中的SCP就可以看出這應(yīng)該是服務(wù)端開啟服務(wù)的)。
>wlmscpfs.exe –d –dfr –dfp d:\DCMWorklist\wlistdb 104???? (注:其中的-d是為了方便我們觀察工具運(yùn)行過程而開啟的調(diào)試開關(guān))
>……
第三步,準(zhǔn)備查詢文件,開啟worklist查詢。
? ? ? ? 服務(wù)端已經(jīng)準(zhǔn)備就緒,下面就是該發(fā)起worklist查詢服務(wù)啦。利用的工具是findscu.exe(從工具名稱中的SCU同樣可以猜測(cè)出這是客戶端)。dcmtk源碼包中同樣給我們提供了查詢worklist的查詢文件,默認(rèn)目錄是dcmtk-3.6.0\dcmtk-3.6.0\dcmwlm\data\wlistqry,打開后可以看到里面以.dump格式存在的文件,再次利用dump2dcm.exe將.dump轉(zhuǎn)換為.wl文件。
>dcmp2dcm.exe .\dcmtk-3.6.0\dcmtk-3.6.0\dcmwlm\data\wklistqrt\wlistqry1.dump d:\DCMWorklist\wlistqry\wlistqry.wl
? ? ? ? 然后我們利用findscu發(fā)起查詢請(qǐng)求,
>findscu.exe –d 127.0.0.1 104 d:\DCMWorklist\wlistqry\wlistqry.wl –aec OFFIS
? ? ? ? 其中aec代表的是被請(qǐng)求或者說被呼叫的應(yīng)用端的名稱,即我們wlistdb文件夾內(nèi)的OFFIS子文件夾。
通過以上三步,我們就簡單的利用dcmtk提供的wlmscpf.exe和findscu.exe工具包以及相應(yīng)的wklistdb數(shù)據(jù)庫文件和wklistqry數(shù)據(jù)庫查詢文件模擬了worklist服務(wù)開啟及查詢的整個(gè)流程。是不是很簡單,很容易上手。
實(shí)際結(jié)果分析:
? ? ? ? 上一部分中提到了在使用wlmscpfs.exe和findscu.exe工具包的時(shí)候開啟了-d調(diào)試模式,目的就是為了方便我們跟蹤整個(gè)通訊的流程。另外為了方便查看,我們利用重定向技術(shù),將wlmscpfs.exe和findscu.exe工具包的調(diào)試信息輸出到txt文件,方便我們事后進(jìn)行再次對(duì)比查看。下面將兩個(gè)工具包的調(diào)試信息用Notopad++打開,對(duì)比分析一下,見下圖:
? ? ? ? 從調(diào)試信息可以清晰的看到wlmscpfs.exe與findscu.exe之間的通信流程,該流程在DICOM3.0標(biāo)準(zhǔn)的第四部分(Service Class Specifications )和第八部分(Network Communication Support for Message Exchange)都有詳細(xì)的介紹,上述的重定向生成的文本文檔就是學(xué)習(xí)DICOM3.0第四、八部分最好的實(shí)例。因?yàn)閐cmtk是開源的,所以這方便我們分析wlmscpfs.exe和findscu.exe兩個(gè)工具包的源碼。具體分析見下一節(jié)。
wlmscpfs.exe和findscu.exe工具包源碼分析:
| ? | wlmscpfs.exe | findscu.exe |
| C/S | worklist服務(wù)端 | worklist客戶端 |
| 源碼文件 | wlmscpfs.cc wlcefs.cc wlmactmg.cc | findscu.cc |
| 內(nèi)部函數(shù) | 1)ConnectToDataSource();//開啟連接 2)WlmActivityManager(); //函數(shù)內(nèi)部利用WSAStartup()啟動(dòng)了Windows套接字服務(wù) 3)StartProvidingService(); //啟動(dòng)worklist管理服務(wù),位于wlmactmg.cc文件。函數(shù)內(nèi)部調(diào)用(a)(b)兩個(gè)函數(shù)。 (a)ASC_initializeNetwork()函數(shù) ASC_initializeNetwork函數(shù)調(diào)用了DICOM協(xié)議封裝的TCP協(xié)議函數(shù)DUL_InitializeNetwork(該函數(shù)內(nèi)部就會(huì)出現(xiàn)我們?cè)谔捉幼志幊讨谐R姷膕ocket、setsockopt、bind和listen函數(shù)) (b)WaitForAssociation();函數(shù) WaitForAssociation函數(shù)調(diào)用了DICOM協(xié)議封裝的TCP/IP協(xié)議函數(shù)receiveTransportConnectionTCP(該函數(shù)內(nèi)部利用的是select端口模式,會(huì)出現(xiàn)套接字編程中常見的select、accept函數(shù)) 4)disconnectfromDataSource(); //斷開連接的函數(shù)。 | 1)WSAStartup();//初始化套接字服務(wù) 2)DcmFindSCU::initializeNetWork(); //函數(shù)內(nèi)部調(diào)用的也是ASC_initializeNetwork函數(shù)。 3)DcmFindSCU::performQuery(); //同樣該函數(shù)內(nèi)部封裝了很多以DUL開頭的協(xié)議操作函數(shù)。DUL是DICOM? Upper Layer 的縮寫。 4)WSACleanup(); |
? ? ? ? 從上述分析中我們基本可以看出,worklist的通訊是建立在TCP/IP這一現(xiàn)有協(xié)議之上的,可以說是對(duì)協(xié)議的二次封裝。與我們平時(shí)進(jìn)行套接字編程的基本流程相似,搞清楚了這一點(diǎn),對(duì)于分析工具包、學(xué)習(xí)工具包的使用都會(huì)有很大的幫助。
worklist數(shù)據(jù)庫文件或查詢文件(*.wl)的生成:
1)問題提出:
? ? ? ? 參照冷哥博文(http://blog.csdn.net/pachleng/article/details/5800513、http://blog.csdn.net/pachleng/article/details/5827232)中的說明,我們可以很容易的利用DCMTK的工具包學(xué)習(xí)worklist操作。但是在實(shí)際應(yīng)用模仿過程中,有的人可能會(huì)好奇為什么要把wklist.wl和wklistqry.wl文件分別放在不同目錄?為什么同為以.wl為擴(kuò)展名的文件,一個(gè)就可以作為worklist的數(shù)據(jù)庫文件放在服務(wù)端,而另一個(gè)就是客戶單的查詢文件?如果想發(fā)起自己的查詢,即C-FIND請(qǐng)求,我們怎么手動(dòng)生成wklistqry.wl文件?
? ? ? ? 針對(duì)上述問題,在dcmtk的論壇中也曾經(jīng)有人提到過,參見(http://forum.dcmtk.org/viewtopic.php?f=1&t=1475&hilit=wlmscpfs.exe%23p5016)。利用UltraEdit工具將wklist.wl和wklistqry.wl文件同時(shí)打開,對(duì)比如下:
? ? ? ? 從上圖中可以看出,作為worklist客戶端數(shù)據(jù)庫文件的wklist.wl中是將患者真實(shí)信息以符合DICOM3.0標(biāo)準(zhǔn)字段的形式存儲(chǔ),而客戶端發(fā)起查詢請(qǐng)求的wklistqry.wl文件內(nèi)部只是簡單的需要查詢的空字段,即各個(gè)字段的值域都為空。參考博文中給出了利用dump2dcm.exe工具包將.dump文件轉(zhuǎn)換成wklistqry.wl文件(dump可以認(rèn)為是普通的文本文件,可以利用記事本等工具進(jìn)行直接編輯)。如下圖所示:
? ? ? ? ?例如作為測(cè)試用的wlist-2.dump文件中的患者ID為123456,生成后的wlist-2.wl文件中的(0010,0020)字段也是123456。
2)實(shí)際測(cè)試:
? ? ? ? 從dump2dcm.exe工具包的說明我們就可以知道,.wl文件其實(shí)就是dcm文件,只是該類文件中并不存在真實(shí)的像素信息。通常只包含信息頭部分,主要指的是患者的各項(xiàng)信息。因此想利用dcmtk的庫函數(shù)直接獲取.wl文件,其實(shí)就是手動(dòng)構(gòu)造dcm文件的過程。參見http://support.dcmtk.org/docs/mod_dcmdata.html#Examples中的第二個(gè)例子,我們可以手動(dòng)生成以“.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的數(shù)據(jù)庫文件,即*不含有影像信息的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; }? ? ? ? 既然服務(wù)端需要的worklist數(shù)據(jù)庫文件和客戶端需要的查詢文件都是.wl文件,唯一的區(qū)別就是一個(gè)有值域,一個(gè)沒有。因此利用上述代碼我們既可以生成worklist數(shù)據(jù)庫文件,也可以生成worklist查詢文件。
3)測(cè)試結(jié)果:
? ? ? ? 查看findscu.exe工具包,我們可以找到-k選項(xiàng),也就是在提供了查詢文件wklistqry.wl的同時(shí)也可以指定限定的查詢字段,例如
>findscu 127.0.0.1 104 -v -k 0010,0020="123456" -aec OFFIS wlistqry.wl
? ? ? ? 如果不添加-k 0010,0020=“123456”限定選項(xiàng),查詢結(jié)果如前文中重定向的結(jié)果相同,而添加了限定字段后,我們能夠查詢到的就只有數(shù)據(jù)庫端中滿足PatientID字段為123456的患者數(shù)據(jù)。具體結(jié)果如下:
? ? ? ? 從中我們可以看到服務(wù)端給我們的反饋是PatientID為123456的患者信息,所返回的信息都是wlistqry.wl文件中要求的字段,其中通過-k 0010,0020=“123456”限定項(xiàng)來限定了查詢的結(jié)果,在服務(wù)端的反饋是兩個(gè)患者中表明有一個(gè)匹配的worklist數(shù)據(jù)庫文件,如上圖中黃色區(qū)域所示。
? ? ?猜想:既然我們可以利用dcmtk自由生成客戶端的.wl查詢文件,而-k 0010,0020=”123456”就是對(duì)該查詢文件的補(bǔ)充,那么是不是如果我們直接把123456寫入到wlistqry.wl中的(0010,0020)字段的值域,而直接利用修改后的查詢文件也會(huì)得到相同的結(jié)果呢?此處利用自己的代碼將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();/***********************************************【猜測(cè)一】:*利用下列語句可以生成worklist的查詢文件*即,* 各個(gè)字段數(shù)據(jù)都為空的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 指令直接發(fā)起查詢。
? ? ? ? 查詢結(jié)果反饋如下圖所示:
? ? ? ? 上圖證明了我們的猜想,通過寫入wlistqry.wl的相關(guān)字段的值域,就等同于在findscu.exe指令中添加-k XXXX,XXXX限定選項(xiàng)。
? ? ? ? 至此我們?cè)敿?xì)的介紹了如何模擬worklist的雙端服務(wù),如何開啟服務(wù)端服務(wù)、發(fā)起客戶端查詢,關(guān)鍵是對(duì)如何利用dcmtk的庫函數(shù)來生成自定義的查詢端.wl文件進(jìn)行了補(bǔ)充設(shè)實(shí)例測(cè)試。
(完)
?
作者:zssure@163.com
時(shí)間:2014-08-23
總結(jié)
以上是生活随笔為你收集整理的DICOM医学图像处理:基于DCMTK工具包学习和分析worklist的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab拟合四次函数表达式,用mat
- 下一篇: matlab光顺拐点,基于MATLAB的