Using PCR plates on STAR

Hello! We’re having some trouble figuring out which measurements we need to take to get PCR plates working.

We have a PLT_CAR_L5PCR with a Eppendorf_96_wellplate_250ul_Vb, here is a picture of the configuration:

Specifically, not sure what the pedestal height would be since the wells go into the pedestal (where it seems that they are usually above it). Additionally, would this scenario need a plate adapter, and if so how would we use that?

1 Like

for this, @CamilloMoschner introduced PlateAdapter: Creating PlateAdapter by BioCam · Pull Request #152 · PyLabRobot/pylabrobot · GitHub

see post on old forum: PLR Feature Announcement: PlateAdapter - PyLabRobot User Discussion - Lab Automation Forums

1 Like

Hi @adit,

I think there are a couple of distinct things to mention:

  1. The plate shown in your picture is not Eppendorf_96_wellplate_250ul_Vb:

Is different to the definition shown in the docs: Eppendorf — PyLabRobot documentation

i.e. you are working with a semi-skirted plate, while this definition is for a non-skirted plate that can be broken into 4 separate pieces.

I don’t know the plate you are using but it would be best to generate its definition and record its Eppendorf manufacturer part number inside the definition.

  1. As @rickwierenga mentioned, your situation is why I invented the PLR PlateAdapter class: this class physically represents the silver-grey block with holes in your image.
    I have not yet gotten around to writing up a nice explainer in the docs, in the meantime you can read its definition and functionality in the PR Rick provided and the Python library file (pylabrobot/pylabrobot/resources/plate_adapter.py at main · PyLabRobot/pylabrobot · GitHub).

In short, the hole-grid of the plate_adapter and the well-grid of a plate need to be aligned. PlateAdapter automates the alignment process for us in the x and y dimensions. I.e. even though you might use very different plates (i.e. dx, dy could be vastly different) when these plates are placed on a PlateAdapter they automatically have the correct x-y coordinates.

I have integrated the specific Hamilton PlateAdapter shown in your image as Hamilton_96_adapter_188182 (Hamilton STAR “ML_STAR” — PyLabRobot documentation)

  1. Deck assignment:

For your specialised carrier this is indeed a bit confusing. Do we have a PLR definition for PLT_CAR_L5PCR?

I am not familiar with this carrier but from your image it appears that this carrier does not have plate_holders/plate_sites but instead just has the Hamilton_96_adapter_188182 directly mounted onto the top plate of the carrier.
I would not be surprised if Hamilton’s GUI is abstracting all of this away by making the top of the adapters the plate_holder/plate_site in its definitions… which then would require painful manual declaration of specific plates onto it.

My recommendation to solve this quickly:

Step 1: define a PLT_CAR_L5PCR in your script and assign it to a deck.

Step 2: measure the front-left-bottom position of the physical Hamilton_96_adapter_188182 instances; store their 5 values.

Step 3: create 5 digital PLR instances of Hamilton_96_adapter_188182 and assign them to the PLT_CAR_L5PCR with the measured location values.

Now you can immediately use the Hamilton_96_adapter_188182 and add your semi-skirted plates on them.

Note: This carrier seems to be standard configured like this? If so we should probably just update its definition to what you figure out in steps 1, 2, 3. Then you only have to measure this accurately once and everyone around the world will be able to use it forever :slight_smile:

  1. Nuance about a PlateAdapter’s plate_z_offset
    Different plate_adapters will have differently shaped holes, and different wells will have different shapes too.
    As a result, the different wellplates will have a different “plate_z_offset” that has to be empirically determined for each plate_adapter-wellplate combination.

You simply modify this value with Hamilton_96_adapter_188182_instance.plate_z_offset = 3.2 at runtime if you find that an offset of 3.2mm is what places the well_cavity_bottom digitally to the the corresponding physical position.

There is a lot going on, so please don’t hesitate to ask more questions.

1 Like

Hi @CamilloMoschner,

