Help with multi dispensing on Hamilton Star

Hello,

I have been using the Hamilton Star to make dilutions and load a 96 well plate for Gator BLI. Unfortunately, I have run into an issue with dispensing different volumes in different pipettes. I created a function to aspirate enough volume for all the wells in a row from a 50ml conical tube and then dispense various amounts across the plate.

This is a simplified version of my code

async def multi_channel_transfer_from_tube(tube_resource, target_plate, column_transfers, tip_resources):
    await lh.pick_up_tips(tip_resources)
    tip_volumes = [0] * 8
    for volumes, column in column_transfers:
        # add volumes to tip_volumes for each channel
        for i, volume in enumerate(volumes):
            tip_volumes[i] += volume
    for i, volume in enumerate(tip_volumes):
        await lh.aspirate(tube_resource, vols=[volume], use_channels=[i])
    for volumes, column in column_transfers:
        await lh.dispense(target_plate[f"A{column}:H{column}"], vols=volumes)
    await lh.discard_tips()

Unfortunately, after calling the function multiple times, channel 7 throws an error that there is too little volume and the pipette cannot “move” to that position.

I tried troubleshooting by using the command STAR.request_volume_in_tip after aspirating and dispensing. What is request_volume_in_tip? Is that motor steps? It doesn’t match the actual volume requested. Also, request_volume_in_tip seems non-linear to the aspirated/dispensed volume in tip. That seems odd if it is geared

Should I be calling a different function for multi dispensing from a tip?

I am not sure how to upload an excel file so I uploaded a table of my troubleshooting. Actual is the response from request_volume_in_tip. Ratio shows the non-linear relationship between volumes expected and actual.

The 3rd and 5th time I called the function the volumes were the same just different plates/columns but the request_volume_in_tip changes for the same volumes. Error happens at the end of the 5th time when trying to dispense 200 ul.

#3
Channel Aspirating Actual Expected Ratio Dispensing (Col 1) Actual Expected Ratio Dispensing (Col 3) Actual Expected Ratio Dispensing (Col 4) Actual Expected Ratio Dispensing (Col 5) Actual Expected Ratio Dispensing (Col 8) Actual Expected
Channel 0 600 1326.6 600 2.211 200 983.1 400 2.45775 200 533 200 2.665 0 533 200 200 0 0 0 0 0
Channel 1 600 1326.6 600 2.211 200 983.1 400 2.45775 200 533 200 2.665 0 533 200 200 0 0 0 0 0
Channel 2 600 1326.6 600 2.211 200 983.1 400 2.45775 200 533 200 2.665 0 533 200 200 0 0 0 0 0
Channel 3 600 1326.6 600 2.211 200 983.1 400 2.45775 200 533 200 2.665 0 533 200 200 0 0 0 0 0
Channel 4 600 1326.6 600 2.211 200 983.1 400 2.45775 200 533 200 2.665 0 533 200 200 0 0 0 0 0
Channel 5 600 1326.6 600 2.211 200 983.1 400 2.45775 200 533 200 2.665 0 533 200 200 0 0 0 0 0
Channel 6 600 1326.6 600 2.211 200 983.1 400 2.45775 200 533 200 2.665 0 533 200 200 0 0 0 0 0
Channel 7 1000 2211.1 1000 2.2111 200 1867.6 800 2.3345 200 1417.5 600 2.3625 200 967.4 400 2.4185 200 517.3 200 2.5865 200 0 0
#4
Channel Aspirating Actual Expected Ratio Dispensing (Col 9) Actual Expected Ratio Dispensing (Col 10) Actual Expected Ratio Dispensing (Col 11) Actual Expected Ratio Dispensing (Col 12) Actual Expected
Channel 0 400 887.2 400 2.218 200 437.1 200 2.1855 0 437.1 200 200 0 0 0 0 0
Channel 1 400 887.2 400 2.218 200 437.1 200 2.1855 0 437.1 200 200 0 0 0 0 0
Channel 2 400 887.2 400 2.218 200 437.1 200 2.1855 0 437.1 200 200 0 0 0 0 0
Channel 3 400 887.2 400 2.218 200 437.1 200 2.1855 0 437.1 200 200 0 0 0 0 0
Channel 4 400 887.2 400 2.218 200 437.1 200 2.1855 0 437.1 200 200 0 0 0 0 0
Channel 5 400 887.2 400 2.218 200 437.1 200 2.1855 0 437.1 200 200 0 0 0 0 0
Channel 6 400 887.2 400 2.218 200 437.1 200 2.1855 0 437.1 200 200 0 0 0 0 0
Channel 7 800 1768.9 800 2.211125 200 1318.8 600 2.198 200 1318.8 600 2.198 200 418.6 200 2.093 200 0 0
#5
Channel Aspirating Actual Expected Ratio Dispensing (Col 1) Actual Expected Ratio Dispensing (Col 2) Actual Expected Ratio Dispensing (Col 3) Actual Expected Ratio Dispensing (Col 4) Actual Expected Ratio Dispensing (Col 5) Actual Expected
Channel 0 600 1326.6 600 2.211 200 876.5 400 2.19125 200 426.4 200 2.132 0 426.4 200 200 0 0 0 0 0
Channel 1 600 1326.6 600 2.211 200 876.5 400 2.19125 200 426.4 200 2.132 0 426.4 200 200 0 0 0 0 0
Channel 2 600 1326.6 600 2.211 200 876.5 400 2.19125 200 426.4 200 2.132 0 426.4 200 200 0 0 0 0 0
Channel 3 600 1326.6 600 2.211 200 876.5 400 2.19125 200 426.4 200 2.132 0 426.4 200 200 0 0 0 0 0
Channel 4 600 1326.6 600 2.211 200 876.5 400 2.19125 200 426.4 200 2.132 0 426.4 200 200 0 0 0 0 0
Channel 5 600 1326.6 600 2.211 200 876.5 400 2.19125 200 426.4 200 2.132 0 426.4 200 200 0 0 0 0 0
Channel 6 600 1326.6 600 2.211 200 876.5 400 2.19125 200 426.4 200 2.132 0 426.4 200 200 0 0 0 0 0
Channel 7 1000 2211.1 1000 2.2111 200 1761 800 2.20125 200 1310.9 600 2.184833 200 860.8 400 2.152 200 410.7 200 2.0535 200 410.7 0

