diff --git a/.claude/sweep-style-state.csv b/.claude/sweep-style-state.csv index 157ca015e..797ac1649 100644 --- a/.claude/sweep-style-state.csv +++ b/.claude/sweep-style-state.csv @@ -4,6 +4,7 @@ contour,2026-05-29,2698,HIGH,3,"F821 line 557: contours() return annotation ""gp geotiff,2026-05-27,2481,HIGH,1;3;4,"Bundled 387 flake8 + ~30 isort fixes since #2285/#2430. F401 x9, F811 x6, F841 x3. E501 x250 (mostly wrapped, 3 file-scope imports keep noqa: E402+E501). E252 x62, blank-line cluster, E128/E127 indents. importorskip imports use # noqa: E402. Cat 5 grep clean." hydro-d8,2026-05-29,2705,HIGH,1;3;4,"flake8+isort over the 13 D8 files only (dinf/mfd out of scope). Cat 3 HIGH: F401 x2 (flow_length_d8 function-local _compute_accum_seeds never called; snap_pour_point_d8 module-level cuda_args unused) - both confirmed dead, no re-export. Cat 1: E127/E128 continuation-indent x90 (mostly multi-line def signatures); E302/E303 blank-line cluster in watershed_d8; E501 x4 (flow_path_d8 + snap_pour_point_d8, wrapped ternaries). Cat 4: isort import-block reordering on all 13 files. No Cat 2 (W-codes), no Cat 5 (grep clean: no bare except, mutable defaults, ==None/==True, or shadowed builtins). flake8+isort clean after fix; 385 D8 tests pass. flow_direction_d8 needed manual blank-line placement to satisfy both isort and E302." polygonize,2026-05-27,2534,HIGH,1;3;4,"F401 line 58 (is_cupy_array unused, not re-exported). E127 lines 83/88 (overload continuation indent in generated_jit). isort: 5-line .utils import block collapses to one line at 100-char limit. Cat 2 clean. Cat 5 grep clean." +proximity,2026-05-29,2725,HIGH,1;3;4;5,"F841 line 1274 original_chunks dead local in unbounded dask+cupy branch (refactor leftover). Cat 5 mutable default target_values: list = [] in proximity/allocation/direction -> None sentinel, normalized to [] in body (never mutated, behaviour preserved). E128 line 291 np.where continuation under-indent in _vectorized_calc_direction. isort: re-sorted xrspatial import block + blank line after inline import cupy as cp. flake8+isort clean after fix; 69 proximity tests pass + new parametrized regression test. Pre-existing E127 (test_proximity.py 726/752) + test-file isort drift left untouched (out of module scope)." rasterize,2026-05-27,2503,HIGH,1;3,F401 line 15 + F811 line 1193 (paired: local import warnings shadowed unused module-level import); E306 line 1775 (nested @cuda.jit). isort clean. Cat 5 grep clean. Fix in PR #2507. resample,2026-05-27,2543,MEDIUM,4,isort drift only: 4 multi-line parenthesised imports collapsed to single/one-per-line under line_length=100 (top-of-file scipy.ndimage + xrspatial.utils; local cupyx imports in _nan_aware_interp_cupy and _interp_block_cupy); two blank-line nits after import math in _run_dask_numpy/_run_dask_cupy. flake8 clean. Cat 5 grep clean. 169 resample tests pass. viewshed,2026-05-29,2690,HIGH,1;4;5,"flake8 E127 x2 (L2013-2014 _viewshed_distance_sweep sig); isort .utils import reflow; shadowed builtin id->node_id (L1409,1474). Fixed via /rockout PR. No behavioural change." diff --git a/xrspatial/proximity.py b/xrspatial/proximity.py index 5bea23d75..329862913 100644 --- a/xrspatial/proximity.py +++ b/xrspatial/proximity.py @@ -24,13 +24,10 @@ class cupy(object): ndarray = False -from xrspatial.pathfinding import _available_memory_bytes -from xrspatial.utils import ( - _validate_raster, - cuda_args, has_cuda_and_cupy, - is_cupy_array, is_dask_cupy, ngjit, -) from xrspatial.dataset_support import supports_dataset +from xrspatial.pathfinding import _available_memory_bytes +from xrspatial.utils import (_validate_raster, cuda_args, has_cuda_and_cupy, is_cupy_array, + is_dask_cupy, ngjit) EUCLIDEAN = 0 GREAT_CIRCLE = 1 @@ -288,7 +285,7 @@ def _vectorized_calc_direction(x1, x2, y1, y2): dy = y2 - y1 d = np.arctan2(-dy, dx) * 57.29578 result = np.where(d < 0, 90.0 - d, - np.where(d > 90.0, 360.0 - d + 90.0, 90.0 - d)) + np.where(d > 90.0, 360.0 - d + 90.0, 90.0 - d)) result[(x1 == x2) & (y1 == y2)] = 0.0 return result.astype(np.float32) @@ -1294,9 +1291,9 @@ def _process_dask(raster, xs, ys): was_dask_cupy = has_cuda_and_cupy() and is_dask_cupy(raster) if was_dask_cupy: import cupy as cp + # Unbounded: convert to dask+numpy for KDTree/line-sweep # (KDTree is CPU-only; O(N log T) beats brute-force O(NT)) - original_chunks = raster.data.chunks raster = raster.copy( data=raster.data.map_blocks( lambda x: x.get(), dtype=raster.dtype, @@ -1369,7 +1366,7 @@ def proximity( raster: xr.DataArray, x: str = "x", y: str = "y", - target_values: list = [], + target_values: list = None, max_distance: float = np.inf, distance_metric: str = "EUCLIDEAN", ) -> xr.DataArray: @@ -1485,6 +1482,9 @@ def proximity( * x (x) int64 0 1 2 3 4 """ + if target_values is None: + target_values = [] + _validate_raster(raster, func_name='proximity', name='raster') proximity_img = _process( @@ -1514,7 +1514,7 @@ def allocation( raster: xr.DataArray, x: str = "x", y: str = "y", - target_values: list = [], + target_values: list = None, max_distance: float = np.inf, distance_metric: str = "EUCLIDEAN", ) -> xr.DataArray: @@ -1627,6 +1627,9 @@ def allocation( * x (x) int64 0 1 2 3 4 """ + if target_values is None: + target_values = [] + _validate_raster(raster, func_name='allocation', name='raster') allocation_img = _process( @@ -1656,7 +1659,7 @@ def direction( raster: xr.DataArray, x: str = "x", y: str = "y", - target_values: list = [], + target_values: list = None, max_distance: float = np.inf, distance_metric: str = "EUCLIDEAN", ) -> xr.DataArray: @@ -1775,6 +1778,9 @@ def direction( * x (x) int64 0 1 2 3 4 """ + if target_values is None: + target_values = [] + _validate_raster(raster, func_name='direction', name='raster') direction_img = _process( diff --git a/xrspatial/tests/test_proximity.py b/xrspatial/tests/test_proximity.py index f3c41b8c5..d02b34be6 100644 --- a/xrspatial/tests/test_proximity.py +++ b/xrspatial/tests/test_proximity.py @@ -1311,3 +1311,26 @@ def test_proximity_res_attr_drives_bounded_dask_padding(): assert isinstance(result.data, da.Array) np.testing.assert_allclose( result.values, expected, equal_nan=True, rtol=1e-5) + + +@pytest.mark.parametrize("func", [proximity, allocation, direction]) +def test_target_values_none_default_matches_empty_list(func): + # target_values default switched from [] to a None sentinel; passing + # None (the default) must behave exactly like passing an empty list. + data = np.asarray([[0., 0., 0., 0., 0.], + [0., 0., 0., 1., 0.], + [0., 0., 0., 0., 0.], + [0., 0., 2., 0., 0.], + [0., 0., 0., 0., 0.]]) + n, m = data.shape + raster = xr.DataArray(data, dims=['y', 'x']) + raster['y'] = np.arange(n)[::-1] + raster['x'] = np.arange(m) + + default_result = func(raster) + explicit_result = func(raster, target_values=[]) + + np.testing.assert_array_equal( + np.nan_to_num(default_result.data), + np.nan_to_num(explicit_result.data), + )