Conjugate GradientΒΆ

ConjugateGradient shows how to perform iterative cone-beam CT reconstruction using either CPU and GPU resources. You can refer to the projectors documentation to see all options available for the back and forwardprojections.

This example uses the Thorax Forbild phantom, available in the dedicated GitHub repository for Forbild phantom files compatible with RTK.

#include "rtkThreeDCircularProjectionGeometryXMLFile.h"
#include "rtkConjugateGradientConeBeamReconstructionFilter.h"
#include "rtkIterationCommands.h"
#include "rtkProjectGeometricPhantomImageFilter.h"

#ifdef RTK_USE_CUDA
#  include <itkCudaImage.h>
#endif
#include <itkImageFileWriter.h>

int
main(int argc, char * argv[])
{
  using OutputPixelType = float;
  constexpr unsigned int Dimension = 3;

#ifdef RTK_USE_CUDA
  using OutputImageType = itk::CudaImage<OutputPixelType, Dimension>;
#else
  using OutputImageType = itk::Image<OutputPixelType, Dimension>;
#endif

  constexpr unsigned int numberOfProjections = 60;
  constexpr double       angularArc = 360.;
  constexpr unsigned int sid = 600;  // source to isocenter distance
  constexpr unsigned int sdd = 1200; // source to detector distance
  constexpr double       scale = 2.;
  std::string            configFileName = "Thorax";
  if (argc > 1 && argv[1] && argv[1][0] != '\0')
  {
    configFileName = argv[1];
  }
  constexpr unsigned int niterations = 3;

  itk::Matrix<double, Dimension, Dimension> rotation;
  rotation[0][0] = 1.;
  rotation[1][2] = 1.;
  rotation[2][1] = 1.;


  // Set up the geometry for the projections
  auto geometry = rtk::ThreeDCircularProjectionGeometry::New();
  for (unsigned int x = 0; x < numberOfProjections; x++)
  {
    geometry->AddProjection(sid, sdd, x * angularArc / numberOfProjections);
  }
  rtk::WriteGeometry(geometry, "geometry.xml");

  // Create a constant image source for the projections
  auto projectionsSource = rtk::ConstantImageSource<OutputImageType>::New();
  projectionsSource->SetOrigin(itk::MakePoint(-63., -63., -63.));
  projectionsSource->SetSpacing(itk::MakeVector(2., 2., 2.));
  projectionsSource->SetSize(itk::MakeSize(64, 64, numberOfProjections));

  // Project the geometric phantom image
  auto pgp = rtk::ProjectGeometricPhantomImageFilter<OutputImageType, OutputImageType>::New();
  pgp->SetInput(projectionsSource->GetOutput());
  pgp->SetGeometry(geometry);
  pgp->SetPhantomScale(scale);
  pgp->SetConfigFile(configFileName);
  pgp->SetRotationMatrix(rotation);
  TRY_AND_EXIT_ON_ITK_EXCEPTION(itk::WriteImage(pgp->GetOutput(), "projections.mha"));

  // Create new empty volume
  auto conjugateGradientSource = rtk::ConstantImageSource<OutputImageType>::New();
  conjugateGradientSource->SetOrigin(itk::MakePoint(-63., -63., -63.));
  conjugateGradientSource->SetSpacing(itk::MakeVector(2., 2., 2.));
  conjugateGradientSource->SetSize(itk::MakeSize(64, 64, 64));

  // Set the forward and back projection filters to be used
  using ConjugateGradientFilterType = rtk::ConjugateGradientConeBeamReconstructionFilter<OutputImageType>;
  auto conjugategradient = ConjugateGradientFilterType::New();

  conjugategradient->SetInputVolume(conjugateGradientSource->GetOutput());
  conjugategradient->SetInputProjectionStack(pgp->GetOutput());
  conjugategradient->SetGeometry(geometry);
  conjugategradient->SetNumberOfIterations(niterations);
#ifdef RTK_USE_CUDA
  conjugategradient->SetCudaConjugateGradient(true);
  conjugategradient->SetForwardProjectionFilter(ConjugateGradientFilterType::FP_CUDARAYCAST);
  conjugategradient->SetBackProjectionFilter(ConjugateGradientFilterType::BP_CUDAVOXELBASED);
#else
  conjugategradient->SetForwardProjectionFilter(ConjugateGradientFilterType::FP_JOSEPH);
  conjugategradient->SetBackProjectionFilter(ConjugateGradientFilterType::BP_VOXELBASED);
#endif

  auto verboseIterationCommand = rtk::VerboseIterationCommand<ConjugateGradientFilterType>::New();
  conjugategradient->AddObserver(itk::AnyEvent(), verboseIterationCommand);

  TRY_AND_EXIT_ON_ITK_EXCEPTION(conjugategradient->Update())

  // Write
  TRY_AND_EXIT_ON_ITK_EXCEPTION(itk::WriteImage(conjugategradient->GetOutput(), "conjugategradient.mha"));

  return EXIT_SUCCESS;
}
import itk
from itk import RTK as rtk

