DICOM医学图像处理:DICOM存储操作之“多幅BMP图像数据存入DCM文件”

背景:

本专栏“DICOM医学图像处理”受众较窄,起初只想作为自己学习积累和工作经验的简单整理。前几天无聊浏览了一下,发现阅读量两极化严重,主要集中在“关于BMP(JPG)与DCM格式转换”和“DICOM 通讯协议”,尤其是许久前的第一篇博文DCMTK开源库的学习笔记1:将DCM文件保存成BMP文件或数据流(即数组)。因此在2014年底前打算写几篇关于DCM格式转换的文章,此次主要聚焦“如何将BMP、JPG等常规图像保存成DCM文件”,以DCMTK库为基础,给出简单的实例。

这几篇博文采用倒叙的方式,先给出可直接运行的源码,然后重点讲解其中易犯的错误,最后是知识点补充。

利用DCMTK实现Multi-BMP存入DCM:

源码以DCMTK为基础,思路参照DCMTK的img2dcm工具包,依赖库包含:netapi32.lib; wsock32.lib; ofstd.lib; oflog.lib; dcmimgle.lib; ijg8.lib; ijg12.lib; ijg16.lib; dcmdata.lib; dcmimage.lib; dcmjpeg.lib; dcmnet.lib; zlib.lib;
libi2d.lib;(【注】:libi2d.lib库是用于导入BMP文件的)

源码如下:

// DcmPixelDataTest.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmdata/dctk.h"
#include "dcmtk/dcmdata/dcistrmf.h"
#include "dcmtk/dcmdata/libi2d/i2dbmps.h"
#include "DicomUtils.h"
#include <direct.h>

int _tmain(int argc, _TCHAR* argv[])
{
	OFCondition status;

	DcmFileFormat fileformat;
	DcmDataset* mydatasete=fileformat.getDataset();
	DicomUtils::AddDicomElements((DcmDataset*&)mydatasete);
	Uint16 rows,cols,samplePerPixel,bitsAlloc,bitsStored,highBit,pixelRpr,planConf,pixAspectH,pixAspectV;
	OFString photoMetrInt;
	Uint32 length;
	E_TransferSyntax ts;
	char* mydata=new char[1024*1024*10];
	memset(mydata,0,sizeof(char)*1024*1024*10);
	char* tmpData=mydata;
	char curDir[100];
	getcwd(curDir,100);
	//循环添加4张图片
	for(int i=0;i<4;++i)
	{
		OFString num;
		char numtmp[100];
		memset(numtmp,0,sizeof(char)*100);
		sprintf(numtmp,"%s\\test\\%d.bmp",curDir,i+1);
		OFString filename=OFString(numtmp);
		I2DBmpSource* bmpSource=new I2DBmpSource();
		bmpSource->setImageFile(filename);

		char* pixData=NULL;
		bmpSource->readPixelData(rows,cols,samplePerPixel,photoMetrInt,bitsAlloc,bitsStored,highBit,pixelRpr,planConf,pixAspectH,pixAspectV,pixData,length,ts);
		memcpy(tmpData,pixData,length);
		tmpData+=length;

		delete bmpSource;
	};

	mydatasete->putAndInsertUint16(DCM_SamplesPerPixel,samplePerPixel);
	mydatasete->putAndInsertString(DCM_NumberOfFrames,"4");
	mydatasete->putAndInsertUint16(DCM_Rows,rows);
	mydatasete->putAndInsertUint16(DCM_Columns,cols);
	mydatasete->putAndInsertUint16(DCM_BitsAllocated,bitsAlloc);
	mydatasete->putAndInsertUint16(DCM_BitsStored,bitsStored);
	mydatasete->putAndInsertUint16(DCM_HighBit,highBit);
	mydatasete->putAndInsertUint8Array(DCM_PixelData,(Uint8*)mydata,4*length);
	mydatasete->putAndInsertOFStringArray(DCM_PhotometricInterpretation,photoMetrInt);
	//mydatasete->putAndInsertString(DCM_PlanarConfiguration,"1");
	status=fileformat.saveFile("c:\\Multibmp2dcmtest.dcm",ts);
	if(status.bad())
	{
		std::cout<<"Error:("<<status.text()<<")\n";
	}
	return 0;
}

代码中的DicomUtils类是一个方法类,提供了一个静态方法AddDicomElement构造DICOM基本元素,代码如下:

#include "DicomUtils.h"

DicomUtils::DicomUtils(void)
{
}

