Basic liquid handling#

In this notebook, you will learn how to use PyHamilton to move water from one range of wells to another.

Note: before running this notebook, you should have:

  • Installed PyHamilton and the USB driver as described in the installation guide.

  • Connected the Hamilton to your computer using the USB cable.

Video of what this code does:

Setting up a connection with the robot#

Start by importing the LiquidHandler class, which will serve as a front end for all liquid handling operations.

Backends serve as communicators between between LiquidHandlers and the actual hardware. Since we are using a Hamilton STAR, we also import the STAR backend.

%load_ext autoreload
%autoreload 2
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import STAR
INFO:opentrons_shared_data.load:Using packaged shared data path: /Users/rickwierenga/pyhamilton/Dropbox (MIT)/Hamilton_Methods/Pyhamilton_Methods/220319_rick_folder/pylabrobot/env/lib/python3.10/site-packages/opentrons_shared_data/data

In addition, import the STARLetDeck, which represents the deck of the Hamilton STAR.

from pylabrobot.resources.hamilton import STARLetDeck

Create a new liquid handler using STAR as its backend.

backend = STAR()
lh = LiquidHandler(backend=backend, deck=STARLetDeck())

The final step is to open communication with the robot. This is done using the setup() method.

await lh.setup()
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Finding Hamilton USB device...
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Found Hamilton USB device.
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Found endpoints. 
Write:
       ENDPOINT 0x2: Bulk OUT ===============================
       bLength          :    0x7 (7 bytes)
       bDescriptorType  :    0x5 Endpoint
       bEndpointAddress :    0x2 OUT
       bmAttributes     :    0x2 Bulk
       wMaxPacketSize   :   0x40 (64 bytes)
       bInterval        :    0x0 
Read:
       ENDPOINT 0x81: Bulk IN ===============================
       bLength          :    0x7 (7 bytes)
       bDescriptorType  :    0x5 Endpoint
       bEndpointAddress :   0x81 IN
       bmAttributes     :    0x2 Bulk
       wMaxPacketSize   :   0x40 (64 bytes)
       bInterval        :    0x0
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Sent command: C0QWid0001
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Received response: C0QWid0001er00/00qw1
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Sent command: R0QWid0002
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Received response: R0QWid0002qw1
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Sent command: C0PGid0003th2840
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Received response: C0PGid0003er00/00

Creating the deck layout#

Now that we have a LiquidHandler instance, we can define the deck layout.

The layout in this tutorial will contain five sets of standard volume tips with filter, 1 set of 96 1mL wells, and tip and plate carriers on which these resources are positioned.

Start by importing the relevant objects and variables from the PyHamilton package. This notebook uses the following resources:

from pylabrobot.resources import (
    TIP_CAR_480_A00,
    PLT_CAR_L5AC_A00,
    Cos_96_DW_1mL,
    HTF_L
)

Then create a tip carrier named tip carrier, which will contain tip rack at all 5 positions. These positions can be accessed using tip_car[x], and are 0 indexed.

tip_car = TIP_CAR_480_A00(name='tip carrier')
tip_car[0] = HTF_L(name='tips_01')

Use assign_child_resources() to assign the tip carrier to the deck of the liquid handler. All resources contained by this carrier will be assigned automatically.

In the rails parameter, we can pass the location of the tip carrier. The locations of the tips will automatically be calculated.

lh.deck.assign_child_resource(tip_car, rails=3)

Repeat this for the plates.

plt_car = PLT_CAR_L5AC_A00(name='plate carrier')
plt_car[0] = Cos_96_DW_1mL(name='plate_01')
lh.deck.assign_child_resource(plt_car, rails=15)

Let’s look at a summary of the deck layout using summary().

lh.summary()
Rail     Resource                   Type                Coordinates (mm)
===============================================================================================
(3)  ├── tip carrier                TipCarrier          (145.000, 063.000, 100.000)
     │   ├── tips_01                TipRack             (162.900, 145.800, 131.450)
     │   ├── <empty>
     │   ├── <empty>
     │   ├── <empty>
     │   ├── <empty>
     │
(15) ├── plate carrier              PlateCarrier        (415.000, 063.000, 100.000)
     │   ├── plate_01               Plate               (433.000, 146.000, 187.150)
     │   ├── <empty>
     │   ├── <empty>
     │   ├── <empty>
     │   ├── <empty>

Picking up tips#

Picking up tips is as easy as querying the tips from the tiprack.

tiprack = lh.get_resource("tips_01")
await lh.pick_up_tips(tiprack["A1:C1"])
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Sent command: C0TTid0004tt01tf1tl0871tv12500tg3tu0
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Received response: C0TTid0004er00/00
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Sent command: C0TPid0005xp01629 01629 01629 00000&yp1458 1368 1278 0000&tm1 1 1 0&tt01tp2244tz2164th2450td0
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Received response: C0TPid0005er00/00

Aspirating and dispensing#

Aspirating and dispensing work similarly to picking up tips: where you use booleans to specify which tips to pick up, with aspiration and dispensing you use floats to specify the volume to aspirate or dispense in \(\mu L\).

The cells below move liquid from wells 'A1:C1' to 'D1:F1' using channels 1, 2, and 3 using the aspirate() and dispense() methods.

