nnUNet与3D Slicer集成:实现医学影像可视化与分割一体化

nnUNet与3D Slicer集成:实现医学影像可视化与分割一体化

【免费下载链接】nnUNet 【免费下载链接】nnUNet 项目地址: https://gitcode.com/gh_mirrors/nn/nnUNet

引言:医学影像分割的痛点与解决方案

你是否还在经历这样的 workflow:用 nnUNet 完成医学影像分割后,不得不将结果文件导出,再手动导入到 3D Slicer 中进行可视化和后续分析?这种繁琐的切换不仅浪费时间,还可能因格式转换错误导致结果偏差。本文将详细介绍如何将 nnUNet 与 3D Slicer 无缝集成,实现从自动分割到交互式可视化的全流程一体化,显著提升医学影像分析效率。

读完本文后,你将能够:

  • 理解 nnUNet 与 3D Slicer 集成的技术原理
  • 掌握使用 Python 脚本实现分割结果自动导入 3D Slicer 的方法
  • 学会在 3D Slicer 中对 nnUNet 分割结果进行高级可视化和后处理
  • 解决常见的格式兼容性和坐标系统一致性问题

技术背景:nnUNet 与 3D Slicer 简介

nnUNet(神经网络医学影像分割工具包)

nnUNet 是一个基于深度学习的医学影像分割框架,以其卓越的性能和易用性在医学影像分割领域广泛应用。它能够自动适应不同的数据集特征,选择最优的网络架构和训练参数,无需用户进行复杂的调参。

3D Slicer(医学影像可视化与分析平台)

3D Slicer 是一款开源的医学影像可视化、分割和registration软件。它提供了丰富的交互工具,支持多模态影像的融合显示、三维重建以及定量分析,是医学影像研究中不可或缺的工具。

集成的必要性

将 nnUNet 的自动分割能力与 3D Slicer 的交互式可视化分析功能相结合,可以实现:

  • 分割结果的即时可视化反馈
  • 基于可视化的分割结果修正
  • 定量分析与报告生成的自动化
  • 多模态影像与分割结果的融合显示

技术原理:数据格式与坐标系统

1. 数据格式兼容性

nnUNet 默认支持多种医学影像格式,通过 SimpleITK 库实现读写功能。以下是 nnUNet 支持的主要文件格式:

格式扩展名特点3D Slicer 支持度
NIfTI.nii.gz神经影像领域标准格式,支持3D和4D数据完全支持
NRRD.nrrd适合存储体数据,支持压缩完全支持
MHA.mhaMeta Image格式,头信息丰富完全支持
Gipl.gipl格拉斯哥影像处理库格式部分支持

nnUNet 的 SimpleITKIO 类实现了这些格式的读写功能,代码片段如下:

class SimpleITKIO(BaseReaderWriter):
    supported_file_endings = [
        '.nii.gz',
        '.nrrd',
        '.mha',
        '.gipl'
    ]
    
    def write_seg(self, seg: np.ndarray, output_fname: str, properties: dict) -> None:
        # 将numpy数组转换为SimpleITK图像
        itk_image = sitk.GetImageFromArray(seg.astype(np.uint8 if np.max(seg) < 255 else np.uint16, copy=False))
        # 设置图像的 spacing, origin 和 direction
        itk_image.SetSpacing(properties['sitk_stuff']['spacing'])
        itk_image.SetOrigin(properties['sitk_stuff']['origin'])
        itk_image.SetDirection(properties['sitk_stuff']['direction'])
        # 写入图像文件
        sitk.WriteImage(itk_image, output_fname, True)

2. 坐标系统一致性

医学影像分割结果在不同软件间传递时,坐标系统的一致性至关重要。nnUNet 和 3D Slicer 都使用 DICOM 坐标系统,但在实现上存在细微差异:

  • nnUNet:默认使用 LPS(Left-Posterior-Superior)坐标系
  • 3D Slicer:默认使用 RAS(Right-Anterior-Superior)坐标系

为解决坐标系统差异,nnUNet 提供了 SimpleITKIOWithReorient 类,支持在读写图像时进行坐标系转换:

