From a3481de468e41520aafae574f0040bdec3f63ae2 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 6 Jan 2025 09:58:57 +0000 Subject: [PATCH 01/13] add y_drive_mm_per_increment information --- .../liquid_handling/backends/hamilton/STAR.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index e75aa3bb26..370aa8fdc1 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7176,15 +7176,25 @@ async def stop_temperature_control_at_hhc(self, device_number: int): # -------------- Extra - Probing labware with STAR - making STAR into a CMM -------------- + y_drive_mm_per_increment= 0.046302082 z_drive_mm_per_increment = 0.01072765 + @staticmethod + def mm_to_y_drive_increment(value_mm: float) -> int: + return round(value_mm / STAR.y_drive_mm_per_increment) + + @staticmethod + def y_drive_increment_to_mm(value_mm: int) -> float: + return round(value_mm * STAR.y_drive_mm_per_increment, 2) + @staticmethod def mm_to_z_drive_increment(value_mm: float) -> int: return round(value_mm / STAR.z_drive_mm_per_increment) @staticmethod - def z_drive_increment_to_mm(value_mm: int) -> float: - return round(value_mm * STAR.z_drive_mm_per_increment, 2) + def z_drive_increment_to_mm(value_increments: int) -> float: + return round(value_increments * STAR.z_drive_mm_per_increment, 2) + async def clld_probe_z_height_using_channel( self, @@ -7206,7 +7216,7 @@ async def clld_probe_z_height_using_channel( Args: channel_idx: The index of the channel to use for probing. Backmost channel = 0. lowest_immers_pos: The lowest immersion position in mm. - start_pos_lld_search: The start position for z-touch search in mm. + start_pos_lld_search: The start position for clld search in mm. channel_speed: The speed of channel movement in mm/sec. channel_acceleration: The acceleration of the channel in mm/sec**2. detection_edge: The edge steepness at capacitive LLD detection. From 827771fdec90a8db053f565fb474143ef97e5695 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 6 Jan 2025 16:14:30 +0000 Subject: [PATCH 02/13] create clld_probe_y_position_using_channel --- .../liquid_handling/backends/hamilton/STAR.py | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index 370aa8fdc1..95c47c7b4b 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7294,6 +7294,178 @@ async def clld_probe_z_height_using_channel( return result_in_mm + + async def clld_probe_y_position_using_channel( + self, + channel_idx: int, # 0-based indexing of channels! + probing_direction: Literal["forward", "backward"], + start_pos_search: Optional[float] = None, # mm + end_pos_search: Optional[float] = None, # mm + channel_speed: float = 10.0, # mm/sec + channel_acceleration_int: Literal[1, 2, 3, 4] = 4, # * 5_000 steps/sec**2 == 926 mm/sec**2 + detection_edge: int = 10, + current_limit_int: Literal[1, 2, 3, 4, 5, 6, 7] = 7, + post_detection_dist: float = 2.0, # mm + ) -> float: + """ + Probes the y-position at which a conductive material is detected using + the channel's capacitive Liquid Level Detection (cLLD) capability. + + This method aims to provide safe probing within defined boundaries to + avoid collisions or damage to the system. It is specifically designed + for conductive materials. + + Args: + channel_idx (int): Index of the channel to use for probing (0-based). + The backmost channel is 0. + probing_direction (Literal["forward", "backward"]): Direction to move + the channel during probing. "forward" increases y-position, + "backward" decreases y-position. + start_pos_search (float, optional): Initial y-position for the search + (in mm). Defaults to the current y-position of the channel. + end_pos_search (float, optional): Final y-position for the search (in mm). + Defaults to the maximum y-position the channel can move to safely. + channel_speed (float): Speed of the channel's movement (in mm/sec). + Defaults to 10.0 mm/sec (i.e. slow default for safety). + channel_acceleration_int (Literal[1, 2, 3, 4]): Acceleration level, + corresponding to 1–4 (* 5,000 steps/sec²). Defaults to 4. + detection_edge (int): Steepness of the edge for capacitive detection. + Must be between 0 and 1023. Defaults to 10. + current_limit_int (Literal[1, 2, 3, 4, 5, 6, 7]): Current limit level, + from 1 to 7. Defaults to 7. + post_detection_dist (float): Distance to move away from the detected + material after detection (in mm). Defaults to 2.0 mm. + + Returns: + float: The detected y-position of the conductive material (in mm). + + Raises: + ValueError: + - If the probing direction is invalid. + - If the specified start or end positions are outside the safe range. + - If no conductive material is detected during the probing process. + """ + + assert probing_direction in ["forward", "backward"], ( + f"Probing direction must be either 'forward' or 'backward', is {probing_direction}." + ) + + # Anti-channel-crash feature + if channel_idx > 0: + channel_idx_minus_one_y_pos = await self.request_y_pos_channel_n(channel_idx-1) + else: + channel_idx_minus_one_y_pos = STAR.y_drive_increment_to_mm(15_000) + if channel_idx < (self.num_channels-1): + channel_idx_plus_one_y_pos = await self.request_y_pos_channel_n(channel_idx+1) + else: + channel_idx_plus_one_y_pos = 6 + # Insight: STAR machines appear to lose connection to a channel below y-position=6 mm + + max_safe_upper_y_pos = channel_idx_minus_one_y_pos - 9 + max_safe_lower_y_pos = channel_idx_plus_one_y_pos + 9 if channel_idx_plus_one_y_pos != 0 else 6 + + # Enable safe start and end positions + if start_pos_search: + assert max_safe_lower_y_pos <= start_pos_search <= max_safe_upper_y_pos, ( + f"Start position for y search must be between \n{max_safe_lower_y_pos} and" + + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." + ) + await self.move_channel_y(y=start_pos_search, channel=channel_idx) + + if end_pos_search: + assert max_safe_lower_y_pos <= end_pos_search <= max_safe_upper_y_pos, ( + f"End position for y search must be between \n{max_safe_lower_y_pos} and" + + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." + ) + + current_channel_y_pos = await self.request_y_pos_channel_n(channel_idx) + + # Set safe y-search end position based on the probing direction + if probing_direction == "forward": + max_y_search_pos = end_pos_search or max_safe_upper_y_pos + if max_y_search_pos < current_channel_y_pos: + raise ValueError( + f"Channel {channel_idx} cannot move forwards: " + f"End position = {max_y_search_pos} < current position = {current_channel_y_pos}" + f"\nDid you mean to move backwards?" + ) + else: # probing_direction == "backwards" + max_y_search_pos = end_pos_search or max_safe_lower_y_pos + if max_y_search_pos > current_channel_y_pos: + raise ValueError( + f"Channel {channel_idx} cannot move backwards: " + f"End position = {max_y_search_pos} > current position = {current_channel_y_pos}" + f"\nDid you mean to move forwards?" + ) + + # Convert mm to increments + max_y_search_pos_increments = STAR.mm_to_y_drive_increment(max_y_search_pos) + channel_speed_increments = STAR.mm_to_y_drive_increment(channel_speed) + + # Machine-compatability check of calculated parameters + assert 0 <= max_y_search_pos_increments <= 15_000, ( + f"Maximum y search position must be between \n0 and" + + f"{STAR.y_drive_increment_to_mm(15_000)} mm, is {max_y_search_pos_increments} mm" # STAR + ) + assert 20 <= channel_speed_increments <= 8_000, ( + f"LLD search speed must be between \n{STAR.y_drive_increment_to_mm(20)}" # STAR + + f"and {STAR.y_drive_increment_to_mm(8_000)} mm/sec, is {channel_speed} mm/sec" # STAR + ) + assert channel_acceleration_int in [1, 2, 3, 4], ( + f"Channel speed must be in [1, 2, 3, 4] (* 5_000 steps/sec**2)" + + f", is {channel_speed} mm/sec" + ) + assert ( + 0 <= detection_edge <= 1_0234 + ), "Edge steepness at capacitive LLD detection must be between 0 and 1023" + assert current_limit_int in [1, 2, 3, 4, 5, 6, 7], ( + f"Currrent limit must be any([1, 2, 3, 4], 5, 6, 7), is {channel_speed} mm/sec" + ) + + max_y_search_pos_str = f"{max_y_search_pos_increments:05}" + channel_speed_str = f"{channel_speed_increments:04}" + channel_acceleration_str = f"{channel_acceleration_int}" + detection_edge_str = f"{detection_edge:04}" + current_limit_str = f"{current_limit_int}" + + # Move channel for cLLD (Note: does not return detected y-position!) + await self.send_command( + module=f"P{channel_idx+1}", + command="YL", + ya=max_y_search_pos_str, # Maximum search position [steps] + gt= detection_edge_str, # Edge steepness at capacitive LLD detection + gl= f"{0:04}", # Offset after edge detection -> always 0 to measure y-pos! + yv=channel_speed_str, # Max speed [steps/second] + yr=channel_acceleration_str, # Acceleration ramp [yr * 5_000 steps/second**2] + yw=current_limit_str, # Current limit + ) + + detected_material_y_pos = await self.request_y_pos_channel_n(channel_idx) + + # Dynamically evaluate post-detection distance to avoid crashes + if probing_direction == "forward": + if channel_idx == self.num_channels - 1: # safe default + adjacent_y_pos = 6 + else: # next channel + adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx + 1) + + max_safe_y_mov_dist_post_detection = detected_material_y_pos - adjacent_y_pos - 9 + move_target = detected_material_y_pos - min(post_detection_dist, max_safe_y_mov_dist_post_detection) + + else: # probing_direction == "backwards" + if channel_idx == 0: # safe default + adjacent_y_pos = STAR.y_drive_increment_to_mm(15_000) + else: # previous channel + adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) + + max_safe_y_mov_dist_post_detection = adjacent_y_pos - detected_material_y_pos - 9 + move_target = detected_material_y_pos + min(post_detection_dist, max_safe_y_mov_dist_post_detection) + + await self.move_channel_y(y=move_target, channel=channel_idx) + + return detected_material_y_pos + + async def request_tip_len_on_channel( self, channel_idx: int, # 0-based indexing of channels! From 3bb196804d4a935ea23f76896d8785bb6b5ac136 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 6 Jan 2025 16:17:46 +0000 Subject: [PATCH 03/13] `make format` --- .../liquid_handling/backends/hamilton/STAR.py | 162 +++++++++--------- 1 file changed, 82 insertions(+), 80 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index 95c47c7b4b..e19e37245a 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7176,7 +7176,7 @@ async def stop_temperature_control_at_hhc(self, device_number: int): # -------------- Extra - Probing labware with STAR - making STAR into a CMM -------------- - y_drive_mm_per_increment= 0.046302082 + y_drive_mm_per_increment = 0.046302082 z_drive_mm_per_increment = 0.01072765 @staticmethod @@ -7195,7 +7195,6 @@ def mm_to_z_drive_increment(value_mm: float) -> int: def z_drive_increment_to_mm(value_increments: int) -> float: return round(value_increments * STAR.z_drive_mm_per_increment, 2) - async def clld_probe_z_height_using_channel( self, channel_idx: int, # 0-based indexing of channels! @@ -7294,7 +7293,6 @@ async def clld_probe_z_height_using_channel( return result_in_mm - async def clld_probe_y_position_using_channel( self, channel_idx: int, # 0-based indexing of channels! @@ -7306,110 +7304,111 @@ async def clld_probe_y_position_using_channel( detection_edge: int = 10, current_limit_int: Literal[1, 2, 3, 4, 5, 6, 7] = 7, post_detection_dist: float = 2.0, # mm - ) -> float: + ) -> float: """ - Probes the y-position at which a conductive material is detected using + Probes the y-position at which a conductive material is detected using the channel's capacitive Liquid Level Detection (cLLD) capability. This method aims to provide safe probing within defined boundaries to - avoid collisions or damage to the system. It is specifically designed + avoid collisions or damage to the system. It is specifically designed for conductive materials. Args: channel_idx (int): Index of the channel to use for probing (0-based). The backmost channel is 0. - probing_direction (Literal["forward", "backward"]): Direction to move - the channel during probing. "forward" increases y-position, + probing_direction (Literal["forward", "backward"]): Direction to move + the channel during probing. "forward" increases y-position, "backward" decreases y-position. - start_pos_search (float, optional): Initial y-position for the search + start_pos_search (float, optional): Initial y-position for the search (in mm). Defaults to the current y-position of the channel. - end_pos_search (float, optional): Final y-position for the search (in mm). + end_pos_search (float, optional): Final y-position for the search (in mm). Defaults to the maximum y-position the channel can move to safely. - channel_speed (float): Speed of the channel's movement (in mm/sec). + channel_speed (float): Speed of the channel's movement (in mm/sec). Defaults to 10.0 mm/sec (i.e. slow default for safety). - channel_acceleration_int (Literal[1, 2, 3, 4]): Acceleration level, + channel_acceleration_int (Literal[1, 2, 3, 4]): Acceleration level, corresponding to 1–4 (* 5,000 steps/sec²). Defaults to 4. detection_edge (int): Steepness of the edge for capacitive detection. Must be between 0 and 1023. Defaults to 10. - current_limit_int (Literal[1, 2, 3, 4, 5, 6, 7]): Current limit level, + current_limit_int (Literal[1, 2, 3, 4, 5, 6, 7]): Current limit level, from 1 to 7. Defaults to 7. - post_detection_dist (float): Distance to move away from the detected + post_detection_dist (float): Distance to move away from the detected material after detection (in mm). Defaults to 2.0 mm. Returns: float: The detected y-position of the conductive material (in mm). Raises: - ValueError: + ValueError: - If the probing direction is invalid. - If the specified start or end positions are outside the safe range. - If no conductive material is detected during the probing process. """ - assert probing_direction in ["forward", "backward"], ( - f"Probing direction must be either 'forward' or 'backward', is {probing_direction}." - ) + assert probing_direction in [ + "forward", + "backward", + ], f"Probing direction must be either 'forward' or 'backward', is {probing_direction}." # Anti-channel-crash feature if channel_idx > 0: - channel_idx_minus_one_y_pos = await self.request_y_pos_channel_n(channel_idx-1) + channel_idx_minus_one_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) else: - channel_idx_minus_one_y_pos = STAR.y_drive_increment_to_mm(15_000) - if channel_idx < (self.num_channels-1): - channel_idx_plus_one_y_pos = await self.request_y_pos_channel_n(channel_idx+1) + channel_idx_minus_one_y_pos = STAR.y_drive_increment_to_mm(15_000) + if channel_idx < (self.num_channels - 1): + channel_idx_plus_one_y_pos = await self.request_y_pos_channel_n(channel_idx + 1) else: - channel_idx_plus_one_y_pos = 6 - # Insight: STAR machines appear to lose connection to a channel below y-position=6 mm - + channel_idx_plus_one_y_pos = 6 + # Insight: STAR machines appear to lose connection to a channel below y-position=6 mm + max_safe_upper_y_pos = channel_idx_minus_one_y_pos - 9 max_safe_lower_y_pos = channel_idx_plus_one_y_pos + 9 if channel_idx_plus_one_y_pos != 0 else 6 - + # Enable safe start and end positions if start_pos_search: - assert max_safe_lower_y_pos <= start_pos_search <= max_safe_upper_y_pos, ( - f"Start position for y search must be between \n{max_safe_lower_y_pos} and" - + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." - ) - await self.move_channel_y(y=start_pos_search, channel=channel_idx) + assert max_safe_lower_y_pos <= start_pos_search <= max_safe_upper_y_pos, ( + f"Start position for y search must be between \n{max_safe_lower_y_pos} and" + + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." + ) + await self.move_channel_y(y=start_pos_search, channel=channel_idx) if end_pos_search: - assert max_safe_lower_y_pos <= end_pos_search <= max_safe_upper_y_pos, ( - f"End position for y search must be between \n{max_safe_lower_y_pos} and" - + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." - ) - + assert max_safe_lower_y_pos <= end_pos_search <= max_safe_upper_y_pos, ( + f"End position for y search must be between \n{max_safe_lower_y_pos} and" + + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." + ) + current_channel_y_pos = await self.request_y_pos_channel_n(channel_idx) # Set safe y-search end position based on the probing direction if probing_direction == "forward": - max_y_search_pos = end_pos_search or max_safe_upper_y_pos - if max_y_search_pos < current_channel_y_pos: - raise ValueError( - f"Channel {channel_idx} cannot move forwards: " - f"End position = {max_y_search_pos} < current position = {current_channel_y_pos}" - f"\nDid you mean to move backwards?" - ) + max_y_search_pos = end_pos_search or max_safe_upper_y_pos + if max_y_search_pos < current_channel_y_pos: + raise ValueError( + f"Channel {channel_idx} cannot move forwards: " + f"End position = {max_y_search_pos} < current position = {current_channel_y_pos}" + f"\nDid you mean to move backwards?" + ) else: # probing_direction == "backwards" - max_y_search_pos = end_pos_search or max_safe_lower_y_pos - if max_y_search_pos > current_channel_y_pos: - raise ValueError( - f"Channel {channel_idx} cannot move backwards: " - f"End position = {max_y_search_pos} > current position = {current_channel_y_pos}" - f"\nDid you mean to move forwards?" - ) + max_y_search_pos = end_pos_search or max_safe_lower_y_pos + if max_y_search_pos > current_channel_y_pos: + raise ValueError( + f"Channel {channel_idx} cannot move backwards: " + f"End position = {max_y_search_pos} > current position = {current_channel_y_pos}" + f"\nDid you mean to move forwards?" + ) # Convert mm to increments max_y_search_pos_increments = STAR.mm_to_y_drive_increment(max_y_search_pos) channel_speed_increments = STAR.mm_to_y_drive_increment(channel_speed) - + # Machine-compatability check of calculated parameters assert 0 <= max_y_search_pos_increments <= 15_000, ( f"Maximum y search position must be between \n0 and" - + f"{STAR.y_drive_increment_to_mm(15_000)} mm, is {max_y_search_pos_increments} mm" # STAR + + f"{STAR.y_drive_increment_to_mm(15_000)} mm, is {max_y_search_pos_increments} mm" # STAR ) assert 20 <= channel_speed_increments <= 8_000, ( - f"LLD search speed must be between \n{STAR.y_drive_increment_to_mm(20)}" # STAR - + f"and {STAR.y_drive_increment_to_mm(8_000)} mm/sec, is {channel_speed} mm/sec" # STAR + f"LLD search speed must be between \n{STAR.y_drive_increment_to_mm(20)}" # STAR + + f"and {STAR.y_drive_increment_to_mm(8_000)} mm/sec, is {channel_speed} mm/sec" # STAR ) assert channel_acceleration_int in [1, 2, 3, 4], ( f"Channel speed must be in [1, 2, 3, 4] (* 5_000 steps/sec**2)" @@ -7430,42 +7429,45 @@ async def clld_probe_y_position_using_channel( # Move channel for cLLD (Note: does not return detected y-position!) await self.send_command( - module=f"P{channel_idx+1}", - command="YL", - ya=max_y_search_pos_str, # Maximum search position [steps] - gt= detection_edge_str, # Edge steepness at capacitive LLD detection - gl= f"{0:04}", # Offset after edge detection -> always 0 to measure y-pos! - yv=channel_speed_str, # Max speed [steps/second] - yr=channel_acceleration_str, # Acceleration ramp [yr * 5_000 steps/second**2] - yw=current_limit_str, # Current limit - ) + module=f"P{channel_idx+1}", + command="YL", + ya=max_y_search_pos_str, # Maximum search position [steps] + gt=detection_edge_str, # Edge steepness at capacitive LLD detection + gl=f"{0:04}", # Offset after edge detection -> always 0 to measure y-pos! + yv=channel_speed_str, # Max speed [steps/second] + yr=channel_acceleration_str, # Acceleration ramp [yr * 5_000 steps/second**2] + yw=current_limit_str, # Current limit + ) detected_material_y_pos = await self.request_y_pos_channel_n(channel_idx) # Dynamically evaluate post-detection distance to avoid crashes if probing_direction == "forward": - if channel_idx == self.num_channels - 1: # safe default - adjacent_y_pos = 6 - else: # next channel - adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx + 1) - - max_safe_y_mov_dist_post_detection = detected_material_y_pos - adjacent_y_pos - 9 - move_target = detected_material_y_pos - min(post_detection_dist, max_safe_y_mov_dist_post_detection) - + if channel_idx == self.num_channels - 1: # safe default + adjacent_y_pos = 6 + else: # next channel + adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx + 1) + + max_safe_y_mov_dist_post_detection = detected_material_y_pos - adjacent_y_pos - 9 + move_target = detected_material_y_pos - min( + post_detection_dist, max_safe_y_mov_dist_post_detection + ) + else: # probing_direction == "backwards" - if channel_idx == 0: # safe default - adjacent_y_pos = STAR.y_drive_increment_to_mm(15_000) - else: # previous channel - adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) - - max_safe_y_mov_dist_post_detection = adjacent_y_pos - detected_material_y_pos - 9 - move_target = detected_material_y_pos + min(post_detection_dist, max_safe_y_mov_dist_post_detection) - + if channel_idx == 0: # safe default + adjacent_y_pos = STAR.y_drive_increment_to_mm(15_000) + else: # previous channel + adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) + + max_safe_y_mov_dist_post_detection = adjacent_y_pos - detected_material_y_pos - 9 + move_target = detected_material_y_pos + min( + post_detection_dist, max_safe_y_mov_dist_post_detection + ) + await self.move_channel_y(y=move_target, channel=channel_idx) return detected_material_y_pos - async def request_tip_len_on_channel( self, channel_idx: int, # 0-based indexing of channels! From 228b96b6a05b11a7229059094e9e01485c0db070 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 6 Jan 2025 16:57:01 +0000 Subject: [PATCH 04/13] fix type checking --- pylabrobot/liquid_handling/backends/hamilton/STAR.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index e19e37245a..cbcee8e332 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7403,7 +7403,7 @@ async def clld_probe_y_position_using_channel( # Machine-compatability check of calculated parameters assert 0 <= max_y_search_pos_increments <= 15_000, ( - f"Maximum y search position must be between \n0 and" + "Maximum y search position must be between \n0 and" + f"{STAR.y_drive_increment_to_mm(15_000)} mm, is {max_y_search_pos_increments} mm" # STAR ) assert 20 <= channel_speed_increments <= 8_000, ( @@ -7411,7 +7411,7 @@ async def clld_probe_y_position_using_channel( + f"and {STAR.y_drive_increment_to_mm(8_000)} mm/sec, is {channel_speed} mm/sec" # STAR ) assert channel_acceleration_int in [1, 2, 3, 4], ( - f"Channel speed must be in [1, 2, 3, 4] (* 5_000 steps/sec**2)" + "Channel speed must be in [1, 2, 3, 4] (* 5_000 steps/sec**2)" + f", is {channel_speed} mm/sec" ) assert ( @@ -7444,11 +7444,11 @@ async def clld_probe_y_position_using_channel( # Dynamically evaluate post-detection distance to avoid crashes if probing_direction == "forward": if channel_idx == self.num_channels - 1: # safe default - adjacent_y_pos = 6 + adjacent_y_pos = 6.0 else: # next channel adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx + 1) - max_safe_y_mov_dist_post_detection = detected_material_y_pos - adjacent_y_pos - 9 + max_safe_y_mov_dist_post_detection = detected_material_y_pos - adjacent_y_pos - 9.0 move_target = detected_material_y_pos - min( post_detection_dist, max_safe_y_mov_dist_post_detection ) @@ -7459,7 +7459,7 @@ async def clld_probe_y_position_using_channel( else: # previous channel adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) - max_safe_y_mov_dist_post_detection = adjacent_y_pos - detected_material_y_pos - 9 + max_safe_y_mov_dist_post_detection = adjacent_y_pos - detected_material_y_pos - 9.0 move_target = detected_material_y_pos + min( post_detection_dist, max_safe_y_mov_dist_post_detection ) From 59375dfba76a5da120c208183aa3772925fc122a Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 6 Jan 2025 17:00:43 +0000 Subject: [PATCH 05/13] silly `make format`-forced multi-line split of list --- pylabrobot/liquid_handling/backends/hamilton/STAR.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index cbcee8e332..c10e6e2d2f 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7417,9 +7417,15 @@ async def clld_probe_y_position_using_channel( assert ( 0 <= detection_edge <= 1_0234 ), "Edge steepness at capacitive LLD detection must be between 0 and 1023" - assert current_limit_int in [1, 2, 3, 4, 5, 6, 7], ( - f"Currrent limit must be any([1, 2, 3, 4], 5, 6, 7), is {channel_speed} mm/sec" - ) + assert current_limit_int in [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + ], f"Currrent limit must be in [1, 2, 3, 4, 5, 6, 7], is {channel_speed} mm/sec" max_y_search_pos_str = f"{max_y_search_pos_increments:05}" channel_speed_str = f"{channel_speed_increments:04}" From 3cf2f4f0fca70b1d235c915d745275abedc03025 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Tue, 7 Jan 2025 16:21:21 +0000 Subject: [PATCH 06/13] update safe back position / protect channel_0 --- .../liquid_handling/backends/hamilton/STAR.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index c10e6e2d2f..3c67ac2a78 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7328,7 +7328,7 @@ async def clld_probe_y_position_using_channel( channel_acceleration_int (Literal[1, 2, 3, 4]): Acceleration level, corresponding to 1–4 (* 5,000 steps/sec²). Defaults to 4. detection_edge (int): Steepness of the edge for capacitive detection. - Must be between 0 and 1023. Defaults to 10. + Must be between 0 and 1024. Defaults to 10. current_limit_int (Literal[1, 2, 3, 4, 5, 6, 7]): Current limit level, from 1 to 7. Defaults to 7. post_detection_dist (float): Distance to move away from the detected @@ -7353,7 +7353,7 @@ async def clld_probe_y_position_using_channel( if channel_idx > 0: channel_idx_minus_one_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) else: - channel_idx_minus_one_y_pos = STAR.y_drive_increment_to_mm(15_000) + channel_idx_minus_one_y_pos = STAR.y_drive_increment_to_mm(13_714) + 9 # y-position=635 mm if channel_idx < (self.num_channels - 1): channel_idx_plus_one_y_pos = await self.request_y_pos_channel_n(channel_idx + 1) else: @@ -7366,14 +7366,14 @@ async def clld_probe_y_position_using_channel( # Enable safe start and end positions if start_pos_search: assert max_safe_lower_y_pos <= start_pos_search <= max_safe_upper_y_pos, ( - f"Start position for y search must be between \n{max_safe_lower_y_pos} and" + f"Start position for y search must be between \n{max_safe_lower_y_pos} and " + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." ) await self.move_channel_y(y=start_pos_search, channel=channel_idx) if end_pos_search: assert max_safe_lower_y_pos <= end_pos_search <= max_safe_upper_y_pos, ( - f"End position for y search must be between \n{max_safe_lower_y_pos} and" + f"End position for y search must be between \n{max_safe_lower_y_pos} and " + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." ) @@ -7402,13 +7402,13 @@ async def clld_probe_y_position_using_channel( channel_speed_increments = STAR.mm_to_y_drive_increment(channel_speed) # Machine-compatability check of calculated parameters - assert 0 <= max_y_search_pos_increments <= 15_000, ( + assert 0 <= max_y_search_pos_increments <= 13_714, ( "Maximum y search position must be between \n0 and" - + f"{STAR.y_drive_increment_to_mm(15_000)} mm, is {max_y_search_pos_increments} mm" # STAR + + f"{STAR.y_drive_increment_to_mm(13_714)+9} mm, is {max_y_search_pos_increments} mm" ) assert 20 <= channel_speed_increments <= 8_000, ( - f"LLD search speed must be between \n{STAR.y_drive_increment_to_mm(20)}" # STAR - + f"and {STAR.y_drive_increment_to_mm(8_000)} mm/sec, is {channel_speed} mm/sec" # STAR + f"LLD search speed must be between \n{STAR.y_drive_increment_to_mm(20)}" + + f"and {STAR.y_drive_increment_to_mm(8_000)} mm/sec, is {channel_speed} mm/sec" ) assert channel_acceleration_int in [1, 2, 3, 4], ( "Channel speed must be in [1, 2, 3, 4] (* 5_000 steps/sec**2)" @@ -7461,7 +7461,7 @@ async def clld_probe_y_position_using_channel( else: # probing_direction == "backwards" if channel_idx == 0: # safe default - adjacent_y_pos = STAR.y_drive_increment_to_mm(15_000) + adjacent_y_pos = STAR.y_drive_increment_to_mm(13_714)+9 # y-position=635 mm else: # previous channel adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) From 14eb82ec5fa1a28826720c378471bc7f9af14d10 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Wed, 8 Jan 2025 09:13:54 +0000 Subject: [PATCH 07/13] `make format` --- pylabrobot/liquid_handling/backends/hamilton/STAR.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index 3c67ac2a78..e4372020e4 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7353,7 +7353,7 @@ async def clld_probe_y_position_using_channel( if channel_idx > 0: channel_idx_minus_one_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) else: - channel_idx_minus_one_y_pos = STAR.y_drive_increment_to_mm(13_714) + 9 # y-position=635 mm + channel_idx_minus_one_y_pos = STAR.y_drive_increment_to_mm(13_714) + 9 # y-position=635 mm if channel_idx < (self.num_channels - 1): channel_idx_plus_one_y_pos = await self.request_y_pos_channel_n(channel_idx + 1) else: @@ -7461,7 +7461,7 @@ async def clld_probe_y_position_using_channel( else: # probing_direction == "backwards" if channel_idx == 0: # safe default - adjacent_y_pos = STAR.y_drive_increment_to_mm(13_714)+9 # y-position=635 mm + adjacent_y_pos = STAR.y_drive_increment_to_mm(13_714) + 9 # y-position=635 mm else: # previous channel adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) From d5a83a1912595d4eb2571c964ee25fb18c923ec5 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Fri, 17 Jan 2025 17:33:29 +0000 Subject: [PATCH 08/13] autonmous tip_bottom_diameter identification --- .../liquid_handling/backends/hamilton/STAR.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index e3eafb3e5d..9e54bd953a 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7221,6 +7221,7 @@ def z_drive_increment_to_mm(value_increments: int) -> float: async def clld_probe_z_height_using_channel( self, channel_idx: int, # 0-based indexing of channels! + tip_len: Optional[float] = None, # mm lowest_immers_pos: float = 99.98, # mm start_pos_search: float = 330.0, # mm channel_speed: float = 10.0, # mm @@ -7320,6 +7321,7 @@ async def clld_probe_y_position_using_channel( self, channel_idx: int, # 0-based indexing of channels! probing_direction: Literal["forward", "backward"], + tip_len: Optional[float] = None, # mm start_pos_search: Optional[float] = None, # mm end_pos_search: Optional[float] = None, # mm channel_speed: float = 10.0, # mm/sec @@ -7401,6 +7403,18 @@ async def clld_probe_y_position_using_channel( ) current_channel_y_pos = await self.request_y_pos_channel_n(channel_idx) + current_channel_z_pos = await self.request_z_pos_channel_n(channel_idx) + if tip_len is None: + tip_len = await self.request_tip_len_on_channel(channel_idx=channel_idx) + await self.move_channel_z(z=current_channel_z_pos, channel=channel_idx) + + tip_len_to_tip_bottom_diameter_dict = { + 29.9: 0.8, # 10 ul tip + 50.4: 0.7, # 50 ul tip + 59.9: 1.2, # teaching needle - BUT not 300 ul tip! + 95.1: 1.2 # 1000 ul tip + } + tip_bottom_diameter = tip_len_to_tip_bottom_diameter_dict[tip_len] # Set safe y-search end position based on the probing direction if probing_direction == "forward": @@ -7495,7 +7509,13 @@ async def clld_probe_y_position_using_channel( await self.move_channel_y(y=move_target, channel=channel_idx) - return detected_material_y_pos + # Correct for tip_bottom_diameter + if probing_direction == "forward": + material_y_pos = detected_material_y_pos + tip_bottom_diameter / 2 + else: + material_y_pos = detected_material_y_pos - tip_bottom_diameter / 2 + + return material_y_pos async def request_tip_len_on_channel( self, From 3a415a5865023a8d56f81f72074f312bfd35e6df Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Fri, 17 Jan 2025 17:46:55 +0000 Subject: [PATCH 09/13] `make format` --- pylabrobot/liquid_handling/backends/hamilton/STAR.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index bfbe1b8d63..5be1894184 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7413,8 +7413,8 @@ async def clld_probe_y_position_using_channel( tip_len_to_tip_bottom_diameter_dict = { 29.9: 0.8, # 10 ul tip 50.4: 0.7, # 50 ul tip - 59.9: 1.2, # teaching needle - BUT not 300 ul tip! - 95.1: 1.2 # 1000 ul tip + 59.9: 1.2, # teaching needle - BUT not 300 ul tip! + 95.1: 1.2, # 1000 ul tip } tip_bottom_diameter = tip_len_to_tip_bottom_diameter_dict[tip_len] From 4aedcf10ffb2600d9895569259c7d901b1d5b603 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Sun, 19 Jan 2025 20:47:41 -0800 Subject: [PATCH 10/13] add tip size bottom to HamiltonTip --- pylabrobot/resources/hamilton/tip_creators.py | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pylabrobot/resources/hamilton/tip_creators.py b/pylabrobot/resources/hamilton/tip_creators.py index 474c98dbe6..157f3b4937 100644 --- a/pylabrobot/resources/hamilton/tip_creators.py +++ b/pylabrobot/resources/hamilton/tip_creators.py @@ -6,7 +6,7 @@ """ import enum -from typing import Union +from typing import Optional, Union from pylabrobot.resources.tip import Tip @@ -46,7 +46,13 @@ def __init__( maximal_volume: float, tip_size: Union[TipSize, str], # union for deserialization, will probably refactor pickup_method: Union[TipPickupMethod, str], # union for deserialization, will probably refactor + tip_size_bottom: Optional[float], ): + """Create a new Hamilton tip. + + Args: + tip_size_bottom: diameter of the tip at the bottom. None if not known. + """ if isinstance(tip_size, str): tip_size = TipSize[tip_size] if isinstance(pickup_method, str): @@ -72,6 +78,7 @@ def __init__( self.pickup_method = pickup_method self.tip_size = tip_size + self.tip_size_bottom = tip_size_bottom def __repr__(self) -> str: return ( @@ -129,6 +136,7 @@ def standard_volume_tip_no_filter() -> HamiltonTip: maximal_volume=400, tip_size=TipSize.STANDARD_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, + tip_size_bottom=1.2, ) @@ -140,6 +148,7 @@ def standard_volume_tip_with_filter() -> HamiltonTip: maximal_volume=360, tip_size=TipSize.STANDARD_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, + tip_size_bottom=1.2, ) @@ -151,6 +160,7 @@ def slim_standard_volume_tip_with_filter() -> HamiltonTip: maximal_volume=360, tip_size=TipSize.HIGH_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, + tip_size_bottom=None, ) @@ -162,6 +172,7 @@ def low_volume_tip_no_filter() -> HamiltonTip: maximal_volume=15, tip_size=TipSize.LOW_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, + tip_size_bottom=0.8, ) @@ -173,6 +184,7 @@ def low_volume_tip_with_filter() -> HamiltonTip: maximal_volume=10, tip_size=TipSize.LOW_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, + tip_size_bottom=0.8, ) @@ -184,6 +196,7 @@ def high_volume_tip_no_filter() -> HamiltonTip: maximal_volume=1250, tip_size=TipSize.HIGH_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, + tip_size_bottom=1.2, ) @@ -195,6 +208,7 @@ def high_volume_tip_with_filter() -> HamiltonTip: maximal_volume=1065, tip_size=TipSize.HIGH_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, + tip_size_bottom=1.2, ) @@ -206,6 +220,7 @@ def wide_high_volume_tip_with_filter() -> HamiltonTip: maximal_volume=1065, tip_size=TipSize.HIGH_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, + tip_size_bottom=12, ) @@ -217,6 +232,7 @@ def ultrawide_high_volume_tip_with_filter() -> HamiltonTip: maximal_volume=1065, tip_size=TipSize.HIGH_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, + tip_size_bottom=32, ) @@ -228,6 +244,7 @@ def four_ml_tip_with_filter() -> HamiltonTip: maximal_volume=4367, tip_size=TipSize.XL, pickup_method=TipPickupMethod.OUT_OF_RACK, + tip_size_bottom=None, ) @@ -239,6 +256,7 @@ def five_ml_tip_with_filter() -> HamiltonTip: maximal_volume=5420, tip_size=TipSize.XL, pickup_method=TipPickupMethod.OUT_OF_RACK, + tip_size_bottom=None, ) @@ -255,6 +273,7 @@ def five_ml_tip() -> HamiltonTip: maximal_volume=5420, tip_size=TipSize.XL, pickup_method=TipPickupMethod.OUT_OF_RACK, + tip_size_bottom=None, ) @@ -266,6 +285,7 @@ def fifty_ul_tip_with_filter() -> HamiltonTip: maximal_volume=60, tip_size=TipSize.STANDARD_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, + tip_size_bottom=0.7, ) @@ -277,4 +297,5 @@ def fifty_ul_tip_no_filter() -> HamiltonTip: maximal_volume=65, tip_size=TipSize.STANDARD_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, + tip_size_bottom=0.7, ) From 7da97a2b4b39e665678469ad8f7e66f93cabc9dd Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Sun, 19 Jan 2025 20:48:51 -0800 Subject: [PATCH 11/13] format docstring --- .../liquid_handling/backends/hamilton/STAR.py | 57 +++++++++---------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index 3bf0523f7a..82990e5488 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7332,42 +7332,41 @@ async def clld_probe_y_position_using_channel( post_detection_dist: float = 2.0, # mm ) -> float: """ - Probes the y-position at which a conductive material is detected using - the channel's capacitive Liquid Level Detection (cLLD) capability. + Probes the y-position at which a conductive material is detected using the channel's capacitive + Liquid Level Detection (cLLD) capability. - This method aims to provide safe probing within defined boundaries to - avoid collisions or damage to the system. It is specifically designed - for conductive materials. + This method aims to provide safe probing within defined boundaries to avoid collisions or damage + to the system. It is specifically designed for conductive materials. Args: - channel_idx (int): Index of the channel to use for probing (0-based). - The backmost channel is 0. - probing_direction (Literal["forward", "backward"]): Direction to move - the channel during probing. "forward" increases y-position, - "backward" decreases y-position. - start_pos_search (float, optional): Initial y-position for the search - (in mm). Defaults to the current y-position of the channel. - end_pos_search (float, optional): Final y-position for the search (in mm). - Defaults to the maximum y-position the channel can move to safely. - channel_speed (float): Speed of the channel's movement (in mm/sec). - Defaults to 10.0 mm/sec (i.e. slow default for safety). - channel_acceleration_int (Literal[1, 2, 3, 4]): Acceleration level, - corresponding to 1–4 (* 5,000 steps/sec²). Defaults to 4. - detection_edge (int): Steepness of the edge for capacitive detection. - Must be between 0 and 1024. Defaults to 10. - current_limit_int (Literal[1, 2, 3, 4, 5, 6, 7]): Current limit level, - from 1 to 7. Defaults to 7. - post_detection_dist (float): Distance to move away from the detected - material after detection (in mm). Defaults to 2.0 mm. + channel_idx: Index of the channel to use for probing (0-based). + The backmost channel is 0. + probing_direction: Direction to move + the channel during probing. "forward" increases y-position, + "backward" decreases y-position. + start_pos_search: Initial y-position for the search + (in mm). Defaults to the current y-position of the channel. + end_pos_search: Final y-position for the search (in mm). + Defaults to the maximum y-position the channel can move to safely. + channel_speed: Speed of the channel's movement (in mm/sec). + Defaults to 10.0 mm/sec (i.e. slow default for safety). + channel_acceleration_int: Acceleration level, + corresponding to 1-4 (* 5,000 steps/sec²). Defaults to 4. + detection_edge: Steepness of the edge for capacitive detection. + Must be between 0 and 1024. Defaults to 10. + current_limit_int: Current limit level, + from 1 to 7. Defaults to 7. + post_detection_dist: Distance to move away from the detected + material after detection (in mm). Defaults to 2.0 mm. Returns: - float: The detected y-position of the conductive material (in mm). + The detected y-position of the conductive material (in mm). Raises: - ValueError: - - If the probing direction is invalid. - - If the specified start or end positions are outside the safe range. - - If no conductive material is detected during the probing process. + ValueError: + - If the probing direction is invalid. + - If the specified start or end positions are outside the safe range. + - If no conductive material is detected during the probing process. """ assert probing_direction in [ From 901440c84625e0d763d1b276492f351e42fbc4c2 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Sun, 19 Jan 2025 20:59:16 -0800 Subject: [PATCH 12/13] use tip.tip_diameter_bottom, get tip from head --- .../liquid_handling/backends/hamilton/STAR.py | 44 ++++++++----------- pylabrobot/resources/hamilton/tip_creators.py | 34 +++++++------- 2 files changed, 35 insertions(+), 43 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index 82990e5488..1e6d625716 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7322,7 +7322,6 @@ async def clld_probe_y_position_using_channel( self, channel_idx: int, # 0-based indexing of channels! probing_direction: Literal["forward", "backward"], - tip_len: Optional[float] = None, # mm start_pos_search: Optional[float] = None, # mm end_pos_search: Optional[float] = None, # mm channel_speed: float = 10.0, # mm/sec @@ -7402,21 +7401,20 @@ async def clld_probe_y_position_using_channel( + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." ) - current_channel_y_pos = await self.request_y_pos_channel_n(channel_idx) - current_channel_z_pos = await self.request_z_pos_channel_n(channel_idx) - if tip_len is None: - tip_len = await self.request_tip_len_on_channel(channel_idx=channel_idx) - await self.move_channel_z(z=current_channel_z_pos, channel=channel_idx) - - tip_len_to_tip_bottom_diameter_dict = { - 29.9: 0.8, # 10 ul tip - 50.4: 0.7, # 50 ul tip - 59.9: 1.2, # teaching needle - BUT not 300 ul tip! - 95.1: 1.2, # 1000 ul tip - } - tip_bottom_diameter = tip_len_to_tip_bottom_diameter_dict[tip_len] + tip = self.head[channel_idx] + if not isinstance(tip, HamiltonTip): + raise TypeError( + f"Channel {channel_idx} does not have a HamiltonTip attached, " + f"found {type(tip)} instead." + ) + if tip.tip_diameter_bottom is None: + raise ValueError( + f"Tip {tip.tip_name} on channel {channel_idx} does not have a bottom diameter set." + ) + tip_bottom_diameter = tip.tip_diameter_bottom # Set safe y-search end position based on the probing direction + current_channel_y_pos = await self.request_y_pos_channel_n(channel_idx) if probing_direction == "forward": max_y_search_pos = end_pos_search or max_safe_upper_y_pos if max_y_search_pos < current_channel_y_pos: @@ -7464,22 +7462,16 @@ async def clld_probe_y_position_using_channel( 7, ], f"Currrent limit must be in [1, 2, 3, 4, 5, 6, 7], is {channel_speed} mm/sec" - max_y_search_pos_str = f"{max_y_search_pos_increments:05}" - channel_speed_str = f"{channel_speed_increments:04}" - channel_acceleration_str = f"{channel_acceleration_int}" - detection_edge_str = f"{detection_edge:04}" - current_limit_str = f"{current_limit_int}" - # Move channel for cLLD (Note: does not return detected y-position!) await self.send_command( module=f"P{channel_idx+1}", command="YL", - ya=max_y_search_pos_str, # Maximum search position [steps] - gt=detection_edge_str, # Edge steepness at capacitive LLD detection + ya=f"{max_y_search_pos_increments:05}", # Maximum search position [steps] + gt=f"{detection_edge:04}", # Edge steepness at capacitive LLD detection gl=f"{0:04}", # Offset after edge detection -> always 0 to measure y-pos! - yv=channel_speed_str, # Max speed [steps/second] - yr=channel_acceleration_str, # Acceleration ramp [yr * 5_000 steps/second**2] - yw=current_limit_str, # Current limit + yv=f"{channel_speed_increments:04}", # Max speed [steps/second] + yr=f"{channel_acceleration_int}", # Acceleration ramp [yr * 5_000 steps/second**2] + yw=f"{current_limit_int}", # Current limit ) detected_material_y_pos = await self.request_y_pos_channel_n(channel_idx) @@ -7512,7 +7504,7 @@ async def clld_probe_y_position_using_channel( # Correct for tip_bottom_diameter if probing_direction == "forward": material_y_pos = detected_material_y_pos + tip_bottom_diameter / 2 - else: + else: # probing_direction == "backwards" material_y_pos = detected_material_y_pos - tip_bottom_diameter / 2 return material_y_pos diff --git a/pylabrobot/resources/hamilton/tip_creators.py b/pylabrobot/resources/hamilton/tip_creators.py index 157f3b4937..ab5a327e3a 100644 --- a/pylabrobot/resources/hamilton/tip_creators.py +++ b/pylabrobot/resources/hamilton/tip_creators.py @@ -46,12 +46,12 @@ def __init__( maximal_volume: float, tip_size: Union[TipSize, str], # union for deserialization, will probably refactor pickup_method: Union[TipPickupMethod, str], # union for deserialization, will probably refactor - tip_size_bottom: Optional[float], + tip_diameter_bottom: Optional[float], ): """Create a new Hamilton tip. Args: - tip_size_bottom: diameter of the tip at the bottom. None if not known. + tip_diameter_bottom: diameter of the tip at the bottom. None if not known. """ if isinstance(tip_size, str): tip_size = TipSize[tip_size] @@ -78,7 +78,7 @@ def __init__( self.pickup_method = pickup_method self.tip_size = tip_size - self.tip_size_bottom = tip_size_bottom + self.tip_diameter_bottom = tip_diameter_bottom def __repr__(self) -> str: return ( @@ -136,7 +136,7 @@ def standard_volume_tip_no_filter() -> HamiltonTip: maximal_volume=400, tip_size=TipSize.STANDARD_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, - tip_size_bottom=1.2, + tip_diameter_bottom=1.2, ) @@ -148,7 +148,7 @@ def standard_volume_tip_with_filter() -> HamiltonTip: maximal_volume=360, tip_size=TipSize.STANDARD_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, - tip_size_bottom=1.2, + tip_diameter_bottom=1.2, ) @@ -160,7 +160,7 @@ def slim_standard_volume_tip_with_filter() -> HamiltonTip: maximal_volume=360, tip_size=TipSize.HIGH_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, - tip_size_bottom=None, + tip_diameter_bottom=None, ) @@ -172,7 +172,7 @@ def low_volume_tip_no_filter() -> HamiltonTip: maximal_volume=15, tip_size=TipSize.LOW_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, - tip_size_bottom=0.8, + tip_diameter_bottom=0.8, ) @@ -184,7 +184,7 @@ def low_volume_tip_with_filter() -> HamiltonTip: maximal_volume=10, tip_size=TipSize.LOW_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, - tip_size_bottom=0.8, + tip_diameter_bottom=0.8, ) @@ -196,7 +196,7 @@ def high_volume_tip_no_filter() -> HamiltonTip: maximal_volume=1250, tip_size=TipSize.HIGH_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, - tip_size_bottom=1.2, + tip_diameter_bottom=1.2, ) @@ -208,7 +208,7 @@ def high_volume_tip_with_filter() -> HamiltonTip: maximal_volume=1065, tip_size=TipSize.HIGH_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, - tip_size_bottom=1.2, + tip_diameter_bottom=1.2, ) @@ -220,7 +220,7 @@ def wide_high_volume_tip_with_filter() -> HamiltonTip: maximal_volume=1065, tip_size=TipSize.HIGH_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, - tip_size_bottom=12, + tip_diameter_bottom=12, ) @@ -232,7 +232,7 @@ def ultrawide_high_volume_tip_with_filter() -> HamiltonTip: maximal_volume=1065, tip_size=TipSize.HIGH_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, - tip_size_bottom=32, + tip_diameter_bottom=32, ) @@ -244,7 +244,7 @@ def four_ml_tip_with_filter() -> HamiltonTip: maximal_volume=4367, tip_size=TipSize.XL, pickup_method=TipPickupMethod.OUT_OF_RACK, - tip_size_bottom=None, + tip_diameter_bottom=None, ) @@ -256,7 +256,7 @@ def five_ml_tip_with_filter() -> HamiltonTip: maximal_volume=5420, tip_size=TipSize.XL, pickup_method=TipPickupMethod.OUT_OF_RACK, - tip_size_bottom=None, + tip_diameter_bottom=None, ) @@ -273,7 +273,7 @@ def five_ml_tip() -> HamiltonTip: maximal_volume=5420, tip_size=TipSize.XL, pickup_method=TipPickupMethod.OUT_OF_RACK, - tip_size_bottom=None, + tip_diameter_bottom=None, ) @@ -285,7 +285,7 @@ def fifty_ul_tip_with_filter() -> HamiltonTip: maximal_volume=60, tip_size=TipSize.STANDARD_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, - tip_size_bottom=0.7, + tip_diameter_bottom=0.7, ) @@ -297,5 +297,5 @@ def fifty_ul_tip_no_filter() -> HamiltonTip: maximal_volume=65, tip_size=TipSize.STANDARD_VOLUME, pickup_method=TipPickupMethod.OUT_OF_RACK, - tip_size_bottom=0.7, + tip_diameter_bottom=0.7, ) From 6c443e39b114af47b49f859dd47cb36d58f601c9 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Sun, 19 Jan 2025 20:59:46 -0800 Subject: [PATCH 13/13] delete this again (added by mistake from merge) --- .../liquid_handling/backends/hamilton/STAR.py | 52 ------------------- 1 file changed, 52 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index 1e6d625716..36a9bbcca4 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7509,58 +7509,6 @@ async def clld_probe_y_position_using_channel( return material_y_pos - async def request_tip_len_on_channel( - self, - channel_idx: int, # 0-based indexing of channels! - ) -> float: - """ - Measures the length of the tip attached to the specified pipetting channel. - - Checks if a tip is present on the given channel. If present, moves all channels - to THE safe Z position, 334.3 mm, measures the tip bottom Z-coordinate, and calculates - the total tip length. Supports tips of lengths 50.4 mm, 59.9 mm, and 95.1 mm. - Raises an error if the tip length is unsupported or if no tip is present. - - Parameters: - channel_idx: Index of the pipetting channel (0-based). - - Returns: - The measured tip length in millimeters. - - Raises: - ValueError: If no tip is present on the channel or if the tip length is unsupported. - """ - - # Check there is a tip on the channel - all_channel_occupancy = await self.request_tip_presence() - if not all_channel_occupancy[channel_idx]: - raise ValueError(f"No tip present on channel {channel_idx}") - - # Level all channels - await self.move_all_channels_in_z_safety() - known_top_position_channel_head = 334.3 # mm - fitting_depth_of_all_standard_channel_tips = 8 # mm - unknown_offset_for_all_tips = 0.4 # mm - - # Request z-coordinate of channel+tip bottom - tip_bottom_z_coordinate = await self.request_z_pos_channel_n( - pipetting_channel_index=channel_idx - ) - - total_tip_len = round( - known_top_position_channel_head - - ( - tip_bottom_z_coordinate - - fitting_depth_of_all_standard_channel_tips - - unknown_offset_for_all_tips - ), - 1, - ) - - if total_tip_len in [50.4, 59.9, 95.1]: # 50ul, 300ul, 1000ul - return total_tip_len - raise ValueError(f"Tip of length {total_tip_len} not yet supported") - async def ztouch_probe_z_height_using_channel( self, channel_idx: int, # 0-based indexing of channels!