Skip to content

Take the IMPACT backend from ITKIMPACT - #1463

Merged
N-Dekker merged 5 commits into
SuperElastix:mainfrom
vboussot:refactor-itkimpact-backend
Aug 31, 2026
Merged

Take the IMPACT backend from ITKIMPACT#1463
N-Dekker merged 5 commits into
SuperElastix:mainfrom
vboussot:refactor-itkimpact-backend

Conversation

@vboussot

@vboussot vboussot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

The IMPACT losses, ModelConfiguration, online (Jacobian) inference, the vector-image interpolator and the Static feature-map generation come from the ITKIMPACT remote module, a one-way dependency Elastix to ITKIMPACT. The local copies (ImpactLoss.h, ImpactTensorUtils.*,
itkBSplineInterpolateVectorImageFunction.*, itkImpactModelConfiguration.h) are removed, and the backend's ModelConfiguration source is compiled into the Impact component so its torch-free public header stays Python-wrappable.

Building. With USE_ImpactMetric ON, ITKIMPACT_INCLUDE_DIR must point at the ITKIMPACT include/ directory. Both the ImpactMetric component and CommonGTest receive it, so elxImpactMetricGTest.cxx resolves the backend headers. ImpactWriteFeatureMaps becomes ImpactFeatureMapOutputDirectory.

Patch geometry follows the backend's. PatchPoint() sends each offset through the fixed image's direction matrix, one column per image axis, and the sampling callbacks receive the patch tensor shape from itk::Impact::PatchTensorShape(). This is the index-to-physical map ITKIMPACT applies in its v4 threader and in ImageToTensorFilter, so both modes describe the same neighbourhood.

CI provisions LibTorch with pip install torch on all three OSes, the same mechanism the backend uses. torch 2.11 is pinned rather than 2.12 because it is the last series still shipping CUDA cu128, the widest GPU coverage for the release: Blackwell plus older cards, driver floor R570, where 2.12 offers only cu126/cu129/cu130. The download_libtorch / build_libtorch actions are no longer referenced, and python-version moves to 3.11 for the
pip step.

@thewtex
thewtex requested a review from N-Dekker August 11, 2026 15:10
The IMPACT losses, ModelConfiguration, online (Jacobian) inference, the
vector-image interpolator and the Static feature-map generation come from the
ITKIMPACT remote module, a one-way dependency Elastix to ITKIMPACT. The local
copies (ImpactLoss.h, ImpactTensorUtils.*, itkBSplineInterpolateVectorImageFunction.*,
itkImpactModelConfiguration.h) are removed, and the backend's ModelConfiguration
source is compiled into the Impact component so its torch-free public header stays
Python-wrappable.

With USE_ImpactMetric ON, ITKIMPACT_INCLUDE_DIR must point at the ITKIMPACT
include/ directory; both the ImpactMetric component and CommonGTest receive it, so
elxImpactMetricGTest.cxx resolves the backend headers. ImpactWriteFeatureMaps
becomes ImpactFeatureMapOutputDirectory.

The patch geometry follows the backend's. PatchPoint() sends each offset through
the fixed image's direction matrix, one column per image axis, and the sampling
callbacks receive the patch tensor shape from itk::Impact::PatchTensorShape().
This is the index-to-physical map ITKIMPACT applies in its v4 threader and in
ImageToTensorFilter, so both modes describe the same neighbourhood.

CI provisions LibTorch with `pip install torch`, the same mechanism the backend
uses, on every runner that has a wheel: all three on GitHub Actions, and Windows
and Ubuntu on Azure. Azure's hosted macOS agent is x86_64 throughout, whatever the
macos-14 image name suggests, CMake there reporting host and target processor x86_64,
no NEON, and SSE2 and SSE4; PyTorch publishes no macOS x86_64 wheel past 2.2.2, so
no interpreter choice helps and that one job builds LibTorch from source, at
the v2.11.0 tag so all three platforms compile against the same headers. On Windows
the steps that run elastix set PATH from cmd, whose entry separator is the one the
loader expects, so the LibTorch DLLs are found next to the plugin. torch 2.11 is pinned rather than 2.12 because it is the last series still
shipping CUDA cu128, the widest GPU coverage for the release: Blackwell plus
older cards, driver floor R570, where 2.12 offers only cu126/cu129/cu130. The
download_libtorch / build_libtorch actions are removed, nothing referencing them.

The three Static baselines are regenerated against this configuration, and the
suite is 8/8 on this branch.
@vboussot
vboussot force-pushed the refactor-itkimpact-backend branch from de9f456 to 7f73707 Compare August 24, 2026 17:51
)

if(USE_ImpactMetric)
find_package(Torch REQUIRED)

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.

Maybe for a follow-up: would it be possible to add a minimum version to the find_package(Torch REQUIRED) call?

More specifically, I'm trying out your PR locally now, and I wonder if my LibTorch 2.8 is still good enough. (I have no problem upgrading to LibTorch 2.11, just wondering!)

Comment thread .github/workflows/ElastixGitHubActions.yml
Comment thread .github/workflows/publish-elastix.yml
"ITKIMPACT_INCLUDE_DIR to its include/ directory.")
endif()
target_include_directories(ImpactMetric PRIVATE ${ITKIMPACT_INCLUDE_DIR})
target_sources(ImpactMetric PRIVATE ${ITKIMPACT_INCLUDE_DIR}/../src/itkModelConfiguration.cxx)

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.

This works as long as ITKIMPACT only has one CXX file (itkModelConfiguration.cxx), of course. OK for now, but it might need to be extended in the future. No problem 👍

Comment thread Components/Metrics/Impact/elxImpactMetric.h Outdated
Comment thread Components/Metrics/Impact/elxImpactMetric.hxx
Comment thread Components/Metrics/Impact/elxImpactMetric.hxx Outdated
itk::ModelConfiguration is now itk::ImpactModelConfiguration in ITKIMPACT,
the name being too generic to introduce in the itk namespace. Track the
rename here, in the includes, the CMake source reference and the workflow
comment.

Name the local holding ImpactFeatureMapOutputDirectory after the parameter
it reads, rather than writeFeatureMapsStr, which described an older boolean
form of the option.

No behaviour change.
@vboussot
vboussot force-pushed the refactor-itkimpact-backend branch from 86f6315 to b5cc4b5 Compare August 28, 2026 09:05
libs:
- elastix-build/bin/libelx-ANNlib.so
- os: windows-2022
libtorch-cpu-url: "https://download.pytorch.org/libtorch/cpu/libtorch-win-shared-with-deps-2.8.0%2Bcpu.zip"

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 guess, the previous approach of downloading libtorch from https://download.pytorch.org/libtorch/cpu (rather than doing pip install torch) still remains useful for the Windows Debug version ("libtorch-win-shared-with-deps-debug-2.11.0+cpu.zip"), when building a Debug configuration of elastix with IMPACT, right? Anyway, no problem, just something to keep in mind 😇

numberOfChannelsVec[i],
patchSizeVecByModel[i],
voxelSizeVecByModel[i],
/*overlap*/ 0u,

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.

For other use-cases, the overlap argument would be non-zero, right? I'm just wondering, because when it is always just zero, it doesn't need to be an extra parameter 🤷 No problem, though, just curious.

#include "itkBSplineInterpolateVectorImageFunction.h"
// Vector-image interpolator consumed from the ITKIMPACT backend (same per-component
// B-spline interpolation as the former local itkBSplineInterpolateVectorImageFunction).
#include "itkInterpolateVectorImageFunction.h"

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.

The name "itkInterpolateVectorImageFunction.h" might (also) eventually cause naming conflicts with ITK (which currently just has a file named "itkInterpolateImageFunction.h")

No problem for now, just something I noticed.

Comment thread Components/Metrics/Impact/itkImpactImageToImageMetric.h Outdated
Comment thread Components/Metrics/Impact/itkImpactImageToImageMetric.h Outdated
@N-Dekker

Copy link
Copy Markdown
Member

@vboussot I'm ready to merge your PR now! Is it also ready to you? I guess so, otherwise you may convert to draft 😺

itkImpactModelConfigurationDetail.h and itkImageToFeaturesMapInternals.h are
implementation details of ITKIMPACT, and no symbol of theirs is used here.
ImpactModelConfiguration holds its pimpl in a shared_ptr, whose deleter is
captured at construction, so the complete type is never needed on this side.
@vboussot
vboussot force-pushed the refactor-itkimpact-backend branch from 4e1cd66 to 83b0cde Compare August 28, 2026 17:38
The Static path tiles whenever the patch size is non-zero -- the mode a
machine short on VRAM has to fall back to -- and assembled its feature maps
with no blending at all, the overlap being hardcoded to 0. The comment
claiming elastix does not tile was wrong.

ImpactOverlap<level> is optional and laid out like PatchSize, per axis and
per model. It defaults to a quarter of each patch axis; per axis rather than
a single value, because an anisotropic patch given the same overlap on every
axis blends its short axis over a far larger fraction of itself than its long
one.

Nothing shipped changes: every Static parameter map here uses a patch size of
0, which runs the whole image in one pass and never reassembles, and the
Jacobian path never reads the overlap.
@vboussot

Copy link
Copy Markdown
Contributor Author

@N-Dekker Thanks for these, all good catches 👍

ModelConfiguration is back to itk::ImpactModelConfiguration, and while I was at it I moved the internal tiling machinery (accumulator, patch grid, blend windows) into itk::Impact.

I did leave InterpolateVectorImageFunction and the ImageToTensorFilter / TensorToImageFilter pair alone, though. They're a generic ITK/torch bridge and a generic vector image interpolator rather than IMPACT concepts, so the resemblance to itkInterpolateImageFunction.h is on purpose 🙂

Your nitpick on the internal headers was spot on, and easier to fix than expected. elastix used no symbol from either, and the pimpl is held in a shared_ptr, so the complete type is never needed on this side. Both includes are gone.

And overlap turned out to be a real gap rather than a dead parameter, so thanks for pulling on that thread. It drives the blending when patches are reassembled, and elastix does tile in Static mode, which is what a machine short on VRAM has to fall back to. Hardcoding 0 meant those feature maps were stitched with no blending at all. It's now an ImpactOverlap parameter, per axis like PatchSize, defaulting to a quarter of each patch axis.

useMixedPrecision);
// The reassembly reads the per-axis overlap, which the constructor can only broadcast
// from its scalar; set it explicitly so an anisotropic patch keeps its own per-axis one.
modelsConfiguration.back().SetOverlaps(overlapVecByModel[i]);

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.

This looks somewhat redundant to me: first overlapVecByModel[i].front() is passed to the appended configuration, then overlapVecByModel[i] is also set into the configuration that is just appended. That's the way you mean it to be, right? Just double-checking 🤓

itk::ImpactModelConfiguration now takes the overlap per axis, so the scalar
plus SetOverlaps pair is gone.
@vboussot

Copy link
Copy Markdown
Contributor Author

Good catch, it was redundant. The constructor took a scalar and broadcast it, so the SetOverlaps right after overwrote what it had just done. And nothing ever read the scalar back.

Fixed in the backend instead of here: the configuration now takes the overlap per axis, like patchSize and voxelSize next to it, and elastix passes it in one call.

@N-Dekker

Copy link
Copy Markdown
Member

Thanks for addressing my overlap comment, @vboussot. Now your pull request is really ready, right? 😃

@vboussot

Copy link
Copy Markdown
Contributor Author

Yes, ready 😃 Thanks for the review !

@N-Dekker N-Dekker left a comment

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.

🎉

@N-Dekker
N-Dekker merged commit 1b006af into SuperElastix:main Aug 31, 2026
5 checks passed
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