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

背景:

续上篇,继续介绍如何将多幅JPG图像数据存入DCM文件。即将有损压缩数据直接写入DCM文件,存储为Multi-frame形式。

多幅JPG图像数据存入DCM文件:

为了避免引起歧义,这里着重说明一下。本博文的描述的场景是:假设我们手中有多张JPG文件,想把JPG文件写入DCM文件,即单个DCM文件包含多幅图像信息的Multi-Frame形式。该问题之前与CSDN博友y317215133y也讨论过,当时我在OFFIS论坛中找到了一个帖子直接给了y317215133y答复。今天重新梳理了一下发现,当时帖子中的情况与我今天要描述的问题略有不同:帖子中作者已经拥有多张图像的原始数据(从作者的描述来看,该数据是非压缩的),希望将该系列数据以压缩形式写入DCM文件中。想必作者执行该操作的目的是减少存储空间,而本博文中我拥有的是JPEG压缩的数据,也就是说我不是为了减少存储空间,而单纯的就是希望将多幅JPEG格式的图像存成Multi-frame
DCM格式,便于归档管理。

帖子中OFFIS DICOM Team人员给出的答复是:1)创建DcmFileFormat对象,利用getDataset()获得其中的数据体指针;2)利用putAndInsertXXX向1)中的Dataset写入非压缩的原始图像数据,即上一篇博文DICOM医学图像处理:DICOM存储操作之“多幅BMP图像数据存入DCM文件”所采用的方法;3)注册JPEG编码参数,例如DJ_PRLossless、DJ_RPLossy等,然后调用chooseRepresentation函数。该部分操作就是对DCM文件进行JPEG有损或无损压缩,具体过程可参照dcmcjpeg.cc中的代码;4)调用saveFile函数将编码后的数据写入Multi-fram
DCM文件。

以上四步操作并未使用DcmPixelSequence类,帖子作者以及博友y317215133y在这种场景下却希望使用DcmPixelSequence学习一下SQ字段的写入操作,其实是选择场景错误才导致错误使用DcmPixelSequence类。帖子最后作者也给出了提示,如下图:

上述正是本文要做的事情,希望通过该实例来讲解DcmPixelSequence类的使用,并进一步学习JPEG压缩的Multi-frame DCM文件。

代码实例:

参照OFFIS论坛中的代码http://forum.dcmtk.org/viewtopic.php?t=1544&highlight=creating+multiframe+dicom+images,直接给出源码:

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

