Skip to content

Commit c0b63bf

Browse files
authored
STAR surface following tutorial update (#742)
1 parent 07a0821 commit c0b63bf

File tree

2 files changed

+77
-49
lines changed

2 files changed

+77
-49
lines changed

docs/user_guide/00_liquid-handling/hamilton-star/surface-following.ipynb

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
"\n",
1010
"Surface following is a feature on Hamilton liquid handling robots that makes the pipette tip follow the surface of a liquid when aspirating (going down) or dispensing (going up).\n",
1111
"\n",
12-
"When using automatic surface following, the robot will automatically move the Z position of the pipette tip based on a user specified value. The amount of surface following required can be computed by subtracting the liquid level before and after each aspiration or dispense. PyLabRobot can do this automatically when the height<>volume functions for the given containers are defined. You can also specify the liquid surface following distance manually.\n",
12+
"When using surface following, the robot will automatically move the Z position of the pipette tip the user-specified distance. The amount of surface following required can be computed by comparing the liquid level before and after each aspiration or dispense. PyLabRobot can do this automatically when the height<>volume functions for the given containers are defined. You can also specify the liquid surface following distance manually.\n",
1313
"\n",
14-
"It is useful to start the surface following only at the liquid level, so it is recommended to use [liquid level detection](./star_lld) with the surface following feature. (See below). VENUS also supports this.\n",
14+
"It is useful to start the surface following only at the liquid level, so it is recommended to use [liquid level detection](./star_lld) with the surface following feature. (See below for syntax, which differs from the LLD tutorial). VENUS also supports surface following while doing LLD.\n",
1515
"\n",
16-
"In PLR, when we have LLD + automatic surface following, we can go beyond VENUS by computing the surface following amount based on the precise location of liquid inside the container. This is necessary because the surface following amount is not only a function of the volume of liquid aspirated or dispensed, but also of the location of liquid inside the container (see below). By doing liquid level detection first to get the precise liquid level, we can use that to compute the surface following amount based on the requested volume _and_ location of liquid inside the container.\n",
16+
"In PLR, when we have LLD + automatic surface following, we can go beyond VENUS by computing the surface following amount based on the precise location of liquid inside the container. This is necessary because the surface following amount is not _just_ a function of the volume of liquid aspirated or dispensed, _but also_ of the location of liquid inside the container (see below). By doing liquid level detection first to get the precise liquid level, we can then use that liquid level height to compute the surface following amount based on the requested volume _and_ location of liquid inside the container.\n",
1717
"\n",
1818
"![](./img/surface_following/surface_following_distance.svg)"
1919
]
@@ -28,7 +28,7 @@
2828
},
2929
{
3030
"cell_type": "code",
31-
"execution_count": 2,
31+
"execution_count": 1,
3232
"id": "d3f86a7d",
3333
"metadata": {},
3434
"outputs": [],
@@ -64,7 +64,7 @@
6464
},
6565
{
6666
"cell_type": "code",
67-
"execution_count": 3,
67+
"execution_count": 2,
6868
"id": "5e9d4e1a",
6969
"metadata": {},
7070
"outputs": [],
@@ -78,12 +78,12 @@
7878
"id": "aebdc554",
7979
"metadata": {},
8080
"source": [
81-
"You can probe the liquid height first using liquid level detection, and then use automatic surface following for subsequent aspirations and dispenses as follows:"
81+
"You can probe the liquid height first using liquid level detection (capacitive), and then use automatic surface following for subsequent aspirations and dispenses as follows:"
8282
]
8383
},
8484
{
8585
"cell_type": "code",
86-
"execution_count": 4,
86+
"execution_count": 3,
8787
"id": "d4858585",
8888
"metadata": {},
8989
"outputs": [],
@@ -113,12 +113,12 @@
113113
"id": "0b3ac98c",
114114
"metadata": {},
115115
"source": [
116-
"You can also pass the liquid height directly to the aspiration and dispense methods, and still use automatic surface following:"
116+
"You can also pass the liquid height directly to the aspiration and dispense methods, and still use automatic surface following. This can be useful when you cannot use LLD."
117117
]
118118
},
119119
{
120120
"cell_type": "code",
121-
"execution_count": 6,
121+
"execution_count": 4,
122122
"id": "ffde0c4c",
123123
"metadata": {},
124124
"outputs": [],
@@ -152,12 +152,12 @@
152152
"id": "aee70228",
153153
"metadata": {},
154154
"source": [
155-
"To manually specify the surface following amount, you can use the `surface_following_distance` backend kwarg of the aspiration and dispense methods. For example, to aspirate 100 µL with a surface following amount of 2 mm above the detected liquid height:"
155+
"To manually specify the surface following amount, you can use the `surface_following_distance` backend kwarg of the aspiration and dispense methods. For example, to aspirate 100 µL with a surface following amount of 2 mm starting at the detected liquid level:"
156156
]
157157
},
158158
{
159159
"cell_type": "code",
160-
"execution_count": 7,
160+
"execution_count": null,
161161
"id": "e3e205b7",
162162
"metadata": {},
163163
"outputs": [],
@@ -167,14 +167,14 @@
167167
" wells,\n",
168168
" vols,\n",
169169
" probe_liquid_height=True,\n",
170-
" surface_following_distance=[2] * len(wells), # mm down from liquid_height\n",
170+
" surface_following_distance=[2] * len(wells), # mm down after finding liquid\n",
171171
" )\n",
172172
"\n",
173173
" await lh.dispense(\n",
174174
" wells,\n",
175175
" vols,\n",
176176
" probe_liquid_height=True,\n",
177-
" surface_following_distance=[2] * len(wells), # mm up from liquid_height\n",
177+
" surface_following_distance=[2] * len(wells), # mm up after finding liquid\n",
178178
" )"
179179
]
180180
}

pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,9 +1719,8 @@ async def probe_liquid_heights(
17191719
# detect liquid heights
17201720
current_absolute_liquid_heights = await asyncio.gather(
17211721
*[
1722-
self.clld_probe_z_height_using_channel(
1722+
self.move_z_drive_to_liquid_surface_using_clld(
17231723
channel_idx=channel,
1724-
move_channels_to_save_pos_after=False,
17251724
lowest_immers_pos=container.get_absolute_location("c", "c", "cavity_bottom").z
17261725
+ tip.total_tip_length
17271726
- tip.fitting_depth,
@@ -1734,6 +1733,11 @@ async def probe_liquid_heights(
17341733
]
17351734
)
17361735

1736+
liquid_levels: List[int] = (await self.request_pip_height_last_lld())["lh"] # type: ignore
1737+
current_absolute_liquid_heights = [
1738+
float(liquid_levels[channel_idx] / 10) for channel_idx in use_channels
1739+
]
1740+
17371741
relative_to_well = [
17381742
current_absolute_liquid_heights[i]
17391743
- resource.get_absolute_location("c", "c", "cavity_bottom").z
@@ -8198,7 +8202,7 @@ async def clld_probe_y_position_using_channel(
81988202

81998203
return material_y_pos
82008204

8201-
async def clld_probe_z_height_using_channel(
8205+
async def move_z_drive_to_liquid_surface_using_clld(
82028206
self,
82038207
channel_idx: int, # 0-based indexing of channels!
82048208
lowest_immers_pos: float = 99.98, # mm
@@ -8209,28 +8213,7 @@ async def clld_probe_z_height_using_channel(
82098213
detection_drop: int = 2,
82108214
post_detection_trajectory: Literal[0, 1] = 1,
82118215
post_detection_dist: float = 2.0, # mm
8212-
move_channels_to_save_pos_after: bool = False,
8213-
) -> float:
8214-
"""Probes the Z-height below the specified channel on a Hamilton STAR liquid handling machine
8215-
using the channels 'capacitive Liquid Level Detection' (cLLD) capabilities.
8216-
N.B.: this means only conductive materials can be probed!
8217-
8218-
Args:
8219-
channel_idx: The index of the channel to use for probing. Backmost channel = 0.
8220-
lowest_immers_pos: The lowest immersion position in mm. This is the position of the channel, NOT including the tip length (as C0 commands do). So you have to add the total_tip_length - fitting_depth.
8221-
start_pos_lld_search: The start position for z-touch search in mm. This is the position of the channel, NOT including the tip length (as C0 commands do). So you have to add the total_tip_length - fitting_depth.
8222-
channel_speed: The speed of channel movement in mm/sec.
8223-
channel_acceleration: The acceleration of the channel in mm/sec**2.
8224-
detection_edge: The edge steepness at capacitive LLD detection.
8225-
detection_drop: The offset after capacitive LLD edge detection.
8226-
post_detection_trajectory (0, 1): Movement of the channel up (1) or down (0) after contacting the surface.
8227-
post_detection_dist: Distance to move into the trajectory after detection in mm.
8228-
move_channels_to_save_pos_after: Flag to move channels to a safe position after operation.
8229-
8230-
Returns:
8231-
The detected Z-height in mm.
8232-
"""
8233-
8216+
):
82348217
lowest_immers_pos_increments = STARBackend.mm_to_z_drive_increment(lowest_immers_pos)
82358218
start_pos_search_increments = STARBackend.mm_to_z_drive_increment(start_pos_search)
82368219
channel_speed_increments = STARBackend.mm_to_z_drive_increment(channel_speed)
@@ -8266,18 +8249,63 @@ async def clld_probe_z_height_using_channel(
82668249
+ f" and {STARBackend.z_drive_increment_to_mm(9_999)} mm, is {post_detection_dist} mm"
82678250
)
82688251

8252+
await self.send_command(
8253+
module=STARBackend.channel_id(channel_idx),
8254+
command="ZL",
8255+
zh=f"{lowest_immers_pos_increments:05}", # Lowest immersion position [increment]
8256+
zc=f"{start_pos_search_increments:05}", # Start position of LLD search [increment]
8257+
zl=f"{channel_speed_increments:05}", # Speed of channel movement
8258+
zr=f"{channel_acceleration_thousand_increments:03}", # Acceleration [1000 increment/second^2]
8259+
gt=f"{detection_edge:04}", # Edge steepness at capacitive LLD detection
8260+
gl=f"{detection_drop:04}", # Offset after capacitive LLD edge detection
8261+
zj=post_detection_trajectory, # Movement of the channel after contacting surface
8262+
zi=f"{post_detection_dist_increments:04}", # Distance to move up after detection [increment]
8263+
)
8264+
8265+
async def clld_probe_z_height_using_channel(
8266+
self,
8267+
channel_idx: int, # 0-based indexing of channels!
8268+
lowest_immers_pos: float = 99.98, # mm
8269+
start_pos_search: float = 330.0, # mm
8270+
channel_speed: float = 10.0, # mm
8271+
channel_acceleration: float = 800.0, # mm/sec**2
8272+
detection_edge: int = 10,
8273+
detection_drop: int = 2,
8274+
post_detection_trajectory: Literal[0, 1] = 1,
8275+
post_detection_dist: float = 2.0, # mm
8276+
move_channels_to_save_pos_after: bool = False,
8277+
) -> float:
8278+
"""Probes the Z-height below the specified channel on a Hamilton STAR liquid handling machine
8279+
using the channels 'capacitive Liquid Level Detection' (cLLD) capabilities.
8280+
N.B.: this means only conductive materials can be probed!
8281+
8282+
Args:
8283+
channel_idx: The index of the channel to use for probing. Backmost channel = 0.
8284+
lowest_immers_pos: The lowest immersion position in mm. This is the position of the channel, NOT including the tip length (as C0 commands do). So you have to add the total_tip_length - fitting_depth.
8285+
start_pos_lld_search: The start position for z-touch search in mm. This is the position of the channel, NOT including the tip length (as C0 commands do). So you have to add the total_tip_length - fitting_depth.
8286+
channel_speed: The speed of channel movement in mm/sec.
8287+
channel_acceleration: The acceleration of the channel in mm/sec**2.
8288+
detection_edge: The edge steepness at capacitive LLD detection.
8289+
detection_drop: The offset after capacitive LLD edge detection.
8290+
post_detection_trajectory (0, 1): Movement of the channel up (1) or down (0) after contacting the surface.
8291+
post_detection_dist: Distance to move into the trajectory after detection in mm.
8292+
move_channels_to_save_pos_after: Flag to move channels to a safe position after operation.
8293+
8294+
Returns:
8295+
The detected Z-height in mm.
8296+
"""
8297+
82698298
try:
8270-
await self.send_command(
8271-
module=STARBackend.channel_id(channel_idx),
8272-
command="ZL",
8273-
zh=f"{lowest_immers_pos_increments:05}", # Lowest immersion position [increment]
8274-
zc=f"{start_pos_search_increments:05}", # Start position of LLD search [increment]
8275-
zl=f"{channel_speed_increments:05}", # Speed of channel movement
8276-
zr=f"{channel_acceleration_thousand_increments:03}", # Acceleration [1000 increment/second^2]
8277-
gt=f"{detection_edge:04}", # Edge steepness at capacitive LLD detection
8278-
gl=f"{detection_drop:04}", # Offset after capacitive LLD edge detection
8279-
zj=post_detection_trajectory, # Movement of the channel after contacting surface
8280-
zi=f"{post_detection_dist_increments:04}", # Distance to move up after detection [increment]
8299+
await self.move_z_drive_to_liquid_surface_using_clld(
8300+
channel_idx=channel_idx,
8301+
lowest_immers_pos=lowest_immers_pos,
8302+
start_pos_search=start_pos_search,
8303+
channel_speed=channel_speed,
8304+
channel_acceleration=channel_acceleration,
8305+
detection_edge=detection_edge,
8306+
detection_drop=detection_drop,
8307+
post_detection_trajectory=post_detection_trajectory,
8308+
post_detection_dist=post_detection_dist,
82818309
)
82828310
except STARFirmwareError:
82838311
await self.move_all_channels_in_z_safety()

0 commit comments

Comments
 (0)