Examples

Hello World

 1"""
 2Minimal hello world with no features.
 3Creates the minimal trame-slicer classes and starts the server.
 4"""
 5
 6from trame.app import TrameApp
 7from trame_vuetify.ui.vuetify3 import SinglePageLayout
 8
 9from trame_slicer.core import LayoutManager, SlicerApp
10from trame_slicer.rca_view import register_rca_factories
11
12
13class MinimalTrameSlicerApp(TrameApp):
14    def __init__(self, server=None):
15        super().__init__(server=server)
16
17        # Slicer application creation
18        self._slicer_app = SlicerApp()
19
20        # Remote controlled view factory registration
21        register_rca_factories(self._slicer_app.view_manager, self._server)
22
23        # Layout creation and view layout registration
24        self._layout_manager = LayoutManager(self._slicer_app.scene, self._slicer_app.view_manager, self._server)
25
26        # Register a layout and set the default view layout
27        self._layout_manager.register_layout_dict(LayoutManager.default_grid_configuration())
28        self._layout_manager.set_layout("Axial Primary")
29
30        # Build the trame UI and populate the layout place-holder
31        with SinglePageLayout(self._server) as self.ui, self.ui.content:
32            self._layout_manager.initialize_layout_grid(self.ui)
33
34
35if __name__ == "__main__":
36    app = MinimalTrameSlicerApp()
37    app.server.start()

Uploading data to the server

 1"""
 2Minimal trame-slicer example showing how to upload and display a volume.
 3Uses a VFileInput for uploading data and loading data into the UI.
 4"""
 5
 6from tempfile import TemporaryDirectory
 7from typing import Any
 8
 9from trame.app import TrameApp
10from trame_client.widgets.html import Div
11from trame_vuetify.ui.vuetify3 import SinglePageLayout
12from trame_vuetify.widgets.vuetify3 import VFileInput
13
14from trame_slicer.core import LayoutManager, SlicerApp
15from trame_slicer.rca_view import register_rca_factories
16from trame_slicer.utils import write_client_files_to_dir
17
18
19class UploadingTrameSlicerApp(TrameApp):
20    def __init__(self, server=None):
21        super().__init__(server=server)
22
23        # Slicer application creation
24        self._slicer_app = SlicerApp()
25
26        # Remote controlled view factory registration
27        register_rca_factories(self._slicer_app.view_manager, self._server)
28
29        # Layout creation and view layout registration
30        self._layout_manager = LayoutManager(self._slicer_app.scene, self._slicer_app.view_manager, self._server)
31
32        # Register a layout and set the default view layout
33        self._layout_manager.register_layout_dict(LayoutManager.default_grid_configuration())
34        self._layout_manager.set_layout("Axial Primary")
35        self._build_ui()
36
37    def _build_ui(self):
38        with SinglePageLayout(self._server) as self.ui:
39            self.ui.root.theme = "dark"
40
41            with self.ui.toolbar:
42                self.ui.toolbar.clear()
43
44                with Div(classes="d-flex flex-row align-left mx-4"):
45                    VFileInput(
46                        change=(
47                            f"trigger('{self.server.controller.trigger_name(self._load_files)}', [$event.target.files])"
48                        ),
49                        prepend_icon="mdi-file-upload",
50                        multiple=True,
51                        hide_input=True,
52                    )
53
54            with self.ui.content:
55                self._layout_manager.initialize_layout_grid(self.ui)
56
57    async def _load_files(self, files: list[dict[str, Any]]) -> None:
58        with TemporaryDirectory() as tmp_dir:
59            volume_files = sorted(write_client_files_to_dir(files, tmp_dir))
60            volume_nodes = self._slicer_app.io_manager.load_volumes(volume_files)
61            self._slicer_app.display_manager.show_volume(volume_nodes[0], do_reset_views=True)
62
63
64if __name__ == "__main__":
65    app = UploadingTrameSlicerApp()
66    app.server.start()

Downloading from the server

 1"""
 2Minimal trame-slicer example showing how to download data from the server.
 3Uses a VFileInput for uploading data and the server.protocol.addAttachment to download the data.
 4"""
 5
 6from pathlib import Path
 7from tempfile import TemporaryDirectory
 8from typing import Any
 9
