How to get the clicked markup node in the 3D view?

Hi,

I’m interested in knowing what node is selected/hovered by the mouse when the left mouse button is pressed.
I’ve tried using the scene interactor and trying to use the vtk picker, but I’m not really able to get it to work.
I’ve also tried adding observers to my markup objects in the hope that they would receive the left mouse button event, but instead the display node receives NoEvent when I press the left mouse button which is not very useful.

So I’m wondering, how do I get the node that was clicked on in the 3D view. I know it must be possible as Slicer is able to highlight the markup object when the mouse is hovered over them, but I can’t really find the code that does that.

Thanks in advance!

Here’s what I got from the slicer-skill.

:robot: This post contains AI-generated content.

Here’s a reply you can post to the forum:


The piece you’re missing is that Slicer’s 3D view doesn’t really hand raw mouse events to MRML nodes — it routes them through displayable managers, which is also why your observers on the display nodes never see anything useful. The good news is that the model displayable manager already exposes the picker you want, and there’s an official snippet for it in the Script Repository under “Get 3D coordinates from 2D display coordinates” (gui.md).

Wire it up to the 3D view’s interactor and you get the clicked node directly:

import slicer, qt, vtk

threeDView = slicer.app.layoutManager().threeDWidget(0).threeDView()
viewNode = threeDView.mrmlViewNode()
modelDM = slicer.app.applicationLogic().GetViewDisplayableManagerByClassName(
    viewNode, "vtkMRMLModelDisplayableManager")

def onLeftClick(caller, event):
    x, y = caller.GetEventPosition()
    # VTK's y is bottom-up; the displayable manager's Pick expects that, so no flip needed.
    if modelDM.Pick(x, y) and modelDM.GetPickedNodeID():
        nodeID = modelDM.GetPickedNodeID()  # this is the *display* node ID
        displayNode = slicer.mrmlScene.GetNodeByID(nodeID)
        dataNode = displayNode.GetDisplayableNode() if displayNode else None
        ras = modelDM.GetPickedRAS()
        cellID = modelDM.GetPickedCellID()
        print(f"Clicked {dataNode.GetName() if dataNode else '(none)'} "
              f"at RAS={tuple(ras)} cell={cellID}")

interactor = threeDView.interactor()
tag = interactor.AddObserver(vtk.vtkCommand.LeftButtonPressEvent, onLeftClick, 0.0)
# To remove later: interactor.RemoveObserver(tag)

A couple of gotchas that probably bit you:

  • GetPickedNodeID() returns the display node ID, not the data node ID. Call GetDisplayableNode() on it to get the model/markup/segmentation node.
  • A priority of 0.0 (or negative) means you observe after the standard interactor style, so you don’t break camera rotation. If you want to suppress default behavior on a hit, raise the priority and call caller.AbortFlagOn().
  • vtkMRMLModelDisplayableManager.Pick() only finds things rendered as polydata (models, segmentation surfaces, markup glyphs). Volume rendering and slice planes need different displayable managers.

For hover (highlight as the cursor moves), swap LeftButtonPressEvent for vtk.vtkCommand.MouseMoveEvent. Be sure to throttle — Pick() on every move event in a heavy scene will hurt FPS; a qt.QTimer with setSingleShot(True) reset on each move is the usual trick.

If you also need this for markups specifically (control-point hover highlight), that’s done inside vtkMRMLMarkupsDisplayableManager via the CanProcessInteractionEvent / ProcessInteractionEvent API on vtkMRMLAbstractDisplayableManager — it’s the modern path for writing a custom interaction, but for “what node did the user click?” the snippet above is the simplest answer.