关于 pylabrobot
PyLabRobot 是用于液体处理机器人的开源 Python 库,面向编写和测试实验室自动化协议的研究人员。统一 API 和模拟后端支持在多种厂商设备间复用协议。
主要功能
- 01跨设备统一 API
- 02异步液体操作
- 03管理实验台与实验耗材
- 04模拟器可视化调试
- 05吸液、分液与转移
适用场景
- 01编写跨机器人通用协议
- 02连接实体设备前模拟测试
- 03按命中清单挑选并转移样本
- 04执行连续稀释与整板转移
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jaechang-hits/ sciagent-skills --skill pylabrobot安装后在 Claude Code 中输入/pylabrobot确认已加载。
详细介绍
pylabrobot
概述
PyLabRobot 是一个开源 Python 库,通过统一的 API 抽象液体处理机器人硬件。只需编写一次协议,即可在任何受支持的机器人上运行——Hamilton STAR、Tecan Freedom EVO、Opentrons OT-2 或模拟后端——无需修改协议代码。PyLabRobot 通过简洁的异步优先接口处理平台布局、资源管理以及吸液和分液操作。
适用场景
- 编写可移植的液体处理协议:你希望使用一个 Python 脚本,就能在 Hamilton、Tecan、Opentrons 和模拟器上运行,无需修改代码。
- 在使用机器人前开发和测试协议:使用模拟后端验证逻辑、液体体积和平台布局,而无需占用实体硬件。
- 自动化孔板重排和目标挑选:根据上游数据(例如筛选得到的命中化合物)在孔板间转移指定孔中的液体。
- 构建连续稀释曲线:在孔板中系统地执行吸液和分液,并精确控制体积梯度。
- 将液体处理集成到 Python 数据流水线中:从分析代码、LIMS 查询或机器学习模型触发机器人操作。
- 快速开发方法:使用 Python 快速迭代,而非依赖供应商专用的脚本环境。
- 对于 Opentrons 专属功能(温控模块控制、内置应用集成),请改用
opentronsPython SDK;如需支持多供应商的可移植性,请使用 PyLabRobot。
前置条件
- Python 包:
pylabrobot - 可选后端:用于 Hamilton STAR 的
pylabrobot[hamilton],用于 OT-2 的pylabrobot[opentrons] - 数据要求:无——平台资源在 Python 中定义
- 环境:Python 3.9+;实体机器人的驱动程序需根据各供应商的文档单独安装
pip install pylabrobot
pip install "pylabrobot[hamilton]" # add Hamilton USB driver
pip install "pylabrobot[opentrons]" # add Opentrons REST driver
快速入门
import asyncio
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import SimulatorBackend
from pylabrobot.resources import Deck, Cos_96_Rd, HTF_L
async def main():
backend = SimulatorBackend(open_browser=False)
lh = LiquidHandler(backend=backend, deck=Deck())
await lh.setup()
plate = Cos_96_Rd(name="plate")
tips = HTF_L(name="tips")
lh.deck.assign_child_resource(plate, rails=2)
lh.deck.assign_child_resource(tips, rails=5)
await lh.pick_up_tips(tips["A1"])
await lh.aspirate(plate["A1"], vols=50)
await lh.dispense(plate["B1"], vols=50)
await lh.drop_tips(tips["A1"])
await lh.stop()
print("Transfer complete: 50 uL from A1 -> B1")
asyncio.run(main())
核心 API
模块 1:LiquidHandler — 设置与拆卸
LiquidHandler 类是中央控制器。它封装了一个后端和一个 Deck。
import asyncio
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import SimulatorBackend
from pylabrobot.resources import Deck
async def main():
backend = SimulatorBackend(open_browser=False)
lh = LiquidHandler(backend=backend, deck=Deck())
await lh.setup() # connect to hardware / start simulator
print("LiquidHandler ready:", lh)
await lh.stop() # disconnect cleanly
asyncio.run(main())
# Connecting to a real Hamilton STAR
from pylabrobot.liquid_handling.backends.hamilton import STAR
async def main():
backend = STAR()
lh = LiquidHandler(backend=backend, deck=Deck())
await lh.setup()
# lh is now connected to physical hardware
await lh.stop()
模块 2:Deck 和资源分配
资源(孔板、吸头架、储液槽)按导轨位置放置在平台上。
from pylabrobot.resources import (
Deck,
Cos_96_Rd, # Corning 96-well round-bottom plate
Cos_384_Sq, # Corning 384-well plate
HTF_L, # Hamilton tip rack (filtered, large)
Trough_1_Row_1_Col_4, # 4-channel reservoir
)
deck = Deck()
plate_96 = Cos_96_Rd(name="sample_plate")
plate_384 = Cos_384_Sq(name="assay_plate")
tips = HTF_L(name="tip_rack")
reservoir = Trough_1_Row_1_Col_4(name="buffer")
deck.assign_child_resource(plate_96, rails=1)
deck.assign_child_resource(plate_384, rails=4)
deck.assign_child_resource(tips, rails=8)
deck.assign_child_resource(reservoir, rails=11)
print("Deck resources:", [r.name for r in deck.children])
模块 3:吸头操作
在液体操作前安装吸头,操作后丢弃吸头。
# Pick up tips from the first column of the tip rack
await lh.pick_up_tips(tips["A1:H1"]) # all 8 tips in column 1
# After liquid operations, drop tips back
await lh.drop_tips(tips["A1:H1"])
# Single tip
await lh.pick_up_tips(tips["A1"])
await lh.drop_tips(tips["A1"])
print("Tip operations complete")
模块 4:吸液操作
从孔中吸取液体。支持单个孔、范围或列表。
# Aspirate 100 uL from a single well
await lh.aspirate(plate["A1"], vols=100)
# Aspirate different volumes from multiple wells simultaneously
await lh.aspirate(
plate["A1:A4"],
vols=[50, 75, 100, 125],
)
print("Aspiration complete")
from pylabrobot.resources import Coordinate
# Aspirate with flow rate and liquid height control
await lh.aspirate(
plate["A1"],
vols=50,
flow_rates=100, # uL/s
offsets=Coordinate(0, 0, 1), # 1 mm above well bottom
)
模块 5:分液操作
将液体分配到目标孔中。
# Dispense 100 uL into a single well
await lh.dispense(plate["B1"], vols=100)
# Multi-well dispense with different volumes
await lh.dispense(
plate["B1:B4"],
vols=[50, 75, 100, 125],
)
print("Dispense complete")
模块 6:Transfer — 高级便捷操作
transfer 将吸液和分液操作合并,用于简单的源到目标转移。
# Transfer 50 uL from A1 -> B1
await lh.transfer(plate["A1"], plate["B1"], transfer_volume=50)
# Multi-well pairwise transfer
sources = plate["A1:A8"]
destinations = plate["B1:B8"]
await lh.transfer(sources, destinations, transfer_volume=75)
print("Transfer complete")
模块 7:模拟后端
SimulatorBackend 会运行基于浏览器的可视化工具,用于调试协议。
from pylabrobot.liquid_handling.backends import SimulatorBackend
# With visual browser (default — opens http://localhost:2121)
backend = SimulatorBackend(open_browser=True)
# Headless simulation (CI/testing)
backend = SimulatorBackend(open_browser=False)
# After setup(), liquid movements are visualized in real time
await lh.setup()
# Check browser for visual confirmation before running on real hardware
print("Simulator running at http://localhost:2121")
核心概念
异步优先设计
所有机器人操作(setup、aspirate、dispense、transfer)都是 Python async 协程。在 async def 函数中使用 asyncio.run() 运行,或在 Jupyter 中使用顶层 await 语法。
import asyncio
async def run_protocol(lh, plate, tips):
await lh.pick_up_tips(tips["A1"])
await lh.aspirate(plate["A1"], vols=50)
await lh.dispense(plate["B1"], vols=50)
await lh.drop_tips(tips["A1"])
print("Protocol complete")
asyncio.run(run_protocol(lh, plate, tips))
孔位寻址
孔位可通过字母数字位置("A1")或切片表示法寻址(列使用 "A1:H1",行使用 "A1:A12")。
well = plate["A1"] # single well
col1 = plate["A1:H1"] # 8 wells in column 1
row_a = plate["A1:A12"] # 12 wells in row A
print(f"Single: {well.name}")
print(f"Column: {len(col1)} wells")
print(f"Row: {len(row_a)} wells")
常见工作流
工作流 1:96 孔板连续稀释
目标:在 96 孔板中进行 2 倍连续稀释。
import asyncio
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import SimulatorBackend
from pylabrobot.resources import Deck, Cos_96_Rd, HTF_L, Trough_1_Row_1_Col_4
async def serial_dilution():
backend = SimulatorBackend(open_browser=False)
lh = LiquidHandler(backend=backend, deck=Deck())
await lh.setup()
plate = Cos_96_Rd(name="plate")
tips = HTF_L(name="tips")
diluent = Trough_1_Row_1_Col_4(name="diluent")
lh.deck.assign_child_resource(plate, rails=1)
lh.deck.assign_child_resource(tips, rails=5)
lh.deck.assign_child_resource(diluent, rails=9)
# Add 100 uL diluent to columns 2-12
for col in range(2, 13):
col_label = f"A{col}:H{col}"
await lh.pick_up_tips(tips[f"A{col}:H{col}"])
await lh.aspirate(diluent["A1:H1"], vols=100)
await lh.dispense(plate[col_label], vols=100)
await lh.drop_tips(tips[f"A{col}:H{col}"])
# Serial transfer: col 1 -> 2 -> ... -> 11
for col in range(1, 12):
src = f"A{col}:H{col}"
dst = f"A{col+1}:H{col+1}"
await lh.pick_up_tips(tips[f"A{col}:H{col}"])
await lh.aspirate(plate[src], vols=100)
await lh.dispense(plate[dst], vols=100)
await lh.drop_tips(tips[f"A{col}:H{col}"])
print("Serial dilution complete: 12 columns, 2-fold steps")
await lh.stop()
asyncio.run(serial_dilution())
工作流 2:从命中列表中挑选目标
目标:根据 CSV 命中列表,将化合物从指定的源孔转移到目标孔板。
import asyncio
import pandas as pd
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import SimulatorBackend
from pylabrobot.resources import Deck, Cos_96_Rd, HTF_L
async def cherry_pick(hit_list_csv: str, volume: float = 50.0):
# CSV must have columns: source_well, dest_well
hits = pd.read_csv(hit_list_csv)
print(f"Cherry-picking {len(hits)} hits at {volume} uL each")
backend = SimulatorBackend(open_browser=False)
lh = LiquidHandler(backend=backend, deck=Deck())
await lh.setup()
src = Cos_96_Rd(name="source")
dst = Cos_96_Rd(name="destination")
tips = HTF_L(name="tips")
lh.deck.assign_child_resource(src, rails=1)
lh.deck.assign_child_resource(dst, rails=4)
lh.deck.assign_child_resource(tips, rails=8)
# Get all well names from tip rack
tip_wells = [w.name for w in tips.wells]
for i, row in hits.iterrows():
await lh.pick_up_tips(tips[tip_wells[i]])
await lh.transfer(src[row["source_well"]], dst[row["dest_well"]],
transfer_volume=volume)
await lh.drop_tips(tips[tip_wells[i]])
print(f"Cherry-pick complete: {len(hits)} transfers done")
await lh.stop()
# asyncio.run(cherry_pick("hits.csv", volume=50))
关键参数
| 参数 | 模块 | 默认值 | 范围 / 选项 | 作用 |
|---|---|---|---|---|
vols | aspirate / dispense | 必填 | 0 – 机器人最大值(µL) | 每个孔吸取或分液的体积 |
flow_rates | aspirate / dispense | 后端默认值 | 10 – 1000 µL/s | 液体移动速度 |
blow_out_air_volume | dispense | 0 | 0 – 30 µL | 分液后吹出的空气体积,用于排空吸头 |
offsets | aspirate / dispense | Coordinate(0,0,0) | 任意 Coordinate | 相对于孔中心的位置偏移(x、y、z,单位:mm) |
open_browser | SimulatorBackend | True | True、False | 设置时是否打开基于浏览器的可视化模拟器 |
rails | deck assignment | 必填 | 1 – 最大 deck 导轨数 | 资源在 deck 上的物理槽位 |
transfer_volume | transfer | 必填 | 0 – 机器人最大值(µL) | 高级别吸取 + 分液转移的体积 |
最佳实践
-
始终先使用模拟器测试:连接物理硬件前,先使用
SimulatorBackend(open_browser=True)运行完整协议。浏览器可视化界面会实时显示 deck 布局和液体移动。 -
需要避免污染时,每次转移都使用新吸头:在挑取工作流中重复使用吸头有交叉污染的风险。通过程序跟踪吸头消耗量,并与吸头架容量进行核对。
-
使用 try/finally 包装协议以进行清理:如果协议执行期间发生异常,务必调用
await lh.stop()以释放硬件连接。try: await run_my_protocol(lh) finally: await lh.stop() -
在脚本开头定义一次资源:创建资源对象,并一次性将其分配到 deck。在运行过程中重新分配同一资源可能会导致机器人内部状态跟踪不同步。
-
预先计算体积和吸头需求:对于高通量运行,在开始前计算所需的总体积和吸头数量,并断言资源充足。
常见配方
配方:分液后混匀
适用场景:向需要均匀混合的细胞培养孔中添加试剂。
async def dispense_and_mix(lh, src, dst, tips, volume=50, mix_vol=40, mix_reps=3):
await lh.pick_up_tips(tips["A1"])
await lh.aspirate(src["A1"], vols=volume)
await lh.dispense(dst["A1"], vols=volume)
for _ in range(mix_reps):
await lh.aspirate(dst["A1"], vols=mix_vol)
await lh.dispense(dst["A1"], vols=mix_vol)
await lh.drop_tips(tips["A1"])
print(f"Dispensed {volume} uL and mixed {mix_reps}x")
配方:整板转印
适用场景:将整块 96 孔板复制到第二块板上。
async def stamp_plate(lh, src_plate, dst_plate, tips, volume=100):
for col in range(1, 13):
col_label = f"A{col}:H{col}"
await lh.pick_up_tips(tips[col_label])
await lh.aspirate(src_plate[col_label], vols=volume)
await lh.dispense(dst_plate[col_label], vols=volume)
await lh.drop_tips(tips[col_label])
print(f"Full plate stamped: {volume} uL per well, 12 columns")
预期输出
- 机器人按脚本执行液体处理操作(物理操作或模拟操作)
http://localhost:2121上的模拟器显示带有各孔体积跟踪的动态 deck- 默认不生成文件输出;可根据需要在封装代码中集成
pandas/ CSV 日志记录
故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
RuntimeError: No backend connected | 操作前未等待 lh.setup() 完成 | 确保在任何液体处理调用前,await lh.setup() 已完成 |
ResourceNotFoundError | 资源名称未分配到 deck | 在引用孔之前,调用 deck.assign_child_resource(resource, rails=N) |
asyncio.InvalidStateError | 在异步上下文之外调用协程 | 将顶层调用封装在 async def main() 中,并使用 asyncio.run(main()) |
孔地址 KeyError | 孔标签格式不正确 | 使用大写字母 + 整数:"A1"、"H12",不要使用 "a1" 或 "A01" |
VolumeError:超过吸头容量 | 请求体积大于吸头最大容量 | 使用合适的吸头类型;HTF_L 最多可容纳 1000 µL |
| 模拟器未显示移动 | open_browser=False 且未打开查看器 | 设置 open_browser=True,或手动打开 http://localhost:2121 |
ImportError: pylabrobot.hamilton | 未安装后端扩展 | pip install "pylabrobot[hamilton]" |
参考资料
- PyLabRobot 文档 — 涵盖所有后端和资源的官方文档
- PyLabRobot GitHub — 源代码、示例和问题跟踪
- Azeloglu & Bhatt (2023),Cell Device — PyLabRobot 论文 — 原始论文
- PyPI:pylabrobot — 安装说明和版本历史