Skip to content

ENH: Tier the Eigen-backed convenience numerics as itk::bridge - #6768

Merged
hjmjohnson merged 1 commit into
InsightSoftwareConsortium:mainfrom
hjmjohnson:enh-tier-unsupported-math-6620
Aug 21, 2026
Merged

ENH: Tier the Eigen-backed convenience numerics as itk::bridge#6768
hjmjohnson merged 1 commit into
InsightSoftwareConsortium:mainfrom
hjmjohnson:enh-tier-unsupported-math-6620

Conversation

@hjmjohnson

@hjmjohnson hjmjohnson commented Aug 19, 2026

Copy link
Copy Markdown
Member

Moves the Eigen-backed convenience numerics from itk::Math / itk:: into
itk::bridge, and renames their headers to itkBridge*.h, so the support tier
is visible in the spelling. Implements #6620.

ITK 6 is not released, so no deprecation window is owed — this is the last
point at which the move is free.

The namespace names what the layer is for: bridging call sites off the
deprecated VNL algorithms
without the caller first having to learn Eigen's
API. It is a migration aid, not a numerics library ITK undertakes to maintain
or validate — downstream code should prefer depending on Eigen directly.
That contract is now written down in three places (see below).

The support contract, and where it is documented

itk::bridge holds convenience wrappers over third-party numerical backends,
provided to ease the initial burden of migrating away from VNL. A vnl_svd
call site becomes itk::bridge::Math::SVD as a mechanical, low-risk edit.

They are not part of the core ITK mission to maintain or validate:

  • API and ABI may change, or be removed, without the deprecation cycle core
    ITK API changes receive.
  • Numerical behavior is the backend's. ITK does not independently validate
    accuracy, conditioning, or convergence, and does not guarantee bit-for-bit
    stability across backend versions.
  • Coverage extends only to what ITK's own migration required; missing
    functionality will not necessarily be added.

Downstream users should prefer Eigen (or another numerical library)
directly
rather than depending on the API/ABI of these wrappers. Using
itk::bridge as a transitional step while retiring VNL is reasonable;
treating it as a permanent dependency is not.

Documented at three levels, so the warning is reachable from wherever a reader
starts:

Where What
Documentation/Doxygen/BridgeNumerics.dox Canonical statement — a \page plus a \namespace itk::bridge block
Each of the 10 headers 5-line \file block pointing at that page
ITK 6 migration guide Prose section, for downstream readers who never open a header
Why — the concern this addresses

VNL was vendored as an implementation detail, but its symbols reached ITK's
public surface. ITK thereby became the de-facto maintainer and compatibility
guarantor of general linear algebra for two decades, and could not change its
numerics backend without a deprecation campaign.

Every itk::Math::* wrapper added by the VNL→Eigen migration is currently
indistinguishable — in namespace, header location, and implied support level —
from core contract API like itk::Image. Downstream adopts them as freely as
it adopted vnl_svd. Naming the tier, and stating its contract in the
documentation, is what prevents the same trap closing a second time.

Scope — 10 headers

itkMathSVD.h, itkMathLDLT.h, itkMathDeterminant.h, itkCholeskySolve.h,
itkQRDecomposition.h, itkSymmetricEigenDecomposition.h,
itkRealEigenDecomposition.h, itkGeneralizedEigenDecomposition.h, and the
two itk::detail helpers — each gaining an itkBridge prefix.

itkCholeskySolve.h is included beyond the list in #6620: it is Eigen-backed,
lives in itk::Math, and is one of "the LU decomposition and the other matrix
solving methods" @blowekamp scoped. Two test consumers.

Out of scope, per @dzenanz: itk::Math::abs, the itkMath.h constants, and
other long-standing utilities — core contract already.

The capability macro becomes ITK_BRIDGE_MATH_HAS_SOLVE_SYMMETRIC.
GoogleTest suite names gain the same prefix.

Fixes the VNL deprecation diagnostics (@greptileai P1)

Greptile found that the VNL deprecation messages still named the pre-move
itk::Math::SVD / itkMathSVD.h, so a consumer following the emitted
guidance could not compile — it verified this by building an external
consumer. Correct, and it was a live defect on the previous head rather than
one introduced here.

The actual extent was larger than the four files reported. 13 VNL headers
plus 2 CMakeLists.txt comments
carried stale spellings across the whole
family, not just SVD:

vnl_svd.h, vnl_svd_fixed.h, vnl_svd_economy.h, vnl_matrix_inverse.h,
vnl_cholesky.h, vnl_ldl_cholesky.h, vnl_determinant.h, vnl_det.h,
vnl_qr.h, vnl_real_eigensystem.h, vnl_symmetric_eigensystem.h,
vnl_generalized_eigensystem.h, vnl_scatter_3x3.h, plus
algo/CMakeLists.txt.

All now name the itk::bridge spellings.

Why no separate CommonMath module

A Modules/Core/CommonMath module was evaluated and rejected: it cannot be
built without a dependency cycle.

  • itkMatrix.h includes itkBridgeMathSVD.h and itkBridgeMathDeterminant.h
    for GetInverse(); itkImageBase.hxx and itkVersor.hxx call
    Math::Determinant. So those headers must sit at-or-below ITKCommon.
  • itkBridgeMathLDLT.h includes itkMatrix.h, itkArray.h, itkArray2D.h
    and itkVector.h. So it must sit at-or-above ITKCommon.

The surface straddles ITKCommon in both directions.

Worth noting for the tiering discussion: ITK core depends on these APIs.
itk::Matrix::GetInverse() needs them. The tier can describe the public
spelling; it cannot describe the dependency.

Why the headers are renamed, not just the namespace

Downstream feature-detects with __has_include(<itkMathSVD.h>). If only the
namespace moved, that probe would still succeed while the symbol inside had
changed — the guard would report the wrong answer and the build would fail at
the call site instead. Renaming makes the probe truthful: the old path
genuinely disappears, so #elif fallbacks work as written.

Test plan — forest build

Validated in the ITK forest testbed (ITK + downstream consumers built against
one locally built ITK):

Repo Build Tests Guard path
ITK all 1633 targets, 0 errors 75/75 tier GTests n/a
ANTs 0 errors objects reference the tiered namespace; 0 reference the old symbol
BRAINSTools 0 errors same

The guard column is the one that matters: a rename like this can compile
cleanly while silently dropping downstream back to vnl_svd. Symbol
inspection confirms both consumers kept the Eigen path.

pre-commit run --all-files clean, re-run after the rebase onto current main.

⚠ ccache can serve stale objects across this rename

If you build a downstream project incrementally with a warm ccache, you may
see link errors naming the old itk::Math::detail::RectangularSVDEigen.

ccache direct mode keys an object on a manifest of the files it included. When
a header is renamed rather than edited, a compile can hash-hit an entry
recorded against the vanished include set and return an object compiled
against headers that no longer exist. ninja -t clean does not help — it
deletes the object and ccache immediately re-serves the stale one.

CCACHE_RECACHE=1 for the downstream build fixes it. CI does cold builds, so
this never appears there — it only affects local evaluation.

@github-actions github-actions Bot added type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots type:Enhancement Improvement of existing methods or implementation area:Examples Demonstration of the use of classes type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct area:Core Issues affecting the Core module area:Filtering Issues affecting the Filtering module area:IO Issues affecting the IO module area:Registration Issues affecting the Registration module area:Segmentation Issues affecting the Segmentation module area:Video Issues affecting the Video module area:Numerics Issues affecting the Numerics module area:Documentation Issues affecting the Documentation module labels Aug 19, 2026
@hjmjohnson
hjmjohnson marked this pull request as ready for review August 19, 2026 21:38
@greptile-apps

This comment was marked as resolved.

@blowekamp

blowekamp commented Aug 19, 2026

Copy link
Copy Markdown
Member

Naming is still open. itk::unsupported::Math::* follows @blowekamp's
proposal and @dzenanz's concurrence on #6620, but if the thread settles on a
different name I will re-spin. Nothing else in the PR depends on the choice.

Is the recommendation for users of the old interface in the long term plan to directly use the Eigen interfaces but in the short term the unsupported interfaces can be used to aid in transitioning?

@hjmjohnson

Copy link
Copy Markdown
Member Author

Is the recommendation for users of the old interface to in the long term plan to directly use the Eigen interfaces but in the short term the unsupported interfaces can be used to aid is transitioning?

@blowekamp I don't ever see that happening. It is what you proposed as an ideal solution, but it's pretty combersome for users to use the raw interfaces of Eigen. Personally, I don't like the "unsupported" naming because I think it indicates that it is unsafe. I like "convenience".

Originally, ITK was conceived to strictly be algorithms (not have any IO included), but it was quickly realized that without the convenience of IO in ITK algorithms, imaging algorithms would not gain traction. I think of these algorithms the same way: without an SVD or reference CG optimizer readily available, ITK becomes much more burdensome to use. SVD is not part of the core mission of ITK, but without easy-to-access implementations, ITK is too clunky to use.

I have accepted that my personal preference to have these common convenience methods in itk::Math was overruled, and I am trying to identify the desired pattern for an acceptable solution before ITKv6 become tagged.

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

Generally looks good.

@blowekamp

Copy link
Copy Markdown
Member

@hjmjohnson Thank you.

So would it perhaps be better to so that it is recommended that new code directly use the Eigen library interface for numeric code, and that legacy code will be supported with this "bridge" interface between vnl data objects and Eigen algorithms?

If so then maybe "bridge" would be a better namespace?

@hjmjohnson

Copy link
Copy Markdown
Member Author

If so then maybe "bridge" would be a better namespace?

I am much more supportive of "bridge" with documentation. (I'm not opposed to "unsupported"; it just rubs me the wrong way).

@hjmjohnson hjmjohnson changed the title ENH: Tier the Eigen-backed convenience numerics as itk::unsupported ENH: Tier the Eigen-backed convenience numerics as itk::bridge Aug 20, 2026
@hjmjohnson
hjmjohnson force-pushed the enh-tier-unsupported-math-6620 branch from 547a37c to be08a62 Compare August 20, 2026 21:41
@github-actions github-actions Bot added the area:ThirdParty Issues affecting the ThirdParty module label Aug 20, 2026
@hjmjohnson

Copy link
Copy Markdown
Member Author

Force-pushed 547a37cbe08a62: renamed the tier from itk::unsupported to itk::bridge, and added the documentation that states what the tier is and is not. Content-only — no rebase mixed in, so the compare link shows just this change.

@dzenanz @N-Dekker — this is a whole-namespace rename since your last look, so a summary rather than asking you to re-read 83 files:

  • itk::unsupporteditk::bridge; itkUnsupported*.hitkBridge*.h; GoogleTest suites Unsupported*Bridge*; macro → ITK_BRIDGE_MATH_HAS_SOLVE_SYMMETRIC. No behavior change.
  • The name now says what the layer is for — bridging call sites off VNL — rather than only what it is not. The support contract is unchanged and is now written down rather than implied: convenience wrappers to ease initial migration, not part of the core ITK mission to maintain or validate, and downstream should prefer depending on Eigen directly. Stated in Documentation/Doxygen/BridgeNumerics.dox (a \page plus a \namespace itk::bridge block), in a \file block in each of the 10 headers, and as a section in the ITK 6 migration guide.

@greptileai — your P1 was correct and it was a live defect, not one the rename introduced. The extent was larger than reported: 13 VNL headers plus 2 CMakeLists.txt comments carried pre-move spellings across the whole family, not just the four SVD-related files. All now name the itk::bridge spellings.

Local verification
  • Full build: 3842/3842 targets, exit 0.
  • ctest -R '^Bridge'75/75 passed. The pre-rename tree had 75 TEST macros in the same files, so nothing was lost in the rename. (The body previously said 78/78; that figure was wrong and is corrected.)
  • pre-commit run --all-files → exit 0 against this exact tree.
  • clang-format reflowed 11 files, since bridge:: is shorter than unsupported:: and changes wrapping. Folded into the commit.

Two follow-ups I have not done here: the branch name still contains unsupported (renaming it would close this PR), and the downstream WIP drafts ANTs #2019 / BRAINSTools #623 still reference the old spelling — they need updating before they can build against this.

@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

The rename is clean and comprehensive. Looking across the key touch-points:

Verified consistent throughout:

  • itkMatrix.h → includes itkBridgeMathSVD.h/itkBridgeMathDeterminant.h, calls bridge::Math::SVD/bridge::Math::Determinant (within namespace itk, so fully resolves to itk::bridge::Math::... correctly)
  • itkBridgeMathSVD.h → namespace block is itk::bridge::Math { ... }, exception messages updated to itk::bridge::Math::SVD, detail:: calls updated to itk::bridge::detail::
  • itkBridgeEigenDecompositionSignConvention.h → correctly placed in itk::bridge::detail
  • All 13 VNL deprecation shims now emit itk::bridge::Math::SVD (itkBridgeMathSVD.h) — the P1 is fully resolved, and the broader extent (all family members, not just SVD) is correct
  • Migration guide updated throughout: the eigendecomposition section, the SVD section, and the new itk::bridge contract preamble that precedes them all

BridgeNumerics.dox is well-structured. The three-level documentation (Doxygen \page + \file blocks in each of the 10 headers + migration guide prose) means the contract is reachable regardless of where a reader starts. The language is appropriately direct: "a migration aid, not a numerics library."

One thing worth confirming: itkMatrix.h::GetInverse() uses bridge::Math::Determinant and bridge::Math::SVD unqualified. Since the method is defined inside namespace itk, these resolve to itk::bridge::Math::... correctly. If your forest build is green that's confirmed in practice, but if you ever want to be defensive against a TU that happens to have another bridge symbol in scope, spelling itk::bridge::Math::Determinant in full is unambiguous. Not a blocker — just worth keeping in mind if you see an unexpected name-lookup issue downstream.

The two acknowledged follow-ups (ANTs #2019 / BRAINSTools #623 need updating to the itk::bridge spellings before they can merge against this) are the right gating concern. The PR itself looks complete.

@hjmjohnson

Copy link
Copy Markdown
Member Author

Pointers to the documentation added in be08a62, since it is easy to lose in an 83-file diff.

1. The canonical statement — Documentation/Doxygen/BridgeNumerics.dox (new file, 57 lines)

Two blocks:

  • Lines 1-41\page BridgeNumericsPage, with sections Intent, Not part of the core ITK mission, and Guidance for downstream users.
  • Lines 43-57 — a \namespace itk::bridge block, so the warning attaches to the namespace page in Doxygen output rather than only to a standalone page nobody navigates to.

2. A representative per-header pointer — itkBridgeMathSVD.h lines 21-27

/** \file
 * Part of the \c itk::bridge migration-aid namespace: a convenience wrapper over a
 * third-party numerical backend, not core ITK API. It is not maintained or validated
 * to core ITK standards and may change API/ABI without a deprecation cycle.
 * Downstream code should prefer calling Eigen directly.
 * \see \ref BridgeNumericsPage
 */

Identical text sits immediately after the include guard in all ten headers: CholeskySolve, EigenDecompositionSignConvention, EigenDecompositionSolverInfo, GeneralizedEigenDecomposition, MathDeterminant, MathLDLT, MathSVD, QRDecomposition, RealEigenDecomposition, SymmetricEigenDecomposition.

It deliberately points at the page rather than restating it, so ten copies cannot drift — a change to the contract edits only the .dox.

3. Downstream-facing prose — ITK 6 migration guide, lines 754-777

Placed before the eigendecomposition section, so the contract is read before the replacement classes are named.

Why three places rather than one

Each hits a different reader where they already are:

Reader Sees
Browsing Doxygen for itk::bridge the \namespace block
Opening a header in an editor the \file block
Planning a VNL migration, never opens a header the migration-guide section

A single .dox page would have been invisible to the second and third.

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

Looks good on a glance.

Comment thread Modules/Core/Common/include/itkBridgeCholeskySolve.h Outdated
@hjmjohnson
hjmjohnson force-pushed the enh-tier-unsupported-math-6620 branch from be08a62 to c66b124 Compare August 21, 2026 13:19
The VNL to Eigen migration has been adding general-purpose numerics to
itk::Math and itk:: directly, where nothing distinguishes them from core
contract API such as itk::Image. That is how VNL became ITK's permanent
maintenance obligation: its symbols reached the public surface and could
not be changed afterwards.

Move the Eigen-backed convenience layer to itk::bridge, rename the
headers to itkBridge*.h so downstream __has_include probes report the
move truthfully instead of matching a header whose contents have
changed, and prefix the GoogleTest suite names to match.

The namespace names what the layer is for: bridging call sites off the
deprecated VNL algorithms without the caller first learning Eigen's API.
It is a migration aid, not a numerics library ITK undertakes to maintain
or validate, and downstream code should prefer depending on Eigen
directly. Documentation/Doxygen/BridgeNumerics.dox states that contract
as a Doxygen page plus a namespace block, and the ITK 6 migration guide
repeats it for downstream readers.

itkCholeskySolve.h is included in the move: it is Eigen-backed, lives in
itk::Math, and is one of the matrix solving methods this tier covers.

A separate Modules/Core/CommonMath module was evaluated and rejected:
itkMatrix.h includes itkBridgeMathSVD.h and itkBridgeMathDeterminant.h
for GetInverse, while itkBridgeMathLDLT.h includes itkMatrix.h, so the
surface straddles ITKCommon in both directions and cannot be split out
without a dependency cycle.

The VNL deprecation diagnostics named the pre-move spellings
(itk::Math::SVD, itkMathSVD.h and siblings), so a consumer following
them could not compile. They now name the itk::bridge spellings.

ITK 6 is not released, so no deprecation window is owed.

Renames the capability macro to ITK_BRIDGE_MATH_HAS_SOLVE_SYMMETRIC.

Issue: InsightSoftwareConsortium#6620
@hjmjohnson
hjmjohnson force-pushed the enh-tier-unsupported-math-6620 branch from c66b124 to a2c58cc Compare August 21, 2026 13:36
@hjmjohnson

Copy link
Copy Markdown
Member Author

Two force-pushes, kept separate so the compare links are readable:

  1. be08a62c66b124plain rebase onto current main (picks up BUG: Let _get_itk_pixelid map np.int64 to itk.int64_t, instead of itk.SL #6771). No content changes; git range-diff reports the patch byte-identical.
  2. c66b124a2c58ccreview fix only: removed the ten per-file \file blocks per @blowekamp, and corrected the commit message that referenced them.
Local verification on a2c58cc
  • Full build after the rebase: 1460 targets, 0 errors.
  • ctest -R '^Bridge'75/75 passed.
  • pre-commit run --all-files → exit 0.
  • Merge base unchanged between pushes 1 and 2, so the second compare shows only the doc removal.

@dzenanz — your approval was against be08a62. The head has moved twice since (rebase + doc removal), so a re-glance may be worth it, though the only content change is the deletion.

@hjmjohnson
hjmjohnson merged commit 9a32647 into InsightSoftwareConsortium:main Aug 21, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Core Issues affecting the Core module area:Documentation Issues affecting the Documentation module area:Examples Demonstration of the use of classes area:Filtering Issues affecting the Filtering module area:IO Issues affecting the IO module area:Numerics Issues affecting the Numerics module area:Registration Issues affecting the Registration module area:Segmentation Issues affecting the Segmentation module area:ThirdParty Issues affecting the ThirdParty module area:Video Issues affecting the Video module type:Enhancement Improvement of existing methods or implementation type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants