Skip to content

test_zonal: close HIGH backend-coverage gaps for crosstab/regions/trim/crop/apply#2530

Merged
brendancol merged 3 commits into
mainfrom
deep-sweep-test-coverage-zonal-2026-05-27
May 28, 2026
Merged

test_zonal: close HIGH backend-coverage gaps for crosstab/regions/trim/crop/apply#2530
brendancol merged 3 commits into
mainfrom
deep-sweep-test-coverage-zonal-2026-05-27

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Summary

Closes test-coverage gaps surfaced by the deep-sweep test-coverage pass on the zonal module (2026-05-27). Adds test_zonal_backend_coverage_2026_05_27.py with 32 tests, all passing on a CUDA host. Source code is untouched.

Cat 1 HIGH -- backend coverage

  • crosstab cupy + dask+cupy parity vs numpy. _crosstab_cupy and _crosstab_dask_cupy are registered in ArrayTypeFunctionMapping but no existing test invoked them.
  • regions cupy + dask+cupy parity vs numpy (cupyx.scipy.ndimage.label path + dask+cupy compute-then-label path).
  • trim dask+numpy + cupy + dask+cupy parity. The dask path _trim_bounds_dask (with its isnan branch) and the cupy data.get() path had no direct coverage.
  • crop dask+numpy + cupy + dask+cupy parity. Same shape of gap as trim for _crop_bounds_dask.
  • apply 3D cupy + dask+cupy parity. Existing 3D apply tests covered numpy + dask+numpy only; the per-layer kernel-launch branches in _apply_cupy and _apply_dask_cupy were untested.

Cat 3 MEDIUM -- geometric edge cases

  • 1x1 single-pixel raster on trim and crop.
  • 1xN and Nx1 strips on regions.

Cat 4 LOW -- parameter coverage

  • regions(neighborhood=6) raises ValueError.
  • suggest_zonal_canvas(crs='Geographic') aspect-ratio pin and invalid-crs KeyError.
  • crosstab cupy with zone_ids and cat_ids subset.
  • crosstab cupy with agg='percentage'.

Cat 5 MEDIUM -- metadata propagation

  • regions preserves coords and attrs on numpy + dask+numpy.
  • trim and crop set the default name and propagate attrs.

Also pins the documented numpy-vs-dask trim asymmetry on the NaN sentinel: numpy _trim uses equality which never matches NaN, dask _trim_bounds_dask has a dedicated isnan branch.

Mutation test: dropping cupy.asnumpy() in _crosstab_cupy flipped test_crosstab_cupy_matches_numpy red, confirming the test detects regressions.

Test plan

  • pytest xrspatial/tests/test_zonal_backend_coverage_2026_05_27.py -> 32 passed on CUDA host
  • Mutation check on _crosstab_cupy -> test fails as expected
  • Source xrspatial/zonal.py is untouched (md5 verified)
  • Pre-existing test_zonal.py tests still pass

@github-actions github-actions Bot added the performance PR touches performance-sensitive code label May 27, 2026
@brendancol
Copy link
Copy Markdown
Contributor Author

PR Review: test_zonal backend-coverage gaps for crosstab / regions / trim / crop / apply

Blockers

None. Source untouched, 32 new tests, all pass on a CUDA host.

Suggestions (applied in 610e455)

  • test_zonal_backend_coverage_2026_05_27.py:203-218 and :241-254 defined essentially the same _canonical_labels helper twice. Lifted to module scope.
  • :54-59 redeclared cuda_and_cupy_available and dask_required locally. general_checks.py already exports cuda_and_cupy_available (identical) and dask_array_available (same skip predicate). Now imported from general_checks; dask_required is kept as a one-line alias for readable decorator sites.
  • Dropped two unused imports (zonal.stats, has_cuda_and_cupy) that flake8 flagged.

Suggestions (deferred)

  • crop(..., zones_ids=...) (:424, :425, :439, :440, :462, :463, :476) uses the legacy parameter name. origin/main renamed it to zone_ids with a deprecation shim (commit a9620ba), but this PR branch is behind main and the shim is not present here, so renaming now would break the tests on this branch. Best handled at rebase, when the shim is available.

Nits (applied)

  • :611-612 docstring contained an inline "wait, Geographic is plus/minus 180 / plus/minus 90 which is 2:1" mid-sentence correction. Rewritten.

Nits (skipped)

  • Single-pixel and strip tests (:547-599) parametrize only over ['numpy', 'dask+numpy']. Adding cupy variants would broaden coverage, but the dedicated cupy/dask+cupy parity tests above already exercise the cupy _trim/_crop/_regions branches on larger inputs, so this would be cosmetic.
  • test_apply_3d_cupy / test_apply_3d_dask_cupy (:486-540) use a constant 5.0 input. A non-constant input would exercise the per-layer kernel mapping more directly, but the current shape/nodata assertions still pin the 3D dispatch branch.

What looks good

  • The _crosstab_cupy / _crosstab_dask_cupy, _regions_cupy / _regions_dask_cupy, _trim_bounds_dask, _crop_bounds_dask, and 3D _apply_cupy / _apply_dask_cupy branches were dispatchable but unreached. This PR closes that gap.
  • Label-set canonicalisation in the regions parity tests handles the scipy-vs-cupyx label ordering difference correctly (partition equality, not raw label values).
  • test_trim_dask_nan_values (:352-382) pins the numpy-vs-dask asymmetry on the NaN sentinel rather than masking it.
  • Commit body notes a mutation against cupy.asnumpy() in _crosstab_cupy flipped the cupy parity test red. Good evidence the new test catches regressions.

Checklist

  • All implemented backends covered with parity tests
  • NaN handling tested
  • Edge cases covered (1x1 raster, 1xN/Nx1 strips, invalid neighborhood)
  • Dask chunk boundaries exercised across multiple chunk shapes
  • No premature materialization in tests
  • Docstrings present on every test

@brendancol brendancol force-pushed the deep-sweep-test-coverage-zonal-2026-05-27 branch from 610e455 to 2491dda Compare May 28, 2026 13:05
… trim / crop / apply

Adds test_zonal_backend_coverage_2026_05_27.py (32 tests, all passing on a
CUDA host) covering test-coverage gaps surfaced by the deep-sweep
test-coverage pass on the zonal module.

Cat 1 HIGH backend coverage:
- crosstab cupy + dask+cupy parity vs numpy (_crosstab_cupy /
  _crosstab_dask_cupy were registered in ArrayTypeFunctionMapping but
  never invoked by any test).
- regions cupy + dask+cupy parity vs numpy (cupyx.scipy.ndimage label
  branch + dask+cupy compute-then-label branch).
- trim dask+numpy + cupy + dask+cupy parity (the cupy data.get() path
  and the dask _trim_bounds_dask isnan branch were untested).
- crop dask+numpy + cupy + dask+cupy parity (matching _crop_bounds_dask
  branch).
- apply 3D cupy + dask+cupy parity (per-layer kernel launch over the
  third axis; existing 3D apply tests covered numpy + dask+numpy only).

Cat 3 MEDIUM geometric edges:
- 1x1 single-pixel raster on trim / crop.
- 1xN and Nx1 strips on regions.

Cat 4 LOW parameter coverage:
- regions(neighborhood=6) -> ValueError pin.
- suggest_zonal_canvas(crs='Geographic') aspect-ratio pin and
  invalid-crs KeyError.
- crosstab cupy with zone_ids / cat_ids subset.
- crosstab cupy with agg='percentage'.

Cat 5 MEDIUM metadata propagation:
- regions preserves coords + attrs (numpy + dask+numpy).
- trim / crop set name and preserve attrs.

Also pins the documented numpy-vs-dask trim asymmetry on the NaN sentinel:
numpy _trim uses equality (never matches NaN), dask _trim_bounds_dask has
a dedicated isnan branch.  Mutation against cupy.asnumpy() in
_crosstab_cupy flipped test_crosstab_cupy_matches_numpy red, confirming
the test detects regressions.  Source untouched.
- Lift duplicated _canonical_labels helper to module scope so a fix
  lands once instead of twice (was duplicated in
  test_regions_cupy_matches_numpy and test_regions_dask_cupy_matches_numpy).
- Import cuda_and_cupy_available and dask_array_available from
  general_checks instead of redeclaring locally.  dask_required is now
  a single-line alias of the shared decorator.
- Drop unused imports (zonal.stats, has_cuda_and_cupy) flagged by flake8.
- Tighten test_suggest_zonal_canvas_geographic_crs docstring (removed
  an unedited mid-sentence "wait..." correction).

zones_ids=... parameter rename to zone_ids=... is intentionally
deferred to rebase: this branch is behind origin/main, the rename +
deprecation shim only exist on main, so renaming here would break the
tests on this branch.

32 tests still pass on the CUDA host.
@brendancol brendancol force-pushed the deep-sweep-test-coverage-zonal-2026-05-27 branch from 2491dda to 683e0dd Compare May 28, 2026 13:45
@brendancol brendancol merged commit bf26699 into main May 28, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance PR touches performance-sensitive code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant