Skip to content

COMP: Use itk::Math::MatrixExponential instead of removed vnl_matrix_exp#1452

Open
hjmjohnson wants to merge 1 commit into
SuperElastix:mainfrom
hjmjohnson:migrate-to-itk-matrix-exponential
Open

COMP: Use itk::Math::MatrixExponential instead of removed vnl_matrix_exp#1452
hjmjohnson wants to merge 1 commit into
SuperElastix:mainfrom
hjmjohnson:migrate-to-itk-matrix-exponential

Conversation

@hjmjohnson

Copy link
Copy Markdown

VXL removed core/vnl/vnl_matrix_exp, which AffineLogTransform included via <vnl/vnl_matrix_exp.h> (InsightSoftwareConsortium/ITK#6452). This migrates the two call sites to itk::Math::MatrixExponential, an ITK-supported Eigen-backed replacement.

Depends on InsightSoftwareConsortium/ITK#6454 (adds itk::Math::MatrixExponential / itkMatrixExponential.h). Hold merge until an ITK containing it is required.

Change
-#include <vnl/vnl_matrix_exp.h>             →  #include "itkMatrixExponential.h"
-vnl_matrix_exp(... .GetVnlMatrix())         →  itk::Math::MatrixExponential(...)
-vnl_matrix_exp(A_bar)                        →  itk::Math::MatrixExponential(A_bar)

Call-site argument types (vnl_matrix_fixed, vnl_matrix) are unchanged; the new function provides matching overloads. Both AffineLogTransform and AffineLogStackTransform components rebuild clean against the new header.

@hjmjohnson hjmjohnson force-pushed the migrate-to-itk-matrix-exponential branch from 2e19386 to c33e80a Compare June 16, 2026 22:04
@hjmjohnson

Copy link
Copy Markdown
Author

Performance impact of migrating AffineLogTransform from vnl_matrix_exp to itk::Math::MatrixExponential. Freshly benchmarked on this transform's two call patterns (-O3 -DNDEBUG, best-of-11 min ns/call). Net: ~1.7–2× faster matrix-exp work per parameter-update, more accurate, results unchanged in practice.

Per-call timing + net AffineLogTransform impact
operation vnl_matrix_exp itk (Eigen) Eigen vs vnl
log-domain 2×2 (SetParameters) ~99 ns ~410 ns 4.1× slower
log-domain 3×3 (SetParameters) ~150 ns ~510 ns 3.4× slower
A_bar 4×4 (Jacobian loop) ~1349 ns ~695 ns 1.95× faster
A_bar 6×6 (Jacobian loop) ~2681 ns ~1333 ns 2.0× faster

Per SetParameters (1 log-domain exp + d² augmented A_bar exps in PrecomputeJacobianOfSpatialJacobian):

old (vnl) new (Eigen) net
2D 95 + 4·1331 = 5419 ns 405 + 4·692 = 3173 ns ~1.7× faster
3D 149 + 9·2676 = 24233 ns 503 + 9·1315 = 12338 ns ~2.0× faster

The d² A_bar Jacobian loop dominates and Eigen wins there, so net it's faster despite the single small log-domain exp being slower. Matrix-exp is a negligible slice of full registration wall-time, so end-to-end timing is effectively unchanged.

Numerical equivalence + accuracy

For near-identity log-domain matrices the absolute output difference between the two methods is ~1e-12 — far below any registration tolerance, so registration results are unchanged. Eigen (Higham scaling-and-squaring) is also ~2–3 orders of magnitude more accurate than vnl's Taylor series on the exp(A)·exp(−A)=I identity at every norm, and stays robust where the Taylor series degrades for large norms.

This PR depends on itk::Math::MatrixExponential landing in ITK (InsightSoftwareConsortium/ITK#6454).

@N-Dekker

Copy link
Copy Markdown
Member

Thanks Hans. Would this compile for both ITK 5 and 6, when we add something like #if ITK >= 6 and put the original code in the #else?

VXL removed core/vnl/vnl_matrix_exp.h, breaking AffineLogTransform's
include of <vnl/vnl_matrix_exp.h> (InsightSoftwareConsortium/ITK#6452).
Switch to itk::Math::MatrixExponential (itkMatrixExponential.h), an
ITK-supported Eigen-backed replacement using Higham scaling-and-squaring.

elastix's minimum is ITK 5.4.1, which lacks itkMatrixExponential.h, so
guard the include and both call sites on __has_include and fall back to
vnl_matrix_exp for older ITK. ITK#6454 adds the function and restores the
vnl_matrix_exp shim. Call-site argument types (vnl_matrix_fixed,
vnl_matrix) are unchanged.
@hjmjohnson hjmjohnson force-pushed the migrate-to-itk-matrix-exponential branch from c33e80a to 5aeef7f Compare June 19, 2026 02:15
@hjmjohnson

Copy link
Copy Markdown
Author

@N-Dekker Good catch — yes, and a guard is necessary: elastix's minimum is ITK 5.4.1 (find_package(ITK 5.4.1)), which has vnl_matrix_exp.h but not itkMatrixExponential.h, so the single-path version broke the minimum-version build. Added in 5aeef7f.

Why __has_include rather than #if ITK_VERSION_MAJOR >= 6

InsightSoftwareConsortium/ITK#6454 (which adds itk::Math::MatrixExponential) has no milestone yet, so the first tagged 6.x release containing it is unknown — keying on the header's presence is robust to that. #6454 also restored the (now-deprecated) vnl_matrix_exp shim, so current ITK main takes the new Eigen path while ITK 5.4.x falls back to vnl.

#ifndef ELX_HAS_ITK_MATRIX_EXPONENTIAL
#  if __has_include("itkMatrixExponential.h")
#    define ELX_HAS_ITK_MATRIX_EXPONENTIAL 1
#  else
#    define ELX_HAS_ITK_MATRIX_EXPONENTIAL 0
#  endif
#endif

Verified both branches compile locally: the Eigen path builds the full elastix/transformix against ITK main; the vnl fallback (forced via -DELX_HAS_ITK_MATRIX_EXPONENTIAL=0) compiles clean against the restored shim.

@hjmjohnson hjmjohnson marked this pull request as ready for review June 19, 2026 02:20

#include <vnl/vnl_matrix_exp.h>
// itk::Math::MatrixExponential exists in ITK >= 6 (ITK#6454); ITK 5.4.x lacks it, so fall back to vnl_matrix_exp.
#ifndef ELX_HAS_ITK_MATRIX_EXPONENTIAL

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.

Thanks Hans. I guess this #ifndef ELX_HAS_ITK_MATRIX_EXPONENTIAL is there to allow users to overrule the automatic detection of "itkMatrixExponential.h", right? So that they can explicitly choose between "itkMatrixExponential.h" and <vnl/vnl_matrix_exp.h>. Is that indeed the idea behind this initial #ifdef?

(Otherwise we might consider #undef ELX_HAS_ITK_MATRIX_EXPONENTIAL at the end of the hxx file.)

@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.

Approved unconditionally, but feel free to comment on my question about the initial #ifndef ELX_HAS_ITK_MATRIX_EXPONENTIAL 😃

@N-Dekker

Copy link
Copy Markdown
Member

@hjmjohnson Maybe we should still check if it won't break our regression tests, as it might yield slightly different result, right? But for now, I guess we have no choice, because without this PR, elastix + ITK6 won't build anymore 🤷

@N-Dekker

Copy link
Copy Markdown
Member

Performance impact of migrating AffineLogTransform from vnl_matrix_exp to itk::Math::MatrixExponential. Freshly benchmarked on this transform's two call patterns (-O3 -DNDEBUG, best-of-11 min ns/call). Net: ~1.7–2× faster matrix-exp work per parameter-update, more accurate, results unchanged in practice.

@hjmjohnson This sounds very promising. Can you possibly share the benchmark code?

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