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

DICOM是由美国放射学院(ACR)和美国国家电气制造商协会(NEMA)开发的标准。在全球范围内用于存储,交换和传输医学图像。DICOM在现代放射成像的发展中一直处于核心地位:DICOM结合了诸如放射成像,超声检查,计算机断层扫描(CT),磁共振成像(MRI)和放射治疗等成像方式的标准。DICOM包括用于图像交换(例如,通过诸如DVD之类的便携式介质),图像压缩,3-D可视化,图像表示和结果报告的协议。更多信息可去维基百科查看。地址
dcm图像类似于这样
在这里插入图片描述
代码实现效果如图
在这里插入图片描述
目前关于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显示
c++模式的jni

extern "C" JNIEXPORT jobject
JNICALL Java_com_lsp_myapplication_MainActivity_getIntFromDcm(JNIEnv *env,jobject , jstring inputPath_) {
        const char *input_pa_name =  env->GetStringUTFChars(inputPath_, JNI_FALSE);
        //dcmtk解析图像用的字典
        const char *_dict=  "/storage/emulated/0/Download/dicom.dic";
        DcmDataDictionary &dict = dcmDataDict.wrlock();
        dict.loadDictionary(_dict);
        dcmDataDict.wrunlock();
        OFFilename *file=new OFFilename(input_pa_name,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);
            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);
            			}
            		}
            	}
            long depth = dcmImage->getDepth();
            long size = dcmImage->getOutputDataSize(8);
            __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));

           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);
            delete dcmFileFormat;
            return dcmBean_;
     }
    return NULL;
}

后记:具体的dcm图像可以去https://dicomlibrary.com这个网站下载,代码是参照ios和windows平台的写的,之前没加数据字典一直解析图像不成功,再回去参照代码后加上字典已能完整解析,可能是这个应用的领域只是医学显得小众不像ffmpeg一样可以有更多参考,希望能起个抛砖引玉的效果,带来多些实例代码,另外目前只编译了arm64-v8a架构后续补上其他的。

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

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在C++使用DCMTK解析多帧图像,您可以遵循以下步骤: 1. 首先,确保已正确安装DCMTK库并将其包含在您的C++项目。 2. 使用`DcmFileFormat`类加载DICOM文件。例如: ```cpp #include "dcmtk/dcmdata/dctk.h" // ... DcmFileFormat fileFormat; OFCondition status = fileFormat.loadFile("input.dcm"); if (status.bad()) { // 处理加载文件失败的情况 } ``` 3. 获取图像数据集并检查是否有多个图像。如果有多个图像,则使用`DcmPixelSequence`和`DcmPixelItem`类来访问每个图像的像素数据。例如: ```cpp DcmDataset* dataset = fileFormat.getDataset(); DcmElement* pixelDataElement = nullptr; if (dataset->findAndGetElement(DCM_PixelData, pixelDataElement).good()) { if (pixelDataElement->ident() == EVR_OB || pixelDataElement->ident() == EVR_OW) { // 处理多帧图像 DcmPixelSequence* pixelSequence = nullptr; if (pixelDataElement->getPixelSequence(pixelSequence).good()) { for (unsigned int frame = 0; frame < pixelSequence->card(); ++frame) { DcmPixelItem* currentItem = nullptr; if (pixelSequence->getItem(currentItem, frame).good()) { // 获取当前帧的像素数据并进行处理 Uint8* pixelData = nullptr; if (currentItem->getUint8Array(pixelData).good()) { // 处理像素数据 } } } } } else { // 单帧图像处理 } } ``` 4. 在处理图像数据时,您可能需要使用DCMTK的其他类和函数来解析和处理像素数据。您可以根据需要进行进一步的操作,例如解码像素数据、处理图像格式等。 请注意,以上代码仅为示例,您可能需要根据具体的需求进行适当的修改和扩展。确保在使用DCMTK库时遵循其相关许可协议。此外,DCMTK库还提供了更多功能和类,您可以参考其官方文档以获取更详细的信息和示例代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值