Reject non-finite target_values in proximity tools#2868
Open
brendancol wants to merge 2 commits into
Open
Conversation
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.
brendancol
commented
Jun 2, 2026
Contributor
Author
brendancol
left a comment
There was a problem hiding this comment.
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.isfiniteraises TypeError, not ValueError, iftarget_valuesends 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
cupyanddask+cupyparams only exercise the GPU code when CUDA is present; otherwise thetest_rasterfixture falls back to numpy data, so on a CPU-only runner those two params re-test the numpy path. This matches the existing convention intest_invalid_max_distance_raisesin the same file, and the validation runs ahead of backend dispatch anyway, so the gap is cosmetic.
What looks good
- The check lives in
_processahead of backend branching, so all four backends raise identically. Right altitude for the fix. - The
target_values.sizeguard 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_runpins that. - Error message style and placement match the neighboring
max_distancevalidation. - 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.
brendancol
commented
Jun 2, 2026
Contributor
Author
brendancol
left a comment
There was a problem hiding this comment.
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-frontValueErroras a non-finite one. Addedtest_non_numeric_target_values_raisesacross 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_raisesin 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 viatarget_values.size. - No new findings. 78 target_values tests pass locally.
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.
Closes #2850
What
proximity,allocation, anddirectiontreated non-finitetarget_valuesdifferently per backend. NumPy matchedinf-valued pixels fortarget_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.target_valuesin the shared_processdispatch, next to the existingdistance_metricandmax_distancechecks. A non-finite entry (inf, -inf, nan) now raisesValueErroron every backend.target_valuesdefault path is unchanged. It already ignores non-finite pixels everywhere.target_valuesdocstrings.Chosen resolution
Reject up front rather than silently ignore. A non-finite target is not a meaningful raster category:
nancan never match a pixel (nan == nanis False), andinfonly "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
test_non_finite_target_values_raisesover [inf, -inf, nan, [2, inf]] x {proximity, allocation, direction} x 4 backendstest_finite_target_values_runconfirms the default path and explicit finite targets still work on every backendtest_proximity.pypasses (339 tests)