Files
2025-11-30 08:30:10 +08:00

10 KiB

Liquid Handling with PyLabRobot

Overview

The liquid handling module (pylabrobot.liquid_handling) provides a unified interface for controlling liquid handling robots. The LiquidHandler class serves as the main interface for all pipetting operations, working across different hardware platforms through backend abstraction.

Basic Setup

Initializing a Liquid Handler

from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import STAR
from pylabrobot.resources import STARLetDeck

# Create liquid handler with STAR backend
lh = LiquidHandler(backend=STAR(), deck=STARLetDeck())
await lh.setup()

# When done
await lh.stop()

Switching Between Backends

Change robots by swapping the backend without rewriting protocols:

# Hamilton STAR
from pylabrobot.liquid_handling.backends import STAR
lh = LiquidHandler(backend=STAR(), deck=STARLetDeck())

# Opentrons OT-2
from pylabrobot.liquid_handling.backends import OpentronsBackend
lh = LiquidHandler(backend=OpentronsBackend(host="192.168.1.100"), deck=OTDeck())

# Simulation (no hardware required)
from pylabrobot.liquid_handling.backends.simulation import ChatterboxBackend
lh = LiquidHandler(backend=ChatterboxBackend(), deck=STARLetDeck())

Core Operations

Tip Management

Picking up and dropping tips is fundamental to liquid handling operations:

# Pick up tips from specific positions
await lh.pick_up_tips(tip_rack["A1"])           # Single tip
await lh.pick_up_tips(tip_rack["A1:H1"])        # Row of 8 tips
await lh.pick_up_tips(tip_rack["A1:A12"])       # Column of 12 tips

# Drop tips
await lh.drop_tips()                             # Drop at current location
await lh.drop_tips(waste)                        # Drop at specific location

# Return tips to original rack
await lh.return_tips()

Tip Tracking: Enable automatic tip tracking to monitor tip usage:

from pylabrobot.resources import set_tip_tracking
set_tip_tracking(True)  # Enable globally

Aspirating Liquids

Draw liquid from wells or containers:

# Basic aspiration
await lh.aspirate(plate["A1"], vols=100)         # 100 µL from A1

# Multiple wells with same volume
await lh.aspirate(plate["A1:H1"], vols=100)      # 100 µL from each well

# Multiple wells with different volumes
await lh.aspirate(
    plate["A1:A3"],
    vols=[100, 150, 200]                          # Different volumes
)

# Advanced parameters
await lh.aspirate(
    plate["A1"],
    vols=100,
    flow_rate=50,                                 # µL/s
    liquid_height=5,                              # mm from bottom
    blow_out_air_volume=10                        # µL air
)

Dispensing Liquids

Dispense liquid into wells or containers:

# Basic dispensing
await lh.dispense(plate["A2"], vols=100)         # 100 µL to A2

# Multiple wells
await lh.dispense(plate["A1:H1"], vols=100)      # 100 µL to each

# Different volumes
await lh.dispense(
    plate["A1:A3"],
    vols=[100, 150, 200]
)

# Advanced parameters
await lh.dispense(
    plate["A2"],
    vols=100,
    flow_rate=50,                                 # µL/s
    liquid_height=2,                              # mm from bottom
    blow_out_air_volume=10                        # µL air
)

Transferring Liquids

Transfer combines aspirate and dispense in a single operation:

# Basic transfer
await lh.transfer(
    source=source_plate["A1"],
    dest=dest_plate["A1"],
    vols=100
)

# Multiple transfers (same tips)
await lh.transfer(
    source=source_plate["A1:H1"],
    dest=dest_plate["A1:H1"],
    vols=100
)

# Different volumes per well
await lh.transfer(
    source=source_plate["A1:A3"],
    dest=dest_plate["B1:B3"],
    vols=[50, 100, 150]
)

# With tip handling
await lh.pick_up_tips(tip_rack["A1:H1"])
await lh.transfer(
    source=source_plate["A1:H12"],
    dest=dest_plate["A1:H12"],
    vols=100
)
await lh.drop_tips()

Advanced Techniques

Serial Dilutions

Create serial dilutions across plate rows or columns:

# 2-fold serial dilution
source_vols = [100, 50, 50, 50, 50, 50, 50, 50]
dest_vols = [0, 50, 50, 50, 50, 50, 50, 50]

# Add diluent first
await lh.pick_up_tips(tip_rack["A1"])
await lh.transfer(
    source=buffer["A1"],
    dest=plate["A2:A8"],
    vols=50
)
await lh.drop_tips()

# Perform serial dilution
await lh.pick_up_tips(tip_rack["A2"])
for i in range(7):
    await lh.aspirate(plate[f"A{i+1}"], vols=50)
    await lh.dispense(plate[f"A{i+2}"], vols=50)
    # Mix
    await lh.aspirate(plate[f"A{i+2}"], vols=50)
    await lh.dispense(plate[f"A{i+2}"], vols=50)
await lh.drop_tips()

Plate Replication

Copy an entire plate layout to another plate:

# Setup tips
await lh.pick_up_tips(tip_rack["A1:H1"])

# Replicate 96-well plate (12 columns)
for col in range(1, 13):
    await lh.transfer(
        source=source_plate[f"A{col}:H{col}"],
        dest=dest_plate[f"A{col}:H{col}"],
        vols=100
    )

