Describe the bug
hotspots() in xrspatial/focal.py skips the kernel validation that apply() and focal_stats() both run. Those two call custom_kernel(kernel) before touching the kernel. hotspots() doesn't. It goes straight to _check_kernel_vs_raster_memory(kernel, ...) near line 1509, which reads kernel.shape directly.
So an invalid kernel either crashes with a confusing AttributeError or silently produces wrong output, depending on what you pass.
Failure modes (numpy backend)
hotspots(agg, kernel=None) raises AttributeError: 'NoneType' object has no attribute 'shape' instead of a clear validation error.
hotspots(agg, kernel=[[1, 1, 1]]) raises AttributeError: 'list' object has no attribute 'shape'. A list should get rejected with the same "not a Numpy array" message custom_kernel raises.
hotspots(agg, kernel=np.ones((2, 2))) succeeds, even though even-dimensioned kernels are rejected by custom_kernel everywhere else.
- A zero-sum kernel (e.g. a Laplacian) divides by zero at line 1269:
convolve_2d(data, kernel / kernel.sum(), boundary). It emits RuntimeWarnings and returns all-zero output instead of raising.
Expected behavior
hotspots() should validate its kernel the way apply() and focal_stats() do: route through custom_kernel() so None, lists, and even-dimensioned kernels raise clear, consistent ValueErrors. Zero-sum kernels should raise an explicit error rather than dividing by zero.
Additional context
custom_kernel() lives in xrspatial/convolution.py. It checks ndarray type and odd dimensions, but not the kernel sum. The zero-sum case is specific to hotspots() since it's the only focal function that divides by kernel.sum().
Describe the bug
hotspots()inxrspatial/focal.pyskips the kernel validation thatapply()andfocal_stats()both run. Those two callcustom_kernel(kernel)before touching the kernel.hotspots()doesn't. It goes straight to_check_kernel_vs_raster_memory(kernel, ...)near line 1509, which readskernel.shapedirectly.So an invalid kernel either crashes with a confusing
AttributeErroror silently produces wrong output, depending on what you pass.Failure modes (numpy backend)
hotspots(agg, kernel=None)raisesAttributeError: 'NoneType' object has no attribute 'shape'instead of a clear validation error.hotspots(agg, kernel=[[1, 1, 1]])raisesAttributeError: 'list' object has no attribute 'shape'. A list should get rejected with the same "not a Numpy array" messagecustom_kernelraises.hotspots(agg, kernel=np.ones((2, 2)))succeeds, even though even-dimensioned kernels are rejected bycustom_kerneleverywhere else.convolve_2d(data, kernel / kernel.sum(), boundary). It emits RuntimeWarnings and returns all-zero output instead of raising.Expected behavior
hotspots()should validate its kernel the wayapply()andfocal_stats()do: route throughcustom_kernel()soNone, lists, and even-dimensioned kernels raise clear, consistentValueErrors. Zero-sum kernels should raise an explicit error rather than dividing by zero.Additional context
custom_kernel()lives inxrspatial/convolution.py. It checks ndarray type and odd dimensions, but not the kernel sum. The zero-sum case is specific tohotspots()since it's the only focal function that divides bykernel.sum().