Skip to content

Instantly share code, notes, and snippets.

@lassoan
Created March 24, 2026 18:10
Show Gist options
  • Select an option

  • Save lassoan/2fa7c7a02054e6b217ebfd3b33688145 to your computer and use it in GitHub Desktop.

Select an option

Save lassoan/2fa7c7a02054e6b217ebfd3b33688145 to your computer and use it in GitHub Desktop.
Example of a serverless trame-slicer application
"""
Standalone MRML 3D view using only the slicer package (no trame).
Sets up:
- vtkMRMLScene + vtkSlicerApplicationLogic
- vtkMRMLViewNode via vtkMRMLViewLogic
- vtkMRMLDisplayableManagerGroup with all standard 3D displayable managers
- Loads MRHead.nrrd and enables GPU ray-cast volume rendering
Usage:
python mrml_3d_view.py
"""
from pathlib import Path
from slicer import (
vtkMRMLCameraDisplayableManager,
vtkMRMLColorLegendDisplayableManager,
vtkMRMLCrosshairDisplayableManager3D,
vtkMRMLCrosshairNode,
vtkMRMLDisplayableManagerGroup,
vtkMRMLLayerDisplayableManager,
vtkMRMLLinearTransformsDisplayableManager,
vtkMRMLMarkupsDisplayableManager,
vtkMRMLModelDisplayableManager,
vtkMRMLOrientationMarkerDisplayableManager,
vtkMRMLRulerDisplayableManager,
vtkMRMLScene,
vtkMRMLSegmentationsDisplayableManager3D,
vtkMRMLThreeDReformatDisplayableManager,
vtkMRMLThreeDViewDisplayableManagerFactory,
vtkMRMLThreeDViewInteractorStyle,
vtkMRMLTransformsDisplayableManager3D,
vtkMRMLViewDisplayableManager,
vtkMRMLViewLogic,
vtkMRMLVolumeRenderingDisplayableManager,
vtkSlicerApplicationLogic,
vtkSlicerVolumeRenderingLogic,
vtkSlicerVolumesLogic,
)
from vtkmodules.vtkCommonCore import vtkCollection, vtkCommand, vtkOutputWindow
from vtkmodules.vtkRenderingCore import (
vtkInteractorStyle3D,
vtkRenderer,
vtkRenderWindow,
vtkRenderWindowInteractor,
)
MRHEAD_PATH = Path(__file__).parent / "MRHead.nrrd"
# Maximum render rate driven by displayable-manager update requests (frames/sec).
MAX_UPDATE_RATE_FPS = 60
THREED_DISPLAYABLE_MANAGERS = [
vtkMRMLVolumeRenderingDisplayableManager,
vtkMRMLCameraDisplayableManager,
vtkMRMLViewDisplayableManager,
vtkMRMLModelDisplayableManager,
vtkMRMLThreeDReformatDisplayableManager,
vtkMRMLCrosshairDisplayableManager3D,
vtkMRMLOrientationMarkerDisplayableManager,
vtkMRMLRulerDisplayableManager,
vtkMRMLSegmentationsDisplayableManager3D,
vtkMRMLMarkupsDisplayableManager,
vtkMRMLTransformsDisplayableManager3D,
vtkMRMLLayerDisplayableManager,
vtkMRMLLinearTransformsDisplayableManager,
vtkMRMLColorLegendDisplayableManager,
]
def setup_scene():
"""Create and configure the MRML scene and application logic."""
# Route VTK warnings to stderr
vtk_out = vtkOutputWindow()
vtk_out.SetDisplayModeToAlwaysStdErr()
vtkOutputWindow.SetInstance(vtk_out)
scene = vtkMRMLScene()
# Crosshair node is expected by several displayable managers
crosshair = vtkMRMLCrosshairNode()
crosshair.SetCrosshairName("default")
scene.AddNode(crosshair)
app_logic = vtkSlicerApplicationLogic()
app_logic.SetMRMLScene(scene)
app_logic.SetViewLogics(vtkCollection())
# Use the color logic that vtkSlicerApplicationLogic already owns internally,
# set it up, and register it under "Colors" so other module logics can find it
color_logic = app_logic.GetColorLogic()
color_logic.SetMRMLScene(scene)
color_logic.AddDefaultColorNodes()
app_logic.SetModuleLogic("Colors", color_logic)
# Connect the factory to the application logic
factory = vtkMRMLThreeDViewDisplayableManagerFactory.GetInstance()
factory.SetMRMLApplicationLogic(app_logic)
return scene, app_logic
def create_3d_view(scene, app_logic):
"""Create a VTK render window and wire it to a vtkMRMLViewNode."""
renderer = vtkRenderer()
renderer.SetUseDepthPeeling(True)
renderer.SetUseDepthPeelingForVolumes(True)
renderer.SetBackground(0.1, 0.1, 0.15)
renderer.SetBackground2(0.2, 0.2, 0.35)
renderer.SetGradientBackground(True)
render_window = vtkRenderWindow()
render_window.SetWindowName("MRML 3D View")
render_window.SetSize(800, 600)
render_window.AddRenderer(renderer)
interactor = vtkRenderWindowInteractor()
interactor.SetRenderWindow(render_window)
interactor.Initialize()
# Build displayable manager group
factory = vtkMRMLThreeDViewDisplayableManagerFactory.GetInstance()
dm_group = vtkMRMLDisplayableManagerGroup()
dm_group.SetRenderer(renderer)
for dm_type in THREED_DISPLAYABLE_MANAGERS:
name = dm_type.__name__
if not factory.IsDisplayableManagerRegistered(name):
factory.RegisterDisplayableManager(name)
dm_group.Initialize(factory, renderer)
# Trigger renders when the displayable managers request them.
# Guard against re-entrancy: Render() causes managers to fire UpdateEvent,
# which would call Render() again, creating an infinite recursion.
_rendering = [False]
def _on_update(*_):
if not _rendering[0]:
_rendering[0] = True
try:
render_window.Render()
finally:
_rendering[0] = False
dm_group.AddObserver(vtkCommand.UpdateEvent, _on_update)
# Create the MRML view node and attach it to the group
view_logic = vtkMRMLViewLogic()
view_logic.SetMRMLApplicationLogic(app_logic)
app_logic.GetViewLogics().AddItem(view_logic)
view_logic.SetMRMLScene(scene)
view_node = view_logic.AddViewNode("View1")
view_node.SetMappedInLayout(True)
dm_group.SetMRMLDisplayableNode(view_node)
# vtkMRMLThreeDViewInteractorStyle is an observer, not the interactor style itself
style = vtkMRMLThreeDViewInteractorStyle()
style.SetDisplayableManagers(dm_group)
style.SetInteractor(interactor)
interactor.SetInteractorStyle(vtkInteractorStyle3D())
# Keep dm_group, view_logic, and style alive for the lifetime of the window.
# They are not referenced after this point but must not be garbage-collected.
render_window._mrml_refs = (dm_group, view_logic, style)
return render_window, interactor, view_node
def load_volume(scene, app_logic):
"""Load MRHead.nrrd using vtkSlicerVolumesLogic."""
volumes_logic = vtkSlicerVolumesLogic()
volumes_logic.SetMRMLScene(scene)
volumes_logic.SetMRMLApplicationLogic(app_logic)
app_logic.SetModuleLogic("Volumes", volumes_logic)
volume_node = volumes_logic.AddArchetypeVolume(str(MRHEAD_PATH), "MRHead", 0)
if not volume_node:
raise RuntimeError(f"Failed to load volume: {MRHEAD_PATH}")
print(f"Loaded: {volume_node.GetName()}")
return volume_node
def enable_volume_rendering(scene, app_logic, volume_node):
"""Create a GPU ray-cast volume rendering display node for the volume."""
from trame_slicer.resources import resources_path
vr_logic = vtkSlicerVolumeRenderingLogic()
vr_logic.SetModuleShareDirectory(resources_path().as_posix())
vr_logic.SetMRMLScene(scene)
vr_logic.SetMRMLApplicationLogic(app_logic)
app_logic.SetModuleLogic("VolumeRendering", vr_logic)
vr_logic.ChangeVolumeRenderingMethod("vtkMRMLGPURayCastVolumeRenderingDisplayNode")
display_node = vr_logic.CreateDefaultVolumeRenderingNodes(volume_node)
display_node.SetVisibility(True)
return display_node
def main():
scene, app_logic = setup_scene()
render_window, interactor, view_node = create_3d_view(scene, app_logic)
volume_node = load_volume(scene, app_logic)
enable_volume_rendering(scene, app_logic, volume_node)
render_window.Render()
interactor.Start()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment