Skip to content

BUG: Seed outward march across grid-aligned zero contours - #6601

Merged
hjmjohnson merged 2 commits into
InsightSoftwareConsortium:mainfrom
physwkim:bug-levelset-neighborhood-extractor-zero-6575
Jul 14, 2026
Merged

hjmjohnson merged 2 commits into
InsightSoftwareConsortium:mainfrom
physwkim:bug-levelset-neighborhood-extractor-zero-6575

Conversation

@physwkim

Copy link
Copy Markdown
Contributor

LevelSetNeighborhoodExtractor::CalculateDistance() classifies an exact-zero pixel as inside but required a strict sign change to detect a crossing, so a grid-aligned zero contour seeded no outward trial points. Addresses B21 of #6575.

Root cause

CalculateDistance() (itkLevelSetNeighborhoodExtractor.hxx:198-207) owns zero on the inside:

if (centerValue == 0.0) { ... m_InsidePoints->InsertElement(...); return 0.0; }
const bool inside = (centerValue <= 0.0);

but the neighbor test at :232 was strict on both sides:

if ((neighValue > 0 && inside) || (neighValue < 0 && !inside))

A neighbor valued exactly zero satisfies neither branch. An outside pixel whose only sub-zero neighbor sits on a grid-aligned zero layer therefore found no crossing in any dimension, hit the distance == 0.0 early return, and was never inserted into OutsidePoints — the outward FastMarching pass starts from nothing. Making the outside branch non-strict (neighValue <= 0 && !inside) restores the class's own convention: zero belongs to the inside, so it is a crossing when seen from the outside.

LevelSetVelocityNeighborhoodExtractor inherits CalculateDistance() unchanged and is fixed with it. SparseFieldLevelSetImageFilter / ParallelSparseFieldLevelSetImageFilter use a structurally different surface-interpolation test (forwardValue * centerValue < 0) — a different algorithm, not this defect.

Local validation

New GoogleTest itkLevelSetNeighborhoodExtractorGTest.cxx, registered in ITKLevelSetsGTests. 5x3 image, columns x=0,1 → -1, x=2 → 0, x=3,4 → +1.

  • SeedsOutsidePointsAcrossGridAlignedZeroContour — fail-before: OutsidePoints empty (size 0, expected 3). Pass-after: 3 points at x=3 with value 1.0, and InsidePoints = 3 points at x=2 with value 0.0.
  • ctest -L ITKLevelSets: every test that reaches the changed code (itkLevelSetNeighborhoodExtractorTest, itkLevelSetVelocityNeighborhoodExtractorTest, itkReinitializeLevelSetImageFilterTest, itkExtensionVelocitiesImageFilterTest, itkBinaryMaskToNarrowBandPointSetFilterTest) passes, with identical results before and after.
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:Segmentation Issues affecting the Segmentation module labels Jul 13, 2026
@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes outside seed detection across grid-aligned zero contours. The main changes are:

  • Treat exact-zero neighbors as contour crossings for outside pixels.
  • Register a new LevelSets GoogleTest source.
  • Add regression coverage for a zero-valued contour column.

Confidence Score: 5/5

This looks safe to merge.

No blocking issues found in the changed code. The updated crossing condition matches the existing zero-is-inside convention. The new test uses the existing LevelSets GoogleTest pattern.

No files need attention.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex ran the requested verification, but its local artifact references were not uploaded.

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
Modules/Segmentation/LevelSets/include/itkLevelSetNeighborhoodExtractor.hxx Updates the outside-neighbor crossing condition so exact-zero neighbors seed outside points.
Modules/Segmentation/LevelSets/test/CMakeLists.txt Adds the new regression test source to the LevelSets GoogleTest driver.
Modules/Segmentation/LevelSets/test/itkLevelSetNeighborhoodExtractorGTest.cxx Adds a focused regression test for outside and inside point classification around a zero contour.

Reviews (1): Last reviewed commit: "BUG: Seed outward march across grid-alig..." | Re-trigger Greptile

@hjmjohnson

Copy link
Copy Markdown
Member

A high-effort automated review found no confirmed defect in this branch (force-push = plain rebase on main). Three design-level notes for consideration:

Review notes
  1. The widened >=/<= sign test also seeds the outward front around tangential exact-zero pixels embedded in a same-sign region (e.g. +1, 0, +1 from quantized inputs) which the old strict test ignored — reinitialized output gains small-distance "craters" at such pixels. If a crossing-only semantic matters, the zero-neighbor case may need a sign check on the far side.
  2. Residual narrow-band asymmetries: a band containing only the exact-zero contour pixels still yields an empty outside front (the centerValue==0 branch seeds inside only), and zero pixels excluded from the band are never seeded at distance 0 — so part of the BUG: Findings collected while porting ~60 filters — reported together in one issue #6575 symptom survives in narrow-band mode. Possibly fine as follow-up scope.
  3. Test nit: the first GTest already asserts inside points despite its SeedsOutsidePoints... name; the second test duplicates its setup entirely and could fold into it.

physwkim and others added 2 commits July 13, 2026 20:52
CalculateDistance already treats an exact-zero pixel as inside
(centerValue <= 0.0). The neighbor crossing test enforced strict
inequality on both sides, so a neighbor valued at exactly zero
never counted as a crossing for an outside center pixel. A pixel
adjacent only to a zero-valued layer therefore queued no outward
FastMarching trial point.

Addresses item B21 of InsightSoftwareConsortium#6575.
Mirror the outward-march fix: an exactly-on-isovalue neighbor is a zero
crossing for an inside center too, so strictly-negative pixels bordering
a grid-aligned zero contour now seed the inward fast march. Without
this, a narrow band that omits the exact-zero pixels leaves the inward
march with no trial points, the counterpart of the outward-march bug.

Adds a narrow-band regression test that excludes the exact-zero pixels
from the band; it fails without either fix.

Assisted-by: Claude Code — identified the asymmetric inside-march branch during review
@hjmjohnson
hjmjohnson force-pushed the bug-levelset-neighborhood-extractor-zero-6575 branch from 5878eb8 to 370c55d Compare July 14, 2026 01:57
@hjmjohnson

Copy link
Copy Markdown
Member

Force-push = plain rebase onto current main, folding the new GTest source into the GTests list that #6589 introduced for this module (two creategoogletestdriver calls in the CI merge build caused/would cause a duplicate-target configure error). Verified locally: configure clean, module tests pass.

@hjmjohnson
hjmjohnson merged commit 57a7872 into InsightSoftwareConsortium:main Jul 14, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Segmentation Issues affecting the Segmentation 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.

2 participants