10from trame.app import TrameApp
11from trame_client.widgets.html import Div
12from trame_vuetify.ui.vuetify3 import SinglePageLayout
13from trame_vuetify.widgets.vuetify3 import VBtn, VFileInput
14
15from trame_slicer.core import LayoutManager, SlicerApp
16from trame_slicer.rca_view import register_rca_factories
17from trame_slicer.utils import write_client_files_to_dir
18
19
20class DownloadingTrameSlicerApp(TrameApp):
21    def __init__(self, server=None):
22        super().__init__(server=server)
23        self._volume_node = None
24
25        # Slicer application creation
26        self._slicer_app = SlicerApp()
27
28        # Remote controlled view factory registration
29        register_rca_factories(self._slicer_app.view_manager, self._server)
30
31        # Layout creation and view layout registration
32        self._layout_manager = LayoutManager(self._slicer_app.scene, self._slicer_app.view_manager, self._server)
33
34        # Register a layout and set the default view layout
35        self._layout_manager.register_layout_dict(LayoutManager.default_grid_configuration())
36        self._layout_manager.set_layout("Axial Primary")
37        self._build_ui()
38
39    def _build_ui(self):
40        with SinglePageLayout(self._server) as self.ui:
41            self.ui.root.theme = "dark"
42
43            with self.ui.toolbar:
44                self.ui.toolbar.clear()
45
46                with Div(classes="d-flex flex-row align-left mx-4"):
47                    VFileInput(
48                        change=(
49                            f"trigger('{self.server.controller.trigger_name(self._load_files)}', [$event.target.files])"
50                        ),
51                        prepend_icon="mdi-file-upload",
52                        glow=True,
53                        multiple=True,
54                        hide_input=True,
55                        density="compact",
56                    )
57
58                    VBtn(
59                        icon="mdi-file-download",
60                        click=(
61                            "utils.download('volume_file.nrrd', trigger('"
62                            f"{self.server.controller.trigger_name(self._download_file)}"
63                            "'), 'application/octet-stream')"
64                        ),
65                        variant="plain",
66                        hide_details=True,
67                        density="compact",
68                    )
69
70            with self.ui.content:
71                self._layout_manager.initialize_layout_grid(self.ui)
72
73    async def _load_files(self, files: list[dict[str, Any]]) -> None:
74        with TemporaryDirectory() as tmp_dir:
75            volume_files = sorted(write_client_files_to_dir(files, tmp_dir))
76            volume_nodes = self._slicer_app.io_manager.load_volumes(volume_files)
77            self._volume_node = volume_nodes[0]
78            self._slicer_app.display_manager.show_volume(self._volume_node, do_reset_views=True)
79
80    async def _download_file(self):
81        if not self._volume_node:
82            return None
83
84        with TemporaryDirectory() as tmp_dir:
85            out_path = Path(tmp_dir) / "volume.nrrd"
86            self._slicer_app.io_manager.write_volume(self._volume_node, out_path)
87            return self._server.protocol.addAttachment(out_path.read_bytes())
88
89
90if __name__ == "__main__":
91    app = DownloadingTrameSlicerApp()
92    app.server.start()

