How does PyLabRobot handle liquid classes?

Hi, I am very familiar with Venus and their liquid class editor. I was wondering a few things:

  1. Generally how do you specify the liquid class parameters for a given aspiration?
  2. Is there a similar tool to Hamilton Liquid Class editor?

Thanks

1 Like

They actually are quite different.

In VENUS, as you know, a liquid class specifies many parameters, like aspiration flow rate, volume correction, etc. The user has to specify the liquid class using a string.

In PyLabRobot, a liquid class is basically an enumeration of the different types of liquid that one would use (water, DNA, glycerol, etc.) See pylabrobot/abstract.py at main · PyLabRobot/pylabrobot · GitHub. The default is water. Each robot (LiquidHandlerBackend) can use this information about the type of liquid to determine the best aspiration/dispense parameters. Note that this is intended to fill in parameters with value Default, and that user specified parameters should always take precedence. This ‘filling in of defaults’ is done on a parameter-by-parameter basis, so if you specify a flow rate for example, the best volume correction curve will still be found automatically. If no flow rate were specified, that parameter would also have a ‘smart default’.

In the STAR backend specifically, which is the one used for Hamilton robots, the available information is combined to find the best VENUS liquid class. That is done by combining the PLR Liquid Class (say “water”), with other information (such as whether an operation is an aspiration or dispense, whether it’s a jet or surface dispense, etc.). Using this combination, the best VENUS Liquid Class is found (see pylabrobot/hamilton.py at main · PyLabRobot/pylabrobot · GitHub). Currently, no option exists to manually specify a liquid class in STAR, but I could add that if you want.

The venus liquid classes that exist in PyLabRobot can be edited by editing the Python file, but I would not necessarily recommend this. It would be much better if we could just have the user pass a liquid class using a parameter, in case they want to override the automatic selection. Let me know if you need that.

This is not the default behavior, or necessarily recommended, since it complicates robot agnosticism, and ‘plain’ parameters are much simpler and more expressive. So I would recommend setting the parameters in LiquidHandler:

Or, if need be, using backend_kwargs (parameters that are ignored by LiquidHandler and passed along to the backend, so basically the parameters you see here):

If anything here sounds imperfect, I’m happy to discuss alternatives!

Hope that helps!

2 Likes

Interesting! Yes I think there would need to be some flexibility built into this abstraction as when working with these instruments nearly every customer will design and deploy a custom liquid class, even the standard water liquid class needs to be changed. All these liquid classes are effected by humidity and elevation (For Hamiltons), so a lot of the time the standards don’t work if you are not in the exact climate as Reno, where they were developed for Hamiltons.

This is where being agnostic with instruments may get tricky, as they fundamentally have different pipetting channels. Hamilton operates on a air displacement model I am not sure how Opentrons, tecans, etc operate

Along with that Hamilton’s use a “correction curve” to move the actual stepper different increments based on the liquid class’s correction curve:

This correction curve can change based on tips size, liquid, humidity, and elevation. These correction curves are 99% of the time not linear and need to be measured manually (see Hamilton’s Liquid Class Verification Kit LVK)

How should we go about introducing more flexibility? Specifying parameters on LH already overrides the parameters inferred from the liquid class.

Consider the following example:

lh.aspirate(my_plate["A1"], vols=[100], flow_rates=[100])

Let’s think about the following as an alternative/additional implementation:

lc = HamiltonLiquidClass(
  ...
  aspiration_flow_rate=100.0,
  ...
)
lh.aspirate(my_plate["A1"], vols=[100], hamilton_liquid_classes=[lc])

I see two benefits:

  1. The correction curve that is currently implemented would be overridden. There is no way to do that right now, which is wrong.
  2. It become easier to group parameters that are often used together. For example, having two liquid classes defined and conditionally using one over the other would just be a single parameter change. And it’s easier to reuse the parameter groups later.

On the other hand, this makes the API more ambiguous (what’s the suggested way of specifying the flow rate? What should lh.aspirate(my_plate["A1"], vols=[100], flow_rates=[100], hamilton_liquid_classes=[lc]) do?). How would we serialize this? Should LH know about liquid classes, and how is that different from just specifying them as plain parameters? Passing both liquid classes and parameters to the backend would introduce ambiguity in the Standard Form, and I’m very much opposed to that. Specifically regarding #2: what is the benefit over constants at the top of the file?

This leads me to conclude that something like

vol = 100
if HAMILTON:
  vol = correct_hamilton_volume(vol)
vol *= MY_MYSTERIOUS_CORRECTION_FACTOR # load from a config file
lh.aspirate(my_plate["A1"], vols=[vol], flow_rates=[100],
  do_not_correct_volume=True) # change param name obviously

is much better.

Just a stream of consciousness that I would love to discuss. Happy to have my mind changed.tr

These correction curves are 99% of the time not linear and need to be measured manually (see Hamilton’s Liquid Class Verification Kit LVK)

That seems super useful and there should definitely be a PyLabRobot integration. Do you have access to one?

Hamilton’s LVK looks like a gravimetric scale with some appreciable precision. Can we build our own gravimetric or colorimetric calibration tools that are hardware-agnostic? (use your own usb scale or plate reader)

If you have a device that connects, this must be possible (would obviously require a baseline too). (One plate reader is already integrated.) That would be an amazing PLR-based app!

1 Like

Think we can get this working. I’ll update you when we need to create new liquid classes in PLR (hopefully soon…)

1 Like

