Skip to content

test(3d glcm): assert the _AVE features, and restore 26 assertions that never run - #433

Open
darkclad wants to merge 2 commits into
PolusAI:mainfrom
darkclad:main-vet-3d-glcm
Open

test(3d glcm): assert the _AVE features, and restore 26 assertions that never run#433
darkclad wants to merge 2 commits into
PolusAI:mainfrom
darkclad:main-vet-3d-glcm

Conversation

@darkclad

Copy link
Copy Markdown

Closes the 29 3GLCM_*_AVE rows that read status=vetted with no in-tree oracle assertion, and wires in 26 assertions that existed in the tree but were unreachable.

The _AVE gap was an attribution problem

assert_3d_glcm_feature_pyradiomics() ends in calc_ave(r.fvals[fcode]) - the average over the 13 3D angles - compared against the PyRadiomics golden. That is the right quantity: PyRadiomics reports one value per feature over its whole direction set, which is the Nyxus *_AVE aggregation. But the assertion was booked against the per-angle base feature, while save_value separately writes the same number to the feature the gap rows actually name (3d_glcm.cpp:148). Nothing asserted the stored *_AVE features.

23 of them now assert fvals[..._AVE][0] directly rather than recomputing calc_ave, so a defect in how save_value populates *_AVE fails there and nowhere else. All pass.

The remaining 6 have no PyRadiomics golden of their own - it deprecates DIS as equivalent to DifferenceAverage and does not report ENERGY, ENTROPY, HOM1, SUMVARIANCE or VARIANCE under their own names - so they are vetted through the identities DIS=DIFAVE, ENERGY=ASM, ENTROPY=JE, HOM1=ID, SUMVARIANCE=CLUTEND, VARIANCE=JVAR. Those were already asserted at 1e-6 on the per-angle values; test_3d_glcm_ave_equivalence_pyradiomics now asserts them on the stored *_AVE features and re-checks that the twin still matches its golden, so neither end of the chain can rot silently.

Five rows carried an incoherent oracle

3GLCM_{DIFAVE,DIS,IDN,IDMN,INFOMEAS2}_AVE read oracle=mirp while their base rows read oracle=pyradiomics for the same measured quantity, with no notes on either. No mirp run has ever existed for 3D GLCM in this tree - no generator, no goldens, no test file. They now name the tool whose golden the assertion actually compares against.

26 assertions that existed but never executed

test_3d_glcm_regression.h (25 tests) was not #included and had zero TEST() registrations. The cause is mechanical: it carried its own definition of get_3d_segmented_phantom(), which redefines the one in test_3d_glcm_pyradiomics.h inside the single test_all.cc translation unit. The live 3D headers forward-declare it; this one now does too.

Wired in, all 25 failed - and the pins were not merely stale but impossible. ID, IDM, IDN, IDMN and JMAX are bounded in [0,1] by construction and were pinned at 2.5, 2.4, 3.8, 3.9 and 1.86. The old table also broke the SUMVARIANCE == CLUTEND identity the family asserts elsewhere (18057.4 against 18057.0). The regenerated values hold that identity exactly, give DIS == DIFAVE to 3.6e-15, and are all in range. They come from the current implementation, which the sibling PyRadiomics file - which does run - vets independently on the compat phantom. Tolerance tightened 10% -> rel=1e-9, and test_3d_glcm_dump_regression() regenerates the table so the next refresh is a gtest filter rather than a hand transcription.

test_3d_glcm_jvar_pyradiomics was a complete assertion with no TEST() calling it (not_covered.md B.2). Registered; it passes.

The goldens had never been reproduced, and one was wrong -------------------------------------------------------- The family had no generator at all - gen_glcm_{mirp,pyradiomics}.py are 2D-only - so SPEC 6.4's "generator script path" was unmet and the numbers rested on a hand-written CLI recipe in a comment. That comment also contradicted itself, naming the ut_ phantom while every assertion uses the compat one.

gen_glcm3d_pyradiomics.py now runs PyRadiomics 3.0.1 against the compat phantom and re-verifies every pinned golden. 19 of the 23 reproduce bit-for-bit, which confirms the table's origin. Five did not:

