AnatomicalOrientation

vtk-examples/Python/VisualizationAlgorithms/AnatomicalOrientation

Description¶

This depicts a human figure transected by the three commonly used anatomical planes:

• Sagittal plane – is perpendicular to the ground divides the body into a left section and a right section.
• Coronal plane – is perpendicular to the ground and divides the body into a front (anterior) section and back (posterior) section.
• Transverse plane – or axial plane is parallel to the ground divides the body into an upper (superior) section and a bottom (inferior) section.

Four annotated cube actors are also provided demonstrating different coordinate systems. The annotations on the faces of the cube actors are:

• Sagittal plane
• L - left.
• R - right.
• Coronal plane
• A - anterior.
• P - posterior.
• Transverse plane
• S - superior.
• I - inferior.

The annotated cube actors demonstrate the various coordinate systems: - The anatomical coordinate system forming a 3D basis is defined along the anatomical axes of anterior-posterior, inferior-superior, and left-right. These are the positive directions. In a Cartesian system this is RPS (Right, Posterior, Superior). The top-left annotated cube actor shows this basis, this is a left-handed system. - RAS (Right, Anterior, Superior), left-right, posterior-anterior, and inferior-superior. This is the usual right-handed system used by VTK and Slicer. The bottom left annotated cube actor shows this basis. - LPS (Left, Posterior, Superior), right-left, anterior-posterior, and inferior-superior. This is used in DICOM images and by the ITK toolkit. The bottom right annotated cube actor shows this basis. - The upper right cube actor has no axes and simply shows the planes.

RPS is a left-handed system whilst RAS and LPS are right-handed.

Note that the text for the planes is carefully placed to avoid obstructing the figure and it also sits slightly above the plane.

Other languages

See (Cxx)

Question

Code¶

