Skip to content

fix(convex-hull): Pick's-theorem hull area so SOLIDITY <= 1#357

Open
darkclad wants to merge 1 commit into
PolusAI:mainfrom
darkclad:main-convex-hull-solidity
Open

fix(convex-hull): Pick's-theorem hull area so SOLIDITY <= 1#357
darkclad wants to merge 1 commit into
PolusAI:mainfrom
darkclad:main-convex-hull-solidity

Conversation

@darkclad

@darkclad darkclad commented Jul 1, 2026

Copy link
Copy Markdown

ConvexHullFeature computed the hull area with the bare shoelace polygon area, which runs through pixel centres and under-counts the rasterised pixel coverage. The ROI area is a pixel count, so SOLIDITY = roi_area / hull_area could exceed 1 (1.3 on the canonical ROI) - impossible.

Use the Pick's-theorem pixel-count-equivalent hull area (shoelace + boundary_points/2 + 1, boundary_points = sum of gcd(|dx|,|dy|) over edges), matching scikit-image convex_area, plus a hull>0 guard. SOLIDITY is now <= 1 in both the trivial and out-of-core paths.

Refresh the vetted goldens in test_shape_morphology_2d.h (CONVEX_HULL_AREA 20->27, SOLIDITY 1.3->0.96296 = 26/27; CIRCULARITY and all other shape goldens unchanged) and add tests/python/test_feature_bugs.py::test_solidity_le_one.

@darkclad

darkclad commented Jul 1, 2026

Copy link
Copy Markdown
Author

Justification — golden value changes for main-convex-hull-solidity

Branch: main-convex-hull-solidity (split from main-feature-validation, based on main)
Source fix: src/nyx/features/convex_hull_nontriv.cpp
Test file touched: tests/test_shape_morphology_2d.h (2 goldens: CONVEX_HULL_AREA, SOLIDITY)

The bug

ConvexHullFeature::calculate computed the hull area with the bare shoelace polygon area
(polygon_area(convHull)), which runs through pixel centres and therefore under-counts the
rasterised pixel coverage of the hull. The ROI area (s_roi) is a pixel count. Dividing a
pixel-count ROI area by a centre-based hull area gives SOLIDITY = s_roi / s_hull > 1 for
small/elongated ROIs — impossible (solidity is by definition ≤ 1). On the canonical fixture the old
golden was SOLIDITY = 1.3.

The fix

Use the Pick's-theorem pixel-count-equivalent hull area so the hull is measured on the same basis
as the ROI (pixel count):

s_hull = shoelace_area + boundary_points/2 + 1      // boundary_points = sum over edges of gcd(|dx|,|dy|)

This matches scikit-image's convex_area (the pixel count of the rasterised hull). Result:
SOLIDITY = s_roi / s_hull ≤ 1, plus a s_hull > 0 guard.

Golden changes (2 keys)

Golden Old New Why
CONVEX_HULL_AREA 20.0 27.0 Pick's-theorem pixel-count hull area on the fixture. External oracle scikit-imageconvex_area = 28 (≈4% high; the two hulls differ by one boundary lattice point).
SOLIDITY 1.3 0.9629629629629629 = AREA_PIXELS_COUNT / CONVEX_HULL_AREA = 26 / 27 = 0.962962…. scikit-image gives 26/28 = 0.929. Now ≤ 1.

Why this is not fudging

  • Internal consistency: the new SOLIDITY is exactly AREA_PIXELS_COUNT (26) / CONVEX_HULL_AREA (27); both the numerator (26, unchanged golden) and the new denominator are in the same table.
  • Nothing else moved: the fix only changes s_hull. CIRCULARITY (which uses s_roi and the
    perimeter, not the hull) is unchanged, as are all other shape goldens. Only the 2 hull-derived
    keys changed.
  • Sign of correctness: the old value 1.3 was provably wrong (solidity can never exceed 1); the new
    value is ≤ 1 and within ~4% of scikit-image's independent convex_area/solidity.
  • Values are byte-identical to the oracle-validated main-feature-validation (copied, not invented);
    only the shared table-merge refactor was dropped (I kept main's two-table structure and edited the
    two numbers in place).

Regression guard

tests/python/test_feature_bugs.py::test_solidity_le_one asserts SOLIDITY <= 1 + 1e-6 on the
canonical ROI through the production featurize() path — the hard correctness invariant that the old
code violated (1.3).

CI

Full suite green on this branch: C++ runAllTests 683/683; pytest tests/python/ 49 passed, 1 skipped.

ConvexHullFeature computed the hull area with the bare shoelace polygon area,
which runs through pixel centres and under-counts the rasterised pixel coverage.
The ROI area is a pixel count, so SOLIDITY = roi_area / hull_area could exceed 1
(1.3 on the canonical ROI) - impossible.

Use the Pick's-theorem pixel-count-equivalent hull area
(shoelace + boundary_points/2 + 1, boundary_points = sum of gcd(|dx|,|dy|) over
edges), matching scikit-image convex_area, plus a hull>0 guard. SOLIDITY is now
<= 1 in both the trivial and out-of-core paths.

Refresh the vetted goldens in test_shape_morphology_2d.h (CONVEX_HULL_AREA 20->27,
SOLIDITY 1.3->0.96296 = 26/27; CIRCULARITY and all other shape goldens unchanged)
and add tests/python/test_feature_bugs.py::test_solidity_le_one.
@darkclad darkclad force-pushed the main-convex-hull-solidity branch from 88e14df to df9459b Compare July 2, 2026 15:33
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