Display Model Name on Mouse Click in 3D View

This post was flagged by the community and is temporarily hidden.

You can get displayable manager as described here: https://www.slicer.org/wiki/Documentation/Nightly/ScriptRepository#Get_displayable_manager_of_a_certain_type_for_a_certain_view

You can get 3D position for example from the crosshair node, which is moved when you hold down Shift key while you are moving the mouse over the 3D view (you can make the crosshair visible by clicking on crosshair icon in the toolbar). To get the model that is at the current crosshair position, you can run this code snippet:

crosshairNode = slicer.mrmlScene.GetNodeByID("vtkMRMLCrosshairNodedefault")
modelDisplayableManager.Pick3D(crosshairNode.GetCrosshairRAS())
modelNode = slicer.mrmlScene.GetNodeByID(modelDisplayableManager.GetPickedNodeID()).GetDisplayableNode()
print(modelNode.GetName())

You can observe crosshair node changes as shown here: https://www.slicer.org/wiki/Documentation/Nightly/ScriptRepository#Get_scalar_values_at_surface_of_a_model

Instead of the crosshair, you use markups fiducial points. They can be dropped and dragged on 3D model surface and provide 3D position and they can also be used to display a label.

You would add most of the code into methods in the module’s logic class (and you would use logic member variables instead of local variables for things that need to be persistent or you need to use frequently, such as handle to the crosshair observer, or the displayable manager). You would call logic functions from the GUI (e.g., add/remove crosshair node observer).

Thank you! I’m creating a free, open-source medical trainer app for medical students like myself, so your assistance is much appreciated.

I’m close to the solution but I am not setting up the crosshair observer correctly as I get the output seen below. The first printed result is correct, but then I get the TypeError as I move the crosshair around.

FMA7163_Skin
Set up crosshair tracker
TypeError: 'NoneType' object is not callable
TypeError: 'NoneType' object is not callable
TypeError: 'NoneType' object is not callable
TypeError: 'NoneType' object is not callable
TypeError: 'NoneType' object is not callable
TypeError: 'NoneType' object is not callable
TypeError: 'NoneType' object is not callable

Would you be able to guide me as to how I can change my code?

class DisplayName_v2Widget(ScriptedLoadableModuleWidget):

  def setup(self):
    ScriptedLoadableModuleWidget.setup(self)
    #
    # Parameters Area
    #
    parametersCollapsibleButton = ctk.ctkCollapsibleButton()
    parametersCollapsibleButton.text = "Update"
    self.layout.addWidget(parametersCollapsibleButton)
    # Layout within the dummy collapsible button
    parametersFormLayout = qt.QFormLayout(parametersCollapsibleButton)
    
    #
    # Start Button
    #
    self.startButton = qt.QPushButton("Start")
    self.startButton.toolTip = "Run the algorithm."
    self.startButton.enabled = True
    parametersFormLayout.addRow(self.startButton)
    # connections
    self.startButton.connect('clicked(bool)', self.onstartButton)

    #
    # Stop Button
    #
    self.stopButton = qt.QPushButton("Stop")
    self.stopButton.toolTip = "Stop the algorithm."
    self.stopButton.enabled = True
    parametersFormLayout.addRow(self.stopButton)
    # connections
    self.stopButton.connect('clicked(bool)', self.onstopButton)
    
    # Add vertical spacer
    self.layout.addStretch(1)

    self.logic = DisplayName_v2Logic()

  def cleanup(self):
    pass

  def onstartButton(self):
    self.logic.setupCrossHairTracker()

  def onstopButton(self):
    self.logic.stopCrossHairTracker()




class DisplayName_v2Logic(ScriptedLoadableModuleLogic):

  def setupCrossHairTracker(self):
    self.crosshairNode = slicer.util.getNode('Crosshair') 
    self.observationId = self.crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, self.printModelName())
    print("Set up crosshair tracker")

  def stopCrossHairTracker(self):
    self.crosshairNode.RemoveObserver(self.observationId)
    print("Stopped crosshair tracker")

  def printModelName(self):
    modelDisplayableManager = None
    threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)
    managers = vtk.vtkCollection()
    threeDViewWidget.getDisplayableManagers(managers)
    for i in range(managers.GetNumberOfItems()):
      obj = managers.GetItemAsObject(i)
      if obj.IsA('vtkMRMLModelDisplayableManager'):
        modelDisplayableManager = obj
        break
    if modelDisplayableManager is None:
      logging.error('Failed to find the model displayable manager')

    crosshairNode = slicer.mrmlScene.GetNodeByID("vtkMRMLCrosshairNodedefault")
    modelDisplayableManager.Pick3D(crosshairNode.GetCrosshairRAS())
    if(slicer.mrmlScene.GetNodeByID(modelDisplayableManager.GetPickedNodeID())):
      modelNode = slicer.mrmlScene.GetNodeByID(modelDisplayableManager.GetPickedNodeID()).GetDisplayableNode()
      print(modelNode.GetName())
    else:
      print("Nothing Selected")'

If you want to share more than a few lines of code, please upload it to github and post the link. Upload the full extension (all the files that the Extension Wizard generated) so that we can see the full context of what and how you do.

Thanks. I’ve added comments on github.
You will need to remove the top-level folder - CMakeLists.txt must be in the root folder of the repository. No need to put version name in module name (DisplayName_v2), as you can retrieve previous versions from a revision-controlled repository.

Thank you! Your comments helped me narrow down and fix the issue. If I want to pass in a class member variable into the callback function “printModelName,” how would I do that? I want to print the model name just one time, not repeatedly (see image). I would like to store the previously printed model name in a class member variable and compare it to the model name about to be printed. The github page is updated.

self.observationId = self.crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, self.printModelName)

Capture3

The callback function is a member of the class, so you can access all member variables directly (self.someVariable).

1 Like

Huge thank you to Andras! I’ve posted the working extension on my github page.

1 Like