AnatomicalOrientation.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
The human data file is taken from:
https://github.com/Slicer/Slicer/blob/master/Base/Logic/Resources/OrientationMarkers/Human.vtp
Thanks to the Slicer people for providing this.
"""

# noinspection PyUnresolvedReferences
import vtkmodules.vtkInteractionStyle
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkCommonTransforms import vtkTransform
from vtkmodules.vtkFiltersGeneral import vtkTransformPolyDataFilter
from vtkmodules.vtkInteractionWidgets import vtkOrientationMarkerWidget
from vtkmodules.vtkRenderingAnnotation import (
vtkAnnotatedCubeActor,
vtkAxesActor
)
from vtkmodules.vtkRenderingCore import (
vtkActor,
vtkPolyDataMapper,
vtkPropAssembly,
vtkRenderWindow,
vtkRenderWindowInteractor,
vtkRenderer
)
from vtkmodules.vtkRenderingFreeType import vtkVectorText

def main():
colors = vtkNamedColors()

fileName = get_program_parameters()

# Create a rendering window, renderer and interactor.
ren = vtkRenderer()
renWin = vtkRenderWindow()
renWin.SetSize(780, 780)
iren = vtkRenderWindowInteractor()
iren.SetRenderWindow(renWin)

# Make an annotated cube actor with axes and then add it into an orientation marker widget.
# Three of these need to be made.

# Right Posterior Superior
xyzLabels = ['X', 'Y', 'Z']
scale = [1.5, -1.5, 1.5]
axes = MakeCubeActor(scale, xyzLabels, colors)
om = vtkOrientationMarkerWidget()
om.SetOrientationMarker(axes)
# Position upper left in the viewport.
om.SetViewport(0.0, 0.8, 0.2, 1.0)
om.SetInteractor(iren)
om.EnabledOn()
om.InteractiveOn()

# Right, Anterior, Superior.
scale = [1.5, 1.5, 1.5]
axes1 = MakeCubeActor(scale, xyzLabels, colors)
om1 = vtkOrientationMarkerWidget()
om1.SetOrientationMarker(axes1)
# Position lower left in the viewport.
om1.SetViewport(0, 0, 0.2, 0.2)
om1.SetInteractor(iren)
om1.EnabledOn()
om1.InteractiveOn()

# Left, Posterior, Superior.
scale = (-1.5, -1.5, 1.5)
axes2 = MakeCubeActor(scale, xyzLabels, colors)
om2 = vtkOrientationMarkerWidget()
om2.SetOrientationMarker(axes2)
# Position lower right in the viewport.
om2.SetViewport(0.8, 0, 1.0, 0.2)
om2.SetInteractor(iren)
om2.EnabledOn()
om2.InteractiveOn()

# Finally create an annotated cube actor adding it into an orientation marker widget.
axes3 = MakeAnnotatedCubeActor(colors)
om3 = vtkOrientationMarkerWidget()
om3.SetOrientationMarker(axes3)
# Position upper right in the viewport.
om3.SetViewport(0.8, 0.8, 1.0, 1.0)
om3.SetInteractor(iren)
om3.EnabledOn()
om3.InteractiveOn()

humanMapper = vtkPolyDataMapper()
humanMapper.SetScalarModeToUsePointFieldData()
humanMapper.SelectColorArray('Color')
humanMapper.SetColorModeToDirectScalars()

humanActor = vtkActor()
humanActor.SetMapper(humanMapper)
bounds = humanActor.GetBounds()
# Scale the actor
humanActor.SetScale(1.0 / max(bounds))

# Make the planes.
actors = MakePlanesActors(colors)
for actor in actors:
# Label them.
for actor in textActors:

# Interact
ren.SetBackground2(colors.GetColor3d('OldLace'))
ren.SetBackground(colors.GetColor3d('MistyRose'))
ren.ResetCamera()
ren.GetActiveCamera().Zoom(1.6)
ren.GetActiveCamera().SetPosition(-2.3, 4.1, 4.2)
# ren.GetActiveCamera().SetPosition(-3.4, 5.5, 0.0)
ren.GetActiveCamera().SetViewUp(0.0, 0.0, 1.0)
ren.ResetCameraClippingRange()
renWin.Render()
# Call SetWindowName after renWin.Render() is called.
renWin.SetWindowName('AnatomicalOrientation')

iren.Initialize()
iren.Start()

def get_program_parameters():
import argparse
description = 'Show a labelled set of anatomical planes transecting a human figure.'
epilogue = '''
Show a labelled set of anatomical planes transecting a human figure.
Annotated cube actors and axes for the various coordinate systems are also shown.
'''
parser = argparse.ArgumentParser(description=description, epilog=epilogue,
formatter_class=argparse.RawDescriptionHelpFormatter)
args = parser.parse_args()
return args.filename

def MakeAxesActor(scale, xyzLabels):
"""
:param scale: Sets the scale and direction of the axes.
:param xyzLabels: Labels for the axes.
:return: The axes actor.
"""
axes = vtkAxesActor()
axes.SetScale(scale)
axes.SetShaftTypeToCylinder()
axes.SetXAxisLabelText(xyzLabels[0])
axes.SetYAxisLabelText(xyzLabels[1])
axes.SetZAxisLabelText(xyzLabels[2])
tprop = axes.GetXAxisCaptionActor2D().GetCaptionTextProperty()
tprop.ItalicOn()
tprop.SetFontFamilyToTimes()
# Use the same text properties on the other two axes.
axes.GetYAxisCaptionActor2D().GetCaptionTextProperty().ShallowCopy(tprop)
axes.GetZAxisCaptionActor2D().GetCaptionTextProperty().ShallowCopy(tprop)
return axes

def MakeAnnotatedCubeActor(colors):
"""
:param colors: Used to determine the cube color.
:return: The annotated cube actor.
"""
# A cube with labeled faces.
cube = vtkAnnotatedCubeActor()
cube.SetXPlusFaceText('R')  # Right
cube.SetXMinusFaceText('L')  # Left
cube.SetYPlusFaceText('A')  # Anterior
cube.SetYMinusFaceText('P')  # Posterior
cube.SetZPlusFaceText('S')  # Superior/Cranial
cube.SetZMinusFaceText('I')  # Inferior/Caudal
cube.SetFaceTextScale(0.5)
cube.GetCubeProperty().SetColor(colors.GetColor3d('Gainsboro'))

cube.GetTextEdgesProperty().SetColor(colors.GetColor3d('LightSlateGray'))

# Change the vector text colors.
cube.GetXPlusFaceProperty().SetColor(colors.GetColor3d('Tomato'))
cube.GetXMinusFaceProperty().SetColor(colors.GetColor3d('Tomato'))
cube.GetYPlusFaceProperty().SetColor(colors.GetColor3d('DeepSkyBlue'))
cube.GetYMinusFaceProperty().SetColor(colors.GetColor3d('DeepSkyBlue'))
cube.GetZPlusFaceProperty().SetColor(colors.GetColor3d('SeaGreen'))
cube.GetZMinusFaceProperty().SetColor(colors.GetColor3d('SeaGreen'))
return cube

def MakeCubeActor(scale, xyzLabels, colors):
"""
:param scale: Sets the scale and direction of the axes.
:param xyzLabels: Labels for the axes.
:param colors: Used to set the colors of the cube faces.
:return: The combined axes and annotated cube prop.
"""
# We are combining a vtkAxesActor and a vtkAnnotatedCubeActor
# into a vtkPropAssembly
cube = MakeAnnotatedCubeActor(colors)
axes = MakeAxesActor(scale, xyzLabels)

# Combine orientation markers into one with an assembly.
assembly = vtkPropAssembly()
return assembly

def MakePlane(resolution, origin, point1, point2, wxyz, translate):
plane = vtkPlaneSource()
plane.SetResolution(*resolution)
plane.SetOrigin(origin)
plane.SetPoint1(point1)
plane.SetPoint2(point2)
trnf = vtkTransform()
trnf.RotateWXYZ(*wxyz)
trnf.Translate(translate)
tpdPlane = vtkTransformPolyDataFilter()
tpdPlane.SetTransform(trnf)
tpdPlane.SetInputConnection(plane.GetOutputPort())
return tpdPlane

def MakePlanesActors(colors):
"""
Make the traverse, coronal and saggital planes.
:param colors: Used to set the color of the planes.
:return: The planes actors.
"""
planes = list()
mappers = list()
actors = list()

# Parameters for a plane lying in the x-y plane.
resolution = [10, 10]
origin = [0.0, 0.0, 0.0]
point1 = [1, 0, 0]
point2 = [0, 1, 0]

planes.append(MakePlane(resolution, origin, point1, point2, [0, 0, 0, 0], [-0.5, -0.5, 0]))  # x-y plane
planes.append(MakePlane(resolution, origin, point1, point2, [-90, 1, 0, 0], [-0.5, -0.5, 0.0]))  # x-z plane
planes.append(MakePlane(resolution, origin, point1, point2, [-90, 0, 1, 0], [-0.5, -0.5, 0.0]))  # y-z plane
for plane in planes:
mapper = vtkPolyDataMapper()
mapper.SetInputConnection(plane.GetOutputPort())
actor = vtkActor()
actor.SetMapper(mapper)
mappers.append(mapper)
actors.append(actor)
actors[0].GetProperty().SetColor(colors.GetColor3d('SeaGreen'))  # Transverse plane
actors[1].GetProperty().SetColor(colors.GetColor3d('DeepSkyBlue'))  # Coronal plane
actors[2].GetProperty().SetColor(colors.GetColor3d('Tomato'))  # Saggital plane
return actors

"""
Generate text to place on the planes.
Careful placement is needed here.
:return: The text actors.
"""
textActors = list()
scale = [0.04, 0.04, 0.04]

text1 = vtkVectorText()
text1.SetText('Transverse\nPlane\n\nSuperior\nCranial')
trnf1 = vtkTransform()
trnf1.RotateZ(-90)
tpdPlane1 = vtkTransformPolyDataFilter()
tpdPlane1.SetTransform(trnf1)
tpdPlane1.SetInputConnection(text1.GetOutputPort())
textMapper1 = vtkPolyDataMapper()
textMapper1.SetInputConnection(tpdPlane1.GetOutputPort())
textActor1 = vtkActor()
textActor1.SetMapper(textMapper1)
textActor1.SetScale(scale)
textActors.append(textActor1)

text2 = vtkVectorText()
text2.SetText('Transverse\nPlane\n\nInferior\n(Caudal)')
trnf2 = vtkTransform()
trnf2.RotateZ(270)
trnf2.RotateWXYZ(*[180, 0, 1, 0])
tpdPlane2 = vtkTransformPolyDataFilter()
tpdPlane2.SetTransform(trnf2)
tpdPlane2.SetInputConnection(text2.GetOutputPort())
textMapper2 = vtkPolyDataMapper()
textMapper2.SetInputConnection(tpdPlane2.GetOutputPort())
textActor2 = vtkActor()
textActor2.SetMapper(textMapper2)
textActor2.SetScale(scale)
textActors.append(textActor2)

text3 = vtkVectorText()
text3.SetText('Sagittal\nPlane\n\nLeft')
trnf3 = vtkTransform()
trnf3.RotateX(90)
trnf3.RotateWXYZ(*[-90, 0, 1, 0])
tpdPlane3 = vtkTransformPolyDataFilter()
tpdPlane3.SetTransform(trnf3)
tpdPlane3.SetInputConnection(text3.GetOutputPort())
textMapper3 = vtkPolyDataMapper()
textMapper3.SetInputConnection(tpdPlane3.GetOutputPort())
textActor3 = vtkActor()
textActor3.SetMapper(textMapper3)
textActor3.SetScale(scale)
textActors.append(textActor3)

text4 = vtkVectorText()
text4.SetText('Sagittal\nPlane\n\nRight')
trnf4 = vtkTransform()
trnf4.RotateX(90)
trnf4.RotateWXYZ(*[-270, 0, 1, 0])
tpdPlane4 = vtkTransformPolyDataFilter()
tpdPlane4.SetTransform(trnf4)
tpdPlane4.SetInputConnection(text4.GetOutputPort())
textMapper4 = vtkPolyDataMapper()
textMapper4.SetInputConnection(tpdPlane4.GetOutputPort())
textActor4 = vtkActor()
textActor4.SetMapper(textMapper4)
textActor4.SetScale(scale)
textActors.append(textActor4)

text5 = vtkVectorText()
text5.SetText('Coronal\nPlane\n\nAnterior')
trnf5 = vtkTransform()
trnf5.RotateY(-180)
trnf5.RotateWXYZ(*[-90, 1, 0, 0])
tpdPlane5 = vtkTransformPolyDataFilter()
tpdPlane5.SetTransform(trnf5)
tpdPlane5.SetInputConnection(text5.GetOutputPort())
textMapper5 = vtkPolyDataMapper()
textMapper5.SetInputConnection(tpdPlane5.GetOutputPort())
textActor5 = vtkActor()
textActor5.SetMapper(textMapper5)
textActor5.SetScale(scale)
textActors.append(textActor5)

text6 = vtkVectorText()
text6.SetText('Coronal\nPlane\n\nPosterior')
trnf6 = vtkTransform()
trnf6.RotateWXYZ(*[90, 1, 0, 0])
tpdPlane6 = vtkTransformPolyDataFilter()
tpdPlane6.SetTransform(trnf6)
tpdPlane6.SetInputConnection(text6.GetOutputPort())
textMapper6 = vtkPolyDataMapper()
textMapper6.SetInputConnection(tpdPlane6.GetOutputPort())
textActor6 = vtkActor()
textActor6.SetMapper(textMapper6)
textActor6.SetScale(scale)