Files
2025-11-30 08:30:10 +08:00

490 lines
12 KiB
Markdown

# Resource Management in PyLabRobot
## Overview
Resources in PyLabRobot represent laboratory equipment, labware, or components used in protocols. The resource system provides a hierarchical structure for managing plates, tip racks, troughs, tubes, carriers, and other labware with precise spatial positioning and state tracking.
## Resource Basics
### What is a Resource?
A resource represents:
- A piece of labware (plate, tip rack, trough, tube)
- Equipment (liquid handler, plate reader)
- A part of labware (well, tip)
- A container of labware (deck, carrier)
All resources inherit from the base `Resource` class and form a tree structure (arborescence) with parent-child relationships.
### Resource Attributes
Every resource requires:
- **name**: Unique identifier for the resource
- **size_x, size_y, size_z**: Dimensions in millimeters (cuboid representation)
- **location**: Coordinate relative to parent's origin (optional, set when assigned)
```python
from pylabrobot.resources import Resource
# Create a basic resource
resource = Resource(
name="my_resource",
size_x=127.76, # mm
size_y=85.48, # mm
size_z=14.5 # mm
)
```
## Resource Types
### Plates
Microplates with wells for holding liquids:
```python
from pylabrobot.resources import (
Cos_96_DW_1mL, # 96-well plate, 1mL deep well
Cos_96_DW_500ul, # 96-well plate, 500µL
Plate_384_Sq, # 384-well square plate
Cos_96_PCR # 96-well PCR plate
)
# Create plate
plate = Cos_96_DW_1mL(name="sample_plate")
# Access wells
well_a1 = plate["A1"] # Single well
row_a = plate["A1:H1"] # Entire row (A1-H1)
col_1 = plate["A1:A12"] # Entire column (A1-A12)
range_wells = plate["A1:C3"] # Range of wells
all_wells = plate.children # All wells as list
```
### Tip Racks
Containers holding pipette tips:
```python
from pylabrobot.resources import (
TIP_CAR_480_A00, # 96 standard tips
HTF_L, # Hamilton tips, filtered
TipRack # Generic tip rack
)
# Create tip rack
tip_rack = TIP_CAR_480_A00(name="tips")
# Access tips
tip_a1 = tip_rack["A1"] # Single tip position
tips_row = tip_rack["A1:H1"] # Row of tips
tips_col = tip_rack["A1:A12"] # Column of tips
# Check tip presence (requires tip tracking enabled)
from pylabrobot.resources import set_tip_tracking
set_tip_tracking(True)
has_tip = tip_rack["A1"].tracker.has_tip
```
### Troughs
Reservoir containers for reagents:
```python
from pylabrobot.resources import Trough_100ml
# Create trough
trough = Trough_100ml(name="buffer")
# Access channels
channel_1 = trough["channel_1"]
all_channels = trough.children
```
### Tubes
Individual tubes or tube racks:
```python
from pylabrobot.resources import Tube, TubeRack
# Create tube rack
tube_rack = TubeRack(name="samples")
# Access tubes
tube_a1 = tube_rack["A1"]
```
### Carriers
Platforms that hold plates, tips, or other labware:
```python
from pylabrobot.resources import (
PlateCarrier,
TipCarrier,
MFXCarrier
)
# Carriers provide positions for labware
carrier = PlateCarrier(name="plate_carrier")
# Assign plate to carrier
plate = Cos_96_DW_1mL(name="plate")
carrier.assign_child_resource(plate, location=(0, 0, 0))
```
## Deck Management
### Working with Decks
The deck represents the robot's work surface:
```python
from pylabrobot.resources import STARLetDeck, OTDeck
# Hamilton STARlet deck
deck = STARLetDeck()
# Opentrons OT-2 deck
deck = OTDeck()
```
### Assigning Resources to Deck
Resources are assigned to specific deck positions using rails or coordinates:
```python
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.resources import STARLetDeck, TIP_CAR_480_A00, Cos_96_DW_1mL
lh = LiquidHandler(backend=backend, deck=STARLetDeck())
# Assign using rail positions (Hamilton STAR)
tip_rack = TIP_CAR_480_A00(name="tips")
source_plate = Cos_96_DW_1mL(name="source")
dest_plate = Cos_96_DW_1mL(name="dest")
lh.deck.assign_child_resource(tip_rack, rails=1)
lh.deck.assign_child_resource(source_plate, rails=10)
lh.deck.assign_child_resource(dest_plate, rails=15)
# Assign using coordinates (x, y, z in mm)
lh.deck.assign_child_resource(
resource=tip_rack,
location=(100, 200, 0)
)
```
### Unassigning Resources
Remove resources from deck:
```python
# Unassign specific resource
lh.deck.unassign_child_resource(tip_rack)
# Access assigned resources
all_resources = lh.deck.children
resource_names = [r.name for r in lh.deck.children]
```
## Coordinate System
PyLabRobot uses a right-handed Cartesian coordinate system:
- **X-axis**: Left to right (increasing rightward)
- **Y-axis**: Front to back (increasing toward back)
- **Z-axis**: Down to up (increasing upward)
- **Origin**: Bottom-front-left corner of parent
### Location Calculations
```python
# Get absolute location (relative to deck/root)
absolute_loc = plate.get_absolute_location()
# Get location relative to another resource
relative_loc = well.get_location_wrt(deck)
# Get location relative to parent
parent_relative = plate.location
```
## State Management
### Tracking Liquid Volumes
Track liquid volumes in wells and containers:
```python
from pylabrobot.resources import set_volume_tracking
# Enable volume tracking globally
set_volume_tracking(True)
# Set liquid in well
plate["A1"].tracker.set_liquids([
(None, 200) # (liquid_type, volume_in_uL)
])
# Multiple liquids
plate["A2"].tracker.set_liquids([
("water", 100),
("ethanol", 50)
])
# Get current volume
volume = plate["A1"].tracker.get_volume() # Returns total volume
# Get liquids
liquids = plate["A1"].tracker.get_liquids() # Returns list of (type, vol) tuples
```
### Tracking Tip Presence
Track which tips are present in tip racks:
```python
from pylabrobot.resources import set_tip_tracking
# Enable tip tracking globally
set_tip_tracking(True)
# Check if tip is present
has_tip = tip_rack["A1"].tracker.has_tip
# Tips are automatically tracked when using pick_up_tips/drop_tips
await lh.pick_up_tips(tip_rack["A1"]) # Marks tip as absent
await lh.return_tips() # Marks tip as present
```
## Serialization
### Saving and Loading Resources
Save resource definitions and states to JSON:
```python
# Save resource definition
plate.save("plate_definition.json")
# Load resource from JSON
from pylabrobot.resources import Plate
plate = Plate.load_from_json_file("plate_definition.json")
# Save deck layout
lh.deck.save("deck_layout.json")
# Load deck layout
from pylabrobot.resources import Deck
deck = Deck.load_from_json_file("deck_layout.json")
```
### State Serialization
Save and restore resource states separately from definitions:
```python
# Save state (tip presence, liquid volumes)
state = plate.serialize_state()
with open("plate_state.json", "w") as f:
json.dump(state, f)
# Load state
with open("plate_state.json", "r") as f:
state = json.load(f)
plate.load_state(state)
# Save all states in hierarchy
all_states = lh.deck.serialize_all_state()
# Load all states
lh.deck.load_all_state(all_states)
```
## Custom Resources
### Defining Custom Labware
Create custom labware when built-in resources don't match your equipment:
```python
from pylabrobot.resources import Plate, Well
# Define custom plate
class CustomPlate(Plate):
def __init__(self, name: str):
super().__init__(
name=name,
size_x=127.76,
size_y=85.48,
size_z=14.5,
num_items_x=12, # 12 columns
num_items_y=8, # 8 rows
dx=9.0, # Well spacing X
dy=9.0, # Well spacing Y
dz=0.0, # Well spacing Z (usually 0)
item_dx=9.0, # Distance between well centers X
item_dy=9.0 # Distance between well centers Y
)
# Use custom plate
custom_plate = CustomPlate(name="my_custom_plate")
```
### Custom Wells
Define custom well geometry:
```python
from pylabrobot.resources import Well
# Create custom well
well = Well(
name="custom_well",
size_x=8.0,
size_y=8.0,
size_z=10.5,
max_volume=200, # µL
bottom_shape="flat" # or "v", "u"
)
```
## Resource Discovery
### Finding Resources
Navigate the resource hierarchy:
```python
# Get all wells in a plate
wells = plate.children
# Find resource by name
resource = lh.deck.get_resource("plate_name")
# Iterate through resources
for resource in lh.deck.children:
print(f"{resource.name}: {resource.get_absolute_location()}")
# Get wells by pattern
wells_a = [w for w in plate.children if w.name.startswith("A")]
```
### Resource Metadata
Access resource information:
```python
# Resource properties
print(f"Name: {plate.name}")
print(f"Size: {plate.size_x} x {plate.size_y} x {plate.size_z} mm")
print(f"Location: {plate.get_absolute_location()}")
print(f"Parent: {plate.parent.name if plate.parent else None}")
print(f"Children: {len(plate.children)}")
# Type checking
from pylabrobot.resources import Plate, TipRack
if isinstance(resource, Plate):
print("This is a plate")
elif isinstance(resource, TipRack):
print("This is a tip rack")
```
## Best Practices
1. **Unique Names**: Use descriptive, unique names for all resources
2. **Enable Tracking**: Turn on tip and volume tracking for accurate state management
3. **Coordinate Validation**: Verify resource positions don't overlap on deck
4. **State Serialization**: Save deck layouts and states for reproducible protocols
5. **Resource Cleanup**: Unassign resources when no longer needed
6. **Custom Resources**: Define custom labware when built-in options don't match
7. **Documentation**: Document custom resource dimensions and properties
8. **Type Checking**: Use isinstance() to verify resource types before operations
9. **Hierarchy Navigation**: Use parent/children relationships to navigate resource tree
10. **JSON Storage**: Store deck layouts in JSON for version control and sharing
## Common Patterns
### Complete Deck Setup
```python
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import STAR
from pylabrobot.resources import (
STARLetDeck,
TIP_CAR_480_A00,
Cos_96_DW_1mL,
Trough_100ml,
set_tip_tracking,
set_volume_tracking
)
# Enable tracking
set_tip_tracking(True)
set_volume_tracking(True)
# Initialize liquid handler
lh = LiquidHandler(backend=STAR(), deck=STARLetDeck())
await lh.setup()
# Define resources
tip_rack_1 = TIP_CAR_480_A00(name="tips_1")
tip_rack_2 = TIP_CAR_480_A00(name="tips_2")
source_plate = Cos_96_DW_1mL(name="source")
dest_plate = Cos_96_DW_1mL(name="dest")
buffer = Trough_100ml(name="buffer")
# Assign to deck
lh.deck.assign_child_resource(tip_rack_1, rails=1)
lh.deck.assign_child_resource(tip_rack_2, rails=2)
lh.deck.assign_child_resource(buffer, rails=5)
lh.deck.assign_child_resource(source_plate, rails=10)
lh.deck.assign_child_resource(dest_plate, rails=15)
# Set initial volumes
for well in source_plate.children:
well.tracker.set_liquids([(None, 200)])
buffer["channel_1"].tracker.set_liquids([(None, 50000)]) # 50 mL
# Save deck layout
lh.deck.save("my_protocol_deck.json")
# Save initial state
import json
with open("initial_state.json", "w") as f:
json.dump(lh.deck.serialize_all_state(), f)
```
### Loading Saved Deck
```python
from pylabrobot.resources import Deck
# Load deck from file
deck = Deck.load_from_json_file("my_protocol_deck.json")
# Load state
import json
with open("initial_state.json", "r") as f:
state = json.load(f)
deck.load_all_state(state)
# Use with liquid handler
lh = LiquidHandler(backend=STAR(), deck=deck)
await lh.setup()
# Access resources by name
source_plate = deck.get_resource("source")
dest_plate = deck.get_resource("dest")
```
## Additional Resources
- Resource Documentation: https://docs.pylabrobot.org/resources/introduction.html
- Custom Resources Guide: https://docs.pylabrobot.org/resources/custom-resources.html
- API Reference: https://docs.pylabrobot.org/api/pylabrobot.resources.html
- Deck Layouts: https://github.com/PyLabRobot/pylabrobot/tree/main/pylabrobot/resources/deck