From 2295a998a4851daff3d2c28b6772aac1cef8f0c4 Mon Sep 17 00:00:00 2001 From: Camillo Moschner <122165124+BioCam@users.noreply.github.com> Date: Mon, 6 Oct 2025 17:56:25 +0100 Subject: [PATCH 01/17] Harmonise surface_following_distance (#692) --- .../backends/hamilton/STAR_backend.py | 109 ++++++++++++++---- 1 file changed, 88 insertions(+), 21 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index d22b6f7e172..7cc41bff4ff 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -2292,7 +2292,7 @@ async def aspirate96( tube_2nd_section_ratio: float = 618.0, immersion_depth: float = 0, immersion_depth_direction: Optional[int] = None, - liquid_surface_sink_distance_at_the_end_of_aspiration: float = 0, + surface_following_distance: float = 0, transport_air_volume: float = 5.0, pre_wetting_volume: float = 5.0, gamma_lld_sensitivity: int = 1, @@ -2304,6 +2304,9 @@ async def aspirate96( surface_following_distance_during_mix: float = 0, speed_of_mix: float = 0.0, limit_curve_index: int = 0, + # Deprecated parameters, to be removed in future versions + # rm: >2026-01 + liquid_surface_sink_distance_at_the_end_of_aspiration: float = 0, ): """Aspirate using the Core96 head. @@ -2446,6 +2449,20 @@ async def aspirate96( # aspiration_volumes=int(blow_out_air_volume * 10) # ) + # # # TODO: delete > 2026-01 # # # + # deprecated liquid_surface_sink_distance_at_the_end_of_aspiration: + if liquid_surface_sink_distance_at_the_end_of_aspiration != 0: + surface_following_distance = liquid_surface_sink_distance_at_the_end_of_aspiration + warnings.warn( + "The liquid_surface_sink_distance_at_the_end_of_aspiration parameter is deprecated and will" + "be removed in the future." + "Use the Hamilton-standard surface_following_distance parameter instead.\n" + "liquid_surface_sink_distance_at_the_end_of_aspiration currently superseding" + "surface_following_distance.", + DeprecationWarning, + ) + # # # delete # # # + x_direction = 0 if position.x >= 0 else 1 return await self.aspirate_core_96( x_position=abs(round(position.x * 10)), @@ -2466,9 +2483,7 @@ async def aspirate96( tube_2nd_section_ratio=round(tube_2nd_section_ratio * 10), immersion_depth=round(immersion_depth * 10), immersion_depth_direction=immersion_depth_direction or (0 if (immersion_depth >= 0) else 1), - liquid_surface_sink_distance_at_the_end_of_aspiration=round( - liquid_surface_sink_distance_at_the_end_of_aspiration * 10 - ), + surface_following_distance=round(surface_following_distance * 10), aspiration_volumes=round(volume * 10), aspiration_speed=round(flow_rate * 10), transport_air_volume=round(transport_air_volume * 10), @@ -2507,7 +2522,7 @@ async def dispense96( tube_2nd_section_ratio: float = 618.0, immersion_depth: float = 0, immersion_depth_direction: Optional[int] = None, - liquid_surface_sink_distance_at_the_end_of_dispense: float = 0, + surface_following_distance: float = 0, transport_air_volume: float = 5.0, gamma_lld_sensitivity: int = 1, swap_speed: float = 2.0, @@ -2520,6 +2535,9 @@ async def dispense96( limit_curve_index: int = 0, cut_off_speed: float = 5.0, stop_back_volume: float = 0, + # Deprecated parameters, to be removed in future versions + # rm: >2026-01 + liquid_surface_sink_distance_at_the_end_of_dispense: float = 0, # surface_following_distance! ): """Dispense using the Core96 head. @@ -2542,7 +2560,7 @@ async def dispense96( tube_2nd_section_ratio: Unknown. immersion_depth: Immersion depth, in mm. See `immersion_depth_direction`. immersion_depth_direction: Immersion depth direction. 0 = go deeper, 1 = go up out of liquid. - liquid_surface_sink_distance_at_the_end_of_dispense: Unknown. + surface_following_distance: Surface following distance, in mm. Default 0. transport_air_volume: Transport air volume, to dispense before aspiration. gamma_lld_sensitivity: Gamma LLD sensitivity. swap_speed: Swap speed (on leaving liquid) [mm/s]. Must be between 0.3 and 160. Default 10. @@ -2645,6 +2663,21 @@ async def dispense96( channel_pattern = [True] * 12 * 8 x_direction = 0 if position.x >= 0 else 1 + + # # # TODO: delete > 2026-01 # # # + # deprecated liquid_surface_sink_distance_at_the_end_of_aspiration: + if liquid_surface_sink_distance_at_the_end_of_dispense != 0: + surface_following_distance = liquid_surface_sink_distance_at_the_end_of_dispense + warnings.warn( + "The liquid_surface_sink_distance_at_the_end_of_dispense parameter is deprecated and will" + "be removed in the future." + "Use the Hamilton-standard surface_following_distance parameter instead.\n" + "liquid_surface_sink_distance_at_the_end_of_dispense currently superseding" + "surface_following_distance.", + DeprecationWarning, + ) + # # # delete # # # + ret = await self.dispense_core_96( dispensing_mode=dispense_mode, x_position=abs(round(position.x * 10)), @@ -2664,9 +2697,7 @@ async def dispense96( tube_2nd_section_ratio=round(tube_2nd_section_ratio * 10), immersion_depth=round(immersion_depth * 10), immersion_depth_direction=immersion_depth_direction or (0 if (immersion_depth >= 0) else 1), - liquid_surface_sink_distance_at_the_end_of_dispense=round( - liquid_surface_sink_distance_at_the_end_of_dispense * 10 - ), + surface_following_distance=round(surface_following_distance * 10), dispense_volume=round(volume * 10), dispense_speed=round(flow_rate * 10), transport_air_volume=round(transport_air_volume * 10), @@ -5539,7 +5570,7 @@ async def aspirate_core_96( tube_2nd_section_ratio: int = 3425, immersion_depth: int = 0, immersion_depth_direction: int = 0, - liquid_surface_sink_distance_at_the_end_of_aspiration: int = 0, + surface_following_distance: float = 0, aspiration_volumes: int = 0, aspiration_speed: int = 1000, transport_air_volume: int = 0, @@ -5558,6 +5589,9 @@ async def aspirate_core_96( limit_curve_index: int = 0, tadm_algorithm: bool = False, recording_mode: int = 0, + # Deprecated parameters, to be removed in future versions + # rm: >2026-01: + liquid_surface_sink_distance_at_the_end_of_aspiration: float = 0, ): """aspirate CoRe 96 @@ -5588,8 +5622,9 @@ async def aspirate_core_96( immersion_depth: Immersion depth [0.1mm]. Must be between 0 and 3600. Default 0. immersion_depth_direction: Direction of immersion depth (0 = go deeper, 1 = go up out of liquid). Must be between 0 and 1. Default 0. - liquid_surface_sink_distance_at_the_end_of_aspiration: Liquid surface sink distance at - the end of aspiration [0.1mm]. Must be between 0 and 990. Default 0. + surface_following_distance_at_the_end_of_aspiration: Surface following distance during + aspiration [0.1mm]. Must be between 0 and 990. Default 0. (renamed for clarity from + 'liquid_surface_sink_distance_at_the_end_of_aspiration' in firmware docs) aspiration_volumes: Aspiration volume [0.1ul]. Must be between 0 and 11500. Default 0. aspiration_speed: Aspiration speed [0.1ul/s]. Must be between 3 and 5000. Default 1000. transport_air_volume: Transport air volume [0.1ul]. Must be between 0 and 500. Default 0. @@ -5643,8 +5678,8 @@ async def aspirate_core_96( assert 0 <= immersion_depth <= 3600, "immersion_depth must be between 0 and 3600" assert 0 <= immersion_depth_direction <= 1, "immersion_depth_direction must be between 0 and 1" assert ( - 0 <= liquid_surface_sink_distance_at_the_end_of_aspiration <= 990 - ), "liquid_surface_sink_distance_at_the_end_of_aspiration must be between 0 and 990" + 0 <= surface_following_distance <= 990 + ), "surface_following_distance must be between 0 and 990" assert 0 <= aspiration_volumes <= 11500, "aspiration_volumes must be between 0 and 11500" assert 3 <= aspiration_speed <= 5000, "aspiration_speed must be between 3 and 5000" assert 0 <= transport_air_volume <= 500, "transport_air_volume must be between 0 and 500" @@ -5672,6 +5707,20 @@ async def aspirate_core_96( channel_pattern_bin_str = reversed(["1" if x else "0" for x in channel_pattern]) channel_pattern_hex = hex(int("".join(channel_pattern_bin_str), 2)).upper()[2:] + # # # TODO: delete > 2026-01 # # # + # deprecated liquid_surface_sink_distance_at_the_end_of_aspiration: + if liquid_surface_sink_distance_at_the_end_of_aspiration != 0.0: + surface_following_distance = liquid_surface_sink_distance_at_the_end_of_aspiration + warnings.warn( + "The liquid_surface_sink_distance_at_the_end_of_aspiration parameter is deprecated and will" + "be removed in the future." + "Use the Hamilton-standard surface_following_distance parameter instead.\n" + "liquid_surface_sink_distance_at_the_end_of_aspiration currently superseding" + "surface_following_distance.", + DeprecationWarning, + ) + # # # delete # # # + return await self.send_command( module="C0", command="EA", @@ -5689,7 +5738,7 @@ async def aspirate_core_96( zq=f"{tube_2nd_section_ratio:05}", iw=f"{immersion_depth:03}", ix=immersion_depth_direction, - fh=f"{liquid_surface_sink_distance_at_the_end_of_aspiration:03}", + fh=f"{surface_following_distance:03}", af=f"{aspiration_volumes:05}", ag=f"{aspiration_speed:04}", vt=f"{transport_air_volume:03}", @@ -5725,7 +5774,7 @@ async def dispense_core_96( maximum_immersion_depth: int = 3425, immersion_depth: int = 0, immersion_depth_direction: int = 0, - liquid_surface_sink_distance_at_the_end_of_dispense: int = 0, + surface_following_distance: float = 0, minimum_traverse_height_at_beginning_of_a_command: int = 3425, minimal_end_height: int = 3425, dispense_volume: int = 0, @@ -5748,6 +5797,9 @@ async def dispense_core_96( limit_curve_index: int = 0, tadm_algorithm: bool = False, recording_mode: int = 0, + # Deprecated parameters, to be removed in future versions + # rm: >2026-01: + liquid_surface_sink_distance_at_the_end_of_dispense: float = 0, # surface_following_distance! ): """dispense CoRe 96 @@ -5774,8 +5826,9 @@ async def dispense_core_96( immersion_depth: Immersion depth [0.1mm]. Must be between 0 and 3600. Default 0. immersion_depth_direction: Direction of immersion depth (0 = go deeper, 1 = go up out of liquid). Must be between 0 and 1. Default 0. - liquid_surface_sink_distance_at_the_end_of_dispense: Liquid surface sink elevation at - the end of aspiration [0.1mm]. Must be between 0 and 990. Default 0. + surface_following_distance: Liquid surface following distance during dispense [0.1mm]. + Must be between 0 and 990. Default 0. (renamed for clarity from + 'liquid_surface_sink_distance_at_the_end_of_dispense' in firmware docs) minimum_traverse_height_at_beginning_of_a_command: Minimal traverse height at begin of command [0.1mm]. Must be between 0 and 3425. Default 3425. minimal_end_height: Minimal height at command end [0.1mm]. Must be between 0 and 3425. @@ -5831,8 +5884,8 @@ async def dispense_core_96( assert 0 <= immersion_depth <= 3600, "immersion_depth must be between 0 and 3600" assert 0 <= immersion_depth_direction <= 1, "immersion_depth_direction must be between 0 and 1" assert ( - 0 <= liquid_surface_sink_distance_at_the_end_of_dispense <= 990 - ), "liquid_surface_sink_distance_at_the_end_of_dispense must be between 0 and 990" + 0 <= surface_following_distance <= 990 + ), "surface_following_distance must be between 0 and 990" assert ( 0 <= minimum_traverse_height_at_beginning_of_a_command <= 3425 ), "minimum_traverse_height_at_beginning_of_a_command must be between 0 and 3425" @@ -5865,6 +5918,20 @@ async def dispense_core_96( channel_pattern_bin_str = reversed(["1" if x else "0" for x in channel_pattern]) channel_pattern_hex = hex(int("".join(channel_pattern_bin_str), 2)).upper()[2:] + # # # TODO: delete > 2026-01 # # # + # deprecated liquid_surface_sink_distance_at_the_end_of_aspiration: + if liquid_surface_sink_distance_at_the_end_of_dispense != 0.0: + surface_following_distance = liquid_surface_sink_distance_at_the_end_of_dispense + warnings.warn( + "The liquid_surface_sink_distance_at_the_end_of_dispense parameter is deprecated and will" + "be removed in the future." + "Use the Hamilton-standard surface_following_distance parameter instead.\n" + "liquid_surface_sink_distance_at_the_end_of_dispense currently superseding" + "surface_following_distance.", + DeprecationWarning, + ) + # # # delete # # # + return await self.send_command( module="C0", command="ED", @@ -5880,7 +5947,7 @@ async def dispense_core_96( pp=f"{pull_out_distance_to_take_transport_air_in_function_without_lld:04}", iw=f"{immersion_depth:03}", ix=immersion_depth_direction, - fh=f"{liquid_surface_sink_distance_at_the_end_of_dispense:03}", + fh=f"{surface_following_distance:03}", zh=f"{minimum_traverse_height_at_beginning_of_a_command:04}", ze=f"{minimal_end_height:04}", df=f"{dispense_volume:05}", From dd565e1edf7eb95863cc504106bb2d62d38f6cbb Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Fri, 10 Oct 2025 21:44:42 -0700 Subject: [PATCH 02/17] aspirate96 --- .../backends/hamilton/STAR_backend.py | 277 +++++++++++------- 1 file changed, 168 insertions(+), 109 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index 7cc41bff4ff..a4857427370 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -2281,13 +2281,13 @@ async def aspirate96( jet: bool = False, blow_out: bool = False, use_lld: bool = False, - air_transport_retract_dist: float = 10, + pull_out_distance_transport_air: float = 10, hlc: Optional[HamiltonLiquidClass] = None, aspiration_type: int = 0, minimum_traverse_height_at_beginning_of_a_command: Optional[float] = None, - minimal_end_height: Optional[float] = None, + min_z_endpos: Optional[float] = None, lld_search_height: float = 199.9, - maximum_immersion_depth: Optional[float] = None, + minimum_height: Optional[float] = None, tube_2nd_section_height_measured_from_zm: float = 3.2, tube_2nd_section_ratio: float = 618.0, immersion_depth: float = 0, @@ -2301,12 +2301,16 @@ async def aspirate96( mix_volume: float = 0, mix_cycles: int = 0, mix_position_from_liquid_surface: float = 0, - surface_following_distance_during_mix: float = 0, + mix_surface_following_distance: float = 0, speed_of_mix: float = 0.0, limit_curve_index: int = 0, # Deprecated parameters, to be removed in future versions # rm: >2026-01 liquid_surface_sink_distance_at_the_end_of_aspiration: float = 0, + minimal_end_height: Optional[float] = None, + air_transport_retract_dist: Optional[float] = None, + maximum_immersion_depth: Optional[float] = None, + surface_following_distance_during_mix: float = 0, ): """Aspirate using the Core96 head. @@ -2321,15 +2325,14 @@ async def aspirate96( automatically. use_lld: If True, use gamma liquid level detection. If False, use liquid height. - air_transport_retract_dist: The distance to retract after aspirating, in millimeters. + pull_out_distance_transport_air: The distance to retract after aspirating, in millimeters. - aspiration_type: The type of aspiration to perform. (0 = simple; 1 = sequence; 2 = cup emptied - ) + aspiration_type: The type of aspiration to perform. (0 = simple; 1 = sequence; 2 = cup emptied) minimum_traverse_height_at_beginning_of_a_command: The minimum height to move to before starting the command. - minimal_end_height: The minimum height to move to after the command. + min_z_endpos: The minimum height to move to after the command. lld_search_height: The height to search for the liquid level. - maximum_immersion_depth: The maximum immersion depth. + minimum_height: Minimum height (maximum immersion depth) tube_2nd_section_height_measured_from_zm: Unknown. tube_2nd_section_ratio: Unknown. immersion_depth: The immersion depth above or below the liquid level. See @@ -2345,8 +2348,7 @@ async def aspirate96( mix_cycles: The number of cycles to perform for mix. mix_position_from_liquid_surface: The position of the mix from the liquid surface. - surface_following_distance_during_mix: The distance to follow the liquid surface - during mix. + mix_surface_following_distance: The distance to follow the liquid surface during mix. speed_of_mix: The speed of mix. limit_curve_index: The index of the limit curve to use. """ @@ -2357,6 +2359,7 @@ async def aspirate96( "https://docs.pylabrobot.org/user_guide/00_liquid-handling/mixing.html" ) + # # # TODO: delete > 2026-01 # # # if immersion_depth_direction is not None: warnings.warn( "The immersion_depth_direction parameter is deprecated and will be removed in the future. " @@ -2365,6 +2368,54 @@ async def aspirate96( DeprecationWarning, ) + if liquid_surface_sink_distance_at_the_end_of_aspiration != 0: + surface_following_distance = liquid_surface_sink_distance_at_the_end_of_aspiration + warnings.warn( + "The liquid_surface_sink_distance_at_the_end_of_aspiration parameter is deprecated and will" + "be removed in the future." + "Use the Hamilton-standard surface_following_distance parameter instead.\n" + "liquid_surface_sink_distance_at_the_end_of_aspiration currently superseding" + "surface_following_distance.", + DeprecationWarning, + ) + + if minimal_end_height is not None: + minimal_end_height = min_z_endpos + warnings.warn( + "The minimal_end_height parameter is deprecated and will be removed in the future." + "Use the Hamilton-standard min_z_endpos parameter instead.\n" + "min_z_endpos currently superseding minimal_end_height.", + DeprecationWarning, + ) + + if air_transport_retract_dist is not None: + pull_out_distance_transport_air = air_transport_retract_dist + warnings.warn( + "The air_transport_retract_dist parameter is deprecated and will be removed in the future." + "Use the Hamilton-standard pull_out_distance_transport_air parameter instead.\n" + "pull_out_distance_transport_air currently superseding air_transport_retract_dist.", + DeprecationWarning, + ) + + if maximum_immersion_depth is not None: + minimum_height = maximum_immersion_depth + warnings.warn( + "The maximum_immersion_depth parameter is deprecated and will be removed in the future." + "Use the Hamilton-standard minimum_height parameter instead.\n" + "minimum_height currently superseding maximum_immersion_depth.", + DeprecationWarning, + ) + + if surface_following_distance_during_mix != 0: + mix_surface_following_distance = surface_following_distance_during_mix + warnings.warn( + "The surface_following_distance_during_mix parameter is deprecated and will be removed in the future." + "Use the Hamilton-standard mix_surface_following_distance parameter instead.\n" + "mix_surface_following_distance currently superseding surface_following_distance_during_mix.", + DeprecationWarning, + ) + # # # delete # # # + assert self.core96_head_installed, "96 head must be installed" # get the first well and tip as representatives @@ -2436,33 +2487,6 @@ async def aspirate96( channel_pattern = [True] * 12 * 8 - # Was this ever true? Just copied it over from pyhamilton. Could have something to do with - # the liquid classes and whether blow_out mode is enabled. - # # Unfortunately, `blow_out_air_volume` does not work correctly, so instead we aspirate air - # # manually. - # if blow_out_air_volume is not None and blow_out_air_volume > 0: - # await self.aspirate_core_96( - # x_position=int(position.x * 10), - # y_positions=int(position.y * 10), - # lld_mode=0, - # liquid_surface_at_function_without_lld=int((liquid_height + 30) * 10), - # aspiration_volumes=int(blow_out_air_volume * 10) - # ) - - # # # TODO: delete > 2026-01 # # # - # deprecated liquid_surface_sink_distance_at_the_end_of_aspiration: - if liquid_surface_sink_distance_at_the_end_of_aspiration != 0: - surface_following_distance = liquid_surface_sink_distance_at_the_end_of_aspiration - warnings.warn( - "The liquid_surface_sink_distance_at_the_end_of_aspiration parameter is deprecated and will" - "be removed in the future." - "Use the Hamilton-standard surface_following_distance parameter instead.\n" - "liquid_surface_sink_distance_at_the_end_of_aspiration currently superseding" - "surface_following_distance.", - DeprecationWarning, - ) - # # # delete # # # - x_direction = 0 if position.x >= 0 else 1 return await self.aspirate_core_96( x_position=abs(round(position.x * 10)), @@ -2472,13 +2496,11 @@ async def aspirate96( minimum_traverse_height_at_beginning_of_a_command=round( (minimum_traverse_height_at_beginning_of_a_command or self._channel_traversal_height) * 10 ), - minimal_end_height=round((minimal_end_height or self._channel_traversal_height) * 10), + minimal_end_height=round((min_z_endpos or self._channel_traversal_height) * 10), lld_search_height=round(lld_search_height * 10), - liquid_surface_at_function_without_lld=round(liquid_height * 10), - pull_out_distance_to_take_transport_air_in_function_without_lld=round( - air_transport_retract_dist * 10 - ), - maximum_immersion_depth=round((maximum_immersion_depth or position.z) * 10), + liquid_surface_no_lld=round(liquid_height * 10), + pull_out_distance_transport_air=round(pull_out_distance_transport_air * 10), + maximum_immersion_depth=round((minimum_height or position.z) * 10), tube_2nd_section_height_measured_from_zm=round(tube_2nd_section_height_measured_from_zm * 10), tube_2nd_section_ratio=round(tube_2nd_section_ratio * 10), immersion_depth=round(immersion_depth * 10), @@ -2496,7 +2518,7 @@ async def aspirate96( mix_volume=round(aspiration.mix.volume * 10) if aspiration.mix is not None else 0, mix_cycles=aspiration.mix.repetitions if aspiration.mix is not None else 0, mix_position_from_liquid_surface=round(mix_position_from_liquid_surface * 10), - surface_following_distance_during_mix=round(surface_following_distance_during_mix * 10), + mix_surface_following_distance=round(mix_surface_following_distance * 10), speed_of_mix=round(aspiration.mix.flow_rate * 10) if aspiration.mix is not None else 1200, channel_pattern=channel_pattern, limit_curve_index=limit_curve_index, @@ -2719,19 +2741,6 @@ async def dispense96( stop_back_volume=round(stop_back_volume * 10), ) - # Was this ever true? Just copied it over from pyhamilton. Could have something to do with - # the liquid classes and whether blow_out mode is enabled. - # # Unfortunately, `blow_out_air_volume` does not work correctly, so instead we dispense air - # # manually. - # if blow_out_air_volume is not None and blow_out_air_volume > 0: - # await self.dispense_core_96( - # x_position=int(position.x * 10), - # y_position=int(position.y * 10), - # lld_mode=0, - # liquid_surface_at_function_without_lld=int((liquid_height + 30) * 10), - # dispense_volume=int(blow_out_air_volume * 10), - # ) - return ret async def iswap_move_picked_up_resource( @@ -5561,13 +5570,13 @@ async def aspirate_core_96( x_direction: int = 0, y_positions: int = 0, minimum_traverse_height_at_beginning_of_a_command: int = 3425, - minimal_end_height: int = 3425, + min_z_endpos: int = 3425, lld_search_height: int = 3425, - liquid_surface_at_function_without_lld: int = 3425, - pull_out_distance_to_take_transport_air_in_function_without_lld: int = 50, - maximum_immersion_depth: int = 3425, + liquid_surface_no_lld: int = 3425, + pull_out_distance_transport_air: int = 3425, + minimum_height: int = 3425, tube_2nd_section_height_measured_from_zm: int = 0, - tube_2nd_section_ratio: int = 3425, + second_section_ratio: int = 3425, immersion_depth: int = 0, immersion_depth_direction: int = 0, surface_following_distance: float = 0, @@ -5583,7 +5592,7 @@ async def aspirate_core_96( mix_volume: int = 0, mix_cycles: int = 0, mix_position_from_liquid_surface: int = 250, - surface_following_distance_during_mix: int = 0, + mix_surface_following_distance: int = 0, speed_of_mix: int = 1000, channel_pattern: List[bool] = [True] * 96, limit_curve_index: int = 0, @@ -5592,6 +5601,12 @@ async def aspirate_core_96( # Deprecated parameters, to be removed in future versions # rm: >2026-01: liquid_surface_sink_distance_at_the_end_of_aspiration: float = 0, + minimal_end_height: int = 3425, + liquid_surface_at_function_without_lld: int = 3425, + pull_out_distance_to_take_transport_air_in_function_without_lld: int = 50, + maximum_immersion_depth: int = 3425, + surface_following_distance_during_mix: int = 0, + tube_2nd_section_ratio: int = 3425, ): """aspirate CoRe 96 @@ -5606,19 +5621,14 @@ async def aspirate_core_96( minimum_traverse_height_at_beginning_of_a_command: Minimum traverse height at beginning of a command 0.1mm] (refers to all channels independent of tip pattern parameter 'tm'). Must be between 0 and 3425. Default 3425. - minimal_end_height: Minimal height at command end [0.1mm]. Must be between 0 and 3425. - Default 3425. + min_z_endpos: Minimal height at command end [0.1mm]. Must be between 0 and 3425. Default 3425. lld_search_height: LLD search height [0.1mm]. Must be between 0 and 3425. Default 3425. - liquid_surface_at_function_without_lld: Liquid surface at function without LLD [0.1mm]. - Must be between 0 and 3425. Default 3425. - pull_out_distance_to_take_transport_air_in_function_without_lld: pull out distance to take - transport air in function without LLD [0.1mm]. Must be between 0 and 3425. Default 50. - maximum_immersion_depth: Minimum height (maximum immersion depth) [0.1mm]. Must be between - 0 and 3425. Default 3425. + liquid_surface_no_lld: Liquid surface at function without LLD [0.1mm]. Must be between 0 and 3425. Default 3425. + pull_out_distance_transport_air: pull out distance to take transport air in function without LLD [0.1mm]. Must be between 0 and 3425. Default 50. + minimum_height: Minimum height (maximum immersion depth) [0.1mm]. Must be between 0 and 3425. Default 3425. tube_2nd_section_height_measured_from_zm: Tube 2nd section height measured from "zm" [0.1mm] Must be between 0 and 3425. Default 0. - tube_2nd_section_ratio: Tube 2nd section ratio (See Fig 2.). Must be between 0 and 10000. - Default 3425. + second_section_ratio: Tube 2nd section ratio (See Fig 2.). Must be between 0 and 10000. Default 3425. immersion_depth: Immersion depth [0.1mm]. Must be between 0 and 3600. Default 0. immersion_depth_direction: Direction of immersion depth (0 = go deeper, 1 = go up out of liquid). Must be between 0 and 1. Default 0. @@ -5640,7 +5650,7 @@ async def aspirate_core_96( mix_cycles: Number of mix cycles. Must be between 0 and 99. Default 0. mix_position_from_liquid_surface: mix position in Z- direction from liquid surface (LLD or absolute terms) [0.1mm]. Must be between 0 and 990. Default 250. - surface_following_distance_during_mix: surface following distance during + mix_surface_following_distance: surface following distance during mix [0.1mm]. Must be between 0 and 990. Default 0. speed_of_mix: Speed of mix [0.1ul/s]. Must be between 3 and 5000. Default 1000. @@ -5651,6 +5661,75 @@ async def aspirate_core_96( Must be between 0 and 2. Default 0. """ + # # # TODO: delete > 2026-01 # # # + # deprecated liquid_surface_sink_distance_at_the_end_of_aspiration: + if liquid_surface_sink_distance_at_the_end_of_aspiration != 0.0: + surface_following_distance = liquid_surface_sink_distance_at_the_end_of_aspiration + warnings.warn( + "The liquid_surface_sink_distance_at_the_end_of_aspiration parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard surface_following_distance parameter instead.\n" + "liquid_surface_sink_distance_at_the_end_of_aspiration currently superseding" + "surface_following_distance.", + DeprecationWarning, + ) + + if minimal_end_height != 3425: + min_z_endpos = minimal_end_height + warnings.warn( + "The minimal_end_height parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard min_z_endpos parameter instead.\n" + "minimal_end_height currently superseding min_z_endpos.", + DeprecationWarning, + ) + + if liquid_surface_at_function_without_lld != 3425: + liquid_surface_no_lld = liquid_surface_at_function_without_lld + warnings.warn( + "The liquid_surface_at_function_without_lld parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard liquid_surface_no_lld parameter instead.\n" + "liquid_surface_at_function_without_lld currently superseding liquid_surface_no_lld.", + DeprecationWarning, + ) + + if pull_out_distance_to_take_transport_air_in_function_without_lld != 50: + pull_out_distance_transport_air = ( + pull_out_distance_to_take_transport_air_in_function_without_lld + ) + warnings.warn( + "The pull_out_distance_to_take_transport_air_in_function_without_lld parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard pull_out_distance_transport_air parameter instead.\n" + "pull_out_distance_to_take_transport_air_in_function_without_lld currently superseding pull_out_distance_transport_air.", + DeprecationWarning, + ) + + if maximum_immersion_depth != 3425: + minimum_height = maximum_immersion_depth + warnings.warn( + "The maximum_immersion_depth parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard minimum_height parameter instead.\n" + "minimum_height currently superseding maximum_immersion_depth.", + DeprecationWarning, + ) + + if surface_following_distance_during_mix != 0: + mix_surface_following_distance = surface_following_distance_during_mix + warnings.warn( + "The surface_following_distance_during_mix parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard mix_surface_following_distance parameter instead.\n" + "surface_following_distance_during_mix currently superseding mix_surface_following_distance.", + DeprecationWarning, + ) + + if tube_2nd_section_ratio != 3425: + second_section_ratio = tube_2nd_section_ratio + warnings.warn( + "The tube_2nd_section_ratio parameter is deprecated and will be removed in the future." + "Use the Hamilton-standard second_section_ratio parameter instead.\n" + "tube_2nd_section_ratio currently superseding second_section_ratio.", + DeprecationWarning, + ) + # # # delete # # # + assert 0 <= aspiration_type <= 2, "aspiration_type must be between 0 and 2" assert 0 <= x_position <= 30000, "x_position must be between 0 and 30000" assert 0 <= x_direction <= 1, "x_direction must be between 0 and 1" @@ -5658,23 +5737,17 @@ async def aspirate_core_96( assert ( 0 <= minimum_traverse_height_at_beginning_of_a_command <= 3425 ), "minimum_traverse_height_at_beginning_of_a_command must be between 0 and 3425" - assert 0 <= minimal_end_height <= 3425, "minimal_end_height must be between 0 and 3425" + assert 0 <= min_z_endpos <= 3425, "min_z_endpos must be between 0 and 3425" assert 0 <= lld_search_height <= 3425, "lld_search_height must be between 0 and 3425" + assert 0 <= liquid_surface_no_lld <= 3425, "liquid_surface_no_lld must be between 0 and 3425" assert ( - 0 <= liquid_surface_at_function_without_lld <= 3425 - ), "liquid_surface_at_function_without_lld must be between 0 and 3425" - assert ( - 0 <= pull_out_distance_to_take_transport_air_in_function_without_lld <= 3425 - ), "pull_out_distance_to_take_transport_air_in_function_without_lld must be between 0 and 3425" - assert ( - 0 <= maximum_immersion_depth <= 3425 - ), "maximum_immersion_depth must be between 0 and 3425" + 0 <= pull_out_distance_transport_air <= 3425 + ), "pull_out_distance_transport_air must be between 0 and 3425" + assert 0 <= minimum_height <= 3425, "minimum_height must be between 0 and 3425" assert ( 0 <= tube_2nd_section_height_measured_from_zm <= 3425 ), "tube_2nd_section_height_measured_from_zm must be between 0 and 3425" - assert ( - 0 <= tube_2nd_section_ratio <= 10000 - ), "tube_2nd_section_ratio must be between 0 and 10000" + assert 0 <= second_section_ratio <= 10000, "second_section_ratio must be between 0 and 10000" assert 0 <= immersion_depth <= 3600, "immersion_depth must be between 0 and 3600" assert 0 <= immersion_depth_direction <= 1, "immersion_depth_direction must be between 0 and 1" assert ( @@ -5695,8 +5768,8 @@ async def aspirate_core_96( 0 <= mix_position_from_liquid_surface <= 990 ), "mix_position_from_liquid_surface must be between 0 and 990" assert ( - 0 <= surface_following_distance_during_mix <= 990 - ), "surface_following_distance_during_mix must be between 0 and 990" + 0 <= mix_surface_following_distance <= 990 + ), "mix_surface_following_distance must be between 0 and 990" assert 3 <= speed_of_mix <= 5000, "speed_of_mix must be between 3 and 5000" assert 0 <= limit_curve_index <= 999, "limit_curve_index must be between 0 and 999" @@ -5707,20 +5780,6 @@ async def aspirate_core_96( channel_pattern_bin_str = reversed(["1" if x else "0" for x in channel_pattern]) channel_pattern_hex = hex(int("".join(channel_pattern_bin_str), 2)).upper()[2:] - # # # TODO: delete > 2026-01 # # # - # deprecated liquid_surface_sink_distance_at_the_end_of_aspiration: - if liquid_surface_sink_distance_at_the_end_of_aspiration != 0.0: - surface_following_distance = liquid_surface_sink_distance_at_the_end_of_aspiration - warnings.warn( - "The liquid_surface_sink_distance_at_the_end_of_aspiration parameter is deprecated and will" - "be removed in the future." - "Use the Hamilton-standard surface_following_distance parameter instead.\n" - "liquid_surface_sink_distance_at_the_end_of_aspiration currently superseding" - "surface_following_distance.", - DeprecationWarning, - ) - # # # delete # # # - return await self.send_command( module="C0", command="EA", @@ -5729,13 +5788,13 @@ async def aspirate_core_96( xd=x_direction, yh=f"{y_positions:04}", zh=f"{minimum_traverse_height_at_beginning_of_a_command:04}", - ze=f"{minimal_end_height:04}", + ze=f"{min_z_endpos:04}", lz=f"{lld_search_height:04}", - zt=f"{liquid_surface_at_function_without_lld:04}", - pp=f"{pull_out_distance_to_take_transport_air_in_function_without_lld:04}", - zm=f"{maximum_immersion_depth:04}", + zt=f"{liquid_surface_no_lld:04}", + pp=f"{pull_out_distance_transport_air:04}", + zm=f"{minimum_height:04}", zv=f"{tube_2nd_section_height_measured_from_zm:04}", - zq=f"{tube_2nd_section_ratio:05}", + zq=f"{second_section_ratio:05}", iw=f"{immersion_depth:03}", ix=immersion_depth_direction, fh=f"{surface_following_distance:03}", @@ -5751,7 +5810,7 @@ async def aspirate_core_96( hv=f"{mix_volume:05}", hc=f"{mix_cycles:02}", hp=f"{mix_position_from_liquid_surface:03}", - mj=f"{surface_following_distance_during_mix:03}", + mj=f"{mix_surface_following_distance:03}", hs=f"{speed_of_mix:04}", cw=channel_pattern_hex, cr=f"{limit_curve_index:03}", From 0f194c5b4c896fa31511db7693dc6ddfc86350bf Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Fri, 10 Oct 2025 22:06:36 -0700 Subject: [PATCH 03/17] dispense96 --- .../backends/hamilton/STAR_backend.py | 308 ++++++++++++------ 1 file changed, 206 insertions(+), 102 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index a4857427370..06b9240f2e7 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -2344,22 +2344,19 @@ async def aspirate96( gamma_lld_sensitivity: The sensitivity of the gamma liquid level detection. swap_speed: Swap speed (on leaving liquid) [1mm/s]. Must be between 0.3 and 160. Default 2. settling_time: The time to wait after aspirating. - mix_volume: The volume of liquid to aspirate for mix. - mix_cycles: The number of cycles to perform for mix. mix_position_from_liquid_surface: The position of the mix from the liquid surface. mix_surface_following_distance: The distance to follow the liquid surface during mix. - speed_of_mix: The speed of mix. limit_curve_index: The index of the limit curve to use. """ + # # # TODO: delete > 2026-01 # # # if mix_volume != 0 or mix_cycles != 0 or speed_of_mix != 0: raise NotImplementedError( "Mixing through backend kwargs is deprecated. Use the `mix` parameter of LiquidHandler.aspirate96 instead. " "https://docs.pylabrobot.org/user_guide/00_liquid-handling/mixing.html" ) - # # # TODO: delete > 2026-01 # # # if immersion_depth_direction is not None: warnings.warn( "The immersion_depth_direction parameter is deprecated and will be removed in the future. " @@ -2534,14 +2531,14 @@ async def dispense96( blow_out: bool = False, hlc: Optional[HamiltonLiquidClass] = None, dispense_mode: Optional[int] = None, - air_transport_retract_dist=10, + pull_out_distance_transport_air=10, use_lld: bool = False, minimum_traverse_height_at_beginning_of_a_command: Optional[float] = None, - minimal_end_height: Optional[float] = None, + min_z_endpos: Optional[float] = None, lld_search_height: float = 199.9, - maximum_immersion_depth: Optional[float] = None, + minimum_height: Optional[float] = None, tube_2nd_section_height_measured_from_zm: float = 3.2, - tube_2nd_section_ratio: float = 618.0, + second_section_ratio: float = 618.0, immersion_depth: float = 0, immersion_depth_direction: Optional[int] = None, surface_following_distance: float = 0, @@ -2551,8 +2548,8 @@ async def dispense96( settling_time: float = 0, mixing_volume: float = 0, mixing_cycles: int = 0, - mixing_position_from_liquid_surface: float = 0, - surface_following_distance_during_mixing: float = 0, + mix_position_from_liquid_surface: float = 0, + mix_surface_following_distance: float = 0, speed_of_mixing: float = 0.0, limit_curve_index: int = 0, cut_off_speed: float = 5.0, @@ -2560,6 +2557,12 @@ async def dispense96( # Deprecated parameters, to be removed in future versions # rm: >2026-01 liquid_surface_sink_distance_at_the_end_of_dispense: float = 0, # surface_following_distance! + maximum_immersion_depth: Optional[float] = None, + minimal_end_height: Optional[float] = None, + mixing_position_from_liquid_surface: float = 0, + surface_following_distance_during_mixing: float = 0, + air_transport_retract_dist=10, + tube_2nd_section_ratio: float = 618.0, ): """Dispense using the Core96 head. @@ -2570,16 +2573,16 @@ async def dispense96( dispense_mode: The dispense mode to use. 0 = Partial volume in jet mode 1 = Blow out in jet mode 2 = Partial volume at surface 3 = Blow out at surface 4 = Empty tip at fix position. If `None`, the mode will be determined based on the `jet`, `empty`, and `blow_out` - air_transport_retract_dist: The distance to retract after dispensing, in mm. + pull_out_distance_transport_air: The distance to retract after dispensing, in mm. use_lld: Whether to use gamma LLD. minimum_traverse_height_at_beginning_of_a_command: Minimum traverse height at beginning of a command, in mm. - minimal_end_height: Minimal end height, in mm. + min_z_endpos: Minimal end height, in mm. lld_search_height: LLD search height, in mm. - maximum_immersion_depth: Maximum immersion depth, in mm. Equals Minimum height during command. + minimum_height: Maximum immersion depth, in mm. Equals Minimum height during command. tube_2nd_section_height_measured_from_zm: Unknown. - tube_2nd_section_ratio: Unknown. + second_section_ratio: Unknown. immersion_depth: Immersion depth, in mm. See `immersion_depth_direction`. immersion_depth_direction: Immersion depth direction. 0 = go deeper, 1 = go up out of liquid. surface_following_distance: Surface following distance, in mm. Default 0. @@ -2587,16 +2590,14 @@ async def dispense96( gamma_lld_sensitivity: Gamma LLD sensitivity. swap_speed: Swap speed (on leaving liquid) [mm/s]. Must be between 0.3 and 160. Default 10. settling_time: Settling time, in seconds. - mixing_volume: Mixing volume, in ul. - mixing_cycles: Mixing cycles. - mixing_position_from_liquid_surface: Mixing position from liquid surface, in mm. - surface_following_distance_during_mixing: Surface following distance during mixing, in mm. - speed_of_mixing: Speed of mixing, in ul/s. + mix_position_from_liquid_surface: Mixing position from liquid surface, in mm. + mix_surface_following_distance: Surface following distance during mixing, in mm. limit_curve_index: Limit curve index. cut_off_speed: Unknown. stop_back_volume: Unknown. """ + # # # TODO: delete > 2026-01 # # # if mixing_volume != 0 or mixing_cycles != 0 or speed_of_mixing != 0: raise NotImplementedError( "Mixing through backend kwargs is deprecated. Use the `mix` parameter of LiquidHandler.dispense instead. " @@ -2611,6 +2612,70 @@ async def dispense96( DeprecationWarning, ) + if liquid_surface_sink_distance_at_the_end_of_dispense != 0: + surface_following_distance = liquid_surface_sink_distance_at_the_end_of_dispense + warnings.warn( + "The liquid_surface_sink_distance_at_the_end_of_dispense parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard surface_following_distance parameter instead.\n" + "liquid_surface_sink_distance_at_the_end_of_dispense currently superseding surface_following_distance.", + DeprecationWarning, + ) + + if maximum_immersion_depth is not None: + minimum_height = maximum_immersion_depth + warnings.warn( + "The maximum_immersion_depth parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard minimum_height parameter instead.\n" + "minimum_height currently superseding maximum_immersion_depth.", + DeprecationWarning, + ) + + if minimal_end_height is not None: + min_z_endpos = minimal_end_height + warnings.warn( + "The minimal_end_height parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard min_z_endpos parameter instead.\n" + "min_z_endpos currently superseding minimal_end_height.", + DeprecationWarning, + ) + + if mixing_position_from_liquid_surface != 0: + mix_position_from_liquid_surface = mixing_position_from_liquid_surface + warnings.warn( + "The mixing_position_from_liquid_surface parameter is deprecated and will be removed in the future " + "Use the Hamilton-standard mix_position_from_liquid_surface parameter instead.\n" + "mix_position_from_liquid_surface currently superseding mixing_position_from_liquid_surface.", + DeprecationWarning, + ) + + if surface_following_distance_during_mixing != 0: + mix_surface_following_distance = surface_following_distance_during_mixing + warnings.warn( + "The surface_following_distance_during_mixing parameter is deprecated and will be removed in the future " + "Use the Hamilton-standard mix_surface_following_distance parameter instead.\n" + "mix_surface_following_distance currently superseding surface_following_distance_during_mixing.", + DeprecationWarning, + ) + + if air_transport_retract_dist != 10: + pull_out_distance_transport_air = air_transport_retract_dist + warnings.warn( + "The air_transport_retract_dist parameter is deprecated and will be removed in the future " + "Use the Hamilton-standard pull_out_distance_transport_air parameter instead.\n" + "pull_out_distance_transport_air currently superseding air_transport_retract_dist.", + DeprecationWarning, + ) + + if tube_2nd_section_ratio != 618.0: + second_section_ratio = tube_2nd_section_ratio + warnings.warn( + "The tube_2nd_section_ratio parameter is deprecated and will be removed in the future " + "Use the Hamilton-standard second_section_ratio parameter instead.\n" + "second_section_ratio currently superseding tube_2nd_section_ratio.", + DeprecationWarning, + ) + # # # delete # # # + assert self.core96_head_installed, "96 head must be installed" # get the first well and tip as representatives @@ -2686,20 +2751,6 @@ async def dispense96( x_direction = 0 if position.x >= 0 else 1 - # # # TODO: delete > 2026-01 # # # - # deprecated liquid_surface_sink_distance_at_the_end_of_aspiration: - if liquid_surface_sink_distance_at_the_end_of_dispense != 0: - surface_following_distance = liquid_surface_sink_distance_at_the_end_of_dispense - warnings.warn( - "The liquid_surface_sink_distance_at_the_end_of_dispense parameter is deprecated and will" - "be removed in the future." - "Use the Hamilton-standard surface_following_distance parameter instead.\n" - "liquid_surface_sink_distance_at_the_end_of_dispense currently superseding" - "surface_following_distance.", - DeprecationWarning, - ) - # # # delete # # # - ret = await self.dispense_core_96( dispensing_mode=dispense_mode, x_position=abs(round(position.x * 10)), @@ -2708,15 +2759,13 @@ async def dispense96( minimum_traverse_height_at_beginning_of_a_command=round( (minimum_traverse_height_at_beginning_of_a_command or self._channel_traversal_height) * 10 ), - minimal_end_height=round((minimal_end_height or self._channel_traversal_height) * 10), + min_z_endpos=round((min_z_endpos or self._channel_traversal_height) * 10), lld_search_height=round(lld_search_height * 10), - liquid_surface_at_function_without_lld=round(liquid_height * 10), - pull_out_distance_to_take_transport_air_in_function_without_lld=round( - air_transport_retract_dist * 10 - ), - maximum_immersion_depth=maximum_immersion_depth or round(position.z * 10), + liquid_surface_no_lld=round(liquid_height * 10), + pull_out_distance_transport_air=round(pull_out_distance_transport_air * 10), + minimum_height=minimum_height or round(position.z * 10), tube_2nd_section_height_measured_from_zm=round(tube_2nd_section_height_measured_from_zm * 10), - tube_2nd_section_ratio=round(tube_2nd_section_ratio * 10), + second_section_ratio=round(second_section_ratio * 10), immersion_depth=round(immersion_depth * 10), immersion_depth_direction=immersion_depth_direction or (0 if (immersion_depth >= 0) else 1), surface_following_distance=round(surface_following_distance * 10), @@ -2730,8 +2779,8 @@ async def dispense96( settling_time=round(settling_time * 10), mixing_volume=round(dispense.mix.volume * 10) if dispense.mix is not None else 0, mixing_cycles=dispense.mix.repetitions if dispense.mix is not None else 0, - mixing_position_from_liquid_surface=round(mixing_position_from_liquid_surface * 10), - surface_following_distance_during_mixing=round(surface_following_distance_during_mixing * 10), + mix_position_from_liquid_surface=round(mix_position_from_liquid_surface * 10), + mix_surface_following_distance=round(mix_surface_following_distance * 10), speed_of_mixing=round(dispense.mix.flow_rate * 10) if dispense.mix is not None else 1200, channel_pattern=channel_pattern, limit_curve_index=limit_curve_index, @@ -5826,16 +5875,16 @@ async def dispense_core_96( x_direction: int = 0, y_position: int = 0, tube_2nd_section_height_measured_from_zm: int = 0, - tube_2nd_section_ratio: int = 3425, + second_section_ratio: int = 3425, lld_search_height: int = 3425, - liquid_surface_at_function_without_lld: int = 3425, - pull_out_distance_to_take_transport_air_in_function_without_lld: int = 50, - maximum_immersion_depth: int = 3425, + liquid_surface_no_lld: int = 3425, + pull_out_distance_transport_air: int = 50, + minimum_height: int = 3425, immersion_depth: int = 0, immersion_depth_direction: int = 0, surface_following_distance: float = 0, minimum_traverse_height_at_beginning_of_a_command: int = 3425, - minimal_end_height: int = 3425, + min_z_endpos: int = 3425, dispense_volume: int = 0, dispense_speed: int = 5000, cut_off_speed: int = 250, @@ -5849,8 +5898,8 @@ async def dispense_core_96( settling_time: int = 5, mixing_volume: int = 0, mixing_cycles: int = 0, - mixing_position_from_liquid_surface: int = 250, - surface_following_distance_during_mixing: int = 0, + mix_position_from_liquid_surface: int = 250, + mix_surface_following_distance: int = 0, speed_of_mixing: int = 1000, channel_pattern: List[bool] = [True] * 12 * 8, limit_curve_index: int = 0, @@ -5859,10 +5908,15 @@ async def dispense_core_96( # Deprecated parameters, to be removed in future versions # rm: >2026-01: liquid_surface_sink_distance_at_the_end_of_dispense: float = 0, # surface_following_distance! + tube_2nd_section_ratio: int = 3425, + liquid_surface_at_function_without_lld: int = 3425, + maximum_immersion_depth: int = 3425, + minimal_end_height: int = 3425, + mixing_position_from_liquid_surface: int = 250, + surface_following_distance_during_mixing: int = 0, + pull_out_distance_to_take_transport_air_in_function_without_lld: int = 50, ): - """dispense CoRe 96 - - Dispensing of liquid using CoRe 96 + """Dispensing of liquid using CoRe 96 Args: dispensing_mode: Type of dispensing mode 0 = Partial volume in jet mode 1 = Blow out @@ -5871,17 +5925,13 @@ async def dispense_core_96( x_position: X-Position [0.1mm] of well A1. Must be between 0 and 30000. Default 0. x_direction: X-direction. 0 = positive 1 = negative. Must be between 0 and 1. Default 0. y_position: Y-Position [0.1mm] of well A1. Must be between 1080 and 5600. Default 0. - maximum_immersion_depth: Minimum height (maximum immersion depth) [0.1mm]. Must be between - 0 and 3425. Default 3425. + minimum_height: Minimum height (maximum immersion depth) [0.1mm]. Must be between 0 and 3425. Default 3425. tube_2nd_section_height_measured_from_zm: Tube 2nd section height measured from "zm" [0.1mm]. Must be between 0 and 3425. Default 0. - tube_2nd_section_ratio: Tube 2nd section ratio (See Fig 2.). Must be between 0 and 10000. - Default 3425. + second_section_ratio: Tube 2nd section ratio (See Fig 2.). Must be between 0 and 10000. Default 3425. lld_search_height: LLD search height [0.1mm]. Must be between 0 and 3425. Default 3425. - liquid_surface_at_function_without_lld: Liquid surface at function without LLD [0.1mm]. - Must be between 0 and 3425. Default 3425. - pull_out_distance_to_take_transport_air_in_function_without_lld: pull out distance to take - transport air in function without LLD [0.1mm]. Must be between 0 and 3425. Default 50. + liquid_surface_no_lld: Liquid surface at function without LLD [0.1mm]. Must be between 0 and 3425. Default 3425. + pull_out_distance_transport_air: pull out distance to take transport air in function without LLD [0.1mm]. Must be between 0 and 3425. Default 50. immersion_depth: Immersion depth [0.1mm]. Must be between 0 and 3600. Default 0. immersion_depth_direction: Direction of immersion depth (0 = go deeper, 1 = go up out of liquid). Must be between 0 and 1. Default 0. @@ -5890,8 +5940,7 @@ async def dispense_core_96( 'liquid_surface_sink_distance_at_the_end_of_dispense' in firmware docs) minimum_traverse_height_at_beginning_of_a_command: Minimal traverse height at begin of command [0.1mm]. Must be between 0 and 3425. Default 3425. - minimal_end_height: Minimal height at command end [0.1mm]. Must be between 0 and 3425. - Default 3425. + min_z_endpos: Minimal height at command end [0.1mm]. Must be between 0 and 3425. Default 3425. dispense_volume: Dispense volume [0.1ul]. Must be between 0 and 11500. Default 0. dispense_speed: Dispense speed [0.1ul/s]. Must be between 3 and 5000. Default 5000. cut_off_speed: Cut-off speed [0.1ul/s]. Must be between 3 and 5000. Default 250. @@ -5908,10 +5957,8 @@ async def dispense_core_96( settling_time: Settling time [0.1s]. Must be between 0 and 99. Default 5. mixing_volume: mix volume [0.1ul]. Must be between 0 and 11500. Default 0. mixing_cycles: Number of mixing cycles. Must be between 0 and 99. Default 0. - mixing_position_from_liquid_surface: mix position in Z- direction from liquid - surface (LLD or absolute terms) [0.1mm]. Must be between 0 and 990. Default 250. - surface_following_distance_during_mixing: surface following distance during mixing [0.1mm]. - Must be between 0 and 990. Default 0. + mix_position_from_liquid_surface: mix position in Z- direction from liquid surface (LLD or absolute terms) [0.1mm]. Must be between 0 and 990. Default 250. + mix_surface_following_distance: surface following distance during mixing [0.1mm]. Must be between 0 and 990. Default 0. speed_of_mixing: Speed of mixing [0.1ul/s]. Must be between 3 and 5000. Default 1000. channel_pattern: list of 96 boolean values limit_curve_index: limit curve index. Must be between 0 and 999. Default 0. @@ -5920,26 +5967,97 @@ async def dispense_core_96( be between 0 and 2. Default 0. """ + # # # TODO: delete > 2026-01 # # # + # deprecated liquid_surface_sink_distance_at_the_end_of_aspiration: + if liquid_surface_sink_distance_at_the_end_of_dispense != 0.0: + surface_following_distance = liquid_surface_sink_distance_at_the_end_of_dispense + warnings.warn( + "The liquid_surface_sink_distance_at_the_end_of_dispense parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard surface_following_distance parameter instead.\n" + "liquid_surface_sink_distance_at_the_end_of_dispense currently superseding surface_following_distance.", + DeprecationWarning, + ) + + if tube_2nd_section_ratio != 3425: + second_section_ratio = tube_2nd_section_ratio + warnings.warn( + "The tube_2nd_section_ratio parameter is deprecated and will be removed in the future." + "Use the Hamilton-standard second_section_ratio parameter instead.\n" + "second_section_ratio currently superseding tube_2nd_section_ratio.", + DeprecationWarning, + ) + + if maximum_immersion_depth != 3425: + minimum_height = maximum_immersion_depth + warnings.warn( + "The maximum_immersion_depth parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard minimum_height parameter instead.\n" + "minimum_height currently superseding maximum_immersion_depth.", + DeprecationWarning, + ) + + if liquid_surface_at_function_without_lld != 3425: + liquid_surface_no_lld = liquid_surface_at_function_without_lld + warnings.warn( + "The liquid_surface_at_function_without_lld parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard liquid_surface_no_lld parameter instead.\n" + "liquid_surface_at_function_without_lld currently superseding liquid_surface_no_lld.", + DeprecationWarning, + ) + + if minimal_end_height != 3425: + min_z_endpos = minimal_end_height + warnings.warn( + "The minimal_end_height parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard min_z_endpos parameter instead.\n" + "minimal_end_height currently superseding min_z_endpos.", + DeprecationWarning, + ) + + if mixing_position_from_liquid_surface != 250: + mix_position_from_liquid_surface = mixing_position_from_liquid_surface + warnings.warn( + "The mixing_position_from_liquid_surface parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard mix_position_from_liquid_surface parameter instead.\n" + "mixing_position_from_liquid_surface currently superseding mix_position_from_liquid_surface.", + DeprecationWarning, + ) + + if surface_following_distance_during_mixing != 0: + mix_surface_following_distance = surface_following_distance_during_mixing + warnings.warn( + "The surface_following_distance_during_mixing parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard mix_surface_following_distance parameter instead.\n" + "mix_surface_following_distance currently superseding surface_following_distance_during_mixing.", + DeprecationWarning, + ) + + if pull_out_distance_to_take_transport_air_in_function_without_lld != 50: + pull_out_distance_transport_air = ( + pull_out_distance_to_take_transport_air_in_function_without_lld + ) + warnings.warn( + "The pull_out_distance_to_take_transport_air_in_function_without_lld parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard pull_out_distance_transport_air parameter instead.\n" + "pull_out_distance_to_take_transport_air_in_function_without_lld currently superseding pull_out_distance_transport_air.", + DeprecationWarning, + ) + # # # delete # # # + assert 0 <= dispensing_mode <= 4, "dispensing_mode must be between 0 and 4" assert 0 <= x_position <= 30000, "x_position must be between 0 and 30000" assert 0 <= x_direction <= 1, "x_direction must be between 0 and 1" assert 1080 <= y_position <= 5600, "y_position must be between 1080 and 5600" - assert ( - 0 <= maximum_immersion_depth <= 3425 - ), "maximum_immersion_depth must be between 0 and 3425" + assert 0 <= minimum_height <= 3425, "minimum_height must be between 0 and 3425" assert ( 0 <= tube_2nd_section_height_measured_from_zm <= 3425 ), "tube_2nd_section_height_measured_from_zm must be between 0 and 3425" - assert ( - 0 <= tube_2nd_section_ratio <= 10000 - ), "tube_2nd_section_ratio must be between 0 and 10000" + assert 0 <= second_section_ratio <= 10000, "second_section_ratio must be between 0 and 10000" assert 0 <= lld_search_height <= 3425, "lld_search_height must be between 0 and 3425" + assert 0 <= liquid_surface_no_lld <= 3425, "liquid_surface_no_lld must be between 0 and 3425" assert ( - 0 <= liquid_surface_at_function_without_lld <= 3425 - ), "liquid_surface_at_function_without_lld must be between 0 and 3425" - assert ( - 0 <= pull_out_distance_to_take_transport_air_in_function_without_lld <= 3425 - ), "pull_out_distance_to_take_transport_air_in_function_without_lld must be between 0 and 3425" + 0 <= pull_out_distance_transport_air <= 3425 + ), "pull_out_distance_transport_air must be between 0 and 3425" assert 0 <= immersion_depth <= 3600, "immersion_depth must be between 0 and 3600" assert 0 <= immersion_depth_direction <= 1, "immersion_depth_direction must be between 0 and 1" assert ( @@ -5948,7 +6066,7 @@ async def dispense_core_96( assert ( 0 <= minimum_traverse_height_at_beginning_of_a_command <= 3425 ), "minimum_traverse_height_at_beginning_of_a_command must be between 0 and 3425" - assert 0 <= minimal_end_height <= 3425, "minimal_end_height must be between 0 and 3425" + assert 0 <= min_z_endpos <= 3425, "min_z_endpos must be between 0 and 3425" assert 0 <= dispense_volume <= 11500, "dispense_volume must be between 0 and 11500" assert 3 <= dispense_speed <= 5000, "dispense_speed must be between 3 and 5000" assert 3 <= cut_off_speed <= 5000, "cut_off_speed must be between 3 and 5000" @@ -5963,11 +6081,11 @@ async def dispense_core_96( assert 0 <= mixing_volume <= 11500, "mixing_volume must be between 0 and 11500" assert 0 <= mixing_cycles <= 99, "mixing_cycles must be between 0 and 99" assert ( - 0 <= mixing_position_from_liquid_surface <= 990 + 0 <= mix_position_from_liquid_surface <= 990 ), "mixing_position_from_liquid_surface must be between 0 and 990" assert ( - 0 <= surface_following_distance_during_mixing <= 990 - ), "surface_following_distance_during_mixing must be between 0 and 990" + 0 <= mix_surface_following_distance <= 990 + ), "mix_surface_following_distance must be between 0 and 990" assert 3 <= speed_of_mixing <= 5000, "speed_of_mixing must be between 3 and 5000" assert 0 <= limit_curve_index <= 999, "limit_curve_index must be between 0 and 999" assert 0 <= recording_mode <= 2, "recording_mode must be between 0 and 2" @@ -5977,20 +6095,6 @@ async def dispense_core_96( channel_pattern_bin_str = reversed(["1" if x else "0" for x in channel_pattern]) channel_pattern_hex = hex(int("".join(channel_pattern_bin_str), 2)).upper()[2:] - # # # TODO: delete > 2026-01 # # # - # deprecated liquid_surface_sink_distance_at_the_end_of_aspiration: - if liquid_surface_sink_distance_at_the_end_of_dispense != 0.0: - surface_following_distance = liquid_surface_sink_distance_at_the_end_of_dispense - warnings.warn( - "The liquid_surface_sink_distance_at_the_end_of_dispense parameter is deprecated and will" - "be removed in the future." - "Use the Hamilton-standard surface_following_distance parameter instead.\n" - "liquid_surface_sink_distance_at_the_end_of_dispense currently superseding" - "surface_following_distance.", - DeprecationWarning, - ) - # # # delete # # # - return await self.send_command( module="C0", command="ED", @@ -5998,17 +6102,17 @@ async def dispense_core_96( xs=f"{x_position:05}", xd=x_direction, yh=f"{y_position:04}", - zm=f"{maximum_immersion_depth:04}", + zm=f"{minimum_height:04}", zv=f"{tube_2nd_section_height_measured_from_zm:04}", - zq=f"{tube_2nd_section_ratio:05}", + zq=f"{second_section_ratio:05}", lz=f"{lld_search_height:04}", - zt=f"{liquid_surface_at_function_without_lld:04}", - pp=f"{pull_out_distance_to_take_transport_air_in_function_without_lld:04}", + zt=f"{liquid_surface_no_lld:04}", + pp=f"{pull_out_distance_transport_air:04}", iw=f"{immersion_depth:03}", ix=immersion_depth_direction, fh=f"{surface_following_distance:03}", zh=f"{minimum_traverse_height_at_beginning_of_a_command:04}", - ze=f"{minimal_end_height:04}", + ze=f"{min_z_endpos:04}", df=f"{dispense_volume:05}", dg=f"{dispense_speed:04}", es=f"{cut_off_speed:04}", @@ -6022,8 +6126,8 @@ async def dispense_core_96( wh=f"{settling_time:02}", hv=f"{mixing_volume:05}", hc=f"{mixing_cycles:02}", - hp=f"{mixing_position_from_liquid_surface:03}", - mj=f"{surface_following_distance_during_mixing:03}", + hp=f"{mix_position_from_liquid_surface:03}", + mj=f"{mix_surface_following_distance:03}", hs=f"{speed_of_mixing:04}", cw=channel_pattern_hex, cr=f"{limit_curve_index:03}", From c33879c69666ea317140b2b6009ba570aa612e29 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Fri, 10 Oct 2025 22:11:50 -0700 Subject: [PATCH 04/17] Update pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index 06b9240f2e7..b2e8ec3f0c5 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -2377,7 +2377,7 @@ async def aspirate96( ) if minimal_end_height is not None: - minimal_end_height = min_z_endpos + min_z_endpos = minimal_end_height warnings.warn( "The minimal_end_height parameter is deprecated and will be removed in the future." "Use the Hamilton-standard min_z_endpos parameter instead.\n" From 1286385d62f5c3a8766f1daab1969deb897ab59b Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Fri, 10 Oct 2025 22:11:59 -0700 Subject: [PATCH 05/17] Update pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index b2e8ec3f0c5..72f8dd0064d 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -2369,7 +2369,7 @@ async def aspirate96( surface_following_distance = liquid_surface_sink_distance_at_the_end_of_aspiration warnings.warn( "The liquid_surface_sink_distance_at_the_end_of_aspiration parameter is deprecated and will" - "be removed in the future." + " be removed in the future." "Use the Hamilton-standard surface_following_distance parameter instead.\n" "liquid_surface_sink_distance_at_the_end_of_aspiration currently superseding" "surface_following_distance.", From 46e47bd61e9e14eefb26553c99f7df4c2d74d6e7 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Fri, 10 Oct 2025 22:12:11 -0700 Subject: [PATCH 06/17] Update pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index 72f8dd0064d..b7ce2df3529 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -5982,7 +5982,7 @@ async def dispense_core_96( second_section_ratio = tube_2nd_section_ratio warnings.warn( "The tube_2nd_section_ratio parameter is deprecated and will be removed in the future." - "Use the Hamilton-standard second_section_ratio parameter instead.\n" + " Use the Hamilton-standard second_section_ratio parameter instead.\n" "second_section_ratio currently superseding tube_2nd_section_ratio.", DeprecationWarning, ) From 19433db15e0472fc0b87813a0dba0ccef6283009 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Fri, 10 Oct 2025 22:13:27 -0700 Subject: [PATCH 07/17] spacing --- .../backends/hamilton/STAR_backend.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index b7ce2df3529..c0965d508e3 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -2368,8 +2368,7 @@ async def aspirate96( if liquid_surface_sink_distance_at_the_end_of_aspiration != 0: surface_following_distance = liquid_surface_sink_distance_at_the_end_of_aspiration warnings.warn( - "The liquid_surface_sink_distance_at_the_end_of_aspiration parameter is deprecated and will" - " be removed in the future." + "The liquid_surface_sink_distance_at_the_end_of_aspiration parameter is deprecated and will be removed in the future. " "Use the Hamilton-standard surface_following_distance parameter instead.\n" "liquid_surface_sink_distance_at_the_end_of_aspiration currently superseding" "surface_following_distance.", @@ -2379,7 +2378,7 @@ async def aspirate96( if minimal_end_height is not None: min_z_endpos = minimal_end_height warnings.warn( - "The minimal_end_height parameter is deprecated and will be removed in the future." + "The minimal_end_height parameter is deprecated and will be removed in the future. " "Use the Hamilton-standard min_z_endpos parameter instead.\n" "min_z_endpos currently superseding minimal_end_height.", DeprecationWarning, @@ -2388,7 +2387,7 @@ async def aspirate96( if air_transport_retract_dist is not None: pull_out_distance_transport_air = air_transport_retract_dist warnings.warn( - "The air_transport_retract_dist parameter is deprecated and will be removed in the future." + "The air_transport_retract_dist parameter is deprecated and will be removed in the future. " "Use the Hamilton-standard pull_out_distance_transport_air parameter instead.\n" "pull_out_distance_transport_air currently superseding air_transport_retract_dist.", DeprecationWarning, @@ -2397,7 +2396,7 @@ async def aspirate96( if maximum_immersion_depth is not None: minimum_height = maximum_immersion_depth warnings.warn( - "The maximum_immersion_depth parameter is deprecated and will be removed in the future." + "The maximum_immersion_depth parameter is deprecated and will be removed in the future. " "Use the Hamilton-standard minimum_height parameter instead.\n" "minimum_height currently superseding maximum_immersion_depth.", DeprecationWarning, @@ -2406,7 +2405,7 @@ async def aspirate96( if surface_following_distance_during_mix != 0: mix_surface_following_distance = surface_following_distance_during_mix warnings.warn( - "The surface_following_distance_during_mix parameter is deprecated and will be removed in the future." + "The surface_following_distance_during_mix parameter is deprecated and will be removed in the future. " "Use the Hamilton-standard mix_surface_following_distance parameter instead.\n" "mix_surface_following_distance currently superseding surface_following_distance_during_mix.", DeprecationWarning, @@ -5772,7 +5771,7 @@ async def aspirate_core_96( if tube_2nd_section_ratio != 3425: second_section_ratio = tube_2nd_section_ratio warnings.warn( - "The tube_2nd_section_ratio parameter is deprecated and will be removed in the future." + "The tube_2nd_section_ratio parameter is deprecated and will be removed in the future. " "Use the Hamilton-standard second_section_ratio parameter instead.\n" "tube_2nd_section_ratio currently superseding second_section_ratio.", DeprecationWarning, @@ -5981,8 +5980,8 @@ async def dispense_core_96( if tube_2nd_section_ratio != 3425: second_section_ratio = tube_2nd_section_ratio warnings.warn( - "The tube_2nd_section_ratio parameter is deprecated and will be removed in the future." - " Use the Hamilton-standard second_section_ratio parameter instead.\n" + "The tube_2nd_section_ratio parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard second_section_ratio parameter instead.\n" "second_section_ratio currently superseding tube_2nd_section_ratio.", DeprecationWarning, ) From 44caeec4f8d7da1f3508b9fb88ea1a76dc648b27 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Fri, 10 Oct 2025 22:37:34 -0700 Subject: [PATCH 08/17] more cleanup --- .../backends/hamilton/STAR_backend.py | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index c0965d508e3..b628e7a628e 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -2335,17 +2335,14 @@ async def aspirate96( minimum_height: Minimum height (maximum immersion depth) tube_2nd_section_height_measured_from_zm: Unknown. tube_2nd_section_ratio: Unknown. - immersion_depth: The immersion depth above or below the liquid level. See - `immersion_depth_direction`. - immersion_depth_direction: The direction of the immersion depth. (0 = deeper, 1 = out of - liquid) + immersion_depth: The immersion depth above or below the liquid level. + surface_following_distance: The distance to follow the liquid surface when aspirating. transport_air_volume: The volume of air to aspirate after the liquid. pre_wetting_volume: The volume of liquid to use for pre-wetting. gamma_lld_sensitivity: The sensitivity of the gamma liquid level detection. swap_speed: Swap speed (on leaving liquid) [1mm/s]. Must be between 0.3 and 160. Default 2. settling_time: The time to wait after aspirating. - mix_position_from_liquid_surface: The position of the mix from the - liquid surface. + mix_position_from_liquid_surface: The position of the mix from the liquid surface. mix_surface_following_distance: The distance to follow the liquid surface during mix. limit_curve_index: The index of the limit curve to use. """ @@ -2481,8 +2478,6 @@ async def aspirate96( swap_speed = swap_speed or (hlc.aspiration_swap_speed if hlc is not None else 100) settling_time = settling_time or (hlc.aspiration_settling_time if hlc is not None else 0.5) - channel_pattern = [True] * 12 * 8 - x_direction = 0 if position.x >= 0 else 1 return await self.aspirate_core_96( x_position=abs(round(position.x * 10)), @@ -2516,7 +2511,7 @@ async def aspirate96( mix_position_from_liquid_surface=round(mix_position_from_liquid_surface * 10), mix_surface_following_distance=round(mix_surface_following_distance * 10), speed_of_mix=round(aspiration.mix.flow_rate * 10) if aspiration.mix is not None else 1200, - channel_pattern=channel_pattern, + channel_pattern=[True] * 12 * 8, limit_curve_index=limit_curve_index, tadm_algorithm=False, recording_mode=0, @@ -2582,8 +2577,7 @@ async def dispense96( minimum_height: Maximum immersion depth, in mm. Equals Minimum height during command. tube_2nd_section_height_measured_from_zm: Unknown. second_section_ratio: Unknown. - immersion_depth: Immersion depth, in mm. See `immersion_depth_direction`. - immersion_depth_direction: Immersion depth direction. 0 = go deeper, 1 = go up out of liquid. + immersion_depth: Immersion depth, in mm. surface_following_distance: Surface following distance, in mm. Default 0. transport_air_volume: Transport air volume, to dispense before aspiration. gamma_lld_sensitivity: Gamma LLD sensitivity. @@ -2746,14 +2740,10 @@ async def dispense96( swap_speed = swap_speed or (hlc.dispense_swap_speed if hlc is not None else 100) settling_time = settling_time or (hlc.dispense_settling_time if hlc is not None else 5) - channel_pattern = [True] * 12 * 8 - - x_direction = 0 if position.x >= 0 else 1 - - ret = await self.dispense_core_96( + return await self.dispense_core_96( dispensing_mode=dispense_mode, x_position=abs(round(position.x * 10)), - x_direction=x_direction, + x_direction=0 if position.x >= 0 else 1, y_position=round(position.y * 10), minimum_traverse_height_at_beginning_of_a_command=round( (minimum_traverse_height_at_beginning_of_a_command or self._channel_traversal_height) * 10 @@ -2781,7 +2771,7 @@ async def dispense96( mix_position_from_liquid_surface=round(mix_position_from_liquid_surface * 10), mix_surface_following_distance=round(mix_surface_following_distance * 10), speed_of_mixing=round(dispense.mix.flow_rate * 10) if dispense.mix is not None else 1200, - channel_pattern=channel_pattern, + channel_pattern=[True] * 12 * 8, limit_curve_index=limit_curve_index, tadm_algorithm=False, recording_mode=0, @@ -2789,8 +2779,6 @@ async def dispense96( stop_back_volume=round(stop_back_volume * 10), ) - return ret - async def iswap_move_picked_up_resource( self, center: Coordinate, From 6d183cce5c5533918276b03bb82efa06a8bbe10b Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Fri, 10 Oct 2025 22:40:42 -0700 Subject: [PATCH 09/17] Update pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index fffe50c5cff..a5407b4e463 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -6102,7 +6102,7 @@ async def dispense_core_96( assert 0 <= mixing_cycles <= 99, "mixing_cycles must be between 0 and 99" assert ( 0 <= mix_position_from_liquid_surface <= 990 - ), "mixing_position_from_liquid_surface must be between 0 and 990" + ), "mix_position_from_liquid_surface must be between 0 and 990" assert ( 0 <= mix_surface_following_distance <= 990 ), "mix_surface_following_distance must be between 0 and 990" From a118d5dcbc9e5e1a5ea0dfffa4ad615c627b26b3 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Fri, 10 Oct 2025 22:40:52 -0700 Subject: [PATCH 10/17] Update pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index a5407b4e463..dbec7826674 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -5737,7 +5737,7 @@ async def aspirate_core_96( warnings.warn( "The liquid_surface_sink_distance_at_the_end_of_aspiration parameter is deprecated and will be removed in the future. " "Use the Hamilton-standard surface_following_distance parameter instead.\n" - "liquid_surface_sink_distance_at_the_end_of_aspiration currently superseding" + "liquid_surface_sink_distance_at_the_end_of_aspiration currently superseding " "surface_following_distance.", DeprecationWarning, ) From 1c50f650872abaf110566e8dd9413a8be16befb8 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Fri, 10 Oct 2025 22:42:12 -0700 Subject: [PATCH 11/17] Update pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index dbec7826674..df24d9544d5 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -2400,8 +2400,7 @@ async def aspirate96( warnings.warn( "The liquid_surface_sink_distance_at_the_end_of_aspiration parameter is deprecated and will be removed in the future. " "Use the Hamilton-standard surface_following_distance parameter instead.\n" - "liquid_surface_sink_distance_at_the_end_of_aspiration currently superseding" - "surface_following_distance.", + "liquid_surface_sink_distance_at_the_end_of_aspiration currently superseding surface_following_distance.", DeprecationWarning, ) From 2337e81e2dcc873bc7ece66458ac81a4b608759e Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Fri, 10 Oct 2025 22:42:19 -0700 Subject: [PATCH 12/17] Update pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index df24d9544d5..a72b12ee030 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -2685,7 +2685,7 @@ async def dispense96( if air_transport_retract_dist != 10: pull_out_distance_transport_air = air_transport_retract_dist warnings.warn( - "The air_transport_retract_dist parameter is deprecated and will be removed in the future " + "The air_transport_retract_dist parameter is deprecated and will be removed in the future. " "Use the Hamilton-standard pull_out_distance_transport_air parameter instead.\n" "pull_out_distance_transport_air currently superseding air_transport_retract_dist.", DeprecationWarning, From bf4eb3a83e63f8887fd5d151d80e44bf5c52b06c Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Fri, 10 Oct 2025 22:42:27 -0700 Subject: [PATCH 13/17] Update pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index a72b12ee030..d640888a975 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -2676,7 +2676,7 @@ async def dispense96( if surface_following_distance_during_mixing != 0: mix_surface_following_distance = surface_following_distance_during_mixing warnings.warn( - "The surface_following_distance_during_mixing parameter is deprecated and will be removed in the future " + "The surface_following_distance_during_mixing parameter is deprecated and will be removed in the future. " "Use the Hamilton-standard mix_surface_following_distance parameter instead.\n" "mix_surface_following_distance currently superseding surface_following_distance_during_mixing.", DeprecationWarning, From 72865ff7260fffb5b2d48dfb1e8ae4a30f633e29 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Fri, 10 Oct 2025 22:42:34 -0700 Subject: [PATCH 14/17] Update pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index d640888a975..5073a224472 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -2694,7 +2694,7 @@ async def dispense96( if tube_2nd_section_ratio != 618.0: second_section_ratio = tube_2nd_section_ratio warnings.warn( - "The tube_2nd_section_ratio parameter is deprecated and will be removed in the future " + "The tube_2nd_section_ratio parameter is deprecated and will be removed in the future. " "Use the Hamilton-standard second_section_ratio parameter instead.\n" "second_section_ratio currently superseding tube_2nd_section_ratio.", DeprecationWarning, From 5e4a5c4f3baff2282afb52e88b2eef4df13d368d Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Sat, 11 Oct 2025 14:49:13 -0700 Subject: [PATCH 15/17] second_section_height --- .../backends/hamilton/STAR_backend.py | 120 ++++++++++++------ 1 file changed, 82 insertions(+), 38 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index 5073a224472..1c69faf35e2 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -1616,7 +1616,6 @@ async def aspirate( second_section_ratio: Optional[List[float]] = None, minimum_height: Optional[List[float]] = None, immersion_depth: Optional[List[float]] = None, - immersion_depth_direction: Optional[List[int]] = None, surface_following_distance: Optional[List[float]] = None, transport_air_volume: Optional[List[float]] = None, pre_wetting_volume: Optional[List[float]] = None, @@ -1645,6 +1644,8 @@ async def aspirate( min_z_endpos: Optional[float] = None, hamilton_liquid_classes: Optional[List[Optional[HamiltonLiquidClass]]] = None, liquid_surfaces_no_lld: Optional[List[float]] = None, + # remove > 2026-01 + immersion_depth_direction: Optional[List[int]] = None, ): """Aspirate liquid from the specified channels. @@ -1664,13 +1665,10 @@ async def aspirate( pull_out_distance_transport_air: The distance to pull out when aspirating air, if LLD is disabled. second_section_height: The height to start the second section of aspiration. - second_section_ratio: Unknown. + second_section_ratio: The ratio of [the bottom of the container * 10000] / [the height top of the container]. minimum_height: The minimum height to move to, this is the end of aspiration. The channel will move linearly from the liquid surface to this height over the course of the aspiration. - immersion_depth: The z distance to move after detecting the liquid, can be into or away from - the liquid surface (dependent on immersion_depth_direction). - immersion_depth_direction: set to 0, tip will move below the detected liquid surface; set to - 1, tip will move away from the detected surface. + immersion_depth: The z distance to move after detecting the liquid, can be into or away from the liquid surface. surface_following_distance: The distance to follow the liquid surface. transport_air_volume: The volume of air to aspirate after the liquid. pre_wetting_volume: The volume of liquid to use for pre-wetting. @@ -1712,6 +1710,7 @@ async def aspirate( and 360. Defaults to well bottom + liquid height. Should use absolute z. """ + # # # TODO: delete > 2026-01 # # # if mix_volume is not None or mix_cycles is not None or mix_speed is not None: raise NotImplementedError( "Mixing through backend kwargs is deprecated. Use the `mix` parameter of LiquidHandler.aspirate instead. " @@ -1725,6 +1724,7 @@ async def aspirate( "out of the liquid.", DeprecationWarning, ) + # # # delete # # # x_positions, y_positions, channels_involved = self._ops_to_fw_positions(ops, use_channels) @@ -1940,7 +1940,6 @@ async def dispense( second_section_ratio: Optional[List[float]] = None, minimum_height: Optional[List[float]] = None, immersion_depth: Optional[List[float]] = None, - immersion_depth_direction: Optional[List[int]] = None, surface_following_distance: Optional[List[float]] = None, cut_off_speed: Optional[List[float]] = None, stop_back_volume: Optional[List[float]] = None, @@ -1964,6 +1963,8 @@ async def dispense( jet: Optional[List[bool]] = None, blow_out: Optional[List[bool]] = None, # "empty" in the VENUS liquid editor empty: Optional[List[bool]] = None, # truly "empty", does not exist in liquid editor, dm4 + # remove in the future + immersion_depth_direction: Optional[List[int]] = None, ): """Dispense liquid from the specified channels. @@ -1977,14 +1978,11 @@ async def dispense( dispensing_mode: The dispensing mode to use for each operation. lld_search_height: The height to start searching for the liquid level when using LLD. liquid_surface_no_lld: Liquid surface at function without LLD. - pull_out_distance_transport_air: The distance to pull out the tip for aspirating transport air - if LLD is disabled. - second_section_height: Unknown. - second_section_ratio: Unknown. + pull_out_distance_transport_air: The distance to pull out the tip for aspirating transport air if LLD is disabled. + second_section_height: The height of the second section. + second_section_ratio: The ratio of [the bottom of the container * 10000] / [the height top of the container]. minimum_height: The minimum height at the end of the dispense. - immersion_depth: The distance above or below to liquid level to start dispensing. See the - `immersion_depth_direction` parameter. - immersion_depth_direction: (0 = go deeper, 1 = go up out of liquid) + immersion_depth: The distance above or below to liquid level to start dispensing. surface_following_distance: The distance to follow the liquid surface. cut_off_speed: Unknown. stop_back_volume: Unknown. @@ -2021,6 +2019,7 @@ async def dispense( documentation. Dispense mode 4. """ + # # # TODO: delete > 2026-01 # # # if mix_volume is not None or mix_cycles is not None or mix_speed is not None: raise NotImplementedError( "Mixing through backend kwargs is deprecated. Use the `mix` parameter of LiquidHandler.dispense instead. " @@ -2034,6 +2033,7 @@ async def dispense( "out of the liquid.", DeprecationWarning, ) + # # # delete # # # x_positions, y_positions, channels_involved = self._ops_to_fw_positions(ops, use_channels) @@ -2321,8 +2321,8 @@ async def aspirate96( min_z_endpos: Optional[float] = None, lld_search_height: float = 199.9, minimum_height: Optional[float] = None, - tube_2nd_section_height_measured_from_zm: float = 3.2, - tube_2nd_section_ratio: float = 618.0, + second_section_height: float = 3.2, + second_section_ratio: float = 618.0, immersion_depth: float = 0, immersion_depth_direction: Optional[int] = None, surface_following_distance: float = 0, @@ -2344,6 +2344,8 @@ async def aspirate96( air_transport_retract_dist: Optional[float] = None, maximum_immersion_depth: Optional[float] = None, surface_following_distance_during_mix: float = 0, + tube_2nd_section_height_measured_from_zm: float = 3.2, + tube_2nd_section_ratio: float = 618.0, ): """Aspirate using the Core96 head. @@ -2366,8 +2368,8 @@ async def aspirate96( min_z_endpos: The minimum height to move to after the command. lld_search_height: The height to search for the liquid level. minimum_height: Minimum height (maximum immersion depth) - tube_2nd_section_height_measured_from_zm: Unknown. - tube_2nd_section_ratio: Unknown. + second_section_height: Height of the second section. + second_section_ratio: Ratio of [the diameter of the bottom * 10000] / [the diameter of the top] immersion_depth: The immersion depth above or below the liquid level. surface_following_distance: The distance to follow the liquid surface when aspirating. transport_air_volume: The volume of air to aspirate after the liquid. @@ -2439,6 +2441,24 @@ async def aspirate96( "mix_surface_following_distance currently superseding surface_following_distance_during_mix.", DeprecationWarning, ) + + if tube_2nd_section_height_measured_from_zm != 3.2: + second_section_height = tube_2nd_section_height_measured_from_zm + warnings.warn( + "The tube_2nd_section_height_measured_from_zm parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard second_section_height parameter instead.\n" + "second_section_height_measured_from_zm currently superseding second_section_height.", + DeprecationWarning, + ) + + if tube_2nd_section_ratio != 618.0: + second_section_ratio = tube_2nd_section_ratio + warnings.warn( + "The tube_2nd_section_ratio parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard second_section_ratio parameter instead.\n" + "second_section_ratio currently superseding tube_2nd_section_ratio.", + DeprecationWarning, + ) # # # delete # # # assert self.core96_head_installed, "96 head must be installed" @@ -2524,8 +2544,8 @@ async def aspirate96( liquid_surface_no_lld=round(liquid_height * 10), pull_out_distance_transport_air=round(pull_out_distance_transport_air * 10), maximum_immersion_depth=round((minimum_height or position.z) * 10), - tube_2nd_section_height_measured_from_zm=round(tube_2nd_section_height_measured_from_zm * 10), - tube_2nd_section_ratio=round(tube_2nd_section_ratio * 10), + second_section_height=round(second_section_height * 10), + second_section_ratio=round(second_section_ratio * 10), immersion_depth=round(immersion_depth * 10), immersion_depth_direction=immersion_depth_direction or (0 if (immersion_depth >= 0) else 1), surface_following_distance=round(surface_following_distance * 10), @@ -2563,7 +2583,7 @@ async def dispense96( min_z_endpos: Optional[float] = None, lld_search_height: float = 199.9, minimum_height: Optional[float] = None, - tube_2nd_section_height_measured_from_zm: float = 3.2, + second_section_height: float = 3.2, second_section_ratio: float = 618.0, immersion_depth: float = 0, immersion_depth_direction: Optional[int] = None, @@ -2589,6 +2609,7 @@ async def dispense96( surface_following_distance_during_mixing: float = 0, air_transport_retract_dist=10, tube_2nd_section_ratio: float = 618.0, + tube_2nd_section_height_measured_from_zm: float = 3.2, ): """Dispense using the Core96 head. @@ -2607,8 +2628,8 @@ async def dispense96( min_z_endpos: Minimal end height, in mm. lld_search_height: LLD search height, in mm. minimum_height: Maximum immersion depth, in mm. Equals Minimum height during command. - tube_2nd_section_height_measured_from_zm: Unknown. - second_section_ratio: Unknown. + second_section_height: Height of the second section, in mm. + second_section_ratio: Ratio of [the diameter of the bottom * 10000] / [the diameter of the top]. immersion_depth: Immersion depth, in mm. surface_following_distance: Surface following distance, in mm. Default 0. transport_air_volume: Transport air volume, to dispense before aspiration. @@ -2699,6 +2720,15 @@ async def dispense96( "second_section_ratio currently superseding tube_2nd_section_ratio.", DeprecationWarning, ) + + if tube_2nd_section_height_measured_from_zm != 3.2: + second_section_height = tube_2nd_section_height_measured_from_zm + warnings.warn( + "The tube_2nd_section_height_measured_from_zm parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard second_section_height parameter instead.\n" + "second_section_height currently superseding tube_2nd_section_height_measured_from_zm.", + DeprecationWarning, + ) # # # delete # # # assert self.core96_head_installed, "96 head must be installed" @@ -2785,7 +2815,7 @@ async def dispense96( liquid_surface_no_lld=round(liquid_height * 10), pull_out_distance_transport_air=round(pull_out_distance_transport_air * 10), minimum_height=minimum_height or round(position.z * 10), - tube_2nd_section_height_measured_from_zm=round(tube_2nd_section_height_measured_from_zm * 10), + second_section_height=round(second_section_height * 10), second_section_ratio=round(second_section_ratio * 10), immersion_depth=round(immersion_depth * 10), immersion_depth_direction=immersion_depth_direction or (0 if (immersion_depth >= 0) else 1), @@ -5643,7 +5673,7 @@ async def aspirate_core_96( liquid_surface_no_lld: int = 3425, pull_out_distance_transport_air: int = 3425, minimum_height: int = 3425, - tube_2nd_section_height_measured_from_zm: int = 0, + second_section_height: int = 0, second_section_ratio: int = 3425, immersion_depth: int = 0, immersion_depth_direction: int = 0, @@ -5675,6 +5705,7 @@ async def aspirate_core_96( maximum_immersion_depth: int = 3425, surface_following_distance_during_mix: int = 0, tube_2nd_section_ratio: int = 3425, + tube_2nd_section_height_measured_from_zm: int = 0, ): """aspirate CoRe 96 @@ -5694,8 +5725,7 @@ async def aspirate_core_96( liquid_surface_no_lld: Liquid surface at function without LLD [0.1mm]. Must be between 0 and 3425. Default 3425. pull_out_distance_transport_air: pull out distance to take transport air in function without LLD [0.1mm]. Must be between 0 and 3425. Default 50. minimum_height: Minimum height (maximum immersion depth) [0.1mm]. Must be between 0 and 3425. Default 3425. - tube_2nd_section_height_measured_from_zm: Tube 2nd section height measured from "zm" [0.1mm] - Must be between 0 and 3425. Default 0. + second_section_height: second ratio height. Must be between 0 and 3425. Default 0. second_section_ratio: Tube 2nd section ratio (See Fig 2.). Must be between 0 and 10000. Default 3425. immersion_depth: Immersion depth [0.1mm]. Must be between 0 and 3600. Default 0. immersion_depth_direction: Direction of immersion depth (0 = go deeper, 1 = go up out of @@ -5796,6 +5826,15 @@ async def aspirate_core_96( "tube_2nd_section_ratio currently superseding second_section_ratio.", DeprecationWarning, ) + + if tube_2nd_section_height_measured_from_zm != 0: + second_section_height = tube_2nd_section_height_measured_from_zm + warnings.warn( + "The tube_2nd_section_height_measured_from_zm parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard tube_2nd_section_height_measured_from_zm parameter instead.\n" + "tube_2nd_section_height_measured_from_zm currently superseding tube_2nd_section_height_measured_from_zm.", + DeprecationWarning, + ) # # # delete # # # assert 0 <= aspiration_type <= 2, "aspiration_type must be between 0 and 2" @@ -5812,9 +5851,7 @@ async def aspirate_core_96( 0 <= pull_out_distance_transport_air <= 3425 ), "pull_out_distance_transport_air must be between 0 and 3425" assert 0 <= minimum_height <= 3425, "minimum_height must be between 0 and 3425" - assert ( - 0 <= tube_2nd_section_height_measured_from_zm <= 3425 - ), "tube_2nd_section_height_measured_from_zm must be between 0 and 3425" + assert 0 <= second_section_height <= 3425, "second_section_height must be between 0 and 3425" assert 0 <= second_section_ratio <= 10000, "second_section_ratio must be between 0 and 10000" assert 0 <= immersion_depth <= 3600, "immersion_depth must be between 0 and 3600" assert 0 <= immersion_depth_direction <= 1, "immersion_depth_direction must be between 0 and 1" @@ -5861,7 +5898,7 @@ async def aspirate_core_96( zt=f"{liquid_surface_no_lld:04}", pp=f"{pull_out_distance_transport_air:04}", zm=f"{minimum_height:04}", - zv=f"{tube_2nd_section_height_measured_from_zm:04}", + zv=f"{second_section_height:04}", zq=f"{second_section_ratio:05}", iw=f"{immersion_depth:03}", ix=immersion_depth_direction, @@ -5893,7 +5930,7 @@ async def dispense_core_96( x_position: int = 0, x_direction: int = 0, y_position: int = 0, - tube_2nd_section_height_measured_from_zm: int = 0, + second_section_height: int = 0, second_section_ratio: int = 3425, lld_search_height: int = 3425, liquid_surface_no_lld: int = 3425, @@ -5934,6 +5971,7 @@ async def dispense_core_96( mixing_position_from_liquid_surface: int = 250, surface_following_distance_during_mixing: int = 0, pull_out_distance_to_take_transport_air_in_function_without_lld: int = 50, + tube_2nd_section_height_measured_from_zm: int = 0, ): """Dispensing of liquid using CoRe 96 @@ -5945,8 +5983,7 @@ async def dispense_core_96( x_direction: X-direction. 0 = positive 1 = negative. Must be between 0 and 1. Default 0. y_position: Y-Position [0.1mm] of well A1. Must be between 1080 and 5600. Default 0. minimum_height: Minimum height (maximum immersion depth) [0.1mm]. Must be between 0 and 3425. Default 3425. - tube_2nd_section_height_measured_from_zm: Tube 2nd section height measured from - "zm" [0.1mm]. Must be between 0 and 3425. Default 0. + second_section_height: Second ratio height. [0.1mm]. Must be between 0 and 3425. Default 0. second_section_ratio: Tube 2nd section ratio (See Fig 2.). Must be between 0 and 10000. Default 3425. lld_search_height: LLD search height [0.1mm]. Must be between 0 and 3425. Default 3425. liquid_surface_no_lld: Liquid surface at function without LLD [0.1mm]. Must be between 0 and 3425. Default 3425. @@ -6061,6 +6098,15 @@ async def dispense_core_96( "pull_out_distance_to_take_transport_air_in_function_without_lld currently superseding pull_out_distance_transport_air.", DeprecationWarning, ) + + if tube_2nd_section_height_measured_from_zm != 0: + second_section_height = tube_2nd_section_height_measured_from_zm + warnings.warn( + "The tube_2nd_section_height_measured_from_zm parameter is deprecated and will be removed in the future. " + "Use the Hamilton-standard second_section_height parameter instead.\n" + "tube_2nd_section_height_measured_from_zm currently superseding second_section_height.", + DeprecationWarning, + ) # # # delete # # # assert 0 <= dispensing_mode <= 4, "dispensing_mode must be between 0 and 4" @@ -6068,9 +6114,7 @@ async def dispense_core_96( assert 0 <= x_direction <= 1, "x_direction must be between 0 and 1" assert 1080 <= y_position <= 5600, "y_position must be between 1080 and 5600" assert 0 <= minimum_height <= 3425, "minimum_height must be between 0 and 3425" - assert ( - 0 <= tube_2nd_section_height_measured_from_zm <= 3425 - ), "tube_2nd_section_height_measured_from_zm must be between 0 and 3425" + assert 0 <= second_section_height <= 3425, "second_section_height must be between 0 and 3425" assert 0 <= second_section_ratio <= 10000, "second_section_ratio must be between 0 and 10000" assert 0 <= lld_search_height <= 3425, "lld_search_height must be between 0 and 3425" assert 0 <= liquid_surface_no_lld <= 3425, "liquid_surface_no_lld must be between 0 and 3425" @@ -6122,7 +6166,7 @@ async def dispense_core_96( xd=x_direction, yh=f"{y_position:04}", zm=f"{minimum_height:04}", - zv=f"{tube_2nd_section_height_measured_from_zm:04}", + zv=f"{second_section_height:04}", zq=f"{second_section_ratio:05}", lz=f"{lld_search_height:04}", zt=f"{liquid_surface_no_lld:04}", From 56d8f288ab7107be55c71a30e2039e7d8da3cb42 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Sat, 11 Oct 2025 15:17:07 -0700 Subject: [PATCH 16/17] move stuff to deletion section, deprecate dispense_mode --- .../backends/hamilton/STAR_backend.py | 94 ++++++++++--------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index 1c69faf35e2..2d289e01b2b 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -1626,10 +1626,7 @@ async def aspirate( detection_height_difference_for_dual_lld: Optional[List[float]] = None, swap_speed: Optional[List[float]] = None, settling_time: Optional[List[float]] = None, - mix_volume: Optional[List[float]] = None, - mix_cycles: Optional[List[int]] = None, mix_position_from_liquid_surface: Optional[List[float]] = None, - mix_speed: Optional[List[float]] = None, mix_surface_following_distance: Optional[List[float]] = None, limit_curve_index: Optional[List[int]] = None, use_2nd_section_aspiration: Optional[List[bool]] = None, @@ -1646,6 +1643,9 @@ async def aspirate( liquid_surfaces_no_lld: Optional[List[float]] = None, # remove > 2026-01 immersion_depth_direction: Optional[List[int]] = None, + mix_volume: Optional[List[float]] = None, + mix_cycles: Optional[List[int]] = None, + mix_speed: Optional[List[float]] = None, ): """Aspirate liquid from the specified channels. @@ -1681,13 +1681,8 @@ async def aspirate( the LLD mode is DUAL. swap_speed: Swap speed (on leaving liquid) [1mm/s]. Must be between 3 and 1600. Default 100. settling_time: The time to wait after mix. - mix_volume: The volume to aspirate for mix. - mix_cycles: The number of cycles to perform for mix. - mix_position_from_liquid_surface: The height to aspirate from for mix - (LLD or absolute terms). - mix_speed: The speed to aspirate at for mix. - mix_surface_following_distance: The distance to follow the liquid surface for - mix. + mix_position_from_liquid_surface: The height to aspirate from for mix (LLD or absolute terms). + mix_surface_following_distance: The distance to follow the liquid surface for mix. limit_curve_index: The index of the limit curve to use. use_2nd_section_aspiration: Whether to use the second section of aspiration. @@ -1934,7 +1929,6 @@ async def dispense( use_channels: List[int], lld_search_height: Optional[List[float]] = None, liquid_surface_no_lld: Optional[List[float]] = None, - dispensing_mode: Optional[List[int]] = None, pull_out_distance_transport_air: Optional[List[float]] = None, second_section_height: Optional[List[float]] = None, second_section_ratio: Optional[List[float]] = None, @@ -1950,10 +1944,7 @@ async def dispense( dp_lld_sensitivity: Optional[List[int]] = None, swap_speed: Optional[List[float]] = None, settling_time: Optional[List[float]] = None, - mix_volume: Optional[List[float]] = None, - mix_cycles: Optional[List[int]] = None, mix_position_from_liquid_surface: Optional[List[float]] = None, - mix_speed: Optional[List[float]] = None, mix_surface_following_distance: Optional[List[float]] = None, limit_curve_index: Optional[List[int]] = None, minimum_traverse_height_at_beginning_of_a_command: Optional[int] = None, @@ -1965,6 +1956,10 @@ async def dispense( empty: Optional[List[bool]] = None, # truly "empty", does not exist in liquid editor, dm4 # remove in the future immersion_depth_direction: Optional[List[int]] = None, + mix_volume: Optional[List[float]] = None, + mix_cycles: Optional[List[int]] = None, + mix_speed: Optional[List[float]] = None, + dispensing_mode: Optional[List[int]] = None, ): """Dispense liquid from the specified channels. @@ -1975,7 +1970,6 @@ async def dispense( Args: ops: The dispense operations to perform. use_channels: The channels to use for the dispense operations. - dispensing_mode: The dispensing mode to use for each operation. lld_search_height: The height to start searching for the liquid level when using LLD. liquid_surface_no_lld: Liquid surface at function without LLD. pull_out_distance_transport_air: The distance to pull out the tip for aspirating transport air if LLD is disabled. @@ -1994,11 +1988,8 @@ async def dispense( dp_lld_sensitivity: The dp LLD sensitivity. (1 = high, 4 = low) swap_speed: Swap speed (on leaving liquid) [0.1mm/s]. Must be between 3 and 1600. Default 100. settling_time: The settling time. - mix_volume: The volume to use for mix. - mix_cycles: The number of mix cycles. mix_position_from_liquid_surface: The height to move above the liquid surface for mix. - mix_speed: The mix speed. mix_surface_following_distance: The distance to follow the liquid surface for mix. limit_curve_index: The limit curve to use for the dispense. minimum_traverse_height_at_beginning_of_a_command: The minimum height to move to before @@ -2019,6 +2010,13 @@ async def dispense( documentation. Dispense mode 4. """ + if jet is None: + jet = [False] * n + if empty is None: + empty = [False] * n + if blow_out is None: + blow_out = [False] * n + # # # TODO: delete > 2026-01 # # # if mix_volume is not None or mix_cycles is not None or mix_speed is not None: raise NotImplementedError( @@ -2033,19 +2031,26 @@ async def dispense( "out of the liquid.", DeprecationWarning, ) + + if dispensing_mode is not None: + warnings.warn( + "The dispensing_mode parameter is deprecated and will be removed in the future. " + "Use the jet, blow_out and empty parameters instead. " + "dispensing_mode currently supersedes the other three parameters if both are provided.", + DeprecationWarning, + ) + dispensing_modes = dispensing_mode + else: + dispensing_modes = [ + _dispensing_mode_for_op(empty=empty[i], jet=jet[i], blow_out=blow_out[i]) + for i in range(len(ops)) + ] # # # delete # # # x_positions, y_positions, channels_involved = self._ops_to_fw_positions(ops, use_channels) n = len(ops) - if jet is None: - jet = [False] * n - if empty is None: - empty = [False] * n - if blow_out is None: - blow_out = [False] * n - if hamilton_liquid_classes is None: hamilton_liquid_classes = [] for i, op in enumerate(ops): @@ -2089,11 +2094,6 @@ async def dispense( else: lld_search_height = [wb + sh for wb, sh in zip(well_bottoms, lld_search_height)] - dispensing_modes = dispensing_mode or [ - _dispensing_mode_for_op(empty=empty[i], jet=jet[i], blow_out=blow_out[i]) - for i in range(len(ops)) - ] - pull_out_distance_transport_air = _fill_in_defaults(pull_out_distance_transport_air, [10.0] * n) second_section_height = _fill_in_defaults(second_section_height, [3.2] * n) second_section_ratio = _fill_in_defaults(second_section_ratio, [618.0] * n) @@ -2324,18 +2324,14 @@ async def aspirate96( second_section_height: float = 3.2, second_section_ratio: float = 618.0, immersion_depth: float = 0, - immersion_depth_direction: Optional[int] = None, surface_following_distance: float = 0, transport_air_volume: float = 5.0, pre_wetting_volume: float = 5.0, gamma_lld_sensitivity: int = 1, swap_speed: float = 2.0, settling_time: float = 1.0, - mix_volume: float = 0, - mix_cycles: int = 0, mix_position_from_liquid_surface: float = 0, mix_surface_following_distance: float = 0, - speed_of_mix: float = 0.0, limit_curve_index: int = 0, # Deprecated parameters, to be removed in future versions # rm: >2026-01 @@ -2346,6 +2342,10 @@ async def aspirate96( surface_following_distance_during_mix: float = 0, tube_2nd_section_height_measured_from_zm: float = 3.2, tube_2nd_section_ratio: float = 618.0, + immersion_depth_direction: Optional[int] = None, + mix_volume: float = 0, + mix_cycles: int = 0, + speed_of_mix: float = 0.0, ): """Aspirate using the Core96 head. @@ -2576,7 +2576,6 @@ async def dispense96( empty: bool = False, blow_out: bool = False, hlc: Optional[HamiltonLiquidClass] = None, - dispense_mode: Optional[int] = None, pull_out_distance_transport_air=10, use_lld: bool = False, minimum_traverse_height_at_beginning_of_a_command: Optional[float] = None, @@ -2586,17 +2585,13 @@ async def dispense96( second_section_height: float = 3.2, second_section_ratio: float = 618.0, immersion_depth: float = 0, - immersion_depth_direction: Optional[int] = None, surface_following_distance: float = 0, transport_air_volume: float = 5.0, gamma_lld_sensitivity: int = 1, swap_speed: float = 2.0, settling_time: float = 0, - mixing_volume: float = 0, - mixing_cycles: int = 0, mix_position_from_liquid_surface: float = 0, mix_surface_following_distance: float = 0, - speed_of_mixing: float = 0.0, limit_curve_index: int = 0, cut_off_speed: float = 5.0, stop_back_volume: float = 0, @@ -2610,16 +2605,19 @@ async def dispense96( air_transport_retract_dist=10, tube_2nd_section_ratio: float = 618.0, tube_2nd_section_height_measured_from_zm: float = 3.2, + immersion_depth_direction: Optional[int] = None, + mixing_volume: float = 0, + mixing_cycles: int = 0, + speed_of_mixing: float = 0.0, + dispense_mode: Optional[int] = None, ): """Dispense using the Core96 head. Args: dispense: The Dispense command to execute. jet: Whether to use jet dispense mode. + empty: Whether to use empty dispense mode. blow_out: Whether to blow out after dispensing. - dispense_mode: The dispense mode to use. 0 = Partial volume in jet mode 1 = Blow out in jet - mode 2 = Partial volume at surface 3 = Blow out at surface 4 = Empty tip at fix position. - If `None`, the mode will be determined based on the `jet`, `empty`, and `blow_out` pull_out_distance_transport_air: The distance to retract after dispensing, in mm. use_lld: Whether to use gamma LLD. @@ -2729,6 +2727,16 @@ async def dispense96( "second_section_height currently superseding tube_2nd_section_height_measured_from_zm.", DeprecationWarning, ) + + if dispense_mode is not None: + warnings.warn( + "The dispense_mode parameter is deprecated and will be removed in the future. " + "Use the combination of the `jet`, `empty` and `blow_out` parameters instead. " + "dispense_mode currently superseding those parameters.", + DeprecationWarning, + ) + else: + dispense_mode = _dispensing_mode_for_op(empty=empty, jet=jet, blow_out=blow_out) # # # delete # # # assert self.core96_head_installed, "96 head must be installed" @@ -2770,8 +2778,6 @@ async def dispense96( liquid_height = position.z + (dispense.liquid_height or 0) - dispense_mode = _dispensing_mode_for_op(empty=empty, jet=jet, blow_out=blow_out) - liquid_to_be_dispensed = Liquid.WATER # default to water. if len(dispense.liquids[0]) > 0 and dispense.liquids[0][-1][0] is not None: # [channel][liquid][PyLabRobot.resources.liquid.Liquid] From 608f472da30b40129b80b38c402cd29a3e285c82 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Sat, 11 Oct 2025 15:23:12 -0700 Subject: [PATCH 17/17] x --- pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py index 2d289e01b2b..26fd30a6cd7 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py @@ -2010,6 +2010,8 @@ async def dispense( documentation. Dispense mode 4. """ + n = len(ops) + if jet is None: jet = [False] * n if empty is None: @@ -2049,8 +2051,6 @@ async def dispense( x_positions, y_positions, channels_involved = self._ops_to_fw_positions(ops, use_channels) - n = len(ops) - if hamilton_liquid_classes is None: hamilton_liquid_classes = [] for i, op in enumerate(ops):