plate = lh.get_resource("plate_01")
await lh.aspirate(plate["A1:C1"], vols=[100.0, 50.0, 200.0])
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Sent command: C0ASid0006at0&tm1 1 1 0&xp04330 04330 04330 00000&yp1460 1370 1280 0000&th2450te2450lp1931 1931 1931&ch000 000 000&zl1881 1881 1881&po0100 0100 0100&zu0032 0032 0032&zr06180 06180 06180&zx1831 1831 1831&ip0000 0000 0000&it0 0 0&fp0000 0000 0000&av01072 00551 02110&as1000 1000 1000&ta000 000 000&ba0000 0000 0000&oa000 000 000&lm0 0 0&ll1 1 1&lv1 1 1&zo000 000 000&ld00 00 00&de0020 0020 0020&wt10 10 10&mv00000 00000 00000&mc00 00 00&mp000 000 000&ms1000 1000 1000&mh0000 0000 0000&gi000 000 000&gj0gk0lk0 0 0&ik0000 0000 0000&sd0500 0500 0500&se0500 0500 0500&sz0300 0300 0300&io0000 0000 0000&il00000 00000 00000&in0000 0000 0000&
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Received response: C0ASid0006er00/00

After the liquid has been aspirated, dispense it in the wells below. Note that while we specify different wells, we are still using the same channels. This is needed because only these channels contain liquid, of course.

await lh.dispense(plate["D1:F1"], vols=[100.0, 50.0, 200.0])
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Sent command: C0DSid0007dm2 2 2&tm1 1 1 0&xp04330 04330 04330 00000&yp1190 1100 1010 0000&zx1871 1871 1871&lp2321 2321 2321&zl1881 1881 1881&po0100 0100 0100&ip0000 0000 0000&it0 0 0&fp0000 0000 0000&zu0032 0032 0032&zr06180 06180 06180&th2450te2450dv01072 00551 02110&ds1200 1200 1200&ss0050 0050 0050&rv000 000 000&ta000 000 000&ba0000 0000 0000&lm0 0 0&dj00zo000 000 000&ll1 1 1&lv1 1 1&de0020 0020 0020&wt00 00 00&mv00000 00000 00000&mc00 00 00&mp000 000 000&ms0010 0010 0010&mh0000 0000 0000&gi000 000 000&gj0gk0
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Received response: C0DSid0007er00/00

Let’s move the liquid back to the original wells.

await lh.aspirate(plate["D1:F1"], vols=[100.0, 50.0, 200.0])
await lh.dispense(plate["A1:C1"], vols=[100.0, 50.0, 200.0])
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Sent command: C0ASid0008at0&tm1 1 1 0&xp04330 04330 04330 00000&yp1190 1100 1010 0000&th2450te2450lp1931 1931 1931&ch000 000 000&zl1881 1881 1881&po0100 0100 0100&zu0032 0032 0032&zr06180 06180 06180&zx1831 1831 1831&ip0000 0000 0000&it0 0 0&fp0000 0000 0000&av01072 00551 02110&as1000 1000 1000&ta000 000 000&ba0000 0000 0000&oa000 000 000&lm0 0 0&ll1 1 1&lv1 1 1&zo000 000 000&ld00 00 00&de0020 0020 0020&wt10 10 10&mv00000 00000 00000&mc00 00 00&mp000 000 000&ms1000 1000 1000&mh0000 0000 0000&gi000 000 000&gj0gk0lk0 0 0&ik0000 0000 0000&sd0500 0500 0500&se0500 0500 0500&sz0300 0300 0300&io0000 0000 0000&il00000 00000 00000&in0000 0000 0000&
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Received response: C0ASid0008er00/00
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Sent command: C0DSid0009dm2 2 2&tm1 1 1 0&xp04330 04330 04330 00000&yp1460 1370 1280 0000&zx1871 1871 1871&lp2321 2321 2321&zl1881 1881 1881&po0100 0100 0100&ip0000 0000 0000&it0 0 0&fp0000 0000 0000&zu0032 0032 0032&zr06180 06180 06180&th2450te2450dv01072 00551 02110&ds1200 1200 1200&ss0050 0050 0050&rv000 000 000&ta000 000 000&ba0000 0000 0000&lm0 0 0&dj00zo000 000 000&ll1 1 1&lv1 1 1&de0020 0020 0020&wt00 00 00&mv00000 00000 00000&mc00 00 00&mp000 000 000&ms0010 0010 0010&mh0000 0000 0000&gi000 000 000&gj0gk0
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Received response: C0DSid0009er00/00

Discarding tips#

Finally, you can discard tips by using the discard_tips() method.

await lh.discard_tips(tiprack["A1:C1"])
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Sent command: C0TRid0010xp01629 01629 01629 00000&yp1458 1368 1278 0000&tm1 1 1 0&tt01tp1314tz1414th2450ti0
INFO:pylabrobot.liquid_handling.backends.hamilton.STAR:Received response: C0TRid0010er00/00kz381 356 365 000 000 000 000 000vz303 360 368 000 000 000 000 000
await lh.stop()
WARNING:root:Closing connection to USB device.