Skip to content

BUG: Skip voting-undecided pixels in MultiLabelSTAPLE seeding#6579

Merged
hjmjohnson merged 1 commit into
InsightSoftwareConsortium:mainfrom
physwkim:bug-multilabel-staple-undecided-seed
Jul 10, 2026
Merged

BUG: Skip voting-undecided pixels in MultiLabelSTAPLE seeding#6579
hjmjohnson merged 1 commit into
InsightSoftwareConsortium:mainfrom
physwkim:bug-multilabel-staple-undecided-seed

Conversation

@physwkim

Copy link
Copy Markdown
Contributor

Addresses item B10 of #6575.

MultiLabelSTAPLEImageFilter seeds each rater's confusion matrix from a LabelVotingImageFilter pass over the same inputs. On voting ties that filter writes its undecided label, which equals the total label count — one past the confusion matrix's last valid column. Array2D::operator[] is an unchecked row pointer into a flat row-major buffer, so ++CM[k][in][out] with out == m_TotalLabelCount lands on CM[k][in + 1][0]: inside the allocation (the matrix has a spare row), but silently corrupting the NEXT observed label's row with phantom "rater observed in+1, estimated truth 0" counts. Every tie pixel in the seeding vote injects one such phantom count, skewing the row-normalized initial confusion matrices the EM iteration starts from — and the filter's public GetConfusionMatrix() output.

The EM loop and both normalizations only ever index rows and columns by real labels, so an undecided voting pixel has no legitimate slot in the matrix: it carries no (observed label, estimated true label) evidence. The fix skips such pixels in the seeding loop instead of letting the increment run past the row.

The new regression test engineers deterministic ties (two raters, four pixels, two of them split 1–1) and sets MaximumNumberOfIterations = 0, which leaves the matrices exactly as seeded. Unfixed, both raters' matrices come out with row 1 = [0.5, 0.5] and row 2 = [1, 0] (the leaked counts) and the output labels are wrong at three of four pixels ([0, 2, 0, 0]); fixed, the matrices are the exact row-normalized vote statistics (row 0 = [1, 0], row 1 = [0, 1], row 2 = [0, 0]) and the output is [0, 1, 2, 2] — agreements labeled, ties undecided. The full pass-by-pass trace is in the test's header comment.

Verified locally (Linux, GCC 13, Release): the new test fails on unmodified main (confusion matrix (1,0) = 0.5 expected 0, (2,0) = 1 expected 0; output [0, 2, 0, 0] expected [0, 1, 2, 2]) and passes with the fix; the full ITKLabelVoting suite plus the ITKImageStatistics, ITKReview, ITKDisplacementField, and ITKRegionGrowing suites pass with the fix applied.

PR Checklist

  • No API changes were made (or the changes have been approved)
  • No major design changes were made (or the changes have been approved)
  • Added test (or behavior not changed)
  • Updated API documentation (or API not changed)
  • Added license to new files (if any)
  • Added Python wrapping to new files (if any) as described in ITK Software Guide Section 9.5
  • Added ITK examples for all new major features (if any)
AI assistance
  • Tool: Claude Code (claude-fable-5, with claude-opus-4-8 subagents)
  • Role: implemented the fix and regression test from the analysis previously filed as item B10 of BUG: Findings collected while porting ~60 filters — reported together in one issue #6575; hand-traced the seeded matrices and final labeling (fixed and unfixed) before implementation
  • Testing: built locally and verified the regression test fails before / passes after the fix; five module test suites pass with the fix
  • Note: clang-format 19.1.7 was not available locally; formatting was hand-matched to ITK style and may need the CI formatter's touch-up

@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 10, 2026
@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes how MultiLabelSTAPLEImageFilter seeds confusion matrices when the initial voting pass produces tie labels. The main changes are:

  • Skip voting-undecided pixels during confusion-matrix seeding.
  • Keep seeded matrix counts limited to real output labels.
  • Add a GoogleTest regression for tied votes, zero EM iterations, seeded matrices, and undecided output labels.
  • Register the new LabelVoting GoogleTest driver in CMake.

Confidence Score: 5/5

Safe to merge with minimal risk.

The change is narrow, matches the existing matrix dimensions, and the test directly covers the corrected seeding behavior.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Checked the multilabelstaple tool availability and found that cmake, ctest, ninja, and pixi were not found, indicating the build environment lacks core configuration tools.
  • Attempted the narrow CMake configure command specified for the PR, and the configure step exited with code 127.
  • Inspected the LabelVoting CMake test registration and the intended GTest assertions that would run in a properly provisioned build.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
