Cannot find rectangle segmentation option

Hi,

This page mentions ‘Rectangle’ in segmentation effect but I cannot find it in my options.
https://www.slicer.org/wiki/Documentation/Nightly/Modules/Editor

Could you tell me where I can find ‘Rectangle’ for segmentation?
And is the same function available for own development?

Thanks

That documentation page is for the legacy Editor module. Rectangle option in Segment Editor module is available in Scissors effect.

Thank you. I was able to find the rectangle icon in Scissors.

Is there any way to modify the rectangle after making one on the image?
I would like to make a cube in the 3D volume or 2D rectangle on 2D image but it apparently makes a column in 3D volume.

How can I edit the boundary?

In addition, I would like to make the rectangle available in AIAA (by Nvidia) instead of Markups so that the user can define rectangle in the 2D image or a cube in the 3D volume then the trained CNN will take the cropped rectangle or cube to predict the mask.

Would it be possible? and how could I use the rectangle in my codes in AIAA?

I am very new to Slicer development and still struggling with understanding a whole architecture.
Any advice would be very helpful!

Thanks

Yes, you can use any effects to modify the segment. You can also use Surface cut effect with smoothing disabled, to adjust corners of the rectangle or prism before applying it.[quote=“Yusuke, post:3, topic:13286”]
I would like to make a cube in the 3D volume or 2D rectangle on 2D image but it apparently makes a column in 3D volume
[/quote]

You can use the Scissors effect on an orthogonal slice to crop the column to the desired region (erase outside). You can also choose to restrict painting a single slice or a specified thickness.

In a AIAA module, you can define an input box by placing 6 markups fiducial points.

Thank you very much for your help!

I think what I want to do is something similar to MarkupPlane but just square.

Do you have any idea how to keep the plane square? I would like to refer the codes for MarkupPlane. Could you please tell me where I can find it?

Again, thank you for your support!

You can enforce a markups plane to be a square by adding an observer to it. The observer’s callback function is executed whenever a control point is changed and then you can move other control points accordingly.

Inwould recommend to check out markups examples in script repository.

Thank you for your support!

I understood that I can add some function to observer using planeNode.AddObserver() but I’m still not quite sure how I can force the plane to be a square.

Could you please point me some script that change the point in plane node to be a square if you have one?

Thanks!

When you get a notification that a control point is moved then update position of the other control point so that the shape remains a square.

My understanding is that the plane markup class is ‘vtkMRMLMarkupsPlaneNode’ but I couldn’t find any reference documentation for it.

In this link, it describes Line, Curve, Angle, and Fiducial.
https://apidocs.slicer.org/master/classvtkMRMLMarkupsNode.html

Am I referring to old documents? Can you please point me the reference for the plane markup class?

Unfortunately, the documentation has not been updated for a while (you can track progress here). Until this gets fixed, you can find documentation in header files or you can use help command in Python:


>>> help(slicer.vtkMRMLMarkupsPlaneNode)
Help on class vtkMRMLMarkupsPlaneNode in module vtkSlicerMarkupsModuleMRMLPython:

class vtkMRMLMarkupsPlaneNode(vtkMRMLMarkupsNode)
 |  vtkMRMLMarkupsPlaneNode - MRML node to represent a plane markup
 |  
 |  Superclass: vtkMRMLMarkupsNode
 |  
 |  Plane Markups nodes contain three control points. Visualization
 |  parameters are set in the vtkMRMLMarkupsDisplayNode class.
 |  
 |  Markups is intended to be used for manual marking/editing of point
 |  positions.
 |  
 |  Coordinate systems used:
 |  - Local: Local coordinates
 |  - World: All parent transforms on node applied to local.
 |  - Plane: Plane coordinate space (Origin of plane at 0,0,0, XYZ axis
 |    aligned to XYZ unit vectors). Can have additional offset/rotation
 |    compared to local.\ingroup Slicer_QtModules_Markups
 |  
 |  Method resolution order:
 |      vtkMRMLMarkupsPlaneNode
 |      vtkMRMLMarkupsNode
 |      MRMLCorePython.vtkMRMLDisplayableNode
 |      MRMLCorePython.vtkMRMLTransformableNode
 |      MRMLCorePython.vtkMRMLStorableNode
 |      MRMLCorePython.vtkMRMLNode
 |      vtkCommonCorePython.vtkObject
 |      vtkCommonCorePython.vtkObjectBase
 |      builtins.object
 |  
 |  Methods defined here:
 |  
 |  CopyContent(...)
 |      V.CopyContent(vtkMRMLNode, bool)
 |      C++: void CopyContent(vtkMRMLNode *node, bool deepCopy=true)
 |          override;
 |      
 |      Copy node content (excludes basic data, such as name and node
 |      references).
 |      \sa vtkMRMLNode::CopyContent
 |  
 |  CreateNodeInstance(...)
 |      V.CreateNodeInstance() -> vtkMRMLNode
 |      C++: vtkMRMLNode *CreateNodeInstance() override;
 |      
 |      MRMLNode methods
 |  
 |  GetAutoSizeScalingFactor(...)
 |      V.GetAutoSizeScalingFactor() -> float
 |      C++: virtual double GetAutoSizeScalingFactor()
 |      
 |      The plane size multiplier used to calculate the size of the
 |      plane. This is only used when the size mode is auto. Default is
 |      1.0.
 |  
 |  GetAxes(...)
 |      V.GetAxes([float, float, float], [float, float, float], [float,
 |          float, float])
 |      C++: void GetAxes(double x[3], double y[3], double z[3])
 |      
 |      The direction vectors defined by the markup points. Calculated as
 |      follows and then transformed by the offset matrix: X: Vector from
 |      1st to 0th point. Y: Cross product of the Z vector and X vectors.
 |      Z: Cross product of the X vector and the vector from the 2nd to
 |      0th point.
 |  
 |  GetAxesWorld(...)
 |      V.GetAxesWorld([float, float, float], [float, float, float],
 |          [float, float, float])
 |      C++: void GetAxesWorld(double x[3], double y[3], double z[3])
 |  
 |  GetClosestPointOnPlaneWorld(...)
 |      V.GetClosestPointOnPlaneWorld((float, float, float), [float,
 |          float, float], bool) -> float
 |      C++: double GetClosestPointOnPlaneWorld(const double posWorld[3],
 |          double closestPosWorld[3], bool infinitePlane=true)
 |      
 |      Get the closest position on the plane in world coordinates.
 |      Returns the signed distance from the input point to the plane.
 |      Positive distance is in the direction of the plane normal, and
 |      negative distance is in the opposite direction.
 |      \param posWorld input position
 |      \param closestPosWorld: output found closest position
 |      \param infinitePlane if false, the closest position will be
 |          restricted to the plane bounds
 |      \return Signed distance from the point to the plane. Positive
 |          distance is in the direction of the plane normal
 |  
 |  GetIcon(...)
 |      V.GetIcon() -> string
 |      C++: const char *GetIcon() override;
 |  
...

Thank you Iassoan!
I think now I see the updated documentation here: https://apidocs.slicer.org/master/classvtkMRMLMarkupsPlaneNode.html#a73ada3ec7e62d0d4d471725853894613

I tried some of the methods but I was not able to find which method can be used for forcing the plane square.

Could you tell me which method forces points to locate as desired?

Documentation available at Slicer: vtkMRMLMarkupsNode Class Reference is now updated daily.

Thanks for your patience :pray:

Thank you for the link.

I’ve been trying to make a plane markup square but not sure how to.
According to the documentation, SetPlaneBounds() looks to change the shape of it.

But SetPlaneBounds() does nothing in my codes and not sure why.
planeMarkup = slicer.mrmlScene.GetFirstNodeByClass(“vtkMRMLMarkupsPlaneNode”)
bounds = [0, 0, 0, 0, 0, 0]
planeMarkup.GetPlaneBounds(bounds)
print(‘currenty bounds : {}’.format(bounds))
new_bounds = [-11, 11, -11, 11, 0, 0]
planeMarkup.SetPlaneBounds(new_bounds)
planeMarkup.GetPlaneBounds(bounds)
print(‘currenty bounds : {}’.format(bounds))

the above codes shows
currenty bounds : [-17.476825980903453, 17.476825980903453, -19.1019142042501, 19.1019142042501, 0.0, 0.0]
currenty bounds : [-17.476825980903453, 17.476825980903453, -19.1019142042501, 19.1019142042501, 0.0, 0.0]
which means the rectangle didn’t change.

Could you tell me how to use the method?

You need to adjust the control points. First control point defines the plane center, second control point defines X axis direction and size, and third control point defines Y axis direction and size.

Thank you Andras,
I tried to change the control point as they are changed.
However, I found changing other control points every time a control point was moved is very slow.
The codes are below and this is just sample codes.

def onControlPointChanged(caller, event):

    markupPlaneNode = caller
    controlPointIndex = markupPlaneNode.GetDisplayNode().GetActiveControlPoint()
    print(controlPointIndex)
    cp_0 = [0, 0, 0]
    cp_1 = [0, 0, 0]
    cp_2 = [0, 0, 0]
    markupPlaneNode.GetNthControlPointPositionWorld(0, cp_0)
    markupPlaneNode.GetNthControlPointPositionWorld(1, cp_1)
    markupPlaneNode.GetNthControlPointPositionWorld(2, cp_2)
    if controlPointIndex == 1:
        cp_2_new = [-(cp_1[1]),cp_1[0],cp_2[2]]
        markupPlaneNode.SetNthControlPointPositionWorld(2, cp_2_new[0], cp_2_new[1], cp_2_new[2])
    elif controlPointIndex == 2:
        cp_1_new = [-(cp_2[1]),cp_2[0],cp_1[2]]
        markupPlaneNode.SetNthControlPointPositionWorld(1, cp_1_new[0], cp_1_new[1], cp_1_new[2])

def test1():

    markupsNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLMarkupsPlaneNode")
    markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, onControlPointChanged)

Is it what you are saying or are there other faster ways?
If you can give me advice, that would be helpful!
Thank you!!

Your code was slow because it recursively called itself (setting control point position triggers control point changed event). You can break this recursion by adding a flag (slicer.updatingControlPoint):

def onControlPointChanged(caller, event):
    if slicer.updatingControlPoint:
        return
    slicer.updatingControlPoint = True
    markupPlaneNode = caller
    movedPointIndex = markupPlaneNode.GetDisplayNode().GetActiveControlPoint()
    planeToWorldMatrix = vtk.vtkMatrix4x4()
    markupPlaneNode.GetPlaneToWorldMatrix(planeToWorldMatrix)
    bounds = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    markupPlaneNode.GetPlaneBounds(bounds)
    sideLength = bounds[movedPointIndex * 2 - 1]
    if movedPointIndex == 1:
        adjustedPointPosition = planeToWorldMatrix.MultiplyPoint([0, sideLength, 0, 1])
        markupPlaneNode.SetNthControlPointPositionWorld(2, *(adjustedPointPosition[0:3]))
    else:
        adjustedPointPosition = planeToWorldMatrix.MultiplyPoint([sideLength, 0, 0, 1])
        markupPlaneNode.SetNthControlPointPositionWorld(1, *(adjustedPointPosition[0:3]))
        adjustedPointPosition = planeToWorldMatrix.MultiplyPoint([0, sideLength, 0, 1])
        markupPlaneNode.SetNthControlPointPositionWorld(2, *(adjustedPointPosition[0:3]))
    slicer.updatingControlPoint = False

slicer.updatingControlPoint = False
markupsNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLMarkupsPlaneNode")
observationId = markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, onControlPointChanged)

Also make sure you don’t print anything in the console at each modified event, as logging takes time, too.

Thank you so much for the codes!!
(markupsNode should be markupPlaneNode, correct?)

I still have to consider the cases where I move movedPointIndex==0 to translate the rectangle.

I changed the codes as the follow but still it doesn’t behave as desired.

def onControlPointChanged(caller, event):
    print('test')
    if slicer.updatingControlPoint:
        return
    print('test2')
    slicer.updatingControlPoint = True
    markupPlaneNode = caller
    movedPointIndex = markupPlaneNode.GetDisplayNode().GetActiveControlPoint()
    planeToWorldMatrix = vtk.vtkMatrix4x4()
    markupPlaneNode.GetPlaneToWorldMatrix(planeToWorldMatrix)
    bounds = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    markupPlaneNode.GetPlaneBounds(bounds)
    sideLength = bounds[movedPointIndex * 2 - 1]
    if movedPointIndex == 1:
        adjustedPointPosition = planeToWorldMatrix.MultiplyPoint([0, sideLength, 0, 1])
        markupPlaneNode.SetNthControlPointPositionWorld(2, *(adjustedPointPosition[0:3]))
    elif movedPointIndex == 2:
        adjustedPointPosition = planeToWorldMatrix.MultiplyPoint([sideLength, 0, 0, 1])
        markupPlaneNode.SetNthControlPointPositionWorld(1, *(adjustedPointPosition[0:3]))
        adjustedPointPosition = planeToWorldMatrix.MultiplyPoint([0, sideLength, 0, 1])
        markupPlaneNode.SetNthControlPointPositionWorld(2, *(adjustedPointPosition[0:3]))
    elif movedPointIndex == 0:
        adjustedPointPosition = planeToWorldMatrix.MultiplyPoint([bounds[3],0, 0, 1])
        markupPlaneNode.SetNthControlPointPositionWorld(1, *(adjustedPointPosition[0:3]))
        adjustedPointPosition = planeToWorldMatrix.MultiplyPoint([0, bounds[3], 0, 1])
        markupPlaneNode.SetNthControlPointPositionWorld(2, *(adjustedPointPosition[0:3]))

    slicer.updatingControlPoint = False

Do you have any idea how to deal with if movedPointIndex == 0? to move the square without changing the orient and size?

It works well for me. What problem did you have?

There are many options. For example you can store the previous planeToWorldMatrix, update it with the new center point position, and get the two new point positions by transforming (sideLength,0,0,1) and (0,sideLength,0,1) points by this updated planeToWorldMatrix.

In your original codes, when I move the center point in the rectangle, the other two points will be located in the same as the center point, which made the rectangle converge into one point.
In my codes above, when I move the center point in the rectangle, the size and the orient of the rectangle will be changed.

Could you elaborate on this?
My understanding is that what you mentioned is what my codes are doing.

Thank you so much for your help!!

You can also translate and rotate planes using the interaction handles:

Click-and-drag the displayed arrows: