Describe the bug
hotspots() returns an all-zeros ("no significance") raster for degenerate dask inputs, while the NumPy and CuPy backends raise an error on the same input.
The NumPy and CuPy paths validate the global Gi* terms through _gistar_global_stats, which raises ValueError when there are fewer than 2 valid (non-NaN) cells, and ZeroDivisionError when the global standard deviation is 0 (a constant raster). The dask paths (_hotspots_dask_numpy, _hotspots_dask_cupy) keep the global mean, std, and valid-cell count as lazy 0-d dask arrays and never run that validation. _gistar_zscore maps every non-finite z-score to 0, so a constant raster, an all-NaN raster, or a raster with a single valid cell computes to an all-zeros result that looks valid instead of failing.
So the same input errors on NumPy/CuPy but quietly answers "no hotspots" on dask. A downstream user gets a plausible-looking statistic with no hint that the input was degenerate.
To Reproduce
import numpy as np
import xarray as xr
import dask.array as da
from xrspatial.focal import hotspots
kernel = np.ones((3, 3))
data = np.zeros((10, 10)) # constant raster -> global std == 0
# NumPy raises ZeroDivisionError
hotspots(xr.DataArray(data), kernel)
# dask quietly returns all zeros
hotspots(xr.DataArray(da.from_array(data, chunks=(5, 5))), kernel).compute()
Expected behavior
The dask backends should signal the degenerate input the same way NumPy does instead of returning a silent all-zeros raster. The dask path is lazy by design (issue #2772), so the validation should fire at compute time, not when the graph is built.
Additional context
- File:
xrspatial/focal.py
- NumPy validation:
_gistar_global_stats
- Dask paths missing it:
_hotspots_dask_numpy, _hotspots_dask_cupy
- Affected backends: dask+numpy, dask+cupy
Describe the bug
hotspots()returns an all-zeros ("no significance") raster for degenerate dask inputs, while the NumPy and CuPy backends raise an error on the same input.The NumPy and CuPy paths validate the global Gi* terms through
_gistar_global_stats, which raisesValueErrorwhen there are fewer than 2 valid (non-NaN) cells, andZeroDivisionErrorwhen the global standard deviation is 0 (a constant raster). The dask paths (_hotspots_dask_numpy,_hotspots_dask_cupy) keep the global mean, std, and valid-cell count as lazy 0-d dask arrays and never run that validation._gistar_zscoremaps every non-finite z-score to 0, so a constant raster, an all-NaN raster, or a raster with a single valid cell computes to an all-zeros result that looks valid instead of failing.So the same input errors on NumPy/CuPy but quietly answers "no hotspots" on dask. A downstream user gets a plausible-looking statistic with no hint that the input was degenerate.
To Reproduce
Expected behavior
The dask backends should signal the degenerate input the same way NumPy does instead of returning a silent all-zeros raster. The dask path is lazy by design (issue #2772), so the validation should fire at compute time, not when the graph is built.
Additional context
xrspatial/focal.py_gistar_global_stats_hotspots_dask_numpy,_hotspots_dask_cupy