Add tests for z_stack

This commit is contained in:
Julian Stirling 2025-11-21 20:42:14 +00:00
parent b9c58b70c9
commit 573330dede
2 changed files with 95 additions and 2 deletions

View file

@ -28,6 +28,7 @@ from .stage import StageDependency as Stage
LOGGER = logging.getLogger(__name__)
MIN_TEST_IMAGE_COUNT = 3
MAX_TEST_IMAGE_COUNT = 9
EXTRA_STACK_CAPTURES = 15
class NotStreamingError(RuntimeError):
@ -133,7 +134,7 @@ class StackParams(BaseModel):
This is 15 images more then the minimum number that are captured.
"""
return self.min_images_to_test + 15
return self.min_images_to_test + EXTRA_STACK_CAPTURES
def slice_to_save(self, sharpest_index: int) -> slice:
"""Return the slice of images to save given the index of the sharpest image."""
@ -734,7 +735,7 @@ class AutofocusThing(lt.Thing):
ims_to_check = slice(-stack_parameters.min_images_to_test, None)
# If the sharpest image isn't found within the maximum number of images
# end the loop and return "restart"
# end the loop and return False indicating the stack failed.
while len(captures) < stack_parameters.max_images_to_test:
time.sleep(stack_parameters.settling_time)

View file

@ -20,6 +20,7 @@ from openflexure_microscope_server.things.autofocus import (
MAX_TEST_IMAGE_COUNT,
_get_capture_by_id,
_get_capture_index_by_id,
EXTRA_STACK_CAPTURES,
)
from openflexure_microscope_server.scan_directories import IMAGE_REGEX
@ -415,3 +416,94 @@ def test_run_smart_stack(pass_on, autofocus_thing, mocker):
# Mock called to save image
assert cam.save_from_memory.call_count == (1 if success else 0)
def setup_and_run_z_stack(check_returns, check_turning_points, autofocus_thing, mocker):
"""Set up a z_stack, run it, and return the result.
:param check_returns: The return values from check_stack_result. Note that if this
is a list it will be set as a side effect (and should be a list of tuples of
results. If it a tuple (or anything else) it is set as a return value.
"""
stack_params = autofocus_thing.create_stack_params(
autofocus_dz=2000,
images_dir="/this/is/fake",
save_resolution=(1640, 1232),
logger=LOGGER,
)
stack_params.settling_time = 0 # Don't settle or tests take forever.
stage = mocker.Mock()
cam = mocker.Mock()
autofocus_thing.capture_stack_image = mocker.Mock()
if isinstance(check_returns, list):
autofocus_thing.check_stack_result = mocker.Mock(side_effect=check_returns)
else:
autofocus_thing.check_stack_result = mocker.Mock(return_value=check_returns)
return autofocus_thing.z_stack(
stack_parameters=stack_params,
check_turning_points=check_turning_points,
cam=cam,
stage=stage,
)
def test_z_stack_turning_toggle_passed(autofocus_thing, mocker):
"""Check that the toggling of turning points is passed to the check."""
check_returns = ("success", "mock_id")
for check_turning in [True, False]:
setup_and_run_z_stack(check_returns, check_turning, autofocus_thing, mocker)
check_kwargs = autofocus_thing.check_stack_result.call_args.kwargs
assert check_kwargs["check_turning_points"] == check_turning
def test_z_stack_returns_on_success_and_restart(autofocus_thing, mocker):
"""Check that if the check returns success or restart then the stack exits with correct return value."""
for result in ["success", "restart"]:
check_returns = (result, "mock_id")
ret = setup_and_run_z_stack(check_returns, True, autofocus_thing, mocker)
assert autofocus_thing.check_stack_result.call_count == 1
# Check the number of images taken is exactly the call count.
ims_taken = autofocus_thing.capture_stack_image.call_count
assert ims_taken == autofocus_thing.stack_min_images_to_test
# And the result is as expected.
assert ret[0] == (result == "success")
def test_z_stack_exits_if_focus_never_found(autofocus_thing, mocker):
"""Check that if the check returns continue the stack exits eventually with a failure."""
check_returns = ("continue", "mock_id")
ret = setup_and_run_z_stack(check_returns, True, autofocus_thing, mocker)
assert autofocus_thing.check_stack_result.call_count == EXTRA_STACK_CAPTURES + 1
# Check the number of images taken is the maximum possible, set by the min images to
# test and the number of extra images that can be taken
ims_taken = autofocus_thing.capture_stack_image.call_count
max_ims = autofocus_thing.stack_min_images_to_test + EXTRA_STACK_CAPTURES
assert ims_taken == max_ims
# And the result is as expected.
assert not ret[0]
def test_z_stack_return(autofocus_thing, mocker):
"""Check z-stack returns as expected for more complex cases the fixed results above."""
for i in range(2, EXTRA_STACK_CAPTURES):
check_returns = [
("restart" if j == i - 1 else "continue", f"id_{j}") for j in range(i)
]
ret = setup_and_run_z_stack(check_returns, True, autofocus_thing, mocker)
# Calculate images taken
images_taken = autofocus_thing.stack_min_images_to_test + i - 1
assert autofocus_thing.capture_stack_image.call_count == images_taken
# Check it reports a failure
assert not ret[0]
# Repeat ending with a success rather than a failure
check_returns = [
("success" if j == i - 1 else "continue", f"id_{j}") for j in range(i)
]
ret = setup_and_run_z_stack(check_returns, True, autofocus_thing, mocker)
# Calculate images taken
assert autofocus_thing.capture_stack_image.call_count == images_taken
# Check it reports a success
assert ret[0]