102 lines
3.1 KiB
Python
102 lines
3.1 KiB
Python
"""Unit tests for validating stack classification logic in AutofocusThing.
|
|
|
|
This module tests whether the `check_stack_result` method correctly classifies
|
|
z-stack sharpness profiles into "success", "continue", or "restart" categories
|
|
based on predefined test cases.
|
|
|
|
Sharpness profiles are loaded from a JSON file and converted into mock capture
|
|
objects to simulate real camera captures.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
|
|
import pytest
|
|
|
|
from labthings_fastapi.testing import create_thing_without_server
|
|
|
|
from openflexure_microscope_server.things.autofocus import AutofocusThing
|
|
|
|
THIS_DIR = os.path.dirname(__file__)
|
|
DATA_PATH = os.path.join(THIS_DIR, "data", "sharpness_test_cases.json")
|
|
|
|
|
|
class MockCapture:
|
|
"""Simple mock object representing a captured image.
|
|
|
|
This class mimics the minimal interface required by
|
|
`check_stack_result` by adding the `sharpness` and `buffer_id`
|
|
attributes.
|
|
|
|
:param sharpness: The sharpness value associated with the image.
|
|
:param buffer_id: A unique identifier for the image buffer.
|
|
"""
|
|
|
|
def __init__(self, sharpness, buffer_id):
|
|
"""Give each capture a sharpness and buffer_id."""
|
|
self.sharpness = sharpness
|
|
self.buffer_id = buffer_id
|
|
|
|
|
|
def make_captures(sharpness_list):
|
|
"""Convert a list of sharpness values into mock capture objects.
|
|
|
|
:param sharpness_list: A list of numeric sharpness values.
|
|
:returns: A list of MockCapture instances with sequential buffer IDs.
|
|
"""
|
|
return [MockCapture(s, i) for i, s in enumerate(sharpness_list)]
|
|
|
|
|
|
def load_cases():
|
|
"""Load the sharpness data from the json.
|
|
|
|
Includes the sharpnesses to test, manually written labels
|
|
on the required result, and whether the test is allowed to
|
|
fail. This ensures future tests will flag any regression on
|
|
tests while allowing improvements.
|
|
"""
|
|
with open(DATA_PATH) as f:
|
|
data = json.load(f)
|
|
|
|
params = []
|
|
for i, case in enumerate(data):
|
|
expected = case["label"]
|
|
|
|
# Allow multiple acceptable labels
|
|
if not isinstance(expected, list):
|
|
expected = [expected]
|
|
|
|
marks = []
|
|
|
|
if case.get("allow_failure", False):
|
|
marks.append(pytest.mark.xfail(reason="Known failing case"))
|
|
|
|
params.append(
|
|
pytest.param(
|
|
case["sharpnesses"],
|
|
expected,
|
|
marks=marks,
|
|
id=f"case_{i}",
|
|
)
|
|
)
|
|
return params
|
|
|
|
|
|
@pytest.mark.parametrize(("sharpnesses", "expected"), load_cases())
|
|
def test_stack_labelling(sharpnesses, expected):
|
|
"""Test stack classification accuracy against labelled sharpness cases.
|
|
|
|
This test loads the test from load_cases and evaluates
|
|
the classification returned by `check_stack_result`.
|
|
|
|
The test ensures that only cases marked with "allow_failure: true" can fail.
|
|
|
|
Expected labels may be a single value or a list of acceptable values.
|
|
"""
|
|
autofocus_thing = create_thing_without_server(AutofocusThing, mock_all_slots=True)
|
|
|
|
captures = make_captures(sharpnesses)
|
|
|
|
result, _ = autofocus_thing.check_stack_result(captures, check_turning_points=False)
|
|
|
|
assert result in expected
|