Get C-arm angles from 3D view orientation

Hi. I am an interventional cardiologist. Currently I am interested in pre-planning for my intervention. In detail, I want to determine the optimal angles for setting the X-ray views.
Is there any extension, or Python codes for displaying these parameter (as I show in the following image).


Instead of only displaying the cube (for orientation), can we read the detail information about the related angles?

Thanks in advanced!

1 Like

There isn’t a dedicated planning tool that I’m aware of. You may want to just use Markups Angles to design your imaging plan and then match the device as best you can.

You can copy-paste this code snippet to the Python console to show the anatomical angles (LAO/RAO, CRA/CAU) of the C-arm in the corner of a 3D view:

threeDViewIndex = 0  # change this to show angles in a different 3D view

def positionerAngleFromViewNormal(viewNormal):
    # According to https://www5.informatik.uni-erlangen.de/Forschung/Publikationen/2014/Koch14-OVA.pdf
    nx = -viewNormal[0]  # L
    ny = -viewNormal[1]  # P
    nz =  viewNormal[2]  # S
    import math
    if abs(ny) > 1e-6:
        primaryAngleDeg = math.atan(-nx/ny) * 180.0 / math.pi
    elif nx >= 0:
        primaryAngleDeg = 90.0
    else:
        primaryAngleDeg = -90.0
    secondaryAngleDeg = math.asin(nz) * 180.0 / math.pi
    return [primaryAngleDeg, secondaryAngleDeg]

def formatPositionerAngle(positionerAngles):
    primaryAngleDeg, secondaryAngleDeg = positionerAngles
    text =  f'{"RAO" if primaryAngleDeg < 0 else "LAO"} {abs(primaryAngleDeg):.1f}\n'
    text += f'{"CRA" if secondaryAngleDeg < 0 else "CAU"} {abs(secondaryAngleDeg):.1f}'
    return text

def cameraUpdated(cameraNode, view):
    viewNormal = cameraNode.GetCamera().GetDirectionOfProjection()
    positionerAngleText = formatPositionerAngle(positionerAngleFromViewNormal(viewNormal))
    view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight, positionerAngleText)
    view.cornerAnnotation().GetTextProperty().SetColor(1,1,0)  # yellow
    view.scheduleRender()

layoutManager = slicer.app.layoutManager()
view = layoutManager.threeDWidget(threeDViewIndex).threeDView()
threeDViewNode = view.mrmlViewNode()
cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(threeDViewNode)
cameraObservation = cameraNode.AddObserver(vtk.vtkCommand.ModifiedEvent, lambda caller, event, view=view: cameraUpdated(caller, view))

cameraUpdated(cameraNode, view)

# Execute the next line to stop updating the positioner angles in the view corner
# cameraNode.RemoveObserver(cameraObservation)

We are developing a fluoro simulator module for interventional cardiology as part of the SlicerHeart project. The module will be able to display not just the angles but simulated fluoro images and compute optimal viewing angles, etc. We’ll release it when the results are first published - probably within a year.

1 Like

Kindly thanks for your super helpful reply :smiley:
Hope to see your module soon.
Thank you so much!

Hi, my image is XA, I want to display the anatomical angles of the C-arm (LAO/RAO, CRA/CAU) in the corner of the red slice viewer, I try to modify your code, but an error is reported when running, how do I modify it?

def positionerAngleFromViewNormal(viewNormal):
    # According to https://www5.informatik.uni-erlangen.de/Forschung/Publikationen/2014/Koch14-OVA.pdf
    nx = -viewNormal[0]  # L
    ny = -viewNormal[1]  # P
    nz =  viewNormal[2]  # S
    import math
    if abs(ny) > 1e-6:
        primaryAngleDeg = math.atan(-nx/ny) * 180.0 / math.pi
    elif nx >= 0:
        primaryAngleDeg = 90.0
    else:
        primaryAngleDeg = -90.0
    secondaryAngleDeg = math.asin(nz) * 180.0 / math.pi
    return [primaryAngleDeg, secondaryAngleDeg]

def formatPositionerAngle(positionerAngles):
    primaryAngleDeg, secondaryAngleDeg = positionerAngles
    text =  f'{"RAO" if primaryAngleDeg < 0 else "LAO"} {abs(primaryAngleDeg):.1f}\n'
    text += f'{"CRA" if secondaryAngleDeg < 0 else "CAU"} {abs(secondaryAngleDeg):.1f}'
    return text

def cameraUpdated(cameraNode, view):
    viewNormal = cameraNode.GetCamera().GetDirectionOfProjection()
    positionerAngleText = formatPositionerAngle(positionerAngleFromViewNormal(viewNormal))
    view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight, positionerAngleText)
    view.cornerAnnotation().GetTextProperty().SetColor(1,1,0)  # yellow
    view.scheduleRender()

layoutManager = slicer.app.layoutManager()
sliceViewName = layoutManager.sliceViewNames()[0]
view = layoutManager.sliceWidget(sliceViewName).sliceView()
sliceViewNode = view.mrmlViewNode()
cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(sliceViewNode)
cameraObservation = cameraNode.AddObserver(vtk.vtkCommand.ModifiedEvent, lambda caller, event, view=view: cameraUpdated(caller, view))

cameraUpdated(cameraNode, view)

What is the error? What Slicer version do you use?

@lassoan The error is:

>>> sliceViewNode = view.mrmlViewNode()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: qMRMLSliceView has no attribute named 'mrmlViewNode'

I need to display angles in red slice viewer, the image is 2D, so I modified your 3D viewer code, but it is wrong, I don’t know how to modify it.

The script above solves a more complex task: computing C-arm angles from the camera viewpoint in a 3D view. You can use it to find a good angulation that you can just on your C-arm.

If you already have an X-ray angiography image sequence then the C-arm angles are already stored in the DICOM series for each frame, we just need to display it.

For a dynamic acquisition - for showing angles changing throughout the series - we would need to create a somewhat complex script. For showing angles for a static-gantry image we could easily just change the DICOM importer to include th C-arm angle in the node name.

What is your overall goal? How do you plan to use XA images in Slicer?

I just want to display the C-arm angles in red slice viewer. I found the angles are in the DICOM tags:(0018,1510) DS PositionerPrimaryAngle, (0018,1511) DS PositionerSecondaryAngle. How can I make it display as (LAO/RAO, CRA/CAU) in the red slice viewer?

Does the C-arm move during your acquisitions? Is it enough to show the angles of the for frame of the sequence?

The acquisition process does not move, and each sequence has an angle. I want to display the angle of the sequence after opening a sequence. The angle information is recorded in the Dicom tag, but I don’t know how to display it.
20230425145028

Would it be sufficient to include the angles in the node name? You can enable showing the buffer name in the slice view corner annotation.

Yes, it’s enough, I just want the angle to be displayed. I don’t know how to get the value of PositionerPrimaryAngle and PositionerSecondaryAngle, and then display it on the red slice viewer. I only know that I can get the angle through the following function by getting those two values:

def formatPositionerAngle(primaryAngleDeg, secondaryAngleDeg):
    text =  f'{"RAO" if primaryAngleDeg < 0 else "LAO"} {abs(primaryAngleDeg):.1f}\n'
    text += f'{"CRA" if secondaryAngleDeg < 0 else "CAU"} {abs(secondaryAngleDeg):.1f}'
    return text

The angles could be added to the name of thr image at the time of DICOM import. The importer is this Python script, you can modify it on your computer.

It works, Thank you!
QQ截图20230506153341

A post was split to a new topic: Angle measurement range