背景介绍:
医学影像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 |
存储同一个序列下的影像数据 |
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*类型,增加了编程的复杂性)
下面给出部分代码:
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);
数据库写入的方式与INI配置文件生成基本相似,只要稍微了解C++数据库编程的人员,就很容易仿照上述INI配置文件的生成过程来完成数据库写入的部分,此处就不细讲了,只给出简单的部分代码:
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)"));
//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