Skip to content

Commit

Permalink
hamilton liquid classes make kwargs (#248)
Browse files Browse the repository at this point in the history
  • Loading branch information
rickwierenga authored Sep 27, 2024
1 parent 25b88eb commit f3cb886
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 453 deletions.
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

0 comments on commit f3cb886

Please sign in to comment.