Image Registration Method BSpline 3
If you are not familiar with the SimpleITK registration framework we recommend that you read the registration overview before continuing with the example.
Overview
This example performs registration of multi-modality images with a multi-resolution BSpline approach.
A BSplineTransform usually has a large number of parameters which increases the complexity and duration of optimizing the deformation. The multi-resolution BSpline approach initially performs the registration at a lower resolution with fewer parameters at the first level and then adapts or resamples the BSpline control points to a higher resolution at subsequent levels. This transformation adaption is done concurrently with the ImageRegistrationMethod’s multi-level feature. This enables the setting of the shrink factor, smoothing sigmas, sampling percentage and BSpline resolution to vary per level to efficiently solve a diverse set of registration problems.
The initial transform is the low resolution BSpline. The scaling
factor of each BSpline used per level is specified with the
ImageRegistration::SetInitialBSpline method’s third argument as an
integer array. The first value is usually 1 and it is reasonable to
double the resolution at each registration level. For this example the
last resolution is 5, so that the result is comparable to the
transformation from the previous example.
It can be important to monitor and observe the transform at each level or iteration. When the “inplace” option is enabled, the transform passed as the initial transform will be up to date during the registration process which enables it to be used in event commands.
See also: Image Registration Method BSpline 1, Image Registration Method BSpline 2.
Code
using itk.simple;
namespace itk.simple.examples
{
class IterationUpdate : Command
{
private ImageRegistrationMethod m_Method;
private Transform m_BSplineTransform;
public IterationUpdate(ImageRegistrationMethod m, Transform bsplineTransform)
{
m_Method = m;
m_BSplineTransform = bsplineTransform;
}
public override void Execute()
{
if (m_Method.GetOptimizerIteration() == 0)
{
// The BSpline is resized before the first optimizer
// iteration is completed per level. Print the transform object
// to show the adapted BSpline transform.
Console.WriteLine(m_BSplineTransform.ToString());
}
Console.WriteLine(String.Format("{0,3} = {1,10:F5}",
m_Method.GetOptimizerIteration(),
m_Method.GetMetricValue()));
}
}
class MultiResolutionIterationUpdate : Command
{
private ImageRegistrationMethod m_Method;
public MultiResolutionIterationUpdate(ImageRegistrationMethod m)
{
m_Method = m;
}
public override void Execute()
{
if (m_Method.GetCurrentLevel() > 0)
{
Console.WriteLine(String.Format("Optimizer stop condition: {0}",
m_Method.GetOptimizerStopConditionDescription()));
Console.WriteLine(String.Format(" Iteration: {0}", m_Method.GetOptimizerIteration()));
Console.WriteLine(String.Format(" Metric value: {0}", m_Method.GetMetricValue()));
}
Console.WriteLine("--------- Resolution Changing ---------");
}
}
class ImageRegistrationMethodBSpline3
{
static void Main(string[] args)
{
if (args.Length < 3)
{
Console.WriteLine("Usage: {0} <fixedImageFile> <movingImageFile> <outputTransformFile>",
System.AppDomain.CurrentDomain.FriendlyName);
return;
}
Image fixedImage = SimpleITK.ReadImage(args[0], PixelIDValueEnum.sitkFloat32);
Image movingImage = SimpleITK.ReadImage(args[1], PixelIDValueEnum.sitkFloat32);
VectorUInt32 transformDomainMeshSize = new VectorUInt32();
for (uint i = 0; i < fixedImage.GetDimension(); i++)
{
transformDomainMeshSize.Add(2);
}
BSplineTransform tx = SimpleITK.BSplineTransformInitializer(fixedImage, transformDomainMeshSize);
Console.WriteLine(String.Format("Initial Number of Parameters: {0}", tx.GetNumberOfParameters()));
ImageRegistrationMethod R = new ImageRegistrationMethod();
R.SetMetricAsJointHistogramMutualInformation();
double learningRate = 5.0;
uint numberOfIterations = 100;
double convergenceMinimumValue = 1e-4;
uint convergenceWindowSize = 5;
R.SetOptimizerAsGradientDescentLineSearch(learningRate, numberOfIterations,
convergenceMinimumValue, convergenceWindowSize);
R.SetInterpolator(InterpolatorEnum.sitkLinear);
VectorUInt32 scaleFactors = new VectorUInt32();
scaleFactors.Add(1); scaleFactors.Add(2); scaleFactors.Add(5);
R.SetInitialTransformAsBSpline(tx, true, scaleFactors);
VectorUInt32 shrinkFactors = new VectorUInt32();
shrinkFactors.Add(4); shrinkFactors.Add(2); shrinkFactors.Add(1);
R.SetShrinkFactorsPerLevel(shrinkFactors);
VectorDouble smoothingSigmas = new VectorDouble();
smoothingSigmas.Add(4); smoothingSigmas.Add(2); smoothingSigmas.Add(1);
R.SetSmoothingSigmasPerLevel(smoothingSigmas);
IterationUpdate cmd1 = new IterationUpdate(R, tx);
R.AddCommand(EventEnum.sitkIterationEvent, cmd1);
MultiResolutionIterationUpdate cmd2 = new MultiResolutionIterationUpdate(R);
R.AddCommand(EventEnum.sitkMultiResolutionIterationEvent, cmd2);
Transform outTx = R.Execute(fixedImage, movingImage);
Console.WriteLine("-------");
Console.WriteLine(tx.ToString());
Console.WriteLine(outTx.ToString());
Console.WriteLine(String.Format("Optimizer stop condition: {0}", R.GetOptimizerStopConditionDescription()));
Console.WriteLine(String.Format(" Iteration: {0}", R.GetOptimizerIteration()));
Console.WriteLine(String.Format(" Metric value: {0}", R.GetMetricValue()));
SimpleITK.WriteTransform(outTx, args[2]);
if (Environment.GetEnvironmentVariable("SITK_NOSHOW") == null)
{
ResampleImageFilter resampler = new ResampleImageFilter();
resampler.SetReferenceImage(fixedImage);
resampler.SetInterpolator(InterpolatorEnum.sitkLinear);
resampler.SetDefaultPixelValue(100);
resampler.SetTransform(outTx);
Image outputImage = resampler.Execute(movingImage);
Image simg1 = SimpleITK.Cast(SimpleITK.RescaleIntensity(fixedImage), PixelIDValueEnum.sitkUInt8);
Image simg2 = SimpleITK.Cast(SimpleITK.RescaleIntensity(outputImage), PixelIDValueEnum.sitkUInt8);
Image cimg = SimpleITK.Compose(simg1, simg2, SimpleITK.Divide(SimpleITK.Add(simg1, simg2), 2.0));
SimpleITK.Show(cimg, "Image Registration Composition");
}
}
}
}
#include <SimpleITK.h>
#include <iostream>
#include <stdlib.h>
#include <iomanip>
namespace sitk = itk::simple;
// use sitk's output operator for std::vector etc..
using sitk::operator<<;
class IterationUpdate : public sitk::Command
{
public:
IterationUpdate(const sitk::ImageRegistrationMethod & m, const sitk::BSplineTransform & tx)
: m_Method(m)
, m_BSplineTransform(tx)
{}
// Override method from parent which is called at for the requested event
void
Execute() override
{
if (m_Method.GetOptimizerIteration() == 0)
{
// The BSpline is resized before the first optimizer
// iteration is completed per level. Print the transform object
// to show the adapted BSpline transform.
std::cout << m_BSplineTransform.ToString() << std::endl;
}
// stash the stream state
std::ios state(NULL);
state.copyfmt(std::cout);
std::cout << std::fixed << std::setfill(' ') << std::setprecision(5);
std::cout << std::setw(3) << m_Method.GetOptimizerIteration();
std::cout << " = " << std::setw(10) << m_Method.GetMetricValue() << std::endl;
std::cout.copyfmt(state);
}
private:
const sitk::ImageRegistrationMethod & m_Method;
const sitk::BSplineTransform & m_BSplineTransform;
};
class MultiResolutionIterationUpdate : public sitk::Command
{
public:
MultiResolutionIterationUpdate(const sitk::ImageRegistrationMethod & m)
: m_Method(m)
{}
// Override method from parent which is called at for the requested event
void
Execute() override
{
// The sitkMultiResolutionIterationEvent occurs before the
// resolution of the transform. This event is used here to print
// the status of the optimizer from the previous registration level.
if (m_Method.GetCurrentLevel() > 0)
{
std::cout << "Optimizer stop condition: " << m_Method.GetOptimizerStopConditionDescription() << std::endl;
std::cout << " Iteration: " << m_Method.GetOptimizerIteration() << std::endl;
std::cout << " Metric value: " << m_Method.GetMetricValue() << std::endl;
}
std::cout << "--------- Resolution Changing ---------" << std::endl;
}
private:
const sitk::ImageRegistrationMethod & m_Method;
};
int
main(int argc, char * argv[])
{
if (argc < 4)
{
std::cerr << "Usage: " << argv[0] << " <fixedImageFilter> <movingImageFile> <outputTransformFile>" << std::endl;
return 1;
}
sitk::Image fixed = sitk::ReadImage(argv[1], sitk::sitkFloat32);
sitk::Image moving = sitk::ReadImage(argv[2], sitk::sitkFloat32);
std::vector<unsigned int> transformDomainMeshSize(fixed.GetDimension(), 2);
sitk::BSplineTransform tx = sitk::BSplineTransformInitializer(fixed, transformDomainMeshSize);
std::cout << "Initial Number of Parameters:" << tx.GetNumberOfParameters() << std::endl;
sitk::ImageRegistrationMethod R;
R.SetMetricAsJointHistogramMutualInformation();
const double learningRate = 5.0;
const unsigned int numberOfIterations = 100u;
const double convergenceMinimumValue = 1e-4;
const unsigned int convergenceWindowSize = 5;
R.SetOptimizerAsGradientDescentLineSearch(
learningRate, numberOfIterations, convergenceMinimumValue, convergenceWindowSize);
R.SetInterpolator(sitk::sitkLinear);
std::vector<unsigned int> scaleFactors = { 1, 2, 5 };
const bool inPlace = true;
R.SetInitialTransformAsBSpline(tx, inPlace, scaleFactors);
std::vector<unsigned int> shrinkFactors = { 4, 2, 1 };
R.SetShrinkFactorsPerLevel(shrinkFactors);
std::vector<double> smoothingSigmas = { 4.0, 2.0, 1.0 };
R.SetSmoothingSigmasPerLevel(smoothingSigmas);
IterationUpdate cmd1(R, tx);
R.AddCommand(sitk::sitkIterationEvent, cmd1);
MultiResolutionIterationUpdate cmd2(R);
R.AddCommand(sitk::sitkMultiResolutionIterationEvent, cmd2);
sitk::Transform outTx = R.Execute(fixed, moving);
std::cout << "-------" << std::endl;
std::cout << outTx.ToString() << std::endl;
std::cout << "Optimizer stop condition: " << R.GetOptimizerStopConditionDescription() << std::endl;
std::cout << " Iteration: " << R.GetOptimizerIteration() << std::endl;
std::cout << " Metric value: " << R.GetMetricValue() << std::endl;
sitk::WriteTransform(outTx, argv[3]);
return 0;
}
import org.itk.simple.*;
class IterationUpdate extends Command {
private ImageRegistrationMethod method;
private Transform bsplineTransform;
public IterationUpdate(ImageRegistrationMethod m, Transform bsplineTransform) {
method = m;
this.bsplineTransform = bsplineTransform;
}
public void execute() {
if (method.getOptimizerIteration() == 0) {
// The BSpline is resized before the first optimizer
// iteration is completed per level. Print the transform object
// to show the adapted BSpline transform.
System.out.println(bsplineTransform.toString());
}
System.out.printf("%3d = %10.5f%n",
method.getOptimizerIteration(),
method.getMetricValue());
}
}
class MultiResolutionIterationUpdate extends Command {
private ImageRegistrationMethod method;
public MultiResolutionIterationUpdate(ImageRegistrationMethod m) {
method = m;
}
public void execute() {
if (method.getCurrentLevel() > 0) {
System.out.println("Optimizer stop condition: " + method.getOptimizerStopConditionDescription());
System.out.println(" Iteration: " + method.getOptimizerIteration());
System.out.println(" Metric value: " + method.getMetricValue());
}
System.out.println("--------- Resolution Changing ---------");
}
}
class ImageRegistrationMethodBSpline3 {
public static void main(String[] args) {
if (args.length < 3) {
System.out.println("Usage: ImageRegistrationMethodBSpline3 <fixedImageFile> <movingImageFile> <outputTransformFile>");
return;
}
Image fixedImage = SimpleITK.readImage(args[0], PixelIDValueEnum.sitkFloat32);
Image movingImage = SimpleITK.readImage(args[1], PixelIDValueEnum.sitkFloat32);
// Create transform domain mesh size vector
VectorUInt32 transformDomainMeshSize = new VectorUInt32();
for (int i = 0; i < fixedImage.getDimension(); i++) {
transformDomainMeshSize.add(2L);
}
BSplineTransform tx = SimpleITK.bSplineTransformInitializer(fixedImage, transformDomainMeshSize);
System.out.println("Initial Number of Parameters: " + tx.getNumberOfParameters());
ImageRegistrationMethod R = new ImageRegistrationMethod();
R.setMetricAsJointHistogramMutualInformation();
double learningRate = 5.0;
int numberOfIterations = 100;
double convergenceMinimumValue = 1e-4;
int convergenceWindowSize = 5;
R.setOptimizerAsGradientDescentLineSearch(learningRate, numberOfIterations,
convergenceMinimumValue, convergenceWindowSize);
R.setInterpolator(InterpolatorEnum.sitkLinear);
VectorUInt32 scaleFactors = new VectorUInt32();
scaleFactors.add(1L); scaleFactors.add(2L); scaleFactors.add(5L);
R.setInitialTransformAsBSpline(tx, true, scaleFactors);
VectorUInt32 shrinkFactors = new VectorUInt32();
shrinkFactors.add(4L); shrinkFactors.add(2L); shrinkFactors.add(1L);
R.setShrinkFactorsPerLevel(shrinkFactors);
VectorDouble smoothingSigmas = new VectorDouble();
smoothingSigmas.add(4.0); smoothingSigmas.add(2.0); smoothingSigmas.add(1.0);
R.setSmoothingSigmasPerLevel(smoothingSigmas);
IterationUpdate cmd1 = new IterationUpdate(R, tx);
R.addCommand(EventEnum.sitkIterationEvent, cmd1);
MultiResolutionIterationUpdate cmd2 = new MultiResolutionIterationUpdate(R);
R.addCommand(EventEnum.sitkMultiResolutionIterationEvent, cmd2);
Transform outTx = R.execute(fixedImage, movingImage);
System.out.println("-------");
System.out.println(tx.toString());
System.out.println(outTx.toString());
System.out.println("Optimizer stop condition: " + R.getOptimizerStopConditionDescription());
System.out.println(" Iteration: " + R.getOptimizerIteration());
System.out.println(" Metric value: " + R.getMetricValue());
SimpleITK.writeTransform(outTx, args[2]);
if (System.getenv("SITK_NOSHOW") == null) {
ResampleImageFilter resampler = new ResampleImageFilter();
resampler.setReferenceImage(fixedImage);
resampler.setInterpolator(InterpolatorEnum.sitkLinear);
resampler.setDefaultPixelValue(100);
resampler.setTransform(outTx);
Image outputImage = resampler.execute(movingImage);
Image simg1 = SimpleITK.cast(SimpleITK.rescaleIntensity(fixedImage), PixelIDValueEnum.sitkUInt8);
Image simg2 = SimpleITK.cast(SimpleITK.rescaleIntensity(outputImage), PixelIDValueEnum.sitkUInt8);
Image cimg = SimpleITK.compose(simg1, simg2, SimpleITK.divide(SimpleITK.add(simg1, simg2), 2.0));
SimpleITK.show(cimg, "Image Registration Composition");
}
}
}
#!/usr/bin/env python
import sys
import os
import SimpleITK as sitk
def command_iteration(method, bspline_transform):
""" Callback invoked each iteration """
if method.GetOptimizerIteration() == 0:
# The BSpline is resized before the first optimizer
# iteration is completed per level. Print the transform object
# to show the adapted BSpline transform.
print(bspline_transform)
print(f"{method.GetOptimizerIteration():3} " + f"= {method.GetMetricValue():10.5f}")
def command_multi_iteration(method):
""" Callback invoked before starting a multi-resolution level.
The sitkMultiResolutionIterationEvent occurs before the
resolution of the transform. This event is used here to print
the status of the optimizer from the previous registration level.
"""
if method.GetCurrentLevel() > 0:
print(
"Optimizer stop condition: " + f"{R.GetOptimizerStopConditionDescription()}"
)
print(f" Iteration: {R.GetOptimizerIteration()}")
print(f" Metric value: {R.GetMetricValue()}")
print("--------- Resolution Changing ---------")
if len(sys.argv) < 4:
print(
"Usage:",
sys.argv[0],
"<fixedImageFilter> <movingImageFile>",
"<outputTransformFile>",
)
sys.exit(1)
fixed = sitk.ReadImage(sys.argv[1], sitk.sitkFloat32)
moving = sitk.ReadImage(sys.argv[2], sitk.sitkFloat32)
transformDomainMeshSize = [2] * fixed.GetDimension()
tx = sitk.BSplineTransformInitializer(fixed, transformDomainMeshSize)
print(f"Initial Number of Parameters: {tx.GetNumberOfParameters()}")
R = sitk.ImageRegistrationMethod()
R.SetMetricAsJointHistogramMutualInformation()
R.SetOptimizerAsGradientDescentLineSearch(
5.0, 100, convergenceMinimumValue=1e-4, convergenceWindowSize=5
)
R.SetInterpolator(sitk.sitkLinear)
R.SetInitialTransformAsBSpline(tx, inPlace=True, scaleFactors=[1, 2, 5])
R.SetShrinkFactorsPerLevel([4, 2, 1])
R.SetSmoothingSigmasPerLevel([4, 2, 1])
R.AddCommand(sitk.sitkIterationEvent, lambda: command_iteration(R, tx))
R.AddCommand(sitk.sitkMultiResolutionIterationEvent, lambda: command_multi_iteration(R))
outTx = R.Execute(fixed, moving)
print("-------")
print(tx)
print(outTx)
print(f"Optimizer stop condition: {R.GetOptimizerStopConditionDescription()}")
print(f" Iteration: {R.GetOptimizerIteration()}")
print(f" Metric value: {R.GetMetricValue()}")
sitk.WriteTransform(outTx, sys.argv[3])
if "SITK_NOSHOW" not in os.environ:
resampler = sitk.ResampleImageFilter()
resampler.SetReferenceImage(fixed)
resampler.SetInterpolator(sitk.sitkLinear)
resampler.SetDefaultPixelValue(100)
resampler.SetTransform(outTx)
out = resampler.Execute(moving)
simg1 = sitk.Cast(sitk.RescaleIntensity(fixed), sitk.sitkUInt8)
simg2 = sitk.Cast(sitk.RescaleIntensity(out), sitk.sitkUInt8)
cimg = sitk.Compose(simg1, simg2, simg1 // 2.0 + simg2 // 2.0)
sitk.Show(cimg, "Image Registration Composition")
# Run with:
#
# Rscript --vanilla ImageRegistrationMethodBSpline3.R fixedImageFilter movingImageFile outputTransformFile
library(SimpleITK)
# Callback invoked each iteration
command_iteration <- function(method, bspline_transform) {
if (method$GetOptimizerIteration() == 0) {
# The BSpline is resized before the first optimizer
# iteration is completed per level. Print the transform object
# to show the adapted BSpline transform.
cat(bspline_transform$ToString(), "\n")
}
cat(sprintf("%3d = %10.5f\n",
method$GetOptimizerIteration(),
method$GetMetricValue()))
}
# Callback invoked before starting a multi-resolution level.
command_multi_iteration <- function(method) {
if (method$GetCurrentLevel() > 0) {
cat(sprintf("Optimizer stop condition: %s\n",
method$GetOptimizerStopConditionDescription()))
cat(sprintf(" Iteration: %d\n", method$GetOptimizerIteration()))
cat(sprintf(" Metric value: %f\n", method$GetMetricValue()))
}
cat("--------- Resolution Changing ---------\n")
}
args <- commandArgs( TRUE )
if (length(args) < 3) {
stop("Usage: fixedImageFilter movingImageFile outputTransformFile")
}
fixed <- ReadImage(args[1], 'sitkFloat32')
moving <- ReadImage(args[2], 'sitkFloat32')
transformDomainMeshSize <- rep(2, fixed$GetDimension())
tx <- BSplineTransformInitializer(fixed, transformDomainMeshSize)
cat(sprintf("Initial Number of Parameters: %d\n", tx$GetNumberOfParameters()))
R <- ImageRegistrationMethod()
R$SetMetricAsJointHistogramMutualInformation()
learningRate <- 5.0
numberOfIterations <- 100
convergenceMinimumValue <- 1e-4
convergenceWindowSize <- 5
R$SetOptimizerAsGradientDescentLineSearch(learningRate, numberOfIterations,
convergenceMinimumValue, convergenceWindowSize)
R$SetInterpolator('sitkLinear')
R$SetInitialTransformAsBSpline(tx, inPlace=TRUE, scaleFactors=c(1, 2, 5))
R$SetShrinkFactorsPerLevel(c(4, 2, 1))
R$SetSmoothingSigmasPerLevel(c(4, 2, 1))
R$AddCommand('sitkIterationEvent', function() command_iteration(R, tx))
R$AddCommand('sitkMultiResolutionIterationEvent', function() command_multi_iteration(R))
outTx <- R$Execute(fixed, moving)
cat("-------\n")
cat(tx$ToString(), "\n")
cat(outTx$ToString(), "\n")
cat(sprintf("Optimizer stop condition: %s\n", R$GetOptimizerStopConditionDescription()))
cat(sprintf(" Iteration: %d\n", R$GetOptimizerIteration()))
cat(sprintf(" Metric value: %f\n", R$GetMetricValue()))
WriteTransform(outTx, args[3])
if (Sys.getenv("SITK_NOSHOW") == "") {
resampler <- ResampleImageFilter()
resampler$SetReferenceImage(fixed)
resampler$SetInterpolator('sitkLinear')
resampler$SetDefaultPixelValue(100)
resampler$SetTransform(outTx)
out <- resampler$Execute(moving)
simg1 <- Cast(RescaleIntensity(fixed), 'sitkUInt8')
simg2 <- Cast(RescaleIntensity(out), 'sitkUInt8')
cimg <- Compose(simg1, simg2, simg1 %/% 2.0 + simg2 %/% 2.0)
Show(cimg, "Image Registration Composition")
}