Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .claude/sweep-error-handling-state.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module,last_inspected,issue,severity_max,categories_found,notes
geotiff,2026-07-02,3604,MEDIUM,2;4,"to_geotiff 0D/1D DataArray raised opaque IndexError from _coords.py coords_to_transform (dims[-2]) instead of clean 'Expected 2D or 3D' ValueError; numpy path + 4D DataArray already clean. Fixed via early ndim guard before dispatch (eager/vrt/gpu) + 3 tests; PR #3604. Read-side param validation + typed-error hierarchy + allow_rotated/allow_invalid_nodata VRT+chunked opt-in threading verified clean (CUDA available, GPU paths run). gh issue create blocked by auto-mode; PR opened. Cat 2+4."
13 changes: 13 additions & 0 deletions xrspatial/geotiff/_writers/eager.py
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,19 @@ def _write_sidecars():
_is_vrt_path = (
isinstance(path, str) and path.lower().endswith('.vrt'))

# Reject arrays that are not 2D or 3D before any backend dispatch or
# georef resolution. ``.ndim`` is defined on DataArray, numpy, cupy,
# and dask inputs. Without this, a 0D/1D *DataArray* reaches
# ``coords_to_transform`` (which indexes ``dims[-2]``) and dies with an
# opaque ``IndexError: tuple index out of range`` pointing at
# ``_coords.py`` internals, while a 0D/1D numpy array or a 4D
# DataArray already gets the clear message below. Running the check
# here up front makes every backend (eager, VRT, GPU) reject the same
# bad shapes identically.
_ndim = getattr(data, 'ndim', None)
if _ndim is not None and _ndim not in (2, 3):
raise ValueError(f"Expected 2D or 3D array, got {_ndim}D")

# Resolve GPU dispatch up front so the JPEG opt-in warning fires
# exactly once. ``_write_geotiff_gpu`` emits its own warning on the
# GPU path; emitting here as well would double-warn callers of
Expand Down
20 changes: 20 additions & 0 deletions xrspatial/geotiff/tests/test_edge_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,26 @@ def test_0d_scalar(self, tmp_path):
with pytest.raises(ValueError, match="Expected 2D"):
to_geotiff(arr, str(tmp_path / 'bad.tif'))

def test_1d_dataarray(self, tmp_path):
# A 1D DataArray used to reach ``coords_to_transform`` (which
# indexes ``dims[-2]``) and raise an opaque ``IndexError: tuple
# index out of range`` instead of the clear message a 1D numpy
# array or a 4D DataArray already gets.
da = xr.DataArray(np.zeros(10, dtype=np.float32), dims=('x',))
with pytest.raises(ValueError, match="Expected 2D or 3D array, got 1D"):
to_geotiff(da, str(tmp_path / 'bad.tif'))

def test_0d_dataarray(self, tmp_path):
da = xr.DataArray(np.float32(42.0))
with pytest.raises(ValueError, match="Expected 2D or 3D array, got 0D"):
to_geotiff(da, str(tmp_path / 'bad.tif'))

def test_1d_dataarray_vrt(self, tmp_path):
# The ``.vrt`` write path shares the same pre-dispatch guard.
da = xr.DataArray(np.zeros(10, dtype=np.float32), dims=('x',))
with pytest.raises(ValueError, match="Expected 2D or 3D array, got 1D"):
to_geotiff(da, str(tmp_path / 'bad.vrt'))

def test_unsupported_compression(self, tmp_path):
arr = np.zeros((4, 4), dtype=np.float32)
# ``to_geotiff`` validates ``compression`` up-front. The
Expand Down
Loading