前言
DICOM
???????DICOM(Digital Imaging and Communications in Medicine)即醫(yī)學(xué)數(shù)字成像和通信,是醫(yī)學(xué)圖像和相關(guān)信息的國(guó)際標(biāo)準(zhǔn)。它定義了質(zhì)量能滿足臨床需要的可用數(shù)據(jù)交換的醫(yī)學(xué)圖像格式。
DICOM被廣泛應(yīng)用于放射醫(yī)療,心血管成像以及放射診療診斷設(shè)備(X射線,CT,核磁共振,超聲等),并且在眼科和牙科等其它醫(yī)學(xué)領(lǐng)域得到越來(lái)越深入廣泛的應(yīng)用。在數(shù)以萬(wàn)計(jì)的在用醫(yī)學(xué)成像設(shè)備中,DICOM是部署最為廣泛的醫(yī)療信息標(biāo)準(zhǔn)之一。當(dāng)前大約有百億級(jí)符合DICOM標(biāo)準(zhǔn)的醫(yī)學(xué)圖像用于臨床使用。
DCMTK
DCMTK是由德國(guó)offis公司提供的開源跨平臺(tái)C++項(xiàng)目,這個(gè)開發(fā)包經(jīng)過10多年的開發(fā)和維護(hù),已經(jīng)基本實(shí)現(xiàn)了DICOM協(xié)議的所有內(nèi)容。該開發(fā)包提供所有的源代碼、支持庫(kù)和幫助文檔。DCMTK提供了在各種操作系統(tǒng)下使用的可能版本,如LINUX、SUN、MACOS、WINDOWS等,用戶可根據(jù)自己的開發(fā)平臺(tái)進(jìn)行編譯。官方網(wǎng)站,開源項(xiàng)目Github
一、編譯環(huán)境
????????在編譯DCMTK過程中,我查了很多相關(guān)的資料,查看網(wǎng)上編譯DCMTK的流程基本上都是基于Xcode4的版本?;綝CMTK版本已經(jīng)更新了很多版本,Xcode和Mac的系統(tǒng)都有更新,編譯的配置也有很大的變化。
????????由于這些弊端,所以記錄一下最新的編譯配置。
編譯環(huán)境:(采用最新的版本編編譯)
Xcode 10.1
Mac OS 10.14
IOS SDK 12
DCMTK (目前最新版本:dcmtk-3.6.4)
二、編譯DCMTK流程
????????下邊會(huì)結(jié)合圖文的形式詳細(xì)講述編譯的整個(gè)過程,根據(jù)下方的流程一步一步的操作,就可以順利的編譯出來(lái)需要的DCMTK庫(kù)。
1. 下載DCMTK源碼
首先下載最新的DCMTK源碼:
1.官網(wǎng)上下載最新的(https://www.dcmtk.org/dcmtk.php.en)
2.DCMTK官方GitHub上下載(https://github.com/DCMTK/dcmtk)
2.下載編譯工具(CMake)
CMake是一個(gè)跨平臺(tái)的安裝(編譯)工具,可以用簡(jiǎn)單的語(yǔ)句來(lái)描述所有平臺(tái)的安裝(編譯過程)。他能夠輸出各種各樣的makefile或者project文件,能測(cè)試編譯器所支持的C++特性,類似UNIX下的automake。
最新的編譯工具CMake官方網(wǎng)站地址(https://cmake.org/download/)
3.CMake編譯
1.把下載的DCMTK加壓到一個(gè)文件DCMTK_TEST中,
2.建立一個(gè)CMake編譯過項(xiàng)目存放的位置DCMTK_Project。
3.打開CMake編譯DCMTK
- 選擇編譯源碼的路徑和生成項(xiàng)目的路徑
-
配置CMake編譯的配置
在這里插入圖片描述
6.點(diǎn)擊CMake的Configure按鈕之后配置Configure(默認(rèn)),點(diǎn)擊Done。
7.當(dāng)?shù)?步執(zhí)行完,生成如下圖的配置。
8.修改自定義的配置分別修改如下后如下圖:
9.點(diǎn)擊Configure按鈕,紅色會(huì)恢復(fù)正常白色
10.點(diǎn)擊General按鈕。(速度比較快)
11.點(diǎn)擊Open Project 按鈕,Xcode自動(dòng)打開生成的DCMTK靜態(tài)庫(kù)的項(xiàng)目。
三、編譯DCMTK靜態(tài)庫(kù)
1.編譯之后的DCMTK工程支持Mac的靜態(tài)庫(kù)的生成,需要設(shè)置項(xiàng)目配置支持IOS靜態(tài)庫(kù)的生成。
2.修改Architectures、Base SDK、Build Active Architectures Only、Supported Platformas。
3.修改IOS Deployment Target的版本(修改成需要的版本)
4.修改需要編譯的Targets項(xiàng)目的配置
5.運(yùn)行ALL_BUILD這個(gè)Targets,選擇真機(jī)或者模擬器,會(huì)出現(xiàn)一個(gè)error。注釋這一行引入libc.h這行代碼,運(yùn)行就不會(huì)出現(xiàn)錯(cuò)誤。
6.編譯支持真機(jī)、模擬器的靜態(tài)庫(kù),可以參考文章https://blog.csdn.net/Future_One/article/details/86172828
7.編譯通過查看生成的靜態(tài)庫(kù)
四、DCMTK項(xiàng)目使用
1.在源碼dcmtk-master文件夾中查找靜態(tài)庫(kù)的頭文件,放在一個(gè)dcmtk的文件夾中
每一個(gè)模塊的目錄下都 有對(duì)應(yīng)的靜態(tài)庫(kù)的頭文件:
例如:dcmdata模塊
/dcmdata/include/dcmtk/dcmdata/靜態(tài)庫(kù)的頭文件
2.把DCMTK_Project項(xiàng)目中生成的頭文件拷貝出來(lái),放在一個(gè)dcmtk的文件夾中
3.把所有的頭文件放在一個(gè)dcmtk文件夾中如下圖:
4.創(chuàng)建一個(gè)DCMTK_New_Demo的項(xiàng)目,導(dǎo)入靜態(tài)庫(kù)和頭文件。
5.添加DCMTK相關(guān)的兩個(gè)系統(tǒng)框架libz.tbd和libiconv.2.4.0.tbd庫(kù)
如果不添加這兩個(gè)庫(kù)會(huì)出現(xiàn)錯(cuò)誤提示不支持模擬器或者真機(jī)(終端使用libo -info 查看對(duì)應(yīng)的庫(kù)都是支持的)
6.修改DCMTK頭文件,文件之間相互引用的路徑
由于DCMTK框架是C++寫的一個(gè)開源框架,頭文件路徑都是采用的include的方式引入,修改修改一下引入的方式。
四、DCMTK寫Demo
Demo是一個(gè)讀取dcm文件,分別對(duì)LS、Loss和未壓縮的文件的讀?。ń鈮簩懭氡镜氐拇a也在代碼中)
.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);
// 獲得當(dāng)前的窗寬 窗位
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(@"---------------------保存成功時(shí)間----%@",[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)];
// 釋放內(nèi)存
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];
// 返回當(dāng)前的img 數(shù)據(jù)
dicom(testImage, width, height);
CGImageRelease(imageRef);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpace);
CFRelease(data);
}
}
Dcmtk框架的顯示dcm的 Demo
下載地址https://github.com/FlameDream/DCMTK_Demo
五、DCMTK配置擴(kuò)展
DCMTK是一個(gè)比較龐大的庫(kù),包括dcm文件讀取、解壓dcm文件、生成RGB裸數(shù)據(jù)、讀取dcm的tag、網(wǎng)絡(luò)、多線程等。工程里可能只需要使用dcmtk框架中的一小部分的功能,可以采用兩種方式解決引入過多無(wú)用的庫(kù)和頭文件
1.把所有庫(kù)文件和頭文件生成并引入項(xiàng)目中,在逐一刪除對(duì)應(yīng)多余的庫(kù)和頭文件
2.直接在CMake中刪除不需要的框架配置(如下圖)
六、性能優(yōu)化
修改DCMTK在CMake的代碼版本類型配置

把Debug;Release;MinSizeRel;RelWithDebInfo修改成Release。隨后生成的.a庫(kù)文件減小,同時(shí)DCMTK的性能也有所提高。
總結(jié)
DCMTK是一個(gè)比較龐大的讀取DICOM的開源庫(kù),由于DCMTK開源框架的文檔比較少,所有有很多使用者使用比較不爽,歡迎大家相互交流