Describe the bug
The CUDA kernels used by focal_stats() propagate NaN through arithmetic, while the numpy path uses nan-safe functions (np.nanmean, np.nansum, np.nanstd, np.nanvar). This means the same input data gives different results depending on the backend when NaN values are present.
Affected kernels: _focal_mean_cuda, _focal_sum_cuda, _focal_std_cuda, _focal_var_cuda, _focal_range_cuda.
The non-focal mean() function handles this correctly -- its _mean_gpu kernel has explicit if not isnan(neighbor_val) checks. The focal_stats kernels just lack the same treatment.
Expected behavior
All backends should skip NaN neighbors in the kernel window, matching the numpy nan-safe behavior. A pixel surrounded by [1, NaN, 3] should compute focal_mean = 2.0, not NaN.
Reproduction
import numpy as np
import xarray as xr
from xrspatial.focal import focal_stats
from xrspatial.convolution import circle_kernel
data = np.array([[1.0, np.nan, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0]])
kernel = circle_kernel(1, 1, 1)
# numpy: center pixel (1,1) focal_mean skips NaN -> correct
result_np = focal_stats(xr.DataArray(data), kernel, stats_funcs=['mean'])
print(result_np.sel(stats='mean').values[1, 1]) # 4.6 (nanmean of [nan,4,5,6,8])
# cupy: center pixel (1,1) focal_mean propagates NaN -> wrong
import cupy
result_cu = focal_stats(xr.DataArray(cupy.asarray(data)), kernel, stats_funcs=['mean'])
print(result_cu.sel(stats='mean').values[1, 1]) # NaN
Affected code
xrspatial/focal.py: _focal_mean_cuda, _focal_sum_cuda, _focal_std_cuda, _focal_var_cuda, _focal_range_cuda
Describe the bug
The CUDA kernels used by
focal_stats()propagate NaN through arithmetic, while the numpy path uses nan-safe functions (np.nanmean,np.nansum,np.nanstd,np.nanvar). This means the same input data gives different results depending on the backend when NaN values are present.Affected kernels:
_focal_mean_cuda,_focal_sum_cuda,_focal_std_cuda,_focal_var_cuda,_focal_range_cuda.The non-focal
mean()function handles this correctly -- its_mean_gpukernel has explicitif not isnan(neighbor_val)checks. The focal_stats kernels just lack the same treatment.Expected behavior
All backends should skip NaN neighbors in the kernel window, matching the numpy nan-safe behavior. A pixel surrounded by
[1, NaN, 3]should computefocal_mean = 2.0, notNaN.Reproduction
Affected code
xrspatial/focal.py:_focal_mean_cuda,_focal_sum_cuda,_focal_std_cuda,_focal_var_cuda,_focal_range_cuda