Hi there,
I’m new to 3D Slicer and now I’m developing a Python scripted module.
Currently, I’m trying to add a checkbox to control the display of the ROI bounding box in the volume rendering scene, which is actually the same as the ‘Display ROI’ checkbox in the volume rendering module.
Now the ROI bounding box can be shown by clicking the checkbox, but the problem is that when I switch the input volume, the state of the checkbox on the GUI panel is always the same.
As shown in the figure, I first turned on the ROI bounding box for ‘MRHead’, but when I switch the input volume to a new volume(‘CTChest’), the state of the checkbox is also checked, but I haven’t checked it yet:
Is there any way to make the state of the checkbox and the input volume correspond one-to-one?
This seems to be because there doesn’t seem to be a connection between the input volume node and the checkbox widget.
However, the checkbox widget derived from CTK:
self.ROICheckBox = ctk.ctkCheckBox()
It seems means I can’t connect it to the input volume by something like:
self.ROICheckBox.setMRMLVolumeNode(VolumeNode)
I attached my source code below, hoping to get specific guidance on how to fix this bug. Please make sure you turn on the volume rendering for both volumes first to test it.
Thank you very much in advance!
import logging
import os
import unittest
import vtk, qt, ctk, slicer
import SegmentStatistics
from slicer.ScriptedLoadableModule import *
from slicer.util import TESTING_DATA_URL
from slicer.util import VTKObservationMixin
class ScriptedLoadableModuleTemplate(ScriptedLoadableModule):
"""Uses ScriptedLoadableModule base class, available at:
https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
"""
def __init__(self, parent):
ScriptedLoadableModule.__init__(self, parent)
self.parent.title = "TestCheckbox" # TODO make this more human readable by adding spaces
self.parent.categories = ["Examples"]
self.parent.dependencies = []
self.parent.contributors = ["John Doe (AnyWare Corp.)"] # replace with "Firstname Lastname (Organization)"
self.parent.helpText = """
The Help text for this scripted module.
"""
self.parent.helpText += self.getDefaultModuleDocumentationLink()
self.parent.acknowledgementText = """
The acknowledgementText
"""
#
# ScriptedLoadableModuleTemplateWidget
#
class ScriptedLoadableModuleTemplateWidget(ScriptedLoadableModuleWidget, VTKObservationMixin):
"""Uses ScriptedLoadableModuleWidget base class, available at:
https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
"""
def __init__(self, parent=None):
"""
Called when the user opens the module the first time and the widget is initialized.
"""
ScriptedLoadableModuleWidget.__init__(self, parent)
VTKObservationMixin.__init__(self) # needed for parameter node observation
self.logic = None
self._parameterNode = None
self._updatingGUIFromParameterNode = False
def setup(self):
ScriptedLoadableModuleWidget.setup(self)
# Instantiate and connect widgets ...
#
# Parameters Area
#
parametersCollapsibleButton = ctk.ctkCollapsibleButton()
parametersCollapsibleButton.text = "ROI"
self.layout.addWidget(parametersCollapsibleButton)
parametersFormLayout = qt.QFormLayout(parametersCollapsibleButton)
self.logic = ScriptedLoadableModuleTemplateLogic()
#
# input volume selector
#
self.inputSelector = slicer.qMRMLNodeComboBox()
self.inputSelector.nodeTypes = ["vtkMRMLScalarVolumeNode"]
self.inputSelector.selectNodeUponCreation = True
self.inputSelector.addEnabled = False
self.inputSelector.removeEnabled = False
self.inputSelector.noneEnabled = False
self.inputSelector.showHidden = False
self.inputSelector.showChildNodeTypes = False
self.inputSelector.setMRMLScene(slicer.mrmlScene)
self.inputSelector.setToolTip( "Pick the input to the algorithm." )
parametersFormLayout.addRow("Input Volume: ", self.inputSelector)
#
# ROI checkbox
#
self.ROICheckBox = ctk.ctkCheckBox()
self.ROICheckBox.enabled = True
self.ROICheckBox.checked = False
parametersFormLayout.addRow("ROI:", self.ROICheckBox)
# self.ROICheckBox.setMRMLVolumeNode(volNode)
self.addObserver(slicer.mrmlScene, slicer.mrmlScene.StartCloseEvent, self.onSceneStartClose)
self.addObserver(slicer.mrmlScene, slicer.mrmlScene.EndCloseEvent, self.onSceneEndClose)
# connections
self.inputSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.updateParameterNodeFromGUI)
self.ROICheckBox.connect('clicked(bool)', self.onCheckBox)
self.initializeParameterNode()
# Add vertical spacer
self.layout.addStretch(1)
def onCheckBox(self):
VolumeNode = self._parameterNode.GetNodeReference('InputVolume')
if VolumeNode:
self.logic.updateROIOnVolume(VolumeNode, self.ROICheckBox.checked)
def cleanup(self):
"""
Called when the application closes and the module widget is destroyed.
"""
self.removeObservers()
def enter(self):
"""
Called each time the user opens this module.
"""
# Make sure parameter node exists and observed
self.initializeParameterNode()
def exit(self):
"""
Called each time the user opens a different module.
"""
# Do not react to parameter node changes (GUI wlil be updated when the user enters into the module)
self.removeObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode)
def onSceneStartClose(self, caller, event):
"""
Called just before the scene is closed.
"""
# Parameter node will be reset, do not use it anymore
self.setParameterNode(None)
def onSceneEndClose(self, caller, event):
"""
Called just after the scene is closed.
"""
# If this module is shown while the scene is closed then recreate a new parameter node immediately
if self.parent.isEntered:
self.initializeParameterNode()
def initializeParameterNode(self):
"""
Ensure parameter node exists and observed.
"""
# Parameter node stores all user choices in parameter values, node selections, etc.
# so that when the scene is saved and reloaded, these settings are restored.
self.setParameterNode(self.logic.getParameterNode())
# Select default input nodes if nothing is selected yet to save a few clicks for the user
if not self._parameterNode.GetNodeReference("InputVolume"):
firstVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")
if firstVolumeNode:
self._parameterNode.SetNodeReferenceID("InputVolume", firstVolumeNode.GetID())
def setParameterNode(self, inputParameterNode):
"""
Set and observe parameter node.
Observation is needed because when the parameter node is changed then the GUI must be updated immediately.
"""
# Unobserve previously selected parameter node and add an observer to the newly selected.
# Changes of parameter node are observed so that whenever parameters are changed by a script or any other module
# those are reflected immediately in the GUI.
if self._parameterNode is not None:
self.removeObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode)
self._parameterNode = inputParameterNode
if self._parameterNode is not None:
self.addObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode)
# Initial GUI update
self.updateGUIFromParameterNode()
def updateGUIFromParameterNode(self, caller=None, event=None):
"""
This method is called whenever parameter node is changed.
The module GUI is updated to show the current state of the parameter node.
"""
if self._parameterNode is None or self._updatingGUIFromParameterNode:
return
# Make sure GUI changes do not call updateParameterNodeFromGUI (it could cause infinite loop)
self._updatingGUIFromParameterNode = True
# Update node selectors and sliders
self.inputSelector.setCurrentNode(self._parameterNode.GetNodeReference("InputVolume"))
self.ROICheckBox.checked = (self._parameterNode.GetParameter("ROI") == "true")
# All the GUI updates are done
self._updatingGUIFromParameterNode = False
def updateParameterNodeFromGUI(self, caller=None, event=None):
"""
This method is called when the user makes any change in the GUI.
The changes are saved into the parameter node (so that they are restored when the scene is saved and loaded).
"""
if self._parameterNode is None or self._updatingGUIFromParameterNode:
return
wasModified = self._parameterNode.StartModify() # Modify all properties in a single batch
self._parameterNode.SetNodeReferenceID("InputVolume", self.inputSelector.currentNodeID)
self._parameterNode.SetParameter("ROI", "true" if self.ROICheckBox.checked else "false")
self._parameterNode.EndModify(wasModified)
#
# ScriptedLoadableModuleTemplateLogic
#
class ScriptedLoadableModuleTemplateLogic(ScriptedLoadableModuleLogic):
"""This class should implement all the actual
computation done by your module. The interface
should be such that other python code can import
this class and make use of the functionality without
requiring an instance of the Widget.
Uses ScriptedLoadableModuleLogic base class, available at:
https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
"""
def __init__(self):
"""
Called when the logic class is instantiated. Can be used for initializing member variables.
"""
ScriptedLoadableModuleLogic.__init__(self)
def updateROIOnVolume(self, VolumeNode, ROIchecked=False):
VolumeLogic = slicer.modules.volumerendering.logic()
displayNode = VolumeLogic.GetFirstVolumeRenderingDisplayNode(VolumeNode)
displayNode.UnRegister(slicer.mrmlScene)
slicer.mrmlScene.AddNode(displayNode)
VolumeNode.AddAndObserveDisplayNodeID(displayNode.GetID())
VolumeLogic.UpdateDisplayNodeFromVolumeNode(displayNode, VolumeNode)
if ROIchecked == True:
displayNode.GetROINode().SetDisplayVisibility(True)
displayNode.SetCroppingEnabled(True)
else:
displayNode.GetROINode().SetDisplayVisibility(False)