dcm(dicom)医学影像android通过dcmtk解析

DICOM是由美国放射学院(ACR)和美国国家电气制造商协会(NEMA)开发的标准。在全球范围内用于存储,交换和传输医学图像。DICOM在现代放射成像的发展中一直处于核心地位:DICOM结合了诸如放射成像,超声检查,计算机断层扫描(CT),磁共振成像(MRI)和放射治疗等成像方式的标准。DICOM包括用于图像交换(例如,通过诸如DVD之类的便携式介质),图像压缩,3-D可视化,图像表示和结果报告的协议。更多信息可去*查看。地址
dcm图像类似于这样
dcm(dicom)医学影像android通过dcmtk解析
首先说明下这是一个没有运行成功的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)这个判断前导致报没有临时输出文件错误,如图dcm(dicom)医学影像android通过dcmtk解析
对应于那些地方需要用到return(),可参照错误日志类似于下图的地方需要模拟器就写上return()
dcm(dicom)医学影像android通过dcmtk解析
这是针对于编译,编译后需要运行,由于Android的imageview一般可展示的就是png,jpg,bitmap,我参考上述博客ios的demo,决定将文件读为int数组再去转bitmap在android上显示,其实参考发现这种输出有点类似于opencv输出构造bitmap显示。具体代码参照windows代码如下,c++模式的jni

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;
}
参照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架构后续补上其他的。

最后留下一个我的代码下载地址

上一篇:dcm文件之“偷梁换柱”(使用npy文件中的数据替换dcm文件中的影像数据,然后重新保存得到新的dcm格式文件)


下一篇:java-如何以编程方式获取Hibernate模型的jOOQ表?