class SimpleITKIOWithReorient(SimpleITKIO):
    def read_images(self, image_fnames, orientation = "RAS") -> Tuple[np.ndarray, dict]:
        # 读取图像
        itk_image = sitk.ReadImage(f)
        # 转换到目标坐标系
        itk_image = sitk.DICOMOrient(itk_image, orientation)
        # ... 后续处理 ...
        
    def write_seg(self, seg, output_fname, properties):
        # ... 前面的代码 ...
        # 转换回原始坐标系
        itk_image = sitk.DICOMOrient(itk_image, properties['sitk_stuff']['original_orientation'])
        sitk.WriteImage(itk_image, output_fname, True)

实现步骤:从分割到可视化的无缝衔接

步骤1:nnUNet 分割结果导出

nnUNet 提供了 export_prediction_from_logits 函数,用于将网络输出的 logits 转换为最终的分割结果并保存为医学影像格式。以下是导出分割结果的示例代码:

from nnunetv2.inference.export_prediction import export_prediction_from_logits

# 导出分割结果为 NIfTI 格式
export_prediction_from_logits(
    predicted_array_or_file=predicted_logits,
    properties_dict=properties,
    configuration_manager=config_manager,
    plans_manager=plans_manager,
    dataset_json_dict_or_file=dataset_json,
    output_file_truncated="/path/to/output/segmentation",
    save_probabilities=False
)

此代码会生成一个 .nii.gz 格式的分割结果文件,包含了必要的元数据(spacing、origin、direction),确保与原始图像的空间对齐。

步骤2:3D Slicer 扩展开发

3D Slicer 支持通过 Python 脚本扩展其功能。我们可以创建一个自定义模块,实现 nnUNet 分割结果的自动导入和可视化。

2.1 创建 Slicer 扩展结构
NNUNetExtension/
├── CMakeLists.txt
├── NNUNetExtension.py
├── NNUNetExtension.ui
└── Resources/
    └── Icons/
        └── icon.png
2.2 实现核心功能

以下是 NNUNetExtension.py 的核心代码,实现了分割结果的导入和可视化:

import os
import vtk
import numpy as np
import sitk
import sitkUtils
from slicer.ScriptedLoadableModule import *
from slicer.util import VTKObservationMixin

class NNUNetExtension(ScriptedLoadableModule):
    def __init__(self, parent):
        ScriptedLoadableModule.__init__(self, parent)
        self.parent.title = "NNUNet Integration"
        self.parent.categories = ["Segmentation"]
        self.parent.dependencies = []
        self.parent.contributors = ["Your Name"]
        self.parent.helpText = "This module integrates nnUNet segmentation results into 3D Slicer."
        self.parent.acknowledgementText = ""

class NNUNetExtensionWidget(ScriptedLoadableModuleWidget, VTKObservationMixin):
    def setup(self):
        ScriptedLoadableModuleWidget.setup(self)
        
        # 添加文件选择按钮
        self.inputFileSelector = qt.QPushButton("Select nnUNet Segmentation File")
        self.inputFileSelector.connect('clicked()', self.selectInputFile)
        self.layout.addWidget(self.inputFileSelector)
        
        # 添加加载按钮
        self.loadButton = qt.QPushButton("Load and Visualize")
        self.loadButton.connect('clicked()', self.loadAndVisualize)
        self.layout.addWidget(self.loadButton)
        
        self.inputFileName = None
    
    def selectInputFile(self):
        self.inputFileName = qt.QFileDialog.getOpenFileName(
            None, "Select nnUNet Segmentation File", "", "NIfTI Files (*.nii.gz);;All Files (*)"
        )
    
    def loadAndVisualize(self):
        if not self.inputFileName:
            qt.QMessageBox.warning(None, "Warning", "Please select a segmentation file first.")
            return
        
        # 使用 SimpleITK 读取分割结果
        sitk_image = sitk.ReadImage(self.inputFileName)
        
        # 将 SimpleITK 图像转换为 Slicer 图像节点
        imageNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", "NNUNet_Segmentation")
        sitkUtils.SetSlicerITKReadImageInformation(sitk_image, imageNode)
        sitkUtils.GetSlicerITKImageFromArray(sitk.GetArrayFromImage(sitk_image), imageNode)
        
        # 创建分割节点
        segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode", "NNUNet_Segmentation_Result")
        segmentationNode.CreateDefaultDisplayNodes()
        segmentationNode.SetReferenceImageGeometryParameterFromVolumeNode(imageNode)
        
        # 将标签图像转换为分割
        segConverter = vtkSlicerSegmentationsModuleLogic.vtkSlicerLabelMapToSegmentationConverter()
        segConverter.SetInputLabelMapVolumeNode(imageNode)
        segConverter.SetOutputSegmentationNode(segmentationNode)
        segConverter.SetOverwriteExistingSegments(True)
        segConverter.Update()
        
        # 设置显示属性
        displayNode = segmentationNode.GetDisplayNode()
        displayNode.SetVisibility(True)
        
        # 将分割结果添加到视图中
        slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpQuantitativeView)
        slicer.util.setSliceViewerLayers(background=imageNode)

