Skip to content

Commit f24c8be

Browse files
committed
fix star tests without parameterset
1 parent d9e1374 commit f24c8be

File tree

3 files changed

+90
-92
lines changed

3 files changed

+90
-92
lines changed

pylabrobot/liquid_handling/backends/hamilton/STAR.py

+47-63
Original file line numberDiff line numberDiff line change
@@ -1436,8 +1436,6 @@ async def aspirate(
14361436
self,
14371437
ops: List[Aspiration],
14381438
use_channels: List[int],
1439-
jet: Optional[List[bool]] = None,
1440-
blow_out: Optional[List[bool]] = None,
14411439
lld_search_height: Optional[List[float]] = None,
14421440
clot_detection_height: Optional[List[float]] = None,
14431441
pull_out_distance_transport_air: Optional[List[float]] = None,
@@ -1487,9 +1485,6 @@ async def aspirate(
14871485
Args:
14881486
ops: The aspiration operations to perform.
14891487
use_channels: The channels to use for the operations.
1490-
jet: whether to search for a jet liquid class. Only used on dispense. Default is False.
1491-
blow_out: whether to blow out air. Only used on dispense. Note that in the VENUS Liquid
1492-
Editor, this is called "empty". Default is False.
14931488
14941489
lld_search_height: The height to start searching for the liquid level when using LLD.
14951490
clot_detection_height: Unknown, but probably the height to search for clots when doing LLD.
@@ -1550,11 +1545,6 @@ async def aspirate(
15501545

15511546
n = len(ops)
15521547

1553-
if jet is None:
1554-
jet = [False] * n
1555-
if blow_out is None:
1556-
blow_out = [False] * n
1557-
15581548
self._assert_valid_resources([op.resource for op in ops])
15591549

15601550
well_bottoms = [op.resource.get_absolute_location().z + op.offset.z + \
@@ -1934,8 +1924,6 @@ async def drop_tips96(
19341924
async def aspirate96(
19351925
self,
19361926
aspiration: Union[AspirationPlate, AspirationContainer],
1937-
jet: bool = False,
1938-
blow_out: bool = False,
19391927

19401928
use_lld: bool = False,
19411929
liquid_height: float = 0,
@@ -1961,19 +1949,14 @@ async def aspirate96(
19611949
mix_cycles: int = 0,
19621950
mix_position_from_liquid_surface: float = 0,
19631951
surface_following_distance_during_mix: float = 0,
1964-
speed_of_mix: float = 120.0,
1952+
mix_speed: float = 120.0,
19651953
limit_curve_index: int = 0,
19661954
):
19671955
""" Aspirate using the Core96 head.
19681956
19691957
Args:
19701958
aspiration: The aspiration to perform.
19711959
1972-
jet: Whether to search for a jet liquid class. Only used on dispense.
1973-
blow_out: Whether to use "blow out" dispense mode. Only used on dispense. Note that this is
1974-
labelled as "empty" in the VENUS liquid editor, but "blow out" in the firmware
1975-
documentation.
1976-
19771960
use_lld: If True, use gamma liquid level detection. If False, use liquid height.
19781961
liquid_height: The height of the liquid above the bottom of the well, in millimeters.
19791962
air_transport_retract_dist: The distance to retract after aspirating, in millimeters.
@@ -2002,7 +1985,7 @@ async def aspirate96(
20021985
liquid surface.
20031986
surface_following_distance_during_mix: The distance to follow the liquid surface
20041987
during mix.
2005-
speed_of_mix: The speed of mix.
1988+
mix_speed: The speed of mix.
20061989
limit_curve_index: The index of the limit curve to use.
20071990
"""
20081991

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

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

2014-
if jet or blow_out:
2015-
raise NotImplementedError("jet and blow out are not implemented for aspirate96 yet")
2016-
20171997
# get the first well and tip as representatives
20181998
if isinstance(aspiration, AspirationPlate):
20191999
top_left_well = aspiration.wells[0]
@@ -2024,28 +2004,25 @@ async def aspirate96(
20242004

20252005
liquid_height = position.z + liquid_height
20262006

2027-
transport_air_volume = transport_air_volume or 0
2028-
blow_out_air_volume = aspiration.blow_out_air_volume or 0
2029-
flow_rate = aspiration.flow_rate or 250
2030-
swap_speed = swap_speed or 100
2031-
settling_time = settling_time or 0.5
2032-
speed_of_mix = speed_of_mix or 10.0
2007+
if transport_air_volume is None:
2008+
transport_air_volume = 0
2009+
if aspiration.blow_out_air_volume is None:
2010+
blow_out_air_volume = 0
2011+
else:
2012+
blow_out_air_volume = aspiration.blow_out_air_volume
2013+
if aspiration.flow_rate is None:
2014+
flow_rate = 250
2015+
else:
2016+
flow_rate = aspiration.flow_rate
2017+
if swap_speed is None:
2018+
swap_speed = 100
2019+
if settling_time is None:
2020+
settling_time = 0.5
2021+
if mix_speed is None:
2022+
mix_speed = 10.0
20332023

20342024
channel_pattern = [True]*12*8
20352025

2036-
# Was this ever true? Just copied it over from pyhamilton. Could have something to do with
2037-
# the liquid classes and whether blow_out mode is enabled.
2038-
# # Unfortunately, `blow_out_air_volume` does not work correctly, so instead we aspirate air
2039-
# # manually.
2040-
# if blow_out_air_volume is not None and blow_out_air_volume > 0:
2041-
# await self.aspirate_core_96(
2042-
# x_position=int(position.x * 10),
2043-
# y_positions=int(position.y * 10),
2044-
# lld_mode=0,
2045-
# liquid_surface_at_function_without_lld=int((liquid_height + 30) * 10),
2046-
# aspiration_volumes=int(blow_out_air_volume * 10)
2047-
# )
2048-
20492026
return await self.aspirate_core_96(
20502027
x_position=round(position.x * 10),
20512028
x_direction=0,
@@ -2081,7 +2058,7 @@ async def aspirate96(
20812058
round(mix_position_from_liquid_surface * 10),
20822059
surface_following_distance_during_mix=
20832060
round(surface_following_distance_during_mix * 10),
2084-
speed_of_mix=round(speed_of_mix * 10),
2061+
mix_speed=round(mix_speed * 10),
20852062
channel_pattern=channel_pattern,
20862063
limit_curve_index=limit_curve_index,
20872064
tadm_algorithm=False,
@@ -2118,7 +2095,7 @@ async def dispense96(
21182095
mixing_cycles: int = 0,
21192096
mixing_position_from_liquid_surface: float = 0,
21202097
surface_following_distance_during_mixing: float = 0,
2121-
speed_of_mixing: float = 120.0,
2098+
mix_speed: float = 120.0,
21222099
limit_curve_index: int = 0,
21232100
cut_off_speed: float = 5.0,
21242101
stop_back_volume: float = 0,
@@ -2154,7 +2131,7 @@ async def dispense96(
21542131
mixing_cycles: Mixing cycles.
21552132
mixing_position_from_liquid_surface: Mixing position from liquid surface, in mm.
21562133
surface_following_distance_during_mixing: Surface following distance during mixing, in mm.
2157-
speed_of_mixing: Speed of mixing, in ul/s.
2134+
mix_speed: Speed of mixing, in ul/s.
21582135
limit_curve_index: Limit curve index.
21592136
cut_off_speed: Unknown.
21602137
stop_back_volume: Unknown.
@@ -2163,9 +2140,6 @@ async def dispense96(
21632140
if hlc is not None:
21642141
raise NotImplementedError("Hamilton liquid classes are deprecated.")
21652142

2166-
if jet or blow_out:
2167-
raise NotImplementedError("jet and blow out are not implemented for aspirate96 yet")
2168-
21692143
assert self.core96_head_installed, "96 head must be installed"
21702144

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

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

2183-
transport_air_volume = transport_air_volume or 0
2184-
blow_out_air_volume = dispense.blow_out_air_volume or 0
2185-
flow_rate = dispense.flow_rate or 120
2186-
swap_speed = swap_speed or 100
2187-
settling_time = settling_time or 5
2188-
speed_of_mixing = speed_of_mixing or 100
2157+
if transport_air_volume is None:
2158+
transport_air_volume = 0
2159+
if dispense.blow_out_air_volume is None:
2160+
blow_out_air_volume = 0
2161+
else:
2162+
blow_out_air_volume = dispense.blow_out_air_volume
2163+
if dispense.flow_rate is None:
2164+
flow_rate = 120
2165+
else:
2166+
flow_rate = dispense.flow_rate
2167+
if swap_speed is None:
2168+
swap_speed = 100
2169+
if settling_time is None:
2170+
settling_time = 5
2171+
if mix_speed is None:
2172+
mix_speed = 100
21892173

21902174
channel_pattern = [True]*12*8
21912175

@@ -2221,7 +2205,7 @@ async def dispense96(
22212205
mixing_cycles=mixing_cycles,
22222206
mixing_position_from_liquid_surface=round(mixing_position_from_liquid_surface*10),
22232207
surface_following_distance_during_mixing=round(surface_following_distance_during_mixing*10),
2224-
speed_of_mixing=round(speed_of_mixing*10),
2208+
mix_speed=round(mix_speed*10),
22252209
channel_pattern=channel_pattern,
22262210
limit_curve_index=limit_curve_index,
22272211
tadm_algorithm=False,
@@ -4938,7 +4922,7 @@ async def aspirate_core_96(
49384922
mix_cycles: int = 0,
49394923
mix_position_from_liquid_surface: int = 250,
49404924
surface_following_distance_during_mix: int = 0,
4941-
speed_of_mix: int = 1000,
4925+
mix_speed: int = 1000,
49424926
channel_pattern: List[bool] = [True] * 96,
49434927
limit_curve_index: int = 0,
49444928
tadm_algorithm: bool = False,
@@ -4992,7 +4976,7 @@ async def aspirate_core_96(
49924976
liquid surface (LLD or absolute terms) [0.1mm]. Must be between 0 and 990. Default 250.
49934977
surface_following_distance_during_mix: surface following distance during
49944978
mix [0.1mm]. Must be between 0 and 990. Default 0.
4995-
speed_of_mix: Speed of mix [0.1ul/s]. Must be between 3 and 5000.
4979+
mix_speed: Speed of mix [0.1ul/s]. Must be between 3 and 5000.
49964980
Default 1000.
49974981
todo: TODO: 24 hex chars. Must be between 4 and 5000.
49984982
limit_curve_index: limit curve index. Must be between 0 and 999. Default 0.
@@ -5038,8 +5022,8 @@ async def aspirate_core_96(
50385022
"mix_position_from_liquid_surface must be between 0 and 990"
50395023
assert 0 <= surface_following_distance_during_mix <= 990, \
50405024
"surface_following_distance_during_mix must be between 0 and 990"
5041-
assert 3 <= speed_of_mix <= 5000, \
5042-
"speed_of_mix must be between 3 and 5000"
5025+
assert 3 <= mix_speed <= 5000, \
5026+
"mix_speed must be between 3 and 5000"
50435027
assert 0 <= limit_curve_index <= 999, "limit_curve_index must be between 0 and 999"
50445028

50455029
assert 0 <= recording_mode <= 2, "recording_mode must be between 0 and 2"
@@ -5080,7 +5064,7 @@ async def aspirate_core_96(
50805064
hc=f"{mix_cycles:02}",
50815065
hp=f"{mix_position_from_liquid_surface:03}",
50825066
mj=f"{surface_following_distance_during_mix:03}",
5083-
hs=f"{speed_of_mix:04}",
5067+
hs=f"{mix_speed:04}",
50845068
cw=channel_pattern_hex,
50855069
cr=f"{limit_curve_index:03}",
50865070
cj=tadm_algorithm,
@@ -5120,7 +5104,7 @@ async def dispense_core_96(
51205104
mixing_cycles: int = 0,
51215105
mixing_position_from_liquid_surface: int = 250,
51225106
surface_following_distance_during_mixing: int = 0,
5123-
speed_of_mixing: int = 1000,
5107+
mix_speed: int = 1000,
51245108
channel_pattern: List[bool] = [True]*12*8,
51255109
limit_curve_index: int = 0,
51265110
tadm_algorithm: bool = False,
@@ -5177,7 +5161,7 @@ async def dispense_core_96(
51775161
surface (LLD or absolute terms) [0.1mm]. Must be between 0 and 990. Default 250.
51785162
surface_following_distance_during_mixing: surface following distance during mixing [0.1mm].
51795163
Must be between 0 and 990. Default 0.
5180-
speed_of_mixing: Speed of mixing [0.1ul/s]. Must be between 3 and 5000. Default 1000.
5164+
mix_speed: Speed of mixing [0.1ul/s]. Must be between 3 and 5000. Default 1000.
51815165
channel_pattern: list of 96 boolean values
51825166
limit_curve_index: limit curve index. Must be between 0 and 999. Default 0.
51835167
tadm_algorithm: TADM algorithm. Default False.
@@ -5224,7 +5208,7 @@ async def dispense_core_96(
52245208
"mixing_position_from_liquid_surface must be between 0 and 990"
52255209
assert 0 <= surface_following_distance_during_mixing <= 990, \
52265210
"surface_following_distance_during_mixing must be between 0 and 990"
5227-
assert 3 <= speed_of_mixing <= 5000, "speed_of_mixing must be between 3 and 5000"
5211+
assert 3 <= mix_speed <= 5000, "mix_speed must be between 3 and 5000"
52285212
assert 0 <= limit_curve_index <= 999, "limit_curve_index must be between 0 and 999"
52295213
assert 0 <= recording_mode <= 2, "recording_mode must be between 0 and 2"
52305214

@@ -5266,7 +5250,7 @@ async def dispense_core_96(
52665250
hc=f"{mixing_cycles:02}",
52675251
hp=f"{mixing_position_from_liquid_surface:03}",
52685252
mj=f"{surface_following_distance_during_mixing:03}",
5269-
hs=f"{speed_of_mixing:04}",
5253+
hs=f"{mix_speed:04}",
52705254
cw=channel_pattern_hex,
52715255
cr=f"{limit_curve_index:03}",
52725256
cj=tadm_algorithm,

pylabrobot/liquid_handling/backends/hamilton/STAR_tests.py

+17-24
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,8 @@ async def test_single_channel_aspiration(self):
406406
self.plate.lid.unassign()
407407
well = self.plate.get_item("A1")
408408
well.tracker.set_liquids([(None, 100 * 1.072)]) # liquid class correction
409-
ps = STARParameterSet.from_hamilton_liquid_class(self.hlc, num_channels=1)
410-
await self.lh.aspirate([well], vols=[self.hlc.compute_corrected_volume(100)], **ps.make_asp_kwargs())
409+
await self.lh.aspirate([well], vols=[self.hlc.compute_corrected_volume(100)],
410+
**self.hlc.make_asp_kwargs(1))
411411

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

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

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

467466
async def test_aspirate_single_resource(self):
468467
self.lh.update_head_state({i: self.tip_rack.get_tip(i) for i in range(5)})
469-
ps = STARParameterSet.from_hamilton_liquid_class(self.hlc, num_channels=5)
470468
corrected_vol = self.hlc.compute_corrected_volume(10)
471469
with no_volume_tracking():
472470
await self.lh.aspirate([self.bb]*5,vols=[corrected_vol]*5, use_channels=[0,1,2,3,4],
473-
liquid_height=[1]*5, **ps.make_asp_kwargs())
471+
liquid_height=[1]*5, **self.hlc.make_asp_kwargs(5))
474472
self._assert_command_sent_once(
475473
"C0ASid0002at0 0 0 0 0 0&tm1 1 1 1 1 0&xp04865 04865 04865 04865 04865 00000&yp2098 1962 "
476474
"1825 1688 1552 0000&th2450te2450lp2000 2000 2000 2000 2000 2000&ch000 000 000 000 000 000&"
@@ -490,12 +488,10 @@ async def test_aspirate_single_resource(self):
490488
async def test_dispense_single_resource(self):
491489
self.lh.update_head_state({i: self.tip_rack.get_tip(i) for i in range(5)})
492490
hlc = StandardVolumeFilter_Water_DispenseJet_Empty
493-
ps = STARParameterSet.from_hamilton_liquid_class(hlc, num_channels=5,
494-
jet=[True]*5, blow_out=[True]*5)
495491
corrected_vol = hlc.compute_corrected_volume(10)
496492
with no_volume_tracking():
497493
await self.lh.dispense([self.bb]*5, vols=[corrected_vol]*5, liquid_height=[1]*5,
498-
**ps.make_disp_kwargs())
494+
jet=[True]*5, blow_out=[True]*5, **hlc.make_disp_kwargs(5))
499495
self._assert_command_sent_once(
500496
"C0DSid0002dm1 1 1 1 1 1&tm1 1 1 1 1 0&xp04865 04865 04865 04865 04865 00000&yp2098 1962 "
501497
"1825 1688 1552 0000&zx1200 1200 1200 1200 1200 1200&lp2000 2000 2000 2000 2000 2000&zl1210 "
@@ -514,11 +510,10 @@ async def test_single_channel_dispense(self):
514510
assert self.plate.lid is not None
515511
self.plate.lid.unassign()
516512
hlc = StandardVolumeFilter_Water_DispenseJet_Empty
517-
ps = STARParameterSet.from_hamilton_liquid_class(hlc, num_channels=1,
518-
jet=[True]*1, blow_out=[True]*1)
519513
corrected_vol = hlc.compute_corrected_volume(100)
520514
with no_volume_tracking():
521-
await self.lh.dispense(self.plate["A1"], vols=[corrected_vol], **ps.make_disp_kwargs())
515+
await self.lh.dispense(self.plate["A1"], vols=[corrected_vol], jet=[True], blow_out=[True],
516+
**hlc.make_disp_kwargs(1))
522517
self._assert_command_sent_once(
523518
"C0DSid0002dm1 1&tm1 0&xp02983 00000&yp1457 0000&zx1866 1866&lp2000 2000&zl1866 1866&"
524519
"po0100 0100&ip0000 0000&it0 0&fp0000 0000&zu0032 0032&zr06180 06180&th2450te2450"
@@ -533,11 +528,10 @@ async def test_multi_channel_dispense(self):
533528
assert self.plate.lid is not None
534529
self.plate.lid.unassign()
535530
hlc = StandardVolumeFilter_Water_DispenseJet_Empty
536-
ps = STARParameterSet.from_hamilton_liquid_class(hlc, num_channels=2,
537-
jet=[True]*2, blow_out=[True]*2)
538531
corrected_vol = hlc.compute_corrected_volume(100)
539532
with no_volume_tracking():
540-
await self.lh.dispense(self.plate["A1:B1"], vols=[corrected_vol]*2, **ps.make_disp_kwargs())
533+
await self.lh.dispense(self.plate["A1:B1"], vols=[corrected_vol]*2, jet=[True]*2,
534+
blow_out=[True]*2, **hlc.make_disp_kwargs(2))
541535

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

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

609603
with no_volume_tracking():
610-
await self.lh.dispense96(self.plate, corrected_volume)
604+
await self.lh.dispense96(self.plate, corrected_volume, **hlc.make_disp96_kwargs(), blow_out=True)
611605

612-
# volume used to be 01072, but that was generated using a non-core liquid class.
613606
self._assert_command_sent_once(
614607
"C0EDid0001da3xs02983xd0yh1457zh2450ze2450lz1999zt1866zm1866iw000ix0fh000df01083dg1200vt050"
615608
"bv00000cm0cs1bs0020wh00hv00000hc00hp000hs1200es0050ev000zv0032ej00zq06180mj000cj0cx0cr000"

0 commit comments

Comments
 (0)