Skip to content

reproject: handle (band, y, x) 3-D inputs (#2182)#2189

Merged
brendancol merged 4 commits into
mainfrom
issue-2182
May 20, 2026
Merged

reproject: handle (band, y, x) 3-D inputs (#2182)#2189
brendancol merged 4 commits into
mainfrom
issue-2182

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Closes #2182

Summary

  • The per-chunk worker assumed the band axis was trailing: it sliced source_data[r:, c:] and read window.shape[2] for the band count. A rasterio/rioxarray-style (band, y, x) input sliced the band/y axes instead of y/x and either crashed with a band coord-length mismatch or returned wrong-shape data.
  • The entry point now transposes 3-D inputs to the canonical (y, x, band) layout before dispatch and transposes the output back to the input's dim order, so a (band, y, x) raster round-trips as (band, y, x).

Backends covered

numpy, dask+numpy, cupy, dask+cupy. New tests run on all four (cupy/dask+cupy paths gated on GPU availability via skipif).

Test plan

  • pytest xrspatial/tests/test_reproject.py::TestReproject3DBandFirst -- 9 passed
  • pytest xrspatial/tests/test_reproject.py -- 277 passed, no regressions
  • Pixel-equality check that (band, y, x) and (y, x, band) inputs produce the same data after a common transpose

The per-chunk worker assumed the band axis was trailing -- it sliced
``source_data[r:, c:]`` and read ``window.shape[2]`` for the band count.
A rasterio/rioxarray-style ``(band, y, x)`` input sliced the band/y
axes instead of y/x and either crashed with a band coord-length
mismatch or returned wrong-shape data.

The entry point now transposes 3-D inputs to the canonical
``(y, x, band)`` layout before dispatch, runs the existing pipeline,
then transposes the output back to the input's dim order so downstream
rioxarray/rasterio code keeps working.

Adds a ``TestReproject3DBandFirst`` class covering ``(band, y, x)``
inputs on the numpy, dask+numpy, cupy, and dask+cupy backends, plus a
pixel-equality check that the two layouts agree.
@github-actions github-actions Bot added the performance PR touches performance-sensitive code label May 20, 2026
Copy link
Copy Markdown
Contributor Author

@brendancol brendancol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: reproject handles (band, y, x) 3-D inputs (#2182)

Blockers

None.

Suggestions

None.

Nits

  • xrspatial/reproject/__init__.py:670: the _canonical tuple is built with _band_dim_in even when it is None. The guard _band_dim_in is not None and _input_dims != _canonical saves the call, but a tuple containing None is a little odd to read. Building the tuple inside the guard would make the intent clearer.
  • xrspatial/tests/test_reproject.py:3996: test_band_first_dask_matches_numpy calls _make_band_first_raster() three times. Caching the raster in a local would save two RNG passes (cosmetic; the test is fast either way).

What looks good

  • The fix is localized to the entry point: transpose to canonical (y, x, band) before dispatch, transpose back after the output DataArray is built. Downstream workers and the per-block dask adapter are unchanged, which keeps the blast radius small.
  • The set-equality check on the final transpose (set(_input_dims) == set(result.dims)) is a defensive guard against crashing if the band-dim name ever differs between input and output.
  • Tests cover all four backends (numpy, dask, cupy, dask+cupy) with both layout-preservation and pixel-equality assertions.
  • xr.DataArray.transpose is metadata-only on dask arrays, so no extra materialization is introduced for the band-first dask path.

Checklist

  • Algorithm matches reference/paper -- N/A (pure layout fix)
  • All backends produce consistent results -- verified by test_band_first_matches_band_last and test_band_first_dask_matches_numpy
  • NaN handling is correct -- unchanged from the (y, x, band) path
  • Edge cases covered -- integer dtype roundtrip included
  • Dask chunk boundaries handled -- lazy transpose preserves chunking semantics
  • No premature materialization or unnecessary copies -- transpose is metadata-only
  • Benchmark not needed -- no perf-sensitive code touched
  • README feature matrix -- N/A (no new API)
  • Docstrings -- existing docstring still accurate

…er (#2182)

- Move the canonical (y, x, band) tuple construction inside the
  `_band_dim_in is not None` guard so the tuple never contains None.
- Cache `_make_band_first_raster()` in `test_band_first_dask_matches_numpy`
  so the raster is built once instead of three times.
# Conflicts:
#	xrspatial/tests/test_reproject.py
@brendancol brendancol merged commit f8069d1 into main May 20, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance PR touches performance-sensitive code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

reproject: 3-D rasters with (band, y, x) dims crash and produce wrong output

1 participant