Program Listing for File DisplayableManagerTest.py

Return to documentation for file (Testing/Python/DisplayableManagerTest.py)

import slicer
from slicer import (
    vtkMRMLLayerDMPipelineFactory,
    vtkMRMLLayerDMPipelineI,
    vtkMRMLLayerDMPipelineManager,
    vtkMRMLLayerDMPipelineScriptedCreator,
    vtkMRMLLayerDisplayableManager,
    vtkMRMLScene,
    vtkMRMLScriptedModuleNode,
    vtkMRMLSliceViewDisplayableManagerFactory,
    vtkMRMLThreeDViewDisplayableManagerFactory,
)
from slicer.ScriptedLoadableModule import ScriptedLoadableModuleTest
from vtk import VTK_OBJECT, vtkActor, vtkImageData, vtkObjectFactory, vtkRenderer

from MockPipeline import MockPipeline


class DisplayableManagerTest(ScriptedLoadableModuleTest):
    def setUp(self):
        slicer.mrmlScene.Clear(0)

        # Verify the DM can be created by the vtk Object Factory
        assert vtkObjectFactory.CreateInstance(vtkMRMLLayerDisplayableManager.__name__) is not None

        # Register the DM to the default views
        vtkMRMLLayerDisplayableManager.RegisterInDefaultViews()

        # Verify registration is correct for both 2D and 3D views
        assert vtkMRMLLayerDisplayableManager.IsRegisteredInFactory(
            vtkMRMLSliceViewDisplayableManagerFactory.GetInstance()
        )
        assert vtkMRMLLayerDisplayableManager.IsRegisteredInFactory(
            vtkMRMLThreeDViewDisplayableManagerFactory.GetInstance()
        )

        self.threeDNode = slicer.mrmlScene.GetNodeByID("vtkMRMLViewNode1")
        self.node = vtkMRMLScriptedModuleNode()

        # Register a pipeline creator with static pipeline
        self.pipeline = MockPipeline()
        self.factory = vtkMRMLLayerDMPipelineFactory.GetInstance()
        self.creator = vtkMRMLLayerDMPipelineScriptedCreator()
        self.creator.SetPythonCallback(
            lambda view, node: self.pipeline if view == self.threeDNode and node == self.node else None
        )
        self.factory.AddPipelineCreator(self.creator)

    def tearDown(self):
        self.factory.RemovePipelineCreator(self.creator)

    def test_registered_pipelines_can_add_their_actors_to_views(self):
        # Configure pipeline renderer added to add a new actor
        actor = vtkActor()

        def onRendererAdded(renderer: vtkRenderer):
            if renderer:
                renderer.AddViewProp(actor)

        self.pipeline.mockOnRendererAdded.side_effect = onRendererAdded

        # Trigger pipeline creation by adding a node to the scene
        slicer.mrmlScene.AddNode(self.node)

        # Verify the pipeline's actor is present in the threed view
        renderWindow = slicer.app.layoutManager().viewWidget(self.threeDNode).viewWidget().renderWindow()
        renderer = renderWindow.GetRenderers().GetItemAsObject(0)
        assert renderer.HasViewProp(actor)

    def test_registered_pipelines_have_access_to_scene_manager_view_and_display_node(self):
        # Trigger pipeline creation by adding a new model node to the scene
        slicer.mrmlScene.AddNode(self.node)

        # Check getters point to the right instances
        assert self.pipeline.GetScene() == slicer.mrmlScene
        assert isinstance(self.pipeline.GetPipelineManager(), vtkMRMLLayerDMPipelineManager)
        assert self.pipeline.GetViewNode() == self.threeDNode
        assert self.pipeline.GetDisplayNode() == self.node

    def test_registered_pipelines_can_override_setters_to_scene_manager_view_and_display_node(self):
        # Trigger pipeline creation by adding a new model node to the scene
        slicer.mrmlScene.AddNode(self.node)

        # Check overridden setters where called
        self.pipeline.mockSetScene.assert_called_with(slicer.mrmlScene)
        self.pipeline.mockSetPipelineManager.assert_called_with(self.pipeline.GetPipelineManager())
        self.pipeline.mockSetViewNode.assert_called_with(self.threeDNode)
        self.pipeline.mockSetDisplayNode.assert_called_with(self.node)

    def test_pipelines_have_access_to_sibling_pipelines(self):
        # Register a second pipeline creator
        otherNode = vtkMRMLScriptedModuleNode()
        otherPipeline = MockPipeline()

        otherCreator = vtkMRMLLayerDMPipelineScriptedCreator()
        otherCreator.SetPythonCallback(
            lambda view, node: otherPipeline if view == self.threeDNode and node == otherNode else None
        )
        self.factory.AddPipelineCreator(otherCreator)

        # Add both nodes to the scene for pipeline creation to happen
        slicer.mrmlScene.AddNode(self.node)
        slicer.mrmlScene.AddNode(otherNode)

        # Assert the first pipeline has access to the second one
        assert self.pipeline.GetNodePipeline(otherNode) == otherPipeline

    def test_pipelines_can_add_observers_to_vtk_objects_and_events(self):
        def onSetScene(scene: vtkMRMLScene):
            assert self.pipeline.GetScene() != scene
            self.pipeline.UpdateObserver(self.pipeline.GetScene(), scene, vtkMRMLScene.NodeAddedEvent)
            vtkMRMLLayerDMPipelineI.SetScene(self.pipeline, scene)

        # Add observer on the scene when its added
        self.pipeline.mockSetScene.side_effect = onSetScene

        slicer.mrmlScene.AddNode(self.node)
        self.pipeline.mockSetScene.assert_called_once()

        modelNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode")
        self.pipeline.mockOnUpdate.assert_called_once()
        args = self.pipeline.mockOnUpdate.call_args[0]
        assert args[0] == slicer.mrmlScene
        assert args[1] == vtkMRMLScene.NodeAddedEvent
        assert self.pipeline.CastCallData(args[2], VTK_OBJECT) == modelNode

    def test_pipelines_update_display_is_called_at_init(self):
        slicer.mrmlScene.AddNode(self.node)
        self.pipeline.mockUpdatePipeline.assert_called_once()

    def test_pipelines_update_display_is_called_at_reset(self):
        slicer.mrmlScene.AddNode(self.node)
        self.pipeline.mockUpdatePipeline.reset_mock()

        self.pipeline.ResetDisplay()
        self.pipeline.mockUpdatePipeline.assert_called_once()

    def test_pipeline_exceptions_are_propagated_to_python(self):
        _error_msg = "Something went wrong in Python"
        self.pipeline.mockUpdatePipeline.side_effect = RuntimeError(_error_msg)

        with self.assertRaises(RuntimeError) as context:
            slicer.mrmlScene.AddNode(self.node)
        assert _error_msg in str(context.exception)

    def test_can_render_to_image_data(self):
        render_window = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()

        # Direct return value call
        image = vtkMRMLLayerDisplayableManager.RenderWindowBufferToImage(render_window)
        assert isinstance(image, vtkImageData)
        assert image.GetDimensions()[0] == render_window.GetSize()[0]
        assert image.GetDimensions()[1] == render_window.GetSize()[1]

        # Mutated call
        image = vtkImageData()
        vtkMRMLLayerDisplayableManager.RenderWindowBufferToImage(render_window, image)
        assert isinstance(image, vtkImageData)
        assert image.GetDimensions()[0] == render_window.GetSize()[0]
        assert image.GetDimensions()[1] == render_window.GetSize()[1]