await lh.drop_tips()

Multi-Channel Pipetting

Use multiple channels simultaneously for parallel operations:

# 8-channel transfer (entire row)
await lh.pick_up_tips(tip_rack["A1:H1"])
await lh.transfer(
    source=source_plate["A1:H1"],
    dest=dest_plate["A1:H1"],
    vols=100
)
await lh.drop_tips()

# Process entire plate with 8-channel
for col in range(1, 13):
    await lh.pick_up_tips(tip_rack[f"A{col}:H{col}"])
    await lh.transfer(
        source=source_plate[f"A{col}:H{col}"],
        dest=dest_plate[f"A{col}:H{col}"],
        vols=100
    )
    await lh.drop_tips()

Mixing Liquids

Mix liquids by repeatedly aspirating and dispensing:

# Mix by aspiration/dispensing
await lh.pick_up_tips(tip_rack["A1"])

# Mix 5 times
for _ in range(5):
    await lh.aspirate(plate["A1"], vols=80)
    await lh.dispense(plate["A1"], vols=80)

await lh.drop_tips()

Volume Tracking

Track liquid volumes in wells automatically:

from pylabrobot.resources import set_volume_tracking

# Enable volume tracking globally
set_volume_tracking(True)

# Set initial volumes
plate["A1"].tracker.set_liquids([(None, 200)])  # 200 µL

# After aspirating 100 µL
await lh.aspirate(plate["A1"], vols=100)
print(plate["A1"].tracker.get_volume())  # 100 µL

# Check remaining volume
remaining = plate["A1"].tracker.get_volume()

Liquid Classes

Define liquid properties for optimal pipetting:

# Liquid classes control aspiration/dispense parameters
from pylabrobot.liquid_handling import LiquidClass

# Create custom liquid class
water = LiquidClass(
    name="Water",
    aspiration_flow_rate=100,
    dispense_flow_rate=150,
    aspiration_mix_flow_rate=100,
    dispense_mix_flow_rate=100,
    air_transport_retract_dist=10
)

# Use with operations
await lh.aspirate(
    plate["A1"],
    vols=100,
    liquid_class=water
)

Error Handling

Handle errors in liquid handling operations:

try:
    await lh.setup()
    await lh.pick_up_tips(tip_rack["A1"])
    await lh.transfer(source["A1"], dest["A1"], vols=100)
    await lh.drop_tips()
except Exception as e:
    print(f"Error during liquid handling: {e}")
    # Attempt to drop tips if holding them
    try:
        await lh.drop_tips()
    except:
        pass
finally:
    await lh.stop()

Best Practices

  1. Always Setup and Stop: Call await lh.setup() before operations and await lh.stop() when done
  2. Enable Tracking: Use tip tracking and volume tracking for accurate state management
  3. Tip Management: Always pick up tips before aspirating and drop them when done
  4. Flow Rates: Adjust flow rates based on liquid viscosity and vessel type
  5. Liquid Height: Set appropriate aspiration/dispense heights to avoid splashing
  6. Error Handling: Use try/finally blocks to ensure proper cleanup
  7. Test in Simulation: Use ChatterboxBackend to test protocols before running on hardware
  8. Volume Limits: Respect tip volume limits and well capacities
  9. Mixing: Mix after dispensing viscous liquids or when accuracy is critical
  10. Documentation: Document liquid classes and custom parameters for reproducibility

Common Patterns

Complete Liquid Handling Protocol

from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import STAR
from pylabrobot.resources import STARLetDeck, TIP_CAR_480_A00, Cos_96_DW_1mL
from pylabrobot.resources import set_tip_tracking, set_volume_tracking

# Enable tracking
set_tip_tracking(True)
set_volume_tracking(True)

# Initialize
lh = LiquidHandler(backend=STAR(), deck=STARLetDeck())
await lh.setup()

try:
    # Define resources
    tip_rack = TIP_CAR_480_A00(name="tips")
    source = Cos_96_DW_1mL(name="source")
    dest = Cos_96_DW_1mL(name="dest")

    # Assign to deck
    lh.deck.assign_child_resource(tip_rack, rails=1)
    lh.deck.assign_child_resource(source, rails=10)
    lh.deck.assign_child_resource(dest, rails=15)

    # Set initial volumes
    for well in source.children:
        well.tracker.set_liquids([(None, 200)])

    # Execute protocol
    await lh.pick_up_tips(tip_rack["A1:H1"])
    await lh.transfer(
        source=source["A1:H12"],
        dest=dest["A1:H12"],
        vols=100
    )
    await lh.drop_tips()

finally:
    await lh.stop()

Hardware-Specific Notes

Hamilton STAR

  • Supports full liquid handling capabilities
  • Uses USB connection for communication
  • Firmware commands executed directly
  • Supports CO-RE (Compressed O-Ring Expansion) tips

Opentrons OT-2

  • Requires IP address for network connection
  • Uses HTTP API for communication
  • Limited to 8-channel and single-channel pipettes
  • Simpler deck layout compared to STAR

Tecan EVO

  • Work-in-progress support
  • Similar capabilities to Hamilton STAR
  • Check current compatibility status in documentation

Additional Resources