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

Andras,

I was wondering if the fluoro simulator module in the SlicerHeart project has been released yet. I could really use it for my procedure planning! I was also wondering how accurate the simulated fluoro images will be; for instance, will they take into account the fanning of the x-ray beam and the distance from the x-ray tube focal spot to the anatomy?

The simulated fluoro image is fully accurate in term of geometry of the cone-beam projection.

The paper about the cathlab simulator is in a very good shape and it will be submitted soon, so it should not take too long to release the module.

1 Like

Andras,

Thank you very much for the quick reply. I look forward to trying the fluoro simulator, but if you haven’t submitted the article yet, then there is no telling how long it may take… I can’t wait that long, so I am working on a poor-man’s version on my own.

I found your code for extracting or defining the c-arm angles, and I figured out how to adjust the colors and opacity to get a reasonable fluoro simulated images, but I am stuck on one issue… the standard fluoro image display shows the projection as viewed from the detector (head up, left side of body on right side of image when at orientation 0,0). In slicer, the image is as viewed from the camera which mimics the x-ray tube in terms of the perspective. For this reason the images that I obtain are flipped L-R relative to the standard fluoro display.

Do you have any suggestions of how to solve this?

Thanks,

David

If you want to flip between showing the front or back of the mesh, you can use reverse perspective projection in the renderer camera. We have discussed this a couple of times on the VTK forum (for example, here).

In the simulator in SlicerHeart we do not support flipping between rendering from the front or back, as we could achieve all visualizations that we needed without implementing this (by rendering 3D content semitransparent). Also, there may be a few things that need to be fixed (one issue that we know about is that currently GPU volume rendering is not compatible with reverse perspective projection).