Skip to content

Commit e329e2c

Browse files
daniel-werMichaelBuessemeyermarkbader
authored
Fix infinite loop in mag calculation during anisotropic downsampling (#934)
* Fix infinite loop in mag calculation during anisotropic downsampling * update changelog * fix typing * Adapt naming of mags. --------- Co-authored-by: MichaelBuessemeyer <[email protected]> Co-authored-by: markbader <[email protected]>
1 parent 139f449 commit e329e2c

File tree

5 files changed

+84
-31
lines changed

5 files changed

+84
-31
lines changed

webknossos/Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ For upgrade instructions, please check the respective _Breaking Changes_ section
1919
### Changed
2020

2121
### Fixed
22+
- Fixed an infinite loop in the mag calculation during anisotropic downsampling in situations where the target mag cannot possibly be reached while adhering to the anisotropic downsampling scheme. [#934](https://github.com/scalableminds/webknossos-libs/pull/934)
2223

2324

2425
## [0.13.3](https://github.com/scalableminds/webknossos-libs/releases/tag/v0.13.3) - 2023-08-08

webknossos/poetry.lock

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webknossos/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ pytest-custom-exit-code = "^0.3.0"
9090
pytest-recording = "^0.12.0"
9191
pytest-split = "^0.8.0"
9292
pytest-sugar = "^0.9.4"
93+
pytest-timeout = "^2.1.0"
9394
types-python-dateutil = "^0.1.6"
9495
# packages for examples:
9596
fastremap = "^1.13.3"

webknossos/tests/dataset/test_downsampling.py

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import warnings
22
from pathlib import Path
3+
from typing import List, Optional, Tuple
34

45
import numpy as np
56
import pytest
@@ -174,14 +175,9 @@ def test_downsample_multi_channel(tmp_path: Path) -> None:
174175
assert np.all(target_buffer == joined_buffer)
175176

176177

177-
def test_anisotropic_mag_calculation() -> None:
178-
# This test does not test the exact input of the user:
179-
# If a user does not specify a max_mag, then a default is calculated.
180-
# Therefore, max_mag=None is not covered in this test case.
181-
# The same applies for `voxel_size`:
182-
# This is either extracted from the properties or set to comply with a specific sampling mode.
183-
184-
mag_tests = [
178+
@pytest.mark.parametrize(
179+
"voxel_size, finest_mag, coarsest_mag, scheme",
180+
[
185181
# Anisotropic
186182
(
187183
(10.5, 10.5, 24), # voxel_size
@@ -260,27 +256,68 @@ def test_anisotropic_mag_calculation() -> None:
260256
),
261257
(None, (2, 2, 1), (8, 8, 4), [(2, 2, 1), (4, 4, 2), (8, 8, 4)]),
262258
(None, (2, 2, 1), (8, 8, 8), [(2, 2, 1), (4, 4, 2), (8, 8, 4)]),
263-
]
264-
265-
for i in range(len(mag_tests)):
266-
voxel_size, from_max_name, max_mag_name, scheme = mag_tests[i]
267-
sampling_scheme = [Mag(m) for m in scheme]
268-
from_mag = Mag(from_max_name)
269-
max_mag = Mag(max_mag_name)
270-
271-
assert sampling_scheme[1:] == calculate_mags_to_downsample(
272-
from_mag, max_mag, None, voxel_size
273-
), f"The calculated downsampling scheme of the {i+1}-th test case is wrong."
274-
275-
for i in range(len(mag_tests)):
276-
voxel_size, finest_mag_name, from_mag_name, scheme = mag_tests[i]
277-
sampling_scheme = [Mag(m) for m in scheme]
278-
from_mag = Mag(from_mag_name)
279-
finest_mag = Mag(finest_mag_name)
280-
281-
assert list(reversed(sampling_scheme[:-1])) == calculate_mags_to_upsample(
282-
from_mag, finest_mag, None, voxel_size
283-
), f"The calculated upsampling scheme of the {i+1}-th test case is wrong."
259+
],
260+
)
261+
def test_mag_calculation(
262+
voxel_size: Optional[Tuple[float, float, float]],
263+
finest_mag: Tuple[int, int, int],
264+
coarsest_mag: Tuple[int, int, int],
265+
scheme: List[Tuple[int, int, int]],
266+
) -> None:
267+
# This test does not test the exact input of the user:
268+
# If a user does not specify a max_mag, then a default is calculated.
269+
# Therefore, max_mag=None is not covered in this test case.
270+
# The same applies for `voxel_size`:
271+
# This is either extracted from the properties or set to comply with a specific sampling mode.
272+
273+
sampling_scheme = [Mag(m) for m in scheme]
274+
275+
assert sampling_scheme[1:] == calculate_mags_to_downsample(
276+
Mag(finest_mag), Mag(coarsest_mag), None, voxel_size
277+
), "The calculated downsampling scheme is wrong."
278+
279+
assert list(reversed(sampling_scheme[:-1])) == calculate_mags_to_upsample(
280+
Mag(coarsest_mag), Mag(finest_mag), None, voxel_size
281+
), "The calculated upsampling scheme is wrong."
282+
283+
284+
@pytest.mark.parametrize(
285+
"voxel_size, finest_mag, coarsest_mag",
286+
[
287+
# Anisotropic
288+
(
289+
(10.5, 10.5, 24),
290+
(2, 2, 2),
291+
(16, 16, 2),
292+
),
293+
(
294+
(10.5, 10.5, 24), # voxel_size
295+
(1, 1, 1), # from_mag
296+
(1, 1, 2), # max_mag
297+
),
298+
(
299+
(10.5, 10.5, 24),
300+
(1, 1, 1),
301+
(4, 4, 16),
302+
),
303+
],
304+
)
305+
@pytest.mark.timeout(1)
306+
def test_invalid_mag_calculation(
307+
voxel_size: Optional[Tuple[float, float, float]],
308+
finest_mag: Tuple[int, int, int],
309+
coarsest_mag: Tuple[int, int, int],
310+
) -> None:
311+
# This test does not test the exact input of the user:
312+
# If a user does not specify a max_mag, then a default is calculated.
313+
# Therefore, max_mag=None is not covered in this test case.
314+
# The same applies for `voxel_size`:
315+
# This is either extracted from the properties or set to comply with a specific sampling mode.
316+
317+
with pytest.raises(RuntimeError):
318+
calculate_mags_to_downsample(
319+
Mag(finest_mag), Mag(coarsest_mag), None, voxel_size
320+
)
284321

285322

286323
def test_default_max_mag() -> None:

webknossos/webknossos/dataset/_downsampling_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def calculate_mags_to_downsample(
9595
)
9696
# In case of isotropic resolution but anisotropic mag we need to ensure unique max dims.
9797
# current mag: 4-4-2, voxel_size: 1,1,1 -> new_mag: 4-4-4, therefore we skip this entry.
98-
if new_mag.max_dim == current_mag.max_dim:
98+
if new_mag.max_dim == current_mag.max_dim and new_mag != current_mag:
9999
current_mag = new_mag
100100
continue
101101
if new_mag == current_mag:

0 commit comments

Comments
 (0)