From 4d5fd0b6986b57458f73d2a0b61aa696e8d87740 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 21 Jun 2026 13:41:29 +0100 Subject: [PATCH 1/3] Provide an API endpoint for camera snapshots rather than relying on legacy. --- .../server/legacy_api.py | 12 +++++++----- .../things/camera/__init__.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/openflexure_microscope_server/server/legacy_api.py b/src/openflexure_microscope_server/server/legacy_api.py index 69996f52..3de031ae 100644 --- a/src/openflexure_microscope_server/server/legacy_api.py +++ b/src/openflexure_microscope_server/server/legacy_api.py @@ -1,4 +1,4 @@ -"""Provide endpoints that mimic the v2 API for OpenFlexure Connect discoverability.""" +"""Provide endpoints that mimic the v2 API for legacy discoverability.""" from socket import gethostname @@ -22,12 +22,14 @@ class JPEGResponse(Response): def add_v2_endpoints(thing_server: lt.ThingServer) -> None: - """Add the v2 API endpoints for OpenFlexure Connect discoverability.""" + """Add the v2 API endpoints for legacy discoverability. + + Old versions of OpenFlexure Connect use the v2 endpoints to check for microscopes. + This provides these routes so the microscope can be discovered by old versions of + connect. + """ app = thing_server.app - # The endpoints below fool OpenFlexure Connect into thinking we are a - # v2 microscope, so we show up correctly. - # This is necessary until Connect is rebuilt. See #557. @app.get("/routes") def routes_stub() -> dict[str, dict]: """Return a stub list of routes. diff --git a/src/openflexure_microscope_server/things/camera/__init__.py b/src/openflexure_microscope_server/things/camera/__init__.py index fa32d6d6..b85b970e 100644 --- a/src/openflexure_microscope_server/things/camera/__init__.py +++ b/src/openflexure_microscope_server/things/camera/__init__.py @@ -20,6 +20,7 @@ from typing import Any, Literal, Mapping, Optional, Self import numpy as np import piexif +from fastapi import Response from PIL import Image from pydantic import BaseModel @@ -259,6 +260,21 @@ class BaseCamera(OFMThing, ABC): """Close hardware connection when the Thing context manager is closed.""" pass + @lt.endpoint( + "get", + "snapshot", + responses={ + 200: { + "description": "A snapshot of the microscope stream", + "content": {"image/jpeg": {}}, + }, + }, + ) + async def snapshot(self) -> Response: + """Return a snapshot from the microscope.""" + jpeg_data = await self.lores_mjpeg_stream.grab_frame() + return Response(content=jpeg_data, media_type="image/jpeg") + @property def focus_fom(self) -> int: """Return the focus figure of merit. From 267a02d0e81126ed9a93631c8340f73d9146a755 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 21 Jun 2026 14:07:00 +0100 Subject: [PATCH 2/3] Test snapshot image is generated --- tests/unit_tests/test_simulated_camera.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/unit_tests/test_simulated_camera.py b/tests/unit_tests/test_simulated_camera.py index 2c7d4a81..8b93152d 100644 --- a/tests/unit_tests/test_simulated_camera.py +++ b/tests/unit_tests/test_simulated_camera.py @@ -2,11 +2,13 @@ import logging import time +from io import BytesIO import numpy as np import pytest from hypothesis import given from hypothesis import strategies as st +from PIL import Image from pydantic import ValidationError import labthings_fastapi as lt @@ -44,6 +46,19 @@ def stage(test_env) -> lt.Thing: return test_env.get_thing_by_type(DummyStage) +def test_snapshot(test_env) -> lt.Thing: + """Check the snapshot GET request returns an image.""" + http_client = test_env.get_thing_client("camera").client + response = http_client.get("camera/snapshot") + response.raise_for_status() + assert response.headers["content-type"] == "image/jpeg" + image = Image.open(BytesIO(response.content)) + + assert image.format == "JPEG" + assert image.width == 320 + assert image.height == 240 + + def test_downsample_shape_2d(): """Test downsampling for 2D array.""" shape_2d = (100, 80) From 2af87e6a3522e0ab98589efcb7be2b5cb20eebff Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 21 Jun 2026 14:22:28 +0100 Subject: [PATCH 3/3] Update picamera test results --- picamera_coverage.zip | Bin 54038 -> 54038 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/picamera_coverage.zip b/picamera_coverage.zip index 3dd24bdf0d4481b7d92a3765fb75c76950bb2796..777a12e18607b7b40e84819d780916e24891cb5a 100644 GIT binary patch delta 349 zcmbQXjCtBJW}yIYW)=|!5Li=qHAXG3sb!;(N`uM?2L7M?v-mCfS@>S?#q;U#ZsAqu zdCjwvr-O%qdp36fw+z>b&5i=|xI%&iSs6JSRoH&nGi-^T%D~4akR`;x;K0DZ!NAbK zz`)YLAOL3b%m7)!z`(%B0Om6>eE4Ar7GeRD42%u@3J$3a%nS?%I8zvej8>E?=q$C?A8yOg;CRrLLnVKYSzHp(Q(JnbP z)xtQ@+|n#5$sozX(9po#+`=F!#l*zi#3;qW$jmIo#5^s@$U><$z?+dtgc&u+Cih;l KK-hlvk|zM;QexZy delta 347 zcmbQXjCtBJW}yIYW)=|!5UA)l8e=KmA-7RTr9tHs1OHF{S^Sp#EPOBc;`wxVxAAK7 zyye-=)5XKYJ(oL(TaN40W=DYqTp>Y%tc;wEDr~>x8E&ohVqjc|_sgZ$&p=DC4rIA6hagycc3m57c?JSIq%*`weQ_NC~ z)6!CtQVo(#5)%zf5>rePlPuEAQWA~L49qReOqFT_ycwB9m{9|4a_=PzgzaZ9c>(}_ Cs$Qx9