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
10 changes: 8 additions & 2 deletions xrspatial/terrain.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,10 @@ def _terrain_numpy(data, seed, x_range_scaled, y_range_scaled, zfactor,
warp_strength, warp_octaves,
worley_blend, worley_seed):
_check_memory(data.shape[0], data.shape[1])
data = data * 0
# zeros_like (not data * 0) so an all-NaN template (e.g. from
# from_template()) is overwritten instead of propagating NaN
# everywhere (NaN * 0 == NaN).
data = np.zeros_like(data)
data[:] = _gen_terrain(
data, seed, x_range=x_range_scaled, y_range=y_range_scaled,
octaves=octaves, lacunarity=lacunarity, persistence=persistence,
Expand Down Expand Up @@ -437,7 +440,10 @@ def _terrain_cupy(data, seed, x_range_scaled, y_range_scaled, zfactor,
warp_strength, warp_octaves,
worley_blend, worley_seed):
_check_gpu_memory(data.shape[0], data.shape[1])
data = data * 0
# zeros_like (not data * 0) so an all-NaN template (e.g. from
# from_template()) is overwritten instead of propagating NaN
# everywhere (NaN * 0 == NaN).
data = cupy.zeros_like(data)
data[:] = _terrain_gpu(
data, seed, x_range=x_range_scaled, y_range=y_range_scaled,
octaves=octaves, lacunarity=lacunarity, persistence=persistence,
Expand Down
58 changes: 58 additions & 0 deletions xrspatial/tests/test_terrain.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,3 +470,61 @@ def test_generate_terrain_dask_cupy_preserves_coords_attrs():
np.testing.assert_array_equal(terrain.x.data, src.x.data)
assert terrain.attrs['crs'] == 'EPSG:5070'
assert terrain.attrs['res'] == (30, 30)


# =====================================================================
# Issue #3525: an all-NaN template (e.g. from_template) must be filled,
# not propagated. data * 0 left NaN * 0 == NaN on numpy / cupy.
# =====================================================================

def _nan_template(backend='numpy'):
"""An empty all-NaN grid, like from_template() returns."""
data = np.full((50, 50), np.nan, dtype=np.float32)
raster = xr.DataArray(data, dims=['y', 'x'])
if has_cuda_and_cupy() and 'cupy' in backend:
import cupy
raster.data = cupy.asarray(raster.data)
if 'dask' in backend and da is not None:
raster.data = da.from_array(raster.data, chunks=(10, 10))
return raster


def test_terrain_all_nan_template_is_finite():
"""All-NaN input must produce all-finite terrain, not all-NaN."""
terrain = generate_terrain(_nan_template())
assert np.isfinite(terrain.data).all()


def test_terrain_all_nan_template_matches_zeros_template():
"""generate_terrain ignores input values: an all-NaN template and an
all-zeros template must yield identical terrain."""
from_nan = generate_terrain(_nan_template())
from_zeros = generate_terrain(create_test_arr())
np.testing.assert_array_equal(from_nan.data, from_zeros.data)


@dask_array_available
def test_terrain_all_nan_template_dask_matches_numpy():
t_np = generate_terrain(_nan_template())
t_dask = generate_terrain(_nan_template(backend='dask')).compute()
assert np.isfinite(t_dask.data).all()
np.testing.assert_allclose(t_np.data, t_dask.data, rtol=1e-5, atol=1e-7)


@cuda_and_cupy_available
def test_terrain_all_nan_template_cupy_matches_numpy():
t_np = generate_terrain(_nan_template())
t_cupy = generate_terrain(_nan_template(backend='cupy'))
assert np.isfinite(t_cupy.data.get()).all()
np.testing.assert_allclose(t_np.data, t_cupy.data.get(),
rtol=1e-5, atol=1e-7)


@cuda_and_cupy_available
@dask_array_available
def test_terrain_all_nan_template_dask_cupy_matches_numpy():
t_np = generate_terrain(_nan_template())
t_dc = generate_terrain(_nan_template(backend='dask+cupy')).compute()
assert np.isfinite(t_dc.data.get()).all()
np.testing.assert_allclose(t_np.data, t_dc.data.get(),
rtol=1e-4, atol=1e-4)
Loading