Optimizing resolution and spacing parameters in Plastimatch DRR

Hi Mik, thank you very much for adding this feature and sorry for the slow response. I am using version 477d3e3 of SlicerRT. I uninstalled, reinstalled, and checked for updates, but I am not sure that it is the right version because the date is 2022-09-10.

With v477d3e3, I don’t see a frame named Markups projection intersection.

Upgrade both Slicer and SlicerRT. The latest versions.

That worked. I started checking the values produced in the Markups projection intersection with an imager plane and the values did not match ones that I generated using the Plastimatch command prompt and an Excel spreadsheet that I made. I am taking it as an indicator that my Excel calculations are off. I will trouble shoot tomorrow and follow up.

Is it possible to populate the “Column, Row:” or “Offset from origin in mm” values for all of the markup fiducials in the list rather than one at a time? In the current configuration of the Markups projection intersection with an imager plane UI, I am copying these values manually into a spreadsheet to have the pixel coordinates together.

The “Coordinates” table in the Control Points frame in the Markups module is great because all of the points can be selected together and copied into an Excel spreadsheet.

That must be checked thoroughly. It could be my mistake in programming, or other kind of silly mistake!

Here is a test scene with a CT volume, Isocenter markups node, and a ControlPoints markups node which you want to project on a imager plane.

Load the scene and use this python script into Slicer python console. It will setup your plan, beam and a drrParameters. drrLogic calculates DRR image. Iterating along all original control point you will be able to calculate all the required projections and save the result into file. File names must be adopted for your system.


# Get isocenter, get control points
isocenterPoint = slicer.mrmlScene.GetFirstNodeByName('Isocenter')
controlPoints = slicer.mrmlScene.GetFirstNodeByName('ControlPoints')

# Get CT
ctVolume = slicer.mrmlScene.GetFirstNodeByClass('vtkMRMLScalarVolumeNode')

# Create RTPlan
rtImagePlan = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLRTPlanNode', 'rtImagePlan')
rtImagePlan.SetAndObserveReferenceVolumeNode(ctVolume)
rtImagePlan.SetIsocenterSpecification(slicer.vtkMRMLRTPlanNode.ArbitraryPoint)
rtImagePlan.SetAndObservePoisMarkupsFiducialNode(isocenterPoint)

# Create RTBeam, add beam to the plan, setup beam parameters
rtImageBeam = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLRTBeamNode', 'rtImageBeam')
rtImagePlan.AddBeam(rtImageBeam)
rtImageBeam.SetGantryAngle(90.)
rtImageBeam.SetSAD(1000.)

# Create vtkMRMLDrrImageComputationNode node, setup and update Normal and View-Up vectors
# Compute DRR using defined rtImageParameters and ctVolume
rtImageParameters = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLDrrImageComputationNode', 'rtImageBeamParams')
rtImageParameters.SetAndObserveBeamNode(rtImageBeam)
rtImageParameters.SetHUThresholdBelow(120)

drrLogic = slicer.modules.drrimagecomputation.logic()
drrLogic.UpdateMarkupsNodes(rtImageParameters)
drrLogic.UpdateNormalAndVupVectors(rtImageParameters) # REQUIRED
drrLogic.ComputePlastimatchDRR( rtImageParameters, ctVolume)

# Get Plastimatch matrices (for testing and control)
plastimatchIntrinsic = vtk.vtkMatrix4x4()
drrLogic.GetPlastimatchIntrinsicMatrix(rtImageParameters, plastimatchIntrinsic)

plastimatchExtrinsic = vtk.vtkMatrix4x4()
drrLogic.GetPlastimatchExtrinsicMatrix(rtImageParameters, plastimatchExtrinsic)

plastimatchProjection = vtk.vtkMatrix4x4()
drrLogic.GetPlastimatchProjectionMatrix(rtImageParameters, plastimatchProjection)

# Create markups nodes for the results
projPoints = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLMarkupsFiducialNode', 'ProjectedPoints')
whPoints = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLMarkupsFiducialNode', 'WidthHeightPoints')
crPoints = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLMarkupsFiducialNode', 'ColumnRowPoints')

# Get number of original control points
nofPoints = controlPoints.GetNumberOfControlPoints()

