Skip to content

Fill all-NaN templates in generate_terrain (numpy/cupy)#3526

Merged
brendancol merged 3 commits into
mainfrom
issue-3525
Jun 26, 2026
Merged

Fill all-NaN templates in generate_terrain (numpy/cupy)#3526
brendancol merged 3 commits into
mainfrom
issue-3525

Conversation

@brendancol

Copy link
Copy Markdown
Contributor

Closes #3525

generate_terrain() returned all-NaN on the numpy and cupy backends when
the template was all-NaN. That is exactly what from_template() hands you,
so the obvious pipeline (from_template(...).xrs.generate_terrain().xrs.to_geotiff(...))
wrote a GeoTIFF that QGIS reads as 100% nodata.

  • Root cause: both backends zeroed their work buffer with data * 0, but
    NaN * 0 == NaN, so the template's NaN values survived and the octave
    loop accumulated noise onto NaN. Switched to np.zeros_like /
    cupy.zeros_like.
  • The dask and dask+cupy paths already allocated fresh zeros per chunk, so
    they were never affected and are unchanged.

Backend coverage: fix touches numpy + cupy; dask and dask+cupy already
worked. New tests run against all four.

Test plan:

  • pytest xrspatial/tests/test_terrain.py (49 passed locally, incl. GPU)
  • All-NaN template now yields finite terrain identical to an all-zeros template
  • numpy/cupy parity on the all-NaN template

The numpy and cupy backends zeroed their work buffer with data * 0, but
NaN * 0 == NaN, so an all-NaN template (what from_template() returns) was
never cleared and the octave loop accumulated noise onto NaN, leaving the
whole output NaN. Saved GeoTIFFs read as 100% nodata in QGIS.

Use np.zeros_like / cupy.zeros_like instead. The dask and dask+cupy paths
already allocated fresh zeros per chunk and were unaffected.

Adds tests covering all four backends: an all-NaN template now yields
finite terrain identical to an all-zeros template.

@brendancol brendancol left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

PR Review: Fill all-NaN templates in generate_terrain (numpy/cupy)

Blockers (must fix before merge)

None.

Suggestions (should fix, not blocking)

  • The dask and dask+cupy NaN-template tests (test_terrain.py:498, 513) only assert the result is finite, not that it matches the numpy result. Those backends were never broken and this PR doesn't touch them, so the gap is minor, but a np.testing.assert_allclose(t_np, t_dask) would turn the all-NaN case into a real cross-backend parity check instead of a smoke test.

Nits (optional improvements)

  • The two added code comments (terrain.py:184, 443) use em dashes. Project convention leans away from them; commas read the same here.

What looks good

  • Root cause is right and the fix is minimal. NaN * 0 == NaN was the actual bug, and zeros_like clears it while staying identical to the old behavior for any finite input, so there's no regression risk on normal templates.
  • Leaving the dask and dask+cupy paths alone is correct; they already allocate fresh zeros per chunk.
  • test_terrain_all_nan_template_matches_zeros_template is a strong invariant. It pins the contract that generate_terrain ignores input values, and it would have failed on the old code.
  • All four backends are covered, and the GPU tests run on the CUDA dev box.

Checklist

  • Algorithm matches reference: n/a (noise generation unchanged)
  • All implemented backends produce consistent results: yes (numpy/cupy parity tested)
  • NaN handling correct: yes (this is the fix)
  • Edge cases covered: yes (all-NaN template, the reported case)
  • Dask chunk boundaries: unchanged
  • No premature materialization or unnecessary copies: yes
  • Benchmark: not needed (bug fix, no perf change)
  • README feature matrix: n/a (no API or backend-support change)
  • Docstrings present and accurate: yes (behavior now matches the existing docstring)

…3525)

- dask / dask+cupy NaN-template tests now assert allclose against the
  numpy result instead of only checking finiteness.
- Replace em dashes in the two new code comments with parentheses.

@brendancol brendancol left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Follow-up review (after ac34ba9)

Both findings from the first pass are addressed:

  • Suggestion (cross-backend parity): the dask and dask+cupy NaN-template tests now run the same template through numpy and assert np.testing.assert_allclose against it, so the all-NaN case is a real parity check, not a smoke test. Renamed to ..._dask_matches_numpy / ..._dask_cupy_matches_numpy.
  • Nit (em dashes): the two new code comments now use parentheses.

No new findings. Full terrain suite passes locally (49 passed, including GPU and dask+cupy on the CUDA box). No blockers.

@brendancol brendancol merged commit 5239c87 into main Jun 26, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

generate_terrain() returns all-NaN on numpy/cupy when template is all-NaN (e.g. from_template)

1 participant