Creating a plane 90 degrees from an existing plane manually or automatically

Hi! I am looking to use 3DSlicer to analyze several CT scans and need to create two planes to base my measurement on. The first is going to be a Frankfort plan based on 3 markups and the second will be a coronal plane 90 degrees vertically from the created Frankfort plane. What would be the best way to do this? I know there is a script function to measure the angle between two planes. In theory, I could place the new plane and then adjust it until it is 90 degrees but that seems time-consuming to do for 100+ images.

Ultimately I want to create a program that will allow me to place a few control points and measurement points and generate the two planes as well as the distance between a series of points and the planes. This is just the first step. I’ve looked into the extension SlicerMorph to help automatic this process but any additional tips would be greatly appreciated since this is my first time using 3D Slicer as a replacement for mimics/dolphin.

TLDR: How can I create two planes 90 degrees from each other based and then place a series of points and get the distance from them in the plane in the fastest, non-manual way possible?

I suppose you are referring to markups planes.

You may automate the plane creation steps by placing 3 fiducial points per the requirements of the Frankfurt plane. The script below will create orthogonal planes using the 3 fiducial points.

def createPlanes(f, angle):
    p1 = [0.0, 0.0, 0.0]
    p2 = [0.0, 0.0, 0.0]
    p3 = [0.0, 0.0, 0.0]
    f.GetNthControlPointPositionWorld(0, p1)
    f.GetNthControlPointPositionWorld(1, p2)
    f.GetNthControlPointPositionWorld(2, p3)
    
    plane1 = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsPlaneNode")
    plane1.CreateDefaultDisplayNodes()
    plane1.SetPlaneType(slicer.vtkMRMLMarkupsPlaneNode.PlaneType3Points)
    plane1.AddControlPointWorld(p1)
    plane1.AddControlPointWorld(p2)
    plane1.AddControlPointWorld(p3)
    
    plane1Normal = plane1.GetNormalWorld()
    plane1Binormal = [0.0, 0.0, 0.0]
    plane1Tangent = [0.0, 0.0, 0.0]
    # Get axes relative to plane1Normal. 
    vtk.vtkMath().Perpendiculars(plane1Normal, plane1Binormal, plane1Tangent, angle)
    
    plane2 = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsPlaneNode")
    plane2.CreateDefaultDisplayNodes()
    plane2.AddControlPointWorld(p1)
    # Set this plane's normal : to plane1Binormal or plane1Tangent.
    plane2.SetNormalWorld(plane1Binormal)
    
# Place 3 fiducial points to define your plane.
# Replace F by the name of your fiducial markups node
f = slicer.util.getNode("F")
# Using a 0° rotation around plane1Normal. Tune as needed.
createPlanes(f, 0)

This should probably be of help to further proceed with your other requirements. As I understand, a big problem would be to know the angle of rotation around plane1Normal.

Hi @Dbradl6 ,

You may have a look at this page to have more insight in your automation scripts. You may pay attention to 12-ResliceToAxis file in particular. This file aims at reslicing in particular. Slices are planes too, it may be easier to place points on slices than on markups planes.

Hi thank you for responding. I agree the biggest issue is going to be getting the angle of rotation. I noticed in your script you have this portion: "# Using a 0° rotation around plane1Normal. Tune as needed.
createPlanes(f, 0) " Could I just create an additional plane and change the angle here from 0 to 90?

Also this might seem very basic but how exactly would I run this? When I pasted it into the python console I got an error. I also tried creating the node F with the three control points first and I still got an error. This was the error I got:

Traceback (most recent call last):
File “”, line 30, in
File “/Applications/Slicer.app/Contents/bin/Python/slicer/util.py”, line 1499, in getNode
raise MRMLNodeNotFoundException(“could not find nodes in the scene by name or id ‘%s’” % (pattern if (isinstance(pattern, str)) else “”))
slicer.util.MRMLNodeNotFoundException: could not find nodes in the scene by name or id ‘F’

You can specify an arbitrary angle of rotation to ::Perpendiculars(). However, it’s in radians; you may yet mean 90 radians :smiley:

I suppose you mean a third plane orthogonal to the 2 others. The simplest way is to append these lines to the function :

plane3 = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsPlaneNode")
plane3.CreateDefaultDisplayNodes()
plane3.AddControlPointWorld(p1)
# Set this plane's normal : to plane1Tangent.
plane3.SetNormalWorld(plane1Tangent)

As it says, your scene does not have a node of whatever class having ‘F’ as name or id.

If, for example, you have been playing with a fiducial node named ‘F’, then deleted it and created another one, the default name will be ‘F_1’. Adapt the script thus.

Ok thank you so much I got it to work perfectly!! One last thing… I realized that I need to shift the second plane horizontally up to a different control point while not changing the angle. I’ll attach a picture. The additional control point/where I want the plane to be moved up to is marked “Sella”. What would be the best way to do that?

Could I just add the fourth point in the initial point-creating process or should I create a third plane using the new point?

You may manually set the center of the second plane to sella’s coordinate in the Markups module’s widget.

Or use an updated script :

def createPlanes(f, plane2Center = None, angle = None):
    p1 = [0.0, 0.0, 0.0]
    p2 = [0.0, 0.0, 0.0]
    p3 = [0.0, 0.0, 0.0]
    f.GetNthControlPointPositionWorld(0, p1)
    f.GetNthControlPointPositionWorld(1, p2)
    f.GetNthControlPointPositionWorld(2, p3)
    
    rotationOfPerpendiculars = 0.0
    if angle is not None:
        rotationOfPerpendiculars = angle
        
    plane2Coordinates = p1
    if plane2Center is not None:
        plane2Coordinates = plane2Center
    
    plane1 = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsPlaneNode")
    plane1.CreateDefaultDisplayNodes()
    plane1.SetPlaneType(slicer.vtkMRMLMarkupsPlaneNode.PlaneType3Points)
    plane1.AddControlPointWorld(p1)
    plane1.AddControlPointWorld(p2)
    plane1.AddControlPointWorld(p3)
    
    plane1Normal = plane1.GetNormalWorld()
    plane1Binormal = [0.0, 0.0, 0.0]
    plane1Tangent = [0.0, 0.0, 0.0]
    # Get axes relative to plane1Normal. 
    vtk.vtkMath().Perpendiculars(plane1Normal, plane1Binormal, plane1Tangent, rotationOfPerpendiculars)
    
    plane2 = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsPlaneNode")
    plane2.CreateDefaultDisplayNodes()
    plane2.AddControlPointWorld(plane2Coordinates)
    # Set this plane's normal : to plane1Binormal or plane1Tangent.
    plane2.SetNormalWorld(plane1Binormal)
    
# Place 3 fiducial points to define your plane.
# Replace F by the name of your fiducial markups node
f = slicer.util.getNode("F")
p4 = [0.0, 0.0, 0.0] # sella
f.GetNthControlPointPositionWorld(3, p4)
# Using a 0° rotation around plane1Normal. Tune as needed.
createPlanes(f, p4, 0.0)

You now get the picture to further modify the script for your specific needs.