Skip to content

focal mean/apply/focal_stats ignore boundary on the cupy backend #2730

@brendancol

Description

@brendancol

Description

mean(), apply(), and focal_stats() ignore the boundary parameter on
the single-GPU CuPy backend. The numpy and dask backends honor
boundary='nearest', 'reflect', and 'wrap', but the CuPy paths always
behave as boundary='nan' (edge clamping).

The CuPy dispatch functions in xrspatial/focal.py never receive the
boundary argument:

  • mean() -> cupy_func=_mean_cupy
  • apply() -> cupy_func=_apply_cupy
  • focal_stats() -> cupy_func=_focal_stats_cupy

Each is registered in the ArrayTypeFunctionMapping without a boundary
partial, unlike the matching numpy and dask entries. The GPU kernels clamp
the neighbourhood window to the raster edge, which matches boundary='nan'
semantics no matter what the caller passed.

Reproduce

import numpy as np, xarray as xr, cupy
from xrspatial import mean

data = np.random.default_rng(0).random((6, 6))
np_agg = xr.DataArray(data, dims=['y', 'x'])
cp_agg = xr.DataArray(cupy.asarray(data), dims=['y', 'x'])

for b in ['nan', 'nearest', 'reflect', 'wrap']:
    n = mean(np_agg, boundary=b).data
    c = mean(cp_agg, boundary=b).data.get()
    print(b, np.allclose(n, c, equal_nan=True, rtol=1e-4))

Output:

nan True
nearest False
reflect False
wrap False

The same divergence occurs for apply() and focal_stats().

Expected

For a given boundary value, all four backends (numpy, cupy, dask+numpy,
dask+cupy) should produce matching results on identical input.

Fix

Pad the CuPy input per the boundary mode (reuse _pad_array) before the GPU
kernel and trim the result, mirroring the numpy boundary path in
_mean_numpy_boundary and _apply_numpy_boundary.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggpuCuPy / CUDA GPU support

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions