Skip to content

focal_stats CUDA kernels propagate NaN instead of skipping it #1092

@brendancol

Description

@brendancol

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfocal toolsFocal statistics and hotspot analysisgpuCuPy / CUDA GPU support

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions