Skip to content

ENH: Add support for multiple metrics and regularization#1437

Open
Leirbag-gabrieL wants to merge 1 commit into
SuperElastix:mainfrom
Leirbag-gabrieL:main
Open

ENH: Add support for multiple metrics and regularization#1437
Leirbag-gabrieL wants to merge 1 commit into
SuperElastix:mainfrom
Leirbag-gabrieL:main

Conversation

@Leirbag-gabrieL

Copy link
Copy Markdown

Hi, apparently it was not possible to do a multi-image registration with a transform metric like this:

(Metric "AdvancedMattesMutualInformation" "AdvancedMattesMutualInformation" "AdvancedMattesMutualInformation" "AdvancedMattesMutualInformation" "TransformBendingEnergyPenalty")
(Metric0Weight 1.0)
(Metric1Weight 1.0)
(Metric2Weight 1.0)
(Metric3Weight 1.0)
(Metric4Weight 10.0)

(NumberOfHistogramBins0 32)
(NumberOfHistogramBins1 32)
(NumberOfHistogramBins2 32)
(NumberOfHistogramBins3 32)

Now it is possible :)

@N-Dekker

Copy link
Copy Markdown
Member

Thank you very much @Leirbag-gabrieL

Can you possibly make a minimal example of a registration that is made possible by your proposed fix? Ideally in the form of a library unit test, like this one:

GTEST_TEST(itkElastixRegistrationMethod, EuclideanDistancePointMetric)
{
static constexpr auto ImageDimension = 2U;
using PixelType = float;
using ImageType = itk::Image<PixelType, ImageDimension>;
using SizeType = itk::Size<ImageDimension>;
using OffsetType = itk::Offset<ImageDimension>;
const OffsetType translationOffset{ { 1, -2 } };
const SizeType imageSize{ { 5, 6 } };
const auto fixedImage = CreateImage<PixelType>(imageSize);
const auto movingImage = CreateImage<PixelType>(imageSize);
elx::DefaultConstruct<ElastixRegistrationMethodType<ImageType>> registration{};
using PointType = itk::Point<float, ImageDimension>;
const PointType fixedPoint{};
PointType movingPoint = fixedPoint;
for (unsigned int i{}; i < ImageDimension; ++i)
{
movingPoint[i] += translationOffset[i];
}
registration.SetFixedImage(fixedImage);
registration.SetMovingImage(movingImage);
registration.SetFixedPoints(::MakeVectorContainer(std::vector<PointType>{ fixedPoint }));
registration.SetMovingPoints(::MakeVectorContainer(std::vector<PointType>{ movingPoint }));
registration.SetParameterObject(CreateParameterObject(
ParameterMapType{ // Parameters in alphabetic order:
{ "ImageSampler", { "Full" } },
{ "MaximumNumberOfIterations", { "2" } },
{ "Metric", { "AdvancedNormalizedCorrelation", "CorrespondingPointsEuclideanDistanceMetric" } },
{ "Optimizer", { "AdaptiveStochasticGradientDescent" } },
{ "Registration", { "MultiMetricMultiResolutionRegistration" } },
{ "Transform", { "TranslationTransform" } } }));
registration.Update();
const auto transformParameters = GetTransformParametersFromFilter(registration);
EXPECT_EQ(ConvertToOffset<ImageDimension>(transformParameters), translationOffset);
}

But then, having something like { "Metric", { "AdvancedMattesMutualInformation", "TransformBendingEnergyPenalty" } } instead.

Otherwise, a complete ("dummy") test set, including elastix parameter file, would also be of help to us. Again, preferably "minimal", so that it won't take much time to process.

using MovingImageDerivativeScalesType = FixedArray<double, Self::MovingImageDimension>;

/** Typedef for transform penalty metrics. */
typedef TransformPenaltyTerm<TFixedImage> TransformMetricType;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nowadays, we are using using, instead of typedef. But a coding style issue like that is not a show-stopper. I can adjust it afterwards. Also, I think I would move the definition of TransformMetricType into the function body of CombinationImageToImageMetric::SetMetric, as that seems to be the only place where it is needed.


/** Typedef for transform penalty metrics. */
typedef TransformPenaltyTerm<TFixedImage> TransformMetricType;
typedef typename TransformMetricType::Pointer TransformMetricPointer;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

TransformMetricPointer is never used, so I think I would remove it. (No show-stopper for now.)

TransformMetricType * testPtr2 = dynamic_cast<TransformMetricType *>(metric);

// Increase newly defined numberOfMetric counters
if (testPtr1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would make it:

if( dynamic_cast<PointSetMetricType *>(metric) )

And remove the local testPtr1 variable.

Similar for testPtr2, oldTestPtr1, oldTestPtr2. No show-stopper to me, though. Just a style thing.

@N-Dekker

Copy link
Copy Markdown
Member

Comment on lines -603 to +605
if ((this->GetNumberOfMovingImages() != 1) && (this->GetNumberOfMovingImages() != nrOfMetrics))
if ((this->GetNumberOfMovingImages() != 1) && (this->GetNumberOfMovingImages() != nrOfImageMetrics))
{
itkExceptionMacro("The NumberOfMovingImages should equal 1 or equal the NumberOfMetrics");
itkExceptionMacro("The NumberOfMovingImages should equal 1 or equal the nrOfImageMetrics");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@stefanklein @mstaring Would this change (comparing the number of input images with the number of image metrics, instead of the total number of metrics) break legacy use cases that would use "dummy" images when they use multiple metrics?

If so, shall we still allow such legacy use cases, by relaxing this check?

@stefanklein

stefanklein commented Jun 19, 2026 via email

Copy link
Copy Markdown
Member

@Leirbag-gabrieL

Copy link
Copy Markdown
Author

Hi,

sorry for responding so late, to answer your question @N-Dekker yes I totally took @alvarez-pa 's pull request as a strating point to do mine.
I see that you made the small little changes you asked me yourself. I was about the do a pull request of the changes you asked me here https://github.com/Leirbag-gabrieL/elastix/tree/pr_adjustments .

I think I should not do it since you just did this one #1453 right ?

@N-Dekker

Copy link
Copy Markdown
Member

Thanks for your work, @Leirbag-gabrieL My intention is indeed to merge PR #1453, and then close both PRs of you and @alvarez-pa. But your commit is still there in PR #1453, so your name will remain in our git log history 😃

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.

3 participants