Heavy lag when displaying segmentations via Trame + TotalSegmentator

I am developing a web-based medical imaging viewer using Trame-slicer. I used TotalSegmentator to generate anatomical masks and then load these .nii.gz files into the Slicer scene for 3D visualization.
Currently, I am loading each organ (Liver, Colon, Heart, etc.) from separate .nii.gz files into individual nodes. I suspect this overhead contributes to the performance degradation.

Merging Masks: Is there a recommended way to merge these multiple NIfTI files into a single vtkMRMLSegmentationNode with multiple segments upon loading? I want to reduce the number of Display Nodes and renderers that Slicer has to manage.

        mask_files = [
            "/trame_server3d/build/segment_result/colon.nii.gz",
            "/trame_server3d/build/segment_result/liver.nii.gz",
            "/trame_server3d/build/segment_result/heart.nii.gz",
            "/trame_server3d/build/segment_result/kidney_cyst_left.nii.gz",
            "/trame_server3d/build/segment_result/kidney_cyst_right.nii.gz",
            "/trame_server3d/build/segment_result/kidney_right.nii.gz",
            "/trame_server3d/build/segment_result/kidney_left.nii.gz",
            # Add more test paths here
        ]
        
        segmentation_nodes = []
        
        for mask_path in mask_files:
            if Path(mask_path).exists():
                logging.info(f"[ViewManager] Loading segmentation: {mask_path}")
                # Because IOManager only handles one file at a time
                node = self.slicer_app.io_manager.load_segmentation(mask_path)
                if node:
                    segmentation_nodes.append(node)

After this, the loadSegment will using these nodes to display them:
```

    def loadSegment(self, segmentation_nodes: list) -> None:
        for node in segmentation_nodes:
            seg_wrapper = Segmentation(
                segmentation_node=node,
                volume_node=self.volume_node,
                editor_logic=self.slicer_app.segmentation_editor.editor_logic
            )
            
            seg_wrapper.enable_surface_representation()

Are there any options available here to explicitly choose between CPU or GPU rendering for segmentations, like in volume 3D render?
```


    def createVolumeVisualization(self, volume_node, preset_name="CT-AAA"):
        """Setup Volume Rendering using Pure VTK Pipeline (CPU Only)."""
        start_time = time.time()
        self.volume_node = volume_node
        
        # 1. Setup Volume Rendering Logic
        vr_logic = self.slicer_app.volume_rendering._logic
        renderOption = self.renderOption
        if renderOption == RenderOption.CPU.value:
            logging.info("[Volume] Rendering with CPU")
            vr_logic.SetDefaultRenderingMethod("vtkMRMLCPURayCastVolumeRenderingDisplayNode")
        elif renderOption == RenderOption.GPU.value:
            logging.info("[Volume] Rendering with GPU")
            vr_logic.SetDefaultRenderingMethod("vtkMRMLGPURayCastVolumeRenderingDisplayNode")
        elif renderOption == RenderOption.MULTI_VOLUME.value:
            vr_logic.SetDefaultRenderingMethod("vtkMRMLMultiVolumeRenderingDisplayNode")

        self.slicer_app.display_manager.show_volume(
            volume_node=volume_node,
            vr_preset=preset_name,
            do_reset_views=True
        )

        self.vr_display_node = self.slicer_app.volume_rendering.get_vr_display_node(volume_node)
        self.view3D.render()
        self.view3D.set_box_visible(False)
        self.view3D.set_background_gradient_color([0, 0, 0], [0, 0, 0])

        elapsed = time.time() - start_time
        logging.info(f"[Volume] Volume ready after {elapsed:.4f} seconds")

In my current environment, I suspect that Trame-Slicer might be defaulting to GPU raycasting (or GPU-based Closed Surface representation) even on a machine that primarily supports CPU rendering, which causes significant lag (Even displaying only segmentation without showing 3D volume still causes lag).
Thank you for your help!

Solution

1. Merge Multiple NIfTI Files into Single SegmentationNode

import slicer
import logging
from pathlib import Path

