一般情况下,我们都是使用DCMTK库来读写DICOM文件,例如使用下面代码保存DICOM文件;
DcmFileFormat* nm_dcm_format = new DcmFileFormat;
OFCondition cond = nm_dcm_format->loadFile(nm_file.c_str());
cond = nm_dcm_format->saveFile(mult_pet_file.c_str());
主要使用了DcmFileFormat
类的saveFile
接口。
DcmFileFormat类的saveFile接口
/** save object to a DICOM file.
* @param fileName name of the file to save (may contain wide chars if support enabled).
* Since there are various constructors for the OFFilename class, a "char *", "OFString"
* or "wchar_t *" can also be passed directly to this parameter.
* @param writeXfer transfer syntax used to write the data (EXS_Unknown means use original)
* @param encodingType flag, specifying the encoding with undefined or explicit length
* @param groupLength flag, specifying how to handle the group length tags
* @param padEncoding flag, specifying how to handle the padding tags
* @param padLength number of bytes used for the dataset padding (has to be an even number)
* @param subPadLength number of bytes used for the item padding (has to be an even number)
* @param writeMode write file with or without meta header. Also allows for updating the
* information in the file meta information header. The default behavior is to delete
* all old meta information in order to create it from scratch.
*
* @return status, EC_Normal if successful, an error code otherwise
*/
virtual OFCondition saveFile(const OFFilename &fileName,
const E_TransferSyntax writeXfer = EXS_Unknown,
const E_EncodingType encodingType = EET_UndefinedLength,
const E_GrpLenEncoding groupLength = EGL_recalcGL,
const E_PaddingEncoding padEncoding = EPD_noChange,
const Uint32 padLength = 0,
const Uint32 subPadLength = 0,
const E_FileWriteMode writeMode = EWM_createNewMeta);
从上面的接口中得知,除了第一个参数是必需的外,其他参数都有默认值,可选项;
DcmFileFormat
类除了saveFile
接口,还提供了其他保存接口;
virtual OFCondition writeXML(STD_NAMESPACE ostream &out, const size_t flags = 0);
virtual OFCondition writeJson(STD_NAMESPACE ostream &out, DcmJsonFormat &format);
virtual OFCondition write(DcmOutputStream &outStream,
const E_TransferSyntax oxfer,
const E_EncodingType enctype,
DcmWriteCache *wcache,
const E_GrpLenEncoding glenc,
const E_PaddingEncoding padenc = EPD_noChange,
const Uint32 padlen = 0,
const Uint32 subPadlen = 0,
Uint32 instanceLength = 0,
const E_FileWriteMode writeMode = EWM_createNewMeta);
virtual OFCondition write(DcmOutputStream &outStream,
const E_TransferSyntax oxfer,
const E_EncodingType enctype,
DcmWriteCache *wcache);
DcmFileFormat类的wrtie接口
wrtie
接口就是将DICOM信息写入到输出流DcmOutputStream
对象中,将DcmOutputStream
对象的内存指针保存到磁盘上,就是一个可以使用的DICOM文件;
saveFile
接口就是使用了wrtie
接口;
OFCondition DcmFileFormat::saveFile(const OFFilename &fileName,
const E_TransferSyntax writeXfer,
const E_EncodingType encodingType,
const E_GrpLenEncoding groupLength,
const E_PaddingEncoding padEncoding,
const Uint32 padLength,
const Uint32 subPadLength,
const E_FileWriteMode writeMode)
{
if (writeMode == EWM_dataset)
{
return getDataset()->saveFile(fileName, writeXfer, encodingType, groupLength,
padEncoding, padLength, subPadLength);
}
OFCondition l_error = EC_InvalidFilename;
/* check parameters first */
if (!fileName.isEmpty())
{
DcmWriteCache wcache;
/* open file for output */
DcmOutputFileStream fileStream(fileName);
/* check stream status */
l_error = fileStream.status();
if (l_error.good())
{
/* write data to file */
transferInit();
l_error = write(fileStream, writeXfer, encodingType, &wcache, groupLength,
padEncoding, padLength, subPadLength, 0 /*instanceLength*/, writeMode);
transferEnd();
}
}
return l_error;
}
DcmFileFormat类
DcmFileFormat
类的saveFile
接口中使用的是DcmOutputFileStream
类对象,用于向一个磁盘文件中写入DICOM文件内容;
DcmOutputStream
类是一个抽象类,不可以被实例化,是一个接口类;
从DcmOutputStream
类中,派生出了DcmOutputBufferStream
类和DcmOutputFileStream
类;
下面是DcmOutputStream
类提供的对外接口;
virtual ~DcmOutputStream ();
virtual OFBool good() const;
virtual OFCondition status() const;
virtual OFBool isFlushed() const;
virtual offile_off_t avail() const;
virtual offile_off_t write(const void *buf, offile_off_t buflen);
virtual void flush ();
virtual offile_off_t tell () const;
virtual OFCondition installCompressionFilter(E_StreamCompression filterType);
DcmOutputBufferStream类
DcmOutputBufferStream类用于将DICOM信息写入调用者给定的固定长度缓冲区的输出流。
DcmOutputBufferStream类的接口如下:
class DCMTK_DCMDATA_EXPORT DcmOutputBufferStream: public DcmOutputStream
{
public:
// buf必须是非空指针,buflen必须大于0。
DcmOutputBufferStream(void *buf, offile_off_t bufLen);
virtual ~DcmOutputBufferStream();
virtual void flushBuffer(void *& buffer, offile_off_t& length);
virtual offile_off_t filled();
private:
DcmOutputBufferStream(const DcmOutputBufferStream&);
DcmOutputBufferStream& operator=(const DcmOutputBufferStream&);
DcmBufferConsumer consumer_;
};
DcmOutputBufferStream
类内部持有了一个DcmBufferConsumer
类对象,用于对内存块的写操作;
DcmOutputFileStream类
DcmOutputFileStream
类写入普通文件的输出流;
class DCMTK_DCMDATA_EXPORT DcmOutputFileStream: public DcmOutputStream
{
public:
DcmOutputFileStream(const OFFilename &filename);
DcmOutputFileStream(FILE *file);
virtual ~DcmOutputFileStream();
private:
DcmOutputFileStream(const DcmOutputFileStream&);
DcmOutputFileStream& operator=(const DcmOutputFileStream&);
DcmFileConsumer consumer_;
};
与DcmOutputBufferStream
类不同之处在于,DcmOutputFileStream
类持有的是一个DcmFileConsumer
类对象;
将DICOM信息写入内存中
从Stack Overflow上看到一个提问DCMTK: Write DICOM file to memory,有人回答Orthanc中的Orthanc::FromDcmtkBridge::SaveToMemoryBuffer()
可以实现这个功能;
下面是Orthanc::FromDcmtkBridge::SaveToMemoryBuffer()
的代码;
bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, DcmDataset& dataSet)
{
// Determine the transfer syntax which shall be used to write the
// information to the file. We always switch to the Little Endian
// syntax, with explicit length.
// http://support.dcmtk.org/docs/dcxfer_8h-source.html
/**
* Note that up to Orthanc 0.7.1 (inclusive), the
* "EXS_LittleEndianExplicit" was always used to save the DICOM
* dataset into memory. We now keep the original transfer syntax
* (if available).
**/
E_TransferSyntax xfer = dataSet.getOriginalXfer();
if (xfer == EXS_Unknown)
{
// No information about the original transfer syntax: This is
// most probably a DICOM dataset that was read from memory.
xfer = EXS_LittleEndianExplicit;
}
E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength;
// Create the meta-header information
DcmFileFormat ff(&dataSet);
ff.validateMetaInfo(xfer);
ff.removeInvalidGroups();
// Create a memory buffer with the proper size
{
const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType); // (*)
buffer.resize(estimatedSize);
}
DcmOutputBufferStream ob(&buffer[0], buffer.size());
// Fill the memory buffer with the meta-header and the dataset
ff.transferInit();
OFCondition c = ff.write(ob, xfer, encodingType, NULL,
/*opt_groupLength*/ EGL_recalcGL,
/*opt_paddingType*/ EPD_withoutPadding);
ff.transferEnd();
if (c.good())
{
// The DICOM file is successfully written, truncate the target
// buffer if its size was overestimated by (*)
ob.flush();
size_t effectiveSize = static_cast<size_t>(ob.tell());
if (effectiveSize < buffer.size())
{
buffer.resize(effectiveSize);
}
return true;
}
else
{
// Error
buffer.clear();
return false;
}
}
仿照的例子
参照这个例子,我写了一个demo;读取一个JPEG无损压缩的DICOM文件,然后保存到内存中的过程;
#include "dcmtk\config\osconfig.h"
#include "dcmtk\dcmdata\dctk.h"
#include "dcmtk\dcmdata\dcostrmb.h"
#include "dcmtk\dcmdata\dcwcache.h"
#include <dcmtk/dcmjpeg/djdecode.h> /* for dcmjpeg decoders */
#include <dcmtk/dcmjpeg/djencode.h>
#include <dcmtk/dcmjpls/djdecode.h> //for JPEG-LS decode
#include <dcmtk/dcmjpls/djencode.h> //for JPEG-LS encode
using namespace std;
int main()
{
DJDecoderRegistration::registerCodecs();
DJLSEncoderRegistration::registerCodecs(); //JPEG-LS encoder registerCodecs
DJLSDecoderRegistration::registerCodecs(); //JPEG-LS decoder registerCodecs
DcmFileFormat *myFileFormat = new DcmFileFormat;
OFCondition cond = myFileFormat->loadFile("G:\\Data\\1.dcm");
E_TransferSyntax xfer = myFileFormat->getDataset()->getOriginalXfer();
if (xfer == EXS_Unknown)
{
// No information about the original transfer syntax: This is
// most probably a DICOM dataset that was read from memory.
xfer = EXS_LittleEndianExplicit;
}
E_EncodingType encodingType = EET_ExplicitLength;
DcmFileFormat ff(myFileFormat->getDataset());
ff.validateMetaInfo(xfer);
ff.removeInvalidGroups();
string buffer;
const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType); // (*)
buffer.resize(estimatedSize);
DcmOutputBufferStream ob(&buffer[0], buffer.size());
ff.transferInit();
OFCondition c = ff.write(ob, xfer, encodingType, NULL,
/*opt_groupLength*/ EGL_recalcGL,
/*opt_paddingType*/ EPD_withoutPadding);
ff.transferEnd();
if (c.good())
{
// The DICOM file is successfully written, truncate the target
// buffer if its size was overestimated by (*)
ob.flush();
size_t effectiveSize = static_cast<size_t>(ob.tell());
if (effectiveSize < buffer.size())
{
buffer.resize(effectiveSize);
}
FILE* pFile;
pFile = fopen("G:\\Data\\3.dcm", "wb");
fwrite(buffer.data(), 1, buffer.size(), pFile);
fclose(pFile);
return true;
}
else
{
// Error
buffer.clear();
return false;
}
}
仿照将一个16位灰度数组保存JPEG无损压缩的DICOM文件
其实就是一个正常保存DICOM文件的过程,只是在最后使用saveFile
的地方使用write
将二进制数组写入DICOM标签中
dset->putAndInsertUint16Array(DCM_PixelData, OFreinterpret_cast(Uint16*, pixData), length / 2);
给DICOM组装一些需要的tag信息:
DicomDataset *dataset_;
DcmFileFormat *dicom_file_;
std::string sopclassuid = "1.2.840.10008.5.1.4.1.1.2";
OFString str_sopinstanceuid;
DcmDataset *dataset = dicom_file_->getDataset();
dataset->findAndGetOFString(DCM_SOPInstanceUID, str_sopinstanceuid);
DcmMetaInfo *meta_info = dicom_file_->getMetaInfo();
meta_info_module_->SetMediaStorageSOPClassUID(sopclassuid);
meta_info_module_->SetMediaStorageSOPInstanceUID(str_sopinstanceuid.c_str());
meta_info_module_->Write(meta_info);
dataset->putAndInsertString(ToDcmTag(DCM_SOPClassUID), sopclassuid.c_str());
dataset->putAndInsertString(ToDcmTag(DCM_ConversionType), "SYN");
dataset->putAndInsertString(ToDcmTag(DCM_StationName), DW_STATION);
dataset->putAndInsertString(ToDcmTag(DCM_Manufacturer), DW_LOGO);
OFCondition state = EC_IllegalParameter;
// JPEG options
E_TransferSyntax opt_oxfer = EXS_JPEGProcess14SV1;
unsigned int opt_selection_value = 6;
unsigned int opt_point_transform = 0;
E_GrpLenEncoding opt_oglenc = EGL_recalcGL;
E_EncodingType opt_oenctype = EET_ExplicitLength;
E_PaddingEncoding opt_opadenc = EPD_noChange;
unsigned int opt_filepad = 0;
unsigned int opt_itempad = 0;
DcmXfer opt_oxferSyn(opt_oxfer);
// create representation parameters for lossy and lossless
DJ_RPLossless rp_lossless(OFstatic_cast(int, opt_selection_value), OFstatic_cast(int, opt_point_transform));
const DcmRepresentationParameter *rp = &rp_lossless;
dataset->chooseRepresentation(opt_oxfer, rp);
if (dataset->canWriteXfer(opt_oxfer)) {
E_TransferSyntax xfer = opt_oxfer;
E_EncodingType encodingType = EET_ExplicitLength;
DcmFileFormat ff(dataset);
ff.validateMetaInfo(xfer);
ff.removeInvalidGroups();
const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType);
buffer.resize(estimatedSize);
DcmOutputBufferStream ob(&buffer[0], buffer.size());
ff.transferInit();
OFCondition c = ff.write(ob, xfer, encodingType, NULL, EGL_recalcGL, EPD_noChange);
ff.transferEnd();
if (c.good())
{
ob.flush();
size_t effectiveSize = static_cast<size_t>(ob.tell());
if (effectiveSize < buffer.size())
{
buffer.resize(effectiveSize);
}
return 0;
}
else
{
buffer.clear();
return 1;
}
}
使用DJ_RPLossless给dataSet设置无损JPEG的参数;
DcmXfer opt_oxferSyn(opt_oxfer);
// create representation parameters for lossy and lossless
DJ_RPLossless rp_lossless(OFstatic_cast(int, opt_selection_value), OFstatic_cast(int, opt_point_transform));
const DcmRepresentationParameter *rp = &rp_lossless;
dataset->chooseRepresentation(opt_oxfer, rp);
参考资料
1.https://support.dcmtk.org/docs/annotated.html
2.https://support.dcmtk.org/docs/classDcmOutputStream.html
3.https://codesearch.isocpp.org/actcd19/main/o/orthanc/orthanc_1.5.6+dfsg-1/Core/DicomParsing/FromDcmtkBridge.cpp