to_geotiff(cog=True, overview_resampling='cubic', nodata=<finite>) produces overview pixels with bad ringing artefacts near nodata regions on float rasters.
Same root cause as #1613, but for the cubic branch of _block_reduce_2d. The writer rewrites NaN to the finite sentinel before reduction. Then _block_reduce_2d(method='cubic') ignores the nodata argument and hands the sentinel-poisoned array straight to scipy.ndimage.zoom(order=3). The cubic spline blends the sentinel into neighbouring cells.
Repro:
import numpy as np, xarray as xr, tempfile, os
from xrspatial.geotiff import to_geotiff, open_geotiff
arr = np.ones((16, 16), dtype=np.float32) * 100.0
arr[:4, :4] = np.nan
da = xr.DataArray(arr, dims=['y', 'x'])
with tempfile.TemporaryDirectory() as tmp:
path = os.path.join(tmp, 'cog_cubic.tif')
to_geotiff(da, path, compression='deflate', tile_size=8, tiled=True,
cog=True, nodata=-9999.0, overview_levels=[1],
overview_resampling='cubic')
ov = open_geotiff(path, overview_level=1)
print(ov.values)
The overview should hold only 100.0, NaN at the masked pixels, and the sentinel. Instead bordering cells read 1133.44, -10290.08, 177.26, 104.70.
The mean, min, max, and median methods already handle this via the nan* reducers after a sentinel-to-NaN mask (#1613). nearest and mode are fine on their own semantics. Only cubic is broken.
Scope:
- CPU:
_block_reduce_2d cubic branch at xrspatial/geotiff/_writer.py:164-171.
- GPU:
_block_reduce_2d_gpu does not implement cubic and raises ValueError. The GPU helper can defer to the CPU cubic path the same way it already does for mode.
Severity HIGH (Cat 2 NaN propagation + Cat 5 backend divergence). The failure mode is quiet: a downstream consumer reading overview tiles for preview sees the ringing as real data.
Found by deep-sweep-accuracy 2026-05-11.
to_geotiff(cog=True, overview_resampling='cubic', nodata=<finite>)produces overview pixels with bad ringing artefacts near nodata regions on float rasters.Same root cause as #1613, but for the
cubicbranch of_block_reduce_2d. The writer rewrites NaN to the finite sentinel before reduction. Then_block_reduce_2d(method='cubic')ignores thenodataargument and hands the sentinel-poisoned array straight toscipy.ndimage.zoom(order=3). The cubic spline blends the sentinel into neighbouring cells.Repro:
The overview should hold only
100.0, NaN at the masked pixels, and the sentinel. Instead bordering cells read1133.44,-10290.08,177.26,104.70.The
mean,min,max, andmedianmethods already handle this via thenan*reducers after a sentinel-to-NaN mask (#1613).nearestandmodeare fine on their own semantics. Onlycubicis broken.Scope:
_block_reduce_2dcubic branch atxrspatial/geotiff/_writer.py:164-171._block_reduce_2d_gpudoes not implementcubicand raisesValueError. The GPU helper can defer to the CPU cubic path the same way it already does formode.Severity HIGH (Cat 2 NaN propagation + Cat 5 backend divergence). The failure mode is quiet: a downstream consumer reading overview tiles for preview sees the ringing as real data.
Found by deep-sweep-accuracy 2026-05-11.