Skip to content

geotiff: cover mask_nodata=False on GPU and VRT readers (#2052)#2102

Merged
brendancol merged 1 commit into
mainfrom
deep-sweep-test-coverage-geotiff-2026-05-18
May 19, 2026
Merged

geotiff: cover mask_nodata=False on GPU and VRT readers (#2052)#2102
brendancol merged 1 commit into
mainfrom
deep-sweep-test-coverage-geotiff-2026-05-18

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Summary

  • mask_nodata was wired through all four public geotiff readers in [Bug] open_geotiff(dtype=...) fails when integer nodata sentinel matches pixels #2052, but test_mask_nodata_kwarg_2052.py only covered eager-numpy and dask+numpy. The pure-GPU mask gating (_backends/gpu.py:709), dask+GPU dispatcher forwarding (_backends/gpu.py:991), eager VRT gating (_backends/vrt.py:320), and chunked VRT graph builder (_backends/vrt.py:408/588) had no direct coverage.
  • A regression silently re-promoting integer rasters to float64 on any of these paths would slip past every existing sweep.
  • 19 new tests, all passing on a GPU host. The fixture writes a tiled deflate-compressed uint16 so the pure nvCOMP decode path runs instead of the CPU-fallback piggyback path.
  • Covers GPU eager, dask+GPU, VRT eager, chunked VRT, dispatcher thread-through for each branch, and cross-backend bit-exact parity under mask_nodata=False.

Coverage matrix

Path mask_nodata=False default (True) dtype= with opt-out
open_geotiff eager (prior) covered covered covered
open_geotiff dask+numpy (prior) covered covered covered
open_geotiff gpu (this PR) added added added
open_geotiff gpu+chunks (this PR) added added --
open_geotiff vrt (this PR) added added added
open_geotiff vrt+chunks (this PR) added added --
read_geotiff_gpu direct (this PR) added added added
read_geotiff_dask direct (this PR) added -- --
read_vrt direct (this PR) added added added

Mutation evidence

  • gpu.py:709 if nodata is not None and mask_nodata -> if nodata is not None and True: flips 4 GPU tests red (eager-GPU preserves uint16, dispatcher thread-through, dtype=uint16 with opt-out, eager vs GPU parity).
  • vrt.py:320 if mask_nodata -> if True (eager VRT branch): flips 4 VRT tests red (eager VRT preserves uint16, dispatcher thread-through, dtype=uint16 with opt-out, eager vs VRT parity).

Notes

Tests only; no source changes. Closes the Cat 1 HIGH backend-coverage gap flagged by pass 17 of the test-coverage sweep on the geotiff module.

Test plan

  • All 19 new tests pass on a GPU host
  • Prior 10 tests in test_mask_nodata_kwarg_2052.py still pass
  • gpu.py:709 mutation flips the targeted GPU tests
  • vrt.py:320 mutation flips the targeted VRT tests
  • CI runs the new tests on the project's GPU runner

mask_nodata was wired through open_geotiff, read_geotiff_gpu,
read_geotiff_dask, and read_vrt in #2052, but test_mask_nodata_kwarg
only hit the eager-numpy and dask+numpy paths. The pure-GPU mask
gating at _backends/gpu.py:709, the dask+GPU forwarding at
_backends/gpu.py:991, the eager VRT gating at _backends/vrt.py:320,
and the chunked VRT builder at _backends/vrt.py:408/588 had no direct
coverage. A regression silently re-promoting integer rasters to
float64 would slip past every existing sweep.

19 new tests, all passing on a GPU host. The fixture writes a tiled
deflate-compressed uint16 so the pure nvCOMP decode path runs instead
of the CPU-fallback piggyback. Covers GPU eager, dask+GPU, VRT eager,
chunked VRT, dispatcher thread-through for each, and cross-backend
bit-exact parity under mask_nodata=False.

Mutation against gpu.py:709 flips 4 GPU tests red. Mutation against
the eager VRT branch flips 4 VRT tests red. Tests only; no source
changes.
@github-actions github-actions Bot added the performance PR touches performance-sensitive code label May 19, 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: geotiff: cover mask_nodata=False on GPU and VRT readers (#2052)

Blockers

None.

Suggestions

  • The dask+GPU coverage is structurally real but may not exercise the GDS chunked builder in environments where kvikio is unavailable. read_geotiff_gpu(chunks=...) first probes _gds_chunk_path_available (xrspatial/geotiff/_backends/gpu.py:775) and falls back to the CPU-decode + cupy upload path when kvikio is missing or its CuFile attribute is unavailable. The test environment shows the warning _try_kvikio_read_tiles fell back to None (AttributeError: module 'kvikio' has no attribute 'CuFile'), so the dask+GPU tests in this PR flow through read_geotiff_dask + map_blocks(cupy.asarray) (the kwarg is forwarded at gpu.py:991) rather than _read_geotiff_gpu_chunked_gds (gpu.py:1016). The kwarg forwarding still gets coverage on the CPU+upload branch, but a regression that drops mask_nodata from the GDS chunked builder alone would not be caught here. Worth a comment in the test module flagging the dual-path nature so a future reader does not assume GDS coverage from these tests, or a kvikio-available CI lane to actually pin the GDS path.

Nits

  • Module docstring at xrspatial/geotiff/tests/test_mask_nodata_gpu_vrt_2052.py:10-15 cites concrete line numbers (gpu.py:709, gpu.py:991, gpu.py:1016, vrt.py:320, vrt.py:408, vrt.py:588). These match HEAD now but will drift the moment an unrelated edit shifts those files. Symbol or function-name references (_read_geotiff_gpu, _read_geotiff_gpu_chunked_gds, the eager / chunked VRT builders) would stay accurate without manual upkeep. Not blocking — the current refs are correct.
  • The uint16 4x4 sentinel array is declared twice (lines 80-86 and 103-109). A small shared constant would remove the duplication. Trivial.

What looks good

  • Mutation evidence is concrete: gpu.py:709 and the eager VRT branch each have a documented 4-test red flip in the PR body, so the assertions actually pin the gating, not just the surrounding plumbing.
  • Tests use bit-exact np.testing.assert_array_equal against a known fixture instead of an approximation, which is the right strength for an integer-preservation contract.
  • Default-promotion tests (e.g. test_read_geotiff_gpu_default_mask_nodata_true_still_promotes at line 137, the chunked counterpart at line 201, VRT counterparts at line 249 and 297) pin both sides of the kwarg so a mask_nodata default flip in either direction is caught.
  • Cross-backend parity tests (eager-numpy ↔ dask-numpy, eager-numpy ↔ eager-GPU, eager-numpy ↔ dask-GPU, eager-numpy ↔ eager-VRT) lock down semantic equivalence under the opt-out, not just per-backend behaviour.
  • Direct entry-point tests (read_geotiff_gpu, read_geotiff_dask, read_vrt) catch dispatcher-bypass regressions that the open_geotiff thread-through tests would not, and vice versa.
  • Tiled + deflate fixture means the GPU eager path goes through nvCOMP decode rather than the CPU-fallback piggyback.
  • Tests-only; zero production changes, so the risk surface is constrained to test correctness.
  • attrs['nodata'] == 0 assertion at line 133 / line 246 catches the orthogonal regression where mask_nodata=False drops the sentinel from attrs entirely.

Checklist

  • Algorithm matches reference/paper — N/A (coverage PR).
  • All implemented backends produce consistent results — cross-backend parity tests confirm.
  • NaN handling is correct — default-promotion tests verify the float64+NaN baseline using np.isnan / cupy.isnan.
  • Edge cases are covered by tests — fixture intentionally has 0 sentinel positions inside a uint16 raster (the original bug shape).
  • Dask chunk boundaries handled correctly — chunks=2 against a 4x4 raster forces multiple chunks, and .compute() parity holds.
  • No premature materialization or unnecessary copies — N/A (tests-only).
  • Benchmark exists or is not needed — not needed.
  • README feature matrix updated (if applicable) — not needed.
  • Docstrings present and accurate — module docstring is informative; per-test docstrings are clear about intent.

@brendancol brendancol merged commit b5bd265 into main May 19, 2026
7 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.

1 participant