Integrating additional view types to the layout

  1"""
  2Minimal trame-slicer example showing how to connect plotly with trame-slicer.
  3Requires pandas and trame-plotly
  4"""
  5
  6from dataclasses import dataclass
  7from enum import Enum
  8from typing import Generic
  9
 10import pandas as pd
 11import plotly.graph_objects as go
 12from slicer import vtkMRMLApplicationLogic, vtkMRMLScene
 13from trame.app import TrameApp
 14from trame.widgets import plotly
 15from trame_client.widgets.core import AbstractElement
 16from trame_client.widgets.trame import SizeObserver
 17from trame_vuetify.ui.vuetify3 import SinglePageLayout
 18
 19from trame_slicer.core import LayoutManager, SlicerApp
 20from trame_slicer.rca_view import register_rca_factories
 21from trame_slicer.views import (
 22    AbstractViewChild,
 23    IViewFactory,
 24    Layout,
 25    LayoutDirection,
 26    ViewLayout,
 27    ViewLayoutDefinition,
 28    ViewProps,
 29)
 30from trame_slicer.views.view_factory import V
 31
 32polar_data = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/polar_dataset.csv")
 33
 34
 35@dataclass
 36class PlotlyView(Generic[AbstractViewChild]):
 37    vuetify_view: AbstractElement
 38
 39
 40class CustomViews(Enum):
 41    PLOTLY_VIEW = "Plotly"
 42
 43
 44def create_polar_fig(width=300, height=300, **_):
 45    fig = go.Figure()
 46    fig.add_trace(
 47        go.Scatterpolar(
 48            r=polar_data["x1"].tolist(),
 49            theta=polar_data["y"].tolist(),
 50            mode="lines",
 51            name="Figure 8",
 52            line_color="peru",
 53        )
 54    )
 55    fig.add_trace(
 56        go.Scatterpolar(
 57            r=polar_data["x2"].tolist(),
 58            theta=polar_data["y"].tolist(),
 59            mode="lines",
 60            name="Cardioid",
 61            line_color="darkviolet",
 62        )
 63    )
 64    fig.add_trace(
 65        go.Scatterpolar(
 66            r=polar_data["x3"].tolist(),
 67            theta=polar_data["y"].tolist(),
 68            mode="lines",
 69            name="Hypercardioid",
 70            line_color="deepskyblue",
 71        )
 72    )
 73
 74    fig.update_layout(
 75        # title = 'Mic Patterns',
 76        margin={"l": 20, "r": 20, "t": 20, "b": 20},
 77        showlegend=False,
 78        width=width,
 79        height=height,
 80    )
 81    return fig
 82
 83
 84class PlotlyViewFactory(IViewFactory):
 85    def __init__(self, server, **_):
 86        super().__init__()
 87        self._server = server
 88
 89    def _get_slicer_view(self, _view: V) -> AbstractViewChild | None:
 90        return None
 91
 92    def can_create_view(self, view: ViewLayoutDefinition) -> bool:
 93        return view.view_type == CustomViews.PLOTLY_VIEW
 94
 95    def _create_view(
 96        self,
 97        view: ViewLayoutDefinition,
 98        _scene: vtkMRMLScene,
 99        _app_logic: vtkMRMLApplicationLogic,
100    ) -> PlotlyView:
101        view_id = view.singleton_tag
102
103        @self._server.state.change("polar_size")
104        def update_polar_size(polar_size, **_):
105            if polar_size is None:
106                return
107
108            self._server.controller.update_polar(create_polar_fig(**polar_size.get("size")))
109
110        with ViewLayout(self._server, template_name=view_id) as vuetify_view, SizeObserver("polar_size"):
111            self._server.controller.update_polar = plotly.Figure(
112                display_mode_bar=("false",),
113            ).update
114
115        return PlotlyView(vuetify_view)
116
117
118def view_layout_configuration() -> dict[str, Layout]:
119    plotly_view = ViewLayoutDefinition("Plotly", CustomViews.PLOTLY_VIEW, ViewProps())
120
121    return {
122        "default": Layout(
123            LayoutDirection.Horizontal,
124            [
125                ViewLayoutDefinition.threed_view(),
126                Layout(
127                    LayoutDirection.Vertical,
128                    [plotly_view, (ViewLayoutDefinition.axial_view())],
129                ),
130            ],
131            flex_sizes=["2"],
132        )
133    }
134
135
136class PlotlyTrameSlicerApp(TrameApp):
137    def __init__(self, server=None):
138        super().__init__(server=server)
139
140        self._slicer_app = SlicerApp()
141
142        register_rca_factories(self._slicer_app.view_manager, self._server)
143        self._slicer_app.view_manager.register_factory(PlotlyViewFactory(self._server))
144
145        self._layout_manager = LayoutManager(self._slicer_app.scene, self._slicer_app.view_manager, self._server)
146        self._layout_manager.register_layout_dict(view_layout_configuration())
147        self._layout_manager.set_layout("default")
148
149        with SinglePageLayout(self._server) as self.ui, self.ui.content:
150            self._layout_manager.initialize_layout_grid(self.ui)
151
152
153if __name__ == "__main__":
154    app = PlotlyTrameSlicerApp()
155    app.server.start()

