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
7 changes: 7 additions & 0 deletions xrspatial/dasymetric.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,13 @@ def _pycnophylactic_numpy(zones_arr, values_dict, nodata_zone,
# pixels that belong to some zone (valid for smoothing)
valid = ~np.isnan(surface)

# no valid pixels (all-NaN zones, or no zone id matched values): the
# smoothing loop has nothing to converge on and np.nanmax over the
# empty valid slice would raise. Return the all-NaN surface, matching
# disaggregate's behaviour on the same inputs.
if not valid.any():
return surface

for _ in range(max_iterations):
# Laplacian smoothing: mean of 4-connected neighbours
smoothed = np.full_like(surface, np.nan)
Expand Down
27 changes: 12 additions & 15 deletions xrspatial/tests/test_dasymetric.py
Original file line number Diff line number Diff line change
Expand Up @@ -1070,12 +1070,11 @@ def test_three_class_conservation_only(self):
# ---------------------------------------------------------------------------

class TestPycnophylacticEmptyValid:
"""pycnophylactic crashes when no pixel is valid for smoothing (#3406).
"""pycnophylactic with no pixel valid for smoothing (#3406).

disaggregate handles the same inputs gracefully (all-NaN output); these
are xfail(strict) until #3406 makes pycnophylactic agree. When the
source fix lands, the tests start XPASSing and strict mode flips them
red, prompting removal of the marker.
disaggregate handles the same inputs gracefully (all-NaN output); #3406
makes pycnophylactic agree by returning the all-NaN surface instead of
raising on the empty-valid slice.
"""

def test_disaggregate_all_nan_zones_is_all_nan(self):
Expand All @@ -1085,23 +1084,21 @@ def test_disaggregate_all_nan_zones_is_all_nan(self):
result = disaggregate(zones, {1: 100.0}, weight)
assert np.all(np.isnan(result.values))

@pytest.mark.xfail(
reason="#3406: pycnophylactic raises ValueError on empty-valid input",
strict=True,
raises=ValueError,
)
def test_pycnophylactic_all_nan_zones(self):
zones = create_test_raster(np.full((3, 3), np.nan), backend='numpy')
result = pycnophylactic(zones, {1: 100.0})
assert np.all(np.isnan(result.values))

@pytest.mark.xfail(
reason="#3406: pycnophylactic raises ValueError on empty-valid input",
strict=True,
raises=ValueError,
)
def test_pycnophylactic_no_matching_zone(self):
zones = create_test_raster(
np.array([[1, 1], [2, 2]], dtype=np.float64), backend='numpy')
result = pycnophylactic(zones, {99: 100.0})
assert np.all(np.isnan(result.values))

@pytest.mark.skipif(not has_cuda_and_cupy(),
reason="CUDA/CuPy not available")
def test_pycnophylactic_all_nan_zones_cupy(self):
"""cupy fallback shares the numpy path; guard it too."""
zones = create_test_raster(np.full((3, 3), np.nan), backend='cupy')
result = pycnophylactic(zones, {1: 100.0})
assert np.all(np.isnan(result.data.get()))
Loading