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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | extern "C" JNIEXPORT jobject JNICALL 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; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | 参照ios平台的代码展示如下,c++模式的jni ```cpp extern "C" JNIEXPORT jobject JNICALL 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架构后续补上其他的。
最后留下一个我的代码下载地址