Initial commit
This commit is contained in:
489
skills/pylabrobot/references/resources.md
Normal file
489
skills/pylabrobot/references/resources.md
Normal file
@@ -0,0 +1,489 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user