mcpField
P
pylabrobot

pylabrobot

github.com/jaechang-hitsその他38468 回インストール1 週間前に更新
リポジトリ

pylabrobot について

PyLabRobotは、ラボの液体処理プロトコルを作成・検証する研究者向けのオープンソースPythonライブラリです。統一APIとシミュレーターにより、複数メーカーの機器でプロトコルを再利用できます。

主な機能

  1. 01機器間で共通のAPI
  2. 02非同期の液体操作
  3. 03デッキとリソースの管理
  4. 04シミュレーターによる可視化デバッグ
  5. 05吸引・分注・移送

活用シーン

  1. 01複数ロボット向けプロトコルの作成
  2. 02実機での実行前のシミュレーション
  3. 03ヒットリストに基づくサンプル選別移送
  4. 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 固有の機能(温度モジュールの制御、組み込みアプリとの統合)には、代わりに opentrons Python 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))

主要なパラメーター

パラメーターモジュールデフォルト範囲/オプション効果
volsaspirate / dispense必須0 – ロボットの最大値 (µL)ウェルごとに吸引または分注する量
flow_ratesaspirate / dispenseバックエンドのデフォルト10 – 1000 µL/s液体を移動させる速度
blow_out_air_volumedispense00 – 30 µL分注後にチップを空にするために吹き出す空気の量
offsetsaspirate / dispenseCoordinate(0,0,0)任意の Coordinateウェル中心からの位置オフセット (x、y、z mm)
open_browserSimulatorBackendTrueTrue、Falseセットアップ時にブラウザーベースのビジュアルシミュレーターを開くかどうか
railsデッキへの割り当て必須1 – デッキレールの最大数リソースを配置するデッキ上の物理スロット
transfer_volumetransfer必須0 – ロボットの最大値 (µL)高レベルの吸引+分注による移送の量

ベストプラクティス

  1. 必ず最初にシミュレーターでテストしてください:物理ハードウェアに接続する前に、SimulatorBackend(open_browser=True) を使ってプロトコル全体を実行してください。ブラウザーのビジュアライザーには、デッキのレイアウトと液体の動きがリアルタイムで表示されます。

  2. コンタミネーションが問題になる場合は、移送ごとに新しいチップを使用してください:チェリーピッキングのワークフローでチップを再利用すると、交差汚染のリスクがあります。プログラムでチップの消費量をチップラックの容量と照らし合わせて管理してください。

  3. クリーンアップにはプロトコルを try/finally で囲んでください:プロトコルの途中で例外が発生した場合は、ハードウェア接続を解放するために必ず await lh.stop() を呼び出してください。

    try:
        await run_my_protocol(lh)
    finally:
        await lh.stop()
    
  4. スクリプトの先頭でリソースを一度だけ定義してください:リソースオブジェクトを作成し、一度だけデッキに割り当てます。実行中に同じリソースを再割り当てすると、ロボットの内部状態の追跡と同期が取れなくなる場合があります。

  5. 量とチップの必要数を事前に計算してください:ハイスループット実行では、開始前に必要な総量とチップ数を計算し、リソースが十分であることをアサートしてください。

よくあるレシピ

レシピ:分注後に混和する

使用場面:均一な混和が必要な細胞培養ウェルへの試薬添加。

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]"

参考資料

コメント