you are running into the problem of the dispensed volume not being the plunger volume on Hamiltons. and unfortunately the mapping is not linear as you point out


there is a correction happening (in venus, and copied that into PLR) for “specified volume → firmware/plunger volume”. unfortunately f(v1+v2+...) ≠ f(v1) + f(v2) + ..., often f(v1+v2+...) < f(v1) + f(v2) + ...

STAR.request_volume_in_tip is the plunger volume

the way to solve this is to add more volume s to the aspiration f(v1+v2+... + s) ≄ f(v1) + f(v2) + ...

the function being used depends on the tip type. this loads a “hamilton liquid class”, that includes a correction function.

let’s say you are using HighVolumeFilter_Water_DispenseSurface_Part (name matching venus. meaning: 1000ul filter tips, water, no blowout/jet mode):

from pylabrobot.liquid_handling.liquid_classes.hamilton.star import HighVolumeFilter_Water_DispenseSurface_Part
print(HighVolumeFilter_Water_DispenseSurface_Part.compute_corrected_volume(1000))
#1028.5
print(sum(HighVolumeFilter_Water_DispenseSurface_Part.compute_corrected_volume(200) for _ in range(5)))
#1055.0

also, people pointed out to me in the past that in a serial dispense the first and last dispense are often cursed and should be done into the aspiration-container or trash.

1 Like

this is done on the backend to provide good parameters

“smart” code like this leads to annoying cases like this, so should generally be avoided. in this case I think it’s the lesser evil compared to having less accurate aspirations/dispenses when making a simple lh.aspirate call

added an option to disable it to make this easier:

Liquid classes also perform differently at different altitudes and humidity, air is a spring and its the plunger for an air displacement system. Most liquid classes were developed in Reno at elevation where its dry as hell. First thing I did was modify that liquid class for our robots.

The results above are really really terrible though, I would not expect that bad from vanilla liquid classes. Begs the question, how are you measuring?

agree that the hamilton liquid classes are not perfect and almost always require fine tuning by the end user (we use a scale for this). but entirely no parameters /volume correction would be even worse.

@cwehrhan how can you tell David’s results are bad? he is only showing target volume and plunger volume, no external metric to compare against

lol ignore that point, I thought he posted actual volume measured :joy:

shows the lower bound for how bad it would be without the default correction


Thank you so much for your reply. I understand overall idea that air is a spring and the environment plays a role, but shouldn’t the correction happen for both the aspiration and dispension? Maybe I don’t quite understand. I could add volume to the aspiration, but I am concerned about building up liquid overtime. Is there a way to reset the plunger with a firmware command? Does this happen when dropping tips? Can you aspirate an air gap between each volume and then dispense so that the plunger ends up within the air gap for each dispense?

My current plan is to buy a trough holder and just take advantage of multi-channel instead of a 50ml tube and multi-dispension. Should take about the same time.

Also, sorry about misleading labeling actual volume and plunger value.

Thanks again! I was going crazy thinking my code was not pipetting the correct amounts.

it does, and it happens correctly when you aspirate and dispense the exact same volume in matching operation. For example, aspirate 200 and dispense 200 or aspirate 200 five times in a row then dispense 200 five times. The problem occurs because the correction function is non-linear (f(v1+v2+...) ≠ f(v1) + f(v2) + ... lis is aspiration, rhs is dispense)

Yes, there is a " Move dispensing drive into Init. position (empty tip)" firmware command for this. This command exists on the PX boards/computers per channel, as opposed to the robot master computer (called C0). For example P1 for the backmost channel (0 in PLR).

We don’t have this wrapped in PLR, but I can add it if you like. The firmware command is this:

await star_backend.send_command(
  module=STARBackend.channel_id(0),  # channel 0
  command="DL",
)

Happens just before pickup actually. So you don’t need to do it manually or worry about moving the plunger up over the protocol.

But again the cleanest way to do it is to compute the corrected volume for the aspiration and dispenses using compute_corrected_volume (see my post above) and ideally to waste the first and last dispense into a waste/the original container.

Yes. I have never done this myself.

I see. I now understand. Out of curiosity, why is it non-linear?

Would it be helpful to develop a function that creates an air gap between each volume? If the plunger movements are non-linear would each volume become inaccurate after the first volume aspirated? How would the volumes be corrected after the first aspirated volume? Would the air gap mess with the volume correction?

Thanks again for your help!

I do not know

I think the air gap would mess with it, but it’s an interesting idea and I do not know for sure. You can use a scale or a plate reader to easily test these hypotheses :thinking: