perf(sizeshape): scatter-based spatial moments + inertia, replacing regionprops einsum (~1.6x)#77
Draft
timtreis wants to merge 3 commits into
Draft
perf(sizeshape): scatter-based spatial moments + inertia, replacing regionprops einsum (~1.6x)#77timtreis wants to merge 3 commits into
timtreis wants to merge 3 commits into
Conversation
1f77175 to
010232e
Compare
…egionprops einsum (~1.6x) `get_sizeshape`'s moment features came from `regionprops_table`'s `moments` / `moments_central` / `moments_normalized` / `moments_hu` columns plus the `inertia_tensor` / `inertia_tensor_eigvals`, all computed per-region with an `einsum` routine whose contraction path is re-derived for every object. On a 1080^2 / 142-object tile cProfile showed this moment machinery dominated the call (~120 ms of 352 ms, ~40 ms of it pure `einsum_path` overhead). Moments are plain reductions, so compute the whole set in one label-scatter pass (`primitives/_moments.py`): raw spatial moments in each object's local bbox frame, central moments by direct centred summation, then normalized + the 7 Hu invariants. The 2D inertia tensor and its eigenvalues are derived from the same central moments (`inertia_2d`), so for 2D regionprops now computes *no* moments at all — its per-region einsum is gone entirely. Raw spatial moments are bit-exact vs regionprops; the centroid-dependent matrices and inertia match to ~1e-13 relative (moments reach ~1e8 magnitude). Object order (ascending label) and the NaN convention for normalized moments (p+q<2) match regionprops. 2D only; the 3D path is unchanged. Measured: get_sizeshape 352 -> 220 ms (1.6x), large tile. No new deps. `spatial_moments_2d` / `inertia_2d` live in `primitives/` so the planned numba sizeshape lane can reuse them. Adds golden tests vs regionprops (raw bit-exact; central/normalized/Hu/inertia across multi / non-contiguous / edge-touching / single-pixel / empty + end-to-end get_sizeshape wiring). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
010232e to
303cd48
Compare
timtreis
added a commit
that referenced
this pull request
Jun 6, 2026
…apper Address /code-review notes on the sizeshape lane: - Extract `moment_feature_dict` into `primitives/_moments.py` as the single source of truth for the 53 `calculate_advanced` moment/inertia feature names and the (p,q) orders exposed. The numba `_sizeshape_2d` now calls it instead of building those keys with inline f-string loops, removing the duplication of the numpy assembly and the f-string/constant drift risk. The numpy `get_sizeshape` adopts the same helper when #77's moment rewrite rebases onto this lane (the sizeshape golden test cross-checks the keys vs the numpy F_* constants meanwhile). - Clarify the wrapper: `pixels` is accepted only for dispatch-signature parity and is unused (sizeshape is purely geometric, like the numpy backend); pass `masks` to `to_bzyx` for axis normalisation and drop the computed-then-discarded `_pixels_zyx`. - Add an end-to-end empty-mask test exercising the full dict assembly with zero objects (the kernels had empty tests; the wrapper assembly did not). 25 tests pass (incl. new empty case); ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…moments The previous commit stripped the explicit moment columns from the 2D regionprops request but kept axis_major_length / axis_minor_length / eccentricity / orientation — and regionprops derives THOSE from moments_central, so it still ran the per-region moment einsum. The added scatter was therefore pure overhead and 2D get_sizeshape regressed (~198ms baseline -> ~216ms). Apply "option B" (already used by the numba sizeshape backend): derive all four from the scatter's central moments via a new `axes_eccentricity_orientation` helper (built on the existing `inertia_2d`), so the 2D regionprops call requests NOTHING moment-related and never runs the einsum. The scatter (raw/central/normalized/hu) is now computed once for 2D and reused by both the moment features and the axis/ecc/orientation features. 3D keeps axis lengths on regionprops (3D moments out of scope). large 1080^2/142obj: regionprops 208->139ms/call, get_sizeshape ~216 -> ~178ms (now below the 198ms pre-opt baseline). Bit-exact vs regionprops to 1e-9 incl. degenerate (single-pixel, line) — new `test_axes_match_*` golden tests added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… label LUT Review-driven cleanups to the scatter-based get_sizeshape, all verified bit-exact vs the PyPI 0.1.19 release (column order + values): - inertia_2d clips eigenvalues to >=0 like skimage, fixing NaN MinorAxisLength / eccentricity>1 on thin/oblique objects (~4% of thin lines triggered it). - spatial_moments_2d takes the per-object bbox origin from label_to_idx_lut's find_objects pass (new return_bbox=) instead of a 1<<31 sentinel + minimum.at; gains an `advanced` flag so the normalized/Hu moments are skipped when the caller won't emit them. - get_sizeshape computes inertia_2d once (shared by the axes derivation and the tensor features) and assembles the 53 advanced moment features via a new moment_feature_dict (grouped order matching 0.1.19); drops the redundant whole-image numpy.unique for the object count. - _moment_matrix powers driven by _ORDER; honest docstring on raw-moment divergence (round-off, grows with object size; not "bit-exact"). - Regression tests: pin the 0.1.19 key order; thin-object clip vs skimage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
What
get_sizeshape's moment features came fromregionprops_table'smoments/moments_central/moments_normalized/moments_huplusinertia_tensor/inertia_tensor_eigvals— all computed per region with aneinsumroutine whose contraction path is re-derived for every object. cProfile on a 1080²/142-object tile showed this moment machinery dominated the call (~120 ms of 352 ms, ~40 ms of it pureeinsum_pathoverhead).This computes the whole set in one label-scatter pass (
primitives/_moments.py): raw spatial moments in each object's local bbox frame, central moments by direct centred summation, then normalized + the 7 Hu invariants. The 2D inertia tensor + eigenvalues are derived from the same central moments (inertia_2d), so for 2D regionprops now computes no moments at all — its per-region einsum is gone entirely.Performance
get_sizeshape: 352 → 220 ms (1.6×) on the large tile (median of 11). Removing inertia from regionprops alone saves ~41 ms (regionprops 147→106 ms); the moment scatter is the rest. No new dependencies.Fidelity
p+q<2) match regionprops. Eigenvalues descending, as skimage.Tests
test/test_sizeshape_moments.py: raw moments bit-exact; central/normalized/Hu and inertia/eigvals vs regionprops atrtol=1e-7across multi-object, non-contiguous labels, edge-touching, single-pixel (degenerate), empty, and end-to-endget_sizeshapewiring. Fulltest_core_measurementsgreen; ruff + CI-style mypy clean.Reuse hook
spatial_moments_2d+inertia_2dlive inprimitives/so the planned numba sizeshape lane can reuse them.