Mfx carrier

following a recent discussion with @CamilloMoschner

so that we know what we’re talking about:

  • base:

  • plate module:

  • tip rack module:

image-

  • tilt module:

all go on the base. it’s a user-configurable carrier

current implementation

the carrier base has CarrierSites as children, which have MFXModules as children. The tree is like this: carrier (base) > carriersite > mfxmodule > plate.

>>> base = MFX_CAR_L5_base(name='base')
>>> base[0]
CarrierSite
>>> base[0] = DWP_module_1 = MFX_DWP_rackbased_module(name="dwp1")
>>> base[1] = DWP_module_2 = MFX_DWP_rackbased_module(name="dwp2")
>>> # assigning a plate
>>> DWP_module_1.assign_child_resource(plate)

proposed: initialize with sites, __getitem__ returns an MFXModule

>>> # at initialization, pass sites to the carrier base
>>> mfx_carrier = MFX_CAR_base_L5(sites={
...   0: mfx_deep_well_site(),
...   1: tilter(),
...   3: mfx_tip_rack_site()
... })
>>> mfx_carrier[0]
PlateCarrierSite(location=Coordinate(0, 1, 0), size_z = 500)
>>> mfx_carrier[1]
Tilter(location=Coordinate(0, 2, 0), size_z = 600)
>>> mfx_carrier[2]
CarrierSite(location=Coordinate(0, 3, 0), size_z = 400)

propposed: remove MFXModule class, replace with CarrierSite and CarrierSite ResourceHolderMixin

The mfx modules can be devided into:

  • plate sites
  • tip rack sites
  • machines (like tilter, heater shaker)

It seems we don’t need a class besides (Plate)CarrierSite and Machine to model these. I propose removing the MFXModule class (which is very similar in implementation to CarrierSite) and replacing the plate modules with PlateCarrierSite, tip rack sites with CarrierSite (as in TipCarrier) and the Tilter/HeaterShaker simply with Machine+ResourceHolderMixin.

eg

def mfx_deep_well_site() -> PlateCarrierSite:
  return PlateCarrierSite(
    size_x,y,z = physical dim of module
    child_location = where plate would go
  )

depending on how you view it, it’s a bit of a shift compared to how CarrierSite is treated currently. Currently, it’s a guiding resource that one could argue is purely abstract (the size_z is always 0). With this change, they would, certainly in the case of mfx carriers, become physical objects with a size in all 3d dimensions.

1 Like

I love this idea!

It represents the reality of mfx_carriers being physically assembled (i.e. mfx_modules screwed into the mfx_carrier_sites) before an automation run.

This would also enable the creation of a carrier method like .show_configuration / show_installed_mfx_modules, as a simpler way of returning the state of the mfx_carrier.


I do not understand this proposal. Could you please elaborate?

Physically speaking, the mfx_carrier has real sites, i.e. 2D areas with a specified origin to which mfx_modules can be screwed onto.

This generates the hierarchy

deck → carrier → carrier_site → mfx_module → child_location (currently attribute of mfx_module)

It sounds like you are proposing to merge carrier_site and mfx_module into PlateCarrierSite?

deck → carrier → PlateCarrierSite

Question: If my interpretation is correct, where is the information of the sites that this new PlateCarrierSite can be assigned to?

Additionally, to me it does not seem intuitive that something we buy with the name “MFX Module” is now represented as a PlateCarrierSite.


To maintain the clarity of the naming and their physical representation I would suggest modifying the structure to:

deck → carrier → carrier_site → mfx_module → PlateSite (at child_location (attribute of mfx_module))

I might have made a mistake when naming PlateCarrierSite.
The behaviour of this site is only dependent on the interaction between

  1. a plate, and
  2. a site to place it to,
    but not on a carrier.
    Therefore the name PlateSite seems more appropriate than PlateCarrierSite and represents a higher level 2D Resource that can be placed onto any Resource that accepts a Plate

For standard PLT_CARs nothing would change through this renaming.
But for mfx_modules it would suddenly enable them to represent PlateSites with a pedestal.

Because at the moment, mfx_modules with a pedestal cannot be used with PLR (without manual declaration of z_offset).