Creating custom segmentation effects

  1"""
  2Minimal trame-slicer example showing how to define a new processing segmentation effect.
  3"""
  4
  5from tempfile import TemporaryDirectory
  6from typing import Any
  7
  8from trame.app import TrameApp
  9from trame_client.widgets.html import Div
 10from trame_vuetify.ui.vuetify3 import SinglePageLayout
 11from trame_vuetify.widgets.vuetify3 import VBtn, VFileInput
 12
 13from trame_slicer.core import LayoutManager, SlicerApp
 14from trame_slicer.rca_view import register_rca_factories
 15from trame_slicer.segmentation import SegmentationEffect, SegmentationEffectPipeline
 16from trame_slicer.utils import vtk_image_to_np, write_client_files_to_dir
 17
 18
 19class MyNewEffect(SegmentationEffect):
 20    def _create_pipeline(
 21        self,
 22        _view_node,
 23        _parameter,
 24    ) -> SegmentationEffectPipeline | None:
 25        return None
 26
 27    def my_apply_method(self):
 28        # Usually shouldn't mutate the segmentation when not current active
 29        if not self.is_active:
 30            return
 31
 32        # Src VTK image data type
 33        source_image_data = self.modifier.get_source_image_data()
 34
 35        # Can be converted to NP (array buffer)
 36        src_array = vtk_image_to_np(source_image_data)
 37
 38        # Modifier required for changing the segmentation
 39        vtk_modifier = self.modifier.create_modifier_labelmap()
 40
 41        # Can be converted to NP (array buffer)
 42        modifier = vtk_image_to_np(vtk_modifier)
 43        modifier[src_array > 42] = 1
 44
 45        # Apply the label map
 46        self.modifier.apply_labelmap(vtk_modifier)
 47
 48
 49class SegmentationEffectTrameSlicerApp(TrameApp):
 50    def __init__(self, server=None):
 51        super().__init__(server=server)
 52        self._volume_node = None
 53        self._segmentation_node = None
 54
 55        # Slicer application creation
 56        self._slicer_app = SlicerApp()
 57
 58        # Remote controlled view factory registration
 59        register_rca_factories(self._slicer_app.view_manager, self._server)
 60
 61        # Layout creation and view layout registration
 62        self._layout_manager = LayoutManager(self._slicer_app.scene, self._slicer_app.view_manager, self._server)
 63
 64        # Register a layout and set the default view layout
 65        self._layout_manager.register_layout_dict(LayoutManager.default_grid_configuration())
 66        self._layout_manager.set_layout("Axial Primary")
 67        self._build_ui()
 68
 69    def _build_ui(self):
 70        with SinglePageLayout(self._server) as self.ui:
 71            self.ui.root.theme = "dark"
 72
 73            with self.ui.toolbar:
 74                self.ui.toolbar.clear()
 75
 76                with Div(classes="d-flex flex-row align-left mx-4"):
 77                    VFileInput(
 78                        change=(
 79                            f"trigger('{self.server.controller.trigger_name(self._load_files)}', [$event.target.files])"
 80                        ),
 81                        prepend_icon="mdi-file-upload",
 82                        glow=True,
 83                        multiple=True,
 84                        hide_input=True,
 85                        density="compact",
 86                    )
 87
 88                    VBtn(
 89                        icon="mdi-brush",
 90                        click=self._on_threshold,
 91                        variant="plain",
 92                        hide_details=True,
 93                        density="compact",
 94                    )
 95
 96            with self.ui.content:
 97                self._layout_manager.initialize_layout_grid(self.ui)
 98
 99    async def _load_files(self, files: list[dict[str, Any]]) -> None:
100        with TemporaryDirectory() as tmp_dir:
101            volume_files = sorted(write_client_files_to_dir(files, tmp_dir))
102            volume_nodes = self._slicer_app.io_manager.load_volumes(volume_files)
103            self._volume_node = volume_nodes[0]
104            self._slicer_app.display_manager.show_volume(self._volume_node, do_reset_views=True)
105
106    def _on_threshold(self):
107        if not self._segmentation_node:
108            self._segmentation_node = self._slicer_app.segmentation_editor.create_empty_segmentation_node()
109
110        self._slicer_app.segmentation_editor.set_active_segmentation(self._segmentation_node, self._volume_node)
111        self._slicer_app.segmentation_editor.set_surface_representation_enabled(False)
112
113        if not self._slicer_app.segmentation_editor.get_segment_ids():
114            self._slicer_app.segmentation_editor.add_empty_segment(segment_name="Segment Name")
115
116        effect: MyNewEffect = self._slicer_app.segmentation_editor.set_active_effect_type(MyNewEffect)
117        effect.my_apply_method()
118
119
120if __name__ == "__main__":
121    app = SegmentationEffectTrameSlicerApp()
122    app.server.start()

