I recently started a project where I would need to invert a BSplineTransformation. I dug now a bit into the different algorithms that ITK provides and had a look at the code. For the InvertDisplacementFieldImageFilter I do not really understand what the two parameters maxErrorToleranceThreshold
and meanErrorToleranceThreshold
actually should do. Unfortunately, of the three methods that are implemented, this one has the least documentation…
I found out that the “error norm” is calculated by scaling the displacement vector at each position by the inverse of the displacement field spacing and then computing the norm of that vector. The “meanError” is now simply the mean over the whole image and the “maxError” is the maximum. I do not understand why these are called “error” - as from the description I would have assumed that these give some idea about how well the inverse is actually constructed.
Especially it is puzzling to me why these values are set in the examples to something \ll 1? This would only make sense if the deformation would be close to zero, or not?
What I also found peculiar, is that I cannot get the same value back in python:
import SimpleITK as sitk
import numpy as np
ref = sitk.Image(100, 100, 100, sitk.sitkUInt8)
ref.SetSpacing([0.5, 0.5, 0.5])
np.random.seed(1234)
tfm = sitk.BSplineTransformInitializer(ref, [4, 4, 4], 3)
tfm.SetParameters(np.random.random((7 * 7 * 7 * 3, )))
disp = sitk.TransformToDisplacementField(
tfm,
sitk.sitkVectorFloat64,
ref.GetSize(),
ref.GetOrigin(),
ref.GetSpacing(),
ref.GetDirection(),
)
u = sitk.GetArrayFromImage(disp)
d_inv = np.divide(1, disp.GetSpacing())
err = np.linalg.norm(u * d_inv, axis=3)
print("Transform MaxNumpy: ", err.max())
print("Transform MeanNumpy: ", err.mean())
f_invert = sitk.InvertDisplacementFieldImageFilter()
f_invert.SetMaximumNumberOfIterations(10)
f_invert.SetMaxErrorToleranceThreshold(0.1)
f_invert.SetMeanErrorToleranceThreshold(0.001)
f_invert.SetEnforceBoundaryCondition(True)
u_invert = f_invert.Execute(disp)
print("MaxErrorNorm: ", f_invert.GetMaxErrorNorm())
print("MeanErrorNorm:", f_invert.GetMeanErrorNorm())
u = sitk.GetArrayFromImage(u_invert)
d_inv = np.divide(1, u_invert.GetSpacing())
err = np.linalg.norm(u * d_inv, axis=3)
print("MaxNumpy: ", err.max())
print("MeanNumpy: ", err.mean())
Which prints for me:
Transform MaxNumpy: 2.3317511267663256
Transform MeanNumpy: 1.862434889362297
MaxErrorNorm: 2.2723706213738817
MeanErrorNorm: 0.11159393739051904
MaxNumpy: 2.3295392382620137
MeanNumpy: 1.7557829087586647
The maximum error is actually not far off, but the average is off by a factor of around 16… Why is that the case?
Would it be a good idea for an inversion of a BSplineTransform to set the threshold to some value close to that of the original transformation, assuming that the inverse should have the same norm?