4D Conjugate GradientΒΆ
This example shows how to perform iterative cone-beam CT reconstruction using either CPU or GPU resources. It reads its data from disk. The data can be generated by the Generate 4D data example or downloaded from Girder.
#include "rtkFourDConjugateGradientConeBeamReconstructionFilter.h"
#include "rtkIterationCommands.h"
#include "rtkSignalToInterpolationWeights.h"
#include "rtkReorderProjectionsImageFilter.h"
#include "rtkProjectionsReader.h"
#include "rtkThreeDCircularProjectionGeometryXMLFileReader.h"
#ifdef RTK_USE_CUDA
# include <itkCudaImage.h>
#endif
#include <itkImageFileWriter.h>
int
main(int, char *[])
{
using OutputPixelType = float;
#ifdef RTK_USE_CUDA
using VolumeSeriesType = itk::CudaImage<OutputPixelType, 4>;
using ProjectionStackType = itk::CudaImage<OutputPixelType, 3>;
#else
using VolumeSeriesType = itk::Image<OutputPixelType, 4>;
using ProjectionStackType = itk::Image<OutputPixelType, 3>;
#endif
// Generate the input volume series, used as initial estimate by 4D conjugate gradient
auto fourDOrigin = itk::MakePoint(-63., -31., -63., 0.);
auto fourDSpacing = itk::MakeVector(4., 4., 4., 1.);
auto fourDSize = itk::MakeSize(32, 16, 32, 8);
using ConstantFourDSourceType = rtk::ConstantImageSource<VolumeSeriesType>;
auto fourDSource = ConstantFourDSourceType::New();
fourDSource->SetOrigin(fourDOrigin);
fourDSource->SetSpacing(fourDSpacing);
fourDSource->SetSize(fourDSize);
fourDSource->Update();
// Read geometry, projections and signal
rtk::ThreeDCircularProjectionGeometry::Pointer geometry;
TRY_AND_EXIT_ON_ITK_EXCEPTION(geometry = rtk::ReadGeometry("four_d_geometry.xml"));
using ReaderType = rtk::ProjectionsReader<ProjectionStackType>;
auto projectionsReader = ReaderType::New();
std::vector<std::string> fileNames = std::vector<std::string>();
fileNames.push_back("four_d_projections.mha");
projectionsReader->SetFileNames(fileNames);
TRY_AND_EXIT_ON_ITK_EXCEPTION(projectionsReader->Update());
// Re-order geometry and projections
// In the new order, projections with identical phases are packed together
std::vector<double> signal = rtk::ReadSignalFile("four_d_signal.txt");
auto reorder = rtk::ReorderProjectionsImageFilter<ProjectionStackType>::New();
reorder->SetInput(projectionsReader->GetOutput());
reorder->SetInputGeometry(geometry);
reorder->SetInputSignal(signal);
TRY_AND_EXIT_ON_ITK_EXCEPTION(reorder->Update())
// Release the memory holding the stack of original projections
projectionsReader->GetOutput()->ReleaseData();
// Compute the interpolation weights
auto signalToInterpolationWeights = rtk::SignalToInterpolationWeights::New();
signalToInterpolationWeights->SetSignal(reorder->GetOutputSignal());
signalToInterpolationWeights->SetNumberOfReconstructedFrames(fourDSize[3]);
TRY_AND_EXIT_ON_ITK_EXCEPTION(signalToInterpolationWeights->Update())
// Set the forward and back projection filters to be used
using ConjugateGradientFilterType =
rtk::FourDConjugateGradientConeBeamReconstructionFilter<VolumeSeriesType, ProjectionStackType>;
auto fourdconjugategradient = ConjugateGradientFilterType::New();
fourdconjugategradient->SetInputVolumeSeries(fourDSource->GetOutput());
fourdconjugategradient->SetNumberOfIterations(2);
#ifdef RTK_USE_CUDA
fourdconjugategradient->SetCudaConjugateGradient(true);
fourdconjugategradient->SetForwardProjectionFilter(ConjugateGradientFilterType::FP_CUDARAYCAST);
fourdconjugategradient->SetBackProjectionFilter(ConjugateGradientFilterType::BP_CUDAVOXELBASED);
#else
fourdconjugategradient->SetForwardProjectionFilter(ConjugateGradientFilterType::FP_JOSEPH);
fourdconjugategradient->SetBackProjectionFilter(ConjugateGradientFilterType::BP_VOXELBASED);
#endif
// Set the newly ordered arguments
fourdconjugategradient->SetInputProjectionStack(reorder->GetOutput());
fourdconjugategradient->SetGeometry(reorder->GetOutputGeometry());
fourdconjugategradient->SetWeights(signalToInterpolationWeights->GetOutput());
fourdconjugategradient->SetSignal(reorder->GetOutputSignal());
auto verboseIterationCommand = rtk::VerboseIterationCommand<ConjugateGradientFilterType>::New();
fourdconjugategradient->AddObserver(itk::AnyEvent(), verboseIterationCommand);
TRY_AND_EXIT_ON_ITK_EXCEPTION(fourdconjugategradient->Update())
// Write
TRY_AND_EXIT_ON_ITK_EXCEPTION(itk::WriteImage(fourdconjugategradient->GetOutput(), "fourdconjugategradient.mha"));
return EXIT_SUCCESS;
}
import itk
from itk import RTK as rtk
OutputPixelType = itk.F
CpuProjectionStackType = itk.Image[OutputPixelType, 3]
CpuVolumeSeriesType = itk.Image[OutputPixelType, 4]
if hasattr(itk, "CudaImage"):
ProjectionStackType = itk.CudaImage[OutputPixelType, 3]
VolumeSeriesType = itk.CudaImage[OutputPixelType, 4]
else:
ProjectionStackType = CpuProjectionStackType
VolumeSeriesType = CpuVolumeSeriesType
# Generate the input volume series, used as initial estimate by 4D conjugate gradient
four_d_source = rtk.constant_image_source(
ttype=[VolumeSeriesType],
origin=[-63.0, -31.0, -63.0, 0.0],
spacing=[4.0, 4.0, 4.0, 1.0],
size=[32, 16, 32, 8],
)
# Read geometry, projections and signal
geometry = rtk.read_geometry("four_d_geometry.xml")
projections_reader = rtk.ProjectionsReader[CpuProjectionStackType].New()
file_names = ["four_d_projections.mha"]
projections_reader.SetFileNames(file_names)
projections_reader.Update()
signal = rtk.read_signal_file("four_d_signal.txt")
# Re-order geometry and projections
# In the new order, projections with identical phases are packed together
reorder = rtk.ReorderProjectionsImageFilter[
ProjectionStackType, ProjectionStackType
].New()
if hasattr(itk, "CudaImage"):
reorder.SetInput(itk.cuda_image_from_image(projections_reader.GetOutput()))
else:
reorder.SetInput(projections_reader.GetOutput())
reorder.SetInputGeometry(geometry)
reorder.SetInputSignal(signal)
reorder.Update()
# Release the memory holding the stack of original projections
projections_reader.GetOutput().ReleaseData()
# Compute the interpolation weights
signal_to_interpolation_weights = rtk.SignalToInterpolationWeights.New()
signal_to_interpolation_weights.SetSignal(reorder.GetOutputSignal())
signal_to_interpolation_weights.SetNumberOfReconstructedFrames(
four_d_source.GetLargestPossibleRegion().GetSize()[3]
)
signal_to_interpolation_weights.Update()
# Set the forward and back projection filters to be used
ConjugateGradientFilterType = rtk.FourDConjugateGradientConeBeamReconstructionFilter[
VolumeSeriesType, ProjectionStackType
]
fourdconjugategradient = ConjugateGradientFilterType.New()
if hasattr(itk, "CudaImage"):
fourdconjugategradient.SetCudaConjugateGradient(True)
fourdconjugategradient.SetForwardProjectionFilter(
ConjugateGradientFilterType.ForwardProjectionType_FP_CUDARAYCAST
)
fourdconjugategradient.SetBackProjectionFilter(
ConjugateGradientFilterType.BackProjectionType_BP_CUDAVOXELBASED
)
else:
fourdconjugategradient.SetForwardProjectionFilter(
ConjugateGradientFilterType.ForwardProjectionType_FP_JOSEPH
)
fourdconjugategradient.SetBackProjectionFilter(
ConjugateGradientFilterType.BackProjectionType_BP_VOXELBASED
)
fourdconjugategradient.SetInputVolumeSeries(four_d_source)
fourdconjugategradient.SetNumberOfIterations(2)
# Set the newly ordered arguments
fourdconjugategradient.SetInputProjectionStack(reorder.GetOutput())
fourdconjugategradient.SetGeometry(reorder.GetOutputGeometry())
fourdconjugategradient.SetWeights(signal_to_interpolation_weights.GetOutput())
fourdconjugategradient.SetSignal(reorder.GetOutputSignal())
class VerboseIterationCommand:
def __init__(self):
self.count = 0
def callback(self):
self.count += 1
print(f"Iteration {self.count}", end="\r")
class VerboseEndCommand:
def callback(self):
print("")
cmd = VerboseIterationCommand()
fourdconjugategradient.AddObserver(itk.IterationEvent(), cmd.callback)
cmd_end = VerboseEndCommand()
fourdconjugategradient.AddObserver(itk.EndEvent(), cmd_end.callback)
fourdconjugategradient.Update()
# Write
writer = itk.ImageFileWriter[CpuVolumeSeriesType].New()
writer.SetFileName("fourdconjugategradient.mha")
writer.SetInput(fourdconjugategradient.GetOutput())
writer.Update()
