Skip to content

Commit a3aa6b2

Browse files
avoid ZeroDivisionError in statistics (#795)
1 parent ee3d17f commit a3aa6b2

File tree

3 files changed

+59
-16
lines changed

3 files changed

+59
-16
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
# Unreleased
33

4+
* fix `utils.get_array_statistics` method to avoid `ZeroDivisionError` when there is no valid pixel
45
* use `GDAL_MEM_ENABLE_OPEN=TRUE` when opening a numpy array with rasterio
56

67
# 7.5.0 (2025-02-26)

rio_tiler/utils.py

+25-16
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ def _weighted_stdev(
5959
values: NDArray[numpy.floating],
6060
weights: NDArray[numpy.floating],
6161
) -> float:
62-
average = numpy.average(values, weights=weights)
63-
variance = numpy.average((values - average) ** 2, weights=weights)
62+
average = numpy.ma.average(values, weights=weights)
63+
variance = numpy.ma.average((values - average) ** 2, weights=weights)
6464
return float(math.sqrt(variance))
6565

6666

@@ -174,35 +174,44 @@ def get_array_statistics(
174174
_weighted_quantiles(data_comp, masked_coverage.compressed(), pp / 100.0)
175175
for pp in percentiles
176176
]
177-
else:
178-
percentiles_values = [numpy.nan] * len(percentiles_names)
179-
180-
if valid_pixels:
181177
majority = float(keys[counts.tolist().index(counts.max())].tolist())
182178
minority = float(keys[counts.tolist().index(counts.min())].tolist())
179+
std = _weighted_stdev(data_comp, masked_coverage.compressed())
180+
med = _weighted_quantiles(data_comp, masked_coverage.compressed())
181+
_min = float(data[b].min())
182+
_max = float(data[b].max())
183+
_mean = float(data_cov.sum() / masked_coverage.sum())
184+
_count = float(masked_coverage.sum())
185+
_sum = float(data_cov.sum())
186+
183187
else:
188+
percentiles_values = [numpy.nan] * len(percentiles_names)
184189
majority = numpy.nan
185190
minority = numpy.nan
186-
187-
_count = masked_coverage.sum()
188-
_sum = data_cov.sum()
191+
std = numpy.nan
192+
med = numpy.nan
193+
_min = numpy.nan
194+
_max = numpy.nan
195+
_count = 0
196+
_sum = 0
197+
_mean = numpy.nan
189198

190199
output.append(
191200
{
192201
# Minimum value, not taking coverage fractions into account.
193-
"min": float(data[b].min()),
202+
"min": _min,
194203
# Maximum value, not taking coverage fractions into account.
195-
"max": float(data[b].max()),
204+
"max": _max,
196205
# Mean value, weighted by the percent of each cell that is covered.
197-
"mean": float(_sum / _count),
206+
"mean": _mean,
198207
# Sum of all non-masked cell coverage fractions.
199-
"count": float(_count),
208+
"count": _count,
200209
# Sum of values, weighted by their coverage fractions.
201-
"sum": float(_sum),
210+
"sum": _sum,
202211
# Population standard deviation of cell values, taking into account coverage fraction.
203-
"std": _weighted_stdev(data_comp, masked_coverage.compressed()),
212+
"std": std,
204213
# Median value of cells, weighted by the percent of each cell that is covered.
205-
"median": _weighted_quantiles(data_comp, masked_coverage.compressed()),
214+
"median": med,
206215
# The value occupying the greatest number of cells.
207216
"majority": majority,
208217
# The value occupying the least number of cells.

tests/test_utils.py

+33
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,39 @@ def test_get_array_statistics():
559559
assert not math.isnan(stats[0]["max"])
560560
assert not math.isnan(stats[0]["max"])
561561

562+
# Totally Masked Array
563+
arr = np.ma.MaskedArray(data=np.zeros((1, 256, 256), dtype="uint8"), mask=True)
564+
stats = utils.get_array_statistics(arr)
565+
assert len(stats) == 1
566+
assert list(stats[0]) == [
567+
"min",
568+
"max",
569+
"mean",
570+
"count",
571+
"sum",
572+
"std",
573+
"median",
574+
"majority",
575+
"minority",
576+
"unique",
577+
"percentile_2",
578+
"percentile_98",
579+
"histogram",
580+
"valid_pixels",
581+
"masked_pixels",
582+
"valid_percent",
583+
]
584+
# Make sure the statistics object are JSON serializable
585+
assert json.dumps(stats[0])
586+
assert math.isnan(stats[0]["min"])
587+
assert math.isnan(stats[0]["max"])
588+
assert math.isnan(stats[0]["max"])
589+
assert math.isnan(stats[0]["mean"])
590+
assert stats[0]["count"] == 0
591+
assert stats[0]["sum"] == 0
592+
assert math.isnan(stats[0]["std"])
593+
assert math.isnan(stats[0]["median"])
594+
562595

563596
def test_resize_array():
564597
"""make sure we resize well."""

0 commit comments

Comments
 (0)