標籤:dicom dcmtk c-find scu worklist
背景:
專欄之前寫過許多關於DICOM協議的相關文章,有關於概念解析的理論性文章,也有執行個體示範的應用性文章。目的只有一個,希望能引導大家快速掌握DICOM協議,並著手進行自訂化開發。
目前DICOM協議實現有多種開源庫,例如基於C++的DCMTK、基於C#的fo-dicom、基於Java的dcm4che。由於時間關係博文中的相關執行個體示範經常會穿插著使用三種開源庫,因此具體到某一種庫可能博文中並未給出示範工程。例如,近期有網友諮詢希望利用DCMTK開源庫自己動手實現C-FIND查詢請求,並對服務端返回的資訊進行定製化處理。因此周末動手編寫了一個極簡版的樣本,代碼裁剪於DCMTK開源庫的findscu工程,供大家交流學習。
準備知識:
為了更好的理解程式碼範例,請耐心閱讀之前專欄裡的相關文章,如果已經對DICOM協議很瞭解且有過開發經驗,或者乾脆就想先動手敲代碼,想從實踐中學習,那麼請自行跳到下一節。
在開始工作之前先閱讀DICOM醫學影像處理:DICOM網路傳輸瞭解DICOM協議的含義以及簡單的建立規則,隨後閱讀DICOM醫學影像處理:全面解析DICOM3.0標準中的通訊服務模組和DICOM:DICOM3.0網路通訊協定(續)進一步瞭解DICOM協議,以及熟悉DCMTK開源庫中對DICOM協議的具體實現。閱讀完上述理論概念性文章後,進一步瀏覽下面兩篇執行個體示範博文DICOM醫學影像處理:基於DCMTK工具包學習和分析worklist、DICOM醫學影像處理:利用fo-dicom發送C-Find查詢Worklist 。
DCMTK實現C-FIND SCU:
待一切先驗知識儲備完成後,就可以進入我們的正題了,網友的需求是:
在閱讀DICOM醫學影像處理:基於DCMTK工具包學習和分析worklist、DICOM醫學影像處理:利用fo-dicom發送C-Find查詢Worklist 兩篇執行個體博文後,希望利用DCMTK嘗試發送C-FIND-RQ請求,然後將返回的C-FIND-RSP訊息進行解析和後處理。
經過上述【準備知識】階段後,想必大家已經瞭解了C-FIND請求建立的正題過程,因此不羅嗦了直接貼代碼,用一個簡單的執行個體來進行實際講解。
a)網路環境初始化
//1)初始化網路環境 WSAData winSockData; /* we need at least version 1.1 */ WORD winSockVersionNeeded = MAKEWORD( 1, 1 ); WSAStartup(winSockVersionNeeded, &winSockData);
b)DCMTK庫初始化
//2)DCMTK環境監測 if(!dcmDataDict.isDictionaryLoaded()) { printf("No data dictionary loaded, check environment variable\n"); }
c)建立DUL串連
//3)網路層ASC初始化 T_ASC_Network* cfindNetwork=NULL; int timeout=50; OFCondition cond=ASC_initializeNetwork(NET_REQUESTOR,0,timeout,&cfindNetwork); if(cond.bad()) { printf("DICOM 底層網路初始化失敗\n"); return -1; } //4)建立底層串連,即TCP層 T_ASC_Association* assoc=NULL; T_ASC_Parameters* params=NULL; DIC_NODENAME localHost; DIC_NODENAME peerHost; OFString temp_str; cond=ASC_createAssociationParameters(?ms,maxReceivePDULength); if(cond.bad()) { printf("DCMTK建立串連失敗\n"); return -2; }
d)判別串連
//5)設定DICOM相關屬性,Presentation Context ASC_setAPTitles(params, ourTitle, peerTitle, NULL); cond = ASC_setTransportLayerType(params, false); if (cond.bad()) return -3; gethostname(localHost, sizeof(localHost) - 1); sprintf(peerHost, "%s:%d", peer, OFstatic_cast(int, port)); ASC_setPresentationAddresses(params, localHost, peerHost); cond=ASC_addPresentationContext(params,1,abstractSyntax,transferSyntaxs,transferSyntaxNum); if(cond.bad()) return -4; //6)真正建立串連 cond=ASC_requestAssociation(cfindNetwork,params,&assoc); if (cond.bad()) { if (cond == DUL_ASSOCIATIONREJECTED) { T_ASC_RejectParameters rej; ASC_getRejectParameters(params, &rej); DCMNET_ERROR("Association Rejected:" << OFendl << ASC_printRejectParameters(temp_str, &rej)); return -5; } else { DCMNET_ERROR("Association Request Failed: " << DimseCondition::dump(temp_str, cond)); return -6; } } //7)判別返回結果 //7.1)串連檢驗階段,驗證Presentation Context if(ASC_countAcceptedPresentationContexts(params)==0) { printf("No acceptable Presentation Contexts\n"); return -7; } T_ASC_PresentationContextID presID; T_DIMSE_C_FindRQ req; T_DIMSE_C_FindRSP rsp; DcmFileFormat dcmff; presID=ASC_findAcceptedPresentationContextID(assoc,abstractSyntax); if(presID==0) { printf("No presentation context\n"); return -8; }
e)發送C-FIND-RQ
//8)發起C-FIND請求 //8.1)準備C-FIND-RQ message bzero(OFreinterpret_cast(char*,&req),sizeof(req));//記憶體初始化為空白; strcpy(req.AffectedSOPClassUID,abstractSyntax); req.DataSetType=DIMSE_DATASET_PRESENT; req.Priority=DIMSE_PRIORITY_LOW; //設定要查詢的資訊為空白時,待會兒查詢結果中會返回 DcmDataset* dataset=new DcmDataset(); InsertQueryItems(dataset,"A^B^C"); //賦值自訂的回呼函數,這就是該回呼函數中可以進行相關資訊的操作 ZSCFindCallback zsCallback; DcmFindSCUCallback* callback=&zsCallback; callback->setAssociation(assoc); callback->setPresentationContextID(presID); /* as long as no error occured and the counter does not equal 0 */ cond = EC_Normal;
f)設定回呼函數,進行自訂處理
class ZSCFindCallback:public DcmFindSCUCallback{public: ZSCFindCallback() { } ~ZSCFindCallback() { } void callback( T_DIMSE_C_FindRQ *request, int responseCount, T_DIMSE_C_FindRSP *rsp, DcmDataset *rspMessage );};
g)擷取C-FIND-RSP
while (cond.good()) { DcmDataset *statusDetail = NULL; /* complete preparation of C-FIND-RQ message */ req.MessageID = assoc->nextMsgID++; /* finally conduct transmission of data */ cond = DIMSE_findUser(assoc, presID, &req, dataset, progressCallback, callback, DIMSE_BLOCKING, timeout, &rsp, &statusDetail); //設定了查詢採用阻塞模式,DIMSE_BLOCKING //設定連線逾時為50 /* *添加異常判別 * */ cond=EC_EndOfStream;//假設異常,返回 }
執行個體測試:
參照之前DICOM醫學影像處理:基於DCMTK工具包學習和分析worklist、DICOM醫學影像處理:利用fo-dicom發送C-Find查詢Worklist博文中的介紹進行測試即可,具體細節不多說,簡單介紹一下:
啟動Worklist SCP:
按照之前博文介紹,構建worklist資料庫,在命令列輸入:
wlmscpfs.exe -d -dfr -dfp ./wlistdb 2234
看到以下結果,說明順利啟動worklist服務端。
啟動C-Find SCP:
在VS環境下,直接運行dumpCFindResponse工程,看到如下結果:
至此,自己構建的C-Find SCU順利實現了發送C-FIND-RQ,並自訂處理C-FIND-RSP的目的。
PS:這裡只是簡單的給出了一個示範,要實際開發自己的C-FIND SCU和C-FIND SCP時需要考慮更多的細節,諸如PresentationContext、TransferSyntax等等。
樣本工程下載:
【GitHub:】 dumpCFindResponse
【Baidu:】 dumpCFindResponse
【CSDN:】 dumpCFindResponse資源下載
[email protected]
時間:2015-03-28
DICOM:基於DCMTK實現C-FIND SCU