How to display the label (or Name) of ControlPoint of vtkMRMLMarkupsCurveNode?

Hi everyone,

I have a question about the module Markups. In a PointList, I can see the label (or Name) of each ControlPoint, and I can choose to display or hide it.

How can I do the same thing for the MarkupsCurveNode? I was not able to display the label (or Name) of a ControlPoint of a MarkupsCurveNode.
If it is possible to display, what is the function to toggle that using python code?

Thank you so much in advance!

Yes, using vtkMRMLMarkupsDisplayNode::SetPointLabelsVisibility:

curveNode.GetDisplayNode().SetPointLabelsVisibility(True)

Hi Kyle,

Thank you so much for your reply!

Would it be possible to trigger the label visibility of only one ControlPoint?

I am trying to create a CurveNode, and would like to add an observer that whenever my mouse hovers over a ControlPoint, its label displays. Could you please let me know the Event or Signal I should use and the function to trigger the one-ControlPoint Visibility?

Thank you again!

No, it looks like it’s currently only applied to all control points.

A workaround could be to set the control point labels that you don’t want displayed to an empty string with curveNode.SetNthControlPointLabel(i, "").

Thank you so much, Kyle!

I have two more questions that may need your help:

  1. Do we have an Event / Signal for ControlPoint Mouse Hover detection?
  2. Can we initialize empty ControlPoints as place-holder in the CurveNode, so that the number of Control Points counts but the empty CurveNode has nothing to display? In this way, I can add/remove the ControlPoints without messing up the sequential significance of each ControPoint.
  1. You can attach a vtk.vtkCommand.ModifiedEvent observer to the display node, and can tell what control point is active using GetActiveComponentType to make sure it is a control point that is active (slicer.vtkMRMLMarkupsDisplayNode.ComponentControlPoint), and GetActiveComponentIndex to get the index of the active control point.

  2. You can initialize the control points and use curveNode.UnsetNthControlPointPosition to mark them as unplaced.

Hi Kyle,

Thank you so much for your help!

I just tested the function curveNode.UnsetNthControlPointPosition(), however, I am not sure this function can help my case.

I would like to create a CurveNode, and add ControlPoint directly to the Nth, because the sequential value N is important to me. Is it possible?

E.g., after calling

node_Curve = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsCurveNode")

I would like to apply a function like this:

for idx_DataNode in range(10):
        node_Curve.UnsetNthControlPointPosition(idx_DataNode)

node_Curve.SetNthControlPoint(10, [2, 3, 4])

and I wish to get something like:

array = node_Curve.GetNthControlPoint(10)
print(array) : [2, 3, 4]

Could you please let me know how to achieve the above case?

You’ll need to initialize the markups node with the desired number of control points. Something like this:
https://slicer.readthedocs.io/en/latest/developer_guide/script_repository.html#how-to-draw-a-curve-using-control-points-stored-in-a-numpy-array

If you know ahead of time how many control points you will need, you can create the markups node with the desired number of unset control points, save it as a json and load that template to populate the curve node.

That looks nice! However, using this array will require all ControlPoints defined.

Is it possible that, I know the number of maximum ControlPoints, and fill in the CurveNode one by one while keeping the undefined ControlPoints with empty place-holders?

If you initialize the curve using the array and unset the control points using UnsetNthControlPointPosition, then you can allow the user place the Nth control point at a later time.

Markups also have a “MaximumNumberOfControlPoints” variable that would limit the number of control points that can be placed, however it doesn’t seem to be exposed for curve nodes.

Hi Kyle,

I really like the idea of combining array initialization updateMarkupsControlPointsFromArray and UnsetNthControlPointPosition together, however, it is unfortunately not user-friendly in practice.

image

As you can see from the screenshot, it cost about 6 seconds for updateMarkupsControlPointsFromArray to initialize the CurveNode with a zero array size (640, 3), and 4.5 seconds for UnsetNthControlPointPositionto do Unset.
During the test, the Slicer interface freezes for half a minute to initialize 3 curves, which is too long.

Interesting. updateMarkupsControlPointsFromArray is instantaneous on my computer (with a numpy array of size [640, 3]), although UnsetAllControlPoints takes several seconds.

You can save the templated landmarks to a json file after they are initialized and load that file into the curve node rather than using updateMarkupsControlPointsFromArray and UnsetNthControlPointPosition every time.

That may not fit my case :sweat_smile:

“640” is not a fixed number, but the length of an ultrasound sequence, which is variable as we load different sequences. :stuck_out_tongue_winking_eye:

Ok, you can try wrapping your calls using:

with slicer.util.NodeModify(curveNode):
  # Node updates here

This will prevent node modification events while you are updating the node.

1 Like

Brilliant! You saved my day!
Before this, I have to write dichotomy algorithm to calculate the index, then insert/remove/replace ControlPoint one by one. Now everything is simplified!

Thank you so much!

1 Like