This is missing a couple of MFX module types:
e.g.:


Furthermore, there isn’t one mfx_carrier but to my understanding at least four:


Though we haven’t integrated all these Hamilton mfx_modules yet (I believe I’m the only person who added 2 to the PLR Resource Library so far?), the current system enables the use of all of them.
I am not sure how this would work for the second proposal?

essentially, yes. But I would maybe phase it as removing MFXModule, and using PlateCarrierSite instead.

I think that should live in the initializer of the MFXCarrier class. When you initialize it as so:

>>> # at initialization, pass sites to the carrier base
>>> mfx_carrier = MFX_CAR_base_L5(sites={
...   0: mfx_deep_well_site(),
...   1: tilter(),
...   3: mfx_tip_rack_site()
... })

it will know the location of each of those indices.

in draft code:

class MFXCarrier(Carrier):
  def __init__(..., sites: list[ResourceHolderMixin]):
    locs = [Coordinate, Coordinate, Coordinate]
    for site, loc in zip(sites):
      self.assign_child_resource(site, loc)

(this is sort of a breaking change, which in October we can still, and I think should, make)

I would say that since it is in reality a plate site, it should be named like that. What if we put this site on an Opentrons? What if we 3d print our own sites?

I agree it adds to the learning curve of PLR when coming from trad. But the issue I have with these arguments is that we tend towards rebuilding venus and other trad software, whereas I think we should think from first principles.

ResourceHolderMixin sort of does what you’re describing here. Except that it is general for all kinds of resources, not just plates.

I think since only carriers have pedestals, it makes sense to implement the pedestal behavior in PlateCarrierSite.

Aha, I did not know about those. I think those can be directly assigned, since they will never take children:

>>> mfx_carrier[0]
Container(name="waste")
>>> mfx_carrier[0].parent
mfx_carrier

While doing this what happens to the original PlateCarrierSite, i.e. the subclass of CarrierSite that was designed for the explicit purpose of fixing the dynamic plate-to-site placement issue?
Where would the plate-to-site placement logic go in this scenario?

I meant to say current PlateCarrierSite == PlateSite but PlateSite can be assigned to anything because…

…does not seem correct to me:
Pedestals are found everywhere: they are on different mfx_modules, they are on heater-shakers, they are on Tecan slots, they might even be on plate_reader trays.

That means to transfer the dynamic plate-to-site behaviour we added to PlateCarrierSite together to all these other plate-taking-sites which are not part of a carrier, the term PlateSite sounds more intuitive?


I agree that this is a breaking change:
It means that MFXCarrier generates a new type of Carrier, i.e. all other Carrier subclasses have designated CarrierSites, except for MFXCarrier?

Doesn’t this create an artificial inconsistency in the carrier system?

Are you saying that you see the entire mfx_module as a “site”?

I think this is my biggest issue with this name: it is inconsistent with the general meaning of the word site:

it is defined as (Google “site definition”) by the OED:

an area of ground on which a town, building, 
or monument is constructed.

…i.e. a 2D object or area. This is also how PLR has so far used this term for CarrierSite.

It seems very unintuitive to use a word designated in general vocabulary for 2D objects/areas for a 3D-metal object that can have very different “sites” for taking child resources, such as the above mentioned examples of plate-, tube- and trough-sites.

What are the arguments for using this term in this new context in your opinion?

If “site” now takes the meaning of the 3D mfx_module, and it is assigned to the (0,0,0) coordinate of the now not directly existing CarrierSite, where is the SLAS-taking plate site (currently known as PlateCarrierSite) origin?
And is the surface of the SLAS-taking plate site still the top_surface of that site, or is it the top of the walls surrounding it?

The immediate child of the mfx carrier, which in my message above I named PlateCarrierSite, but you argue we should name something else.

agreed

what do you mean with ‘designated’ in this sentence?

in this sense I agree that “site” is a confusing name. Maybe “PlateHolder” is more intuitive.

I really just used the term because we were using it before but I’m open to changing the name.

the origin of the plate-taking “site” could be an argument, which would be 000 for “PlateCarrierSites” on a PlateCarrier.

I had to make sure this is correct:

