DICOM笔记-使用DCMTK库的DcmOutputBufferStream类将DICOM信息序列化到内存中

一般情况下,我们都是使用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

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
DCMtk是一个开源的DICOM工具包,可以用来读取和处理DICOM图像。下面是使用DCMtk读取DICOM序列的步骤: 1. 安装DCMtk工具包 可以从DCMtk官网上下载DCMtk工具包,并按照官方文档进行安装。 2. 打开命令行窗口 在Windows系统,可以按下“Win+R”键组合打开运行窗口,输入“cmd”并回车,打开命令行窗口。 3. 进入DICOM序列所在的目录 使用“cd”命令进入DICOM序列所在的目录。例如,如果DICOM序列存储在“C:\DICOM\MyDICOM”目录下,可以使用以下命令进入该目录: cd C:\DICOM\MyDICOM 4. 使用DCMtk命令行工具dcmsort排序DICOM序列 dcmsort命令可以对DICOM序列进行排序,将序列的文件按照时间顺序排列。可以使用以下命令对DICOM序列进行排序: dcmsort * 5. 使用DCMtk命令行工具dcmdump读取DICOM序列 dcmdump命令可以读取DICOM序列的文件,并将文件内容输出到命令行窗口。可以使用以下命令读取DICOM序列的第一个文件: dcmdump -M +sd C:\DICOM\MyDICOM\0001.dcm,“-M +sd”参数表示输出文件的元数据信息和像素数据,文件路径“C:\DICOM\MyDICOM\0001.dcm”是需要读取的DICOM文件路径。 6. 使用DCMtk命令行工具dcmdjpeg将DICOM图像转换为JPEG格式 dcmdjpeg命令可以将DICOM图像转换为JPEG格式,并保存到指定路径。可以使用以下命令将DICOM序列的第一个文件转换为JPEG格式: dcmdjpeg C:\DICOM\MyDICOM\0001.dcm C:\DICOM\MyDICOM\0001.jpg 其,“C:\DICOM\MyDICOM\0001.dcm”是需要转换的DICOM文件路径,“C:\DICOM\MyDICOM\0001.jpg”是转换后的JPEG文件路径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黑山老妖的笔记本

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值