Skip to main content

Python SDK for compiling protocols for the MGI PrepALL workstation.

Project description

mgi_prepall Protocol API

mgi_prepall is a Python SDK that compiles a lab procedure (liquid handling, module control, gripper moves) into a ProtocolDefinition JSON for the PrepALL workstation.

A minimal protocol script:

from mgi_prepall import Context

metadata = {
    "protocolName": "hello",
    "author": "me",
    "description": "",
}

def run(ctx: Context) -> None:
    ctx.init(
        lane1="plateRack", lane2="plateRack",
        lane3="plateRack", lane4="trashAndPCR",
        useTrashBin=True, trashBinType="B",
    )
    pipette = ctx.load_pipette("p1000_flex8")
    tips = ctx.load_labware("mgi_prepall_plate_carrier_low_adapter", "1-1") \
              .load_labware("mgi_96_filtertiprack_200ul", code="T1")
    src  = ctx.load_labware("mgi_prepall_plate_carrier_low_adapter", "1-2") \
              .load_labware("mgi_96_well_plate", code="P1")
    dst  = ctx.load_labware("mgi_prepall_plate_carrier_low_adapter", "1-3") \
              .load_labware("mgi_96_well_plate", code="P2")

    pipette.transfer(50.0, (src, "A1"), (dst, "A1"), tiprack=tips)

Compile to JSON:

python -m mgi_prepall my_protocol.py -o my_protocol.json

Getting Started

This page walks through a complete protocol from deck layout to compilation. A runnable version lives in examples/transfer_demo.py.

1. Protocol script structure

Every protocol is a standalone .py file that exports two things:

  • a module-level metadata dict (optional but recommended)
  • a top-level run(ctx) function — the runner injects a fresh Context instance
from mgi_prepall import Context

metadata = {
    "protocolName": "transfer demo",
    "author": "me",
    "description": "",
}

def run(ctx: Context) -> None:
    ...

2. Configure the deck: ctx.init

The PrepAll deck is a 4-lane × 4-slot grid plus a few gripper-reachable stacks. Each lane's base type is declared with a LaneType:

ctx.init(
    lane1="plateRack",       # plate carriers
    lane2="activeModule",    # active modules
    lane3="plateRack",
    lane4="trashAndPCR",     # trash + PCR
    useTrashBin=True,
    trashBinType="B",
)

Constraints (any violation raises ValueError):

  • lane4 == "trashAndPCR"useTrashBin=TruetrashBinType is not None
  • Some module models are pinned to a specific slot (e.g. the thermocycler must sit at 4-1, see MODULE_MODEL_REQUIRED_SLOT)
  • temperature / heaterShaker modules can only go into a lane whose type is activeModule

See Context.init for the full signature including stacks=... and ingredients=....

3. Load instruments and labware

pipette = ctx.load_pipette("p1000_flex8")
gripper = ctx.load_gripper("gripper")

# Only deck adapters (plate carriers) can sit directly in a slot
slot11 = ctx.load_labware("mgi_prepall_plate_carrier_low_adapter", "1-1")
slot12 = ctx.load_labware("mgi_prepall_plate_carrier_low_adapter", "1-2")

# Plates, tip racks, etc. are stacked onto adapters / modules via `.load_labware`
tiprack = slot11.load_labware("mgi_96_filtertiprack_200ul", code="T1")
src     = slot12.load_labware("mgi_96_well_plate", code="P1")

Four load_* factories return the four resource kinds:

API Returns
ctx.load_pipette(name) Pipette
ctx.load_gripper(name) Gripper
ctx.load_module(model, slot) Module subclass (Temperature / HeaterShaker / Thermocycler)
ctx.load_labware(loadName, slot) Labware — only deck adapters; see DeckAdapterLoadName

Anything other than a deck adapter (plates, tip racks, tube racks, …) must be stacked onto an adapter / module via labware.load_labware(...) or module.load_labware(...).

4. Move liquid

pipette.transfer(
    100.0,
    (src, "A1"),    # 8-channel column anchor: A1 expands to A1..H1
    (dst, "A1"),
    tiprack=tiprack,
)

source and dest accept two shapes:

  • (labware, "A1") — column anchor, automatically expanded by pipette.channel (8 by default) into a full column
  • (labware, ["A1", "B1", ...]) — explicit well list whose length must be a multiple of channels

Many options are available — see Pipette.transfer for the full reference. Common patterns:

Need How
Don't change tip every cycle new_tip="once" or "never"
Mix before / mix after mix_before=(5, 100)
One source, many destinations path="multiDispense"
Many sources, one destination path="multiAspirate"
Liquid-level detection aspirate_detect=AspirateDetect(...)
Drop tip back into source well drop_location="sourceWell"

5. Step-by-step liquid handling

When you need finer control than transfer — e.g. inspecting tip state mid-sequence, splitting an aspirate and dispense across non-trivial logic, or building a custom protocol fragment — use the four standalone step APIs. Each one emits its own step form (visible in the designer / saved JSON) plus the matching runtime command(s). A runnable example covering single-rack and cross-rack scenarios lives at examples/single_step_demo.py.

# 1. Pick up tips. With a single rack you get one pickUpTip step;
#    with a TipRackGroup you may get multiple steps when the pickup spans racks.
pipette.pick_up_tip(tiprack)

# 2. Aspirate — supports the full transfer-aligned option set
pipette.aspirate(
    50, (src, "A1"),
    flow_rate=80,
    offset=Offset(z=2),
    pre_aspirate=10,                           # front air buffer at well top
    air_gap=5,                                 # rear air seal at well top
    aspirate_detect=AspirateDetect(endZOffset=1.2, follow=True),
)

# 3. Dispense
pipette.dispense(
    65, (dst, "A1"),                           # 50 main + 10 pre + 5 air_gap
    flow_rate=120,
    delay_seconds=1,
)

# 4. Drop tip — "trash" (default) or "sourceWell"
pipette.drop_tip()

Both aspirate / dispense require a tip to be held (raise RuntimeError otherwise). They mirror the per-cycle behavior inside transfer, so the resulting commands match what an equivalent transfer call would have emitted.

API Step form Runtime commands per call
pick_up_tip pickUpTip (one per source rack) pickUpTip
aspirate aspirate aspirate (+ optional pre_aspirate at top / air_gap at top / moveToWell + waitForDuration)
dispense dispense dispense (+ optional moveToWell + waitForDuration)
drop_tip dropTip (one for trash; one per source rack for sourceWell) moveToAddressableAreaForDropTip + dropTipInPlace (trash) or dropTip per source rack (sourceWell)

Cross-rack pickup (TipRackGroup)

If tiprack is a TipRackGroup and the first rack runs short, the SDK keeps scanning into the next rack(s) until channels tips are collected. Each (rack, column) worth of tips becomes its own pickUpTip step — so an 8-channel pickup of 5 + 3 (rack A + rack B) produces two consecutive pickUpTip steps:

tips = ctx.create_tip_rack_group([rack_a, rack_b], name="tips")
# ... rack A is now mostly used: only D1..H1 are left ...
pipette.pick_up_tip(tips)
# emits:
#   pickUpTip step  rack=A  channels=5  channelKeys=["1","2","3","4","5"]  wells=[D1..H1]
#   pickUpTip step  rack=B  channels=3  channelKeys=["6","7","8"]          wells=[A1..C1]

A subsequent drop_tip("sourceWell") mirrors the split — one dropTip step per source rack, returning each tip to its origin. drop_tip("trash") always emits a single step regardless of how many racks the tips came from.

Inside transfer / mix the same cross-rack pickup happens automatically, but those multiple pickUpTip runtime commands stay bundled inside the single moveLiquid / mix step — transfer is one logical operation. RuntimeError("Tip racks exhausted") is raised only when the racks together can't supply channels tips.

6. Modules, gripper, pauses

temp_mod = ctx.load_module("prepAllTemperatureModuleV1", "2-1")
temp_mod.set_temperature(4)
temp_mod.wait_for_temperature(4, timeout_seconds=60)

thermo = ctx.load_module("prepAllThermocyclerModuleV1", "4-1")
thermo.run_method("FastFS")    # `wait=True` blocks until completion

ctx.pause("Operator: please verify reagents")    # untilResume
ctx.delay(minutes=5)                              # untilTime

The gripper carries plates / tip racks between stacks and deck adapters:

fresh_tips = gripper.load_labware_to(           # 出料: feed off the stack
    ctx.find_labware_by_code("T2"),
    target=adapter_slot,
)
gripper.store_labware_to(used_tips)              # 回料: put back onto a stack
gripper.move_labware(plate, target_module)       # carry to a module

See Gripper for details.

7. Compile

# write to file
python -m mgi_prepall my_protocol.py -o my_protocol.json

# write to stdout
python -m mgi_prepall my_protocol.py

# or via the installed console script
mgi_prepall my_protocol.py -o my_protocol.json

Or from Python:

from mgi_prepall.runner import compile_script_to_json

json_text = compile_script_to_json("my_protocol.py", indent=2)

See runner for details.

Next steps

  • Browse the API Reference for any specific method
  • Read the Examples closest to your scenario

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mgi_prepall-0.0.2-py3-none-any.whl (117.5 kB view details)

Uploaded Python 3

File details

Details for the file mgi_prepall-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: mgi_prepall-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 117.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for mgi_prepall-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 02d5d738e03ec0c6adb5cc1a190442a2d535cd78e3bbeeccbb555bf313ce8748
MD5 43d1995eba55440fd596b90ce924c5ab
BLAKE2b-256 2c812034f83b2cd83b130943938b9781bbda833a7e2f1762a785ef5faa20f2cb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page