Proposal 2, i.e. removal of MFXModule, is not making PLR less like VENUS. It makes it exactly like VENUS:

In VENUS mfx_modules are completely abstracted away.
First one has to create a new mfx_carrier_instance .tml file in the “Labware Assistant 3D”:

→ This is equivalent to a MFXCarrier instance that has predefined its sites, i.e. proposal_1 of this thread.

For VENUS, this generates 2 files:
The .tml file which is the carrier (/template in Hamilton jargon) definition file,
and the 3D-render file in the ancient file format of .x
image

Once imported into VENUS one can easily view what the definition of this carrier looks like:

i.e. there is no mentioning of the mfx_module.
It represent the plate_module as a floating-in-air site → is this what proposal_2 suggests doing in PLR?

It is only in the 3D-rendering file that the mfx_modules are actually presented.

image

This means that the physical representation of real items, i.e. mfx_modules, is a PLR-first, aimed at helping users setting up their system.

It represent the plate_module as a floating-in-air site

This is what made it so hard for me to generate MFXModule in the first place… the geometry of the modules is not represented in VENUS, only the PlateSite on top of the module is. (i.e. there is no manufacturer-created ground truth)

True, that is imprecise wording:

I meant that all other carriers in PLR have child resources of type CarrierSite.
As subclasses of Resource CarrierSite is callable directly.

Proposal_2 suggests removing CarrierSite only for MFXCarrier, and moving only their origin information into MFXCarrier itself (from my understanding so far).

This means a sudden incosistency of carriers (MFXCarrier vs all other subclasses of Carrier, i.e. PLT, Tube, Trough) has been created.


The confusion to me stems from having used the term “site” with a different meaning.


This is the part I still don’t understand technically:

If if the PlateCarrierSite is defined as it is currently, i.e. the SLAS site onto which a plate is placed, then moving it into the origin of a mfx_carrier_site’s 000 origin will assign the plate to an incorrect location.
(I’m working on some visualisations for this)

Ultimately though I am interested in learning more about the reasoning behind these proposals?

Rather than discussing their implementations in a particular way maybe we can identify and discuss different ways of achieving the same goal.

With proposal_1 I can see the advantages…

  • simplifying instantiation of purpose-built mfx_carriers (erasing the need for multiple lines of .assign_child_resource() would be particularly nice)
  • querying the mfx_carrier instance’s mfx_module configuration at runtime using their model attribute (which I can see being very useful in complex setups)
  • even if the PlateSite of a mfx_module is not a direct child of MFXCarrier we can easily generate a “port”, i.e. a forwarding of the site to the mfx_carrier instance, still enabling
>>> mfx_carrier[0]
MFXModule(location=Coordinate(0, 4, 18), size_z = 78, model="MFX_plate_module_w_pedestal")

…which would enable the index calling of the sites on a mfx_carrier in the same way any other carrier’s sites are called (but now we don’t lose the information of the real mfx_module configuration, because chances are we are testing them out and want to see what works well).

With proposal_2 I am struggling to see advantages yet but see many disadvantages (though I might still be missing information).

I was responding to “to me it does not seem intuitive that something we buy with the name “MFX Module” is now represented as a PlateCarrierSite .” I do not think that that is an issue.

In terms of the tree/resource structure, I think from first principles we arrive at the same thing that is already implemented in venus.

No. The PlateSite (substitute Site with the new word) would have the correct height so that it touches with the carrier. (the site=module location would 000 wrt the carrier)

In a sense. I would conceptualize the assigned modules as taking the same role that CarrierSites do for other carriers. If you look at the definition of a current PlateCarrier I think it makes a lot of sense:

def PLT_CAR_L5AC_A00(name: str) -> PlateCarrier:
  """ Carrier for 5 deep well 96 Well PCR Plates
  Hamilton cat. no.: 182090
  """
  return PlateCarrier(
    name=name,
    size_x=135.0,
    size_y=497.0,
    size_z=130.0,
    sites=create_homogeneous_carrier_sites(klass=PlateCarrierSite, locations=[
        Coordinate(4.0, 8.5, 86.15),
        Coordinate(4.0, 104.5, 86.15),
        Coordinate(4.0, 200.5, 86.15),
        Coordinate(4.0, 296.5, 86.15),
        Coordinate(4.0, 392.5, 86.15),
      ],
      site_size_x=127.0,
      site_size_y=86.0,
      pedestal_size_z=-4.74
    ),
    model="PLT_CAR_L5AC_A00"
  )