DicomUtils::~DicomUtils(void)
{
}
void DicomUtils::AddDicomElements(DcmDataset*& dataset)
{
	//构建测试数据

	/*	添加患者信息	*/
	dataset->putAndInsertUint16(DCM_AccessionNumber,0);
	dataset->putAndInsertString(DCM_PatientName,"zssure",true);
	dataset->putAndInsertString(DCM_PatientID,"2234");
	dataset->putAndInsertString(DCM_PatientBirthDate,"20141221");
	dataset->putAndInsertString(DCM_PatientSex,"M");

	/*	添加Study信息	*/
	dataset->putAndInsertString(DCM_StudyDate,"20141221");
	dataset->putAndInsertString(DCM_StudyTime,"195411");
	char uid[100];
	dcmGenerateUniqueIdentifier(uid,SITE_STUDY_UID_ROOT);
	dataset->putAndInsertString(DCM_StudyInstanceUID,uid);
	dataset->putAndInsertString(DCM_StudyID,"1111");

	/*	添加Series信息	*/
	dataset->putAndInsertString(DCM_SeriesDate,"20141221");
	dataset->putAndInsertString(DCM_SeriesTime,"195411");
	memset(uid,0,sizeof(char)*100);
	dcmGenerateUniqueIdentifier(uid,SITE_SERIES_UID_ROOT);
	dataset->putAndInsertString(DCM_SeriesInstanceUID,uid);
	/*	添加Image信息	*/
	dataset->putAndInsertString(DCM_ImageType,"ORIGINAL\\PRIMARY\\AXIAL");
	dataset->putAndInsertString(DCM_ContentDate,"20141221");
	dataset->putAndInsertString(DCM_ContentTime,"200700");
	dataset->putAndInsertString(DCM_InstanceNumber,"1");
	dataset->putAndInsertString(DCM_SamplesPerPixel,"1");
	dataset->putAndInsertString(DCM_PhotometricInterpretation,"MONOCHROME2");
	dataset->putAndInsertString(DCM_PixelSpacing,"0.3\\0.3");
	dataset->putAndInsertString(DCM_BitsAllocated,"16");
	dataset->putAndInsertString(DCM_BitsStored,"16");
	dataset->putAndInsertString(DCM_HighBit,"15");
	dataset->putAndInsertString(DCM_WindowCenter,"600");
	dataset->putAndInsertString(DCM_WindowWidth,"800");
	dataset->putAndInsertString(DCM_RescaleIntercept,"0");
	dataset->putAndInsertString(DCM_RescaleSlope,"1");

}

问题分析:

1)文件格式错误:

我在方法类DicomUtils中默认添加的SamplePerPixel标签值为1,如果最终读取完像素数据(即readPixelData函数调用完)未重新写入BMP相应的SamplePerPixel字段,会引发文件格式错误,在SanteSoft DICOM Editor软件中打开弹出如下错误:

利用DCMTK自带的dcmdump.exe工具分析结果如下:

由此可以看出像素数据读取失败。

2)图像信息显示错误:

上图是正确图像,下图是由于Photometric Interpretation字段写入错误导致的,静态类DicomUtils中默认的Photometric Interpretation值为MONOCHROME2,修改为I2DBmpSource中readPixelData函数返回的photoMetrInt参数后图像数据显示正确,如下图所示:

3)图像色彩显示错误:

在静态类DicomUtils中并未添加Planar Configuration字段,因此DCMTK自动填充该字段为0,如果我们修改为1,会出现图像色彩错误,如下图:

BMP格式:

关于BMP格式介绍的博文很多,可参考http://blog.csdn.net/zhandoushi1982/article/details/5196017或者http://blog.csdn.net/gwwgle/article/details/4775396。BMP文件数据主要有以下几部分组成:1)文件头,即结构BITMAPFILEHEADER,
*PBITMAPFILEHEADER,类似于DCM中的DcmMetaInfo;2)图像描述信息块,该部分记录了图像信息块的大小、图像的宽度、高度、图像通道数(即Plane)、像素位数(即后面DICOM标准中的SamplesPerPixel)、图像压缩方式、图像数据区大小等等;3)颜色表,即调色板。该部分与DICOM标准中的COLOR PALETTE,随着像素位数不同颜色表大小也不同,当像素位数为24或更大,即SamplesPerPixel=3时,像素数据本身就可以代表颜色,因此不需要颜色表;4)图像数据区,即文件中存储的真正的像素信息。【注】:这里有一个坐标转换,标准的BMP文件像素存储顺序是由左到右、由下到上,即坐标原点为图像左下角;而DICOM标准存储顺序为从左到右,从上到下,坐标原点为图像左上角,因此在自己读取时需要进行反转。

获取BMP图像信息方法:

1)直接读取二进制

了解了BMP文件的具体格式,可以利用常用的二进制操作方式,直接从文件中提取像素数据。这种代码网上也很多,可参考:http://www.jb51.net/article/56274.htm

2)DCMTK库

DCMTK库中的I2DBmpSource类是专门用来解析BMP文件的,并且提供了BMP到DICOM数据格式的转换。具体的使用可参照我上面的实例,也可参考DCMTK给出的img2dcm工具包源码。

