DICOM是由美国放射学院(ACR)和美国国家电气制造商协会(NEMA)开发的标准。在全球范围内用于存储,交换和传输医学图像。DICOM在现代放射成像的发展中一直处于核心地位:DICOM结合了诸如放射成像,超声检查,计算机断层扫描(CT),磁共振成像(MRI)和放射治疗等成像方式的标准。DICOM包括用于图像交换(例如,通过诸如DVD之类的便携式介质),图像压缩,3-D可视化,图像表示和结果报告的协议。更多信息可去维基百科查看。地址,
dcm图像类似于这样
首先说明下这是一个没有运行成功的demo,目前关于dcm查了国内外的网站,在androID上解析的文章太少。dicom有三个比较有名的开源框架(dcmtk、dcm4che、fo-dicom),参考博客,我自己试过两个框架dcm4che,dcmtk,但都没有成功。dcm4che比较简单,有jar包,也有demo,链接,但我运行文件没有成功。本文重点说说另一个开源框架,三个开源框架中评分最高的dcmtk。代码是c编写开源的,由于没有androID的参考代码,我参照windows平台参考代码的链接,ios平台的demo参考demo链接1参考demo2分别编写了编写了一次。但运行都只是读出了文件部分信息,读取图片信息没有成功。
由于dcmtk是开源,并且没有编译为linux可用的so,所以需要自己编译下,windows系统可用子系统 ubuntu(WSL)编译,参考博客。特别注意因为ndk编译时代码有用模拟器验证,如果ubuntu没有配置sdk,可将CMake/dcmtkUseandroidSDK.cmake文件中部分有关于模拟器的代码执行return(),还有return()需要大概注意下位置。我在编译时有过在function(DCMTK_SETUP_ANDROID_EMulATOR)中return()在if(NOT ANDROID_TEMPORARY_fileS_LOCATION)这个判断前导致报没有临时输出文件错误,如图
对应于那些地方需要用到return(),可参照错误日志类似于下图的地方需要模拟器就写上return()
这是针对于编译,编译后需要运行,由于AndroID的imagevIEw一般可展示的就是png,jpg,bitmap,我参考上述博客ios的demo,决定将文件读为int数组再去转bitmap在androID上显示,其实参考发现这种输出有点类似于opencv输出构造bitmap显示。具体代码参照windows代码如下,c++模式的jni
extern "C" JNIEXPORT jobjectJNICALL Java_com_lsp_myapplication_MainActivity_getIntFromDcm(jnienv *env, Jstring inputPath_) { // __androID_log_print(ANDROID_LOG_ERROR, "tag", "inputPath_:%s",inputPath_); const char *input_pa_name= "/storage/emulated/0/Download/aa.dcm"; //const char *input_pa_name= env->GetStringUTFChars(inputPath_, 0); //chardata= inputPath_ ? env->GetStringUTFChars(inputPath_, NulL) : NulL; //env->ReleaseStringUTFChars(filename, chardata); //const char *chardata = env->GetStringUTFChars(filename, JNI_FALSE); OFfilename *file=new OFfilename(input_pa_name,false); DcmfileFormat *dcmfileFormat = new DcmfileFormat(); OFCondition status = dcmfileFormat->loadfile(*file); if (status.good()) { __androID_log_print(ANDROID_LOG_ERROR, "tag", "6啦"); OFString patIEntname; DcmDataset *dcmDataset = dcmfileFormat->getDataset(); OFCondition condition = dcmDataset->findAndGetoFString(DCM_PatIEntname,patIEntname); if (condition.good()) { __androID_log_print(ANDROID_LOG_ERROR, "tag", "condition.good = %s", patIEntname.c_str()); //LOGE(patIEntname.c_str()); } else { __androID_log_print(ANDROID_LOG_ERROR, "tag", "condition. BAD"); //LOGE("condition. BAD"); } const char *transferSyntax; DcmMetaInfo *dcmMetaInfo = dcmfileFormat->getMetaInfo(); OFCondition transferSyntaxOfCondition = dcmMetaInfo->findAndGetString(DCM_TransferSyntaxUID, transferSyntax); __androID_log_print(ANDROID_LOG_ERROR,"tag", "transferSyntaxOfCondition %s", transferSyntaxOfCondition.text()); __androID_log_print(ANDROID_LOG_ERROR,"tag", "transferSyntax %s", transferSyntax); // 获得当前的窗宽 窗位 float64 windowCenter; dcmDataset->findAndGetfloat64(DCM_WindowCenter, windowCenter); __androID_log_print(ANDROID_LOG_ERROR,"tag", "windowCenter %f",windowCenter); float64 windowWIDth; dcmDataset->findAndGetfloat64(DCM_WindowWIDth, windowWIDth); __androID_log_print(ANDROID_LOG_ERROR,"tag", "windowWIDth %f",windowWIDth); E_TransferSyntax xfer = dcmDataset->getoriginalXfer(); __androID_log_print(ANDROID_LOG_ERROR,"tag", "E_TransferSyntax %d", xfer); const char * model; dcmDataset->findAndGetString(DCM_Modality, model); __androID_log_print(ANDROID_LOG_ERROR,"tag", "-------------Model: %s",model); std::string losslesstransUID = "1.2.840.10008.1.2.4.70"; std::string losstransUID = "1.2.840.10008.1.2.4.51"; std::string losslessp14 = "1.2.840.10008.1.2.4.57"; std::string lossyP1 = "1.2.840.10008.1.2.4.50"; std::string lossyRLE = "1.2.840.10008.1.2.5"; bool isYaSuo = 0; DicomImage *dcmImage = NulL; //todo if (transferSyntax == NulL){ isYaSuo = false;// 无压缩 }else { if (transferSyntax == losslesstransUID || transferSyntax == losstransUID || transferSyntax == losslessp14 || transferSyntax == lossyP1) { //对压缩的图像像素进行解压 DJDecoderRegistration::registerCodecs(); dcmDataset->chooseRepresentation(EXS_littleEndianExplicit, NulL); DJDecoderRegistration::cleanup(); isYaSuo = true;// 有压缩 } else if (transferSyntax == lossyRLE) { DcmRLEDecoderRegistration::registerCodecs(); dcmDataset->chooseRepresentation(EXS_littleEndianExplicit, NulL); DcmRLEDecoderRegistration::cleanup(); isYaSuo = true;// 有压缩 } else { isYaSuo = false;// 无压缩 } } long imageHeight = 0; long imageWIDth = 0; if (isYaSuo){ //dcmImage = new DicomImage(input_pa_name); dcmImage = new DicomImage((DcmObject*)dcmDataset, dcmDataset->getoriginalXfer(), CIF_TakeOverExternalDataset); if (dcmImage->getStatus() == EIS_normal) { imageWIDth = dcmImage->getWIDth(); imageHeight = dcmImage->getHeight(); if (windowWIDth < 1) { // 设置窗宽窗位 dcmImage->setRoiWindow(0, 0, imageWIDth, imageHeight); // 重新对winCenter, winWIDth赋值 dcmImage->getwindow(windowCenter, windowWIDth); } } }else{ dcmImage = new DicomImage(dcmMetaInfo, dcmDataset->getoriginalXfer(), CIF_TakeOverExternalDataset); if (dcmImage->getStatus() == EIS_normal) { imageWIDth = dcmImage->getWIDth(); imageHeight = dcmImage->getHeight(); if (windowWIDth < 1) { dcmImage->setRoiWindow(0, 0, imageWIDth, imageHeight); dcmImage->getwindow(windowCenter, windowWIDth); } } } //NSArray *paths = NSSearchPathForDirectorIEsInDomains(NSCachesDirectory,NSUserDomainMask,YES); //Nsstring *pathCache = [paths objectAtIndex:0]; //LOGE("----------Cache:---%@",pathCache); //todo long depth = dcmImage->getDepth(); long size = dcmImage->getoutputDataSize(8); //dcmImage->setwindow(windowCenter, windowWIDth); __androID_log_print(ANDROID_LOG_ERROR,"tag", "png height %ld ", imageHeight); __androID_log_print(ANDROID_LOG_ERROR,"tag", "png wIDth %ld ", imageWIDth); __androID_log_print(ANDROID_LOG_ERROR,"tag", "png depth %ld ", depth); __androID_log_print(ANDROID_LOG_ERROR,"tag", "png size %ld ", size); __androID_log_print(ANDROID_LOG_ERROR,"tag", "int size %ld",sizeof(int)); unsigned char *pixelData = (unsigned char *) (dcmImage->getoutputData(8, 0, 0)); long size1 = imageHeight * imageWIDth; unsigned char temp = NulL; jint * p = (int *)malloc(imageWIDth * imageHeight * sizeof(int)); // int *p = new int[size1]; if(strcmp(model,"SC") == 0){ __androID_log_print(ANDROID_LOG_ERROR,"tag", "-------执行SC------"); unsigned char r = NulL; unsigned char g = NulL; unsigned char b = NulL; for (int j = 0; j < size1; ++j) { r = pixelData[j * 3] ; g = pixelData[j * 3 + 1] ; b = pixelData[j * 3 + 2] ; p[j] = r | g << 8 | b << 16 | 0xff000000; } }else{ __androID_log_print(ANDROID_LOG_ERROR,"tag", "-------执行elseSC------"); for (int i = 0; i < size1; ++i) { temp = pixelData[i]; p[i] = temp | (temp << 8) | (temp << 16) | 0xff000000; } } if (pixelData != NulL) { __androID_log_print(ANDROID_LOG_ERROR,"tag", "pixelData not null"); } jintArray jntarray = env->NewIntArray( size1); env->ReleaseIntArrayElements(jntarray, p, 0); jclass myClass = env->FindClass("com/lsp/myapplication/DcmBean"); // 获取类的构造函数,记住这里是调用无参的构造函数 jmethodID ID = env->getmethodID(myClass, "<init>", "()V"); // 创建一个新的对象 jobject dcmBean_ = env->NewObject(myClass, ID); jfIEldID w = env->GetFIEldID(myClass, "wIDth", "J"); jfIEldID h = env->GetFIEldID(myClass, "height", "J"); jfIEldID dcm = env->GetFIEldID(myClass, "dcm", "[I"); env->SetLongFIEld(dcmBean_, w, imageWIDth); env->SetLongFIEld(dcmBean_, h, imageHeight); env->SetobjectFIEld(dcmBean_, dcm, jntarray); free(pixelData); free(p); delete dcmImage; delete dcmfileFormat; return myClass; } return NulL;}
参照ios平台的代码展示如下,c++模式的jni```cppextern "C" JNIEXPORT jobjectJNICALL Java_com_lsp_myapplication_MainActivity_getIntFromDcm(jnienv *env, Jstring inputPath_) { __androID_log_print(ANDROID_LOG_ERROR, "tag", "inputPath_:%s",inputPath_); __androID_log_print(ANDROID_LOG_ERROR, "tag", "1啦"); const char *chardata= "/storage/emulated/0/Download/Imagefilename002.dcm"; //chardata= inputPath_ ? env->GetStringUTFChars(inputPath_, NulL) : NulL; //env->ReleaseStringUTFChars(filename, chardata); //const char *chardata = env->GetStringUTFChars(filename, JNI_FALSE); OFfilename *file=new OFfilename(chardata,false); DcmfileFormat *dcmfileFormat = new DcmfileFormat(); OFCondition status = dcmfileFormat->loadfile(*file); if (status.good()) { OFString patIEntname; DcmDataset *dcmDataset = dcmfileFormat->getDataset(); OFCondition condition = dcmDataset->findAndGetoFString(DCM_PatIEntname,patIEntname); if (condition.good()) { __androID_log_print(ANDROID_LOG_ERROR, "tag", "condition.good = %s", patIEntname.c_str()); //LOGE(patIEntname.c_str()); } else { __androID_log_print(ANDROID_LOG_ERROR, "tag", "condition. BAD"); //LOGE("condition. BAD"); } const char *transferSyntax; DcmMetaInfo *dcmMetaInfo = dcmfileFormat->getMetaInfo(); OFCondition transferSyntaxOfCondition = dcmMetaInfo->findAndGetString( DCM_TransferSyntaxUID, transferSyntax); __androID_log_print(ANDROID_LOG_ERROR,"tag", "transferSyntaxOfCondition %s", transferSyntaxOfCondition.text()); __androID_log_print(ANDROID_LOG_ERROR,"tag", "transferSyntax %s", transferSyntax); // 获得当前的窗宽 窗位 float64 windowCenter; dcmDataset->findAndGetfloat64(DCM_WindowCenter, windowCenter); __androID_log_print(ANDROID_LOG_ERROR,"tag", "windowCenter %f",windowCenter); float64 windowWIDth; dcmDataset->findAndGetfloat64(DCM_WindowWIDth, windowWIDth); __androID_log_print(ANDROID_LOG_ERROR,"tag", "windowWIDth %f",windowWIDth); E_TransferSyntax xfer = dcmDataset->getoriginalXfer(); __androID_log_print(ANDROID_LOG_ERROR,"tag", "E_TransferSyntax %d", xfer); const char * model; dcmDataset->findAndGetString(DCM_Modality, model); __androID_log_print(ANDROID_LOG_ERROR,"tag", "-------------Model: %s",model); //NSArray *paths = NSSearchPathForDirectorIEsInDomains(NSCachesDirectory,NSUserDomainMask,YES); //Nsstring *pathCache = [paths objectAtIndex:0]; //LOGE("----------Cache:---%@",pathCache); // Dicom DicomImage *m_dcmImage = NulL; if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.70") == 0) { __androID_log_print(ANDROID_LOG_ERROR,"tag", "-------ct解析------"); DJDecoderRegistration::registerCodecs(); OFCondition chooSEOfCondition = dcmDataset->chooseRepresentation( EXS_littleEndianExplicit, NulL); m_dcmImage = new DicomImage((DcmObject *) dcmDataset, xfer); //利用dataset生成DicomImage,需要上面的解压方法; DJDecoderRegistration::cleanup(); }else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.80") == 0){ // LS 解码器 DJLSDecoderRegistration::registerCodecs(); OFCondition chooSEOfCondition = dcmDataset->chooseRepresentation(EXS_littleEndianExplicit, NulL); // if (dcmDataset->canWriteXfer(EXS_littleEndianExplicit)) { // OFCondition ofCondition = dcmfileFormat->savefile(filenameSave, // EXS_littleEndianExplicit); // // if (ofCondition.good()) { // LOGE("---------------保存成功----------------"); // LOGE("---------------------保存成功时间----%@",[self getTimeNow]); // }else{ // LOGE("-------------------Save Fail ------------------"); // } // } m_dcmImage = new DicomImage((DcmObject *) dcmDataset, xfer); //利用dataset生成DicomImage,需要上面的解压方法; DJLSDecoderRegistration::cleanup(); }else{ // // LS 解码器 // DJLSDecoderRegistration::registerCodecs(); // OFCondition chooSEOfCondition = dcmDataset->chooseRepresentation( // EXS_littleEndianExplicit, NulL); m_dcmImage = new DicomImage((DcmObject *) dcmDataset, xfer); //利用dataset生成DicomImage,需要上面的解压方法; // DJLSDecoderRegistration::cleanup(); } long height = m_dcmImage->getHeight(); long wIDth = m_dcmImage->getWIDth(); long depth = m_dcmImage->getDepth(); long size = m_dcmImage->getoutputDataSize(8); m_dcmImage->setwindow(windowCenter, windowWIDth); __androID_log_print(ANDROID_LOG_ERROR,"tag", "png height %ld ", height); __androID_log_print(ANDROID_LOG_ERROR,"tag", "png wIDth %ld ", wIDth); __androID_log_print(ANDROID_LOG_ERROR,"tag", "png depth %ld ", depth); __androID_log_print(ANDROID_LOG_ERROR,"tag", "png size %ld ", size); __androID_log_print(ANDROID_LOG_ERROR,"tag", "int size %ld",sizeof(int)); unsigned char *pixelData = (unsigned char *) (m_dcmImage->getoutputData(8, 0, 0)); long size1 = height * wIDth; unsigned char temp = NulL; jint * p = (int *)malloc(wIDth * height * sizeof(int)); // int *p = new int[size1]; if(strcmp(model,"SC") == 0){ __androID_log_print(ANDROID_LOG_ERROR,"tag", "-------执行SC------"); unsigned char r = NulL; unsigned char g = NulL; unsigned char b = NulL; for (int j = 0; j < size1; ++j) { r = pixelData[j * 3] ; g = pixelData[j * 3 + 1] ; b = pixelData[j * 3 + 2] ; p[j] = r | g << 8 | b << 16 | 0xff000000; } }else{ __androID_log_print(ANDROID_LOG_ERROR,"tag", "-------执行elseSC------"); for (int i = 0; i < size1; ++i) { temp = pixelData[i]; p[i] = temp | (temp << 8) | (temp << 16) | 0xff000000; } } if (pixelData != NulL) { __androID_log_print(ANDROID_LOG_ERROR,"tag", "pixelData not null"); } jintArray jntarray = env->NewIntArray( size1); env->ReleaseIntArrayElements(jntarray, p, 0); jclass myClass = env->FindClass("com/lsp/myapplication/DcmBean"); // 获取类的构造函数,记住这里是调用无参的构造函数 jmethodID ID = env->getmethodID(myClass, "<init>", "()V"); // 创建一个新的对象 jobject dcmBean_ = env->NewObject(myClass, ID); jfIEldID w = env->GetFIEldID(myClass, "wIDth", "J"); jfIEldID h = env->GetFIEldID(myClass, "height", "J"); jfIEldID dcm = env->GetFIEldID(myClass, "dcm", "[I"); env->SetLongFIEld(dcmBean_, w, wIDth); env->SetLongFIEld(dcmBean_, h, height); env->SetobjectFIEld(dcmBean_, dcm, jntarray); free(pixelData); free(p); delete m_dcmImage; delete dcmfileFormat; return myClass; } return NulL;}
后记:具体的dcm图像可以去https://dicomlibrary.com这个网站下载,代码是一个参照ios和windows平台的写的,但运行起来只读出了部分信息,图片信息没有读取成功,欢迎读者完善和交流,只是自己找遍了国内国外都没找到dcmtk在androID的实例代码,可能是这个应用的领域只是医学显得小众不像ffmpeg一样可以有更多参考,希望能起个抛砖引玉的效果,带来多些实例代码,另外目前只编译了arm64-v8a架构后续补上其他的。
最后留下一个我的代码下载地址
总结以上是内存溢出为你收集整理的dcm(dicom)医学影像android通过dcmtk解析全部内容,希望文章能够帮你解决dcm(dicom)医学影像android通过dcmtk解析所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)