标签:dcmtk dicom 图像处理 jpeg multi-frame
续上篇,继续介绍如何将多幅JPG图像数据存入DCM文件。即将有损压缩数据直接写入DCM文件,存储为Multi-frame形式。
为了避免引起歧义,这里着重说明一下。本博文的描述的场景是:假设我们手中有多张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)却可以看到正常的结果。如下图所示:
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之间的对应关系是“一对多”。
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服务端
时间:2014-12-27
DICOM医学图像处理:DICOM存储操作之 “多幅JPG图像数据存入DCM文件”
标签:dcmtk dicom 图像处理 jpeg multi-frame
原文地址:http://blog.csdn.net/zssureqh/article/details/42200303