Skip to content

Commit 5325aa5

Browse files
authored
bump: validate agg, count, and spread inputs (#3614)
* bump: validate agg, count, and spread inputs bump() only validated width and height. Bad values for the agg template, count, and spread surfaced numpy/dispatcher errors from deep in the call or were silently accepted: - agg as a plain ndarray -> 'Unsupported Array Type' from the backend dispatcher; a 3D/1D DataArray -> 'too many values to unpack' from the bare h, w = agg.shape unpack. Neither named agg. - count negative/float -> raw np.empty errors. - spread negative/float -> accepted silently, dropping the spreading the caller asked for despite the docstring saying int. Add _validate_raster(agg, ndim=2, numeric=False) and _validate_scalar for count (>=0) and spread (>=0), matching the existing width/height checks. bump only reads agg's shape/backend so dtype stays unchecked. spread=0 and count=0 remain valid. Verified across numpy, cupy, dask, and dask+cupy. error-handling sweep 2026-07-02 * docs: mark bump() count parameter as optional Note the default count heuristic in the docstring, matching the optional handling the input validation already relies on.
1 parent 6139e56 commit 5325aa5

3 files changed

Lines changed: 64 additions & 4 deletions

File tree

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
module,last_inspected,issue,severity_max,categories_found,notes
2-
geotiff,2026-07-02,3604,MEDIUM,2;4,"to_geotiff 0D/1D DataArray raised opaque IndexError from _coords.py coords_to_transform (dims[-2]) instead of clean 'Expected 2D or 3D' ValueError; numpy path + 4D DataArray already clean. Fixed via early ndim guard before dispatch (eager/vrt/gpu) + 3 tests; PR #3604. Read-side param validation + typed-error hierarchy + allow_rotated/allow_invalid_nodata VRT+chunked opt-in threading verified clean (CUDA available, GPU paths run). gh issue create blocked by auto-mode; PR opened. Cat 2+4."
1+
module,last_inspected,issue,severity_max,categories_found,notes
2+
bump,2026-07-02,,HIGH,1;2;3;4,"bump() agg template unvalidated: plain ndarray -> ArrayTypeFunctionMapping 'Unsupported Array Type'; 3D/1D DataArray -> 'too many values to unpack'. count/spread unvalidated (internal numpy errors / silent). Added _validate_raster(agg,ndim=2) + _validate_scalar for count,spread. all 4 backends verified (CUDA present)."
3+
geotiff,2026-07-02,3604,MEDIUM,2;4,"to_geotiff 0D/1D DataArray raised opaque IndexError from _coords.py coords_to_transform (dims[-2]) instead of clean 'Expected 2D or 3D' ValueError; numpy path + 4D DataArray already clean. Fixed via early ndim guard before dispatch (eager/vrt/gpu) + 3 tests; PR #3604. Read-side param validation + typed-error hierarchy + allow_rotated/allow_invalid_nodata VRT+chunked opt-in threading verified clean (CUDA available, GPU paths run). gh issue create blocked by auto-mode; PR opened. Cat 2+4."

xrspatial/bump.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ class cupy(object):
1515
except ImportError:
1616
da = None
1717

18-
from xrspatial.utils import (ArrayTypeFunctionMapping, _validate_scalar, has_cuda_and_cupy,
19-
is_cupy_array, ngjit)
18+
from xrspatial.utils import (ArrayTypeFunctionMapping, _validate_raster, _validate_scalar,
19+
has_cuda_and_cupy, is_cupy_array, ngjit)
2020

2121
# Upper bound on bump count to prevent accidental OOM from the default
2222
# w*h//10 heuristic. 16 bytes per bump (int32 loc pair + float64 height),
@@ -347,7 +347,15 @@ def heights(locations, src, src_range, height = 20):
347347
Description: Example Bump Map
348348
units: km
349349
"""
350+
_validate_scalar(spread, func_name='bump', name='spread',
351+
dtype=int, min_val=0)
352+
if count is not None:
353+
_validate_scalar(count, func_name='bump', name='count',
354+
dtype=int, min_val=0)
355+
350356
if agg is not None:
357+
_validate_raster(agg, func_name='bump', name='agg', ndim=2,
358+
numeric=False)
351359
h, w = agg.shape
352360
else:
353361
_validate_scalar(width, func_name='bump', name='width',

xrspatial/tests/test_bump.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,54 @@ def test_bump_dask_bypasses_raster_guard():
379379
result = bump(agg=agg, count=10, spread=0)
380380
assert result.shape == (100_000, 100_000)
381381
assert isinstance(result.data, da.Array)
382+
383+
384+
# --- Input-validation regression tests ---
385+
386+
def test_bump_agg_must_be_dataarray():
387+
"""A plain ndarray template raises a clear TypeError naming `agg`,
388+
not an inscrutable 'Unsupported Array Type' from the dispatcher."""
389+
import pytest
390+
391+
with pytest.raises(TypeError, match=r"bump\(\): `agg` must be an "
392+
r"xarray\.DataArray"):
393+
bump(agg=np.zeros((10, 10)))
394+
395+
396+
def test_bump_agg_must_be_2d():
397+
"""A 3D or 1D template raises a clear ValueError naming `agg` and the
398+
2D requirement, not 'too many values to unpack'."""
399+
import pytest
400+
401+
with pytest.raises(ValueError, match=r"bump\(\): `agg` must be 2D"):
402+
bump(agg=xr.DataArray(np.zeros((3, 10, 10)), dims=['b', 'y', 'x']))
403+
with pytest.raises(ValueError, match=r"bump\(\): `agg` must be 2D"):
404+
bump(agg=xr.DataArray(np.zeros(10), dims=['x']))
405+
406+
407+
def test_bump_count_validated():
408+
"""`count` gets the same clean validation as width/height instead of
409+
surfacing raw numpy errors."""
410+
import pytest
411+
412+
with pytest.raises(ValueError, match=r"bump\(\): `count` must be >= 0"):
413+
bump(width=10, height=10, count=-5)
414+
with pytest.raises(TypeError, match=r"bump\(\): `count` must be int"):
415+
bump(width=10, height=10, count=5.0)
416+
417+
418+
def test_bump_spread_validated():
419+
"""`spread` is documented as int; negative and non-int values raise
420+
instead of being silently ignored."""
421+
import pytest
422+
423+
with pytest.raises(ValueError, match=r"bump\(\): `spread` must be >= 0"):
424+
bump(width=10, height=10, spread=-3)
425+
with pytest.raises(TypeError, match=r"bump\(\): `spread` must be int"):
426+
bump(width=10, height=10, spread=2.5)
427+
428+
429+
def test_bump_spread_zero_still_allowed():
430+
"""spread=0 (single-pixel bumps) must remain valid."""
431+
result = bump(width=10, height=10, count=5, spread=0)
432+
assert result.shape == (10, 10)

0 commit comments

Comments
 (0)