Still ramping up on PLR so bear with me - appreciate your help!

Thanks for the clarification on the plate - I am still hoping that it is similar enough (same twin.tec brand from Eppendorf) and also 250uL volume, but not sure yet. Do you have any guidance on how to go about making a new plate definition? Additionally, not sure what to name it because I would give it the same name. I believe it is this plate, part number 0030129334.

This is the default configuration of the PLT_CAR_L5PCR as far as I can tell. Here is the carrier (link). I am a bit confused because this does seem to be defined already in PLR. The behavior I think a novice user would expect in this case is that the PlateAdapter would be integrated into the definition of the plate carrier, and wouldn’t require the definition of a separate object, though of course I’m happy to do the extra steps for now!

For Step 2: I understood where the FLB position represents, but could you elaborate on where the reference point would be so I know what measurement to take?

For Step 4: Could you explain what exactly I’m measuring for Plate z offset? I understand that this depends on the width of the wells and depth of the wells and holes, but not quite sure where I’m measuring from. I believe the well cavity bottom is touching the bottom of the plate adapter for this plate, if that helps.

As a side node - we were able to get aspirate/dispense to work via a hack on these plates: used the L5PCR carrier, defined a pedestal height of 15mm (the height of the metal block), and used a different plate definition (one of the corning ones). I know this is probably a fluke since we didn’t use the right plate spec and had an incorrect understanding of the pedestal Z height, but putting that out there in case the info is useful.

Thank you!

1 Like

this is how lots of labs use venus too: choose a relatively close but not exactly right plate definition, then make minor adjustments to the location of the plate to make sure the pipettes go to the right place

your approach does work, but as you point out, can lead to unexpected problems like the iSWAP gripping at the wrong height. it will be better to quickly define the novel plate yourself.

the resource definitions in pylabrobot are not yet extremely clear about what values you need to measure & where/how to measure them correctly: some of this has been documented well in PRs. I’ll leave rick & camillo to fill in with how they achieve this

2 Likes

I completely agree with @ben:

You can get working with using “close-enough” definitions, but that is building a house of cards, anytime you want to change something you will hit new difficulties. From my experience with “classic automation” companies’ software, this is the reason why things move so slowly.
PLR is all about enabling dynamically changing your automation setups… so that you can run real experiments, as opposed to run the same process over and over again, i.e. “process churning” (which it still can do, and is important, but now you can do more) :slight_smile:

Plate Definition

Let me make some new guides for generating novel plate definitions in PLR over the weekend.

In the meantime, I had a quick look into your plate: I think you are correct, Eppendorf part number 0030129334 (manufacturer link) is what you are working with.

Looking at the technical drawings there are some differences:


though not “too” massive, and most importantly the material_z_thickness of the wells is pretty much the same.

(Note that neither is following the ANSI/SLAS 1-2004 Microplates - Footprint Dimensions standards, i.e. 127.76 mm × 85.48 mm (±0.5 mm))

But you found a flaw in the naming system here: You are correct, they should be named the same based on PLR’s current naming convention for plates (documentation link).

We briefly discussed this possiblity when we introduced it but deemed it unlikely to be an immediate issue… now it is an issue :sweat_smile:

Do you (and @rickwierenga) think an additional “_<miscellaneous_descriptor>” would be useful here?

i.e. your plate would be called Eppendorf_96_wellplate_250ul_Vb_semiskirted?

I would like to try and avoid exact catalogue numbers in the definition name itself as it is quite error-prone and doesn’t give the user immediate information (you can see I tried this out with the Hamilton plate_adapter above a long time ago). The catalgoue number still has to be recorded in the docstring of the definition and the documentation pages, so other people can buy the exact item that is being defined.

1 Like

phenomenal point & useful writeup, as always!

2 Likes

Thank you so much Camillo, this is really helpful!

Totally makes sense regarding doing it the right way - wanted to see if we could have it working at all, and now will work on implementing it with this method!

Wanted to follow up regarding the PCR carrier that is already in PLR. I am confused what the current definition of that is if what we physically have is the default PLT_CAR_L5PCR. And regarding the FLB measurement - where is that relative to?

Thanks for sending the drawings - you are right, they are indeed different. We will give it a shot based on modifying the existing plate definition, but would definitely appreciate a guide to defining the plates! In terms of naming, the plate name you sent makes sense, but it feels like you’d likely be playing whack-a-mole with new properties of plates that you need to distinguish. Also - whether a plate is skirted or not seems important and maybe belongs in the default names, however this runs the risk of the names getting too long. Maybe it would be helpful to have some kind of database that can be queried based on different attributes of the plate, including the catalogue number if desired?

1 Like

This was actually auto-imported from VENUS, back when I thought this could be trusted, but it has actually never been verified. The current definition is probably made by a hamilton engineer hacking it into their existing framework, and history has shown these definitions are not always super accurate. I should remove those soon… I think it would be neat to make a Carrier which has those PlateAdapters as direct children, on which the plates go, instead of ResourceHolder (fka CarrierSite) as traditional plate carriers. (PlateAdapter should inherit from ResourceHolder). This would be trivial, since Camillo has already defined Hamilton_96_adapter_188182.

[quote=“CamilloMoschner, post:6, topic:74”]
Let me make some new guides for generating novel plate definitions in PLR over the weekend. current docs are very brief.