Dimension = 3
OutputPixelType = itk.F
OutputImageType = itk.Image[OutputPixelType, Dimension]

niterations = 10
numberOfProjections = 180
angularArc = 360.0
sid = 600
sdd = 1200
scale = 2.0

# Using RTK applications python APIs to create the geometry and projections
rtk.rtksimulatedgeometry(
    nproj=numberOfProjections,
    arc=angularArc,
    sid=sid,
    sdd=sdd,
    output="geometry.xml",
)
rtk.rtkprojectgeometricphantom(
    geometry="geometry.xml",
    output="projections.mha",
    phantomfile="Thorax",
    phantomscale=scale,
    rotation="1,0,0,0,0,-1,0,1,0",
    spacing=2,
    size=128,
)

projections = itk.imread("projections.mha")
geometry = rtk.read_geometry("geometry.xml")

# Create a constant image source to initialize the reconstruction
conjugate_gradient_source = rtk.constant_image_source(
    ttype=[OutputImageType],
    origin=[-127.0, -127.0, -127.0],
    size=[128, 128, 128],
    spacing=[2.0] * 3,
)

# Set the forward and back projection filters to be used
if hasattr(itk, "CudaImage"):
    OutputCudaImageType = itk.CudaImage[itk.F, Dimension]
    ConjugateGradientFilterType = rtk.ConjugateGradientConeBeamReconstructionFilter[
        OutputCudaImageType, OutputCudaImageType, OutputCudaImageType
    ]
    conjugategradient = ConjugateGradientFilterType.New()
    conjugategradient.SetCudaConjugateGradient(True)
    conjugategradient.SetInput(itk.cuda_image_from_image(conjugate_gradient_source))
    conjugategradient.SetInput(1, itk.cuda_image_from_image(projections))
    conjugategradient.SetBackProjectionFilter(
        ConjugateGradientFilterType.BackProjectionType_BP_CUDAVOXELBASED
    )
    conjugategradient.SetForwardProjectionFilter(
        ConjugateGradientFilterType.ForwardProjectionType_FP_CUDARAYCAST
    )

else:
    ConjugateGradientFilterType = rtk.ConjugateGradientConeBeamReconstructionFilter[
        OutputImageType, OutputImageType, OutputImageType
    ]
    conjugategradient = ConjugateGradientFilterType.New()
    conjugategradient.SetInput(conjugate_gradient_source)
    conjugategradient.SetInput(1, projections)
    conjugategradient.SetBackProjectionFilter(
        ConjugateGradientFilterType.BackProjectionType_BP_VOXELBASED
    )
    conjugategradient.SetForwardProjectionFilter(
        ConjugateGradientFilterType.ForwardProjectionType_FP_JOSEPH
    )

conjugategradient.SetGeometry(geometry)
conjugategradient.SetNumberOfIterations(niterations)


# Create a command to observe the iterations
class VerboseIterationCommand:
    def __init__(self):
        self.count = 0

    def callback(self):
        self.count = self.count + 1
        print(f"Iteration {self.count}", end="\r")


class VerboseEndCommand:
    def callback(self):
        print("")


cmd = VerboseIterationCommand()
conjugategradient.AddObserver(itk.IterationEvent(), cmd.callback)
cmd = VerboseEndCommand()
conjugategradient.AddObserver(itk.EndEvent(), cmd.callback)
conjugategradient.Update()

# Write
writer = itk.ImageFileWriter[OutputImageType].New()
writer.SetFileName("conjugategradient.mha")
writer.SetInput(conjugategradient.GetOutput())
writer.Update()

The results displayed with rtkshowgeometry are:

visu