fix(morphology): geodetic length/thickness from real perimeter (was size_t-truncated) - #392
Open
darkclad wants to merge 2 commits into
Open
fix(morphology): geodetic length/thickness from real perimeter (was size_t-truncated)#392darkclad wants to merge 2 commits into
darkclad wants to merge 2 commits into
Conversation
added 2 commits
July 23, 2026 12:59
…NS_2_VANISH -> 80/113
Vet three more 2D morphology features against analytic / scikit-image oracles
(morphology 77 -> 80), and document why four others in the "future-vettable" list
are not cleanly promotable.
Promoted:
- DIAMETER_EQUAL_PERIMETER -> analytic. == PERIMETER/pi (contour.cpp), pure double
arithmetic on the vetted PERIMETER; recomputed at 1e-9 in the existing
documented-formula-conformance test (same treatment as CIRCULARITY/ROUNDNESS).
- ORIENTATION -> skimage. skimage regionprops measures the major-axis angle from the
row axis; Nyxus measures the same ellipse from the x axis, so NYXUS ==
90 - degrees(skimage.orientation) = 70.4174. Matches to 10 decimals because the
ellipse angle is invariant to the pixel-size second-moment correction (it shifts
mu20 and mu02 equally). New gtest test_shape2d_skimage_orientation_and_erosions +
tests/vetting/oracles/gen_morphology_skimage.py.
- EROSIONS_2_VANISH -> skimage. Nyxus' 3x3 (8-connected) erosion count ==
skimage binary_erosion(square(3)) = 1; disk(1)/4-connected gives 2, so the test
also pins the connectivity convention.
Documented (stay regression, with reasons in the registry notes):
- GEODETIC_LENGTH: geo_len_thickness.cpp truncates PERIMETER to size_t and uses
integer division, so its value (10.0) deviates ~11% from the real-valued rectangle
formula (~11.13). Vetting it analytically would bake in the truncation -- a latent
precision issue to fix first.
- ROI_RADIUS_MEAN/MAX: mean/max per-pixel min SQUARED distance to the ROI's own
contour pixels (roi_radius.cpp); scipy distance_transform_edt uses a different
reference (distance to background, not squared), so no standard tool reproduces it.
- EROSIONS_2_VANISH_COMPLEMENT: degenerate 0 on this fixture; needs a holed fixture.
- MAJOR/MINOR_AXIS_LENGTH, ECCENTRICITY: ~1.4% moment-normalization gap vs skimage,
left regression.
gtest 717/717; pytest 72 passed (7 arrow fails are the tiff-only local build, not
regressions). Coverage regenerated (604 -> 607 / 758).
…ize_t-truncated) geo_len_thickness.cpp assigned PERIMETER (a double) into a size_t and then used integer division, truncating the perimeter to its floor and rounding every intermediate term. On the 8x8 shape2d fixture this made GEODETIC_LENGTH 10.0 and THICKNESS 3.0 -- ~11% off the rectangle-model formula (geodetic ~11.13, thickness ~2.34). Keep perimeter and area as double and use floating-point division, so both features equal the documented side lengths of the rectangle with the same area and perimeter (the roots of x^2 - (P/2)x + A = 0). Vet both by documented-formula conformance (like CIRCULARITY / DIAMETER_EQUAL_PERIMETER): GEODETIC_LENGTH == P/4 + sqrt(P^2/16 - A), THICKNESS == P/2 - GEODETIC_LENGTH, recomputed at 1e-9 from the vetted AREA + PERIMETER in test_shape2d_documented_formula_conformance_no_external_oracle. GEODETIC_LENGTH regression -> vetted (analytic); THICKNESS imea -> analytic (its prior imea row was tracker-only -- test_morphology_imea.h never existed -- and the loose 1000x snapshot had pinned the truncated 3.0). Goldens updated; the redundant loose geodetic gtest removed. morphology 80 -> 81. gtest 716/716; pytest 72 passed (7 arrow fails are the tiff-only local build; no Python test covers these features). Coverage 607 -> 608 / 758.
vjaganat90
approved these changes
Jul 29, 2026
| {"GEODETIC_LENGTH", 10.0}, | ||
| {"THICKNESS", 3.0}, | ||
| // Real-valued rectangle-model roots after the geo_len_thickness.cpp perimeter-truncation fix | ||
| // (were the integer-truncated 10.0 / 3.0). Now vetted by analytic conformance in |
Member
There was a problem hiding this comment.
GREAT! I have always suspected these
| } | ||
|
|
||
| TEST(TEST_NYXUS, TEST_SHAPE2D_VERIFIABLE_WITH_3P_BUILTIN_ORACLE_CONTOUR_DIAMETER_EQUAL_PERIMETER) | ||
| TEST(TEST_NYXUS, TEST_SHAPE2D_CONVEX_HULL_FEATURES) |
Member
There was a problem hiding this comment.
Not a blocker. If all the oracle supported tests are in tests/vetting then we should releax the naming of test groups and fixtures to original names, We should discuss this offline.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
geo_len_thickness.cpp assigned PERIMETER (a double) into a size_t and then used integer
division, truncating the perimeter to its floor and rounding every intermediate term. On
the 8x8 shape2d fixture this made GEODETIC_LENGTH 10.0 and THICKNESS 3.0 -- ~11% off the
rectangle-model formula (geodetic ~11.13, thickness ~2.34). Keep perimeter and area as
double and use floating-point division, so both features equal the documented side lengths
of the rectangle with the same area and perimeter (the roots of x^2 - (P/2)x + A = 0).
Vet both by documented-formula conformance (like CIRCULARITY / DIAMETER_EQUAL_PERIMETER):
GEODETIC_LENGTH == P/4 + sqrt(P^2/16 - A), THICKNESS == P/2 - GEODETIC_LENGTH, recomputed
at 1e-9 from the vetted AREA + PERIMETER in
test_shape2d_documented_formula_conformance_no_external_oracle. GEODETIC_LENGTH
regression -> vetted (analytic); THICKNESS imea -> analytic (its prior imea row was
tracker-only -- test_morphology_imea.h never existed -- and the loose 1000x snapshot had
pinned the truncated 3.0). Goldens updated; the redundant loose geodetic gtest removed.
morphology 80 -> 81.
gtest 716/716; pytest 72 passed (7 arrow fails are the tiff-only local build; no Python
test covers these features). Coverage 607 -> 608 / 758.