SimpleITK——创建nrrd体素模型
在介绍如何生成nrrd前,了解一下为什么医学影像上一般使用nrrd的体素模型?
为什么医学影像上一般使用nrrd的体素模型?
在医学影像领域,NRRD(Nearly Raw Raster Data)格式被广泛用于存储体素模型(如CT、MRI数据),主要基于以下技术优势:
1. 灵活的数据存储方式
- 支持原始数据无损存储
NRRD可直接存储未经压缩的体素数据(如16位整型、32位浮点),避免DICOM等格式的二次编码损失,适合科研和精准医疗。 - 多维数据兼容性
可处理3D体素(CT/MRI)、4D时序数据(如fMRI、心脏动态扫描)甚至更高维数据(如DTI纤维追踪)。
2. 强大的元数据支持
- 关键医学参数嵌入
通过文本头文件(Header)记录:- 体素间距(
spacings:如0.5mm×0.5mm×1.0mm) - 空间方向矩阵(
space directions,用于对齐DICOM坐标系) - 患者体位标识(如LPS/RAS坐标系)
- 体素间距(
- 可扩展性
支持自定义字段(如扫描协议、厂商参数),比DICOM更轻量但足够专业。
3. 跨平台与开源生态
- 无专利限制
相比DICOM(需授权),NRRD为开源格式,兼容ITK、VTK、3D Slicer等主流医学软件。 - 简化预处理流程
直接存储已重建的体素矩阵,省去DICOM多层文件合并的步骤,加速AI模型训练(如nnUNet直接读取NRRD)。
4. 与其他格式的对比
| 特性 | NRRD | DICOM | NIfTI |
|---|---|---|---|
| 数据维度 | 支持任意维度 | 单层2D为主 | 3D/4D |
| 元数据灵活性 | 高(自定义字段) | 高(但结构复杂) | 中(固定字段) |
| 坐标系支持 | 明确空间方向 | 需计算 | 固定(RAS/LPS) |
| 适用场景 | 科研/算法开发 | 临床归档 | 神经影像(fMRI) |
典型应用场景
- 放射治疗规划:NRRD存储的CT体素数据可直接用于剂量计算(如RayStation)。
- 深度学习:MONAI等框架默认支持NRRD,便于加载标注数据(如
image.nrrd+label.nrrd)。 - 三维重建:通过VTK将NRRD转换为STL模型用于3D打印。
注意事项
- 临床兼容性:医院PACS系统通常仍以DICOM为主,NRRD多用于科研后端。
- 压缩选项:NRRD支持GZIP压缩(如
*.nhdr+*.raw.gz),但会牺牲部分读写速度。
以上是愚通过ds了解到的一些情况,仅供参考。
代码实现
以下为调用SimpleITK实现将Dicom文件转化为Nrrd体素模型的示例:
public class DicomToNRRDHelper { /// <summary> /// DICOM序列转化为NRRD文件 /// </summary> /// <param name="dicomDirectory">dicom文件路径</param> /// <param name="outPutFilename">包含路径和后缀名,且后缀名必须为.NRRD(因为需要以体素渲染)</param> /// <returns>失败为false;成功为true</returns> [MethodImpl(MethodImplOptions.Synchronized)] public static bool DicomToNRRD(string dicomDirectory, string outPutFilename) { // 输入验证 if (!Directory.Exists(dicomDirectory)) { NlogHelper.Logger.Error($"Directory not found: {dicomDirectory}"); return false; } if (!outPutFilename.EndsWith(".nrrd", StringComparison.OrdinalIgnoreCase)) { NlogHelper.Logger.Error("Output file must have .nrrd extension"); return false; } Image image3D = null; try { var seriesIDs = ImageSeriesReader.GetGDCMSeriesIDs(dicomDirectory); if (seriesIDs == null || seriesIDs.Length == 0) { NlogHelper.Logger.Error($"No DICOM series found in: {dicomDirectory}"); return false; } var seriesFileNames = ImageSeriesReader.GetGDCMSeriesFileNames(dicomDirectory, seriesIDs[0]); NlogHelper.Logger.Debug($"Processing {seriesFileNames.Count} DICOM files"); if (seriesFileNames.Count > 1) { using var reader = new ImageSeriesReader(); reader.SetFileNames(seriesFileNames); image3D = reader.Execute(); } else if (seriesFileNames.Count == 1) { using var reader = new ImageFileReader(); reader.SetFileName(seriesFileNames[0]); image3D = reader.Execute(); } else { return false; } // 保留原始像素类型 using var filter = new CastImageFilter(); // 保留原始像素类型选项 filter.SetOutputPixelType(image3D.GetPixelID()); using var convertedImage = filter.Execute(image3D); using (var writer = new ImageFileWriter()) { writer.SetFileName(outPutFilename); writer.Execute(convertedImage); } NlogHelper.Logger.Info($"Successfully created: {outPutFilename}"); return true; } catch (Exception ex) { NlogHelper.Logger.Error(ex, $"Failed to convert DICOM to NRRD"); return false; } finally { image3D?.Dispose(); } } }
注意事项
- SimpleITK中涉及到image、reader、writer最好都显示释放一下或 使用using语句,以达到使用完及时回收这些非托管资源。尽量不要依赖于它们在终结器中通过调用dispose的相关方法来实现资源回收,这个在实际项目中有过惨痛教训(资源不能及时回收,导致非托管对象无限增长,最终导致程序崩溃)。
- 对于非托管对象的释放,特别是在try catch语句中,若异常时容易忽略,尽量在finally中处理一下。
- 日志要尽量完善,如文件的长度检测;文件路径的检测;文件名的规范性检测等待
以上需要在实际开发中引起注意。
*****有道无术,术尚可求;有术无道,止于术。*****

浙公网安备 33010602011771号