ContainerRack#

ContainerRack models a movable rack with positions that can each hold one resource, such as a tube. Each position is a ResourceHolder, which stays in the rack when its contents are removed. A rack can be partly filled or completely empty.

The resource hierarchy is:

ContainerRack
└── ResourceHolder (one per position)
    └── Resource (present when the position is occupied)

ContainerRack inherits the position lookup methods of ItemizedResource and adds methods for working with the contents of those positions. Its dimensions and layout are configurable; it does not require a particular footprint.

Create a rack#

This example creates an empty rack with two rows and three columns. The dimensions are illustrative, in millimeters. Use measured dimensions when defining your own labware.

from pylabrobot.resources import ContainerRack, Tube, create_ordered_items_2d
from pylabrobot.resources.resource_holder import ResourceHolder

rack = ContainerRack(
  name="sample_rack",
  size_x=80,
  size_y=60,
  size_z=20,
  ordered_items=create_ordered_items_2d(
    ResourceHolder,
    num_items_x=3,
    num_items_y=2,
    dx=5,
    dy=5,
    dz=0,
    item_dx=25,
    item_dy=25,
    size_x=16,
    size_y=16,
    size_z=20,
    name_prefix="sample_rack",
  ),
)

ordered_items must contain at least one holder. The helper creates holders with positions relative to the rack and assigns identifiers A1, B1, A2, B2, A3, B3, in that order. Row A is at the back of the rack (larger y); column 1 is at the left (smaller x).

dx, dy, and dz locate the leftmost, frontmost holder’s origin relative to the rack. item_dx and item_dy give the spacing between holder origins. Each holder also has a child_location, which defaults to (0, 0, 0) and determines where its contents sit relative to it.

Fill positions#

Assign a resource to a position with rack[identifier] = resource:

tube = Tube(
  name="sample_1",
  size_x=16,
  size_y=16,
  size_z=40,
  max_volume=5000,
)
rack["A1"] = tube

assert rack.has_container("A1")
assert not rack.has_container("B1")

This assigns the tube as a child of the holder at A1. Assigning another resource to A1 replaces the existing contents in the resource model. These operations describe the layout; they do not command a robot to move anything.

To fill several positions, supply a list or tuple with one resource per position. The endpoints of the string range "A2:A3" are included:

tubes = [
  Tube(name=f"sample_{i}", size_x=16, size_y=16, size_z=40, max_volume=5000)
  for i in (2, 3)
]
rack["A2:A3"] = tubes

The base ContainerRack accepts any Resource. For a rack whose contents must be tubes, use TubeRack, its specialization that checks the assigned resource type.

Access holders and containers#

Use get_item() to retrieve a holder and get_container() to retrieve its contents. Bracket lookup always returns a list of holders, even for a single position:

holder = rack.get_item("A1")
assert rack["A1"] == [holder]
assert holder.resource is tube
assert rack.get_container("A1") is tube

assert rack.get_containers("A2:A3") == tubes
assert rack.row_containers("A") == [tube, *tubes]
assert len(rack.get_occupied_containers()) == 3

Method

Result

has_container("A1")

Whether a known position is occupied.

get_container("A1")

The resource at one position.

get_containers("A2:A3")

The resources at the selected positions.

row_containers("A")

The resources in a row; row indices can also be zero-based integers.

column_containers(0)

The resources in a column, using a zero-based index.

get_all_containers()

The resources at every position, in rack order.

get_occupied_containers()

The resources at occupied positions, skipping empty positions.

The retrieval methods raise ValueError if any requested position is empty, except get_occupied_containers(). For example, get_all_containers() would raise for the partly filled rack above. Check has_container() before retrieving an individual position that may be empty.

For an irregular layout, you can supply your own dictionary of positioned ResourceHolder objects with identifiers such as "primary" and "backup". Single-position lookup and assignment work with these names. Row, column, and string-range selection require Excel-style identifiers such as A1.

Remove containers#

Unassign a container to remove it from its holder. Use empty() to remove all containers while keeping the rack and its holders:

rack.get_container("A1").unassign()
assert not rack.has_container("A1")

rack.empty()
assert rack.get_occupied_containers() == []
assert rack.num_items == 6

empty() removes resources from the modeled rack; it does not empty liquid from the containers.