From e7f669cb5676ae8018eb9a11d22231bdc506f555 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 14 Dec 2025 16:08:59 +0000 Subject: [PATCH] No blocking portals --- .../things/camera/opencv.py | 5 ++--- .../things/camera/picamera.py | 22 +++++-------------- .../things/camera/simulation.py | 5 ++--- tests/test_camera.py | 9 +++----- 4 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/openflexure_microscope_server/things/camera/opencv.py b/src/openflexure_microscope_server/things/camera/opencv.py index 5a6a1675..3882730e 100644 --- a/src/openflexure_microscope_server/things/camera/opencv.py +++ b/src/openflexure_microscope_server/things/camera/opencv.py @@ -70,18 +70,17 @@ class OpenCVCamera(BaseCamera): return False def _capture_frames(self) -> None: - portal = lt.get_blocking_portal(self) while self._capture_enabled: ret, frame = self.cap.read() if not ret: LOGGER.error(f"Failed to capture frame from camera {self.camera_index}") break jpeg = cv2.imencode(".jpg", frame)[1].tobytes() - self.mjpeg_stream.add_frame(jpeg, portal) + self.mjpeg_stream.add_frame(jpeg) jpeg_lores = cv2.imencode(".jpg", cv2.resize(frame, (320, 240)))[ 1 ].tobytes() - self.lores_mjpeg_stream.add_frame(jpeg_lores, portal) + self.lores_mjpeg_stream.add_frame(jpeg_lores) @lt.action def discard_frames(self) -> None: diff --git a/src/openflexure_microscope_server/things/camera/picamera.py b/src/openflexure_microscope_server/things/camera/picamera.py index c7b5ec0b..ac3078a1 100644 --- a/src/openflexure_microscope_server/things/camera/picamera.py +++ b/src/openflexure_microscope_server/things/camera/picamera.py @@ -68,9 +68,7 @@ class MissingCalibrationError(RuntimeError): class PicameraStreamOutput(Output): """An Output class that sends frames to a stream.""" - def __init__( - self, stream: lt.outputs.MJPEGStream, portal: lt.deps.BlockingPortal - ) -> None: + def __init__(self, stream: lt.outputs.MJPEGStream) -> None: """Create an output that puts frames in an MJPEGStream. We need to pass the stream object, and also the blocking portal, because @@ -80,7 +78,6 @@ class PicameraStreamOutput(Output): """ Output.__init__(self) self.stream = stream - self.portal = portal def outputframe( self, @@ -91,7 +88,7 @@ class PicameraStreamOutput(Output): _audio: bool = False, ) -> None: """Add a frame to the stream's ringbuffer.""" - self.stream.add_frame(frame, self.portal) + self.stream.add_frame(frame) class SensorMode(BaseModel): @@ -480,18 +477,12 @@ class StreamingPiCamera2(BaseCamera): stream_name = "lores" if main_resolution[0] > 1280 else "main" picam.start_recording( MJPEGEncoder(self.mjpeg_bitrate), - PicameraStreamOutput( - self.mjpeg_stream, - lt.get_blocking_portal(self), - ), + PicameraStreamOutput(self.mjpeg_stream), name=stream_name, ) picam.start_encoder( MJPEGEncoder(100000000), - PicameraStreamOutput( - self.lores_mjpeg_stream, - lt.get_blocking_portal(self), - ), + PicameraStreamOutput(self.lores_mjpeg_stream), name="lores", ) except Exception as e: @@ -515,9 +506,8 @@ class StreamingPiCamera2(BaseCamera): else: self.stream_active = False if stop_web_stream: - portal = lt.get_blocking_portal(self) - self.mjpeg_stream.stop(portal) - self.lores_mjpeg_stream.stop(portal) + self.mjpeg_stream.stop() + self.lores_mjpeg_stream.stop() LOGGER.info("Stopped MJPEG stream.") # Adding a sleep to prevent camera getting confused by rapid commands diff --git a/src/openflexure_microscope_server/things/camera/simulation.py b/src/openflexure_microscope_server/things/camera/simulation.py index 3c543a61..79a4cff8 100644 --- a/src/openflexure_microscope_server/things/camera/simulation.py +++ b/src/openflexure_microscope_server/things/camera/simulation.py @@ -290,7 +290,6 @@ class SimulatedCamera(BaseCamera): noise_level: float = lt.property(default=2.0) def _capture_frames(self) -> None: - portal = lt.get_blocking_portal(self) last_frame_t = time.time() while self._capture_enabled: wait_time = last_frame_t - time.time() - self.frame_interval @@ -299,9 +298,9 @@ class SimulatedCamera(BaseCamera): last_frame_t = time.time() try: frame = self.generate_frame() - self.mjpeg_stream.add_frame(_frame2bytes(frame), portal) + self.mjpeg_stream.add_frame(_frame2bytes(frame)) ds_frame = frame.resize((320, 240), resample=Image.NEAREST) - self.lores_mjpeg_stream.add_frame(_frame2bytes(ds_frame), portal) + self.lores_mjpeg_stream.add_frame(_frame2bytes(ds_frame)) except Exception as e: LOGGER.exception(f"Failed to capture frame: {e}, retrying...") diff --git a/tests/test_camera.py b/tests/test_camera.py index c6009f71..18c8214a 100644 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -51,8 +51,6 @@ def test_handle_broken_frame(): camera.mjpeg_stream.grab_frame = flaky_grabber with camera_server(camera): - portal = lt.get_blocking_portal(camera) - # Check that this does cause broken frames. # The noqa is because we don't know exactly when the error is thrown so we # can't have a single simple statement in the pytest raises. @@ -60,13 +58,13 @@ def test_handle_broken_frame(): OSError, match="broken data stream when reading image file" ): for _i in range(15): - jpeg = camera.grab_jpeg(portal) + jpeg = camera.grab_jpeg() np.asarray(Image.open(jpeg.open())) # Check that grab_as_array handles the broken frames and completes without # the same error. for _i in range(15): - array = camera.grab_as_array(portal) + array = camera.grab_as_array() assert isinstance(array, np.ndarray) @@ -74,8 +72,7 @@ def test_simulation_cam_calibration(): """Test that the simulated camera can be calibrated and reports calibration correctly.""" camera = SimulatedCamera() with camera_server(camera): - portal = lt.get_blocking_portal(camera) assert camera.calibration_required - camera.full_auto_calibrate(portal) + camera.full_auto_calibrate() assert not camera.calibration_required assert camera.background_detector_status.ready