# Loop all original control points, calculate projection and offsets
for i in range(nofPoints):
    cpRAS = [0,0,0]
    projPAS = [0,0,0]
    offsetWidthHeight = [0,0]
    offsetColumnRow = [0,0]
    # Get control point coordinate
    controlPoints.GetNthControlPointPositionWorld(i, cpRAS)
    # Calculate projected point offsets
    if drrLogic.GetPointOffsetFromImagerOrigin(rtImageParameters, cpRAS, offsetWidthHeight, offsetColumnRow):
        whPoints.AddControlPoint(offsetWidthHeight[0], offsetWidthHeight[1], 0, controlPoints.GetNthControlPointLabel(i))
        crPoints.AddControlPoint(offsetColumnRow[0], offsetColumnRow[1], 0, controlPoints.GetNthControlPointLabel(i))
    # Calculate projected point world coordinates
    if drrLogic.GetRayIntersectWithImagerPlane(rtImageParameters, cpRAS, projPAS):
        projPoints.AddControlPoint(projPAS, controlPoints.GetNthControlPointLabel(i))

# Save the results into csv files
slicer.modules.markups.logic().ExportControlPointsToCSV(projPoints, "/tmp/ProjectedControlPoints.csv")
slicer.modules.markups.logic().ExportControlPointsToCSV(whPoints, "/tmp/WidthHeightPoints.csv")
slicer.modules.markups.logic().ExportControlPointsToCSV(crPoints, "/tmp/ColumnRowPoints.csv")

Once calculated projections are OK. You can modify script as you wish. Calculated markups nodes are also available in Slicer as well.

I will add table widget next week, it allows you to copy all the data easier. The scrip will be also valid though.

That would be great, thank you! Sorry for the slow response.

Were you able to implement the table widget?

I hate to ask for more, but is there any way for Slicer to output a file containing the DRR? I am using the Plastimatch command prompt to generate a .pfm file after I set the configurations in Slicer and I would love to avoid that step.

Yes. A new PR is on the way, but on Linux i wasn’t able to copy/paste data from table widget into spreadsheet. I will add saving of projection data into vtkMRMLTableNode shortly.

What file format for DRR do you need? Are you trying to avoid using plastimatch drr command at all, and use only 3D Slicer?

In subject hierarchy:

  1. Right click on DRR image node
  2. Export to file…
  3. Save as mha or raw format, you can open that file in ImageJ.

Check box was added so you can save valid projected data into table node. That data can be copy/pasted into a spreadsheet (tested in LibreOffice Calc).

@fieldr4
Another upgrade in ready to test. You can try DRR cli module and select .pfm file format as output, the resulted pfm file will be in the 3D Slicer temporary directory. That feature is only work with “DRR Generation” module, not with “DRR Image Computation”.

Let me know the results.

Thank you again for implementing this. It has really streamlined my workflow.

Is there a way to project multiple lines? I am using the Line tool to mark vertebral endplates and then copying the line endpoints into a Point List to be able to use the Markups perspective projection on the imager plane functionality.

Do you mean many markups line nodes or something else?

I meant multiple markup lines simultaneously. I am measuring vertebral endplates in the lumbar spine, which necessitates at least 11 lines per CT. See example below.

Lines

I need to project the points from these lines into the DRR coordinate reference frame.

Projected these lines as cloud of points (fiducials) or as line nodes?

1 Like

I believe as clouds of points. See example below with two points projected onto DRR:

image

I would like to replicate this for all of the endpoints of the line segments.

If all required markups line nodes are visible, and DRR parameter node and DRR image were created successfully, then you can try to use the code below. It iterates along all visible line nodes and projects each first and second control point of each line on the DRR plane.

You must check the result carefully.


drrLogic.ShowMarkupsNodes(False)
lines = slicer.mrmlScene.GetNodesByClass('vtkMRMLMarkupsLineNode')
projPoints = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLMarkupsFiducialNode', 'ProjectedPoints')

for line in lines:
    cpRAS = [0,0,0]
    projPAS = [0,0,0]
    
    if line.GetMarkupsDisplayNode().GetVisibility() == 1:
      line.GetNthControlPointPositionWorld( 0, cpRAS)
      if drrLogic.GetRayIntersectWithImagerPlane(rtImageParameters, cpRAS, projPAS):
          projPoints.AddControlPoint(projPAS, line.GetNthControlPointLabel(0)  + '_Proj')
    
      line.GetNthControlPointPositionWorld( 1, cpRAS)
      if drrLogic.GetRayIntersectWithImagerPlane(rtImageParameters, cpRAS, projPAS):
          projPoints.AddControlPoint(projPAS, line.GetNthControlPointLabel(1)  + '_Proj')
    
    line.UnRegister(None)


1 Like

Hi Mik,

I have been using the “Markups perspective projection on the imager plane” in the DRR Image Computation module and realized that it may be producing erroneous results. My use case involves placing points on spinal anatomic landmarks, then observing their position in the resulting DRR. The projected points in the example below appear in a different location than I would expect:

The red projected points do not contact the anatomic structures on which the original points are positioned. What could be causing this?