Hi everyone,
When using the aspirate & dispense commands I am receiving errors that appear to arise from the commands’ default values.
Starting with…
Aspiration
await lh.aspirate(
[source_well],
vols=[10],
lld_mode = [lh.backend.LLDMode(1)],
)
…triggers error:
File ~/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/STAR.py:3769, in STAR.aspirate_pip(self, aspiration_type, tip_pattern, x_positions, y_positions, minimum_traverse_height_at_beginning_of_a_command, min_z_endpos, lld_search_height, clot_detection_height, liquid_surface_no_lld, pull_out_distance_transport_air, second_section_height, second_section_ratio, minimum_height, immersion_depth, immersion_depth_direction, surface_following_distance, aspiration_volumes, aspiration_speed, transport_air_volume, blow_out_air_volume, pre_wetting_volume, lld_mode, gamma_lld_sensitivity, dp_lld_sensitivity, aspirate_position_above_z_touch_off, detection_height_difference_for_dual_lld, swap_speed, settling_time, homogenization_volume, homogenization_cycles, homogenization_position_from_liquid_surface, homogenization_speed, homogenization_surface_following_distance, limit_curve_index, tadm_algorithm, recording_mode, use_2nd_section_aspiration, retract_height_over_2nd_section_to_empty_tip, dispensation_speed_during_emptying_tip, dosing_drive_speed_during_2nd_section_search, z_drive_speed_during_2nd_section_search, cup_upper_edge, ratio_liquid_rise_to_tip_deep_in, immersion_depth_2nd_section)
3767 print(f"swap_speed = {swap_speed}")
3768 print(f"homogenization_speed = {homogenization_speed}")
-> 3769 assert all(3 <= x <= 1600 for x in swap_speed), "swap_speed must be between 3 and 1600"
3770 assert all(0 <= x <= 99 for x in settling_time), "settling_time must be between 0 and 99"
3771 assert all(0 <= x <= 12500 for x in homogenization_volume), \
3772 "homogenization_volume must be between 0 and 12500"
AssertionError: swap_speed must be between 3 and 1600
And when giving a specific swap_speed
then another error arises:
File ~/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/STAR.py:3777, in STAR.aspirate_pip(self, aspiration_type, tip_pattern, x_positions, y_positions, minimum_traverse_height_at_beginning_of_a_command, min_z_endpos, lld_search_height, clot_detection_height, liquid_surface_no_lld, pull_out_distance_transport_air, second_section_height, second_section_ratio, minimum_height, immersion_depth, immersion_depth_direction, surface_following_distance, aspiration_volumes, aspiration_speed, transport_air_volume, blow_out_air_volume, pre_wetting_volume, lld_mode, gamma_lld_sensitivity, dp_lld_sensitivity, aspirate_position_above_z_touch_off, detection_height_difference_for_dual_lld, swap_speed, settling_time, homogenization_volume, homogenization_cycles, homogenization_position_from_liquid_surface, homogenization_speed, homogenization_surface_following_distance, limit_curve_index, tadm_algorithm, recording_mode, use_2nd_section_aspiration, retract_height_over_2nd_section_to_empty_tip, dispensation_speed_during_emptying_tip, dosing_drive_speed_during_2nd_section_search, z_drive_speed_during_2nd_section_search, cup_upper_edge, ratio_liquid_rise_to_tip_deep_in, immersion_depth_2nd_section)
3773 assert all(0 <= x <= 99 for x in homogenization_cycles), \
3774 "homogenization_cycles must be between 0 and 99"
3775 assert all(0 <= x <= 900 for x in homogenization_position_from_liquid_surface), \
3776 "homogenization_position_from_liquid_surface must be between 0 and 900"
-> 3777 assert all(4 <= x <= 5000 for x in homogenization_speed), \
3778 "homogenization_speed must be between 4 and 5000"
3779 assert all(0 <= x <= 3600 for x in homogenization_surface_following_distance), \
3780 "homogenization_surface_following_distance must be between 0 and 3600"
3781 assert all(0 <= x <= 999 for x in limit_curve_index), \
3782 "limit_curve_index must be between 0 and 999"
AssertionError: homogenization_speed must be between 4 and 5000
Investigation of STAR.aspirate_pip()
.
To troubleshoot I added the lines
3767 print(f"swap_speed = {swap_speed}")
3768 print(f"homogenization_speed = {homogenization_speed}")
which return
swap_speed = [0]
homogenization_speed = [0]
Now if I understand this correctly there are a number of aspirate
commands that all link together to send the final one to the machine, in this case STAR
, in what I am just going to call “command generation chain” (for a lack of a better phrase):
STAR.aspirate_pip()
(where the actual firmware command is generated) → STAR.aspirate()
(a convenient wrapper?) → liquid_handler.aspirate()
(the atomic command generator to use for any machine?) → machine
/ the physical Hamilton STAR
Bugs could lurk anywhere along the way but the error message indicates it is inside the STAR.py
file and the line number shows it is triggered by the first of these methods, i.e. STAR.aspirate_pip()
.
The definition of this method is:
async def aspirate_pip(
self,
aspiration_type: List[int] = [0],
tip_pattern: List[bool] = [True],
x_positions: List[int] = [0],
y_positions: List[int] = [0],
minimum_traverse_height_at_beginning_of_a_command: int = 3600,
min_z_endpos: int = 3600,
lld_search_height: List[int] = [0],
clot_detection_height: List[int] = [60],
liquid_surface_no_lld: List[int] = [3600],
pull_out_distance_transport_air: List[int] = [50],
second_section_height: List[int] = [0],
second_section_ratio: List[int] = [0],
minimum_height: List[int] = [3600],
immersion_depth: List[int] = [0],
immersion_depth_direction: List[int] = [0],
surface_following_distance: List[int] = [0],
aspiration_volumes: List[int] = [0],
aspiration_speed: List[int] = [500],
transport_air_volume: List[int] = [0],
blow_out_air_volume: List[int] = [200],
pre_wetting_volume: List[int] = [0],
lld_mode: List[int] = [1],
gamma_lld_sensitivity: List[int] = [1],
dp_lld_sensitivity: List[int] = [1],
aspirate_position_above_z_touch_off: List[int] = [5],
detection_height_difference_for_dual_lld: List[int] = [0],
swap_speed: List[int] = [100],
settling_time: List[int] = [5],
homogenization_volume: List[int] = [0],
homogenization_cycles: List[int] = [0],
homogenization_position_from_liquid_surface: List[int] = [250],
homogenization_speed: List[int] = [500],
homogenization_surface_following_distance: List[int] = [0],
limit_curve_index: List[int] = [0],
tadm_algorithm: bool = False,
recording_mode: int = 0,
# For second section aspiration only
use_2nd_section_aspiration: List[bool] = [False],
retract_height_over_2nd_section_to_empty_tip: List[int] = [60],
dispensation_speed_during_emptying_tip: List[int] = [468],
dosing_drive_speed_during_2nd_section_search: List[int] = [468],
z_drive_speed_during_2nd_section_search: List[int] = [215],
cup_upper_edge: List[int] = [3600],
ratio_liquid_rise_to_tip_deep_in: List[int] = [16246],
immersion_depth_2nd_section: List[int] = [30]
):
…clearly indicating the default value for swap_speed: List[int] = [100],
and homogenization_speed: List[int] = [500],
- indicating to me that the error new values are set to these arguments at a later timepoint.
Investigation of STAR.aspirate()
.
So I was searching for the culprit in the next method in line, i.e. STAR.aspirate()
:
Which just takes the defaults from STAR.aspirate_pip()
- except it changes some based on whether there is an hlc
aka HamiltonLiquidClass
defined.
Indeed, the only possible re-assignment of swap_speed
and homogenization_speed
to set them to 0
across the entire “command generation chain” is given by STAR.aspirate()
via…
swap_speed = _fill_in_defaults(swap_speed,
default=[int(hlc.aspiration_swap_speed*10) if hlc is not None else 0
for hlc in hamilton_liquid_classes])
...
homogenization_speed = _fill_in_defaults(homogenization_speed,
default=[int(hlc.aspiration_mix_flow_rate*10) if hlc is not None else 0
for hlc in hamilton_liquid_classes])
This makes sense → when not being given a liquid_classes=[HamiltonLiquidClass]
argument it overrides the swap_speed
, homogenization_speed
and a bunch of other arguments to 0 - but the only ones who the assertion statement flags as an error are swap_speed
and homogenization_speed
I believe this should be prevented by the STAR._fill_in_defaults()
method, and I don’t know why this doesn’t appear to be happening?
But if the problem is that no HamiltonLiquidClass
was used then using one should also fix this issue.
But it doesn’t:
await lh.aspirate(
[source_well],
vols=[10],
lld_mode = [lh.backend.LLDMode(1)],
liquid_classes = [StandardVolume_Water_DispenseSurface_Empty],
)
…because the liquid class is somehow not seen as reflected by a print(f"hamilton_liquid_classes = {hamilton_liquid_classes}")
statement placed inside the STAR.aspirate()
method:
hamilton_liquid_classes = None
swap_speed = [0]
homogenization_speed = [0]
Investigation of liquid_handler.aspirate()
.
Leaving me to investigate the next command in the “command generation chain”, i.e. liquid_handler.aspirate()
.
This method, and indeed the entire liquid_handler.py
file never mentions liquid_classes because they are neatly stored away in the backend_kwargs
.
But clearly they are never actually given forward from liquid_handler.aspirate()
to STAR.aspirate()
.
To figure out where we lose them I added print(f"liquid_handler.py - aspirate - backend_kwargs:extras: {extras}")
to liquid_handler.py:717
, and this is what the same run command as above then returns:
liquid_handler.py - aspirate - backend_kwargs:extras: {'liquid_classes'}
hamilton_liquid_classes = None
swap_speed = [0]
homogenization_speed = [0]
…meaning, the liquid_classes variable is actually deleted before being handed to STAR.aspirate()
…
Actionable results
- Making default
lh.aspirate()
, used in automation scripts, work → maybe the default values are not accurately assignedif hlc is None
? - Making liquid_classes work by not deleting them before handover from
liquid_handler.aspirate()
toSTAR.aspirate()
I tried implementing no. 2 by deleting lines liquid_handler.py:718
and liquid_handler.py:719
:
for extra in extras:
del backend_kwargs[extra]
…which means STAR.aspirate()
is actually handed the liquid_classes
argument.
But this threw up error:
File ~/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/STAR.py:61, in need_iswap_parked.<locals>.wrapper(self, *args, **kwargs)
58 if self.iswap_installed and not self.iswap_parked:
59 await self.park_iswap()
---> 61 result = await method(self, *args, **kwargs) # pylint: disable=not-callable
63 return result
TypeError: STAR.aspirate() got an unexpected keyword argument 'liquid_classes'
Leading me to think that liquid_classes are not yet implemented in PLR for STAR machines.
Please correct anything that you see that doesn’t look right. Chasing these errors is difficult and I am still learning to navigate the software architecture.