Summary
to_geotiff rejects compression='jpeg' across every dispatch path (CPU, GPU auto-detect, and gpu=True), but write_geotiff_gpu accepts it and writes a working JPEG file. Since to_geotiff(..., gpu=True) is documented as calling write_geotiff_gpu internally, the two entry points should agree on what compression='jpeg' does.
Reproducer
import tempfile, os, numpy as np, xarray as xr, cupy
import xrspatial.geotiff as geo
arr = np.random.randint(0, 255, (256, 256), dtype=np.uint8)
da_gpu = xr.DataArray(cupy.asarray(arr), dims=['y', 'x'])
with tempfile.TemporaryDirectory() as td:
# to_geotiff(gpu=True, jpeg) raises ValueError
try:
geo.to_geotiff(da_gpu, os.path.join(td, 'a.tif'), compression='jpeg')
except ValueError as e:
print('to_geotiff(gpu_data, jpeg):', e) # rejected
# write_geotiff_gpu(jpeg) succeeds and produces a readable file
p = os.path.join(td, 'b.tif')
geo.write_geotiff_gpu(da_gpu, p, compression='jpeg')
print('write_geotiff_gpu(gpu_data, jpeg): size=', os.path.getsize(p))
# round-trips through xrspatial, PIL, and rasterio
The rejection message on to_geotiff says the encoder omits the JPEGTables tag (347) so libtiff/GDAL/rasterio reject the file. In practice the bytes produced by both the CPU _writer.write(compression='jpeg', ...) path and write_geotiff_gpu(compression='jpeg', ...) round-trip through xrspatial, PIL, and rasterio without errors (per-tile JFIF streams are self-contained). The rejection in to_geotiff may be over-cautious.
Severity
HIGH (Cat 5, public API surface drift). write_geotiff_gpu is the documented explicit entry point; users who hit the to_geotiff(gpu=True) rejection cannot rely on the documented equivalence.
Fix options
Pick one:
- Drop the JPEG rejection in
to_geotiff. Treat JPEG like any other codec and let it pass through to the GPU/CPU writer. Verify the round-trip in CI on multiple readers (libtiff, GDAL, rasterio, PIL).
- Add the JPEG rejection to
write_geotiff_gpu. Mirror the upfront ValueError on the GPU writer so both entry points fail identically. Document JPEG as "use a different codec" in both docstrings.
- Document the per-tile JFIF caveat. Accept JPEG in both writers, but warn (
warnings.warn) when the file is missing JPEGTables and may not round-trip through readers that require it.
Option 2 is the most conservative; option 3 surfaces the trade-off without restricting users.
Deprecation impact
None. Whichever option lands, both entry points end up with the same JPEG acceptance contract. No kwarg rename.
Found by /sweep-api-consistency on 2026-05-11.
Summary
to_geotiffrejectscompression='jpeg'across every dispatch path (CPU, GPU auto-detect, andgpu=True), butwrite_geotiff_gpuaccepts it and writes a working JPEG file. Sinceto_geotiff(..., gpu=True)is documented as callingwrite_geotiff_gpuinternally, the two entry points should agree on whatcompression='jpeg'does.Reproducer
The rejection message on
to_geotiffsays the encoder omits the JPEGTables tag (347) so libtiff/GDAL/rasterio reject the file. In practice the bytes produced by both the CPU_writer.write(compression='jpeg', ...)path andwrite_geotiff_gpu(compression='jpeg', ...)round-trip through xrspatial, PIL, and rasterio without errors (per-tile JFIF streams are self-contained). The rejection into_geotiffmay be over-cautious.Severity
HIGH (Cat 5, public API surface drift).
write_geotiff_gpuis the documented explicit entry point; users who hit theto_geotiff(gpu=True)rejection cannot rely on the documented equivalence.Fix options
Pick one:
to_geotiff. Treat JPEG like any other codec and let it pass through to the GPU/CPU writer. Verify the round-trip in CI on multiple readers (libtiff, GDAL, rasterio, PIL).write_geotiff_gpu. Mirror the upfrontValueErroron the GPU writer so both entry points fail identically. Document JPEG as "use a different codec" in both docstrings.warnings.warn) when the file is missing JPEGTables and may not round-trip through readers that require it.Option 2 is the most conservative; option 3 surfaces the trade-off without restricting users.
Deprecation impact
None. Whichever option lands, both entry points end up with the same JPEG acceptance contract. No kwarg rename.
Found by
/sweep-api-consistencyon 2026-05-11.