create_homogeneous_carrier_sites simply creates a list of CarrierSites (klass) at the requested locations.

The proposal of putting the locations in the initializer would only make sense when having a class for a particular base, and as you point out there are multiple bases. Perhaps a better approach would be to also pass the locations as an argument (through sites):

class MFXCarrier(Carrier):
  def __init__(..., sites: list[ResourceHolderMixin]):
    for site, loc in zip(sites):
      self.assign_child_resource(site, loc)

def MFX_CAR_L5_base(name, modules: list[ResourceHolderMixin]):
  locs = [Coordinate, Coordinate, Coordinate]
  for module, loc in zip(modules, locs): module.location = loc
  return MFXCarrier(sites=modules)

# cross-use "module" and "site", we can fix this later

Again, we wouldn’t do that: the PlateCarrier"Site" would have a z height, and know where on itself to assign the plate when it comes. It knows that through a parameter that points to the origin of the Plate plane.

This is the reason for proposal 2

I think it’s consistent, when considering that the children of all Carriers would be specific resources where you could place your Plates/TipRacks, etc. Right now, to me, the mfxmodule is an outlier because its children (CarrierSite) take resources (modules) that hold the actual resources of interest (plates).

We want to be able to say lh.move_plate(plate, mfx_carrier[0]) and go to the plate site, rather than the place on the mfxcarrier that the platesite is on.

We’ve identified the reason for proposal 2. That is very helpful.
In this light, we can look at what is the minimal change needed to implement the goal.

This sounds like a child_location attribute?

All of this makes proposal_2 not sound like the "removal of MFXModule but simply renaming MFXModule to PlateCarrierSite even though it does not conform with the meaning of the word “site”.

MFXModule already has a child_location attribute. When proposal 1 is implemented it will be called directly from mfx_carrier indexing and can be used in the exact way as desired for move operations, i.e. the goal of proposal 2 would already be fulfilled by implementing proposal 1?

My questions are:

  1. What is the advantage of renaming MFXModule like this?
  2. The current issue with mfx_modules is that they do not have the adaptive plate-to-site behaviour. That is only implemented when assigning a plate to a PlateCarrierSite, a 2D area. The issue with MFXModule is having a plate assigned to its child_location does not actuate this adaptive placement.
    Let’s say we rename MFXModule to PlateCarrierSite and it still has such a child_location where is the placement logic codified?
  3. Related, what additional (so far not addressed) changes will have to be made to the existing PlateCarrierSite definition?
    (We have now found out that it would at least require a currently not existing child_location but that would then also change the placement logic?)

exactly

yes

It’s not just renaming, it’s repurposing one class to do both and remove another class. This makes things simpler.

In the PlateCarrierSite. The child_location Coordinate could be defined as the height of the pedestal, where a non-skirted plate would sit. Then the behavior of sinking could be used for both. (as mentioned: child_location would be 000 for PlateCarrierSites on PlateCarriers, which is their only current use case)

I think this should be all, summarizing what we said above:

  • adding the child_location to PlateCarrierSite, default to 000

  • finding a new word to use instead of “Site” or “CarrierSite”

  • essentially they should be like the current MFXPlateModule but also work for sites on ‘regular’ PlateCarriers

  • removing CarrierSite as children of MFXCarrier, instead assign the ‘modules’ directly as PlateCarrierSite, CarrierSite (for a tip rack), Tilter as Machine, etc.

  • this will give us the sinking behavior for plate carrier sites

  • Removing MFXModule

Thank you for all the explanations, @rickwierenga.

After explaining the goals for these two separate proposals, this is starting to make a lot more sense to me.

Proposal_1 is aiming at adding all the above functionalities (e.g. direct mfx_carrier indexing used e.g. in move operations).

Proposal_2 is aimed, as you said, at simplifying various PLR tasks.
It proposes the generation of a new resource - I now believe this is the one we have been talking about for a while but sometimes called it SinglePlateTakingResource.

