Overview
PyLabRobot (PLR) is a hardware-agnostic Python SDK for lab automation. Every device is a front end (LiquidHandler, PlateReader, HeaterShaker, ...) plus a backend for a specific instrument, so the same protocol code runs on a simulator, a Hamilton STAR, or an Opentrons OT-2 by swapping one backend line. All device calls are async and must be awaited inside an event loop (asyncio.run(main()), or directly in a Jupyter cell).
When to Use This Skill
- Writing or debugging a liquid-handling protocol in Python for Hamilton STAR/STARlet/Vantage, Tecan EVO, or Opentrons OT-2
- Simulating a protocol (chatterbox backend, tip/volume tracking, browser visualizer) before touching hardware
- Defining deck layouts: carriers, tip racks, plates, troughs, tubes, and custom labware
- Integrating plate readers, heater shakers, incubators, centrifuges, pumps, scales, or thermocyclers into one workflow
- Porting a protocol from one robot vendor to another
Does NOT Trigger
| Scenario | Use Instead |
|---|---|
Writing an Opentrons OT-2/Flex protocol with the official Opentrons Python API (apiLevel, opentrons_simulate) |
alterlab-opentrons |
| Sending work to a remote cloud lab instead of running your own robot | alterlab-ginkgo-cloud |
| Ordering protein binding/expression assays from the Adaptyv Bio foundry | alterlab-adaptyv |
| Finding, writing, or publishing a human-readable protocol with a DOI | alterlab-protocolsio |
Core Capabilities
| Area | What it covers | Reference |
|---|---|---|
| Liquid handling | aspirate, dispense, transfer, tips, multichannel moves, serial dilutions, error handling |
references/liquid-handling.md |
| Resources | Decks, carriers, plates, tip racks, troughs, indexing, tracking, saving layouts, custom labware | references/resources.md |
| Hardware backends | STAR, Vantage, EVO, OT-2, chatterbox; switching backends | references/hardware-backends.md |
| Analytical equipment | Plate readers (CLARIOstar, BioTek, SpectraMax, Byonoy), scales | references/analytical-equipment.md |
| Material handling | Heater shakers, temperature controllers, incubators, centrifuges, pumps, thermocyclers | references/material-handling.md |
| Visualization | Browser visualizer, simulation-driven testing | references/visualization.md |
Quick Start (simulation)
import asyncio
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import LiquidHandlerChatterboxBackend # STARBackend() on a real STAR
from pylabrobot.resources import (
PLT_CAR_L5AC_A00, # plate carrier (5 sites)
STARLetDeck,
TIP_CAR_480_A00, # tip carrier (5 sites)
cor_96_wellplate_360uL_Fb,
hamilton_96_tiprack_1000uL_filter,
set_tip_tracking,
set_volume_tracking,
)
async def main():
set_tip_tracking(True) # catch missing tips and over-aspiration in simulation
set_volume_tracking(True)
lh = LiquidHandler(backend=LiquidHandlerChatterboxBackend(), deck=STARLetDeck())
await lh.setup()
try:
# Labware goes into carrier sites; carriers go on deck rails.
tip_car = TIP_CAR_480_A00(name="tip_carrier")
tip_car[0] = tip_rack = hamilton_96_tiprack_1000uL_filter(name="tips_01")
lh.deck.assign_child_resource(tip_car, rails=3)
plt_car = PLT_CAR_L5AC_A00(name="plate_carrier")
plt_car[0] = source = cor_96_wellplate_360uL_Fb(name="source")
plt_car[1] = dest = cor_96_wellplate_360uL_Fb(name="dest")
lh.deck.assign_child_resource(plt_car, rails=15)
for well in source.get_all_items():
well.tracker.set_liquids([(None, 300)])
# 8-channel column copy: "A1:H1" is column 1 (8 wells), one tip per channel
await lh.pick_up_tips(tip_rack["A1:H1"])
await lh.aspirate(source["A1:H1"], vols=[100] * 8)
await lh.dispense(dest["A1:H1"], vols=[100] * 8)
await lh.discard_tips() # to the deck's trash; return_tips() puts them back
print(dest.get_well("A1").tracker.get_used_volume()) # 100.0
finally:
await lh.stop()
asyncio.run(main())
To run on hardware, replace the backend (STARBackend(), VantageBackend(), EVOBackend(), OpentronsOT2Backend(host="<robot IP>")) and the deck (STARDeck(), VantageDeck(size=1.3), EVO150Deck(), OTDeck()); the protocol body stays the same apart from deck positions.
API Rules That Prevent Most Errors
- Indexing returns lists.
plate["A1"]is a one-element list;plate["A1:H1"]is column 1 andplate["A1:A12"]is row A. Useplate.get_well("A1")ortip_rack.get_item("A1")for a single object (for example to reach.tracker). - Volumes are lists.
vols=[100]for one channel,vols=[100] * 8for eight; per-channel options (flow_rates,liquid_height,blow_out_air_volume) are lists too. - Tips.
pick_up_tips(spots), thendiscard_tips()(trash),return_tips()(back to the rack), ordrop_tips(spots)(explicit spots — it has no default). transferis one-to-many.lh.transfer(source.get_well("A1"), dest["A1:H1"], target_vols=[50] * 8)aspirates once and dispenses into each target with the channel-0 tip.source_vol=is the total volume, split across targets (equally or byratios), not a per-target volume.- Labware names. Use the current lower-case factories (
cor_96_wellplate_360uL_Fb); capitalised legacy names such asCor_96_wellplate_360ul_Fbwarn and will be removed. - Always
stop()infinally:so USB/serial connections are released after an error.
Common Workflows
One-to-many dispense and a serial dilution
# 50 uL of diluent into each of A2..A5 from one source well (single channel)
await lh.pick_up_tips(tip_rack["A2"])
await lh.transfer(source.get_well("H12"), dest["A2:A5"], target_vols=[50] * 4)
await lh.discard_tips()
# 2-fold dilution along row A (A1 -> A5): move 50 uL to the next well and mix
await lh.pick_up_tips(tip_rack["A3"])
for col in range(1, 5):
await lh.aspirate(dest[f"A{col}"], vols=[50])
await lh.dispense(dest[f"A{col + 1}"], vols=[50])
for _ in range(3): # mix
await lh.aspirate(dest[f"A{col + 1}"], vols=[40])
await lh.dispense(dest[f"A{col + 1}"], vols=[40])
await lh.discard_tips()
Reading a plate
from pylabrobot.plate_reading import CLARIOstarBackend, PlateReader
reader = PlateReader(name="clariostar", size_x=0, size_y=0, size_z=0,
backend=CLARIOstarBackend()) # needs pylabrobot[ftdi]
await reader.setup()
await reader.open()
reader.assign_child_resource(dest) # or move the plate there with the robot's gripper
await reader.close()
result = await reader.read_absorbance(wavelength=450, use_new_return_type=True)
od450 = result[0]["data"] # 8 x 12 nested list
await reader.stop()
PlateReader has no temperature method; readers that support heating expose it on the backend (for example await reader.backend.set_temperature(37) on BioTek and Molecular Devices backends). See references/analytical-equipment.md for other readers and for scales.
Best Practices
- Simulate first. Run the whole protocol on
LiquidHandlerChatterboxBackendwith tip and volume tracking on, and watch it in theVisualizer, before the first hardware run. - Dry-run on hardware. Do a first physical run with water or without liquid, at reduced volumes, and keep a hand near the stop button.
- Keep layouts in files. Save the deck with
lh.deck.save("deck.json")and commit it with the protocol; record the pylabrobot version. - Check labware definitions. Verify geometry for custom or rarely used labware before trusting aspiration heights.
- Start slow devices early. Heating and incubator set points take minutes; do liquid handling while they settle.
- Official docs. https://docs.pylabrobot.org (user guide and API), https://github.com/PyLabRobot/pylabrobot, forum https://discuss.pylabrobot.org.
Troubleshooting
| Symptom | Likely cause |
|---|---|
TypeError: Resources must be Containers, got [[Well(...)]] |
A list was passed where one well is expected (transfer source); use plate.get_well("A1") |
TypeError: drop_tips() missing ... 'tip_spots' |
Use discard_tips() or pass explicit spots |
NoTipError / HasTipError with tracking on |
The protocol reuses a tip spot or picks up while holding tips; check tip bookkeeping |
RuntimeError: ... is not installed. Install with: pip install pylabrobot[...] |
Install the connection extra named in the message |
ImportError for pylabrobot.temperature_control, pylabrobot.incubation, Trough_100ml |
Outdated names; see references/material-handling.md and references/resources.md for the 0.2.2 modules and labware |
Part of the AlterLab Academic Skills suite.