Skip to content

focal: mean() dtype differs by backend; apply()/focal_stats() dask meta advertises float64 but computes float32 #3217

@brendancol

Description

@brendancol

Describe the bug

Two related dtype problems in xrspatial/focal.py:

  1. mean() returns a different dtype depending on the backend. The function casts input to float64 (agg.data.astype(float)) before dispatch, and the numpy and dask+numpy paths keep that. But _mean_cupy re-casts to float32 (cupy.asarray(data, dtype=cupy.float32)) and _mean_dask_cupy does data.astype(cupy.float32), so the GPU backends silently downcast float64 input and lose precision. _mean_dask_cupy also builds its graph with an untyped meta=cupy.array(()), so the lazy DataArray advertises float64 while .compute() returns float32.

  2. apply() and focal_stats() advertise the wrong dtype on the dask backends. Preserve input float dtype in apply() and focal_stats() (#2769) #2805 made the chunk functions preserve the input float dtype via _promote_float, but the map_overlap calls in _apply_dask_numpy, _apply_dask_cupy, and _focal_stats_dask_cupy still pass an untyped meta (np.array(()) / cupy.array(())), which defaults to float64. For float32 or integer input, the lazy result claims float64 but computes to float32.

Measured on this host (all four backends live):

mean   in=f8 numpy       advertised=float64 computed=float64
mean   in=f8 cupy        advertised=float32 computed=float32
mean   in=f8 dask+numpy  advertised=float64 computed=float64
mean   in=f8 dask+cupy   advertised=float64 computed=float32   <- mismatch
apply  in=f4 dask+numpy  advertised=float64 computed=float32   <- mismatch
apply  in=i4 dask+cupy   advertised=float64 computed=float32   <- mismatch
fstats in=f4 dask+numpy  advertised=float64 computed=float32   <- mismatch

Expected behavior

  • mean() returns float64 on all four backends, matching the CPU contract and the docstring examples, with no precision loss on the GPU paths.
  • The dask graphs for apply() and focal_stats() advertise the dtype they actually compute.

Same class of bug as #2682 (aspect) and #2723 (proximity), which were fixed by passing a typed meta/dtype to the dask graph constructors.

Additional context

Found by the metadata propagation sweep against focal. attrs, coords, dims, and .name checked clean across all four backends on the same run.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdaskDask backend / chunked arraysgpuCuPy / CUDA GPU supportseverity:mediumSweep finding: MEDIUMsweep-metadataFound by /sweep-metadata

    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