Skip to content

Reject non-finite target_values in proximity tools#2868

Open
brendancol wants to merge 2 commits into
mainfrom
issue-2850
Open

Reject non-finite target_values in proximity tools#2868
brendancol wants to merge 2 commits into
mainfrom
issue-2850

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Closes #2850

What

proximity, allocation, and direction treated non-finite target_values differently per backend. NumPy matched inf-valued pixels for target_values=[np.inf] and returned a real grid, while dask and cupy masked non-finite pixels out and returned all-NaN for the same raster.

  • Validate target_values in the shared _process dispatch, next to the existing distance_metric and max_distance checks. A non-finite entry (inf, -inf, nan) now raises ValueError on every backend.
  • The empty-target_values default path is unchanged. It already ignores non-finite pixels everywhere.
  • Note the finiteness requirement in the three target_values docstrings.

Chosen resolution

Reject up front rather than silently ignore. A non-finite target is not a meaningful raster category: nan can never match a pixel (nan == nan is False), and inf only "matched" on NumPy by accident. An explicit error is clearer than dropping entries silently and matches the existing validation style in _process.

Backend coverage

numpy, cupy, dask+numpy, dask+cupy. The validation lives in the shared dispatch ahead of backend branching, so all four paths raise identically.

Test plan

  • New test_non_finite_target_values_raises over [inf, -inf, nan, [2, inf]] x {proximity, allocation, direction} x 4 backends
  • New test_finite_target_values_run confirms the default path and explicit finite targets still work on every backend
  • Full test_proximity.py passes (339 tests)

Explicit target_values containing inf or nan produced backend-dependent
output. NumPy matched inf-valued pixels and returned a real
distance/allocation/direction grid, while dask and cupy masked
non-finite pixels out and returned all-NaN for the same raster.

Validate target_values in the shared _process dispatch, next to the
existing distance_metric and max_distance checks. Any non-finite entry
now raises ValueError on all four backends. The empty-target_values
default path, which already ignores non-finite pixels everywhere, is
unchanged.

Add cross-backend tests covering inf, -inf, nan, and a mixed
[finite, inf] list for proximity, allocation, and direction. Document
the finiteness requirement in the three target_values docstrings.
@github-actions github-actions Bot added the performance PR touches performance-sensitive code label Jun 2, 2026
Copy link
Copy Markdown
Contributor Author

@brendancol brendancol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: Reject non-finite target_values in proximity tools

Blockers (must fix before merge)

None.

Suggestions (should fix, not blocking)

None.

Nits (optional improvements)

  • proximity.py:1184: np.isfinite raises TypeError, not ValueError, if target_values ends up object/string dtype (e.g. ["a"]). That path was already broken before this PR (the old code fails downstream too), so it is out of scope here, but a follow-up that rejects non-numeric target_values with a clear message would round this out.
  • test_proximity.py:364: the cupy and dask+cupy params only exercise the GPU code when CUDA is present; otherwise the test_raster fixture falls back to numpy data, so on a CPU-only runner those two params re-test the numpy path. This matches the existing convention in test_invalid_max_distance_raises in the same file, and the validation runs ahead of backend dispatch anyway, so the gap is cosmetic.

What looks good

  • The check lives in _process ahead of backend branching, so all four backends raise identically. Right altitude for the fix.
  • The target_values.size guard keeps the empty-list default path untouched, and the fixture already carries inf/nan pixels that the default path still ignores. test_finite_target_values_run pins that.
  • Error message style and placement match the neighboring max_distance validation.
  • Tests cover inf, -inf, nan, and a mixed finite+inf list across all three public functions.

Checklist

  • Behavior is consistent across backends (validation precedes dispatch)
  • NaN/inf handling is correct (both rejected; default path still ignores them)
  • Edge cases covered (empty list preserved, mixed list rejected)
  • No dask/cupy materialization concerns (validation is on the small 1D target_values array)
  • Docstrings updated on all three public functions
  • README feature matrix: not applicable, no new function or backend change

Review follow-up: np.isfinite raised a downstream TypeError for a
non-numeric target_values dtype (e.g. strings). Guard the dtype kind
so non-numeric entries raise the same up-front ValueError as non-finite
ones. Add a cross-backend test for the string case.
Copy link
Copy Markdown
Contributor Author

@brendancol brendancol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review (after c8761de)

Disposition of prior findings

  • Nit 1 (non-numeric target_values gave a downstream TypeError): fixed. The validation now guards dtype.kind not in "iuf", so a string or other non-numeric target_values raises the same up-front ValueError as a non-finite one. Added test_non_numeric_target_values_raises across all four backends.
  • Nit 2 (cupy params fall back to numpy on CPU-only runners): dismissed. This is the established convention in test_invalid_max_distance_raises in the same file, and the validation runs ahead of backend dispatch, so the GPU and CPU paths can't diverge here. Matching the sibling test is the right call.

New change check

  • The dtype guard short-circuits before np.isfinite, so the bool/complex/string cases never reach the float-only path. Empty-list default still skipped via target_values.size.
  • No new findings. 78 target_values tests pass locally.

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.

proximity/allocation/direction: non-finite target_values give backend-dependent output

1 participant