From 1e7069b37538937b6c8505a9e54249c46bb990a4 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Tue, 3 Dec 2024 00:16:26 +0000 Subject: [PATCH] Add more tests, speed up dummy stage I've sped up the stage motion, to run tests in a more reasonable time. CSM doesn't yet calibrate in the test - I suspect debugging of capture_array is needed. --- pyproject.toml | 44 +-------- .../things/stage/dummy.py | 5 +- tests/test_dummy_server.py | 94 ++++++++++++------- 3 files changed, 66 insertions(+), 77 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 05ec3ba5..9da31ef9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,49 +58,13 @@ build-backend = "setuptools.build_meta" # don't believe it's critical at present, because we don't use # the Python package - we distribute a tarball directly. -[tool.black] -exclude = '(\.eggs|\.git|\.venv|\venv|node_modules/)' - -[tool.isort] -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true -ensure_newline_before_comments = true -line_length = 88 - -[tool.pylint.'MESSAGES CONTROL'] -# W1203 warns about using f strings in logging statements. -# I'm really not concerned about the performance implications of this, -# particularly as we often have the log level set quite high. I think -# for our current purposes, using f strings in logging statements is -# more readable and thus a good idea in many places - I've disabled -# this warning code globally for that reason. -disable = "fixme,C,R,W1203" -max-line-length = 88 - [tool.mypy] plugins = ["pydantic.mypy"] [tool.ruff] target-version = "py39" -[tool.poe.executor] -type = "virtualenv" -location = ".venv" - -[tool.poe.tasks] -black = "black ." -black_check = "black --check ." -ruff = "ruff . --fix" -ruff_check = "ruff ." -isort = "isort openflexure_microscope" -pylint = "pylint openflexure_microscope" -mypy = "mypy --cobertura-xml-report openflexure_microscope openflexure_microscope" -test = "pytest . --junitxml=pytest_report.xml" -serve = "python -m openflexure_microscope.api.app" - -format = ["black", "isort", "ruff"] -lint = ["ruff_check", "pylint", "mypy"] - -check = ["format", "lint", "test"] \ No newline at end of file +[tool.pytest.ini_options] +addopts = [ + "--import-mode=importlib", +] diff --git a/src/openflexure_microscope_server/things/stage/dummy.py b/src/openflexure_microscope_server/things/stage/dummy.py index 907ebb16..ced0802c 100644 --- a/src/openflexure_microscope_server/things/stage/dummy.py +++ b/src/openflexure_microscope_server/things/stage/dummy.py @@ -13,6 +13,9 @@ class DummyStage(BaseStage): This stage should work similarly to a Sangaboard stage, but without any hardware attached. """ + def __init__(self, step_time: float=0.001, **kwargs): + super().__init__(**kwargs) + self.step_time = step_time def __enter__(self): self.instantaneous_position = self.position @@ -27,7 +30,7 @@ class DummyStage(BaseStage): self.moving = True try: fraction_complete = 0.0 - dt = 0.001 + dt = self.step_time max_displacement = max(abs(v) for v in displacement) start_time = time.time() while time.time() - start_time < dt * max_displacement: diff --git a/tests/test_dummy_server.py b/tests/test_dummy_server.py index 697fabd0..9ebe8754 100644 --- a/tests/test_dummy_server.py +++ b/tests/test_dummy_server.py @@ -1,4 +1,5 @@ import json +import os import tempfile from fastapi import Depends, FastAPI @@ -6,49 +7,70 @@ from fastapi.testclient import TestClient from labthings_fastapi.client import ThingClient from PIL import Image import piexif +import pytest from openflexure_microscope_server.server import ThingServer from openflexure_microscope_server.things.camera.simulation import SimulatedCamera from openflexure_microscope_server.things.stage.dummy import DummyStage from openflexure_microscope_server.things.autofocus import AutofocusThing +from openflexure_microscope_server.things.camera_stage_mapping import CameraStageMapper -temp_folder = tempfile.TemporaryDirectory() -server = ThingServer(temp_folder.name) -server.add_thing(SimulatedCamera(), "/camera/") -server.add_thing(DummyStage(), "/stage/") -server.add_thing(AutofocusThing(), "/autofocus/") +@pytest.fixture +def thing_server(): + temp_folder = tempfile.TemporaryDirectory() + server = ThingServer(settings_folder=temp_folder.name) + server.add_thing(SimulatedCamera(), "/camera/") + server.add_thing(DummyStage(step_time=0.000001), "/stage/") + server.add_thing(AutofocusThing(), "/autofocus/") + server.add_thing(CameraStageMapper(), "/camera_stage_mapping/") + assert os.path.exists(os.path.join(temp_folder.name, "camera/")) + # NB yield is important: otherwise, the temp folder gets deleted before the test runs + yield server -def test_autofocus(): - with TestClient(server.app) as client: - autofocus = ThingClient.from_url("/autofocus/", client) - _ = autofocus.fast_autofocus() +@pytest.fixture +def client(thing_server): + with TestClient(thing_server.app) as client: + yield client -def test_grab_jpeg(): - with TestClient(server.app) as client: - camera = ThingClient.from_url("/camera/", client) - blob = camera.grab_jpeg() - _image = Image.open(blob.open()) +@pytest.fixture +def slower_client(thing_server): + thing_server.things["/stage/"].step_time = 0.0002 + with TestClient(thing_server.app) as client: + yield client -def test_capture_jpeg_metadata(): - with TestClient(server.app) as client: - camera = ThingClient.from_url("/camera/", client) - blob = camera.capture_jpeg() - image = Image.open(blob.open()) - exif_dict = piexif.load(image.info["exif"]) - encoded_metadata = exif_dict["Exif"][piexif.ExifIFD.UserComment] - metadata = json.loads(encoded_metadata) - assert "position" in metadata["/stage/"] +def test_autofocus(slower_client): + client = slower_client + autofocus = ThingClient.from_url("/autofocus/", client) + _ = autofocus.fast_autofocus() -def test_stage(): - with TestClient(server.app) as client: - stage = ThingClient.from_url("/stage/", client) - start = stage.position - move = {"x": 1, "y": 2, "z": 3} - stage.move_relative(**move) - pos = stage.position - for s, m, p in zip(start.values(), move.values(), pos.values()): - assert s + m == p - stage.move_relative(**{k: -v for k, v in move.items()}) - pos = stage.position - for s, p in zip(start.values(), pos.values()): - assert s == p +def test_grab_jpeg(client): + camera = ThingClient.from_url("/camera/", client) + blob = camera.grab_jpeg() + _image = Image.open(blob.open()) + +def test_capture_jpeg_metadata(client): + camera = ThingClient.from_url("/camera/", client) + blob = camera.capture_jpeg() + image = Image.open(blob.open()) + exif_dict = piexif.load(image.info["exif"]) + encoded_metadata = exif_dict["Exif"][piexif.ExifIFD.UserComment] + metadata = json.loads(encoded_metadata) + assert "position" in metadata["/stage/"] + +def test_stage(client): + stage = ThingClient.from_url("/stage/", client) + start = stage.position + move = {"x": 1, "y": 2, "z": 3} + stage.move_relative(**move) + pos = stage.position + for s, m, p in zip(start.values(), move.values(), pos.values()): + assert s + m == p + stage.move_relative(**{k: -v for k, v in move.items()}) + pos = stage.position + for s, p in zip(start.values(), pos.values()): + assert s == p + +# Currently this fails, not yet sure why. +#def test_camera_stage_mapping_calibration(client): +# camera_stage_mapping = ThingClient.from_url("/camera_stage_mapping/", client) +# camera_stage_mapping.calibrate_xy()