From 4e78f3d104b111cef2fb86d04c50a4b79162a978 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Tue, 24 Jun 2025 17:01:16 +0100 Subject: [PATCH] Fix exposure time adjusting when setting the same value and add test Closes #433 --- .../picamera2/test_exposure_time_drift.py | 49 +++++++++++++++---- .../things/camera/picamera.py | 7 +++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/hardware-specific-tests/picamera2/test_exposure_time_drift.py b/hardware-specific-tests/picamera2/test_exposure_time_drift.py index cf8e1976..1fe65e53 100644 --- a/hardware-specific-tests/picamera2/test_exposure_time_drift.py +++ b/hardware-specific-tests/picamera2/test_exposure_time_drift.py @@ -1,3 +1,10 @@ +""" +Check exposure times do not drift. + +This can get very tedious. Recommende running pytest with -s option +to monitor progress. +""" + import logging import time @@ -11,7 +18,7 @@ from openflexure_microscope_server.things.camera.picamera import StreamingPiCame logging.basicConfig(level=logging.DEBUG) -def test_exposure_time_drift(): +def _test_exposure_time_drift(desired_time): """ Capture 10 full resolution images and check that the exposure time remains constant @@ -23,16 +30,40 @@ def test_exposure_time_drift(): with TestClient(server.app) as test_client: client = ThingClient.from_url("/camera/", client=test_client) - # 5444 is an exposure time supported by PiCamera2 - client.exposure_time = 5444 + exposure_tol = cam.persistent_control_tolerances["ExposureTime"] + client.exposure_time = desired_time + print(f"Setting desired time of {desired_time}") time.sleep(0.5) - initial_et = client.exposure_time - print(f"Before capture, et is {client.exposure_time}") - for i in range(5): + pre_capture_et = client.exposure_time + print(f"Pre-capture the time is set to {pre_capture_et}") + # Check exp is set correctly within known tolerance + assert abs(pre_capture_et - desired_time) < exposure_tol + for i in range(10): client.capture_jpeg(resolution="full") - print(f"After capture, et is {client.exposure_time}") - final_et = client.exposure_time - assert initial_et == final_et + if i == 0: + # Exposure can update on first capture, due to frame rate restrictions + first_et = client.exposure_time + assert abs(first_et - pre_capture_et) < exposure_tol + frame_et = client.exposure_time + print(f"Frame {i} captured with exposure time {frame_et}") + # Check no further drift in value + assert first_et == frame_et + + # Set the exposure time to the value it already is. To check it doesn't shift + print(f"Setting exposure time to {frame_et} to check it doesn't change") + client.exposure_time = frame_et + time.sleep(0.5) + # Check before and after capture + assert client.exposure_time == frame_et + client.capture_jpeg(resolution="full") + assert client.exposure_time == frame_et + print("Exposure time didn't change!!") + print(f"End of test for exposure target {desired_time}") + + +def test_exposure_time_drift(): + for desired_time in [100, 1000, 10000]: + _test_exposure_time_drift(desired_time) if __name__ == "__main__": diff --git a/src/openflexure_microscope_server/things/camera/picamera.py b/src/openflexure_microscope_server/things/camera/picamera.py index 4372dabc..ecba1694 100644 --- a/src/openflexure_microscope_server/things/camera/picamera.py +++ b/src/openflexure_microscope_server/things/camera/picamera.py @@ -201,6 +201,13 @@ class StreamingPiCamera2(BaseCamera): "ExposureTime", int, description="The exposure time in microseconds" ) + @exposure_time.setter + def exposure_time(self, value: int): + with self.picamera() as cam: + # Note: This set a value 1 higher than requested as picamera2 always sets + # a lower value than requested, even if the requested is allowed + cam.set_controls({"ExposureTime": value + 1}) + _sensor_modes = None @thing_property