Segmentation effect pipeline
The segmentation effect pipeline(s) are responsible for displaying visual feedbacks or provide user interaction features in trame-slicer’s render windows (ie. Slice and 3D views).
See also
The segmentation effect pipeline relies on the SlicerLayerDisplayableManager library for their inner working and users only need to implement specific VTK pipeline logic for their implementation.
Naming conventions
As the segmentation effect pipeline inherits the vtkMRMLLayerDMScriptedPipeline which is a C++ VTK wrapping, the VTK naming conventions are kept for these classes.
API
Effects pipeline have access to the following attributes :
GetEffectParameterNode(self): The effect’s vtkMRMLScriptedModuleNode
GetModifier(self): The SegmentModifier responsible for modifying the active segmentation
GetSegmentation(self): The Segmentation instance the effect is applied on
OnEffectParameterUpdate(self): The callback triggered when the effect’s parameters have changed
OnViewModified(self): The callback triggered when the view on which the effect is attached is modified
ProcessInteractionEvent(self, event_data: vtkMRMLInteractionEventData): Callback called when the user interacts with the view if the effect pipeline is the most appropriate for handling the interaction (function of the CanProcessInteractionEvent call)
CanProcessInteractionEvent(self, eventData: vtkMRMLInteractionEventData): Callback called when the user interacts with the view (ie. mouse move events / clicks / etc.)
_effect: The effect which instantiated the effect pipeline
_view: The AbstractView (SliceView / 3D View / …) in which the effect is displayed
Examples
Visual feedback
The following snippet provides an example of threshold effect pipeline.
The class inherits the SegmentationEffectPipeline base class and overrides its
creator. In its creator, it instantiates a VTK pipeline to display the threshold
area in the 2D views.
The VTK objects are added to the VTK render window in the OnRendererAdded
method.
Periodic updates of the display is wrapped in an asyncio task for periodicity. As this effect only provides a visual feedback, it doesn’t need to implement any interaction method.
Connection to the actual volume and segmentation node are done in the
OnViewModified and _UpdateThreshold respectively.
Access to the parameters is done using the
create_scripted_module_dataclass_proxy method. This helper method allows to
convert a vtkMRMLScriptedModuleNode present in the scene to a dataclass object
instance and simplifies transfer of strongly type parameters between Slicer and
trame. )
class SegmentationThresholdPipeline2D(SegmentationEffectPipeline):
def __init__(self):
super().__init__()
self.lookup_table = vtkLookupTable()
self.lookup_table.SetRampToLinear()
self.lookup_table.SetNumberOfTableValues(2)
self.lookup_table.SetTableRange(0, 1)
self.lookup_table.SetTableValue(0, 0, 0, 0, 0)
self.color_mapper = vtkImageMapToRGBA()
self.color_mapper.SetOutputFormatToRGBA()
self.color_mapper.SetLookupTable(self.lookup_table)
self.threshold = vtkImageThreshold()
self.threshold.SetInValue(1)
self.threshold.SetOutValue(0)
self.threshold.SetOutputScalarTypeToUnsignedChar()
# Feedback actor
self.mapper = vtkImageMapper()
self.dummy_image = vtkImageData()
self.dummy_image.AllocateScalars(VTK_UNSIGNED_INT, 1)
self.mapper.SetInputData(self.dummy_image)
self.actor = vtkActor2D()
self.actor.VisibilityOff()
self.actor.SetMapper(self.mapper)
self.mapper.SetColorWindow(255)
self.mapper.SetColorLevel(128)
# Setup pipeline
self.color_mapper.SetInputConnection(self.threshold.GetOutputPort())
self.mapper.SetInputConnection(self.color_mapper.GetOutputPort())
# Preview coroutine
self.preview_update_period_s = 0.1
self.preview_steps = 10
self.preview_state = 0
self.preview_direction = 1
loop = asyncio.get_event_loop()
self.preview_task = loop.create_task(self._UpdatePreviewState())
async def _UpdatePreviewState(self):
while True:
await asyncio.sleep(self.preview_update_period_s)
self.preview_state += self.preview_direction
if self.preview_state > self.preview_steps or self.preview_state < 0:
self.preview_direction *= -1
self.preview_state = max(0, min(self.preview_steps, self.preview_state))
self._UpdateThreshold()
def OnRendererAdded(self, renderer: vtkRenderer | None) -> None:
super().OnRendererAdded(renderer)
if renderer:
renderer.AddViewProp(self.actor)
def OnRendererRemoved(self, renderer: vtkRenderer) -> None:
super().OnRendererRemoved(renderer)
if renderer and renderer.HasViewProp(self.actor):
renderer.RemoveViewProp(self.actor)
def SetActive(self, isActive: bool):
super().SetActive(isActive)
self.actor.SetVisibility(isActive)
self.RequestRender()
def OnEffectParameterUpdate(self):
super().OnEffectParameterUpdate()
if not self.GetEffectParameterNode():
return
self._UpdateThreshold()
def _UpdateThreshold(self):
if not self.IsActive():
return
param = create_scripted_module_dataclass_proxy(
ThresholdParameters, self.GetEffectParameterNode(), self.GetScene()
)
active_id = self.GetModifier().active_segment_id
if segmentation := self.GetSegmentation():
opacity = 0.5 + 0.5 * self.preview_state / self.preview_steps
r, g, b = segmentation.get_segment_properties(active_id).color
self.lookup_table.SetTableValue(1, r, g, b, opacity)
self.threshold.ThresholdBetween(param.min_value, param.max_value)
self.OnViewModified()
self.threshold.Update()
self.RequestRender()
def SetView(self, view: SliceView):
if self._view:
self._view.modified.disconnect(self.OnViewModified)
super().SetView(view)
if self._view:
self._view.modified.connect(self.OnViewModified)
def OnViewModified(self, *_):
if not self._view or not self.GetModifier():
return
self.threshold.SetInputConnection(
self._view.get_volume_layer_logic(self.GetModifier().volume_node)
.GetReslice()
.GetOutputPort()
)