Skip to content

BUG: Guard NormalizedCorrelationImageFilter zero-variance NaN - #6612

Merged
hjmjohnson merged 1 commit into
InsightSoftwareConsortium:mainfrom
physwkim:bug-normalized-correlation-nan-6575
Jul 13, 2026
Merged

hjmjohnson merged 1 commit into
InsightSoftwareConsortium:mainfrom
physwkim:bug-normalized-correlation-nan-6575

Conversation

@physwkim

Copy link
Copy Markdown
Contributor

NormalizedCorrelationImageFilter divides by a norm that is exactly zero for a constant template or a locally-constant neighborhood, producing NaN or Inf. Addresses B46 of #6575.

Root cause

Three unguarded divisions in itkNormalizedCorrelationImageFilter.hxx: / k at :130 (the template norm) and numerator / denominator at :192 and :228 (the neighborhood norm, duplicated verbatim in the mask and no-mask branches). Each denominator is exactly zero when its signal has zero variance — a constant template, or a neighborhood that falls entirely inside a constant region of the image.

All three now yield the filter's existing zero sentinel. That is not a new convention: origin/main already returns zero for pixels outside the mask ("Not under the mask. Set the normalized correlation to zero."). This routes two more undefined cases through the same sentinel instead of through NaN/Inf. The ambiguity that 0 is also a legitimate correlation value for two orthogonal non-constant signals is pre-existing and unchanged.

Local validation

New GoogleTest itkNormalizedCorrelationImageFilterGTest.cxx (the module had no GTest driver).

  • ConstantTemplateProducesFiniteZeroOutput — fail-before: -nan everywhere. Pass-after: all 0.
  • ConstantNeighborhoodProducesFiniteZeroOutput — fail-before: a mix of -nan and inf (the numerator is not bit-exactly zero after normalization rounding, so 0/0 sometimes resolved to ±inf). Pass-after: 0.
  • ConstantNeighborhoodUnderMaskProducesFiniteZeroOutput — covers the duplicated mask branch. Fail-before: inf. Pass-after: 0.

ctest -L ITKConvolution with ExternalData fetched, at main and at the branch: 44/45 pass at both, nothing flipped (the one Not Run is ITKConvolutionKWStyleTest, whose tool is not built locally). itkNormalizedCorrelationImageFilterTest, the only baseline consumer of this filter in the tree, reports ImageError = 0 at both states — its sf4.png/circle.png fixture never drives the filter into a zero-variance case, which is consistent with the fact that no existing baseline contains non-finite pixels.

AI assistance

@github-actions github-actions Bot added type:Bug Inconsistencies or issues which will cause an incorrect result under some or all circumstances 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 area:Filtering Issues affecting the Filtering module labels Jul 13, 2026
@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR guards zero-variance normalized correlation cases and adds tests for them. The main changes are:

  • Zero output for exact zero template norms.
  • Zero output for exact zero neighborhood norms in masked and unmasked paths.
  • New GoogleTest coverage for constant template and constant image cases.
  • GoogleTest dependency and driver registration for the Convolution module.

Confidence Score: 4/5

The zero-variance guard can still emit non-finite output when variance cancellation produces NaN.

Exact-zero denominator checks cover the new tests, but the same changed paths miss NaN norms from cancellation-prone variance math. The CMake and GoogleTest wiring matches existing ITK patterns.

Modules/Filtering/Convolution/include/itkNormalizedCorrelationImageFilter.hxx

T-Rex T-Rex Logs

What T-Rex did

  • Reproduced the NaN guard bypass in a focused C++ harness that executes the normalization arithmetic for a large constant template.
  • Reproduced the NaN neighborhood norm bypass using a second C++ harness that mirrors the neighborhood loop and denominator logic for eight-pixel neighborhoods.
  • Logged the environment and build attempt results, showing tool availability gaps and a targeted configure failure due to missing cmake.
  • Reviewed test-related changes and registrations, including GTest setup and three named tests, with logs capturing test dependencies and broader source excerpts.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