步骤3:实现一键式工作流

为了进一步简化流程,我们可以创建一个 Python 脚本,实现从 nnUNet 分割到 3D Slicer 可视化的一键式操作:

#!/usr/bin/env python

import os
import subprocess
import tempfile
from nnunetv2.inference.predict_from_raw_data import predict_from_raw_data

def nnunet_segment_and_visualize(input_image_path, task_id, model_folder):
    # 创建临时目录存储分割结果
    with tempfile.TemporaryDirectory() as temp_dir:
        # 运行 nnUNet 分割
        predict_from_raw_data(
            input_folder=os.path.dirname(input_image_path),
            output_folder=temp_dir,
            model_folder=model_folder,
            task_id=task_id,
            fold=0,
            save_probabilities=False,
            num_processes_preprocessing=8,
            num_processes_segmentation_export=8,
            folder_with_segs_from_prev_stage=None,
            num_parts=1,
            part_id=0
        )
        
        # 获取分割结果文件路径
        seg_result_path = os.path.join(temp_dir, os.path.basename(input_image_path).replace('.nii.gz', '_seg.nii.gz'))
        
        # 使用 Slicer 打开分割结果
        slicer_cmd = [
            "slicer",
            "--python-code",
            f"nnunet_import_segmentation('{seg_result_path}')"
        ]
        
        # 启动 Slicer
        subprocess.Popen(slicer_cmd)

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description='Run nnUNet segmentation and visualize in 3D Slicer.')
    parser.add_argument('input_image', help='Path to input image file')
    parser.add_argument('task_id', type=int, help='nnUNet task ID')
    parser.add_argument('model_folder', help='Path to trained model folder')
    args = parser.parse_args()
    
    nnunet_segment_and_visualize(args.input_image, args.task_id, args.model_folder)

高级应用:自定义可视化与后处理

1. 多模态影像融合

3D Slicer 支持多模态影像的融合显示,可以将 nnUNet 的分割结果与原始影像(如 CT、MRI)叠加显示,便于结果验证和分析。

# 在 3D Slicer Python 控制台中运行以下代码

# 获取主窗口布局管理器
layoutManager = slicer.app.layoutManager()

# 设置 4-up 视图布局
layoutManager.setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView)

# 获取各个视图
redView = layoutManager.sliceWidget("Red")
yellowView = layoutManager.sliceWidget("Yellow")
greenView = layoutManager.sliceWidget("Green")
threeDView = layoutManager.threeDWidget(0)

# 设置背景图像(如 CT)
backgroundVolume = slicer.util.getNode("CT_Image")
redView.sliceLogic().GetSliceCompositeNode().SetBackgroundVolumeID(backgroundVolume.GetID())
yellowView.sliceLogic().GetSliceCompositeNode().SetBackgroundVolumeID(backgroundVolume.GetID())
greenView.sliceLogic().GetSliceCompositeNode().SetBackgroundVolumeID(backgroundVolume.GetID())

