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 は、液体ハンドリングロボットのハードウェアを統一された API で操作できるようにする、オープンソースの Python ライブラリです。プロトコルを一度記述すれば、プロトコルコードを変更せずに、対応するロボット(Hamilton STAR、Tecan Freedom EVO、Opentrons OT-2、シミュレーションバックエンド)で実行できます。PyLabRobot は、すっきりとした非同期優先のインターフェースを通じて、デッキレイアウト、リソース管理、吸引・吐出操作を処理します。
使用する場面
- 移植可能な液体ハンドリングプロトコルの作成:コードを変更せずに Hamilton、Tecan、Opentrons、シミュレーターで動作する単一の Python スクリプトが必要な場合。
- ロボットを使う前のプロトコル開発とテスト:シミュレーションバックエンドを使用して、実機を占有せずにロジック、容量、デッキレイアウトを検証する場合。
- プレートの再フォーマットとチェリーピッキングの自動化:上流のデータ(例:スクリーニングで得られたヒット化合物)に基づいて、プレート間で特定のウェルを移送する場合。
- 段階希釈曲線の作成:正確な容量刻みでプレート全体にわたり系統的に吸引・吐出する場合。
- 液体ハンドリングと 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 コルーチンです。asyncio.run() を使用するか、Jupyter のトップレベル await 構文を使って、async def 関数内で実行します。
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 | デッキへの割り当て | 必須 | 1 – デッキレールの最大数 | リソースを配置するデッキ上の物理スロット |
transfer_volume | transfer | 必須 | 0 – ロボットの最大値 (µL) | 高レベルの吸引+分注による移送の量 |
ベストプラクティス
-
必ず最初にシミュレーターでテストしてください:物理ハードウェアに接続する前に、
SimulatorBackend(open_browser=True)を使ってプロトコル全体を実行してください。ブラウザーのビジュアライザーには、デッキのレイアウトと液体の動きがリアルタイムで表示されます。 -
コンタミネーションが問題になる場合は、移送ごとに新しいチップを使用してください:チェリーピッキングのワークフローでチップを再利用すると、交差汚染のリスクがあります。プログラムでチップの消費量をチップラックの容量と照らし合わせて管理してください。
-
クリーンアップにはプロトコルを try/finally で囲んでください:プロトコルの途中で例外が発生した場合は、ハードウェア接続を解放するために必ず
await lh.stop()を呼び出してください。try: await run_my_protocol(lh) finally: await lh.stop() -
スクリプトの先頭でリソースを一度だけ定義してください:リソースオブジェクトを作成し、一度だけデッキに割り当てます。実行中に同じリソースを再割り当てすると、ロボットの内部状態の追跡と同期が取れなくなる場合があります。
-
量とチップの必要数を事前に計算してください:ハイスループット実行では、開始前に必要な総量とチップ数を計算し、リソースが十分であることをアサートしてください。
よくあるレシピ
レシピ:分注後に混和する
使用場面:均一な混和が必要な細胞培養ウェルへの試薬添加。
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 ウェルプレート全体を 2 枚目のプレートに複製する場合。
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のシミュレーターに、ウェルごとの液量を追跡できるアニメーション付きデッキが表示されます。- デフォルトではファイルは出力されません。必要に応じて、ラッパーコードで
pandas/ CSV ロギングを統合してください。
トラブルシューティング
| 問題 | 原因 | 解決策 |
|---|---|---|
RuntimeError: No backend connected | 操作前に lh.setup() が await されていない | 液体処理の呼び出し前に await lh.setup() が完了するようにしてください |
ResourceNotFoundError | リソース名がデッキに割り当てられていない | ウェルを参照する前に deck.assign_child_resource(resource, rails=N) を呼び出してください |
asyncio.InvalidStateError | コルーチンが非同期コンテキスト外で呼び出されている | トップレベルの呼び出しを async def main() で囲み、asyncio.run(main()) を使用してください |
ウェルアドレスの KeyError | ウェルラベルの形式が正しくない | 小文字の "a1" や "A01" ではなく、大文字のアルファベット+整数を使用してください(例:"A1"、"H12") |
VolumeError: チップ容量を超過 | 要求量がチップの最大容量を超えている | 適切なチップタイプを使用してください。HTF_L は最大 1000 µL に対応します |
| シミュレーターで動きが表示されない | open_browser=False でビューアーがない | open_browser=True に設定するか、http://localhost:2121 を手動で開いてください |
ImportError: pylabrobot.hamilton | バックエンドの追加機能がインストールされていない | pip install "pylabrobot[hamilton]" |
参考資料
- PyLabRobot Documentation — すべてのバックエンドとリソースを網羅した公式ドキュメント
- PyLabRobot GitHub — ソースコード、サンプル、課題トラッカー
- Azeloglu & Bhatt (2023), Cell Device — PyLabRobot paper — 原著論文
- PyPI: pylabrobot — インストール方法とバージョン履歴