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
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架构后续补上其他的。
最后留下一个我的代码下载地址