2This example shows how to create a custom volume rendering for a volume node using the SlicerLayerDisplayableManager
5It goes over the following concepts:
6 - Creates a VR display pipeline on a selected volume node in the scene
7 - Register the pipeline creation mechanism
10 This example is implemented as a scripted module and can be added as such to Slicer.
17from slicer.ScriptedLoadableModule
import ScriptedLoadableModule, ScriptedLoadableModuleWidget
20from LayerDMLib
import ScriptedPipelineSceneConnector, vtkMRMLLayerDMScriptedPipeline
22 vtkMRMLAbstractViewNode,
25 vtkMRMLScriptedModuleNode,
33 vtkColorTransferFunction,
34 vtkGPUVolumeRayCastMapper,
46 ScriptedLoadableModule.__init__(self, parent)
47 self.parent.title =
"Custom VR Pipeline Example"
48 self.parent.categories = [
"Examples"]
49 self.parent.dependencies = []
50 self.parent.contributors = []
51 self.parent.helpText =
""
52 self.parent.acknowledgementText =
""
57 slicer.app.connect(
"startupCompleted()", _sceneConnector.connect)
61 """In this example, we will display a custom volume rendering and use VTK compute shader for processing the volume."""
67 _sceneConnector.connect()
68 ScriptedLoadableModuleWidget.setup(self)
71 layout = qt.QVBoxLayout(widget)
85 toggleDisplayButton = qt.QPushButton(
"Toggle display")
87 layout.addWidget(toggleDisplayButton)
90 self.layout.addWidget(widget)
97 wasVisible = CustomVRPipeline.GetVRNodeVisibility(volumeNode, slicer.mrmlScene)
98 vrNode = CustomVRPipeline.CreateVRNode(volumeNode, slicer.mrmlScene)
99 CustomVRPipeline.SetVRNodeVisible(vrNode,
not wasVisible)
103 Called when the module is about to be unloaded and by slicer.util.reloadScriptedModule before the widget is
104 rebuilt (module Reload button).
106 Here, we disconnect the pipelines from the scene (unregister the pipeline creator from the factory
107 singleton and remove the scene observer). Without this, every reload would add a duplicate creator
108 and observer, and the previously registered creator would keep instantiating pipelines from the
111 _sceneConnector.disconnect()
112 ScriptedLoadableModuleWidget.cleanup(self)
116 """Custom VR pipeline"""
120 In the pipeline creation, we create the different VTK objects.
121 Here, the mapper properties are static, but they could be set in the data node and be reactive.
130 colorFunction = vtkColorTransferFunction()
131 colorFunction.AddRGBPoint(0, 0.0, 0.0, 0.0)
132 colorFunction.AddRGBPoint(3900, 1.0, 1.0, 1.0)
135 opacityFunction = vtkPiecewiseFunction()
136 opacityFunction.AddPoint(1000, 0.0)
137 opacityFunction.AddPoint(1900, 1.0)
138 opacityFunction.AddPoint(3900, 1.0)
165 node.SetAttribute(
"VolumeNodeID", volumeNode.GetID())
166 node.SetAttribute(
"IsVisible", str(0))
167 return scene.AddNode(node)
171 vrNode = cls.
GetVRNode(volumeNode, scene)
180 value = node.GetAttribute(
"IsVisible")
if node
is not None else None
181 return bool(int(value))
if value
else False
185 node.SetAttribute(
"IsVisible", str(int(isVisible)))
190 Returns True if the input vktMRMLNode is a scripted node and has the pipeline type attribute matching the
191 current pipeline class.
193 return isinstance(node, vtkMRMLScriptedModuleNode)
and node.GetAttribute(
"PipelineType") == cls.
_GetClassName()
197 cls, viewNode: vtkMRMLAbstractViewNode, node: vtkMRMLNode,
198 ) -> vtkMRMLLayerDMScriptedPipeline |
None:
200 Since we are creating pipelines for 3D views only, we check here if the view node is a ThreedView node and
201 if the node matches the current pipeline type (i.e. was created using the _CreatePipelineNode method).
204 if not cls.
IsPipelineNode(node)
or not isinstance(viewNode, vtkMRMLViewNode):
212 Convenience method to get the name of the current class.
213 This method will return the actual class name for inheriting classes.
219 Triggered when the pipeline is displayed on a new renderer.
220 default behavior: does nothing.
222 Here, we add our actor to the input renderer.
223 If the pipeline renderer has changed, the pipeline's UpdateDisplay method will be triggered and in turn its
224 UpdateFromMRML method will be triggered.
226 Since we don't control the actual renderer used by the pipeline, this should be used systematically.
227 See also: self.GetRenderer()
230 if renderer
is None or renderer.HasViewProp(self.
_actor):
232 renderer.AddViewProp(self.
_actor)
236 Triggered when the pipeline is removed from its previous renderer.
237 default behavior: does nothing.
239 Here, we add our actor to the input renderer.
240 If the pipeline renderer has changed, the pipeline's UpdateDisplay method will be triggered and in turn its
241 UpdateFromMRML method will be triggered.
243 Since we don't control the actual renderer used by the pipeline, this should be used systematically.
244 See also: self.GetRenderer()
247 if renderer
is None or not renderer.HasViewProp(self.
_actor):
249 renderer.RemoveViewProp(self.
_actor)
253 Triggered by self.UpdateDisplay() calls:
254 - Called automatically at pipeline creation / add to the render window
255 - Called automatically when switching renderer
256 Override to update the representation of the pipeline in the different views.
258 See also: self.RequestRender()
259 default behavior: does nothing.
267 Observer update callback.
268 Triggered when any object & events observed using UpdateObservation is triggered.
270 :param obj: vtkObject instance which triggered the callback
271 :param eventId: Event id which triggered the callback
272 :param callData: Optional observer call data. Use self.CastCallData(callData, vtkType) to convert to Python
283 Set the display node for the pipeline has changed (initialization).
284 default behavior: Stored and display node is observed for vtkCommand::ModifiedEvent.
285 See also: self.UpdateObservation(prevObj, newObj, eventIds)
286 See also: self.OnUpdate(obj, eventId, callData)
288 :param node: The new instance of display node for the pipeline
295 transform = vtkTransform()
298 transform.SetMatrix(ijkToWorldMatrix)
300 self.
_actor.SetUserTransform(transform)
301 self.
_mapper.SetInputConnection(volumeNode.GetImageDataConnection()
if volumeNode
else None)
305 Converts a volume's IJK coordinates to World coordinates.
307 :return: the IJK to World matrix, or None if there is no volume node.
314 transformNode = volumeNode.GetParentTransformNode()
316 ijkToWorldMatrix = vtkMatrix4x4()
317 if not transformNode:
318 volumeNode.GetIJKToRASMatrix(ijkToWorldMatrix)
319 return ijkToWorldMatrix
322 if not transformNode.IsTransformToWorldLinear():
326 ijkToRasMatrix = vtkMatrix4x4()
327 volumeNode.GetIJKToRASMatrix(ijkToRasMatrix)
330 nodeToWorldMatrix = vtkMatrix4x4()
331 transformNode.GetMatrixTransformToWorld(nodeToWorldMatrix)
334 vtkMatrix4x4.Multiply4x4(nodeToWorldMatrix, ijkToRasMatrix, ijkToWorldMatrix)
335 return ijkToWorldMatrix
341 def RemoveVRNode(cls, volumeNode: vtkMRMLVolumeNode, scene: vtkMRMLScene):
345 scene.RemoveNode(node)
347 logging.warning(f
"No VR node for: {volumeNode.GetID()}")
350 def GetVRNode(cls, volumeNode: vtkMRMLVolumeNode, scene: vtkMRMLScene):
351 for node
in slicer.util.getNodesByClass(
"vtkMRMLScriptedModuleNode", scene):
357 """Convenience method to check if the pipeline's volume node is visible."""
359 if volumeNode
is None:
361 return bool(volumeNode.GetDisplayVisibility())
365 Convenience method to get the volume node associated with the pipeline's data node.
367 Here, we use the following APIs:
368 - GetScene: This will return the scene on which the pipeline is attached
369 - GetDisplayNode: This will return the pipeline's data node instance
371 return self.GetScene().GetNodeByID(self.
_GetVolumeNodeID(self.GetDisplayNode()))
375 Convenience method to update the volume and the volume's transform node observers.
377 Here, we use the UpdateObservation method:
378 - UpdateObservation(vtkObject* prevObj, vtkObject* obj, const std::vector<unsigned long>& events) -> bool
379 - UpdateObservation(vtkObject* prevObj, vtkObject* obj, unsigned long event = vtkCommand::ModifiedEvent) -> bool
381 This method should be used to add an observer on VTK object.
382 By default, the object's modified event will be observed.
383 The method also supports lists of events.
385 On modify event, the class's self.OnUpdate method will be called.
387 Warning: prevObj is not mutated by this call. To update the pointer, a manual set is required after update.
389 See also: self.OnUpdate
401 Convenience method to update the volume's transform node observer.
403 Note: Here we explicitly observe the transform modified event as modifying the transform doesn't trigger
406 See also: self._ObserveVolumeNode
412 self.UpdateObservation(
415 vtkMRMLTransformNode.TransformModifiedEvent,
424 self.UpdateObservation(self.
_imageData, imageData)
429 """Convenience method to get the volume node ID attached to the input MRML node."""
431 return node.GetAttribute(
"VolumeNodeID")
437 Connects this module's pipelines to the scene: creates pipelines for our VR data nodes and garbage
438 collects the VR data node when the associated volume node is removed from the scene.
440 The ScriptedPipelineSceneConnector base class keeps track of what was connected so that the connection
441 can be undone (module unload) or replaced (module reload).
445 return [CustomVRPipeline]
450 @calldata_type(VTK_OBJECT)
453 Garbage collect our VR data node when the associated volume node is removed from the scene.
454 Note: We use self.getScene() instead of the slicer.mrmlScene singleton so that the connector can be
455 used with an explicit scene (for instance in trame-slicer where the singleton is not available).
457 if isinstance(node, vtkMRMLVolumeNode):
458 CustomVRPipeline.RemoveVRNode(node, self.getScene())
_ObserveVolumeTransformNode(self)
OnUpdate(self, obj, eventId, callData)
_GetVolumeNodeID(cls, node)
None OnRendererAdded(self, vtkRenderer renderer)
IsPipelineNode(cls, node)
vtkMRMLScriptedModuleNode CreateVRNode(cls, volumeNode, scene)
SetVRNodeVisible(cls, node, bool isVisible)
_ObserveVolumeImageData(self)
vtkMRMLVolumeNode|None _volumeNode
SetDisplayNode(self, node)
vtkMRMLVolumeNode|None _GetVolumeNode(self)
RemoveVRNode(cls, vtkMRMLVolumeNode volumeNode, vtkMRMLScene scene)
vtkMRMLLayerDMScriptedPipeline|None TryCreatePipeline(cls, vtkMRMLAbstractViewNode viewNode, vtkMRMLNode node)
None OnRendererRemoved(self, vtkRenderer renderer)
_UpdateActorVisibility(self)
bool _IsVolumeVisible(self)
GetVRNode(cls, vtkMRMLVolumeNode volumeNode, vtkMRMLScene scene)
bool GetVRNodeVisibility(cls, volumeNode, scene)
_UpdateMapperConnection(self)
_GetVolumeTransformMatrixToWorld(self)
list getSceneObservers(self)