HPInc / HP-Digital-Microfluidics

HP Digital Microfluidics Software Platform and Libraries
MIT License
2 stars 0 forks source link

Reify off-board locations for pipetting #164

Open EvanKirshenbaum opened 5 months ago

EvanKirshenbaum commented 5 months ago

As presently implemented, the Pipettor is responsible for knowing where to obtain reagents from and where to put products and waste (and any reagents transferred back to source locations).

For the DummyPipettor, this doesn't matter, and for a manual pipettor (#163), we can assume that the user knows what's where (although this facility could be useful to add information to the prompts to make errors less likely).

For the Opentrons OT2, it is assumed that by the config and reagents parameters to its __init__(), it know

  1. what labware is in what slots,
  2. which of this labware is to be used for input and which for output (i.e., for products)
  3. which labware wells are allocated to which reagents (with, possibly, multiple wells for the same reagent) and how much (if any) of reagent is in each well.

This information is passed to the OT2 itself, and its software takes care of keeping track of what's where to satisfy requests, which just talk in terms of reagents and quantities.

This works fine for protocols like combinatorial synthesis, which can run unattended, but it won't work when people want to use an OT2 interactively via the macro language (#147).

Migrated from internal repository. Originally created by @EvanKirshenbaum on Jun 09, 2022 at 11:44 AM PDT.
EvanKirshenbaum commented 5 months ago

This issue was referenced by the following commit before migration:

EvanKirshenbaum commented 5 months ago

What I think we're going to want to do is to reify the notion of a WellPlate as something associated with a Pipettor, where each WellPlate has a number of WellPlateWells, nameable by name (e.g., A2) or XYLoc.

The Pipettor model will want to keep track of what (it believes) is where, update as a MonitoredProperty, and the user or protocol should be able to change the reagent or volume of a WellPlateWell to reflect additions or removals that the system is unaware of (including telling the system about initial conditions).

For the OT2, this means that

  1. Either the OT2 needs to parse the reagents spec and config spec to figure out the initial conditions or, possibly better, the robot needs to tell the system on its first ready call where everything is.
  2. The robot needs to tell the system (on the ready call) what deltas actually happened on the last request.
    • If we assume that everything starts out empty and unallocated, this will handle getting the initial locations as well.
  3. The system needs to tell the robot (probably piggybacking on the next request) about any deltas it's been told of since the last update.
Migrated from internal repository. Originally created by @EvanKirshenbaum on Jun 09, 2022 at 12:03 PM PDT.
EvanKirshenbaum commented 5 months ago

I'm thinking of simplifying this a bit.

Instead of WellPlates and WellPlateWells, we'll just have a PipettingSource, analagous to PipettingTarget, but concrete. It will have

The Pipettor will have the ability to look up PipettingSources by reagent (getting a possibly empty sequence) and name (getting an Optional[PipettingSource]).

In the macro language, you can talk about, e.g.,

source "A2"
source "A2/2" // e.g., A2 at slot 2, but interpreted by the Pipettor
r's source
r's source = "A2"
r's source = 2uL
source "A3"'s contents = 200uL of r1
r's source's volume
Migrated from internal repository. Originally created by @EvanKirshenbaum on Jun 20, 2022 at 10:50 AM PDT.
EvanKirshenbaum commented 5 months ago

Going into the contractor freeze, what's implemented seems stable, and I've merged it into master, even though the Opentrons protocol doesn't transfer source information back and forth.

What I've currently got is a PipettingSource class that has

A Pipettor has

    @abstractmethod
    def perform(self, transfer: Transfer) -> None:
        ...
    @abstractmethod
    def sources_for(self, reagent: Reagent) -> Sequence[PipettingSource]: 
        ...   
    def source_for(self, reagent: Reagent) -> Optional[PipettingSource]:
        ...

where source_for() calls sources_for() and returns

  1. None if sources_for() returns an empty sequence,
  2. the first source in the sequence with max_volume > 0, if that exists, or
  3. the first source in the sequence.

This is all implemented for DummyPipettor, ManualPipettor, and OT2, but the OT2 implementation doesn't yet exchange information with the robot to keep it up to date.

What it does do is add a source to a set of dirty sources if the bounds or reagent change unless it thinks it's receiving changes from the robot, in a synchronized region. The OT2 creates WPWells (a subclass of PipettingSource) for each well in each well plate listed in the config and assigns them according to the reagents specified to __init__().

What I intend to do next is that either on each call or on ready calls (I think each call makes more sense) either side can send a list of sources that need to be updated. This would include the plate, well name, reagent, and quantity.

When replying to a message, the list would be created based on the dirty set, which would then be cleared. When receiving a message, the sources would be updated.

Migrated from internal repository. Originally created by @EvanKirshenbaum on Jun 23, 2022 at 4:57 PM PDT.