iOS 平台上编译DCMTK静态库--详细流程讲解

前言

DICOM

       DICOM(Digital Imaging and Communications in Medicine)即医学数字成像和通信,是医学图像和相关信息的国际标准。它定义了质量能满足临床需要的可用数据交换的医学图像格式。

DICOM被广泛应用于放射医疗,心血管成像以及放射诊疗诊断设备(X射线,CT,核磁共振,超声等),并且在眼科和牙科等其它医学领域得到越来越深入广泛的应用。在数以万计的在用医学成像设备中,DICOM是部署最为广泛的医疗信息标准之一。当前大约有百亿级符合DICOM标准的医学图像用于临床使用。

DCMTK

DCMTK是由德国offis公司提供的开源跨平台C++项目,这个开发包经过10多年的开发和维护,已经基本实现了DICOM协议的所有内容。该开发包提供所有的源代码、支持库和帮助文档。DCMTK提供了在各种操作系统下使用的可能版本,如LINUX、SUN、MACOS、WINDOWS等,用户可根据自己的开发平台进行编译。官方网站,开源项目Github

一、编译环境

        在编译DCMTK过程中,我查了很多相关的资料,查看网上编译DCMTK的流程基本上都是基于Xcode4的版本。基本DCMTK版本已经更新了很多版本,Xcode和Mac的系统都有更新,编译的配置也有很大的变化。

        由于这些弊端,所以记录一下最新的编译配置。

编译环境:(采用最新的版本编编译)
Xcode 10.1
Mac OS 10.14
IOS SDK 12
DCMTK (目前最新版本:dcmtk-3.6.4)

二、编译DCMTK流程

        下边会结合图文的形式详细讲述编译的整个过程,根据下方的流程一步一步的操作,就可以顺利的编译出来需要的DCMTK库。

1. 下载DCMTK源码

