Describe the bug
morph_erode and morph_dilate seed the running min/max from the centre cell before the kernel loop runs. When the kernel has a 0 at the centre (the centre is excluded from the structuring element footprint), the loop correctly skips the centre cell, but the seed already came from it. The centre value contaminates the result.
Affects all four backends:
_erode_kernel_numpy (xrspatial/morphology.py:133-159), line 145 seeds mn = val
_dilate_kernel_numpy (xrspatial/morphology.py:162-188), line 174 seeds mx = val
_erode_gpu (xrspatial/morphology.py:230-253), line 240
_dilate_gpu (xrspatial/morphology.py:256-279), line 266
Reproducer
import numpy as np, xarray as xr
from xrspatial.morphology import morph_erode, morph_dilate
# Cross-with-hole kernel: centre excluded
kernel = np.array([[1,1,1],[1,0,1],[1,1,1]], dtype=np.uint8)
# Erosion: centre is the local minimum, neighbours are all 5
data = np.full((5, 5), 5.0)
data[2, 2] = 1.0
agg = xr.DataArray(data)
print(morph_erode(agg, kernel=kernel, boundary='nearest').data[2, 2])
# Expected: 5.0 (centre excluded; all 8 neighbours are 5.0)
# Actual: 1.0 (centre value leaked in)
# Dilation: centre is the local maximum
data2 = np.full((5, 5), 5.0)
data2[2, 2] = 100.0
agg2 = xr.DataArray(data2)
print(morph_dilate(agg2, kernel=kernel, boundary='nearest').data[2, 2])
# Expected: 5.0
# Actual: 100.0
Expected behavior
When kernel[centre] == 0, the centre cell should not influence the output. Only kernel-included neighbours should contribute to the local min/max.
Fix
Skip the centre seed. Assign from the first kernel-included neighbour, then compare against subsequent ones. Keep existing NaN-propagation semantics.
Categories
Off-by-one / structuring-element semantics. Affects numpy, cupy, dask+numpy, dask+cupy backends.
Describe the bug
morph_erodeandmorph_dilateseed the running min/max from the centre cell before the kernel loop runs. When the kernel has a 0 at the centre (the centre is excluded from the structuring element footprint), the loop correctly skips the centre cell, but the seed already came from it. The centre value contaminates the result.Affects all four backends:
_erode_kernel_numpy(xrspatial/morphology.py:133-159), line 145 seedsmn = val_dilate_kernel_numpy(xrspatial/morphology.py:162-188), line 174 seedsmx = val_erode_gpu(xrspatial/morphology.py:230-253), line 240_dilate_gpu(xrspatial/morphology.py:256-279), line 266Reproducer
Expected behavior
When
kernel[centre] == 0, the centre cell should not influence the output. Only kernel-included neighbours should contribute to the local min/max.Fix
Skip the centre seed. Assign from the first kernel-included neighbour, then compare against subsequent ones. Keep existing NaN-propagation semantics.
Categories
Off-by-one / structuring-element semantics. Affects numpy, cupy, dask+numpy, dask+cupy backends.