Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/sweep-api-consistency-state.csv
Original file line number Diff line number Diff line change
@@ -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."
Expand Down
18 changes: 13 additions & 5 deletions xrspatial/bump.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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
-------
Expand Down Expand Up @@ -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)
13 changes: 13 additions & 0 deletions xrspatial/tests/test_bump.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Loading