UniteLabs
Deck

Save/Load a Deck

Persist deck configurations to JSON, reload them across workflow runs, and bundle them alongside your workflow code.

The rationale for saving deck layouts — audit trail, reproducibility, version control — is covered in Deck. This guide covers the procedural API.

Saving a Deck

Save to a JSON file

Call save() on any deck object. It writes an indented JSON file at the path you specify, accepting either a string or a pathlib.Path.

from unitelabs.liquid_handling.hamilton import MicrolabSTAR

hamilton = MicrolabSTAR(name="Microlab STAR")
await hamilton.initialize()

# ... add carriers and labware ...

hamilton.deck.save("deck_layouts/my_deck.json")

The resulting JSON file captures the full deck configuration: every carrier, its track position, and all nested labware with their identifiers and state. The file is human-readable and safe to commit to version control.

Commit your deck JSON files alongside your workflow scripts. This gives you a full audit trail of which physical deck layout was used for each experiment.

What the JSON looks like

A saved deck file has this structure:

{
  "description": "STARlet deck for Plate Painter Pro",
  "carriers": [
    {
      "id": "tip_carrier",
      "model": "TIP_CAR_480_A00",
      "track": 15,
      "labware": {
        "0": {
          "model": "HamiltonTipRack_300",
          "id": "tip_rack",
          "filled_with": "HamiltonTip_300_Filter"
        }
      }
    },
    {
      "id": "reservoir_carrier",
      "model": "PLT_CAR_L5AC_A00",
      "track": 11,
      "labware": {
        "0": { "model": "StandardTrough", "id": "reservoir_ice", "volume_ul": 200000 },
        "1": { "model": "StandardTrough", "id": "reservoir_orange", "volume_ul": 200000 }
      }
    },
    {
      "id": "plate_carrier",
      "model": "PLT_CAR_L5AC_A00",
      "track": 7,
      "labware": {
        "0": { "model": "Standard96Plate", "id": "wellplate" }
      }
    }
  ]
}

Export to a Python script

If you prefer a deck definition that is readable as code rather than a data file, you can represent the same layout as a Python script that builds it programmatically. This is also the format shown in Building a Deck.

from unitelabs.liquid_handling.hamilton import HamiltonDeck
from unitelabs.labware.hamilton import (
    TIP_CAR_480_A00,
    PLT_CAR_L5AC_A00,
    HamiltonTipRack_300,
    HamiltonTip_300_Filter,
    StandardTrough,
    Standard96Plate,
)

deck = HamiltonDeck()

tip_carrier = TIP_CAR_480_A00(identifier="tip_carrier")
tip_carrier[0] = HamiltonTipRack_300(identifier="tip_rack", filled_with=HamiltonTip_300_Filter)
deck.add(tip_carrier, track=15)

reservoir_carrier = PLT_CAR_L5AC_A00(identifier="reservoir_carrier")
reservoir_carrier[0] = StandardTrough(identifier="reservoir_ice", volume_ul=200_000)
reservoir_carrier[1] = StandardTrough(identifier="reservoir_orange", volume_ul=200_000)
deck.add(reservoir_carrier, track=11)

plate_carrier = PLT_CAR_L5AC_A00(identifier="plate_carrier")
plate_carrier[0] = Standard96Plate(identifier="wellplate")
deck.add(plate_carrier, track=7)

The Python script form is useful when you want deck changes to produce meaningful diffs in code review. The JSON form is more convenient for loading at runtime. Both work well together: use the Python script as the authoritative source and regenerate the JSON from it when needed.

Loading a Deck

Load into a new deck object

Use HamiltonDeck.load() to create a fully configured deck object from a saved JSON file. This is useful when you want to inspect or manipulate the deck before attaching it to a liquid handler.

from unitelabs.liquid_handling.hamilton.modules import HamiltonDeck

deck = HamiltonDeck.load("deck_layouts/my_deck.json")
print(deck.summary())

Load in-place into a liquid handler

Use lh.deck.load() to load a deck configuration directly into an already-configured liquid handler. This is the most common pattern in workflow scripts: configure the instrument, load the saved deck layout, then initialize.

from unitelabs.liquid_handling.hamilton import MicrolabSTARMock

hamilton = MicrolabSTARMock(name="Microlab STAR")
await hamilton.configure()

hamilton.deck.load("deck_layouts/my_deck.json")

await hamilton.initialize()
print(hamilton.deck.summary())
Call lh.deck.load() after configure() but before initialize(). This ensures the instrument initializes with the correct layout already applied.

Reconstructing from a dictionary

If you have a deck serialized as a Python dictionary (for example, fetched from a database or returned by an API) use HamiltonDeck.from_json():

from unitelabs.liquid_handling.hamilton.modules import HamiltonDeck

# deck_dict could come from a database, an API response, etc.
deck = HamiltonDeck.from_json(deck_dict)

To get the dictionary from a live deck, use deck.to_json():

deck_dict = hamilton.deck.to_json()

Bundling Deck Configs with Workflow Code

For production workflows, keep your deck JSON files inside your workflow package rather than relying on filesystem paths that may differ between machines. A simple helper resolves the path relative to the package itself.

Project layout:

my_workflow/
├── __init__.py
├── library/
│   ├── __init__.py        ← path helper lives here
│   └── standard_deck.json ← deck config bundled with the code
├── phase_01_initialization.py
└── phase_02_run.py

my_workflow/library/__init__.py:

from pathlib import Path

_LIBRARY_DIR = Path(__file__).parent


def get_library_path(filename: str) -> str:
    path = _LIBRARY_DIR / filename
    if not path.exists():
        raise FileNotFoundError(f"Library file not found: {path}")
    return str(path)

my_workflow/phase_01_initialization.py:

from unitelabs.liquid_handling.hamilton import MicrolabSTARMock
from my_workflow.library import get_library_path


async def initialize_workcell() -> MicrolabSTARMock:
    hamilton = MicrolabSTARMock(name="Microlab STAR")
    await hamilton.configure()

    deck_config_path = get_library_path("standard_deck.json")
    hamilton.deck.load(deck_config_path)

    await hamilton.initialize()
    return hamilton
Bundling deck configs inside your Python package means the correct layout is always co-located with the workflow that uses it. When you deploy or share the workflow, the deck definition travels with it.

Quick Reference

MethodWhat it does
deck.save("path.json")Write the current deck to a JSON file
deck.to_json()Return the deck as a Python dictionary
HamiltonDeck.load("path.json")Create a new deck object from a JSON file
HamiltonDeck.from_json(dict)Create a new deck object from a dictionary
lh.deck.load("path.json")Load a deck configuration in-place into a liquid handler

Next Steps