Creating custom widgets

  1"""
  2Minimal trame-slicer example showing how to create a custom widget.
  3
  4Adaptation of https://github.com/KitwareMedical/SlicerLayerDisplayableManager/tree/main/Examples/Python/ModelGlowDM
  5for the trame-slicer examples.
  6"""
  7
  8import sys
  9from tempfile import TemporaryDirectory
 10from typing import Any
 11
 12from LayerDMLib import vtkMRMLLayerDMScriptedPipeline
 13from slicer import (
 14    vtkMRMLAbstractViewNode,
 15    vtkMRMLInteractionEventData,
 16    vtkMRMLLayerDMPipelineFactory,
 17    vtkMRMLLayerDMPipelineScriptedCreator,
 18    vtkMRMLModelNode,
 19    vtkMRMLNode,
 20    vtkMRMLScene,
 21    vtkMRMLScriptedModuleNode,
 22    vtkMRMLTransformNode,
 23    vtkMRMLViewNode,
 24    vtkSlicerLayerDMLogic,
 25)
 26from trame.app import TrameApp
 27from trame_client.widgets.html import Div
 28from trame_vuetify.ui.vuetify3 import SinglePageLayout
 29from trame_vuetify.widgets.vuetify3 import VFileInput
 30from vtk import (
 31    vtkActor,
 32    vtkCommand,
 33    vtkGeneralTransform,
 34    vtkMath,
 35    vtkNamedColors,
 36    vtkOutlineGlowPass,
 37    vtkPolyDataMapper,
 38    vtkRenderer,
 39    vtkRenderStepsPass,
 40    vtkTransformPolyDataFilter,
 41)
 42
 43from trame_slicer.core import LayoutManager, SlicerApp
 44from trame_slicer.rca_view import register_rca_factories
 45from trame_slicer.utils import write_client_files_to_dir
 46
 47
 48class ModelGlowPipeline(vtkMRMLLayerDMScriptedPipeline):
 49    _glowPass = None
 50    _basicPasses = None
 51    _pipeline_creator = None
 52
 53    def __init__(self):
 54        super().__init__()
 55
 56        colors = vtkNamedColors()
 57        self._glowMapper = vtkPolyDataMapper()
 58        self._glowActor = vtkActor()
 59        self._glowActor.SetMapper(self._glowMapper)
 60        self._glowActor.GetProperty().SetColor(colors.GetColor3d("Magenta"))
 61        self._glowActor.GetProperty().LightingOff()
 62
 63        self._modelNode = None
 64        self._modelTransform = None
 65        self._polyData = None
 66
 67    @classmethod
 68    def _InitGlowPass(cls, renderer: vtkRenderer):
 69        if cls._glowPass is None:
 70            cls._basicPasses = vtkRenderStepsPass()
 71            cls._glowPass = vtkOutlineGlowPass()
 72            cls._glowPass.SetDelegatePass(cls._basicPasses)
 73
 74        if renderer is not None:
 75            renderer.SetPass(cls._glowPass)
 76
 77    @classmethod
 78    def IsPipelineNode(cls, node):
 79        return isinstance(node, vtkMRMLScriptedModuleNode) and node.GetAttribute("PipelineType") == cls._GetClassName()
 80
 81    @classmethod
 82    def TryCreatePipeline(
 83        cls, viewNode: vtkMRMLAbstractViewNode, node: vtkMRMLNode
 84    ) -> vtkMRMLLayerDMScriptedPipeline | None:
 85        if not cls.IsPipelineNode(node) or not isinstance(viewNode, vtkMRMLViewNode):
 86            return None
 87
 88        return cls()
 89
 90    @classmethod
 91    def _GetClassName(cls) -> str:
 92        return cls.__name__
 93
 94    def OnRendererAdded(self, renderer: vtkRenderer) -> None:
 95        if renderer is None:
 96            return
 97
 98        self._InitGlowPass(renderer)
 99        renderer.AddViewProp(self._glowActor)
