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!

Automatically-generated content using jumbojing/slicerClaw. Accuracy of the answer has not been verified and code has not been tested.

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. · GitHub

1. Merge Multiple NIfTI Files into Single SegmentationNode

The LLM-provided script may work, but there should be no need for custom scripting. If you use TotalSegmentator extension that takes care of correct and efficient loading of segments (using correct colors, terminology, etc.). The extension also request TotalSegmentator to create a single labelmap output (if the particular model supports that), so no merging is necessary.

2. Control CPU/GPU Rendering for Segmentations

Segmentations are always rendered using GPU.

I don’t think that the changes suggested by the LLM make any difference, as they are just some display options that don’t affect rendering speed.

Probaly the issue you are running into is that in Trame, rendering of segmentation can be slow if you run Slicer in a container that only has a software renderer. Make sure you set up GPU-accelerated rendering in your container.

If setting up GPU-accelerated rendering is impossible (e.g., you don’t have a GPU that your container can use) then you can speed up rendering by simplifying the segmentation: you can smooth the segments, or you can increase decimation factor (in the binary labelmap to closed surface conversion rule).

1 Like

Thanks you. Since most of the implementation is CPU-driven, I tried changing the decimation factor and it indeed made the volume rotation very smooth.

1 Like