Handle Markups events from an external module

I’ve done some debugging and it turns out that PointModifiedEvent and similar high-level events still use integer parameters, so you can access them using

@vtk.calldata_type(vtk.VTK_INT)
def onMarkupChanged(caller,event,call_data):
    print(event)
    print(call_data)

More sophisticated interaction events are available at lower level (in VTK widgets) and those could be propagated to MRML nodes, but vtkEventData based objects are not Python-wrapped so they could not be used from Python. I’ve asked about this on VTK forum. We’ll proceed according to what we hear back from there (it’ll be either a VTK fix or we add a vtkObject based wrapper around the vtkEventData based object).

Hi,

I have been working with the Markups and more specifically the PointAddedEvent.
I have noticed that the event is only called when you switch to one view from another (like @mirclem noticed).
I need to be able to know when a Markup control point is added (i.e. defined) in the view. Unfortunately the PointAddedEvent doesn’t fire when the point becomes defined. Here is the snippet that I used:

m = slicer.mrmlScene.AddNode(slicer.vtkMRMLMarkupsFiducialNode())

def onPointAdded(caller, event):
  print("added ", caller.GetNthControlPointPositionStatus(0))

m.AddObserver(slicer.vtkMRMLMarkupsNode.PointAddedEvent, p)

This prints:

added 1 # View change (PointUndefined)
added 1 # View change (PointUndefined)
#Missing added 2 (PointDefined) :(

It doesn’t send a signal when the status becomes PointDefined.

I am aware that I could manage that through the PointModifiedEvent and check the point’s status but that seems counter-intuitive and it makes it harder for the UI.

Do you think adding a PointAddedEvent when the point becomes defined makes sense ?

Thanks !
Johan

2 Likes

It seems to be a common need to get notification when a point becomes defined, so I’ll add a new event for this.

1 Like

I’ve added a new pair of events (available in Slicer Preview Release from tomorrow): slicer.vtkMRMLMarkupsNode.PointPositionDefinedEvent and slicer.vtkMRMLMarkupsNode.PointPositionUndefinedEvent. These are only invoked when a point is placed or a placed point is removed. Not impacted by point previews.

def onMarkupPointPositionDefined(caller, event):
    markupsNode = caller
    movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()
    logging.info(f"Markup point added: point ID = {movingMarkupIndex}")

def onMarkupPointPositionUndefined(caller, event):
    markupsNode = caller
    logging.info(f"Markup point removed.")

markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")
markupsNode.CreateDefaultDisplayNodes()
markupsNode.AddFiducial(0,0,0)
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointPositionDefinedEvent, onMarkupPointPositionDefined)
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointPositionUndefinedEvent, onMarkupPointPositionUndefined)
1 Like

This is great ! Thanks so much !
I was able to quickly make my code work with these.

P.S.:
What’s the PointPlacedEvent ? Was it added for future use ?

I added that event ID but finally did not end up using it. I’ll write some tests and with that commit remove PointPlacedEvent.

1 Like

As to the two events, there may be a bug in bool vtkMRMLMarkupsNode::InsertControlPoint(ControlPoint *controlPoint, int targetIndex), line 689 of vtkMRMLMarkupsNode.cxx:

// let observers know that a markup was added
  this->InvokeCustomModifiedEvent(vtkMRMLMarkupsNode::PointAddedEvent, static_cast<void*>(&targetIndex));
  if (controlPoint->PositionStatus == vtkMRMLMarkupsNode::PositionDefined)
    {
    this->InvokeCustomModifiedEvent(vtkMRMLMarkupsNode::PointPositionUndefinedEvent, static_cast<void*>(&targetIndex));
    }
  this->UpdateMeasurements();

I think PointPositionUndefinedEvent should be PointPositionDefinedEvent?

Good catch, thank you, indeed when a point was inserted into a curve then wrong event ID was used. I’ve pushed a fix now, which will be available in the Slicer Preview Release from tomorrow.