3D Slicer Displayable Manager architecture reminder(s)
The library is integrated in 3D Slicer’s displayable manager system. To properly understand its behavior and connection to the existing architecture, a summary of the displayable manager architecture and behavior is done in this section.
Developers already familiar with this system can skip to the next documentation section.
Access to the original documentation can be found on the 3D Slicer read the docs webpage.
Main components
3D Slicer’s rendering is handled by:
Layout manager: Responsible for creating the application Qt layout based on an XML description.
(Qt) views: Views are based on the CTK abstract view and populate the layout manager
Displayable manager group: DM groups are set to the views and handle
Displayable managers: A collection of displayable managers living in the DM group
(VTK) Render Window: VTK handles the low-level rendering. Interactions / actor creation / etc. are dispatched from / to Slicer by the displayable manager system.
Rendering initialization
The diagram below presents the view initialization sequence:
---
title: Rendering initialization sequence
---
sequenceDiagram
participant User
participant LayoutManager as Layout Manager
participant Widget as Widget
participant DMGroup as Displayable Manager Group
participant DM as Displayable Manager
participant Scene
participant RW as Render Window
User ->> LayoutManager: Switch layout
LayoutManager ->> LayoutManager: Load layout XML
LayoutManager ->> Scene: Add missing view node
LayoutManager ->> Widget: Create widget from node
Widget ->> DMGroup: Create / Initialize
DMGroup ->> DM: Create / Initialize
DM ->> Scene: Update from MRML
DM ->> RW: Add VTK actors
The layout manager is responsible for creating new view data nodes when switching from one layout to another. View data nodes are singleton nodes with unique IDs. When the view nodes are not yet present in the scene, the layout manager will create them. For each created node type, it will use the associated factory responsible for instantiating the underlying Qt widgets.
During the creation of the Qt widgets, each widget will create a new instance of displayable manager group and one instance of each displayable manager registered in their factory singleton instance.
Thanks to this mechanism, the displayable managers present in the views are independent from one-another and each widget will have their instance of each displayable manager.
This initialization implies that little connection of the displayable manager is done with other application components. The displayable managers will have access to:
The Scene
The application logic
The parent widget’s default vtkRenderer instance (and by extension its render window)
The parent widget’s view node instance
By convention, one displayable manager will be responsible with one type of node type although some displayable managers may contain other linked responsibilities to ease implementation.
By convention, most displayable managers are reactive to display node types which contain only representation information of data nodes to which they are attached.
Relationship between data nodes / display nodes / representation
The diagram below shows the relationship between a data node, its display node and the other components described before.
flowchart LR
modelNode_1 --> modelDisplayNode_1
modelNode... --> modelDisplayNode...
modelDisplayNode_1 --> VTKPipeline_3D_1
modelDisplayNode_1 --> VTKPipeline_2D_1
modelDisplayNode... --> VTKPipeline_3D...
modelDisplayNode... --> VTKPipeline_2D...
subgraph view_3d [3D View]
subgraph model_dm_3d [Model Displayable Manager]
VTKPipeline_3D_1
VTKPipeline_3D...
end
end
subgraph view_axial [Axial View]
subgraph model_dm_axial [Model Displayable Manager]
VTKPipeline_2D_1
VTKPipeline_2D...
end
end
In this example, changes to the either the source data (polydata / connectivity) or its display properties (colors / opacity / …) will trigger an update in the VTK pipeline.
Reactivity handling
To operate normally, the displayable manager needs to properly handle changes happening both in the scene, the view and the display node it is currently displaying in its VTK renderer.
The scene events can be handled thanks to the following abstract methods present in the abstract displayable maanger class :
OnMRMLSceneNodeAdded(vtkMRMLNode* node): Watch to add a widget / representation for given nodes
OnMRMLSceneNodeRemoved(vtkMRMLNode* node): Watch to remove a widget / representation for a given node on delete
OnMRMLSceneEndImport(): Watch for an MRB file that has finished importing.
OnMRMLSceneEndClose(): Watch to clean up after a scene close.
In addition to these events, the following data events need to be manually observed at the pipeline or displayable level:
Camera changes
View node changes
Display node changes
Data node changes
The type of event to observe and the reaction to the events will depend on the specifics of the displayable manager.
Interaction handling
The previous sections detailed how a representation is created and displayed in the views with the displayable manager system. In this section, we will have a rapid look at the interaction system.
At low level, the user interactions are intercepted by the VTK layer. To simplify the customization and complex handling of user interactions, the interactions are forwarded to the displayable managers through the vtkMRMLViewInteractorStyle. The interaction logic is summarized in the diagram below:
---
title: Simplified Displayable Manager interaction logic
---
stateDiagram-v2
state if_start <<choice>>
[*] --> if_start
if_start --> [*]: DisplayableManagers == nullptr
Handle3DEvent: Handle 3D Events
Handle3DEvent --> FindCandidate
if_start --> FindCandidate: else
if_start --> Handle3DEvent: 3D Event
FindCandidate: Find closest DM that can process
state if_find <<choice>>
FindCandidate --> if_find
if_find --> [*]: Closest == nullptr
if_find --> UpdateFocus: else
state if_focus <<choice>>
UpdateFocus: Update Focused
UpdateFocus --> if_focus
if_focus --> [*]: Focused == nullptr
ProcessEvent: ProcessInteraction
if_focus --> ProcessEvent: else
ProcessEvent --> [*]
The displayable manager provides the following methods to handle interactions :
bool CanProcessInteractionEvent(vtkMRMLInteractionEventData* eventData, double& distance2): Return true + distance to the event if the displayable manager can process the interaction event present at given coordinates.
bool ProcessInteractionEvent(vtkMRMLInteractionEventData* eventData): Return true / false and process the actual event if the displayable manager was selected (more on that below).
void LoseFocus(vtkMRMLInteractionEventData* eventData): To handle a switch to another displayable manager if the displayable manager was handling prior events and another displayable manager was chosen for newer events.