# 设置前景图像(nnUNet 分割结果)
foregroundVolume = slicer.util.getNode("NNUNet_Segmentation")
redView.sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(foregroundVolume.GetID())
yellowView.sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(foregroundVolume.GetID())
greenView.sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(foregroundVolume.GetID())

# 调整前景透明度
redView.sliceLogic().GetSliceCompositeNode().SetForegroundOpacity(0.5)
yellowView.sliceLogic().GetSliceCompositeNode().SetForegroundOpacity(0.5)
greenView.sliceLogic().GetSliceCompositeNode().SetForegroundOpacity(0.5)

2. 三维重建与量化分析

3D Slicer 提供了强大的三维重建功能,可以将 nnUNet 的分割结果转换为三维模型,并进行体积、表面积等定量分析。

# 在 3D Slicer Python 控制台中运行以下代码

# 获取分割节点
segmentationNode = slicer.util.getNode("NNUNet_Segmentation_Result")

# 获取分割编辑器小部件
segmentEditorWidget = slicer.qMRMLSegmentEditorWidget()
segmentEditorWidget.setMRMLScene(slicer.mrmlScene)
segmentEditorNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentEditorNode")
segmentEditorWidget.setMRMLSegmentEditorNode(segmentEditorNode)
segmentEditorWidget.setSegmentationNode(segmentationNode)

# 选择要分析的器官(例如肝脏)
segmentID = segmentationNode.GetSegmentIDBySegmentName("Liver")

# 计算体积
volumeMeasurement = vtkMRMLSegmentationNode.GetMeasurement("Volume")
volume = segmentationNode.GetMeasurement(segmentID, volumeMeasurement).GetValue()
print(f"Liver Volume: {volume:.2f} mm³")

# 计算表面积
surfaceAreaMeasurement = vtkMRMLSegmentationNode.GetMeasurement("SurfaceArea")
surfaceArea = segmentationNode.GetMeasurement(segmentID, surfaceAreaMeasurement).GetValue()
print(f"Liver Surface Area: {surfaceArea:.2f} mm²")

# 三维重建
segmentationNode.CreateClosedSurfaceRepresentation()

# 在 3D 视图中显示
threeDView = slicer.app.layoutManager().threeDWidget(0)
threeDView.threeDView().resetCamera()

3. 批量处理与报告生成

结合 3D Slicer 的批量处理功能和报表生成模块,可以实现对多个病例的自动分割、分析和报告生成。

# 批量处理脚本示例

import os
import csv
from slicer.util import filesFromWildcard

# 设置输入输出路径
input_dir = "/path/to/input/images"
output_dir = "/path/to/output/reports"
model_folder = "/path/to/nnunet/model"
task_id = 123

# 创建输出目录
os.makedirs(output_dir, exist_ok=True)

# 创建报告 CSV 文件
report_csv = os.path.join(output_dir, "segmentation_report.csv")
with open(report_csv, 'w', newline='') as csvfile:
    csvwriter = csv.writer(csvfile)
    csvwriter.writerow(["Case ID", "Liver Volume (mm³)", "Tumor Volume (mm³)", "Tumor Count"])

    # 处理每个病例
    for image_path in filesFromWildcard(input_dir, "*.nii.gz"):
        case_id = os.path.basename(image_path).split('.')[0]
        print(f"Processing case: {case_id}")
        
        # 运行 nnUNet 分割
        # ... (分割代码与前面相同)
        
        # 导入到 Slicer 并分析
        # ... (分析代码与前面相同)
        
        # 获取测量结果
        liver_volume = ...  # 从前面的分析中获取肝脏体积
        tumor_volume = ...  # 从前面的分析中获取肿瘤体积
        tumor_count = ...   # 从前面的分析中获取肿瘤数量
        
        # 写入报告
        csvwriter.writerow([case_id, liver_volume, tumor_volume, tumor_count])
        
        # 生成 3D 可视化截图
        screenshot_path = os.path.join(output_dir, f"{case_id}_3d_view.png")
        slicer.app.layoutManager().threeDWidget(0).grabWindow().save(screenshot_path)

常见问题与解决方案

