Skip to content

Support multiple metrics and regularizations, add tests and backward compatibility ("legacy hack")#1453

Open
N-Dekker wants to merge 11 commits into
mainfrom
support-multiple-metrics-regularizations
Open

Support multiple metrics and regularizations, add tests and backward compatibility ("legacy hack")#1453
N-Dekker wants to merge 11 commits into
mainfrom
support-multiple-metrics-regularizations

Conversation

@N-Dekker

Copy link
Copy Markdown
Member

Comment on lines +3070 to +3079
// Parameters in alphabetic order:
{ "FixedImagePyramid", ParameterValuesType(3, "FixedRecursiveImagePyramid") },
{ "ImageSampler", ParameterValuesType(3, "Random") },
{ "Interpolator", ParameterValuesType(3, "BSplineInterpolator") },
{ "MaximumNumberOfIterations", { "2" } },
{ "Metric", { "AdvancedNormalizedCorrelation", "AdvancedNormalizedCorrelation", "TransformBendingEnergyPenalty" } },
{ "MovingImagePyramid", ParameterValuesType(3, "MovingRecursiveImagePyramid") },
{ "Optimizer", { "AdaptiveStochasticGradientDescent" } },
{ "Registration", { "MultiMetricMultiResolutionRegistration" } },
{ "Transform", { "TranslationTransform" } } }));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mstaring @stefanklein This unit test is intended to check that the original "legacy hack" still works. Can you please check that this is indeed the way the "legacy hack" worked? I mean, was the trick to add a dummy fixed image, a dummy moving image, an extra "FixedImagePyramid", an extra "MovingImagePyramid", an extra "ImageSampler", and an extra "Interpolator"?

@N-Dekker

N-Dekker commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

@stefanklein @mstaring Now I'm starting to doubt if a transform based metric like "TransformBendingEnergyPenalty" can work without having its own images. TransformBendingEnergyPenaltyTerm is derived from TransformPenaltyTerm<TFixedImage, TScalarType>, which is derived from AdvancedImageToImageMetric<TFixedImage, TFixedImage>. AdvancedImageToImageMetric has a pointer to a fixed image, and a pointer to a moving image.

The "GetValue" member functions of TransformBendingEnergyPenaltyTerm (for example TransformBendingEnergyPenaltyTerm::ThreadedGetValueAndDerivative) iterate over image samples.


Looks like the logic is implemented in elastix::MultiMetricMultiResolutionRegistration::SetComponents(), here:

/** Samplers are not always needed: */
for (unsigned int i = 0; i < nrOfMetrics; ++i)
{
if (this->GetElastix()->GetElxMetricBase(i)->GetAdvancedMetricUseImageSampler())
{
/** Try the i-th sampler for the i-th metric. */
if (this->GetElastix()->GetElxImageSamplerBase(i))
{
this->GetElastix()->GetElxMetricBase(i)->SetAdvancedMetricImageSampler(
this->GetElastix()->GetElxImageSamplerBase(i)->GetAsITKBaseType());
}
else
{
/** When a different fixed image pyramid is used for each metric,
* using one sampler for all metrics makes no sense.
*/
if (this->GetElastix()->GetElxFixedImagePyramidBase(i))
{
log::error(std::ostringstream{} << "ERROR: An ImageSamper for metric " << i << " must be provided!");
itkExceptionMacro("Not enough ImageSamplers provided!\nProvide an ImageSampler for metric "
<< i << ", like:\n (ImageSampler \"Random\" ... \"Random\")");
}
/** Try the zeroth image sampler for each metric. */
if (this->GetElastix()->GetElxImageSamplerBase(0))
{
this->GetElastix()->GetElxMetricBase(i)->SetAdvancedMetricImageSampler(
this->GetElastix()->GetElxImageSamplerBase(0)->GetAsITKBaseType());
}
else
{
log::error("ERROR: No ImageSampler has been specified.");
itkExceptionMacro("One of the metrics requires an ImageSampler, but it is not available!");
}
}
} // if sampler required by metric
} // for loop over metrics

So when the i-th metric requires to use an image sampler (including "TransformBendingEnergyPenalty") and there is no "i-th sampler", the metric may use "the zeroth image sampler".

So then, I guess this pull request should be OK, right?

Leirbag-gabrieL and others added 11 commits June 23, 2026 16:31
Ran clang-format afterwards.

