Probe y-position of resource using channel with cLLD
Hi everyone,
In this PR I created a new STAR method which enables the detection of a conductive item in the y-dimension.
Background
In biowetlab automation we constantly have to measure various resources. Automating this measurement taking provides many advantages, including...
- increase in robustness of our definition,
- dynamic updates of prior definitions based on new deck states or variation in our physical resource,
- accelerated labware definition, ...
In PR#69 - Probe z height using channel and PR#260 - Build ztouch Probing Function I created two functions which enable automated measurements of items in the z-dimension:
STAR.clld_probe_z_height_using_channel- uses capacitance-sensing to detect the item on deckSTAR.ztouch_probe_z_height_using_channel- uses force-sensing to detect the item on deck
PR Content
Here I create STAR.clld_probe_y_position_using_channel:
class STAR(HamiltonLiquidHandler):
...
async def clld_probe_y_position_using_channel(
self,
channel_idx: int, # 0-based indexing of channels!
probing_direction: Literal["forward", "backward"],
start_pos_search: Optional[float] = None, # mm
end_pos_search: Optional[float] = None, # mm
channel_speed: float = 10.0, # mm/sec
channel_acceleration_int: Literal[1, 2, 3, 4] = 4, # * 5_000 steps/sec**2 == 926 mm/sec**2
detection_edge: int = 10,
current_limit_int: Literal[1, 2, 3, 4, 5, 6, 7] = 7,
post_detection_dist: float = 2.0, # mm
) -> float:
"""
Probes the y-position at which a conductive material is detected using
the channel's capacitive Liquid Level Detection (cLLD) capability.
This method aims to provide safe probing within defined boundaries to
avoid collisions or damage to the system. It is specifically designed
for conductive materials.
Args:
channel_idx (int): Index of the channel to use for probing (0-based).
The backmost channel is 0.
probing_direction (Literal["forward", "backward"]): Direction to move
the channel during probing. "forward" increases y-position,
"backward" decreases y-position.
start_pos_search (float, optional): Initial y-position for the search
(in mm). Defaults to the current y-position of the channel.
end_pos_search (float, optional): Final y-position for the search (in mm).
Defaults to the maximum y-position the channel can move to safely.
channel_speed (float): Speed of the channel's movement (in mm/sec).
Defaults to 10.0 mm/sec (i.e. slow default for safety).
channel_acceleration_int (Literal[1, 2, 3, 4]): Acceleration level,
corresponding to 1–4 (* 5,000 steps/sec²). Defaults to 4.
detection_edge (int): Steepness of the edge for capacitive detection.
Must be between 0 and 1023. Defaults to 10.
current_limit_int (Literal[1, 2, 3, 4, 5, 6, 7]): Current limit level,
from 1 to 7. Defaults to 7.
post_detection_dist (float): Distance to move away from the detected
material after detection (in mm). Defaults to 2.0 mm.
Returns:
float: The detected y-position of the conductive material (in mm).
Raises:
ValueError:
- If the probing direction is invalid.
- If the specified start or end positions are outside the safe range.
- If no conductive material is detected during the probing process.
"""
This method enables the detection of conductive items in the y-dimension. I have added various features to make this method as safe as possible - please report any issues that you might encounter.
Use
The method is designed with intuition and simplicity in mind:
Basic mode:
- Move the channel in the x-, y- and z-position you want.
(I recommend
await lh.backend.prepare_for_manual_channel_operation(channel=7)before this to give your channel maximal y-space to move in) - call:
await lh.backend.clld_probe_y_position_using_channel(
channel_idx=7,
probing_direction= "forward", # forward | backward
- the channel will move in the direction you tell it to (at a default speed of 10 mm/sec, i.e. very slow for safety reasons; 1 - 370 mm/sec is theoretically possible).
- the method returns the y-position of the item it detected.
The method has various safety features to avoid collisions. e.g. it computes the position of the previous and next channels, and the front/back of the arm to generate a "safe y space" in which it can move without crashing into another channel. Nevertheless, PLR takes no responsibility for damage to any machine or material, so please use caution when using this function.
Next steps
With automated measurements of items on a deck in the y dimension (conductive materials only) and z dimension (conductive & non-conductive materials), the logical step would be to search whether we can achieve the same in the x dimension.
I will continue keeping an eye out for such functionality.
However, the x-drive appears to be controlled separately from the capacitance sensor (which is the same controller as the y and z drive), and it is therefore questionable whether a clld_probe_x_position method is hardware/firmware-achievable.
Please report any issues with and/or suggestions for improvement of STAR.clld_probe_y_position_using_channel() in this PR so we compile the information here.
Happy automation 🦾
Small clarifications to the method:
- It is important to note that the returned value represents the y-value of the lowest possible channel part:
- if a tip is attached to the channel -> measures the y-position of the bottom of the tip -> WARNING: tips are cone(ish)-shaped; if the tip touches a conductive material at a higher point than its bottom, the returned value is still for the bottom and therefore it is not the true y-position of the item that triggered the capacitance sensor.
To avoid this complication I recommend:
- Use z measurement to detect top of the item you want to measure first.
- Then move channel away from the item in the y dimension.
- Move channel to 1-2 mm below the items top z height.
- Perform y-probing from this position (1-2 mm of most tips should be almost identical to the true returned y-position)
- if no tip is attached the channel head can still be used by itself but I do not know whether the returned y-position represents the center of the channel-head or its edge/circumference -> this represents a y-difference of 4.5 mm!
- if a tip is attached to the channel -> measures the y-position of the bottom of the tip -> WARNING: tips are cone(ish)-shaped; if the tip touches a conductive material at a higher point than its bottom, the returned value is still for the bottom and therefore it is not the true y-position of the item that triggered the capacitance sensor.
To avoid this complication I recommend:
- You can use various tips for this method, but I found the STAR(let)-inbuilt teaching needles to be particularly reliable and easy to use.
- Note the limitations of a >8-channel STAR system with this method: More than 8 channels results in some channels not being able to reach every position on the deck -> you have to use separate channels for y-probing in the "front" vs "back" areas of the machine.
- I mentioned above that I changed the default search speed of the method to 10 mm/sec for safety reasons (for information purposes: the firmware default speed is ~277 mm/sec - do not use that speed!). I should mention that this speed does not only provide a higher safety tolerance but it also makes the measurement more accurate: higher speeds might result in the channel having already moved further than the material (i.e. into the material / bending the tip), which would result in an inaccurate y-position measurement.
Small clarifications to the method:
It is important to note that the returned value represents the y-value of the lowest possible channel part:
....
Just some inputs on this:
- I recommend to always use teaching needles! They are precise, do not bend, and have a cylindrical section at the bottom, therefore less issues regarding the influence of Z-pos to Y-pos. But you still need to adjust for the diameter of the needle!
- Probing without tip is also possible and the same applies: Need to correct for the diameter of the stop-disk!
- Just to repeat what you have allready said: Always use a slow speed! It prevents damage and improves accuracy!
Thank you @jrast, these are some excellent points!
But you still need to adjust for the diameter of the needle!
Do you know whether there is some (perhaps machine-accessible) database of the diameters of the bottom of Hamilton tips that we could integrate into PLR for this purpose? To my knowledge, the machines only know the diameter of the tip "collar" / top of the tip, which is always 8 mm for 1000ul channels. i.e. we are missing the information of the more variable bottom of the tip which we'd need to make y-probing more accurate.
-> Once we know the value of tip_bottom_diameter we can just modify clld_probe_y_position_using_channel() to
return material_y_pos = y_pos_at_which_capacitance_sensor_triggered + `tip_bottom_diameter` / 2 # if the channel was sent "foward"
OR
return material_y_pos = y_pos_at_which_capacitance_sensor_triggered - `tip_bottom_diameter` / 2 # if the channel was sent "backward"
I'm not aware of such a database, at least not a public accessible one. Maybe ask in the labautomation forum, there are a couple of Hamilton guys, maybe they have more information available than me.
Okay, I've got the measurements of the tip_bottom_diameter that we need.
But I'm not sure what's the best way to integrate them?
Directly requiring inputting them into this class method seems very error prone - measuring the tip_bottom_diameter is not super precise.
Idea: 💡 Each tip_bottom_diameter is directly mapped to its tip's length.
What do you think about requiring tip Len as input, and this class method contains a dict that maps the known tip_bottom_diameter-to-tip_len relationship?
If tip_len is None, no problem, that's what I made the measure tip_len method for 😅
This would be very similar to how the ztouch method handles tip_len.
Pushing the proposed integration tomorrow but wanted to hear your guys opinion on the design choice first.
how about storing this information on the tip class, and just passing the class?
how about storing this information on the tip class, and just passing the class?
This! It's 100% a property if the resource and any other variant would be strange.
Sadly the LH Backend does not know which tip is present (only the LH itself knows), so the Tip Instance must be passed to the method.
Sadly the LH Backend does not know which tip is present (only the LH itself knows),
is it time to change this? i ran into a context where i need this as well
What does that look like in practice?
We cannot give this method a 'class'. We can only give it an instance of a class, i.e. an object.
And then we have to ask... Which instance of a tip are we giving it?
And hopefully we don't make a mistake and give it the wrong Tip instance.
If the backend would have information regarding deck state, including what tip instance is currently attached, then I agree, it would make things a lot easier.
But that is not the case yet.
That's why I think making the robot actually figure out what it doesn't know by itself is the safest option.
Though I like the idea of storing and using all information in the Tip class itself. That way we could also add a class attribute for whether the tip is conductive. And write in checks for it.
Let's move this to a topic on discuss.pylabrobot? It's off-topic for this PR.
It's off-topic for this PR.
How so? Finding a solution for this is the key to finishing this PR, even if it is just a temporary one we upgrade later?
Let's move this to a topic on discuss.pylabrobot? It's off-topic for this PR.
i just went ahead (https://github.com/PyLabRobot/pylabrobot/pull/361), it seemed like a good idea
It's off-topic for this PR.
How so? Finding a solution for this is the key to finishing this PR, even if it is just a temporary one we upgrade later?
Sorry, off-topic was not what I meant. It's more like "this does not only affect this addition / PR, but might also affect other possible features."
But with #361 @rickwierenga already implemented this, which seems a good idea for the moment.