You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用GDCM逐切片写入体素数据,降低内存占用

GDCM 大体积DICOM逐切片写入方案

现有方案的问题

我查阅的所有GDCM写入图像数据的示例,均将图像体视作单一连续的buffer处理,基础代码结构如下:

#include "gdcmImage.h"
#include "gdcmImageWriter.h"
#include "gdcmFileDerivation.h"
#include "gdcmUIDGenerator.h"

int write_image(...)
{
  size_t width = ..., height = ..., depth = ...;
  auto im = new gdcm::Image;

  std::vector<...> buffer;
  auto p = buffer.data();

  im->SetNumberOfDimensions(3);
  im->SetDimension(0, width);
  im->SetDimension(1, height);
  im->SetDimension(1, depth);

  im->GetPixelFormat().SetSamplesPerPixel(...);
  im->SetPhotometricInterpretation( gdcm::PhotometricInterpretation::... );

  unsigned long l = im->GetBufferLength();
  if( l != width * height * depth * sizeof(...) ){ return SOME_ERROR; }
  gdcm::DataElement pixeldata( gdcm::Tag(0x7fe0,0x0010) );
  pixeldata.SetByteValue( buffer.data(), buffer.size()*sizeof(*buffer.data()) );
  im->SetDataElement( pixeldata );

  gdcm::UIDGenerator uid;
  auto file = new gdcm::File;

  gdcm::FileDerivation fd;
  const char UID[] = ...;
  fd.AddReference( ReferencedSOPClassUID, uid.Generate() );
  fd.SetFile( *file );
  // If all Code Value are ok the filter will execute properly
  if( !fd.Derive() ){ return SOME_ERROR; }

  gdcm::ImageWriter w;
  w.SetImage( *im );
  w.SetFile( fd.GetFile() );

  // Set the filename:
  w.SetFileName( "some_image.dcm" );
  if( !w.Write() ){ return SOME_ERROR; }

  return 0;
}

该方案的核心缺陷是会产生全量像素数据拷贝,对于4096×4096×2048尺寸、单个体素12位的体数据(总内存占用约48GiB)存在两个明显的不适用场景:

  • 成像系统生成的数据并非以连续单一内存块的形式存储,而是按切片拆分
  • 总数据量仅能刚好适配所用系统的内存容量,没有额外空间存放拷贝数据

逐切片写入解决方案

GDCM提供了gdcm::StreamImageWriter类支持分块写入像素数据,无需加载全量体数据到内存,仅需要单切片的内存开销即可完成写入,核心实现如下:

实现步骤

  1. 配置图像元信息(尺寸、像素格式、光度解释等),无需关联全量像素buffer
  2. 初始化StreamImageWriter实例,绑定输出文件、传入配置好的图像元信息
  3. 按切片顺序依次读取单帧数据,调用写入接口逐块写入

示例代码

#include "gdcmImage.h"
#include "gdcmStreamImageWriter.h"
#include "gdcmFileDerivation.h"
#include "gdcmUIDGenerator.h"

int write_large_volume(...)
{
  const size_t width = 4096, height = 4096, depth = 2048;
  const size_t bytes_per_pixel = 2; // 12位像素按16位对齐存储
  // 仅分配单切片buffer,内存开销仅约32MB
  std::vector<uint16_t> slice_buf(width * height);

  // 配置图像元信息
  gdcm::Image im;
  im.SetNumberOfDimensions(3);
  im.SetDimension(0, width);
  im.SetDimension(1, height);
  im.SetDimension(2, depth);
  im.GetPixelFormat().SetSamplesPerPixel(1);
  im.GetPixelFormat().SetBitsAllocated(16);
  im.GetPixelFormat().SetBitsStored(12);
  im.GetPixelFormat().SetHighBit(11);
  im.SetPhotometricInterpretation(gdcm::PhotometricInterpretation::MONOCHROME2);

  // 生成DICOM文件派生信息
  gdcm::UIDGenerator uid;
  gdcm::File file;
  gdcm::FileDerivation fd;
  const char ReferencedSOPClassUID[] = "1.2.840.10008.5.1.4.1.1.128"; // 可根据实际模态替换对应SOP类UID
  fd.AddReference(ReferencedSOPClassUID, uid.Generate());
  fd.SetFile(file);
  if (!fd.Derive()) {
    return -1;
  }

  // 初始化流写入器
  gdcm::StreamImageWriter writer;
  writer.SetFileName("large_volume.dcm");
  writer.SetFile(fd.GetFile());
  // 定义单次写入块大小为一整个切片
  if (!writer.DefinePixelBuffer(im, height * width * bytes_per_pixel)) {
    return -2;
  }

  // 逐切片写入
  for (size_t z = 0; z < depth; ++z) {
    // 替换为自身的切片读取逻辑,将第z帧数据写入slice_buf
    your_slice_read_impl(z, slice_buf.data(), slice_buf.size());
    if (!writer.Write(slice_buf.data(), slice_buf.size() * bytes_per_pixel)) {
      return -3;
    }
  }

  return 0;
}

注意点

  • 原有示例代码中第三个维度的SetDimension参数错误,维度索引从0开始,第三个维度的索引为2,不要重复填写1
  • 如果使用12位紧缩存储格式(两个相邻像素占3字节),仅需要调整像素格式配置和bytes_per_pixel参数,写入逻辑无需改动
  • 流写入器要求像素数据按DICOM标准顺序传入,即从上到下、从左到右、按切片顺序依次写入,不要调整传入顺序

内容的提问来源于stack exchange,提问作者datenwolf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.17 17:45:05