首先下载最新的DCMTK源码:
1.官网上下载最新的(https://www.dcmtk.org/dcmtk.php.en)

官网下载

2.DCMTK官方GitHub上下载(https://github.com/DCMTK/dcmtk)
GitHub

2.下载编译工具(CMake)

CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。

最新的编译工具CMake官方网站地址(https://cmake.org/download/)

CMake

3.CMake编译

1.把下载的DCMTK加压到一个文件DCMTK_TEST中,
2.建立一个CMake编译过项目存放的位置DCMTK_Project。


在这里插入图片描述

3.打开CMake编译DCMTK
在这里插入图片描述
  1. 选择编译源码的路径和生成项目的路径
  2. 配置CMake编译的配置


    在这里插入图片描述

    6.点击CMake的Configure按钮之后配置Configure(默认),点击Done。

在这里插入图片描述

7.当第6步执行完,生成如下图的配置。


在这里插入图片描述

8.修改自定义的配置分别修改如下后如下图:


在这里插入图片描述

9.点击Configure按钮,红色会恢复正常白色
10.点击General按钮。(速度比较快)
在这里插入图片描述

11.点击Open Project 按钮,Xcode自动打开生成的DCMTK静态库的项目。


项目

三、编译DCMTK静态库

1.编译之后的DCMTK工程支持Mac的静态库的生成,需要设置项目配置支持IOS静态库的生成。

在这里插入图片描述

2.修改Architectures、Base SDK、Build Active Architectures Only、Supported Platformas。
在这里插入图片描述

3.修改IOS Deployment Target的版本(修改成需要的版本)
在这里插入图片描述

4.修改需要编译的Targets项目的配置
在这里插入图片描述

5.运行ALL_BUILD这个Targets,选择真机或者模拟器,会出现一个error。注释这一行引入libc.h这行代码,运行就不会出现错误。
在这里插入图片描述

6.编译支持真机、模拟器的静态库,可以参考文章https://blog.csdn.net/Future_One/article/details/86172828
7.编译通过查看生成的静态库
在这里插入图片描述

四、DCMTK项目使用

1.在源码dcmtk-master文件夹中查找静态库的头文件,放在一个dcmtk的文件夹中
每一个模块的目录下都 有对应的静态库的头文件:
例如:dcmdata模块
/dcmdata/include/dcmtk/dcmdata/静态库的头文件

在这里插入图片描述

2.把DCMTK_Project项目中生成的头文件拷贝出来,放在一个dcmtk的文件夹中


在这里插入图片描述

3.把所有的头文件放在一个dcmtk文件夹中如下图:


在这里插入图片描述

4.创建一个DCMTK_New_Demo的项目,导入静态库和头文件。
在这里插入图片描述

5.添加DCMTK相关的两个系统框架libz.tbd和libiconv.2.4.0.tbd库

如果不添加这两个库会出现错误提示不支持模拟器或者真机(终端使用libo -info 查看对应的库都是支持的)

在这里插入图片描述

6.修改DCMTK头文件,文件之间相互引用的路径
由于DCMTK框架是C++写的一个开源框架,头文件路径都是采用的include的方式引入,修改修改一下引入的方式。

四、DCMTK写Demo

Demo是一个读取dcm文件,分别对LS、Loss和未压缩的文件的读取(解压写入本地的代码也在代码中)


.h
typedef void (^dcmReturn)(UIImage *imgData, long imgWidth, long  imgHeight);

@interface DCMImgShow : NSObject
-(void)getImgDataFileName:(NSString *)fileName withImgisInverse:(BOOL)isInverse withBlock:(dcmReturn)dicom;
@end



.m

-(void)getImgDataFileName:(NSString *)fileName withImgisInverse:(BOOL)isInverse withBlock:(dcmReturn)dicom{
    
  
    NSString *filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:@"dcm"];
    const char *fileNameStr = [filePath cStringUsingEncoding:NSASCIIStringEncoding];
    
    DcmFileFormat *dcmFileFormat = new DcmFileFormat();
    OFCondition status = dcmFileFormat->loadFile(fileNameStr);
    
    if (status.good()) {
        OFString patientName;
        DcmDataset *dcmDataset = dcmFileFormat->getDataset();
        OFCondition condition = dcmDataset->findAndGetOFString(DCM_PatientName,
                                                               patientName);
        if (condition.good()) {
            NSLog(@"pat name %s", patientName.c_str());
        } else {
            NSLog(@"condition. BAD");
        }
        
        
        const char *transferSyntax;
        DcmMetaInfo *dcmMetaInfo = dcmFileFormat->getMetaInfo();
        OFCondition transferSyntaxOfCondition = dcmMetaInfo->findAndGetString(
                                                                              DCM_TransferSyntaxUID, transferSyntax);
        NSLog(@"transferSyntaxOfCondition  %s", transferSyntaxOfCondition.text());
        NSLog(@"transferSyntax  %s", transferSyntax);
        
        // 获得当前的窗宽 窗位
        
        Float64 windowCenter;
        dcmDataset->findAndGetFloat64(DCM_WindowCenter, windowCenter);
        NSLog(@"windowCenter %f", windowCenter);
        
        Float64 windowWidth;
        dcmDataset->findAndGetFloat64(DCM_WindowWidth, windowWidth);
        NSLog(@"windowWidth %f", windowWidth);
        
        E_TransferSyntax xfer = dcmDataset->getOriginalXfer();
        NSLog(@"E_TransferSyntax %d", xfer);
        
         const char * model;
        dcmDataset->findAndGetString(DCM_Modality, model);
        
        NSLog(@"-------------Model: %s",model);
        
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES);
        NSString *pathCache = [paths objectAtIndex:0];
        NSLog(@"----------Cache:---%@",pathCache);
        
       
        // Dicom
        DicomImage *m_dcmImage = NULL;
        if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.70") == 0) {
            
            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()) {
//                    NSLog(@"---------------保存成功----------------");
//                    NSLog(@"---------------------保存成功时间----%@",[self getTimeNow]);
//                }else{
//                    NSLog(@"-------------------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);
        NSLog(@"png height %ld ", height);
        NSLog(@"png width %ld ", width);
        NSLog(@"png depth %ld ", depth);
        NSLog(@"png size %ld ", size);
        NSLog(@"int size %ld",sizeof(int));

        unsigned char *pixelData = (unsigned char *) (m_dcmImage->getOutputData(8, 0, 0));

        long size1 = height * width;
        unsigned char temp = NULL;
        
        int * p = (int *)malloc(width * height * sizeof(int));
//        int *p = new int[size1];
        
       if(strcmp(model,"SC") == 0){
            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{
           for (int i = 0; i < size1; ++i) {
               temp = pixelData[i];
               p[i] = temp | (temp << 8) | (temp << 16) | 0xff000000;
           }
       }

        if (pixelData != NULL) {
            NSLog(@"pixelData not null");
        }

        NSData *imgData = [NSData dataWithBytes:(Byte *)p length:size1 * sizeof(int)];
        
        // 释放内存
        free(pixelData);
        free(p);
        
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)imgData);
        
        CGImageRef imageRef = CGImageCreate(width,             //width
                                            height,            //height
                                            8,                 //bits per component
                                            32,                //bits per pixel
                                            width*4,           //bytesPerRow
                                            colorSpace,        //colorspace
                                            kCGImageAlphaNone | kCGImageAlphaNoneSkipLast,        //kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder16Little,// bitmap info
                                            provider,               //CGDataProviderRef
                                            NULL,                   //decode
                                            true,                  //should interpolate
                                            kCGRenderingIntentDefault   //intent
                                            );
        
        if (isInverse) {
            UIImage *testImage = [UIImage imageWithCGImage:imageRef];
            
            dicom(testImage, width, height);
            CGImageRelease(imageRef);
            CGDataProviderRelease(provider);
            CGColorSpaceRelease(colorSpace);
            return;
        }
        
        size_t                  bytesPerRow;
        bytesPerRow = CGImageGetBytesPerRow(imageRef);
        
        CFDataRef   data;
        UInt8*      buffer;
        data = CGDataProviderCopyData(provider);
        buffer = (UInt8*)CFDataGetBytePtr(data);
        UIImage *testImage = [UIImage imageWithCGImage:imageRef];
        
        // 返回当前的img 数据
        dicom(testImage, width, height);
        CGImageRelease(imageRef);
        CGDataProviderRelease(provider);
        CGColorSpaceRelease(colorSpace);
        CFRelease(data);
    }
}

Dcmtk框架的显示dcm的 Demo

下载地址https://github.com/FlameDream/DCMTK_Demo

五、DCMTK配置扩展

DCMTK是一个比较庞大的库,包括dcm文件读取、解压dcm文件、生成RGB裸数据、读取dcm的tag、网络、多线程等。工程里可能只需要使用dcmtk框架中的一小部分的功能,可以采用两种方式解决引入过多无用的库和头文件
1.把所有库文件和头文件生成并引入项目中,在逐一删除对应多余的库和头文件
2.直接在CMake中删除不需要的框架配置(如下图)


在这里插入图片描述

六、性能优化

修改DCMTK在CMake的代码版本类型配置


修改程序的版本

把Debug;Release;MinSizeRel;RelWithDebInfo修改成Release。随后生成的.a库文件减小,同时DCMTK的性能也有所提高。

总结

DCMTK是一个比较庞大的读取DICOM的开源库,由于DCMTK开源框架的文档比较少,所有有很多使用者使用比较不爽,欢迎大家相互交流

你可能感兴趣的:(iOS 平台上编译DCMTK静态库--详细流程讲解)