Skip to content

Commit

Permalink
fix star tests without parameterset
Browse files Browse the repository at this point in the history
  • Loading branch information
rickwierenga committed Sep 27, 2024
1 parent d9e1374 commit 3bfbb55
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 92 deletions.
110 changes: 47 additions & 63 deletions pylabrobot/liquid_handling/backends/hamilton/STAR.py
Original file line number Diff line number Diff line change
Expand Up @@ -1436,8 +1436,6 @@ async def aspirate(
self,
ops: List[Aspiration],
use_channels: List[int],
jet: Optional[List[bool]] = None,
blow_out: Optional[List[bool]] = None,
lld_search_height: Optional[List[float]] = None,
clot_detection_height: Optional[List[float]] = None,
pull_out_distance_transport_air: Optional[List[float]] = None,
Expand Down Expand Up @@ -1487,9 +1485,6 @@ async def aspirate(
Args:
ops: The aspiration operations to perform.
use_channels: The channels to use for the operations.
jet: whether to search for a jet liquid class. Only used on dispense. Default is False.
blow_out: whether to blow out air. Only used on dispense. Note that in the VENUS Liquid
Editor, this is called "empty". Default is False.
lld_search_height: The height to start searching for the liquid level when using LLD.
clot_detection_height: Unknown, but probably the height to search for clots when doing LLD.
Expand Down Expand Up @@ -1550,11 +1545,6 @@ async def aspirate(

n = len(ops)

if jet is None:
jet = [False] * n
if blow_out is None:
blow_out = [False] * n

self._assert_valid_resources([op.resource for op in ops])

well_bottoms = [op.resource.get_absolute_location().z + op.offset.z + \
Expand Down Expand Up @@ -1934,8 +1924,6 @@ async def drop_tips96(
async def aspirate96(
self,
aspiration: Union[AspirationPlate, AspirationContainer],
jet: bool = False,
blow_out: bool = False,

use_lld: bool = False,
liquid_height: float = 0,
Expand All @@ -1961,19 +1949,14 @@ async def aspirate96(
mix_cycles: int = 0,
mix_position_from_liquid_surface: float = 0,
surface_following_distance_during_mix: float = 0,
speed_of_mix: float = 120.0,
mix_speed: float = 120.0,
limit_curve_index: int = 0,
):
""" Aspirate using the Core96 head.
Args:
aspiration: The aspiration to perform.
jet: Whether to search for a jet liquid class. Only used on dispense.
blow_out: Whether to use "blow out" dispense mode. Only used on dispense. Note that this is
labelled as "empty" in the VENUS liquid editor, but "blow out" in the firmware
documentation.
use_lld: If True, use gamma liquid level detection. If False, use liquid height.
liquid_height: The height of the liquid above the bottom of the well, in millimeters.
air_transport_retract_dist: The distance to retract after aspirating, in millimeters.
Expand Down Expand Up @@ -2002,7 +1985,7 @@ async def aspirate96(
liquid surface.
surface_following_distance_during_mix: The distance to follow the liquid surface
during mix.
speed_of_mix: The speed of mix.
mix_speed: The speed of mix.
limit_curve_index: The index of the limit curve to use.
"""

Expand All @@ -2011,9 +1994,6 @@ async def aspirate96(

assert self.core96_head_installed, "96 head must be installed"

if jet or blow_out:
raise NotImplementedError("jet and blow out are not implemented for aspirate96 yet")

# get the first well and tip as representatives
if isinstance(aspiration, AspirationPlate):
top_left_well = aspiration.wells[0]
Expand All @@ -2024,28 +2004,25 @@ async def aspirate96(

liquid_height = position.z + liquid_height

transport_air_volume = transport_air_volume or 0
blow_out_air_volume = aspiration.blow_out_air_volume or 0
flow_rate = aspiration.flow_rate or 250
swap_speed = swap_speed or 100
settling_time = settling_time or 0.5
speed_of_mix = speed_of_mix or 10.0
if transport_air_volume is None:
transport_air_volume = 0
if aspiration.blow_out_air_volume is None:
blow_out_air_volume = 0
else:
blow_out_air_volume = aspiration.blow_out_air_volume
if aspiration.flow_rate is None:
flow_rate = 250
else:
flow_rate = aspiration.flow_rate
if swap_speed is None:
swap_speed = 100
if settling_time is None:
settling_time = 0.5
if mix_speed is None:
mix_speed = 10.0

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)
# )

return await self.aspirate_core_96(
x_position=round(position.x * 10),
x_direction=0,
Expand Down Expand Up @@ -2081,7 +2058,7 @@ async def aspirate96(
round(mix_position_from_liquid_surface * 10),
surface_following_distance_during_mix=
round(surface_following_distance_during_mix * 10),
speed_of_mix=round(speed_of_mix * 10),
mix_speed=round(mix_speed * 10),
channel_pattern=channel_pattern,
limit_curve_index=limit_curve_index,
tadm_algorithm=False,
Expand Down Expand Up @@ -2118,7 +2095,7 @@ async def dispense96(
mixing_cycles: int = 0,
mixing_position_from_liquid_surface: float = 0,
surface_following_distance_during_mixing: float = 0,
speed_of_mixing: float = 120.0,
mix_speed: float = 120.0,
limit_curve_index: int = 0,
cut_off_speed: float = 5.0,
stop_back_volume: float = 0,
Expand Down Expand Up @@ -2154,7 +2131,7 @@ async def dispense96(
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_speed: Speed of mixing, in ul/s.
limit_curve_index: Limit curve index.
cut_off_speed: Unknown.
stop_back_volume: Unknown.
Expand All @@ -2163,9 +2140,6 @@ async def dispense96(
if hlc is not None:
raise NotImplementedError("Hamilton liquid classes are deprecated.")

if jet or blow_out:
raise NotImplementedError("jet and blow out are not implemented for aspirate96 yet")

assert self.core96_head_installed, "96 head must be installed"

# get the first well and tip as representatives
Expand All @@ -2180,12 +2154,22 @@ async def dispense96(

dispense_mode = _dispensing_mode_for_op(empty=empty, jet=jet, blow_out=blow_out)

transport_air_volume = transport_air_volume or 0
blow_out_air_volume = dispense.blow_out_air_volume or 0
flow_rate = dispense.flow_rate or 120
swap_speed = swap_speed or 100
settling_time = settling_time or 5
speed_of_mixing = speed_of_mixing or 100
if transport_air_volume is None:
transport_air_volume = 0
if dispense.blow_out_air_volume is None:
blow_out_air_volume = 0
else:
blow_out_air_volume = dispense.blow_out_air_volume
if dispense.flow_rate is None:
flow_rate = 120
else:
flow_rate = dispense.flow_rate
if swap_speed is None:
swap_speed = 100
if settling_time is None:
settling_time = 5
if mix_speed is None:
mix_speed = 100

channel_pattern = [True]*12*8

Expand Down Expand Up @@ -2221,7 +2205,7 @@ async def dispense96(
mixing_cycles=mixing_cycles,
mixing_position_from_liquid_surface=round(mixing_position_from_liquid_surface*10),
surface_following_distance_during_mixing=round(surface_following_distance_during_mixing*10),
speed_of_mixing=round(speed_of_mixing*10),
mix_speed=round(mix_speed*10),
channel_pattern=channel_pattern,
limit_curve_index=limit_curve_index,
tadm_algorithm=False,
Expand Down Expand Up @@ -4938,7 +4922,7 @@ async def aspirate_core_96(
mix_cycles: int = 0,
mix_position_from_liquid_surface: int = 250,
surface_following_distance_during_mix: int = 0,
speed_of_mix: int = 1000,
mix_speed: int = 1000,
channel_pattern: List[bool] = [True] * 96,
limit_curve_index: int = 0,
tadm_algorithm: bool = False,
Expand Down Expand Up @@ -4992,7 +4976,7 @@ async def aspirate_core_96(
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 [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.
mix_speed: Speed of mix [0.1ul/s]. Must be between 3 and 5000.
Default 1000.
todo: TODO: 24 hex chars. Must be between 4 and 5000.
limit_curve_index: limit curve index. Must be between 0 and 999. Default 0.
Expand Down Expand Up @@ -5038,8 +5022,8 @@ async def aspirate_core_96(
"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"
assert 3 <= speed_of_mix <= 5000, \
"speed_of_mix must be between 3 and 5000"
assert 3 <= mix_speed <= 5000, \
"mix_speed 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"
Expand Down Expand Up @@ -5080,7 +5064,7 @@ async def aspirate_core_96(
hc=f"{mix_cycles:02}",
hp=f"{mix_position_from_liquid_surface:03}",
mj=f"{surface_following_distance_during_mix:03}",
hs=f"{speed_of_mix:04}",
hs=f"{mix_speed:04}",
cw=channel_pattern_hex,
cr=f"{limit_curve_index:03}",
cj=tadm_algorithm,
Expand Down Expand Up @@ -5120,7 +5104,7 @@ async def dispense_core_96(
mixing_cycles: int = 0,
mixing_position_from_liquid_surface: int = 250,
surface_following_distance_during_mixing: int = 0,
speed_of_mixing: int = 1000,
mix_speed: int = 1000,
channel_pattern: List[bool] = [True]*12*8,
limit_curve_index: int = 0,
tadm_algorithm: bool = False,
Expand Down Expand Up @@ -5177,7 +5161,7 @@ async def dispense_core_96(
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.
speed_of_mixing: Speed of mixing [0.1ul/s]. Must be between 3 and 5000. Default 1000.
mix_speed: 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.
tadm_algorithm: TADM algorithm. Default False.
Expand Down Expand Up @@ -5224,7 +5208,7 @@ async def dispense_core_96(
"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"
assert 3 <= speed_of_mixing <= 5000, "speed_of_mixing must be between 3 and 5000"
assert 3 <= mix_speed <= 5000, "mix_speed 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"

Expand Down Expand Up @@ -5266,7 +5250,7 @@ async def dispense_core_96(
hc=f"{mixing_cycles:02}",
hp=f"{mixing_position_from_liquid_surface:03}",
mj=f"{surface_following_distance_during_mixing:03}",
hs=f"{speed_of_mixing:04}",
hs=f"{mix_speed:04}",
cw=channel_pattern_hex,
cr=f"{limit_curve_index:03}",
cj=tadm_algorithm,
Expand Down
41 changes: 17 additions & 24 deletions pylabrobot/liquid_handling/backends/hamilton/STAR_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,8 +406,8 @@ async def test_single_channel_aspiration(self):
self.plate.lid.unassign()
well = self.plate.get_item("A1")
well.tracker.set_liquids([(None, 100 * 1.072)]) # liquid class correction
ps = STARParameterSet.from_hamilton_liquid_class(self.hlc, num_channels=1)
await self.lh.aspirate([well], vols=[self.hlc.compute_corrected_volume(100)], **ps.make_asp_kwargs())
await self.lh.aspirate([well], vols=[self.hlc.compute_corrected_volume(100)],
**self.hlc.make_asp_kwargs(1))

# This passes the test, but is not the real command.
self._assert_command_sent_once(
Expand All @@ -426,9 +426,8 @@ async def test_single_channel_aspiration_liquid_height(self):
self.plate.lid.unassign()
well = self.plate.get_item("A1")
well.tracker.set_liquids([(None, 100 * 1.072)]) # liquid class correction
ps = STARParameterSet.from_hamilton_liquid_class(self.hlc, num_channels=1)
await self.lh.aspirate([well], vols=[self.hlc.compute_corrected_volume(100)],
liquid_height=[10], **ps.make_asp_kwargs())
liquid_height=[10], **self.hlc.make_asp_kwargs(1))

# This passes the test, but is not the real command.
self._assert_command_sent_once(
Expand All @@ -448,9 +447,9 @@ async def test_multi_channel_aspiration(self):
wells = self.plate.get_items("A1:B1")
for well in wells:
well.tracker.set_liquids([(None, 100 * 1.072)]) # liquid class correction
ps = STARParameterSet.from_hamilton_liquid_class(self.hlc, num_channels=2)
corrected_vol = self.hlc.compute_corrected_volume(100)
await self.lh.aspirate(self.plate["A1:B1"], vols=[corrected_vol]*2, **ps.make_asp_kwargs())
await self.lh.aspirate(self.plate["A1:B1"], vols=[corrected_vol]*2,
**self.hlc.make_asp_kwargs(2))

# This passes the test, but is not the real command.
self._assert_command_sent_once(
Expand All @@ -466,11 +465,10 @@ async def test_multi_channel_aspiration(self):

async def test_aspirate_single_resource(self):
self.lh.update_head_state({i: self.tip_rack.get_tip(i) for i in range(5)})
ps = STARParameterSet.from_hamilton_liquid_class(self.hlc, num_channels=5)
corrected_vol = self.hlc.compute_corrected_volume(10)
with no_volume_tracking():
await self.lh.aspirate([self.bb]*5,vols=[corrected_vol]*5, use_channels=[0,1,2,3,4],
liquid_height=[1]*5, **ps.make_asp_kwargs())
liquid_height=[1]*5, **self.hlc.make_asp_kwargs(5))
self._assert_command_sent_once(
"C0ASid0002at0 0 0 0 0 0&tm1 1 1 1 1 0&xp04865 04865 04865 04865 04865 00000&yp2098 1962 "
"1825 1688 1552 0000&th2450te2450lp2000 2000 2000 2000 2000 2000&ch000 000 000 000 000 000&"
Expand All @@ -490,12 +488,10 @@ async def test_aspirate_single_resource(self):
async def test_dispense_single_resource(self):
self.lh.update_head_state({i: self.tip_rack.get_tip(i) for i in range(5)})
hlc = StandardVolumeFilter_Water_DispenseJet_Empty
ps = STARParameterSet.from_hamilton_liquid_class(hlc, num_channels=5,
jet=[True]*5, blow_out=[True]*5)
corrected_vol = hlc.compute_corrected_volume(10)
with no_volume_tracking():
await self.lh.dispense([self.bb]*5, vols=[corrected_vol]*5, liquid_height=[1]*5,
**ps.make_disp_kwargs())
jet=[True]*5, blow_out=[True]*5, **hlc.make_disp_kwargs(5))
self._assert_command_sent_once(
"C0DSid0002dm1 1 1 1 1 1&tm1 1 1 1 1 0&xp04865 04865 04865 04865 04865 00000&yp2098 1962 "
"1825 1688 1552 0000&zx1200 1200 1200 1200 1200 1200&lp2000 2000 2000 2000 2000 2000&zl1210 "
Expand All @@ -514,11 +510,10 @@ async def test_single_channel_dispense(self):
assert self.plate.lid is not None
self.plate.lid.unassign()
hlc = StandardVolumeFilter_Water_DispenseJet_Empty
ps = STARParameterSet.from_hamilton_liquid_class(hlc, num_channels=1,
jet=[True]*1, blow_out=[True]*1)
corrected_vol = hlc.compute_corrected_volume(100)
with no_volume_tracking():
await self.lh.dispense(self.plate["A1"], vols=[corrected_vol], **ps.make_disp_kwargs())
await self.lh.dispense(self.plate["A1"], vols=[corrected_vol], jet=[True], blow_out=[True],
**hlc.make_disp_kwargs(1))
self._assert_command_sent_once(
"C0DSid0002dm1 1&tm1 0&xp02983 00000&yp1457 0000&zx1866 1866&lp2000 2000&zl1866 1866&"
"po0100 0100&ip0000 0000&it0 0&fp0000 0000&zu0032 0032&zr06180 06180&th2450te2450"
Expand All @@ -533,11 +528,10 @@ async def test_multi_channel_dispense(self):
assert self.plate.lid is not None
self.plate.lid.unassign()
hlc = StandardVolumeFilter_Water_DispenseJet_Empty
ps = STARParameterSet.from_hamilton_liquid_class(hlc, num_channels=2,
jet=[True]*2, blow_out=[True]*2)
corrected_vol = hlc.compute_corrected_volume(100)
with no_volume_tracking():
await self.lh.dispense(self.plate["A1:B1"], vols=[corrected_vol]*2, **ps.make_disp_kwargs())
await self.lh.dispense(self.plate["A1:B1"], vols=[corrected_vol]*2, jet=[True]*2,
blow_out=[True]*2, **hlc.make_disp_kwargs(2))

self._assert_command_sent_once(
"C0DSid0002dm1 1 1&tm1 1 0&xp02983 02983 00000&yp1457 1367 0000&zx1866 1866 1866&lp2000 2000 "
Expand Down Expand Up @@ -586,9 +580,8 @@ async def test_core_96_aspirate(self):
assert self.plate.lid is not None
self.plate.lid.unassign()
hlc = HighVolumeFilter_96COREHead1000ul_Water_DispenseSurface_Empty
ps = STARParameterSet.from_hamilton_liquid_class(hlc)
corrected_volume = hlc.compute_corrected_volume(100) # need different liquid class
await self.lh.aspirate96(self.plate, volume=corrected_volume, **ps.make_asp96_kwargs())
corrected_volume = hlc.compute_corrected_volume(100)
await self.lh.aspirate96(self.plate, volume=corrected_volume, **hlc.make_asp96_kwargs())

# volume used to be 01072, but that was generated using a non-core liquid class.
self._assert_command_sent_once(
Expand All @@ -603,13 +596,13 @@ async def test_core_96_dispense(self):
await self.lh.pick_up_tips96(self.tip_rack2) # pick up high volume tips
if self.plate.lid is not None:
self.plate.lid.unassign()
corrected_volume = self.hlc.compute_corrected_volume(100) # need different liquid class
await self.lh.aspirate96(self.plate, corrected_volume) # aspirate first
hlc = HighVolumeFilter_96COREHead1000ul_Water_DispenseSurface_Empty
corrected_volume = hlc.compute_corrected_volume(100)
await self.lh.aspirate96(self.plate, corrected_volume, **hlc.make_asp96_kwargs())

with no_volume_tracking():
await self.lh.dispense96(self.plate, corrected_volume)
await self.lh.dispense96(self.plate, corrected_volume, **hlc.make_disp96_kwargs(), blow_out=True)

# volume used to be 01072, but that was generated using a non-core liquid class.
self._assert_command_sent_once(
"C0EDid0001da3xs02983xd0yh1457zh2450ze2450lz1999zt1866zm1866iw000ix0fh000df01083dg1200vt050"
"bv00000cm0cs1bs0020wh00hv00000hc00hp000hs1200es0050ev000zv0032ej00zq06180mj000cj0cx0cr000"
Expand Down
Loading

0 comments on commit 3bfbb55

Please sign in to comment.