问题1:分割结果与原始影像不对齐

可能原因:坐标系统不一致或元数据丢失。

解决方案

  1. 确保使用最新版本的 nnUNet 和 3D Slicer
  2. 在导出分割结果时保留完整的元数据
  3. 使用 SimpleITKIOWithReorient 类确保坐标系统一致
# 确保导出时包含完整的元数据
export_prediction_from_logits(
    # ... 其他参数 ...
    output_file_truncated="/path/to/output/segmentation",
    save_probabilities=False
)

# 检查元数据是否完整
properties = np.load("/path/to/output/segmentation.pkl", allow_pickle=True).item()
print("Spacing:", properties['sitk_stuff']['spacing'])
print("Origin:", properties['sitk_stuff']['origin'])
print("Direction:", properties['sitk_stuff']['direction'])

问题2:3D Slicer 中无法正确显示标签

可能原因:标签值超出显示范围或颜色映射不正确。

解决方案

  1. 确保分割结果使用适当的数据类型(uint8 或 uint16)
  2. 在 3D Slicer 中手动调整颜色映射
# 在 3D Slicer 中设置颜色映射
segmentationNode = slicer.util.getNode("NNUNet_Segmentation_Result")
displayNode = segmentationNode.GetDisplayNode()

# 设置特定标签的颜色
segmentIDs = segmentationNode.GetSegmentIDs()
for segID in segmentIDs:
    segment = segmentationNode.GetSegment(segID)
    segName = segment.GetName()
    
    # 根据器官名称设置颜色
    if "Liver" in segName:
        segment.SetColor(0.2, 0.8, 0.2)  # 绿色
    elif "Tumor" in segName:
        segment.SetColor(0.8, 0.2, 0.2)  # 红色
    elif "Kidney" in segName:
        segment.SetColor(0.2, 0.2, 0.8)  # 蓝色

问题3:大型数据集处理效率低下

可能原因:内存不足或处理线程设置不合理。

解决方案

  1. 增加预处理和导出的线程数
  2. 使用分块处理大型图像
  3. 优化 3D Slicer 的缓存设置
# 优化 nnUNet 处理参数
predict_from_raw_data(
    # ... 其他参数 ...
    num_processes_preprocessing=16,  # 增加预处理线程数
    num_processes_segmentation_export=16,  # 增加导出线程数
    num_parts=4,  # 将分割分成4部分处理
    part_id=0  # 当前处理的部分ID
)

结论与展望

nnUNet 与 3D Slicer 的集成实现了医学影像分割与可视化分析的无缝衔接,显著提升了工作效率。通过本文介绍的方法,研究者和临床医生可以快速获得高质量的分割结果并进行深入的可视化分析。

未来,我们可以期待:

  1. 更紧密的集成:将 nnUNet 直接嵌入 3D Slicer 作为内置模块
  2. 实时分割:利用 GPU 加速实现近乎实时的分割与可视化反馈
  3. AI 辅助的交互式分割:结合 nnUNet 的自动分割和 3D Slicer 的手动修正功能

通过不断优化这一工作流,我们可以为医学影像分析提供更强大、更易用的工具,推动相关领域的研究和临床应用发展。

参考资料

  1. Isensee, F., Jaeger, P. F., Kohl, S. A., Petersen, J., & Maier-Hein, K. H. (2021). nnU-Net: a self-configuring method for deep learning-based biomedical image segmentation. Nature methods, 18(2), 203-211.

  2. Fedorov, A., Beichel, R., Kalpathy-Cramer, J., Finet, G., Fillion-Robin, J. C., Pujol, S., ... & Pieper, S. (2012). 3D Slicer as an image computing platform for the Quantitative Imaging Network. Magnetic resonance imaging, 30(9), 1323-1341.

  3. nnUNet 官方文档: https://github.com/MIC-DKFZ/nnUNet

  4. 3D Slicer 官方文档: https://slicer.readthedocs.io/

【免费下载链接】nnUNet 【免费下载链接】nnUNet 项目地址: https://gitcode.com/gh_mirrors/nn/nnUNet

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

抵扣说明:

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

余额充值