Skip to content

fix(morphology): geodetic length/thickness from real perimeter (was size_t-truncated) - #392

Open
darkclad wants to merge 2 commits into
PolusAI:mainfrom
darkclad:main-geodetic-fix
Open

fix(morphology): geodetic length/thickness from real perimeter (was size_t-truncated)#392
darkclad wants to merge 2 commits into
PolusAI:mainfrom
darkclad:main-geodetic-fix

Conversation

@darkclad

Copy link
Copy Markdown

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.

Demian Vladi 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 vjaganat90 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.

Overall LGTM!

{"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

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.

GREAT! I have always suspected these

Comment thread tests/test_all.cc
}

TEST(TEST_NYXUS, TEST_SHAPE2D_VERIFIABLE_WITH_3P_BUILTIN_ORACLE_CONTOUR_DIAMETER_EQUAL_PERIMETER)
TEST(TEST_NYXUS, TEST_SHAPE2D_CONVEX_HULL_FEATURES)

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.

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.

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.

2 participants