yes, seems necessary. (also motivated by the huge variety in the corning 96-well plates: https://www.corning.com/catalog/cls/documents/drawings/MicroplateDimensions96-384-1536.pdf)

The PlateCarrier’s LFB (flb should be lfb, in xyz syntax). Best calibrated by just going to the PlateAdapter’s lfb using this and computing the carrier location site using HamiltonDeck.rails_to_location (it is a standard carrier except for the sites), then taking the difference between those two numbers.

unless there is another finer, univerisally-applicable differentiator than what we already have, we do need to have the misc suffix. By documenting part numbers and pictures on the site, it should be manageable.

Interesting proposal, but I agree that such long names are not necessarily optimal. I don’t think it’s a good reducer of entropy since most plates are skirted.

We will make this soon. Requires having a structured format (probably in __metadata__ property) of all this information.

1 Like

This is a good point, however:

  • semi-skirted pcr plates are commonly used for library preps, and only pcr plates are semi-skirted
  • some PlateLocations can only support semi-skirted plates
  • other PlateLocations only support full-skirted plates (like the inheco ODTC thermocycler), so trying to use a semi-skirted plate should throw an exception (failure mode: plate fails to get pushed out by little pusher bars, condensation sticks it to the heat block such that iSWAP/CO-RE cannot pick up)

Plus, CO-RE/iSWAP grippers need to travel to an entirely different range of heights to successfully pick/place the plate (much higher than usual), where the Lid of a normal plate would be.

Making LH/workcell behavior differences obvious through an additional *_semiskirted to the name of the plate may help a user recognize that the semiskirted plr plate belongs to a category that will be treated slightly differently by LH than the same pcr plate def without “_semiskirted”

if you’re talking about functional aspects, like “trying to use a semi-skirted plate should throw an exception”, that would have to be done by looking at attributes* since the plate name is not necessarily available at that time (all these named methods simply return a Plate instance, and the user may define their own plates not following out standard).

perhaps i am wrong in saying that most plates are fully skirted. if i am, then sure we can add that as a part of the name.

*i am not sure we can currently differentiate between semi and fully skirted plates by attributes alone

100% agree on using attributes. my naming suggestion is just because these half-skirt pcr plates are used everywhere.

We can. In anticipation of this discussion I added

plate_type: Literal["skirted", "semi-skirted", "non-skirted"] = "skirted"

As an attribute to the Plate definition in Creating Plate Adapter PR#152.

i.e. we can write more stringent checks based on the Plate definition already if we wanted to.


But I think the bigger issue here is not accessing or establishing attributes but it is namespace of the functions that hold specific plates’ definitions.

These functions require…

  1. a name that is intuitive, easy to write and gives information about what is being used (this basically excludes part numbers directly in the name), and
  2. must enable the creation of unique names.

If 2 is not met than similar but different plates can appear with the same name… As is the case in the example of this thread.

If one then performs a PLR wildcard import (not that I recommend ever doing that) then the one last defined will overwrite the namespace of the others when imported at runtime, and lead to unexpected behaviour.

I think a “_miscellaneous-identifier” suffix might be the best option until we can come up with a better solution.

N.B.: I’m fairly certain that this is the reason why the Opentrons Labware library has barely grown in the last couple of years :upside_down_face:

2 Likes

let’s do it.

shall we add _pcr plates?

I think _pcr is too vague. What defines a PCR plate and differentiates it from other plates?

Ideally the miscellaneous identifier captures some physical differences.
In this case here I think _semiskirted is the simplest differentiator.

1 Like

all right

1 Like

Hey, all! Adit and I put together a draft pull request for the Eppendorf_96_wellplate_250ul_Vb_semiskirted definition. I pulled all of the values directly from the spec posted by Camillo earlier in the thread, and can align in the x-direction but am having issues with well spacing in the y-direction (tips are too close together compared to the wells). Let me know if you see any errors that I’m missing!

3 Likes

Successfully updated a plate definition as mentioned above, but running into a problem with plate_z_offset. So far, neither setting the offset this way or setting it by using location=Coordinate(x, y, z) is sufficient to make the tips not go too far down into the well.

This is currently the attempts I have at making the tip not touch the bottom of the well. First, trying plate_z_offset:

pcr_plt_car_0 = PLT_CAR_L5PCR(name='pcr_plate_carrier_0')
pcr_plt_car_0[0] = pcr_adapter_00 = Hamilton_96_adapter_188182('pcr_adapter_00')
pcr_adapter_00.plate_z_offset=3.2

Next, trying to set the 3.2 mm value when assigning the plate to the adapter (which is not the correct way to go about it, as it’s setting the entire plate above the adapter, but wanted to see if this hack worked)

pcr_plate_00 = Eppendorf_96_wellplate_250ul_Vb_semiskirted('pcr_plate_00')
pcr_adapter_00.assign_child_resource(pcr_plate_00, location=Coordinate(0, 0, 3.2))

Hello everyone,

I am trying to use PCR plates on Vantage.
Thermo Scientific AB-0990 0.2ml Ultra Rigit Semi-skirted 96-well PCR Plate (PCR Plate, 96-well, rigid, semi-skirted)
I could only find this profile: Eppendorf_96_wellplate_250ul_Vb and decided to use it. For some reason the tips landed around that black wire, missing the wells completely (see image 2). It looks like the dimensions of the plates are different. I wonder if I could use the adaptor or should I try to create a new plate with new dimensions?


can you use the adapter if it is one of the ones in PLR. @CamilloMoschner do you recognize the adapter Danylo is using?

defining plates

(we should put this on the website)

For the plate, you will have to make a new definition. This drawing looks like it has the same part number. A quick eyeball / caliper could confirm if it’s the definition you’re looking for. Then, take those measurements and create a new PLR definition.

For determining material_z_thickness, you can use Camillo’s z-probing: Z-probing — PyLabRobot documentation.

  1. make an assumption, like 1mm. It doesn’t matter. Define the plate and put it on the deck on a carrier. Assign it in PLR.
  2. get the center of a well using plate.get_item("A1").get_absolute_location("c", "c", "t")
  3. move a channel (like channel 0) on x and y to the well location. Then use z-probing to get the height
  4. Remove the plate from the carrier, and while the channel is still at the same loation, move it down again. You now know the difference between the height with and without the well, which will be the thickness of the bottom of the well.
2 Likes