def merge_nifti_files(mask_files, segmentation_name="TotalSegmentation"):
    """Merge multiple NIfTI files into a single SegmentationNode."""
    
    # Create single segmentation node
    seg_node = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')
    seg_node.SetName(segmentation_name)
    seg_node.CreateDefaultDisplayNodes()
    
    temp_nodes = []
    
    for mask_path in mask_files:
        if not Path(mask_path).exists():
            continue
        
        # Extract organ name
        organ_name = Path(mask_path).stem.replace('.nii', '')
        
        # Load as labelmap
        label_node = slicer.util.loadLabelVolume(mask_path, {'name': f"temp_{organ_name}"})
        temp_nodes.append(label_node)
        
        # Import to segmentation
        slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(
            label_node, seg_node, organ_name
        )
        
        logging.info(f"Added segment: {organ_name}")
    
    # Cleanup temporary nodes
    for node in temp_nodes:
        slicer.mrmlScene.RemoveNode(node)
    
    return seg_node

2. Control CPU/GPU Rendering for Segmentations

def configure_segmentation_rendering(seg_node, use_gpu=False):
    """Configure segmentation rendering method."""
    
    # Get display node
    display_node = seg_node.GetDisplayNode()
    if not display_node:
        display_node = seg_node.CreateDefaultDisplayNodes()
    
    # Set representation (affects performance)
    if use_gpu:
        # GPU-based rendering (faster but requires GPU)
        display_node.SetRepresentation2D("Outline")
        display_node.SetRepresentation3D("Closed surface")
    else:
        # CPU-optimized rendering
        display_node.SetRepresentation2D("Outline")
        display_node.SetRepresentation3D("Closed surface")
        
        # Reduce quality for better performance
        display_node.SetOpacity3D(0.8)
        
        # Disable auto-update for performance
        seg_node.SetDisplayVisibility(False)
        # ... do processing ...
        seg_node.SetDisplayVisibility(True)
    
    return display_node

# Alternative: Use Binary Labelmap representation (CPU-friendly)
def use_labelmap_representation(seg_node):
    """Use binary labelmap representation (CPU-friendly)."""
    segmentation = seg_node.GetSegmentation()
    
    # Use binary labelmap representation instead of closed surface
    # This is more CPU-friendly
    segmentation.SetMasterRepresentationName("Binary labelmap")
    
    # Disable automatic surface creation
    segmentation.SetConversionParameter("Apply to all representations", "0")

3. Optimized Loading in Trame-Slicer

def load_segments_optimized(self, mask_files):
    """Load and merge all segments at once."""
    
    # Merge all NIfTI files into one segmentation node
    merged_seg = merge_nifti_files(mask_files, "TotalSegmentation")
    
    # Configure for CPU rendering
    configure_segmentation_rendering(merged_seg, use_gpu=False)
    
    # Enable surface representation
    seg_wrapper = Segmentation(
        segmentation_node=merged_seg,
        volume_node=self.volume_node,
        editor_logic=self.slicer_app.segmentation_editor.editor_logic
    )
    
    # For CPU optimization, reduce resolution
    display_node = merged_seg.GetDisplayNode()
    if display_node:
        display_node.SetOpacity3D(0.7)
        # Reduce smooth factor for faster rendering
        display_node.SetSurfaceSmoothingFactor(0.5)
    
    return [merged_seg]  # Return single node instead of list

Key Benefits:

  • Reduces display nodes from N to 1
  • Less memory usage (single renderer)
  • Better performance (especially for CPU rendering)
  • Easier management of segment visibility and colors

Performance Tips:

  1. Use Binary labelmap representation for CPU
  2. Reduce SurfaceSmoothingFactor for faster rendering
  3. Set appropriate Opacity3D (0.5-0.8)
  4. Disable auto-updates during batch operations
  5. Use merged segmentation instead of multiple nodes

From GitHub - jumbojing/slicerClaw: A revolutionary, lightning-fast AI assistant natively integrated into 3D Slicer.