@rickwierenga we’ve finally run into liquid class issues blocking us from using pylabrobot in production, so we’re digging into the Hamilton LVK scale we have & seeing if we can make a tiny notebook to help with physical test cases for determining proper liquid classes

4 Likes

Hi @ben,
Please let me know if you would like us to verify any LVK-based PLR implementations. We just got the Hamilton LVK to generate highly optimised liquid classes and we are very interested in a programmatic interaction with it.

Hi everyone,

Have there been any updates on the HamiltonLiquidClass?

I am still unsure what the difference between a VENUS “liquid class” and a PLR HamiltonLiquidClass is:

What does “enumeration of different types of liquid” mean?

Looking at the definition in /pylabrobot/liquid_handling/liquid_classes/hamilton/base.py…

class HamiltonLiquidClass:
  """ A liquid class like used in VENUS / Venus on Vantage. """

  def __init__(
    self,
    curve: Dict[float, float],

    aspiration_flow_rate: float,
    aspiration_mix_flow_rate: float,
    aspiration_air_transport_volume: float,
    aspiration_blow_out_volume: float,
    aspiration_swap_speed: float,
    aspiration_settling_time: float,
    aspiration_over_aspirate_volume: float,
    aspiration_clot_retract_height: float,

    dispense_flow_rate: float,
    dispense_mode: float,
    dispense_mix_flow_rate: float,
    dispense_air_transport_volume: float,
    dispense_blow_out_volume: float,
    dispense_swap_speed: float,
    dispense_settling_time: float,
    dispense_stop_flow_rate: float,
    dispense_stop_back_volume: float,
  ):

…and the use case from above…

…they appear almost identical, i.e. modifying the aspiration and dispense parameters to achieve the volume that is desired.

Furthermore, there are over 450 HamiltonLiquidClasses defined in /pylabrobot/liquid_handling/liquid_classes/hamilton/base.py…

like…

star_mapping[(300, False, True, True, Liquid.WATER, True, False)] = \
StandardVolumeFilter_Water_DispenseJet_Part = HamiltonLiquidClass(
  curve={300.0: 313.5, 0.0: 0.0, 100.0: 110.2, 20.0: 27.2},
  aspiration_flow_rate=100.0,
  aspiration_mix_flow_rate=100.0,
  aspiration_air_transport_volume=5.0,
  aspiration_blow_out_volume=0.0,
  aspiration_swap_speed=2.0,
  aspiration_settling_time=1.0,
  aspiration_over_aspirate_volume=0.0,
  aspiration_clot_retract_height=0.0,
  dispense_flow_rate=180.0,
  dispense_mode=2.0,
  dispense_mix_flow_rate=1.0,
  dispense_air_transport_volume=5.0,
  dispense_blow_out_volume=0.0,
  dispense_swap_speed=1.0,
  dispense_settling_time=0.0,
  dispense_stop_flow_rate=150.0,
  dispense_stop_back_volume=10.0
)

Does this mean the above suggestions have been implemented and are operational?

If yes, can we expand these liquid classes to incorporate all the functionalities of the aspirate and dispense functions, i.e. expand the HamiltonLiquidClass’s capabilities beyond those of VENUS’ liquid classes towards what the machines are truly capable of?

E.g. the above example of a HamiltonLiquidClass does not incorporate information like immersion_depth, immersion_depth_direction or surface_following_distance.
Since the immersion_depth into a liquid directly affects how much liquid is being pushed into the pipette tip during immersion into the liquid, this is just one important “liquid handling factor” that would be useful to incorporate into the HamiltonLiquidClass.

Nothing substantial after my last post.

Just names of actual liquids, like water, ethanol, etc.

Yes, the posts above were just an explanation of things that were already implemented at that time.

I provided the Hamilton liquid classes as a convenient way to transfer a ‘bag of parameters’ from venus to PLR, but preferably we just expose all of these parameters natively in PLR without relying on Hamilton liquid classes. I don’t want to expand the definition of Hamilton liquid classes in PLR, unless of course it makes them closer to VENUS.

For sure. As you wrote, these serve the same purpose as the Hamilton liquid classes but ‘expand’ on them. As my current project progresses, I’ll think about ways to conveniently group these parameters in a PLR-friendly way.

In regards to the correction curve:

HamiltonLiquidClass(
  curve={300.0: 313.5, 0.0: 0.0, 100.0: 110.2, 20.0: 27.2},
  aspiration_flow_rate=100.0,
...

How is this curve implemented, i.e. what happens when an aspiration is called with a volume which is not equivalent to any of the keys of the dictionary?

Is there some form of interpolation happening in the background, if yes, in which file and is it just linear interpolation, is there a model fitted that calculates the corrected volume for any target volume?
(I couldn’t find any)

Linear interpolation between the points, and maintaining the ratio of target/actual volume when smaller or bigger than the edge points. See HamiltonLiquidClass.compute_corrected_volume, liquid_handling/liquid_classes/hamilton/base.py.

1 Like

Just seeing the activity on this! Not sure if I would include immersion_depth, imersion_depth_direction or surface_following_distance in the liquid class, those are dispense parameters no? I think you are going to have a very hard time quantifying how immersion depth effects pipetting. I would doubt it would be statistically significant, unless you have data on this? I have not seen this before!

I think it’s also important to note that for different types of aspirations/dispenses not all of those parameters above are used. Stop back volume for examples is not available for use on a Jet Empty dispense!

Ultimately I think some kind of universal liquid class editor will need to be made in PLR, custom liquid classes are essential liquid handling operations. Every company makes their own, especially for Hamiltons as they use air displacement pipetting

2 Likes