DCMTK開源庫的學習筆記4:利用ini配置文件對dcm影像進行歸檔,dcmtkdcm
轉:http://blog.csdn.net/zssureqh/article/details/8846337
背景介紹:
醫學影像PACS工作站的服務端需要對大量的dcm文件進行歸檔,寫入數據庫處理。由於醫學圖像的特殊性,每一個患者(即所謂的Patient)每做一次檢查(即Study)都至少會產生一組圖像序列(即Series),而每一組圖像序列下會包含大量的dcm文件(例如做一次心髒CTA的診斷,完整的一個心髒斷層掃描序列大約有200幅圖像)。DICOM3.0協議中對每一幅影像是按照特定的三個UID(唯一標示符)來進行標記的,分別是StudyInstanceUID、SeriesInstanceUID、SOPInstanceUID。其中StudyInstanceUID代表了唯一的一次檢查(Study),SeriesInstanceUID代表了相應檢查下的唯一序列(Series)、SOPInstanceUID代表了唯一檢查下的唯一序列下的唯一圖像。通常PACS工作站都是利用這三個UID來對dcm文件進行歸檔處理。
歸檔的設計:
1、基本的歸檔結構是:
第一級:StudyInstanceUID
存儲同一患者的影像數據
第二級:SeriesInstanceUID
存儲同一次檢查下的影像數據
第三級:SOPInstanceUID
存儲同一個序列下的影像數據
了解了大致的歸檔結構後,現在應該考慮怎樣將dcm記錄寫入到數據庫中?最直觀的想法就是將每一個dcm文件都記錄在數據庫中,這樣當需要讀取指定的dcm文件時通過給定的三個UID直接在數據庫中查詢就能夠得到。但是如此一來,數據庫的容量會急劇增加,同一患者在數據庫中存在著大量的冗余記錄。因為患者的影像數據是按照上述的三級目錄來歸檔的,大量的相關的影像數據存儲在服務器的同一個目錄下,對於同一個序列的圖像可以直接在第二級目錄中利用SOPInstanceUID來進行檢索,而不需要進行數據庫的查詢。但是當二級目錄下的文件數量較大時,檢索文件件中的文件同樣需要耗費大量的時間,那麼怎樣可以提高檢索效率呢?答案就是:配置文件。由於在歸檔的時候我們已經讀取過每個dcm文件的三個UID,那麼可以將歸檔時候讀取的UID信息寫入到相應的INI配置文件中,並存儲到相應的圖像序列目錄下。那麼在檢索圖像時,通過前兩級UID可以快速在數據庫中查詢到影像數據的歸檔目錄,當進入到指定的歸檔目錄後,利用歸檔時生成的INI文件,可以快速的檢索到指定的dcm文件,另外如果歸檔時將一些常用的dcm文件信息一同寫入到INI配置文件中(如圖像的寬度、高度、患者姓名、出生年月、窗寬/窗位等),在後續的一些圖像處理中同樣能夠節約時間,提高效率。
2、INI配置文件的生成
INI配置文件的格式就不細講了,CSDN中已有很多詳細講解的博文,請大家自行參閱。這次詳細講解一下利用dcmtk開源庫來提取相應的dcm文件信息並寫入到ini配置文件中的方法。
DCMTK開源庫是一個很好的醫學影像開發基礎庫,其很好的實現了DICOM3.0標准,且類的繼承體系簡單明了,與DICOM3.0標准一一對應(隨後會寫關於“dcmtk開源庫的繼承體系與DICOM3.0標准的對應關系”的博文)。這裡我們只用到了dcmtk中的DcmItem類,該類派生自DcmObject基礎類,其含有ElementList成員變量,存儲了DICOM3.0標准中規定的一系列的數據元(Data Element)基本結構如下圖所示:
注:DcmItem類就是Dataset(數據集)的子類。其內部包含了數據元序列(即ElementList數據成員)。
通過閱讀關於DcmItem類的源碼,總結歸納了以下幾種dcmtk開源庫給出的操作dcm文件相應數據元的函數:findAndGet 函數、findOrCreate函數、findAndXXX函數、putAndInsert函數,以及insertXXX函數,如下圖:
至此我們可以利用findAndGet函數類來提取dcm文件中的相關信息,結合WindowsAPI函數來進行INI配置文件的歸檔。由於INI配置文件就是文本文件,因此我們選用了DcmItem中的findAndGetString函數來提取dcm文件中的數據元,利用findAndGetString函數能夠直接得到字符串格式(const char*)的數據元,另外,結合WritePrivateProfileString函數來生成INI配置文件。(注:此處findAndGetString函數正好與WritePrivateProfileString函數的格式匹配,如果采用findAndGet的其他函數,如findAndGetSin32,就需要利用itoa等函數將整型轉換成const char*類型,增加了編程的復雜性)
下面給出部分代碼:
[cpp] view plaincopyprint?
- DcmTagKey THU_DCM_ELEMENTS[]=
- {DCM_InstanceNubmber, DCM_Rows,DCM_Columns,DCM_PatientName};//定義需要寫入到ini文件中的dcm數據元標簽數組
- int num=sizeof(THU_DCM_ELEMNTS)/sizeof(DcmTagKey);
- OFString mImageValue;
- OFString mGap("|");//INI配置文件中各個數據元之間的間隔符
- OFString mImageModule("ImageModule\\");//配置文件的節名稱
- OFString mSOPInstanceUID;
- OFString mSeriesInstanceUID;
- DcmDataset *pDataset=mDcmFile->getDataset();
- DcmMetaInfo *pMetaInfo=mDcmFile->getMetaInfo();
- pDataset->findAndGetOFString(DCM_SeriesInstanceUID,mSeriesInstanceUID);
- pDataset->findAndGetOFString(DCM_SOPInstanceUID,mSOPInstanceUID);
- mImageModule+=mSeriesInstanceUID;
- for(int i=0;i<num;++i)
- {
- OFString mValueRecord;
- DcmElement *element;
- if(THU_DCM_ELEMNTS[i].getGroup()>0x0002)// to determine if the THU_DCM_ELEMENTS[i] is MetaInfo
- {
- //the element belongs to Dataset
- pDataset->findAndGetOFStringArray(THU_DCM_ELEMNTS[i],mValueRecord);
- mValueRecord+=mGap;
- }
- else
- {
- //the element belongs to MetaInfo
- if(THU_DCM_ELEMNTS[i].getGroup()==0x0000 && THU_DCM_ELEMNTS[i].getElement()==0x0000)
- {
- mValueRecord=mGap;
- }
- else
- {
- pMetaInfo->findAndGetOFStringArray(THU_DCM_ELEMNTS[i],mValueRecord);
- mValueRecord+=mGap;
- }
-
- }
- mImageValue+=mValueRecord;
- }
- ::WritePrivateProfileString(mImageModule.c_str(),mSOPInstanceUID.c_str(),mImageValue.c_str(),iniFileName);
3、數據庫的寫入:
數據庫寫入的方式與INI配置文件生成基本相似,只要稍微了解C++數據庫編程的人員,就很容易仿照上述INI配置文件的生成過程來完成數據庫寫入的部分,此處就不細講了,只給出簡單的部分代碼:
[cpp] view plaincopyprint?
- DcmFileFormat fileformat;
- TCHAR FilePath[MAX_PATH];
- OFCondition oc = fileformat.loadFile(FilePath);
- DcmDataset *pDataset=fileformat.getDataset();
- char query[1000];
- memset(query,0,sizeof(char)*1000);
- lstrcat(query,_T("insert into patient values ("));
-
- const char *tString;
- pDataset->findAndGetString(DCM_InstanceNumber,tString);
- lstrcat(query,tString);
- lstrcat(query,_T(","));
- pDataset->findAndGetString(DCM_PatientName,tString);
- lstrcat(query,_T("\""));
- lstrcat(query,tString);
- lstrcat(query,_T("\""));
- lstrcat(query,_T(","));
- pDataset->findAndGetString(DCM_PatientID,tString);
- lstrcat(query,tString);
- lstrcat(query,_T(","));
- pDataset->findAndGetString(DCM_PatientBirthDate,tString);
- lstrcat(query,tString);
- lstrcat(query,_T(","));
- pDataset->findAndGetString(DCM_PatientSex,tString);
- lstrcat(query,"\"");
- lstrcat(query,tString);
- lstrcat(query,"\"");
- lstrcat(query,",");
- pDataset->findAndGetString(DCM_PatientAge,tString);
- char temp[100];
- memcpy(temp,tString,lstrlen(tString));
- temp[lstrlen(tString)]=_T('\0');
- for(int i=0;i<strlen(temp);++i)
- if(temp[i]==_T('Y'))
- temp[i]=_T('\0');
- lstrcat(query,temp);
- lstrcat(query,",");
- lstrcat(query,_T("2013)"));
- n > </span>//mysql數據庫的寫入
- MYSQL* con;
- con=mysql_init((MYSQL*)0);
- if(con!=NULL && mysql_real_connect(con,host,user,passwd,db,port,unix_socket,client_flag))
- {
- if(!mysql_select_db(con,db))
- {
- ::printf("Selcet successfully the database!\n");
-
- con->reconnect=1;
- int rt=mysql_real_query(mysql,query,strlen(query));
- if(rt)
- {
- ::printf("Error making insert!!!\n");
- }
-
- }
-
- }
對於MYSQL的C++操作,可以參見博文:http://www.cnblogs.com/justinzhang/archive/2011/09/23/2185963.html