-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Build ztouch Probing Function #260
Conversation
This information depends on the tip that is currently mounted, which is only known by LiquidHandler. I tried to make STAR as stateless as possible. The tips used are passed to say In this case, a call is made directly to the backend where this information is not available. One way to do this is to go through LH, but that seems suboptimal because this functionality is not generally applicable. The alternative seems to be passing the parameter directly the backend (if we want to keep STAR with minimal state). We can add a convenience feature to LiquidHandler for getting the length of the currently mounted tips if helpful. |
I am not sure. Do you think a convenience feature would be clearer than my example from above?:
measured_z_touch = await lh.backend.ztouch_probe_z_height_using_channel(
channel_idx=0,
tip_len=lh.head[0].get_tip().total_tip_length,
channel_speed=6.0, start_pos_search=250.0,
detection_limiter_in_PWM=0, push_down_force_in_PWM=0,
move_channels_to_save_pos_after=False
)
I guess I understand the reason for statelessness in the STAR backend. Keeping the I don't see a path around this either and we'll just have to hand the backend the tip_length. ...what I don't understand is how |
i had to add lowest_immers_pos=100, because unfortunately it seems i have to update the channel firmware
|
not necessarily, up to you
Very much by design. LiquidHandler is the state manager.
I guess it knows which tip is mounted, and the length is defined with TT earlier? (also, only tangentially related, when discarding tips to trash it will just move until it feels resistance, and then discard. but you don't get a reading from that) |
ztouch_probe_z_height_using_channel should then also know this, but maybe we can't assume all the firmware commands are at the same level/written at the same time |
What is your return from? await lh.backend.send_command(
module="PX",
command="RF" # request firmware
)
I completely agree; I'm not sure how the firmware keeps memory of the tip but I think you are right that we cannot assume different firmware commands are written the same way. Btw, what do people think about the name "ztouch_probing"? My alternative suggestion is "force_probing" but since I don't know the mechanism of action behind the force sensing I was hesitant to use it, and "ztouch_probing" is a reference to the |
'P1RFid0012rf4.0S f 2020-07-31' I like ztouch_probing! |
This seems to be discrepancy between the two commands which might or might not be intended. Basically the tip length would also be available through |
Slightly off-topic: Having the LiquidHandler as state manager is probably the right way to go. At the same time the |
I'm not sure I understand what you mean, @jrast: To my understanding, it does not enable "request tip_length on channel n", which is what we'd need to remove the attribute |
This is an amazing idea! It would enable a whole array of automated requests, and solve the To finish this PR: |
Sorry, this was not clear. I was talking about the Firmware which would have access to the tip length by looking at what's in the tip definition table written with the |
@jrast, I had a bit of an epiphany and invented a way for the STAR to measure the tip length on a specified channel, even though, as you confirmed, it is impossible to request/query that information from the firmware, and made it into a standalone function for us: async def request_tip_len_on_channel(
self,
channel_idx: int, # 0-based indexing of channels!
) -> float:
"""
Measures the length of the tip attached to the specified pipetting channel.
Checks if a tip is present on the given channel. If present, moves all channels
to THE safe Z position, 334.3 mm, measures the tip bottom Z-coordinate, and calculates
the total tip length. Supports tips of lengths 50.4 mm, 59.9 mm, and 95.1 mm.
Raises an error if the tip length is unsupported or if no tip is present.
Parameters:
channel_idx (int): Index of the pipetting channel (0-based).
Returns:
float: The measured tip length in millimeters.
Raises:
ValueError: If no tip is present on the channel or if the tip length is
unsupported.
"""
# Check there is a tip on the channel
all_channel_occupancy = await self.request_tip_presence()
if all_channel_occupancy[channel_idx]:
# Level all channels
await self.move_all_channels_in_z_safety()
known_top_position_channel_head = 334.3 # mm
fitting_depth_of_all_standard_channel_tips = 8 # mm
unknown_offset_for_all_tips = 0.4 # mm
# Request z-coordinate of channel+tip bottom
tip_bottom_z_coordinate = await self.request_z_pos_channel_n(
pipetting_channel_index=channel_idx
)
total_tip_len = round(known_top_position_channel_head - (
tip_bottom_z_coordinate - fitting_depth_of_all_standard_channel_tips - \
unknown_offset_for_all_tips
),1)
if total_tip_len in [50.4, 59.9, 95.1]: # 50ul, 300ul, 1000ul
return total_tip_len
else:
raise ValueError(f"Tip of length {total_tip_len} not yet supported")
else:
raise ValueError(f"No tip present on channel {channel_idx}") Does anyone have an idea where the I identified them empirically here with the following measurements: - 50ul: 334.3 - 292.3 = 42.0 + 8 = 50.0 mm -> 50.4 mm (actual tip_50ul_len)
- 300ul: 334.3 - 282.8 = 51.5 + 8 = 59.5 mm -> 59.9 mm (actual tip_300ul_len)
- 1000ul: 334.3 - 247.6 = 86.7 + 8 = 94.7 mm -> 95.1 mm (actual tip_1000ul_len) They are also part of the tip definitions. |
Where should this go though? It is fundamentally a property of LH.
I love this! if total_tip_len in [50.4, 59.9, 95.1]: # 50ul, 300ul, 1000ul
return total_tip_len
else:
raise ValueError(f"Tip of length {total_tip_len} not yet supported") why not just |
There's almost always a code solution :) Imagine the benefit to Hamilton: Caveat: to get this information the channel_head will move to the safe z-height=334.3 mm (pretty much the opposite of a danger to the machine, but creates a little bit of a bouncing movement)
Because we don't know any potential offset for other tips. I could only measure the offset for 50ul, 300ul, and 1_000ul Hamilton CO-REII tips. (standard teaching needles are simply 300ul tips in terms of their definition) This way people who do want to use these tips have the ability to simply add the corresponding tip_len - after empirical validation - to this list. (Though I would not recommend trying slim fit tips from a design perspective because of the above mentioned buckling issue, and wide-bore tips are unlikely to reach the very bottom of narrow cavities, e.g. PCR_wellplate well bottoms) |
So to catch up on some of the questions:
I can't say for sure on the STAR platform. I know of some internal position offsets / shifts on the Vantage / STAR V platform which are related to the CORE-I / CORE-II (tip coupling mechanisms) change. On Vantage there should also be a easier way to get the tip length of the mounted tip, when I find the code snipped I can provide some guidance here.
I can confirm this: Hamilton has currently no means to update (or access) the device remotly.
Good question... And if the state of the LH is exposed, the next question would be "what is considered part of the state"? |
ec230b5
to
ca55114
Compare
how would we quantify accuracy? can we use the PLR model as a ground truth? (precision is easy to measure) |
ohh wow, what I would say though, yes, 50ul tips are the softest and buckle quite easily (even though I haven't seen them being crushed likes this before.
The question here is Where does the PLR model come from? Some resource dimensions are not realistically measurable by hand, e.g. the material_z_thickness of a well. This means we might not be able to compare the ztouch_probing measurements to a PLR model because there is no PLR model yet, and the point of ztouch_probing is to generate a PLR model. -> My alternative: use the metal teaching needle that all machines already have: |
One update I was thinking of after our last conversation that I would like to implement before we merge: adding an "RF" (request firmware) command at the beginning of the ztouch_probing function. Raise an error if firmware <2022, explaining that their machines PX module has to be upgraded to enable this functionality. |
defaults we get tips from a variety of sources, and perhaps these are just very very old 🤷♂️
playing with fire (steel) anything to do before merging? |
ab37585
to
cfdd227
Compare
4f75a03
to
44c2617
Compare
Really cool change that will make calibrating labware a lot easier! |
Hi everyone,
In this PR I am integrating a way of probing the height of non-conductive materials, e.g. 96-well plates, and standard tubes, to enable their automated definition generation.
The Problem
In PR #69 I integrated the function
probe_z_height_using_channel
which uses Hamilton STAR(let) channels' capacitive Liquid Level Detection (cLLD) capability to detect the z-height of any conductive material.Though very useful and highly precise, this function does not allow z-height probing of the resources/labware we have to most commonly generate definitions for, plates and tubes, because these resources are made of highly insulating/non-conductive plastic.
Task definition: We therefore urgently need a function that uses only the machine available (STAR(let)) to identify the z-height of these non-conductive materials.
PR Content
Here I ...
STAR.ztouch_probe_z_height_using_channel(channel_idx: int, tip_len: float)
: this function uses the same functionality thataspirate
anddispense
withlld_mode=4
, i.e. z-touchoff, has. In other words, the channel is conducting a controlled "crash" of the tip, which triggers some form of force-sensor in the Z-drive.This enables the probing of non-conductive materials, including plates and tubes.
STAR.probe_z_height_using_channel()
toSTAR.clld_probe_z_height_using_channel()
, to clearly distinguish these two probing functions,The current version of the new function:
Next Steps
I have added one
TODO
toSTAR.ztouch_probe_z_height_using_channel()
:At the moment, users have to directly declare the
total_tip_length
to thetip_len
argument.This can easily be automated at runtime using:
i.e. by accessing the
total_tip_length
via the liquid_handler.I would like to remove this attribute altogether and have the function retrieve the
total_tip_length
information by itself at the STAR level, rather than the liquid_handler level.Notes
How accurate is
STAR.ztouch_probe_z_height_using_channel()
?I performed an array of tests in which I ztouch_probed conductive materials and compared their measured z-height against the same positions clld_probed height:
I still need to write the complete analysis up, but in short: ztouch_probing deviates between 0 (minimum) - 320 (maximum) micrometers, in my hands.
This is in absolute terms.
Does this matter?
Whether this deviation matters depends on what you are trying to measure:
If you try to identify absolute locations, it likely matters, and I recommend using clld_probing in these cases - if possible, because the material is conductive (e.g.
PlateCarrierSite
mapping).However, if you try to identify relative positions you can use ztouch_probing for measuring both/multiple locations of interest with low to no concern about this deviation:
e.g. all
Container
s require defining theirmaterial_z_thickness
see PR #183; example:

This attribute of
Container
is very difficult to actually measure (e.g. because calipers cannot enter the container, ...).But using ztouch_probing one can identify the well_cavity_bottom, then remove the plate, and use ztouch_probing again to identify the height of the site below.
If the
PlateCarrierSite
has a pedestal, the difference between these two measurements is thematerial_z_height
... measured, and therefore automatable.Since both measurements can be assumed to experience any absolute deviation in the same way, their difference holds true, regardless of absolute deviation between ztouch_probing and clld_probing :)
Does it damage the channel?
There might be some concern about damaging the channel when using controlled "crashes" to probe resources.
Nothing can guarantee no damage.
But as long as the force and detection attributes are not pushed to their extremes (i.e.
detection_limiter_in_PWM
&push_down_force_in_PWM
), channel damage is deemed very unlikely.I defaulted these values to 1 and 0 respectively, making the force-sensing extremely responsive and essentially generating no noticeable force, to decrease damaging risks in the odd case that anyone accidentally finds these functions and doesn't read this PR/the documentation pages (work in progress).
What tips can you use?
All standard tips, i.e. 10 ul, 50 ul, 300 ul, 1000 ul, work using ztouch_probing, and you are not limited to using their conductive version but might also use their transparent or needle/metal version.
That being said, there might be a slight change in accuracy depending on tip choice:
Lower-volume tips are thinner and can buckle under the ztouch_probing force generated which might give a lower z-height than real.
I therefore switched to using a teaching needle for probing (see image above of probing a PCR 96-well plate).
However, if one does not have needles/metal tips available and requires a quick measurement it is possible to successfully use standard tips, taking a mental note of the caveats discussed here.
Please let me know whether you have any suggestions on how to further improve these two functions.
I expect these updates to save us all time, and to lead to highly robust PLR Resource Library contributions.
Happy automation 🦾