From d2aebf8b10cadfbc6f129687ff443c86b3fd5867 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Tue, 28 Apr 2026 14:50:04 +0100 Subject: [PATCH 1/2] Make properties of JPEGSharpnessMonitor read-only For thread-safety reasons we should not modify the lists used by `JPEGSharpnessMonitor`. This commit marks them as read-only properties and types them as `Sequence` so `mypy` should flag if they get modified. --- .../things/autofocus.py | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/openflexure_microscope_server/things/autofocus.py b/src/openflexure_microscope_server/things/autofocus.py index 61d1ff0a..5e8c9c85 100644 --- a/src/openflexure_microscope_server/things/autofocus.py +++ b/src/openflexure_microscope_server/things/autofocus.py @@ -263,10 +263,30 @@ class JPEGSharpnessMonitor: self.camera = camera self.stage = stage LOGGER.debug(f"Created sharpness monitor with {stage}, {camera}") - self.stage_positions: list[Mapping[str, int]] = [] - self.stage_times: list[float] = [] - self.jpeg_times: list[float] = [] - self.jpeg_sizes: list[int] = [] + self._stage_positions: list[Mapping[str, int]] = [] + self._stage_times: list[float] = [] + self._jpeg_times: list[float] = [] + self._jpeg_sizes: list[int] = [] + + @property + def stage_positions(self) -> Sequence[Mapping[str, int]]: + """The positions recorded for the stage.""" + return self._stage_positions + + @property + def stage_times(self) -> Sequence[float]: + """The timestamps recorded for stage position updates.""" + return self._stage_times + + @property + def jpeg_times(self) -> Sequence[float]: + """The timestamps recorded for captured JPEG frames.""" + return self._jpeg_times + + @property + def jpeg_sizes(self) -> Sequence[int]: + """The recorded JPEG frame sizes used as a sharpness metric.""" + return self._jpeg_sizes running = False From 9334e9955177d1abc3b41963eb8897a82af5c633 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Tue, 28 Apr 2026 14:58:55 +0100 Subject: [PATCH 2/2] Fix typing of JPEGSharpnessMonitor attributes appends should be done on the underlying `_`-prefixed attributes, not on the `Sequence` typed properties. --- .../things/autofocus.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/openflexure_microscope_server/things/autofocus.py b/src/openflexure_microscope_server/things/autofocus.py index 5e8c9c85..00cd7a4d 100644 --- a/src/openflexure_microscope_server/things/autofocus.py +++ b/src/openflexure_microscope_server/things/autofocus.py @@ -294,8 +294,8 @@ class JPEGSharpnessMonitor: """Start monitoring the frame sizes.""" self.running = True async for frame in self.camera.lores_mjpeg_stream.frame_async_generator(): - self.jpeg_times.append(time.time()) - self.jpeg_sizes.append(len(frame)) + self._jpeg_times.append(time.time()) + self._jpeg_sizes.append(len(frame)) if not self.running: break @@ -329,15 +329,15 @@ class JPEGSharpnessMonitor: collected. """ # Store the start time and position - self.stage_times.append(time.time()) - self.stage_positions.append(self.stage.position) + self._stage_times.append(time.time()) + self._stage_positions.append(self.stage.position) # Main move self.stage.move_relative(z=dz, block_cancellation=block_cancellation) # Store the end time and position - self.stage_times.append(time.time()) - self.stage_positions.append(self.stage.position) + self._stage_times.append(time.time()) + self._stage_positions.append(self.stage.position) # Index of the data for this movement data_index: int = len(self.stage_positions) - 2