100
101    def OnRendererRemoved(self, renderer: vtkRenderer) -> None:
102        if renderer is None:
103            return
104        renderer.RemoveViewProp(self._glowActor)
105
106    def GetRenderOrder(self) -> int:
107        return 1
108
109    def UpdatePipeline(self):
110        self._UpdateMapperConnection()
111        self._UpdateActorVisibility()
112        self.RequestRender()
113
114    def OnUpdate(self, obj, _eventId, _callData):
115        if obj == self._modelNode:
116            self._ObserveModelTransformNode()
117
118        self.ResetDisplay()
119
120    def SetDisplayNode(self, node):
121        super().SetDisplayNode(node)
122        self._ObserveModelNode()
123
124    def CanProcessInteractionEvent(self, eventData: vtkMRMLInteractionEventData) -> tuple[bool, float]:
125        # Only process mouse move events to avoid blocking camera interaction on click / drag
126        isMouseMoveEvent = eventData.GetType() == vtkCommand.MouseMoveEvent
127
128        if not isMouseMoveEvent or not self._IsModelVisible() or self._polyData is None:
129            return False, sys.float_info.max
130
131        pos = eventData.GetWorldPosition()
132        glowActorBounds = self._polyData.GetBounds()
133        isInBounds = (
134            glowActorBounds[0] < pos[0] < glowActorBounds[1]
135            and glowActorBounds[2] < pos[1] < glowActorBounds[3]
136            and glowActorBounds[4] < pos[2] < glowActorBounds[5]
137        )
138        distance2 = vtkMath.Distance2BetweenPoints(pos, self._polyData.GetCenter())
139        return isInBounds, distance2
140
141    def ProcessInteractionEvent(self, _eventData: vtkMRMLInteractionEventData) -> bool:
142        if not self.GetDisplayNode():
143            return False
144
145        self.GetDisplayNode().SetAttribute("IsSelected", str(1))
146        return True
147
148    def LoseFocus(self, eventData: vtkMRMLInteractionEventData | None) -> None:
149        super().LoseFocus(eventData)
150        if not self.GetDisplayNode():
151            return
152        self.GetDisplayNode().SetAttribute("IsSelected", str(0))
153
154    def _UpdateMapperConnection(self):
155        modelNode: vtkMRMLModelNode = self._GetModelNode()
156        self._polyData = self._TransformPolyData(modelNode.GetPolyData() if modelNode else None)
157        self._glowMapper.SetInputData(self._polyData)
158
159    def _TransformPolyData(self, polyData):
160        transformNode = self._modelNode.GetParentTransformNode() if self._modelNode else None
161        if transformNode is None:
162            return polyData
163        transformFilter = vtkTransformPolyDataFilter()
164        transform = vtkGeneralTransform()
165        transformNode.GetTransformToWorld(transform)
166        transformFilter.SetTransform(transform)
167        transformFilter.SetInputData(polyData)
168        transformFilter.Update()
169        return transformFilter.GetOutput()
170
171    def _UpdateActorVisibility(self):
172        isSelected = bool(self.GetDisplayNode() and int(self.GetDisplayNode().GetAttribute("IsSelected")))
173        self._glowActor.SetVisibility(self._IsModelVisible() and isSelected)
174
175    @classmethod
176    def CreateGlowNode(cls, modelNode: vtkMRMLModelNode, scene: vtkMRMLScene):
177        node = vtkMRMLScriptedModuleNode()
178        node.SetAttribute("PipelineType", cls._GetClassName())
179        node.SetAttribute("ModelNodeID", modelNode.GetID())
180        node.SetAttribute("IsSelected", str(0))
181        node = scene.AddNode(node)
182        modelNode.AddNodeReferenceID(vtkSlicerLayerDMLogic.GetDisplayRole(), node.GetID())
183        return node
184
185    def _IsModelVisible(self) -> bool:
186        modelNode = self._GetModelNode()
187        if modelNode is None:
188            return False
189        return bool(modelNode.GetDisplayVisibility())
190
191    def _GetModelNode(self) -> vtkMRMLModelNode | None:
192        return self.GetScene().GetNodeByID(self._GetModelNodeID(self.GetDisplayNode()))
193
194    def _ObserveModelNode(self):
195        if self._modelNode == self._GetModelNode():
196            return
197
198        self.UpdateObserver(self._modelNode, self._GetModelNode())
199        self._modelNode = self._GetModelNode()
200        self._ObserveModelTransformNode()
201
202    def _ObserveModelTransformNode(self):
203        transformNode = self._modelNode.GetParentTransformNode() if self._modelNode else None
204        if self._modelTransform == transformNode:
205            return
206
207        self.UpdateObserver(self._modelTransform, transformNode, vtkMRMLTransformNode.TransformModifiedEvent)
208        self._modelTransform = transformNode
209
210    @classmethod
211    def _GetModelNodeID(cls, node):
212        if cls.IsPipelineNode(node):
213            return node.GetAttribute("ModelNodeID")
214        return ""
215
216    @classmethod
217    def Register(cls):
218        if cls._pipeline_creator is not None:
219            return
220        cls._pipeline_creator = vtkMRMLLayerDMPipelineScriptedCreator()
221        cls._pipeline_creator.SetPythonCallback(cls.TryCreatePipeline)
222        vtkMRMLLayerDMPipelineFactory.GetInstance().AddPipelineCreator(cls._pipeline_creator)
223
224
225class ModelGlowTrameSlicerApp(TrameApp):
226    def __init__(self, server=None):
227        super().__init__(server=server)
228
229        # Slicer application creation
230        self._slicer_app = SlicerApp()
231
232        # Remote controlled view factory registration
233        register_rca_factories(self._slicer_app.view_manager, self._server)
234
235        # Layout creation and view layout registration
236        self._layout_manager = LayoutManager(self._slicer_app.scene, self._slicer_app.view_manager, self._server)
237
238        # Register a layout and set the default view layout
239        self._layout_manager.register_layout_dict(LayoutManager.default_grid_configuration())
240        self._layout_manager.set_layout("3D Only")
241        self._build_ui()
242
243        # Register our custom widget
244        ModelGlowPipeline.Register()
245
246    def _build_ui(self):
247        with SinglePageLayout(self._server) as self.ui:
248            self.ui.root.theme = "dark"
249
250            with self.ui.toolbar:
251                self.ui.toolbar.clear()
252
253                with Div(classes="d-flex flex-row align-left mx-4"):
254                    VFileInput(
255                        change=(
256                            "trigger('"
257                            f"{self.server.controller.trigger_name(self._load_model_files)}"
258                            f"', [$event.target.files])"
259                        ),
260                        prepend_icon="mdi-file-upload",
261                        glow=True,
262                        multiple=True,
263                        hide_input=True,
264                        density="compact",
265                    )
266
267            with self.ui.content:
268                self._layout_manager.initialize_layout_grid(self.ui)
269
270    async def _load_model_files(self, files: list[dict[str, Any]]) -> None:
271        with TemporaryDirectory() as tmp_dir:
272            model_files = sorted(write_client_files_to_dir(files, tmp_dir))
273            for model_file in model_files:
274                model_node = self._slicer_app.io_manager.load_model(model_file)
275                model_node.CreateDefaultDisplayNodes()
276                ModelGlowPipeline.CreateGlowNode(model_node, self._slicer_app.scene)
277
278
279if __name__ == "__main__":
280    app = ModelGlowTrameSlicerApp()
281    app.server.start()

Using trame-slicer without any servers

 1"""
 2Example of starting a server less viewer with one 3D view in interactive mode
 3"""
 4
 5from pathlib import Path
 6
 7from trame_slicer.core import SlicerApp
 8from trame_slicer.views import DirectViewFactory, ViewLayoutDefinition
 9
10
11def main(is_interactive: bool = True):
12    # Create the Slicer app
13    slicer_app = SlicerApp()
14
15    # Define and instantiate a 3D view
16    slicer_app.view_manager.register_factory(DirectViewFactory(do_render_offscreen=not is_interactive))
17    threed_view = slicer_app.view_manager.create_view(ViewLayoutDefinition.threed_view())
18
19    # Load and display a volume node
20    mr_head_path = Path(__file__).parent / ".." / ".." / "tests" / "data" / "mr_head.nrrd"
21    volume_node = slicer_app.io_manager.load_volumes(mr_head_path.as_posix())[0]
22    slicer_app.display_manager.show_volume(volume_node, vr_preset="MR-Default")
23
24    # Start the VTK render window interactor
25    threed_view.update_size(400, 300)
26    if is_interactive:
27        threed_view.start_interactor()
28
29
30if __name__ == "__main__":
31    main()