From cb35fa53f963ef946c8b6d7d9d9922cfce3baf13 Mon Sep 17 00:00:00 2001 From: ben-ray Date: Sun, 29 Jun 2025 00:45:55 -0700 Subject: [PATCH 1/8] quick calibration 4x 20x --- pylabrobot/plate_reading/__init__.py | 2 +- pylabrobot/plate_reading/biotek_backend.py | 38 +++++++++++++++------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/pylabrobot/plate_reading/__init__.py b/pylabrobot/plate_reading/__init__.py index 966430ea09..8ceb68f08c 100644 --- a/pylabrobot/plate_reading/__init__.py +++ b/pylabrobot/plate_reading/__init__.py @@ -1,4 +1,4 @@ -from .biotek_backend import Cytation5Backend, Cytation5ImagingConfig +from .biotek_backend import Cytation5Backend, CytationImagingConfig from .clario_star_backend import CLARIOStarBackend from .image_reader import ImageReader from .imager import Imager diff --git a/pylabrobot/plate_reading/biotek_backend.py b/pylabrobot/plate_reading/biotek_backend.py index 29e0ee6d99..6749ddffbe 100644 --- a/pylabrobot/plate_reading/biotek_backend.py +++ b/pylabrobot/plate_reading/biotek_backend.py @@ -76,9 +76,13 @@ async def _golden_ratio_search( return (b + a) / 2 +class CytationType(enum.Enum): + Cytation5 = "Cytation5" + Cytation1 = "Cytation1" @dataclass -class Cytation5ImagingConfig: +class CytationImagingConfig: + model: CytationType = CytationType.Cytation5 camera_serial_number: Optional[str] = None max_image_read_attempts: int = 8 @@ -86,6 +90,18 @@ class Cytation5ImagingConfig: objectives: Optional[List[Optional[Objective]]] = None filters: Optional[List[Optional[ImagingMode]]] = None +_FOV: dict[CytationType, dict[int, Optional[tuple[float, float]]]] = { + CytationType.Cytation1: { + 4: (1288 / 596, 964 / 596), + 20: (1288 / 3000, 964 / 3000), + 40: None, # not calibrated + }, + CytationType.Cytation5: { + 4: (3474 / 1000, 3474 / 1000), + 20: (694 / 1000, 694 / 1000), + 40: (347 / 1000, 347 / 1000), + }, +} class Cytation5Backend(ImageReaderBackend): """Backend for biotek cytation 5 image reader. @@ -98,16 +114,15 @@ def __init__( self, timeout: float = 20, device_id: Optional[str] = None, - imaging_config: Optional[Cytation5ImagingConfig] = None, + imaging_config: Optional[CytationImagingConfig] = None, ) -> None: super().__init__() self.timeout = timeout self.io = FTDI(device_id=device_id) - self.spinnaker_system: Optional["PySpin.SystemPtr"] = None self.cam: Optional["PySpin.CameraPtr"] = None - self.imaging_config = imaging_config or Cytation5ImagingConfig() + self.imaging_config = imaging_config or CytationImagingConfig() self._filters: Optional[List[Optional[ImagingMode]]] = self.imaging_config.filters self._objectives: Optional[List[Optional[Objective]]] = self.imaging_config.objectives self._version: Optional[str] = None @@ -128,6 +143,7 @@ def __init__( async def setup(self, use_cam: bool = False) -> None: logger.info("[cytation5] setting up") + await self.io.setup() await self.io.usb_reset() await self.io.set_latency_timer(16) @@ -1173,14 +1189,12 @@ def image_size(magnification: float) -> Tuple[float, float]: # "wide fov" is an option in gen5.exe, but in reality it takes the same pictures. So we just # simply take the wide fov option. # um to mm (plr unit) - if magnification == 4: - return (3474 / 1000, 3474 / 1000) - if magnification == 20: - return (694 / 1000, 694 / 1000) - if magnification == 40: - return (347 / 1000, 347 / 1000) - raise ValueError(f"Don't know image size for magnification {magnification}") - + try: + size = _FOV[self.imaging_config.model][magnification] + except KeyError: + raise ValueError(f"Don't know image size for model {self.imaging_config.model} and magnification {magnification}") + return size + if self._objective is None: raise RuntimeError("Objective not set. Run set_objective() first.") magnification = self._objective.magnification From 32cf9b77fc25949adeb6155eb780cbf17fb25aeb Mon Sep 17 00:00:00 2001 From: ben-ray Date: Sun, 29 Jun 2025 00:51:24 -0700 Subject: [PATCH 2/8] ruff --- pylabrobot/plate_reading/biotek_backend.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pylabrobot/plate_reading/biotek_backend.py b/pylabrobot/plate_reading/biotek_backend.py index 6749ddffbe..72878cf046 100644 --- a/pylabrobot/plate_reading/biotek_backend.py +++ b/pylabrobot/plate_reading/biotek_backend.py @@ -76,10 +76,12 @@ async def _golden_ratio_search( return (b + a) / 2 + class CytationType(enum.Enum): Cytation5 = "Cytation5" Cytation1 = "Cytation1" + @dataclass class CytationImagingConfig: model: CytationType = CytationType.Cytation5 @@ -90,6 +92,7 @@ class CytationImagingConfig: objectives: Optional[List[Optional[Objective]]] = None filters: Optional[List[Optional[ImagingMode]]] = None + _FOV: dict[CytationType, dict[int, Optional[tuple[float, float]]]] = { CytationType.Cytation1: { 4: (1288 / 596, 964 / 596), @@ -103,6 +106,7 @@ class CytationImagingConfig: }, } + class Cytation5Backend(ImageReaderBackend): """Backend for biotek cytation 5 image reader. @@ -143,7 +147,6 @@ def __init__( async def setup(self, use_cam: bool = False) -> None: logger.info("[cytation5] setting up") - await self.io.setup() await self.io.usb_reset() await self.io.set_latency_timer(16) @@ -1192,9 +1195,11 @@ def image_size(magnification: float) -> Tuple[float, float]: try: size = _FOV[self.imaging_config.model][magnification] except KeyError: - raise ValueError(f"Don't know image size for model {self.imaging_config.model} and magnification {magnification}") + raise ValueError( + f"Don't know image size for model {self.imaging_config.model} and magnification {magnification}" + ) return size - + if self._objective is None: raise RuntimeError("Objective not set. Run set_objective() first.") magnification = self._objective.magnification From b96e1c569fc18983fd8e78ada1c4eb9c31274a06 Mon Sep 17 00:00:00 2001 From: ben-ray Date: Sun, 29 Jun 2025 02:26:00 -0700 Subject: [PATCH 3/8] switch to strings to fix autoreload issues --- pylabrobot/plate_reading/biotek_backend.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/pylabrobot/plate_reading/biotek_backend.py b/pylabrobot/plate_reading/biotek_backend.py index 72878cf046..4b8f14f695 100644 --- a/pylabrobot/plate_reading/biotek_backend.py +++ b/pylabrobot/plate_reading/biotek_backend.py @@ -1,5 +1,4 @@ import asyncio -import enum import logging import math import re @@ -76,15 +75,11 @@ async def _golden_ratio_search( return (b + a) / 2 - -class CytationType(enum.Enum): - Cytation5 = "Cytation5" - Cytation1 = "Cytation1" - +CytationModel = Literal["Cytation1", "Cytation5"] @dataclass class CytationImagingConfig: - model: CytationType = CytationType.Cytation5 + model: CytationModel = "Cytation5" camera_serial_number: Optional[str] = None max_image_read_attempts: int = 8 @@ -93,14 +88,13 @@ class CytationImagingConfig: filters: Optional[List[Optional[ImagingMode]]] = None -_FOV: dict[CytationType, dict[int, Optional[tuple[float, float]]]] = { - CytationType.Cytation1: { - 4: (1288 / 596, 964 / 596), +_FOV: dict[str, dict[int, Optional[tuple[float, float]]]] = { + "Cytation1": { + 4: (1288 / 596, 964 / 596), 20: (1288 / 3000, 964 / 3000), - 40: None, # not calibrated }, - CytationType.Cytation5: { - 4: (3474 / 1000, 3474 / 1000), + "Cytation5": { + 4: (3474 / 1000, 3474 / 1000), 20: (694 / 1000, 694 / 1000), 40: (347 / 1000, 347 / 1000), }, @@ -1227,7 +1221,7 @@ def image_size(magnification: float) -> Tuple[float, float]: images: List[Image] = [] for x_pos, y_pos in positions: await self.set_position(x=x_pos, y=y_pos) - await asyncio.sleep(0.1) + await asyncio.sleep(0.2) images.append( await self._acquire_image( color_processing_algorithm=color_processing_algorithm, pixel_format=pixel_format From 1bd000e347c2e7a6c98fc74a433f1a720c839622 Mon Sep 17 00:00:00 2001 From: ben-ray Date: Sun, 29 Jun 2025 02:27:51 -0700 Subject: [PATCH 4/8] switch to strings to fix issues with autoreload --- pylabrobot/plate_reading/biotek_backend.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pylabrobot/plate_reading/biotek_backend.py b/pylabrobot/plate_reading/biotek_backend.py index 4b8f14f695..ad145176e7 100644 --- a/pylabrobot/plate_reading/biotek_backend.py +++ b/pylabrobot/plate_reading/biotek_backend.py @@ -1,4 +1,5 @@ import asyncio +import enum import logging import math import re @@ -75,11 +76,11 @@ async def _golden_ratio_search( return (b + a) / 2 -CytationModel = Literal["Cytation1", "Cytation5"] +CytationModel = Literal["cytation1", "cytation5"] @dataclass class CytationImagingConfig: - model: CytationModel = "Cytation5" + model: CytationModel = "cytation5" camera_serial_number: Optional[str] = None max_image_read_attempts: int = 8 @@ -89,11 +90,11 @@ class CytationImagingConfig: _FOV: dict[str, dict[int, Optional[tuple[float, float]]]] = { - "Cytation1": { + "cytation1": { 4: (1288 / 596, 964 / 596), 20: (1288 / 3000, 964 / 3000), }, - "Cytation5": { + "cytation5": { 4: (3474 / 1000, 3474 / 1000), 20: (694 / 1000, 694 / 1000), 40: (347 / 1000, 347 / 1000), From 17c80682b10ea0cbdd65b0167585a0e5ea117038 Mon Sep 17 00:00:00 2001 From: ben-ray Date: Sun, 29 Jun 2025 02:53:18 -0700 Subject: [PATCH 5/8] reset sleep time oops --- pylabrobot/plate_reading/biotek_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylabrobot/plate_reading/biotek_backend.py b/pylabrobot/plate_reading/biotek_backend.py index ad145176e7..32d0481061 100644 --- a/pylabrobot/plate_reading/biotek_backend.py +++ b/pylabrobot/plate_reading/biotek_backend.py @@ -1222,7 +1222,7 @@ def image_size(magnification: float) -> Tuple[float, float]: images: List[Image] = [] for x_pos, y_pos in positions: await self.set_position(x=x_pos, y=y_pos) - await asyncio.sleep(0.2) + await asyncio.sleep(0.1) images.append( await self._acquire_image( color_processing_algorithm=color_processing_algorithm, pixel_format=pixel_format From 15ace2fe5d6af353cee59696701a88a71ee19dc1 Mon Sep 17 00:00:00 2001 From: ben-ray Date: Sun, 29 Jun 2025 03:08:42 -0700 Subject: [PATCH 6/8] ruff again --- pylabrobot/plate_reading/biotek_backend.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pylabrobot/plate_reading/biotek_backend.py b/pylabrobot/plate_reading/biotek_backend.py index 32d0481061..181f6f44bb 100644 --- a/pylabrobot/plate_reading/biotek_backend.py +++ b/pylabrobot/plate_reading/biotek_backend.py @@ -76,8 +76,10 @@ async def _golden_ratio_search( return (b + a) / 2 + CytationModel = Literal["cytation1", "cytation5"] + @dataclass class CytationImagingConfig: model: CytationModel = "cytation5" @@ -91,11 +93,11 @@ class CytationImagingConfig: _FOV: dict[str, dict[int, Optional[tuple[float, float]]]] = { "cytation1": { - 4: (1288 / 596, 964 / 596), + 4: (1288 / 596, 964 / 596), 20: (1288 / 3000, 964 / 3000), }, "cytation5": { - 4: (3474 / 1000, 3474 / 1000), + 4: (3474 / 1000, 3474 / 1000), 20: (694 / 1000, 694 / 1000), 40: (347 / 1000, 347 / 1000), }, From 196e824c04c7ee3aed38c25fbc9a9fd89e3dd8e0 Mon Sep 17 00:00:00 2001 From: ben-ray Date: Sun, 29 Jun 2025 18:35:26 -0700 Subject: [PATCH 7/8] fix typing --- pylabrobot/plate_reading/biotek_backend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylabrobot/plate_reading/biotek_backend.py b/pylabrobot/plate_reading/biotek_backend.py index 181f6f44bb..7e1875ff3a 100644 --- a/pylabrobot/plate_reading/biotek_backend.py +++ b/pylabrobot/plate_reading/biotek_backend.py @@ -91,7 +91,7 @@ class CytationImagingConfig: filters: Optional[List[Optional[ImagingMode]]] = None -_FOV: dict[str, dict[int, Optional[tuple[float, float]]]] = { +_FOV: dict[str, dict[float, tuple[float, float]]] = { "cytation1": { 4: (1288 / 596, 964 / 596), 20: (1288 / 3000, 964 / 3000), @@ -1196,9 +1196,9 @@ def image_size(magnification: float) -> Tuple[float, float]: f"Don't know image size for model {self.imaging_config.model} and magnification {magnification}" ) return size - if self._objective is None: raise RuntimeError("Objective not set. Run set_objective() first.") + magnification = self._objective.magnification img_width, img_height = image_size(magnification) From cccc4d3841728358278cfc97f1875ec60702676d Mon Sep 17 00:00:00 2001 From: ben-ray Date: Sun, 29 Jun 2025 18:35:40 -0700 Subject: [PATCH 8/8] ruf --- pylabrobot/plate_reading/biotek_backend.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pylabrobot/plate_reading/biotek_backend.py b/pylabrobot/plate_reading/biotek_backend.py index 7e1875ff3a..0917f809d2 100644 --- a/pylabrobot/plate_reading/biotek_backend.py +++ b/pylabrobot/plate_reading/biotek_backend.py @@ -1196,9 +1196,10 @@ def image_size(magnification: float) -> Tuple[float, float]: f"Don't know image size for model {self.imaging_config.model} and magnification {magnification}" ) return size + if self._objective is None: raise RuntimeError("Objective not set. Run set_objective() first.") - + magnification = self._objective.magnification img_width, img_height = image_size(magnification)