Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hamilton liquid classes make kwargs #248

Merged
merged 15 commits into from
Sep 27, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Move `LiquidHandlerChatterboxBackend` from `liquid_handling.backends.chatterbox_backend` to `liquid_handling.backends.chatterbox` (https://github.com/PyLabRobot/pylabrobot/pull/242)
- Changed `pedestal_size_z=-5` to `pedestal_size_z=-4.74` for `PLT_CAR_L5AC_A00` (https://github.com/PyLabRobot/pylabrobot/pull/255)
- rename `homogenization_` parameters in `STAR` to `mix_` (https://github.com/PyLabRobot/pylabrobot/pull/261)
- Hamilton liquid classes are no longer automatically inferred on the backends (`STAR`/`Vantage`). Instead, they create kwargs with `make_(asp|disp)(96)?_kwargs` (https://github.com/PyLabRobot/pylabrobot/pull/248)
- This also applies to volume correction curves, which are now the users' responsibility.

### Added

Expand Down
280 changes: 76 additions & 204 deletions pylabrobot/liquid_handling/backends/hamilton/STAR.py

Large diffs are not rendered by default.

54 changes: 41 additions & 13 deletions pylabrobot/liquid_handling/backends/hamilton/STAR_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.standard import Pickup, GripDirection
from pylabrobot.liquid_handling.liquid_classes.hamilton.star import (
StandardVolumeFilter_Water_DispenseSurface,
StandardVolumeFilter_Water_DispenseJet_Empty,
HighVolumeFilter_96COREHead1000ul_Water_DispenseSurface_Empty)
from pylabrobot.plate_reading import PlateReader
from pylabrobot.plate_reading.plate_reader_tests import MockPlateReaderBackend
from pylabrobot.resources import (
Expand Down Expand Up @@ -224,6 +228,10 @@ def __init__(self, name: str):

await self.lh.setup()

self.hlc = StandardVolumeFilter_Water_DispenseSurface.copy()
self.hlc.aspiration_air_transport_volume = 0
self.hlc.dispense_air_transport_volume = 0

async def asyncTearDown(self):
await self.lh.stop()

Expand Down Expand Up @@ -370,7 +378,9 @@ async def test_aspirate56(self):
self.plate.lid.unassign()
for well in self.plate.get_items(["A1", "B1"]):
well.tracker.set_liquids([(None, 100 * 1.072)]) # liquid class correction
await self.lh.aspirate(self.plate["A1", "B1"], vols=[100, 100], use_channels=[4, 5])
corrected_vol = self.hlc.compute_corrected_volume(100)
await self.lh.aspirate(self.plate["A1", "B1"], vols=[corrected_vol, corrected_vol],
use_channels=[4, 5], **self.hlc.make_asp_kwargs(2))
self._assert_command_sent_once("C0ASid0004at0 0 0 0 0 0 0&tm0 0 0 0 1 1 0&xp00000 00000 00000 "
"00000 02983 02983 00000&yp0000 0000 0000 0000 1457 1367 0000&th2450te2450lp2000 2000 2000 "
"2000 2000 2000 2000&ch000 000 000 000 000 000 000&zl1866 1866 1866 1866 1866 1866 1866&"
Expand All @@ -394,7 +404,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
await self.lh.aspirate([well], vols=[100])
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 @@ -413,7 +424,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
await self.lh.aspirate([well], vols=[100], liquid_height=[10])
await self.lh.aspirate([well], vols=[self.hlc.compute_corrected_volume(100)],
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 @@ -433,7 +445,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
await self.lh.aspirate(self.plate["A1:B1"], vols=[100]*2)
corrected_vol = self.hlc.compute_corrected_volume(100)
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 @@ -449,8 +463,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)})
corrected_vol = self.hlc.compute_corrected_volume(10)
with no_volume_tracking():
await self.lh.aspirate([self.bb]*5,vols=[10]*5, use_channels=[0,1,2,3,4], liquid_height=[1]*5)
await self.lh.aspirate([self.bb]*5,vols=[corrected_vol]*5, use_channels=[0,1,2,3,4],
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 @@ -469,9 +485,11 @@ 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
corrected_vol = hlc.compute_corrected_volume(10)
with no_volume_tracking():
await self.lh.dispense([self.bb]*5, vols=[10]*5, use_channels=[0,1,2,3,4],
liquid_height=[1]*5, blow_out=[True]*5, jet=[True]*5)
await self.lh.dispense([self.bb]*5, vols=[corrected_vol]*5, liquid_height=[1]*5,
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 @@ -489,8 +507,11 @@ async def test_single_channel_dispense(self):
self.lh.update_head_state({0: self.tip_rack.get_tip("A1")})
assert self.plate.lid is not None
self.plate.lid.unassign()
hlc = StandardVolumeFilter_Water_DispenseJet_Empty
corrected_vol = hlc.compute_corrected_volume(100)
with no_volume_tracking():
await self.lh.dispense(self.plate["A1"], vols=[100], jet=[True], blow_out=[True])
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 @@ -504,8 +525,11 @@ async def test_multi_channel_dispense(self):
# TODO: Hamilton liquid classes
assert self.plate.lid is not None
self.plate.lid.unassign()
hlc = StandardVolumeFilter_Water_DispenseJet_Empty
corrected_vol = hlc.compute_corrected_volume(100)
with no_volume_tracking():
await self.lh.dispense(self.plate["A1:B1"], vols=[100]*2, jet=[True]*2, blow_out=[True]*2)
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 @@ -553,7 +577,9 @@ async def test_core_96_aspirate(self):
# TODO: Hamilton liquid classes
assert self.plate.lid is not None
self.plate.lid.unassign()
await self.lh.aspirate96(self.plate, volume=100, blow_out=True)
hlc = HighVolumeFilter_96COREHead1000ul_Water_DispenseSurface_Empty
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 @@ -568,12 +594,14 @@ 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()
await self.lh.aspirate96(self.plate, 100, blow_out=True) # 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, 100, blow_out=True)
await self.lh.dispense96(self.plate, corrected_volume, blow_out=True,
**hlc.make_disp96_kwargs())

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