From f5b65c42ec3e2e813a81087a49a03fc9fa6e6240 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Sat, 16 Aug 2025 12:48:09 -0700 Subject: [PATCH 1/6] allow rails up to -4 in hamilton deck --- pylabrobot/resources/hamilton/hamilton_decks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylabrobot/resources/hamilton/hamilton_decks.py b/pylabrobot/resources/hamilton/hamilton_decks.py index ad5adc3d0fe..aa68aa04858 100644 --- a/pylabrobot/resources/hamilton/hamilton_decks.py +++ b/pylabrobot/resources/hamilton/hamilton_decks.py @@ -149,8 +149,8 @@ def assign_child_resource( # TODO: many things here should be moved to Resource and Deck, instead of just STARLetDeck - if rails is not None and not 0 <= rails <= self.num_rails: - raise ValueError(f"Rails must be between 0 and {self.num_rails}.") + if rails is not None and not -4 <= rails <= self.num_rails: + raise ValueError(f"Rails must be between -4 and {self.num_rails}.") # Check if resource exists. if self.has_resource(resource.name): From 76f0b961289ab216ca9ea34f102cddce39d8bb31 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Sat, 16 Aug 2025 12:48:52 -0700 Subject: [PATCH 2/6] fix error message in lh.dispense96 --- pylabrobot/liquid_handling/liquid_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylabrobot/liquid_handling/liquid_handler.py b/pylabrobot/liquid_handling/liquid_handler.py index dec4840d6a0..3b147fb49ea 100644 --- a/pylabrobot/liquid_handling/liquid_handler.py +++ b/pylabrobot/liquid_handling/liquid_handler.py @@ -1761,7 +1761,7 @@ async def dispense96( else: if isinstance(resource, Plate): if resource.has_lid(): - raise ValueError("Aspirating from plate with lid") + raise ValueError("Dispensing to plate with lid") containers = resource.get_all_items() else: # List[Well] containers = resource From 63f0b710e5ab26f82cdeb609f4576d553e7ccd1d Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Sat, 16 Aug 2025 12:50:59 -0700 Subject: [PATCH 3/6] [cytomat] increase retrieve plate timeout --- pylabrobot/incubators/cytomat/cytomat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylabrobot/incubators/cytomat/cytomat.py b/pylabrobot/incubators/cytomat/cytomat.py index 48ceb58e94d..8e0028c2eff 100644 --- a/pylabrobot/incubators/cytomat/cytomat.py +++ b/pylabrobot/incubators/cytomat/cytomat.py @@ -263,7 +263,7 @@ async def action_transfer_to_storage( # used by insert_plate self, site: PlateHolder ) -> OverviewRegisterState: """Open lift door, retrieve from transfer, close door, place at storage""" - return await self.send_action("mv", "ts", self._site_to_firmware_string(site)) + return await self.send_action("mv", "ts", self._site_to_firmware_string(site), timeout=120) async def action_storage_to_transfer( # used by retrieve_plate self, site: PlateHolder From ca0eae4b3f21c9f6c24ab5470c52226e7eb6f623 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Sat, 16 Aug 2025 12:51:46 -0700 Subject: [PATCH 4/6] clarify cytation setup error message --- pylabrobot/plate_reading/biotek_backend.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pylabrobot/plate_reading/biotek_backend.py b/pylabrobot/plate_reading/biotek_backend.py index a522a537b75..7d5517eaac5 100644 --- a/pylabrobot/plate_reading/biotek_backend.py +++ b/pylabrobot/plate_reading/biotek_backend.py @@ -769,7 +769,14 @@ def _get_device_info(self, cam): for feature in features: node_feature = PySpin.CValuePtr(feature) node_feature_name = node_feature.GetName() - node_feature_value = node_feature.ToString() if PySpin.IsReadable(node_feature) else None + try: + node_feature_value = node_feature.ToString() if PySpin.IsReadable(node_feature) else None + except Exception as e: + raise RuntimeError( + f"Got an error while reading feature {node_feature_name}. " + "Is the cytation in use by another notebook? " + f"Error: {str(e)}" + ) from e device_info[node_feature_name] = node_feature_value return device_info From 9240c5ab2ed139a9017e458bf12004090cb38929 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Sat, 16 Aug 2025 12:54:55 -0700 Subject: [PATCH 5/6] [cytation] use _acquire_image in autofocus --- pylabrobot/plate_reading/biotek_backend.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/pylabrobot/plate_reading/biotek_backend.py b/pylabrobot/plate_reading/biotek_backend.py index 7d5517eaac5..35a5f76e238 100644 --- a/pylabrobot/plate_reading/biotek_backend.py +++ b/pylabrobot/plate_reading/biotek_backend.py @@ -890,17 +890,8 @@ async def auto_focus(self, timeout: float = 30): # objective function: variance of laplacian async def evaluate_focus(focus_value): - result = await self.capture( # TODO: _acquire_image - plate=plate, - row=row, - column=column, - mode=imaging_mode, - objective=objective, - focal_height=focus_value, - exposure_time=exposure, - gain=gain, - ) - image = result.images[0] + await self.set_focus(focus_value) + image = await self._acquire_image() if not CV2_AVAILABLE: raise RuntimeError( From 8076479fb13e3b9c2953279bcb208458c69072d5 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Sat, 16 Aug 2025 12:55:37 -0700 Subject: [PATCH 6/6] [cytation] only use inner 50% to focus --- pylabrobot/plate_reading/biotek_backend.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pylabrobot/plate_reading/biotek_backend.py b/pylabrobot/plate_reading/biotek_backend.py index 35a5f76e238..060dcee48a8 100644 --- a/pylabrobot/plate_reading/biotek_backend.py +++ b/pylabrobot/plate_reading/biotek_backend.py @@ -898,9 +898,15 @@ async def evaluate_focus(focus_value): f"cv2 needs to be installed for auto focus. Import error: {_CV2_IMPORT_ERROR}" ) + # cut out 25% on each side + np_image = np.array(image, dtype=np.float64) + height, width = np_image.shape[:2] + crop_height = height // 4 + crop_width = width // 4 + np_image = np_image[crop_height : height - crop_height, crop_width : width - crop_width] + # NVMG: Normalized Variance of the Gradient Magnitude # Chat invented this i think - np_image = np.array(image, dtype=np.float64) sobel_x = cv2.Sobel(np_image, cv2.CV_64F, 1, 0, ksize=3) sobel_y = cv2.Sobel(np_image, cv2.CV_64F, 0, 1, ksize=3) gradient_magnitude = np.sqrt(sobel_x**2 + sobel_y**2)