3)CxImage第三方库

CxImage是一款免费的、优秀的图像操作类库,可以快捷的存取、显示、转换各种图像,例如BMP、GIF、ICO、TGA、JPEG、PCX、PNG、TIFF、MNG、RAS等等;CxImage使用简单,文档详细,只有一个API接口文件;支持Windows、Linux和Unix等多平台,支持32位和64位。

关于CxImage的复杂使用,可参见CodeProject中大神的博文:http://www.codeproject.com/Articles/1300/CxImage

另外我在博文DCMTK开源库的学习笔记1:将DCM文件保存成BMP文件或数据流(即数组)中给出的源码是结合了CxImage和DCMTK两种开源库,这也是常见的一种组合方式,具体细节可参考我的GitHub上的源码。

DICOM文件格式:

1)Samples Per Pixel:

标签为(0028,0002),具体的介绍在DICOM3.0标准第3部分的附录C7.6.3.1。含义表示【the number of separate planes in this image】,就像PhotoShop中的通道,每个通道表示一种颜色(除了RGB三个通道以外,也会存在第四个通透性通道)。对于灰度图像(monochrome或gray)和颜色表图像(palette,就是BMP格式中介绍的有调色板的BMP文件),该标签值为1,RGB图像或其他色彩模式图像,该标签值为3。本实例中使用的BMP图像是RGB格式的,因此SamplePerPixel=3,起初的文件格式错误就是由于该字段设置为1所致。

2)Photometric Interpretation:

标签为(0028,0004),具体介绍在DICOM3.0标准第3部分的附录C7.6.3.1.2。该字段常见的值有MONOCHROME1、MONOCHROME2、PALETTE COLOR、RGB,其中MONOCHROME1和MONOCHROME2表示单通道灰度图像,只是两者对黑色和白色的映射相反而已;PALETTE COLOR就是BMP中提到的调色板图像,此时需要SamplesPerPixel字段为1,;RGB是常见的R(红)、G(绿)、B(蓝)三通道彩色图像,此时SamplesPerPixel字段值为3,这就是我们实例中使用的图像。除此以外DICOM3.0标准中还给出了YBR_FULL、HSV、ARGB、CMYK等方式,此处就不详细介绍了。

3)Planar Configuration:

标签为(0028,0006),具体介绍在DICOM3.0标准第3部分附录C7.6.3.1.3。当Samples Per Pixel字段的值大于1时,Planar Configuration字段规定了实际像素信息的存储方式,具体如下:

最终结果:

博文中实例代码最终在C盘根目录生成Multibmp2dcmtest.dcm文件,利用Sante DICOM Editor打开可以顺利看到文件中包含了我们插入的四张bmp图像,如下图:

至此将多幅BMP图像写入DCM文件的任务顺利完成了,其实将多幅图像写入DCM文件与写入单幅BMP图像是完全相同的,只需要将多张BMP图像(此时要求每张BMP图像的宽度和高度相同)像素数据首尾相接的写入DCM中的PixelData标签下,即(7FE0,0010);此时将NumberofFrames标签赋值为图像张数,DCM文件编辑器就可自动识别提取各张图像。

源码:

百度网盘:http://pan.baidu.com/s/1dDrhHlR

GitHub:https://github.com/zssure-thu/CSDN/tree/master

后续博文介绍:

多幅JPEG图像数据存入DCM文件

fo-dicom搭建简单的PACS Server服务端

作者:[email protected]

时间:2014-12-24

时间: 2024-12-20 01:07:33

DICOM医学图像处理:DICOM存储操作之“多幅BMP图像数据存入DCM文件”的相关文章

DICOM医学图像处理:DICOM存储操作之 “多幅JPG图像数据存入DCM文件”

背景: 续上篇,继续介绍如何将多幅JPG图像数据存入DCM文件.即将有损压缩数据直接写入DCM文件,存储为Multi-frame形式. 多幅JPG图像数据存入DCM文件: 为了避免引起歧义,这里着重说明一下.本博文的描述的场景是:假设我们手中有多张JPG文件,想把JPG文件写入DCM文件,即单个DCM文件包含多幅图像信息的Multi-Frame形式.该问题之前与CSDN博友y317215133y也讨论过,当时我在OFFIS论坛中找到了一个帖子直接给了y317215133y答复.今天重新梳理了一下

DICOM医学图像处理:WEB PACS初谈二,图像的传输

背景: 如前一篇专栏博文所述,借助于CGI或FastCGI技术转发浏览器发送过来的用户请求,启动本地的DCMTK和CxImage库响应.然后将处理结果转换成常规图像返回到浏览器来实现Web PACS.本博文通过实际的代码測试来验证这一模式的可行性,同一时候对C语言编写CGI脚本提出了一些问题. 难题: 计划參照DCMTK自带工具dcm2pnm.exe的源代码.通过DicomImage将DCM文件转换成BMP文件,然后利用CGI技术返回到浏览器.实现一次简单的WEB PACS的影像传输模拟.详细的