Modules/Segmentation/LabelVoting/include/itkMultiLabelSTAPLEImageFilter.hxx Skips voting-undecided labels during confusion-matrix seeding, preventing one-past-column writes into the next row.
Modules/Segmentation/LabelVoting/test/itkMultiLabelSTAPLEImageFilterGTest.cxx Adds a focused regression test that verifies tie pixels do not corrupt seeded confusion matrices and remain undecided in the output.
Modules/Segmentation/LabelVoting/test/CMakeLists.txt Adds a GoogleTest driver for the new LabelVoting regression test following existing ITK module patterns.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Inputs as Rater input images
participant STAPLE as MultiLabelSTAPLEImageFilter
participant Voting as LabelVotingImageFilter
participant CM as Confusion matrices
participant Output as Output image

STAPLE->>Voting: Run initial label voting over inputs
Voting-->>STAPLE: Voted labels, including undecided ties
loop Each rater and pixel
    STAPLE->>STAPLE: "Check voted label < total label count"
    alt Real voted label
        STAPLE->>CM: Increment observed label / voted truth count
    else Undecided tie label
        STAPLE->>STAPLE: Skip seeding update
    end
end
STAPLE->>CM: Normalize seeded rows
STAPLE->>Output: Generate final labels from seeded/EM matrices
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Inputs as Rater input images
participant STAPLE as MultiLabelSTAPLEImageFilter
participant Voting as LabelVotingImageFilter
participant CM as Confusion matrices
participant Output as Output image

STAPLE->>Voting: Run initial label voting over inputs
Voting-->>STAPLE: Voted labels, including undecided ties
loop Each rater and pixel
    STAPLE->>STAPLE: "Check voted label < total label count"
    alt Real voted label
        STAPLE->>CM: Increment observed label / voted truth count
    else Undecided tie label
        STAPLE->>STAPLE: Skip seeding update
    end
end
STAPLE->>CM: Normalize seeded rows
STAPLE->>Output: Generate final labels from seeded/EM matrices
Loading

Reviews (2): Last reviewed commit: "BUG: Skip voting-undecided pixels in Mul..." | Re-trigger Greptile

@hjmjohnson
hjmjohnson marked this pull request as draft July 10, 2026 11:49
InitializeConfusionMatrixArrayFromVoting() seeds each per-rater
confusion matrix from a LabelVotingImageFilter run over the same
inputs. Each matrix is allocated (m_TotalLabelCount + 1) rows by
m_TotalLabelCount columns. On a voting tie the seed filter writes its
undecided label, which equals m_TotalLabelCount -- one past the last
valid column. Array2D::operator[] is an unchecked row pointer into a
flat row-major buffer, so the increment for such a pixel lands on the
next row's column 0 ([in + 1][0]) instead of a real slot, silently
corrupting the next observed label's row with phantom counts.

The EM loop and both normalizations only ever read rows and columns
indexed by real labels [0, m_TotalLabelCount), so a voting-undecided
pixel carries no (observed label, estimated true label) evidence.
Guard the increment so those pixels are skipped instead of writing
past the matrix row.

Adds a deterministic regression test with two raters whose votes tie
on half the pixels, asserting the seeded confusion matrices and the
combined output.

Addresses item B10 of InsightSoftwareConsortium#6575.
@hjmjohnson
hjmjohnson force-pushed the bug-multilabel-staple-undecided-seed branch from aeadf48 to 6df2f5f Compare July 10, 2026 12:08
@hjmjohnson
hjmjohnson requested a review from ntustison July 10, 2026 13:06
@hjmjohnson
hjmjohnson marked this pull request as ready for review July 10, 2026 13:11
@hjmjohnson
hjmjohnson merged commit 7bf61bb into InsightSoftwareConsortium:main Jul 10, 2026
22 checks passed
physwkim added a commit to physwkim/sitk-rs that referenced this pull request Jul 12, 2026
++CM[k][in.Get()][out.Get()] with out.Get() == max_label + 1 indexes one
past the row and, in the flat row-major Array2D, lands on
[in.Get() + 1][0] -- every seeding-vote tie fabricates a count for the
next input label deciding label 0. A tie carries no evidence about any
rater's confusion between two real labels, so it now contributes no
count. Matches upstream fix PR InsightSoftwareConsortium/ITK#6579.
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