Skip to content

Commit 35df23a

Browse files
authored
Avoid division by zero in volume normalization (#381)
1 parent 3f4215c commit 35df23a

File tree

2 files changed

+54
-3
lines changed

2 files changed

+54
-3
lines changed

src/highdicom/volume.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3258,7 +3258,10 @@ def normalize_mean_std(
32583258
) -> Self:
32593259
"""Normalize the intensities using the mean and variance.
32603260
3261-
The resulting volume has zero mean and unit variance.
3261+
The resulting volume has zero mean and unit variance. If the input
3262+
array (or a given channel if ``per_channel``) contains only a single
3263+
unique value, the returned array will be shifted to the output mean and
3264+
will not be scaled (since this would result in a division by zero).
32623265
32633266
Parameters
32643267
----------
@@ -3279,17 +3282,31 @@ def normalize_mean_std(
32793282
be promoted to floating point.
32803283
32813284
"""
3285+
if output_std <= 0.0:
3286+
raise ValueError(
3287+
"The 'output_std' must be greater than or equal to zero."
3288+
)
3289+
32823290
if (
32833291
per_channel and
32843292
self.number_of_channel_dimensions > 0
32853293
):
32863294
mean = self.array.mean(axis=(0, 1, 2), keepdims=True)
32873295
std = self.array.std(axis=(0, 1, 2), keepdims=True)
3296+
3297+
scale = std / output_std
3298+
3299+
# Avoid division by zero
3300+
scale[scale == 0.0] = 1.0
32883301
else:
32893302
mean = self.array.mean()
32903303
std = self.array.std()
3304+
3305+
# Avoid division by zero
3306+
scale = 1.0 if std == 0.0 else (std / output_std)
3307+
32913308
new_array = (
3292-
(self.array - mean) / (std / output_std) + output_mean
3309+
(self.array - mean) / scale + output_mean
32933310
)
32943311

32953312
return self.with_array(new_array)
@@ -3332,11 +3349,22 @@ def normalize_min_max(
33323349
):
33333350
imin = self.array.min(axis=(0, 1, 2), keepdims=True)
33343351
imax = self.array.max(axis=(0, 1, 2), keepdims=True)
3352+
3353+
peak_to_peak = imax - imin
3354+
3355+
# Avoid division by zerp
3356+
peak_to_peak[peak_to_peak == 0.0] = output_range
3357+
scale_factor = output_range / peak_to_peak
33353358
else:
33363359
imin = self.array.min()
33373360
imax = self.array.max()
33383361

3339-
scale_factor = output_range / (imax - imin)
3362+
if imin == imax:
3363+
# Avoid division by zero
3364+
scale_factor = 1.0
3365+
else:
3366+
scale_factor = output_range / (imax - imin)
3367+
33403368
new_array = (self.array - imin) * scale_factor + output_min
33413369

33423370
return self.with_array(new_array)

tests/test_volume.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,29 @@ def test_normalize():
10721072
assert np.isclose(normed.array.max(), 1.0)
10731073

10741074

1075+
def test_normalize_uniform():
1076+
# Normaliztion when std is zero
1077+
arr = np.ones((10, 10, 10))
1078+
vol = Volume(
1079+
arr,
1080+
np.eye(4),
1081+
coordinate_system="PATIENT",
1082+
)
1083+
1084+
normed = vol.normalize_mean_std()
1085+
assert np.array_equal(normed.array, np.zeros_like(arr))
1086+
1087+
arr = np.ones((10, 10, 10))
1088+
vol = Volume(
1089+
arr,
1090+
np.eye(4),
1091+
coordinate_system="PATIENT",
1092+
)
1093+
1094+
normed = vol.normalize_min_max()
1095+
assert np.array_equal(normed.array, np.zeros_like(arr))
1096+
1097+
10751098
@pytest.mark.parametrize(
10761099
'kw,pytype',
10771100
[

0 commit comments

Comments
 (0)