3GLCM_IDN pinned 0.9822362042997563 -> 0.9067759330416398 (7.7%)
3GLCM_IDM pinned 0.4040020605537021 -> 0.3726945904589868 (7.8%)
3GLCM_ID pinned 0.47211428859469606 -> 0.4459415317170447 (5.5%)
3GLCM_IDMN pinned 0.9822362042997563 -> 0.9797065356412845 (0.26%)
3GLCM_CORRELATION 0.4305477709920443 -> 0.43309121847659515 (0.59%)

3GLCM_IDN and 3GLCM_IDMN were pinned to byte-identical values - the IDMN number pasted into the IDN slot. PyRadiomics reports different values for the two. It survived because the 10% band absorbed a 7.7% error, which is the "tolerance loose enough to pass a known-bad value" SPEC 7 warns about. The other four are consistent with the inverse-difference family's known version sensitivity.

All five are re-pinned to the fresh run. Nyxus still agrees with the corrected goldens inside the same 10% band - that band covers a real convention gap, Nyxus' asymmetric offset-1 cooc matrix against PyRadiomics' symmetric default.

Registry

All 59 rows get a real config_recipe id (two new: glcm3d.pyradiomics_bincount20 and glcm3d.regression_ut_phantom, which differ in bin count and are therefore not comparable to each other), target_test cleared, source moved off tracker, and current_test synced to the files that actually cover each feature - it previously named test_3d_glcm_regression.h for all 59 when that file asserts 25 and did not run at all.

Noted for PRs 8-10: test_3d_{gldm,glrlm,glszm,ngtdm}_regression.h and test_3d_firstorder_matlab.h are unreachable the same way, but unlike the GLCM file their assert bodies are wrapped in #if 0, so wiring them in yields tests that assert nothing until the body is restored. test_3d_ngldm_regression.h is a third variant - included and running, but its body is #if 0 too, so its 19 tests have been passing without checking anything.

Verified: runAllTests 824/824 on Windows and under ASan+UBSan on Linux; pytest tests/python/ full directory; check_coverage.py --check, check_test_names.py --check and audit/scan_glcm3d_coverage.py --check all clean.

Demian Vladi added 2 commits August 14, 2026 11:34
…at never ran

Closes the 29 3GLCM_*_AVE rows that read status=vetted with no in-tree oracle assertion, and wires
in 26 assertions that existed in the tree but were unreachable.

The _AVE gap was an attribution problem
---------------------------------------
assert_3d_glcm_feature_pyradiomics() ends in calc_ave(r.fvals[fcode]) - the average over the 13 3D
angles - compared against the PyRadiomics golden. That is the right quantity: PyRadiomics reports
one value per feature over its whole direction set, which is the Nyxus *_AVE aggregation. But the
assertion was booked against the per-angle base feature, while save_value separately writes the same
number to the feature the gap rows actually name (3d_glcm.cpp:148). Nothing asserted the stored
*_AVE features.

23 of them now assert fvals[..._AVE][0] directly rather than recomputing calc_ave, so a defect in
how save_value populates *_AVE fails there and nowhere else. All pass.

The remaining 6 have no PyRadiomics golden of their own - it deprecates DIS as equivalent to
DifferenceAverage and does not report ENERGY, ENTROPY, HOM1, SUMVARIANCE or VARIANCE under their own
names - so they are vetted through the identities DIS=DIFAVE, ENERGY=ASM, ENTROPY=JE, HOM1=ID,
SUMVARIANCE=CLUTEND, VARIANCE=JVAR. Those were already asserted at 1e-6 on the per-angle values;
test_3d_glcm_ave_equivalence_pyradiomics now asserts them on the stored *_AVE features and re-checks
that the twin still matches its golden, so neither end of the chain can rot silently.

Five rows carried an incoherent oracle
--------------------------------------
3GLCM_{DIFAVE,DIS,IDN,IDMN,INFOMEAS2}_AVE read oracle=mirp while their base rows read
oracle=pyradiomics for the same measured quantity, with no notes on either. No mirp run has ever
existed for 3D GLCM in this tree - no generator, no goldens, no test file. They now name the tool
whose golden the assertion actually compares against.

