DICOM醫學影像處理:DICOM儲存操作之 “多幅JPG映像資料存入DCM檔案”,dicomdcm
背景:
續上篇,繼續介紹如何將多幅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服務端
作者:zssure@163.com
時間:2014-12-27