#include "stdafx.h"
#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmdata/dctk.h"
#include "dcmtk/dcmdata/dcistrmf.h"
#include "dcmtk/dcmdata/dcpixel.h"
#include "dcmtk/dcmdata/dcpixseq.h"
#include "dcmtk/dcmdata/dcpxitem.h"
/*----BMP图像解析----*/
#include "dcmtk/dcmdata/libi2d/i2dbmps.h"
#include "DicomUtils.h"
/*----JPEG图像解析----*/
#include "dcmtk/dcmdata/libi2d/i2djpgs.h"
#include "dcmtk/dcmdata/libi2d/i2doutpl.h"
#include "dcmtk/dcmdata/dcerror.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 curDir[255];
	getcwd(curDir,255);
	DcmPixelSequence *seq=new DcmPixelSequence(DcmTag(DCM_PixelData,EVR_OB));
	/*!------zssure:begin,添加一个空的Dicom Pixel Item充当Offset Fragment------!*/
	//seq->insert(new DcmPixelItem(DcmTag(DCM_Item,EVR_OB)));
	/*-------zssure:end,可顺利解决多幅JPEG存入DCM的问题-------------------------*/
	//循环添加4张图片
	for(int i=0;i<4;++i)
	{
		OFString num;
		char numtmp[255];
		memset(numtmp,0,sizeof(char)*255);
		sprintf(numtmp,"%s\\jpeg-test\\%d.jpg",curDir,i+1);
		OFString filename=OFString(numtmp);
		I2DJpegSource* bmpSource=new I2DJpegSource();
		bmpSource->setImageFile(filename);

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

		DcmPixelItem *newItem=new DcmPixelItem(DcmTag(DCM_Item,EVR_OB));
		if(newItem!=NULL)
		{
			seq->insert(newItem);
			OFCondition result=newItem->putUint8Array((Uint8*)pixData,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->putAndInsertOFStringArray(DCM_PhotometricInterpretation,photoMetrInt);
	mydatasete->insert(seq,OFFalse,OFFalse);
	status=fileformat.saveFile("c:\\MultiJpeg2Multi-frameDCMtest-error.dcm",ts);
	if(status.bad())
	{
		std::cout<<"Error:("<<status.text()<<")\n";
	}
	return 0;
}

PS:DicomUtils类是DICOM文件操作静态类,具体见后续工程源码。

上述代码可以顺利生成Multi-frame DCM文件,从文件大小来看结果也应该正常。但是打开时却提示“内存无法读取错误”,如下图:

但是比较奇怪的是,利用dcmdump.exe工具和Sante DICOM Editor的预览窗口(Enable Icons)却可以看到正常的结果。如下图所示:

错误分析:

DICOM标准中的JPEG压缩

DICOM3.0标准第5部分附录A中给出了协议中常见的JPEG压缩格式,如下图:

常见的JPEG图像采用的就是1.2.840.10008.1.2.4.50,本博文中给出的四副测试图像就是这种格式。至于JPEG具体的压缩和编码流程可参考wiki百科http://zh.wikipedia.org/zh-cn/JPEG

标准中指出,如果DICOM文件时Multi-frame类型,每幅图像(frame)需要分别压缩(encoded seperately)。压缩数据在写入DICOM中的DcmPixelData字段时可能会被分片(fragment),切记:每个片段(fragment)中的数据一定来自同一文件(即frame),而每幅图像(frame)不一定存储在同一个片段(fragment),因此frame与fragment之间的对应关系是“一对多”

DcmPixelData字段(7FE0,0010):

DICOM3.0标准第5部分第8章指出,如果数据以压缩形式存储,那么PixelData的VR只能采用OB(原始数据存储通常采用OW,如果数据存储位数小于等于8也可以采用OB形式,正如我的上一篇博文。压缩数据会被分割为包含自身长度的多个片段(fragments),最终以截止符(FFFE,E0DD)结束。如下图所示:

一幅图像(frame)可以包含在一个片段(fragment)中,也可以被分割为多个片段。使用时可通过比较【字段NumberOfFrames(0028,0008)】与【字段PixelData的Item个数-1】来判别,

NumberOfFrames==ItemsOfPixelData-1,表明每幅图像都包含在一个片段里(fragment);

NumberOfFrames<ItemsOfPixelData-1,表明有图像被分为多个片段存储;

解决方案:

注意,上面需要对PixelData字段的Items数【减去1】,如表A.4-1、A.4-2所示,无论如何PixelData字段中都会包含一个Offset item。——这正是我们上述代码错误的原因,为了证明这一点,让我们在插入各幅图像之前添加一个空的DcmPixelItem,即在for循环之前添加如下两行代码:

	/*!------zssure:begin,添加一个空的Dicom Pixel Item充当Offset Fragment------!*/
	seq->insert(new DcmPixelItem(DcmTag(DCM_Item,EVR_OB)));
	/*-------zssure:end,可顺利解决多幅JPEG存入DCM的问题-------------------------*/

此刻用DICOM浏览器可以顺利打开我们生成的MultiJPEG2DCMtest.dcm,如下图所示:

利用二进制查看器可以看到,PixelData字段多了一个SQ Item,即充当Offset的空的DcmPixelItem,如下图所示:

另外从最终文件大小可以看出,这种方式并未减少存储空间。

备注:

DICOM3.0标准中给出了DCM数据的压缩方法和存储方式,至于压缩数据(有损压缩,例如本例中采用的1.2.840.10008.1.2.4.50)在临床是否有应用价值不属于协议考虑范围。

PS:今天偶然回了趟学校,发现原来一年一度的考研提前了,看到大家在寒冷的天气里在考场外辛苦的候考,真心祝愿大家能够考入自己理想中的学校,(^ω^)。

后续博文介绍:

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

作者:[email protected]

时间:2014-12-27

时间: 2024-10-03 13:45:21

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

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

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

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

DICOM医学图像处理:DIMSE消息发送与接收“大同小异”之DCMTK fo-dicom mDCM

背景: 从DICOM网络传输一文开始,相继介绍了C-ECHO.C-FIND.C-STORE.C-MOVE等DIMSE-C服务的简单实现,博文中的代码给出的实例都是基于fo-dicom库来实现的,原因只有一个:基于C#的fo-dicom库具有高封装性.对于初学者来说实现大多数的DIMSE-C.DIMSE-N服务几乎都是"傻瓜式"操作--构造C-XXX-RQ.N-XXX-RQ然后绑定相应的OnResponseReceived处理函数即可.本博文希望在前几篇预热的基础上,对比DCMTK.fo