This commit was proposed by Gabriel Husak (Leirbag-gabrieL), Apr 28, 2026 at #1437 It appears very much inspired by Pablo Alvarez (alvarez-pa), Jun 15, 2021, #482.
Removed two unnecessarily typedef's from the public interface of CombinationImageToImageMetric.
Following "Use the prefix form (`++i`) of the increment and decrement operators unless...", https://google.github.io/styleguide/cppguide.html#Preincrement_and_Predecrement

Also removed unnecessary `this->` from those cases.
Avoided repeatedly calling GetNumberOfInterpolators().
Avoided repeatedly calling GetNumberOfFixedImagePyramids() and GetNumberOfMovingImagePyramids().
Avoided repeatedly calling GetNumberOfFixedImages() and GetNumberOfMovingImages().
Based on issue #481 "Multiple metrics and regularization", Pablo Alvarez (alvarez-pa), Jun 15, 2021.
Allowed users to add "dummy" images when having more metrics than images (which was a "hack" before it was officially supported to have more metrics than images).
@N-Dekker N-Dekker force-pushed the support-multiple-metrics-regularizations branch from ecc68c0 to 8774750 Compare June 23, 2026 14:32
@N-Dekker

N-Dekker commented Jun 23, 2026

Copy link
Copy Markdown
Member Author

elastix supports the following 23 metrics. I guess we should check which metrics do or don't use the input images. Because it appears that transform based metrics (like "TransformBendingEnergyPenalty") may still use the images. I think CheckOnInitialize should become less strict than the current (main/release) version, but I doubt if that should be based on whether or not metrics are transform based.

"AdvancedKappaStatistic"    
"AdvancedMattesMutualInformation"    
"AdvancedMeanSquares"    
"AdvancedNormalizedCorrelation"    
"TransformBendingEnergyPenalty"    
"CorrespondingPointsEuclideanDistanceMetric"    
"DisplacementMagnitudePenalty"    
"DistancePreservingRigidityPenalty"    
"GradientDifference"    
"Impact"    
"KNNGraphAlphaMutualInformation"    
"MissingStructurePenalty"    
"NormalizedGradientCorrelation"    
"NormalizedMutualInformation"    
"PatternIntensity"    
"PCAMetric"    
"PCAMetric2"    
"PolydataDummyPenalty"    
"TransformRigidityPenalty"    
"StatisticalShapePenalty"    
"SumOfPairwiseCorrelationCoefficientsMetric"    
"SumSquaredTissueVolumeDifference"    
"VarianceOverLastDimensionMetric"

Four metric classes are derived from TransformPenaltyTerm (which is itself derived from AdvancedImageToImageMetric):

TransformBendingEnergyPenaltyTerm
DisplacementMagnitudePenaltyTerm
DistancePreservingRigidityPenaltyTerm
TransformRigidityPenaltyTerm

I think the only thing that matters is whether elastix::MetricBase::GetAdvancedMetricUseImageSampler() returns true or false. Metric classes at the following 13 locations do SetUseImageSampler(true):

Common\CostFunctions\itkParzenWindowHistogramImageToImageMetric
Components\Metrics\AdvancedKappaStatistic\itkAdvancedKappaStatisticImageToImageMetric
Components\Metrics\AdvancedMeanSquares\itkAdvancedMeanSquaresImageToImageMetric
Components\Metrics\AdvancedNormalizedCorrelation\itkAdvancedNormalizedCorrelationImageToImageMetric
Components\Metrics\BendingEnergyPenalty\itkTransformBendingEnergyPenaltyTerm
Components\Metrics\DisplacementMagnitudePenalty\itkDisplacementMagnitudePenaltyTerm
Components\Metrics\Impact\itkImpactImageToImageMetric
Components\Metrics\KNNGraphAlphaMutualInformation\itkKNNGraphAlphaMutualInformationImageToImageMetric
Components\Metrics\PCAMetric2\itkPCAMetric2
Components\Metrics\PCAMetric\itkPCAMetric
Components\Metrics\SumOfPairwiseCorrelationsMetric\itkSumOfPairwiseCorrelationCoefficientsMetric
Components\Metrics\SumSquaredTissueVolumeDifferenceMetric\itkSumSquaredTissueVolumeDifferenceImageToImageMetric
Components\Metrics\VarianceOverLastDimension\itkVarianceOverLastDimensionImageMetric

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants