Skip to content

Commit 9cca00b

Browse files
committed
Update README benchmarks and enable all backend write support
README: - Updated feature matrix: write_geotiff now shows Dask ✅ and CuPy 🔄 (fallback). Added write_geotiff_gpu and read_geotiff_dask rows. Updated VRT to show Dask support. - Added comprehensive benchmark tables for read (real-world + synthetic) and write (CPU vs GPU vs rioxarray) across all sizes and codecs. - 100% consistency verified across all tested files. Backend support for write_geotiff: - NumPy: direct write (existing) - Dask DataArray: .compute() then write (existing, now documented) - CuPy raw array: .get() to numpy then write (new) - CuPy DataArray: .data.get() then write (new) - Dask+CuPy: .compute().get() then write (new) - Python list: np.asarray() then write (existing) For GPU-native compression (no CPU transfer), use write_geotiff_gpu.
1 parent b1ed372 commit 9cca00b

File tree

2 files changed

+51
-13
lines changed

2 files changed

+51
-13
lines changed

README.md

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,17 @@ Native GeoTIFF and Cloud Optimized GeoTIFF reader/writer. No GDAL required.
139139

140140
| Name | Description | NumPy | Dask | CuPy GPU | Cloud |
141141
|:-----|:------------|:-----:|:----:|:--------:|:-----:|
142-
| [read_geotiff](xrspatial/geotiff/__init__.py) | Read GeoTIFF / COG to DataArray | ✅️ | ✅️ | ✅️ | ✅️ |
143-
| [write_geotiff](xrspatial/geotiff/__init__.py) | Write DataArray as GeoTIFF / COG | ✅️ | | | ✅️ |
144-
| [read_geotiff_gpu](xrspatial/geotiff/__init__.py) | GPU-accelerated read (nvCOMP + GDS) | | | ✅️ | |
145-
| [read_vrt / write_vrt](xrspatial/geotiff/__init__.py) | Virtual Raster Table mosaic | ✅️ | | | |
146-
| [open_cog](xrspatial/geotiff/__init__.py) | HTTP range-request COG reader | ✅️ | | | |
142+
| [read_geotiff](xrspatial/geotiff/__init__.py) | Read GeoTIFF / COG / VRT to DataArray | ✅️ | ✅️ | ✅️ | ✅️ |
143+
| [write_geotiff](xrspatial/geotiff/__init__.py) | Write DataArray as GeoTIFF / COG | ✅️ | ✅️ | 🔄 | ✅️ |
144+
| [read_geotiff_gpu](xrspatial/geotiff/__init__.py) | GPU-native read (nvCOMP + GDS) | | | ✅️ | |
145+
| [write_geotiff_gpu](xrspatial/geotiff/__init__.py) | GPU-native write (nvCOMP batch compress) | | | ✅️ | |
146+
| [read_geotiff_dask](xrspatial/geotiff/__init__.py) | Dask lazy read via windowed chunks | | ✅️ | | |
147+
| [read_vrt / write_vrt](xrspatial/geotiff/__init__.py) | Virtual Raster Table mosaic | ✅️ | ✅️ | | |
148+
| [open_cog](xrspatial/geotiff/__init__.py) | HTTP range-request COG reader | ✅️ | | | ✅️ |
147149

148150
**Compression codecs:** Deflate, LZW (Numba JIT), ZSTD, PackBits, JPEG (Pillow), uncompressed
149151

150-
**GPU decompression:** Deflate and ZSTD via nvCOMP batch API; LZW via Numba CUDA kernels
152+
**GPU codecs:** Deflate and ZSTD via nvCOMP batch API; LZW via Numba CUDA kernels
151153

152154
**Features:**
153155
- Tiled, stripped, BigTIFF, multi-band (RGB/RGBA), sub-byte (1/2/4/12-bit)
@@ -160,12 +162,36 @@ Native GeoTIFF and Cloud Optimized GeoTIFF reader/writer. No GDAL required.
160162
- Overview generation: mean, nearest, min, max, median, mode, cubic
161163
- Planar config, big-endian byte swap, PixelIsArea/PixelIsPoint
162164

163-
**GPU read performance** (read + slope, A6000, nvCOMP):
165+
**Read performance** (real-world files, A6000 GPU):
164166

165-
| Size | Deflate GPU | Deflate CPU | Speedup |
166-
|:-----|:-----------:|:-----------:|:-------:|
167-
| 8192x8192 | 769ms | 1364ms | 1.8x |
168-
| 16384x16384 | 2417ms | 5788ms | 2.4x |
167+
| File | Format | xrspatial CPU | rioxarray | GPU (nvCOMP) |
168+
|:-----|:-------|:------------:|:---------:|:------------:|
169+
| render_demo 187x253 | uncompressed | **0.2ms** | 2.4ms | 0.7ms |
170+
| Landsat B4 1310x1093 | uncompressed | **1.0ms** | 6.0ms | 1.7ms |
171+
| Copernicus 3600x3600 | deflate+fp3 | 241ms | 195ms | 872ms |
172+
| USGS 1as 3612x3612 | LZW+fp3 | 275ms | 215ms | 747ms |
173+
| USGS 1m 10012x10012 | LZW | **1.25s** | 1.80s | **990ms** |
174+
175+
**Read performance** (synthetic tiled, GPU shines at scale):
176+
177+
| Size | Codec | xrspatial CPU | rioxarray | GPU (nvCOMP) |
178+
|:-----|:------|:------------:|:---------:|:------------:|
179+
| 4096x4096 | deflate | 265ms | 211ms | **158ms** |
180+
| 4096x4096 | zstd | **73ms** | 159ms | **58ms** |
181+
| 8192x8192 | deflate | 1.06s | 859ms | **565ms** |
182+
| 8192x8192 | zstd | **288ms** | 668ms | **171ms** |
183+
184+
**Write performance** (synthetic tiled):
185+
186+
| Size | Codec | xrspatial CPU | rioxarray | GPU (nvCOMP) |
187+
|:-----|:------|:------------:|:---------:|:------------:|
188+
| 2048x2048 | deflate | 424ms | 110ms | **135ms** |
189+
| 2048x2048 | zstd | 49ms | 83ms | 81ms |
190+
| 4096x4096 | deflate | 1.68s | 447ms | **302ms** |
191+
| 8192x8192 | deflate | 6.84s | 2.03s | **1.11s** |
192+
| 8192x8192 | zstd | 847ms | 822ms | 1.03s |
193+
194+
**Consistency:** 100% pixel-exact match vs rioxarray on all tested files (Landsat 8, Copernicus DEM, USGS 1-arc-second, USGS 1-meter).
169195

170196
-----------
171197
### **Reproject / Merge**

xrspatial/geotiff/__init__.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,16 @@ def write_geotiff(data: xr.DataArray | np.ndarray, path: str, *,
304304
epsg = _wkt_to_epsg(crs) # try to extract EPSG from WKT/PROJ
305305

306306
if isinstance(data, xr.DataArray):
307-
arr = data.values
307+
# Handle CuPy-backed DataArrays: convert to numpy for CPU write
308+
raw = data.data
309+
if hasattr(raw, 'get'):
310+
arr = raw.get() # CuPy -> numpy
311+
elif hasattr(raw, 'compute'):
312+
arr = raw.compute() # Dask -> numpy
313+
if hasattr(arr, 'get'):
314+
arr = arr.get() # Dask+CuPy -> numpy
315+
else:
316+
arr = np.asarray(raw)
308317
# Handle band-first dimension order (band, y, x) -> (y, x, band)
309318
if arr.ndim == 3 and data.dims[0] in ('band', 'bands', 'channel'):
310319
arr = np.moveaxis(arr, 0, -1)
@@ -338,7 +347,10 @@ def write_geotiff(data: xr.DataArray | np.ndarray, path: str, *,
338347
_unit_ids = {'none': 1, 'inch': 2, 'centimeter': 3}
339348
res_unit = _unit_ids.get(str(unit_str), None)
340349
else:
341-
arr = np.asarray(data)
350+
if hasattr(data, 'get'):
351+
arr = data.get() # CuPy -> numpy
352+
else:
353+
arr = np.asarray(data)
342354

343355
if arr.ndim not in (2, 3):
344356
raise ValueError(f"Expected 2D or 3D array, got {arr.ndim}D")

0 commit comments

Comments
 (0)