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()