26 assertions that existed but never executed
---------------------------------------------
test_3d_glcm_regression.h (25 tests) was not #included and had zero TEST() registrations. The cause
is mechanical: it carried its own definition of get_3d_segmented_phantom(), which redefines the one
in test_3d_glcm_pyradiomics.h inside the single test_all.cc translation unit. The live 3D headers
forward-declare it; this one now does too.

Wired in, all 25 failed - and the pins were not merely stale but impossible. ID, IDM, IDN, IDMN and
JMAX are bounded in [0,1] by construction and were pinned at 2.5, 2.4, 3.8, 3.9 and 1.86. The old
table also broke the SUMVARIANCE == CLUTEND identity the family asserts elsewhere (18057.4 against
18057.0). The regenerated values hold that identity exactly, give DIS == DIFAVE to 3.6e-15, and are
all in range. They come from the current implementation, which the sibling PyRadiomics file - which
does run - vets independently on the compat phantom. Tolerance tightened 10% -> rel=1e-9, and
test_3d_glcm_dump_regression() regenerates the table so the next refresh is a gtest filter rather
than a hand transcription.

test_3d_glcm_jvar_pyradiomics was a complete assertion with no TEST() calling it
(not_covered.md B.2). Registered; it passes.

The goldens had never been reproduced, and one was wrong
--------------------------------------------------------
The family had no generator at all - gen_glcm_{mirp,pyradiomics}.py are 2D-only - so SPEC 6.4's
"generator script path" was unmet and the numbers rested on a hand-written CLI recipe in a comment.
That comment also contradicted itself, naming the ut_ phantom while every assertion uses the compat
one.

gen_glcm3d_pyradiomics.py now runs PyRadiomics 3.0.1 against the compat phantom and re-verifies every
pinned golden. 19 of the 23 reproduce bit-for-bit, which confirms the table's origin. Five did not:

  3GLCM_IDN    pinned 0.9822362042997563  ->  0.9067759330416398   (7.7%)
  3GLCM_IDM    pinned 0.4040020605537021  ->  0.3726945904589868   (7.8%)
  3GLCM_ID     pinned 0.47211428859469606 ->  0.4459415317170447   (5.5%)
  3GLCM_IDMN   pinned 0.9822362042997563  ->  0.9797065356412845   (0.26%)
  3GLCM_CORRELATION 0.4305477709920443    ->  0.43309121847659515  (0.59%)

3GLCM_IDN and 3GLCM_IDMN were pinned to byte-identical values - the IDMN number pasted into the IDN
slot. PyRadiomics reports different values for the two. It survived because the 10% band absorbed a
7.7% error, which is the "tolerance loose enough to pass a known-bad value" SPEC 7 warns about. The
other four are consistent with the inverse-difference family's known version sensitivity.

All five are re-pinned to the fresh run. Nyxus still agrees with the corrected goldens inside the
same 10% band - that band covers a real convention gap, Nyxus' asymmetric offset-1 cooc matrix
against PyRadiomics' symmetric default.

Registry
--------
All 59 rows get a real config_recipe id (two new: glcm3d.pyradiomics_bincount20 and
glcm3d.regression_ut_phantom, which differ in bin count and are therefore not comparable to each
other), target_test cleared, source moved off tracker, and current_test synced to the files that
actually cover each feature - it previously named test_3d_glcm_regression.h for all 59 when that
file asserts 25 and did not run at all.

Noted for PRs 8-10: test_3d_{gldm,glrlm,glszm,ngtdm}_regression.h and test_3d_firstorder_matlab.h
are unreachable the same way, but unlike the GLCM file their assert bodies are wrapped in #if 0, so
wiring them in yields tests that assert nothing until the body is restored. test_3d_ngldm_regression.h
is a third variant - included and running, but its body is #if 0 too, so its 19 tests have been
passing without checking anything.

Verified: runAllTests 824/824 on Windows and under ASan+UBSan on Linux; pytest tests/python/ full
directory; check_coverage.py --check, check_test_names.py --check and
audit/scan_glcm3d_coverage.py --check all clean.
… an unused include, correct the 3D wiring note
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant