diff --git a/.claude/sweep-api-consistency-state.csv b/.claude/sweep-api-consistency-state.csv index 143e7c0d8..81cc96246 100644 --- a/.claude/sweep-api-consistency-state.csv +++ b/.claude/sweep-api-consistency-state.csv @@ -1,4 +1,5 @@ module,last_inspected,issue,severity_max,categories_found,notes +bump,2026-07-02,3608,MEDIUM,1;2;3;5,"Sweep 2026-07-02 (deep-sweep-api-consistency-bump-2026-07-02). 3 MEDIUM, 1 LOW. Compared bump() against sibling synth generators perlin/worley/generate_terrain (all: agg first-positional, name= param, output named, attrs preserved). FIXED (issue #3608, PR #3610): (Cat 2/5) bump returned an unnamed DataArray with no name= param while all 3 siblings name their output; added keyword-only name='bump' set on both agg and width/height return paths. Bundled (Cat 3) count docstring said required int but signature is Optional[int]=None with width*height//10 default (capped 10M) -- corrected. Added test_bump_names_output_like_siblings; test_bump.py 19 pass incl cupy+dask+cupy (GPU host). DOCUMENTED not fixed (breaking): (Cat 1 MEDIUM) agg is keyword-only/last in bump but positional-first in siblings, so bump(some_dataarray) raises TypeError where perlin(some_dataarray) works; moving agg positional would break the historical bump(width,height) signature -- needs its own deprecation plan, not a clean shim. (Cat 3 LOW) height_func has no type hint and a freeform docstring type line; left alone per LOW=document-only. Not findings: Cat 4 no default drift within bump; Cat 5 bump is re-exported in __init__.py (no __all__ but matches module convention). attrs=dict(res=1) left untouched (metadata-sweep concern, and documented in the plot example)." classify,2026-06-20,3398,MEDIUM,1;3,"Sweep 2026-06-20 (deep-sweep-api-consistency-classify-2026-06-20). 1 MEDIUM Cat 1 finding filed as #3398 and fixed on this branch. (MEDIUM Cat 1 positional-order drift) natural_breaks ordered its params (agg, num_sample, name, k) while the other two classifiers that take the same trio order them (agg, k, num_sample, name): quantile(agg, k=4, num_sample, name), maximum_breaks(agg, k=5, num_sample, name). So natural_breaks(raster, 5) silently set num_sample=5 instead of k=5. Fix reorders natural_breaks to (agg, k=5, num_sample=20000, name) and adds a _natural_breaks_legacy_order shim: when k= is a keyword AND a second positional is present (the only way pre-1.0 callers passed num_sample, since k was last and always keyword), the positional is treated as the old num_sample with a DeprecationWarning. Keeps the one example notebook call natural_breaks(raster, 20000, k=4) working. Bundled trivial Cat 3 fix in same PR: binary() was the only public classifier with no type hints -- added agg: xr.DataArray, name: Optional[str], -> xr.DataArray to match the other 9. Tests: test_natural_breaks_positional_k_matches_siblings (new positional k == keyword k) and test_natural_breaks_legacy_positional_num_sample_warns (legacy order warns + maps identically). Full test_classify.py (now 91) + test_validation.py pass. Cat 4 considered NOT a finding: quantile k=4 (quartiles) vs k=5 (quintiles) elsewhere is the documented PySAL/mapclassify convention, not drift. No Cat 2 return drift (all 10 publics return xr.DataArray/Dataset via @supports_dataset, coords/dims/attrs preserved). No Cat 5 orphan API (all 10 re-exported in __init__.py; no __all__ but consistent with module convention). Cross-cutting, notes only: first-arg agg (classify family) vs raster (reproject/rasterize/polygonize) is library-wide drift, out of per-module scope. cuda-validated: CUDA_AVAILABLE=True on this host; natural_breaks new order + legacy shim smoke-tested on numpy AND cupy entry points (both warn + remap), dataset path binds name correctly, binary verified on cupy." focal,2026-06-10,3215;3216,MEDIUM,3;4,"Sweep 2026-06-10 (deep-sweep-api-consistency-focal-2026-06-10). 2 MEDIUM findings filed, fixed on branches -01/-02 off this one. (#3215, MEDIUM Cat 4 cross-backend default parity, branch -01) apply() default func=_calc_mean is an @ngjit CPU function but the cupy/dask+cupy paths launch func as a CUDA kernel via _focal_stats_func_cupy func[griddim, blockdim], so apply(cupy_agg, kernel) raises TypeError 'CPUDispatcher' object is not subscriptable (dask+cupy builds the graph and fails at compute). Prior 2026-05-29 sweep dispositioned this LOW as 'documented in the docstring', but the docstring covers explicit funcs -- the default itself is unusable on 2 of 4 backends. Fix: func=None sentinel resolved per backend (_calc_mean CPU, _focal_mean_cuda GPU), explicit-func behavior unchanged; same PR adds the missing name= param to the apply() docstring (signature has name='focal_apply'; mean/focal_stats/hotspots document theirs). (#3216, MEDIUM Cat 3, branch -02) hotspots() docstring lists 3 backends but dask_cupy_func=_hotspots_dask_cupy is dispatched and works; kernel param documented as binary ('values of 1 indicate the kernel') while hotspots accepts weighted kernels and the Gi* formula in the same docstring uses weights w_ij (apply/focal_stats reject non-binary via _validate_binary_kernel, hotspots deliberately does not). Docs-only fix. LOW documented, not fixed: among the 4 focal publics only mean() has @supports_dataset (Dataset-support drift; feature gap, not an API bug). Cross-cutting, notes only per template: emerging_hotspots(raster=), viewshed(raster=), calc_cellsize(raster) still use raster while focal standardized on agg with a DeprecationWarning shim (#2689/PR #2699); library-wide first-arg drift, belongs to those modules' sweeps. No Cat 1 in-module (agg canonical, raster alias warns, both-args raises). No Cat 2 return drift (mean/apply/hotspots 2D same-type, focal_stats 3D (stats,y,x) as documented). No Cat 5 orphan API (apply/focal_stats/hotspots documented in focal.rst autosummary and consumed via xrspatial.focal module path; only mean re-exported top-level; emerging_hotspots top-level vs hotspots module-level asymmetry noted, additive export would be a design call, not filed). cuda-validated: CUDA_AVAILABLE=True on this host; mean/apply/focal_stats/hotspots smoke-tested on cupy with kwarg parity; the apply default crash reproduced on GPU; hotspots weighted-kernel acceptance verified empirically." geotiff,2026-07-01,3593,MEDIUM,3,"Sweep 2026-07-01 (deep-sweep-api-consistency-geotiff-2026-07-01). 1 MEDIUM Cat 3 finding filed as #3593 and fixed on this branch: to_geotiff docstring's release-contract tier list put extra_tags pass-through under [advanced] (no kwarg gate) while SUPPORTED_FEATURES['writer.extra_tags']='experimental' and _validate_write_rich_tag_optin raises ValueError on a fresh DataArray with attrs['extra_tags'] unless allow_experimental_codecs=True (verified empirically). Fix moves the bullet to [experimental], names the opt-in and the round-trip exemption, and adds a drift-guard test in tests/unit/test_signatures.py. Clean elsewhere: signature/docstring param parity is exact on open_geotiff (28 params) and to_geotiff (24 params); deprecated aliases (name/mask_nodata/mask_and_scale, plot_geotiff) all emit DeprecationWarning; no default drift between to_geotiff and _write_geotiff_gpu (identical signatures minus gpu); no orphan API (examples/docs only import __all__ names); xarray backend declares open_dataset_parameters correctly. cuda-validated: CUDA_AVAILABLE=True; smoke-tested all 4 read paths (eager/dask/gpu/dask+gpu) and cupy write round-trip accept the same public kwargs. Cross-cutting, notes only: reproject/merge use chunk_size= and name= where geotiff/templates use chunks= (dask/xarray convention) and open_geotiff deprecated name= for default_name= (rioxarray parity); offender is reproject, out of per-module scope, not filed here." diff --git a/xrspatial/bump.py b/xrspatial/bump.py index c7518b461..a8c7a37da 100644 --- a/xrspatial/bump.py +++ b/xrspatial/bump.py @@ -163,7 +163,8 @@ def bump(width: int = None, height_func=None, spread: int = 1, *, - agg: xr.DataArray = None) -> xr.DataArray: + agg: xr.DataArray = None, + name: str = 'bump') -> xr.DataArray: """ Generate a simple bump map to simulate the appearance of land features. @@ -180,8 +181,9 @@ def bump(width: int = None, height : int, optional Total height, in pixels, of the image. Not required when ``agg`` is provided. - count : int - Number of bumps to generate. + count : int, optional + Number of bumps to generate. Defaults to ``width * height // 10`` + (capped at 10,000,000) when not provided. height_func : function which takes x, y and returns a height value Function used to apply varying bump heights to different elevations. @@ -191,6 +193,10 @@ def bump(width: int = None, Template raster whose shape, chunks, and backend (NumPy, CuPy, Dask, Dask+CuPy) determine the output type. When provided, ``width`` and ``height`` are inferred from ``agg.shape``. + name : str, default='bump' + Name of the output DataArray, for consistency with the other + synthetic-terrain generators (``perlin``, ``worley``, + ``generate_terrain``). Returns ------- @@ -401,7 +407,9 @@ def heights(locations, src, src_range, height = 20): return DataArray(out, coords=agg.coords, dims=agg.dims, - attrs=dict(res=1)) + attrs=dict(res=1), + name=name) else: bumps = _finish_bump(w, h, locs, heights, spread) - return DataArray(bumps, dims=['y', 'x'], attrs=dict(res=1)) + return DataArray(bumps, dims=['y', 'x'], attrs=dict(res=1), + name=name) diff --git a/xrspatial/tests/test_bump.py b/xrspatial/tests/test_bump.py index bb9857a32..be1343eb4 100644 --- a/xrspatial/tests/test_bump.py +++ b/xrspatial/tests/test_bump.py @@ -138,6 +138,19 @@ def test_bump_agg_infers_shape(): np.testing.assert_array_equal(result.values, result2.values) +def test_bump_names_output_like_siblings(): + """bump() names its output DataArray, consistent with perlin/worley/ + generate_terrain (API-consistency sweep 2026-07-02).""" + # default name + result = bump(10, 10) + assert result.name == 'bump' + + # custom name, both with and without agg + agg = xr.DataArray(np.zeros((10, 10)), dims=['y', 'x']) + assert bump(agg=agg, name='ridges').name == 'ridges' + assert bump(10, 10, name='ridges').name == 'ridges' + + def test_bump_decay_strongest_at_center_1102(): """Pixels adjacent to center should be taller than pixels far from center.