Skip to content

Add focal variety statistic (count of distinct values in window) #1040

@brendancol

Description

@brendancol

Author of Proposal: brendan

Reason or problem

focal_stats supports mean, max, min, range, std, var, and sum, but not variety (count of distinct values in a neighborhood window). Variety matters for categorical rasters like land-use or land-cover grids, where you want to know how many different classes fall within a given window.

Proposal

Add a variety statistic to focal_stats.

Design:

  • CPU: a _calc_variety function with @ngjit. Takes the kernel-masked neighborhood values, drops NaNs, counts unique values. The existing _apply_numpy / _apply_dask_numpy machinery handles windowing and dispatch.
  • GPU: a _focal_variety_cuda kernel with @cuda.jit. Iterates over the neighborhood and counts distinct values using a small local buffer (kernel sizes are typically small; a fixed-size buffer of 25 slots works). Wire it into _stats_cupy_mapper and _stats_cuda_mapper.
  • Dask: no extra work. The CPU/GPU paths are already wrapped by map_overlap through the existing dispatch in _focal_stats_cpu and _focal_stats_dask_cupy.
  • Output type: float32 (like other stats). NaN means no valid neighbors.

Usage:

from xrspatial.focal import focal_stats
from xrspatial.convolution import circle_kernel

kernel = circle_kernel(1, 1, 1)
result = focal_stats(land_cover, kernel, stats_funcs=['variety'])
# result shape: (1, rows, cols), values are integer counts cast to float32

Value: Variety is a standard focal statistic in ArcGIS and QGIS. Adding it lets users do categorical raster analysis without writing custom kernels.

Stakeholders and impacts

Users working with land-cover or other classified rasters. The change is additive (new stat name in the dispatch dicts) and doesn't touch existing behavior.

Drawbacks

Variety on continuous floating-point data can produce misleading results (every pixel may be unique). This matches how ArcGIS handles it, so no special guard is needed beyond documentation.

Alternatives

Users could write a custom @ngjit function and pass it to focal.apply(), but that gives no GPU support and no way to request it by name in focal_stats.

Unresolved questions

None.

Additional notes

range was mentioned in the original request but already exists in focal_stats.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestfocal toolsFocal statistics and hotspot analysis

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions