PLR STAR Backend Arguments Missing

Hi everyone,

Has something changed about the PLR backend commands for aspirate and dispense for STAR machines?

await lh.aspirate(
        tube_list,
        use_channels = use_channels[:no_channels],
        vols=[20]*no_channels,
        hamilton_liquid_classes = [HTF_water_LC]*no_channels,
        aspiration_speed=[20]*no_channels,
        blow_out=[True]*no_channels,
        blow_out_air_volume=[4]*no_channels,
        minimum_height=mm2dmm(tube_list[0].get_absolute_location(z="bottom").z+1)
        )

returns a warning:

c:\users\cmoschner\desktop\pylabrobot\pylabrobot\liquid_handling\liquid_handler.py:292: UserWarning: Extra arguments to backend.aspirate: {'aspiration_speed'}
  warnings.warn(f"Extra arguments to backend.{method.__name__}: {extra}")

→ Looking into STAR.py and its def aspirate() method (the “abstract” backend command) , it is clear that this method does not have an aspiration_speed argument but aspirate_pip (the “true”/direct backend command to the STAR) does.


Explanation tangent:
To iterate for clarity purposes, the command generation stack is:

  • lh.aspirate() - what users are likely to use (across different devices), sends information to…
  • lh.backend.aspirate() / STAR.aspirate() (in this case) - what gives this specific liquid handler (i.e. STAR) higher level information, acts as an intermediate #TODO: need to convert all arguments given to this method to ul and mm units (currently a mess of ul. deci-ul, mm, deci–mm for different arguments), sends information to…
  • lh.backend.aspirate_pip() / STAR.aspirate_pip() (in this case) - what converts the information into the final firmware command, cumbersome to use because it has to be given positional information, hence the layer above to deal with this. #TODO: need to convert all arguments given to this method to deci-ul and deci-mm units as integers or strings with the appropriate length padding.

So I dived into the STAR.aspirate() code and found that even though one cannot give it an aspiration_speed argument, it has to generate one itself and hand it to STAR.aspirate_pip().
It does so via

aspiration_speed = [int(fr * 10) for fr in flow_rates]

flow_rates in turn is specified just above this assignment via

flow_rates = [
      op.flow_rate or (hlc.aspiration_flow_rate if hlc is not None else 100)
        for op, hlc in zip(ops, hamilton_liquid_classes)]

But importantly, flow_rates is not an argument of STAR.aspirate() either.

→ This means that PLR users can only change aspiration speed, i.e. “flow rate during aspiration”, by using hamilton_liquid_classes if I am not mistaken?

If we don’t, then the aspiration speed is always 100 (ul/s).

I have definitely used aspiration_speed as an argument before which makes me wonder what might have happened, or whether I have overlooked an important piece of code somewhere.
→ This has actually just broken a previously functional automation protocol I created that depends on aspiration_speed being different from 100 (ul/s).

Please let me know whether you can spot my mistake.

Also, everything above also applies to dispense().

It is indirectly, via the ops: List[Aspiration] parameter.

@dataclass
class Aspiration:
  """ Aspiration contains information about an aspiration. """

  resource: Container
  offset: Coordinate
  tip: Tip
  volume: float
  flow_rate: Optional[float]
  liquid_height: Optional[float]
  blow_out_air_volume: Optional[float]
  liquids: List[Tuple[Optional[Liquid], float]]

These are created in LH:

    # create operations
    aspirations = [Aspiration(resource=r, volume=v, offset=o, flow_rate=fr, liquid_height=lh, tip=t,
                              blow_out_air_volume=bav, liquids=lvs)
                   for r, v, o, fr, lh, t, bav, lvs in
                    zip(resources, vols, offsets, flow_rates, liquid_height, tips,
                        blow_out_air_volume, liquids)]

Flow rates are given to LH in the flow_rates parameter (uL/s).

If the user does not specify a flow_rate, it will be None in the Aspiration dataclass, in which case STAR prefers to look at the hamilton liquid class, if one exists. If even that fails, a default value of 100 is used. This means that if you specify an hlc and a flow rate, the flow rate takes priority.

The reason this is handled in this way is that flow rates are part of the “standard” (standard.py), and we reasonably expect all LiquidHandlers to be able to control this. You’d only need to specialize parameters for STAR (via backend_kwargs) where they are not part of the standard, eg lld. With flow_rate, there is the additional “complication” of potentially having an hlc to look at for better default values.

This is surprising because aspiration_speed has never been a parameter of LiquidHandler.aspirate or STAR.aspirate. It has always been flow_rates.

1 Like

Thank you!
So it was just me missing an important piece of code - that is an easy fix :sweat_smile:

1 Like

You make me paranoid with the aspiration_speed comment. What was the issue?

No you were right in your previous message:

I am looking through the version control system: 4 days ago it did indeed already show the warning when I added aspiration_speed. Git tracks everything :slight_smile:

I must have overlooked the warning back then, and now that my automation protocol has been broken with the update to Tube (the source of the liquid) looked more carefully at all warning and error messages.

1 Like

:sweat_smile:

wonderful example of using git to track versions of protocols. Imagine living without source control on your protocols!

(check the other thread, I think I can unbreak the tube aspiration)

1 Like

The stuff of my nightmares…

1 Like

A related question to this thread:

How is the hamilton_liquid_classes dictionary key aspiration_over_aspirate_volume implemented?

Tbh I am not a big fan of the concept of over_aspirate_volume but I am familiar with it in old school liquid classes and couldn’t find how the information is conveyed to the machine.

aspiration_over_aspirate_volume is not implemented yet. Is that simply an addition to the aspiration volume?

To my understanding, yes :sweat_smile:

When I first looked into liquid classes I thought it was really silly:
Why not add this volume into a volume correction curve straight away?
How does blow out volume still work when there is extra volume aspirated that should not be dispensed, or does it not?

But usually there is a reason for anything Hamilton does, and we just don’t know it yet.

Then it is not clear to me how to implement this in PLR.

If you want this, could you share two firmware strings, one for asp and one for disp?

To be honest… I really do not want this :joy:

But I am trying to identify backend arguments that are missing to discuss what they do, whether we need them, and how to implement them.

aspiration_over_aspirate_volume was easy to find because it is already in hamilton_liquid_classes but not in any aspiration command (liquid_handler, backend.aspirate, backend.aspirate_pip).

I guess this is the time I share my little (incomplete) helper infographic to the open-source community :slight_smile:

2 Likes

beautiful diagram!

2 Likes

Thank you @ben.

I should mention a small warning with the infographic, it is a bit old now and I found some updates that I never got the time to include:

  • cLLD_mode_2 / pressure is missing (I barely use it)
  • tip volumes in the “tip used” columns are not recommended by me, I found the 1000ul tip is way more versatile and can transfer at least 50 ul very accurately
  • aspiration & dispensation arguments shown are a subset of the possible arguments available that I investigated, from my knowledge there are at least 44 aspiration arguments and 36 dispensation arguments. But I found the ones shown to be of the most interest to me.
  • argument pull_out_distance_transport_air is not just important for cLLD but is always important
  • I added the default values to the dispensation arguments but forgot to add the default values for the aspiration arguments

I still shared this infographic because I still use it quite a bit and I hope others might spot something that is missing or not working as expected when they try out these arguments, and tell us about it :slight_smile: