COMP: Prefer the tiered itk::bridge::Math spelling for ITK's Eigen numerics - #623
Conversation
ff93ffe to
b9528ae
Compare
…-inverse (gated) Route the normal-equations left pseudo-inverse in GTRACT Matrix_Inverse through a gated SymmetricInverse helper. When a new-enough ITK provides itk::Math::SolveSymmetric (capability macro ITK_MATH_HAS_SOLVE_SYMMETRIC), (M^T M)^-1 is built column-by-column from Eigen LDLT solves of (M^T M) X = I; otherwise the legacy vnl_matrix_inverse is used. Guarded by __has_include plus the macro, so it builds unchanged against the currently pinned ITK. Math justification: Matrix_Inverse(M) returns the left pseudo-inverse (M^T M)^-1 M^T used to solve the diffusion-tensor least-squares system. The Gram matrix M^T M is symmetric positive-definite by construction, so its inverse is symmetric and LDLT (Bunch-Kaufman symmetric pivoting) is the appropriate factorization; solving (M^T M) X = I column by column yields (M^T M)^-1 exactly. The caller holds the returned pseudo-inverse matrix and reuses it per voxel, so Matrix_Inverse must still return the matrix (form-the-inverse-via-solve), and it is a one-time setup cost, not a per-voxel hot path. Validation: Cross-checked the full pseudo-inverse (M^T M)^-1 M^T against the vnl_matrix_inverse path on random tall M at the float precision used here (8 cases): worst relative difference 6.3e-6, i.e. float machine precision. (Absolute difference is larger and condition-number dependent because an ill-conditioned M^T M inverted in float legitimately diverges between algorithms; the fair metric is relative to the pseudo-inverse magnitude.) The gated file compiles clean against the pinned ITK vnl headers via the legacy path (syntax-checked; no in-tree BRAINSTools build was available for a full object build). Part of the vnl -> itk::Math de-vendoring effort; symmetric-inverse site identified in the forest audit following the ANTs adoption.
…verses (gated) Route two symmetric-matrix inverses through gated SymmetricInverse helpers, adopting itk::Math::SolveSymmetric (Eigen LDLT) when a new-enough ITK provides it (capability macro ITK_MATH_HAS_SOLVE_SYMMETRIC) and falling back to vnl_matrix_inverse otherwise: - BRAINSABC LLSBiasCorrector: per-class covariance inverse (invCovars). - BRAINSConstellationDetector fcsv_to_hdf5: EPCA Gram inverse (Zi Zi^T). Both guarded by __has_include plus the macro, so they build unchanged against the currently pinned ITK. The unrelated QR-R inverse in LLSBiasCorrector (a general triangular matrix, not symmetric) is intentionally left on vnl_matrix_inverse. Math justification: A covariance matrix and a Gram matrix Zi Zi^T are symmetric positive-(semi)- definite by construction, so their inverses are symmetric and LDLT (Bunch-Kaufman symmetric pivoting) is the appropriate factorization; solving A X = I column by column yields A^-1 exactly. Both sites use the full inverse matrix downstream and are one-time setup costs (per-class / per-training), not per-voxel hot paths. Validation: The SymmetricInverse-vs-vnl_matrix_inverse equivalence was cross-checked on random SPD matrices (double: worst relative difference <= 2.6e-15; float SPD Gram in the sibling ANTs prototypes: ~float machine precision). Compile note: no in-tree BRAINSTools build was available, so these two files were not object-compiled; the swap is a minimal restructure of existing vnl operations on the legacy path, and the helper logic is validated in the shared harness (.devlocal/tensor-solve-prototype). Part of the vnl -> itk::Math de-vendoring effort; sites from the forest audit.
…rseSymmetric Replace the open-coded column-by-column inverse loops in the gated SymmetricInverse helpers (GTRACT algo.cxx, fcsv_to_hdf5.cxx, LLSBiasCorrector.hxx) with a single itk::Math::InverseSymmetric(A) call. The prior loop re-factorized A once per column (O(n^4)); InverseSymmetric factorizes once (O(n^3)). Behavior and the #ifdef ITK_MATH_HAS_SOLVE_SYMMETRIC gate are unchanged; the legacy vnl_matrix_inverse fallback is retained and still compiles against the pinned ITK (GTRACT algo.cxx syntax-checked). Requires an ITK carrying itk::Math::InverseSymmetric on the new path.
…nce inverse (gated) Route the per-class covariance inverse in EMSegmentationFilter (used to form the Gaussian invcov for Mahalanobis posteriors) through a gated em_detail:: SymmetricInverse helper: itk::Math::InverseSymmetric (Eigen LDLT) when available, vnl_matrix_inverse otherwise. The covariance is symmetric positive-(semi)definite, so LDLT is the appropriate factorization. Computed once per class before the per-voxel posterior loop (setup, not hot). Gated by __has_include plus ITK_MATH_HAS_SOLVE_SYMMETRIC; builds unchanged against the pinned ITK. This site was found alongside the LLSBiasCorrector covariance during the audit follow-up.
ITK moved the Eigen-backed convenience numerics into itk::bridge and renamed the headers to itkBridge*.h. Probe the tiered header first so a post-move ITK stays on the Eigen path, and keep the pre-move arm so older ITK checkouts still resolve; the vnl fallback is unchanged. The itk::bridge tier is explicitly a migration aid rather than supported ITK API. Every call site here is guarded by ITK_BRIDGE_MATH_HAS_SOLVE_SYMMETRIC and falls back to vnl, so BRAINSTools does not hard-depend on that tier.
b9528ae to
8bd1332
Compare
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
|
Correction to this PR's test plan, posted after merge. The "895/895 targets against ITK main @313b5234d52" result in the body is not It went unnoticed because both ITK vintages compile this code cleanly: against What still stands:
A full BRAINSTools SuperBuild pinned to an ITK 6 |
Keeps BRAINSTools on ITK's Eigen-backed numerics now that
InsightSoftwareConsortium/ITK#6768 has merged, moving the convenience numerics to
itk::bridge::Math(InsightSoftwareConsortium/ITK#6620). Chained__has_includeguards keep both the post-move and pre-move ITK on the Eigen path, so this builds
against either vintage.
Affects GTRACT
algo.cxx,LLSBiasCorrector.hxx,EMSegmentationFilter.hxx,and
fcsv_to_hdf5.cxx.Why this is needed even though nothing breaks
The pre-existing guards were bare
__has_includeprobes of the pre-move header:After the ITK rename that probe simply fails. There is no compile error — the
capability macro stays undefined and the code silently falls back to
vnl_matrix_inverse, with nothing in any log to say so.Chaining the probe keeps either ITK vintage on the Eigen path:
Because both arms are retained, this is safe against pre-move and post-move ITK alike.
Test plan — built and measured against merged ITK
Validated against ITK
mainat313b5234d52(i.e. after #6768 merged), installedfrom a dedicated build.
1. Guard-arm probe. A syntax-only compile of the guard against the installed ITK,
with one
#errorper arm, reports which branch the preprocessor actually takes:all four files'
itkBridgeMathLDLT.hchain resolves toARM=BRIDGE(eigen), notthe
vnlfallback.This matters because a successful build is consistent with all three arms — the
vnlfallback compiles fine, it just quietly abandons Eigen. The probe is whatdistinguishes them.
2. Inner build.
ninjaover the inner project: 895/895 targets, 0 errors,0
FAILEDlines. All four changed files are in that build:GTRACT/Common/algo.cxxGTRACTCommon.dir/algo.cxx.oBRAINSConstellationDetector/src/fcsv_to_hdf5.cxxfcsv_to_hdf5Lib.dir/fcsv_to_hdf5.cxx.oBRAINSABC/brainseg/EMSegmentationFilter.hxxBRAINSABCCOMMONLIB.dir/EMSegmentationFilter_float+float.cxx.oBRAINSABC/brainseg/LLSBiasCorrector.hxxBRAINSABCCOMMONLIB.dir/BRAINSABCUtilities.cxx.oConfigured with
USE_ANTS=OFF,BRAINSTools_USE_QT=OFF,USE_BRAINSConstellationDetectorGUI=OFF,USE_DebugImageViewer=OFF,USE_BRAINSSuperResolution=OFF— the reused EP tree carries an ITK-5.4-based ANTsand a Qt-less VTK, and none of those tools contain the files this PR touches.
Not run: the BRAINSTools test suite. This change alters which numerical backend is
selected, not the algorithms.