Reason
Surfaced by Copilot review on #1895. Two pre-existing parity gaps between the GDS chunked GPU read path and the eager GPU read path.
Bool band acceptance. _read_geotiff_gpu_chunked_gds in xrspatial/geotiff/_backends/gpu.py validates band with a numeric range check (band < 0 or band >= n_bands_out). Because bool is an int subclass, band=True on a multi-band file passes this check and silently selects band 1. The eager GPU path (read_geotiff_gpu) and the dask path (#1786) both reject bool / np.bool_ up front. The GDS chunked path should match.
LERC masked_fill dropped. _decode_window_gpu_direct calls gpu_decode_tiles_from_file and gpu_decode_tiles without forwarding masked_fill. The eager GPU path computes masked_fill = _resolve_masked_fill(ifd.nodata_str, file_dtype) if compression == COMPRESSION_LERC else None and threads it into both kernels (see the equivalent block in the eager body). On a LERC-compressed file with a per-pixel valid mask, the chunked path leaves invalid pixels at LERC's zero fill instead of restoring the nodata sentinel before _apply_nodata_mask_gpu runs, producing silently wrong pixel values vs the eager path.
Proposal
Mirror the eager-GPU validation and masked_fill into _read_geotiff_gpu_chunked_gds / _decode_window_gpu_direct:
- Reject
band with isinstance(band, (bool, np.bool_)) before the numeric range check.
- Resolve
masked_fill once in _read_geotiff_gpu_chunked_gds (if compression == COMPRESSION_LERC) and forward it through _decode_window_gpu_direct to both kernels.
A parity-matrix entry for LERC + chunks on the same multi-band fixture would catch both regressions automatically.
Stakeholders and Impacts
Callers using read_geotiff_gpu(path, chunks=...) against LERC COGs or with band=True. Public signatures unchanged.
Refs #1813, #1895, #1786.
Reason
Surfaced by Copilot review on #1895. Two pre-existing parity gaps between the GDS chunked GPU read path and the eager GPU read path.
Bool band acceptance.
_read_geotiff_gpu_chunked_gdsinxrspatial/geotiff/_backends/gpu.pyvalidatesbandwith a numeric range check (band < 0 or band >= n_bands_out). Becauseboolis anintsubclass,band=Trueon a multi-band file passes this check and silently selects band 1. The eager GPU path (read_geotiff_gpu) and the dask path (#1786) both rejectbool/np.bool_up front. The GDS chunked path should match.LERC
masked_filldropped._decode_window_gpu_directcallsgpu_decode_tiles_from_fileandgpu_decode_tileswithout forwardingmasked_fill. The eager GPU path computesmasked_fill = _resolve_masked_fill(ifd.nodata_str, file_dtype) if compression == COMPRESSION_LERC else Noneand threads it into both kernels (see the equivalent block in the eager body). On a LERC-compressed file with a per-pixel valid mask, the chunked path leaves invalid pixels at LERC's zero fill instead of restoring the nodata sentinel before_apply_nodata_mask_gpuruns, producing silently wrong pixel values vs the eager path.Proposal
Mirror the eager-GPU validation and masked_fill into
_read_geotiff_gpu_chunked_gds/_decode_window_gpu_direct:bandwithisinstance(band, (bool, np.bool_))before the numeric range check.masked_fillonce in_read_geotiff_gpu_chunked_gds(if compression == COMPRESSION_LERC) and forward it through_decode_window_gpu_directto both kernels.A parity-matrix entry for LERC + chunks on the same multi-band fixture would catch both regressions automatically.
Stakeholders and Impacts
Callers using
read_geotiff_gpu(path, chunks=...)against LERC COGs or withband=True. Public signatures unchanged.Refs #1813, #1895, #1786.