From a4aac53072c0f826171152e98c9918bb805dd08c Mon Sep 17 00:00:00 2001 From: jaknapper Date: Fri, 13 Mar 2026 10:48:22 +0000 Subject: [PATCH 1/6] Flush more frames and use a request when doing exposure time --- .../things/camera/picamera_recalibrate_utils.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py b/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py index 0a5637ab..6e694301 100644 --- a/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py +++ b/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py @@ -253,9 +253,18 @@ def _test_exposure_settings(camera: Picamera2, percentile: float) -> _ExposureTe percentile (which will be compared to the target), as well as the camera's shutter and gain values. """ - camera.capture_array("raw") # controls might not be updated for the first frame? + # Flush stale frames + for _ in range(4): + r = camera.capture_request() + r.release() + + # A single request, to ensure metadata matches frame + request = camera.capture_request() + metadata = request.get_metadata() + image = request.make_array("raw") + request.release() max_brightness = np.percentile( - _channels_from_bayer_array(camera.capture_array("raw")), + _channels_from_bayer_array(image), percentile, ) # The reported brightness can, theoretically, be negative or zero @@ -268,7 +277,6 @@ def _test_exposure_settings(camera: Picamera2, percentile: float) -> _ExposureTe "camera's black level compensation has gone wrong." ) max_brightness = 1 - metadata = camera.capture_metadata() result = _ExposureTest( level=max_brightness, exposure_time=int(metadata["ExposureTime"]), From ae04f3b13322d4b245add041ee3428caf5d535b0 Mon Sep 17 00:00:00 2001 From: jaknapper Date: Fri, 13 Mar 2026 13:22:19 +0000 Subject: [PATCH 2/6] Drop frame drop to 2 --- .../things/camera/picamera_recalibrate_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py b/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py index 6e694301..b5bc6c06 100644 --- a/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py +++ b/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py @@ -254,10 +254,10 @@ def _test_exposure_settings(camera: Picamera2, percentile: float) -> _ExposureTe the camera's shutter and gain values. """ # Flush stale frames - for _ in range(4): + for _ in range(2): r = camera.capture_request() r.release() - + # A single request, to ensure metadata matches frame request = camera.capture_request() metadata = request.get_metadata() From 3b84c266b4635d450c1fdcdc141925895c636d49 Mon Sep 17 00:00:00 2001 From: Joe Knapper Date: Mon, 16 Mar 2026 14:31:21 +0000 Subject: [PATCH 3/6] Apply suggestions from code review of branch exposure-test Co-authored-by: Richard Bowman --- .../things/camera/picamera_recalibrate_utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py b/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py index b5bc6c06..90d6785f 100644 --- a/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py +++ b/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py @@ -260,9 +260,11 @@ def _test_exposure_settings(camera: Picamera2, percentile: float) -> _ExposureTe # A single request, to ensure metadata matches frame request = camera.capture_request() - metadata = request.get_metadata() - image = request.make_array("raw") - request.release() + try: + metadata = request.get_metadata() + image = request.make_array("raw") + finally: + request.release() max_brightness = np.percentile( _channels_from_bayer_array(image), percentile, From a11d46777fb19916b94a34cd9c3b572047225990 Mon Sep 17 00:00:00 2001 From: jaknapper Date: Mon, 16 Mar 2026 15:01:10 +0000 Subject: [PATCH 4/6] Only drop frames when setting min exposure time --- .../things/camera/picamera_recalibrate_utils.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py b/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py index 90d6785f..1ce49f86 100644 --- a/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py +++ b/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py @@ -241,6 +241,9 @@ def _set_minimum_exposure(camera: Picamera2, sensor_info: SensorInfo) -> None: # to the minimum possible, which is ~8us for PiCamera v2 camera.set_controls({"AeEnable": False, "AnalogueGain": 1, "ExposureTime": 1}) time.sleep(sensor_info.long_pause) + # Flush stale frames + for _ in range(2): + r = camera.capture_metadata() def _test_exposure_settings(camera: Picamera2, percentile: float) -> _ExposureTest: @@ -253,11 +256,6 @@ def _test_exposure_settings(camera: Picamera2, percentile: float) -> _ExposureTe percentile (which will be compared to the target), as well as the camera's shutter and gain values. """ - # Flush stale frames - for _ in range(2): - r = camera.capture_request() - r.release() - # A single request, to ensure metadata matches frame request = camera.capture_request() try: From 0bc6e45df91594bf1f05f8f5863e8317ecfcee37 Mon Sep 17 00:00:00 2001 From: jaknapper Date: Mon, 16 Mar 2026 15:07:13 +0000 Subject: [PATCH 5/6] Picamera test --- picamera_coverage.zip | Bin 54038 -> 54038 bytes .../camera/picamera_recalibrate_utils.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/picamera_coverage.zip b/picamera_coverage.zip index 09438bb3cc01239acf74a6c89d403e793896500e..f971f0639d28b4def872ad5b7978f6fb5492c1bc 100644 GIT binary patch delta 421 zcmbQXjCtBJW}yIYW)=|!5V%uO5c6d7^^}c5$_=8t?-}@i^1tKX!=J?Wgl`338J{!n z`^}C5S9$A$I9VAv8&%kT$urzq>&3v%z%Z48fq@BR1Oz)UFeorE7$iW2SQx;34u%7d zpMw<$fJp`x2D=7^sf>IK3=PbN94w5SDJ)e>TNolZ7#J8D3OYC#87AC)^M)t)_O{$+ zUTKyC7q%rfGs-YDH-=V5rxIC({%5t|ho3nORQHFB|g#4h9AX1_nk3#)d{V5TA>Ip@E@cp74kJo8>0RIBGiuOJ?!9CIcg5thmplO~1a>(9 delta 372 zcmbQXjCtBJW}yIYW)=|!5XhgD9@C!9xp$+Ga)T(JJOlqv{&)O)`IGsc@~z^l;7j6@ z-|Q&B$`|R#$;!yt$i((bp5fM7F9vo7hN%n;3Jg3mK!z|dFfcMOFmNz1Ffn}iVF?yu z0h0`j4g3lYsSV5w3=c#&SQt6ASXS}e-j*w!!<2BtEXSaMdGfM8BQ{Gm7DmpJ$@lu? zS$r7y8z(dMzcSQkW?|$^U}lOv%d^I*nSn(@;>E=Tg99843=Sac7#kYdc-ffOfbD%@ z$@rglv)lw32M&;jKK None: time.sleep(sensor_info.long_pause) # Flush stale frames for _ in range(2): - r = camera.capture_metadata() + _r = camera.capture_metadata() def _test_exposure_settings(camera: Picamera2, percentile: float) -> _ExposureTest: From aaee5c080a2235373f7bc00797097cd96507927f Mon Sep 17 00:00:00 2001 From: Joe Knapper Date: Tue, 17 Mar 2026 13:39:34 +0000 Subject: [PATCH 6/6] Picamera tests --- 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 f971f0639d28b4def872ad5b7978f6fb5492c1bc..f6005301f27a01c84f0b01e53e3c5e0d89705739 100644 GIT binary patch delta 289 zcmbQXjCtBJW}yIYW)=|!5C}^vj7by?G@B^YAr-;EAIB@jugjCp^O1WFj|FEacP6_I zryg6x#)DT_H}7RT#KzSkz{D;tD$3Zwyt$M2GNZAAf`Wpsu7ZMkMq+V>xQ_~X7Oj3-E3=GVTjS|gMCSSN1&A!!)JZ0B=Sn5oT0xPwu^B0e8dX HvzI&peMC^I delta 238 zcmbQXjCtBJW}yIYW)=|!5V%uO5c6d7^^}Q19a6mv{BgYg{JK1Ic-6Qs^5k-ManEB< z SGpZjZ_g=DqyJ7O#OP&CO8cP=d