Modules/Filtering/Convolution/include/itkNormalizedCorrelationImageFilter.hxx Adds exact-zero guards for template and neighborhood norms, but NaN norms can still bypass the guard after floating-point cancellation.
Modules/Filtering/Convolution/itk-module.cmake Adds the GoogleTest test dependency needed by the new test driver.
Modules/Filtering/Convolution/test/CMakeLists.txt Registers a new GoogleTest driver for the Convolution module.
Modules/Filtering/Convolution/test/itkNormalizedCorrelationImageFilterGTest.cxx Adds regression tests for exact zero-variance template and neighborhood cases.

Reviews (1): Last reviewed commit: "BUG: Guard NormalizedCorrelationImageFil..." | Re-trigger Greptile

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

Mostly looks good.

@hjmjohnson
hjmjohnson force-pushed the bug-normalized-correlation-nan-6575 branch from d4930a0 to db1ca36 Compare July 13, 2026 15:05
A constant template or a locally-constant neighborhood makes the
template-norm or neighborhood-norm denominator zero, so the
correlation division produced NaN (or Inf on rounding). With double
pixels the variance can also cancel to slightly negative, making the
norm itself NaN and bypassing an exact-zero test. Both sites now
test !(norm > 0) and return zero, the conventional value for
undefined correlation.

Addresses item B46 of InsightSoftwareConsortium#6575.
@hjmjohnson
hjmjohnson force-pushed the bug-normalized-correlation-nan-6575 branch from db1ca36 to 86df3ba Compare July 13, 2026 19:03
@hjmjohnson

Copy link
Copy Markdown
Member

Restructured the zero-variance template guard for performance (86df3ba): instead of a per-element ternary in the normalization loop, the degenerate case now early-outs of ThreadedGenerateData — fills the thread's output region with zeros and skips the convolution entirely. The per-pixel denominator > 0 guards are unchanged (that condition is per-pixel, protecting flat input windows, and cannot be hoisted). All 32 Convolution tests including the 6 new GTests pass locally; rebased onto current main.

@hjmjohnson
hjmjohnson merged commit 8996872 into InsightSoftwareConsortium:main Jul 13, 2026
21 checks passed
@hjmjohnson

Copy link
Copy Markdown
Member

A high-effort automated review of this branch surfaced a few items worth addressing before merge. I could not push fixes directly because this PR has maintainer edits disabled ("Allow edits by maintainers" is unchecked — enabling it would let me apply these for you).

Findings (ranked)
  1. Silent degenerate output (itkNormalizedCorrelationImageFilter.hxx zero-variance guard): a constant or radius-0 template now yields a clean all-zero output with no diagnostic, where the previous NaN flood made the misconfiguration visible. Suggest an itkWarningMacro in the early-return so the degenerate template is still surfaced.
  2. Comment budget: the new 3-line comment above the guard narrates the code; the repo caps in-source comments at one short line (review-blocking per AGENTS.md pitfall 7). One line such as // Cancellation can make k NaN for a constant template, hence !(k > 0). suffices.
  3. Duplicated setup: the guard's zeroOutput/zeroProgress duplicate the output/progress created later in the function — hoisting them above the guard removes the copy.
  4. Test fragility: the three Cancellation* tests assert on the sign of a few-ulp cancellation residue, which is FMA/platform dependent (verified: -ffp-contract changes the residue). Consider asserting !isnan + |value| <= 1 instead of exact zero.
  5. Minor: six float/double test twins could be a typed-test suite; the per-pixel EXPECT_FALSE(isnan(...)) before EXPECT_EQ(..., 0) is redundant (NaN already fails the EQ).

@physwkim

Copy link
Copy Markdown
Contributor Author

A high-effort automated review of this branch surfaced a few items worth addressing before merge. I could not push fixes directly because this PR has maintainer edits disabled ("Allow edits by maintainers" is unchecked — enabling it would let me apply these for you).

I’ve enabled it. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Filtering Issues affecting the Filtering module type:Bug Inconsistencies or issues which will cause an incorrect result under some or all circumstances 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.

4 participants