Hi everyone,
@rickwierenga and I have developed a new PLR feature, the creation of the resource class PlateAdapter
, in pull request #152.
(Whenever possible we should write some brief announcements and documentation in this forum to explain what issue the new feature addresses, how it solves it, how to use it and what its current limitations are and how they might inform next steps, before adding a revised version to the docs.)
Summary of the current issue
Currently, whenever a resource (e.g. a carrier, plate, tiprack, …) is placed on a deck PLR gives you two options to declare the position of the new resource relative to its parent:
- hardcoding: i.e. you have to actively say
location=Coordinate(x,y,z)
to declare where the bottom-left / origin of your new resource is relative to the origin of the parent → this is suboptimal because every user has to figure our their specific coordinates for every resource before and after every movement. - autoplacement: if
location=None
then PLR assumes you want to place the new resource directly on the parent, e.g. aPlate
placed on aPlateCarrierSite
will now share the same origin location. (there are some small differences to this rule forMFXModule
s)
The second mode is more convenient when moving plates during a run but fails when you want to place a plate on any of the following:
- plate adapters
- magnetic racks,
- plate adapters for shakers and temperature control machines, and
- on-deck thermocyclers (ODTCs).
As a consequence, PLR has so far not allowed the use of
- semi-skirted plates, and
- non-skirted plates
because these plates cannot sit on a flatPlateCarrierSite
by themselves.
The issue with placement on the above machines is that they are all representatives of PlateAdapters
.
PlateAdapters
are a block of metal or plastic in which holes are arranged in a grid that follows the ANSI SLAS 4-2004 (R2012) Well Position Standards.
This means the wells are spaced as follows: 96-well plate, 9x9mm^2; 384-well plate, 4.5x4.5mm^2; 1536-well plate, 2.25x2.25mm^2.
However, there are 2 complications to this standard:
- The well-grid is well standardized, the placement of the well-grid on the plate is not.
- PlateAdapters have a well standardized hole-grid to fit the well-grid, but PlateAdapters come in all shapes and sizes.
The positioning of the well-grid on a plate is given in PLR using Well.dx
and Well.dy
, denoting how far away the origin of the bottom-left well (“H1” for a 96-well plate) is from the origin of the plate. I have previously generated this infographic to visualise this and facilitate plate definition generation (though this shows a 24-well plate the same position arguments apply):
This means that Plate
and PlateAdapter
have their own dx
and dy
and Hole/Well.size_x
.
The mission of a Plate
→ PlateAdapter
movement command is to align the 2D center of the bottom-left Plate.well with the 2D center of the bottom-left PlateAdapter.hole, despite their differences in relatve grid positioning.
Here is a visualisation of what happens if this is ignored and a Plate is moved onto a magnetic rack before this PR:
→ your run will fail
How PlateAdapter solves this issue
Issue 1: Complexity of automated placement of Plate onto PlateAdapter
- We created a new
PlateAdapter
class inresources/plate_adapter.py
. - When a new
Plate
is assigned to aPlateAdapter
thePlateAdapter.assign_child_resource(Plate)
method calculates the necessary location adjustment automatically. PlateAdapter
is therefore fundamentally different to otherResource
subclasses because the location of a child that is assigned to it does not have to be hardcoded .
Issue 2: Missing Plate Support due to lack of PlateAdapter
- With
PlateAdapter
working, semi- and non-skirted plates can now be used in PLR.
Examples
# Generate MFX carrier instances & assign to deck
shaker_carrier_1 = PLT_CAR_L4_SHAKER(name='shaker_carrier_1')
lh.deck.assign_child_resource(shaker_carrier_1, rails=1)
# Add heater-cooler
shaker_carrier_1[2] = hhc_1 = Hamilton_HC(name="hhc_1") # MFXModule in development
# Add PlateAdapter onto heater-cooler
hhc_adapter_188182_1 = Hamilton_96_adapter_188182(name="hhc_adapter_188182_1")
hhc_adapter_188182_1_location = hhc_1.child_resource_location + Coordinate(x=7.5, y=7.0, z=0.0)
hhc_1.assign_child_resource(hhc_adapter_188182_1 , location=hhc_adapter_188182_1_location)
With this deck setup any plate can now be moved onto the adapter on the heater-cooler even though different plates have their well-grid located differently on the plate. PlateAdapter
automatically calculates the correct x-y position to place the plate.
await lh.move_plate(plate_x, hhc_adapter_188182_1)
Limitations/Next Steps
At the moment only two PlateAdapters
are defined in PLR:
Alpaqua_96_magnum_flx
Hamilton_96_adapter_188182
We encourage users to contribute more definitions to add to increase the number of PLR-supported PlateAdapters
.
PlateAdapter
only calculates the correct x-y position for placing the Plate
at the moment.
The z-coordinate has to be empirically evaluated due to a.) complexities of Plate well-bottom geometries, and b.) a general issue regarding z-height definitions in Plate
that is currently being solved.
Please use this thread to ask questions and add suggestions of how to make this feature more useful.
Happy automation