DICOM医学图像处理:DCMTK的wiki资料学习之PACS调试

背景: 前段时间着重从dcmtk和fo-dicom(mDCM)源码角度进行剖析,期望加深对DICOM协议的理解.知其然,知其所以然.如果"所以然"很不好懂,那我们还是先多多"知其然"吧.搞清楚原理的目的不也是为了更好的运用于实践么?所以理论和实践应该彼此交错进行,理论搞不动了就搞搞应用,应用久了就钻研钻研理论. 以前上DCMTK官网仅仅是浏览关于开源库中各个类的设计模式.依赖关系.最近在打开DCMTK官网的wiki时,才发现OFFIS对DCMTK的介绍是如此的详细.

DICOM医学图像处理:storescp.exe与storescu.exe源码剖析,学习C-STORE请求

背景: 上一篇专栏博文中针对PACS终端(或设备终端,如CT设备)与RIS系统之间worklist查询进行了介绍,并着重对比分析了DICOM3.0中各部分对DICOM网络通讯服务的定义.此次通过结合早些时间的博文DICOM医学图像处理:基于DCMTK工具包学习和分析worklist,对DCMTK开源库中提供的storescp.exe和storescu.exe工具的源码进行剖析,从底层深入了解C-STORE服务的触发及响应. 分析思路: storescp.exe和storescu.exe分别充当着

DICOM医学图像处理:DICOM网络传输

背景: 专栏取名为DICOM医学图像处理原因是:博主是从医学图像处理算法研究时开始接触DICOM协议的.当初认识有局限性,认为DICOM只是一个简单的文件格式约定,简而言之,我当时认为DICOM协议就是扩展名为DCM文件的格式说明.其实不然,随着对医疗行业的深入,对DICOM协议也有了更全面的认识.而今才发现DCM文件只是DICOM协议一部分中的一小节,仅仅是整个协议中的一个数据结构,而DICOM协议更多的是关于医疗行业各种服务及相关流程的约定,因此其实DICOM协议中最主要的是信息流,是对医院

DICOM医学图像处理:Dcmtk与fo-dicom保存文件的不同设计模式之“同步VS异步”+“单线程VS多线程”

一.背景: 最近一直在做DCM相关的编程工作,以前项目使用C++居多,所以使用DCMTK开源库,而目前团队使用C#居多,所以需要转向使用fo-dicom库,由于前一篇专栏文章DICOM医学图像处理:利用fo-dicom发送C-Find查询Worklist在调试过程中需要对DIMSE信息进行手动保存,偶然间发现了dcmtk开源库与fo-dicom开源库在保存dcm文件时使用的方式差异很大,因此决定研究一下,期望通过对比分析来看一下孰优孰劣. 二.dcmtk与fo-dicom保存文件函数的源码剖析:

DICOM医学图像处理:DCMTK在VS2012中的配置

背景: 最近由于项目需要,将原本的开发IDE环境由VS2008升级到了VS2012.本以为编译完成后的DCMTK开源库可以直接从VS2008移植到VS2012.但是通过项目属性添加完包含目录和依赖库后,编译会出现大量的链接错误(大多是跟dcmdata.lib.oflog.lib有关). 解决方法: 重新按照原本的博客前辈柳北风儿(大神目前已经博客转移到网易:http://blog.163.com/[email protected]/),利用CMake工具,选择VS2012本地编译器对DCMTK3

DICOM医学图像处理:开源库mDCM与DCMTK的比较分析(一),JPEG无损压缩DCM图像(续)

背景: 上周通过单步调试,找出了开源库mDCM与DCMTK在对DICOM图像进行JPEG无损压缩时的细小区别,并顺利实现了在C++和C#环境下对DICOM图像的压缩.但是问题接踵而至啊,随着项目的深入,发现在单独的测试工程中可以实现的mDCM版本,在嵌入到项目整体中后,却意外地出现了错误,并未顺利实现DICOM图像的JPEG无损压缩.因此需要继续详细对比分析mDCM与DCMTK两者,期望寻找原因. 问题分析: 开启项目的日志功能后,得到的信息反馈为: No registered codec fo

C++读取和存储一幅BMP图像

C++读取和存储一幅BMP图像 转载请注明出处 之前写过一篇介绍BMP格式的文章,这是C++处理数字图像的基础,只有知道了BMP文件在计算机里的存储结构,才能谈之后的图像处理.这里是连接地址:http://blog.csdn.net/carvin_zh/article/details/45875277 C++源码: #include<iostream> #include<Windows.h> #include<malloc.h> #include<stdlib.h