Defining irregular Resources

Hi everyone,

I was wondering whether someone is aware of a more elegant solution:

Task: Generate a PLR definition for a heater-cooler adapter similar to this…

image

My current solution:

def Hamilton_24_adapter_2ml_Eppis(name: str, with_lid: bool = False) -> Plate:
  """ Hamilton cat. no.: 188181
  Tube rack adapter for HHC and TCC, predefined for 2ml Eppendorf tubes.
  Requires assignment at Coordinate(x=, y=, z=) relative to its HHC parent.
  """

  new_plate = Plate(
    name=name,
    size_x=110.0,
    size_y=83.0,
    size_z=48.0,
    ordered_items={},
    lid=None,
    model="HHC_adapter_24_2ml_Eppis",
  )
  
  dx=12
  dy=2
  dz=2 # adapter_tube_hole_bottom_thickness
  item_dx=15.0
  num_items_x=6

  for idx_x in range(num_items_x):
    for idx, idx_y in enumerate([68.5, 41.5, 26.5, 0.0]):
      location = Coordinate(x=dx+item_dx*idx_x, y=dy+idx_y, z=dz)
      new_well = Well(
        name=f"HHC_adapter_24_2ml_Eppis_{idx_x}_{idx}",
        size_x=11,
        size_y=11,
        size_z=32.5+11,
        bottom_type=WellBottomType.V,
        cross_section_type=CrossSectionType.CIRCLE,
        material_z_thickness=.0.5,
        compute_volume_from_height=_compute_volume_from_height_HHC_adapter_24_2ml_Eppis,
        compute_height_from_volume=_compute_height_from_volume_HHC_adapter_24_2ml_Eppis
      )
      new_plate.assign_child_resource(new_well, location=location)
  return new_plate

This worked very well until some recent changes in the implementation of ordered_items.

It still generates the correct geometry but not having defined ordered_items means that the Plate does not have identifiers for the Wells it contains, i.e. Hamilton_24_adapter_2ml_Eppis(name="test_plate")["A1"] will raise an error.

I am searching for a quick way to add identifiers back to this plate.

1 Like

Of course, modelling this TubeRack as a Plate is not the nicest way either.

But it is quite efficient in this case, and I believe PLR should have a simple way to define irregular resources like this and still add custom identifiers (and maybe there already is a way that I missed).


Figure 1 - Correct Visualizer representation of HHC TubeRack modelled as a Plate

There isn’t/shouldn’t be a big difference between Plates and TubeRacks, except that one has Wells and the other has Tubes as children.

The type of the ordered_items param is actually Dict[str, T], where T is a generic for either Well or Tube (or TipSpot in the case of a TipRack). You can pass any dictionary of identifier->resource here. create_ordered_items_2d is just a nice shorthand for the regular case.

You could do this:

import string
from pylabrobot.resources import Plate, Well, Coordinate, WellBottomType, CrossSectionType, Tube

def Hamilton_24_adapter_2ml_Eppis(name: str, with_lid: bool = False) -> Plate:
  """ Hamilton cat. no.: 188181
  Tube rack adapter for HHC and TCC, predefined for 2ml Eppendorf tubes.
  Requires assignment at Coordinate(x=, y=, z=) relative to its HHC parent.
  """

  dx=12
  dy=2
  dz=2 # adapter_tube_hole_bottom_thickness
  item_dx=15.0
  num_items_x=6

  ordered_items = {}

  for idx_x in range(num_items_x):
    for idx, idx_y in enumerate([68.5, 41.5, 26.5, 0.0]):
      location = Coordinate(x=dx+item_dx*idx_x, y=dy+idx_y, z=dz)
      item_id = f"{string.ascii_uppercase[idx]}{idx_x}"
      new_well = Well(
        name=f"HHC_adapter_24_2ml_Eppis_{idx_x}_{idx}",
        size_x=11,
        size_y=11,
        size_z=32.5+11,
        bottom_type=WellBottomType.V,
        cross_section_type=CrossSectionType.CIRCLE,
        material_z_thickness=0.5,
        compute_volume_from_height=_compute_volume_from_height_HHC_adapter_24_2ml_Eppis,
        compute_height_from_volume=_compute_height_from_volume_HHC_adapter_24_2ml_Eppis
      )
      new_well.location = location # set location before assignment
      ordered_items[item_id] = new_well

  new_plate = Plate(
    name=name,
    size_x=110.0,
    size_y=83.0,
    size_z=48.0,
    ordered_items=ordered_items, # create plate with ordered_items
    lid=None,
    model="HHC_adapter_24_2ml_Eppis",
  )
  return new_plate

x = Hamilton_24_adapter_2ml_Eppis("test")

Now,

>>> tube_rack["A1"]
[Well(name=test_HHC_adapter_24_2ml_Eppis_1_0, location=(027.000, 070.500, 002.000), size_x=11, size_y=11, size_z=43.5, category=well)]
>>> tube_rack._ordering
['A0',
 'B0',
 'C0',
 'D0',
 'A1',
 'B1',
...

implementation explanation: in the implementation of ItemizedResource, you can see that identifiers are actually arbitrarily defined. When getting an item by id, it simply gets the child as the same index at that id. This means you’re not constrained to using conventional transposed-excel style notation.

identifier = self._ordering.index(identifier)
...
return cast(T, self.children[identifier])
1 Like

Thank you!

This is a wonderful and extremely versatile solution :slight_smile:

…isn’t it just extremely efficient to share even irregular Resource/labware definitions in just 40 lines of text :slight_smile:

2 Likes