如何使用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类支持分块写入像素数据,无需加载全量体数据到内存,仅需要单切片的内存开销即可完成写入,核心实现如下:
实现步骤
- 配置图像元信息(尺寸、像素格式、光度解释等),无需关联全量像素buffer
- 初始化
StreamImageWriter实例,绑定输出文件、传入配置好的图像元信息 - 按切片顺序依次读取单帧数据,调用写入接口逐块写入
示例代码
#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
相关产品推荐
相关产品推荐