This merges the functionalities of the existing resources…

  • MFXModule, i.e. a “box” that takes a child at a prespecified child_location with
  • PlateCarrierSite, i.e. a 2D area that manages correct plate placement logic based on the plate’s dz value vs the site’s pedestal_z_height
    …and afterwards erases these resources as they are.

This new resource is supposed to take the plate-to-site placement logic from the existing PlateCarrierSite and apply it to the child_location of the MFXModule (please correct if this is missing something).


I can see the rational behind doing this now.
But I see this also as an opportunity to not only simplify the mfx carrier system but to fix the broken plate placement issue on all plate-taking sites:

I see “plate-taking site” as the superset of every 2D area that a plate can be placed upon, this includes:

  • PlateCarrierSite (i.e. direct PLT_CAR children) → fixed by creating this special site
  • “plate-taking site” on a MFXPlateModule → broken
  • “plate-taking site” on a heater-shaker machine → broken
  • “plate-taking site” on a temperature control machine → broken
  • “plate-taking site” on a tilter → broken
  • “plate-taking site” on a measurement machine (e.g. plate reader, microscope, …) → broken

(“broken” being defined as “not capable of autocorrecting plate placement of plates with different dz values onto PlateSite’s with different pedestal_z_height values”)

Visually speaking:


…shows that all these resources have “plate-taking sites” on them:

In the absence of a better term I guess they can all be described as PlateHolder which accepts a new Plate onto its PlateSite?

I believe there are pros and cons to both:

  1. proposal_2: erase PlateSite (currently called PlateCarrierSite because erroneously assumed by me to only be relevant to PlateCarriers) and integrate its unique information (i.e. pedestal_z_height) and resulting behaviour into PlateHolder’s child_location directly.
  2. keep PlateSite and PlateHolder separate

proposal_2 is definitely simpler.
But it means that if one exchanges the PlateSite on e.g. a temperature control machine (HHC) from one with a pedestal to one without a pedestal two separate definitions have to be generated, instead of replacing the PlateSite that is handed to the PlateHolder.

I thought it was worth mentioning that there is this option.

But laying this all out changed my mind and I vote for proposal_2 as well - with the caveat that I don’t think “site” should be used for the 3D items shown in the above images, simply because it does not conform with the established meaning of the word and imo increases risk of confusion.
We don’t have to use PlateHolder but since there is a ResourceHolderMixin (though I don’t know what the “Mixin” means) it seems like a possible alternative.

yes exactly!!

I love PlateHolder. We should be able to use this term for these modules, including as chilidren of PlateCarrier.

I don’t think we need a PlateSite as well, it can all be one resource.

This explains it well:

PlateHolder would likely inherit from both Resource and ResourceHolderMixin. Resource would provide the base functionalities, and the Mixin would provide support for placing rotated (along the z-axis) resources onto the holder. PlateHolder itself would add the z sinking behavior.

1 Like

Alright, should we generate an implementation plan:

We could divide proposal_1 and _2 into two separate PRs and build them in parallel (I feel confident in constructing the new PlateHolder quite quickly) or implement everything in one PR?

The most important part is just to only merge when nothing breaks PLR:main.
And before merging we’ll have to send out a warning to make sure users who depend on affected “PLR-hidden”/non-added definitions (i.e. all mfx_modules, potential bespoke carriers, …) have time and instructions to update them.

  • proposal 1
  • proposal 2
1 Like

Awesome, thank you for the hard work. I’ll have a look through everything and give some feedback.

For posterity, this is the PLR Resource Management System before these two PRs:

To make your own version that you can read in detail:
→ cd to pylabrobot/pylabrobot/:
Generate class UML of PLR resources

pyreverse -o dot -p pylabrobot --colorized --max-color-depth 3 -S pylabrobot.resources

Render as PNG or PDF

dot -Tpng classes_pylabrobot.dot -o classes_pylabrobot.png

We’re mostly interested in this corner for the discussion of this thread:

I found it useful to visualise the hierarchy to keep an eye on everything :slight_smile:

Will make more after the coming changes, and think of a simplified version for the docs.

2 Likes