Support variable channel spacing via per-channel minimum Y spacing firmware retrieval#915
Support variable channel spacing via per-channel minimum Y spacing firmware retrieval#915BioCam wants to merge 15 commits intoPyLabRobot:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the Hamilton STAR backend to support per-channel minimum Y spacing (queried from firmware or provided by the simulator) instead of relying on a single hardcoded spacing, enabling correct behavior on 4ch/18mm single-rail and mixed-spacing channel configurations.
Changes:
- Replace global channel spacing with a per-channel spacing list and enforce spacing using a pairwise
max()rule. - Add firmware queries to retrieve per-channel minimum Y spacing during setup; add simulator overrides to supply these values without hardware calls.
- Extend generic liquid-handling offset utilities to accept a configurable
min_spacing, and add tests covering behavioral differences between 9mm vs 18mm spacing.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py |
Introduces per-channel spacing storage, firmware retrieval, and updates spacing enforcement/validation logic across Y-motion related code paths. |
pylabrobot/liquid_handling/backends/hamilton/STAR_chatterbox.py |
Adds constructor/setup support for simulated per-channel spacing and overrides spacing query methods. |
pylabrobot/liquid_handling/utils.py |
Renames the generic min-spacing constant and adds min_spacing parameters to offset helper functions (default preserved). |
pylabrobot/liquid_handling/backends/hamilton/STAR_tests.py |
Adds tests validating reachability and movement command differences for 4ch/18mm vs 9mm spacing configurations. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| rounded: If True (default), round the result to 1 decimal place. Hardware reports | ||
| exact values (e.g. 8.98) which round to the expected 9.0. | ||
|
|
There was a problem hiding this comment.
just choose a behavior that works, a parameter is making it overly complex
I would lean towards not rounding and letting the user (caller) round if they need to
There was a problem hiding this comment.
the user doesn't have access to a lot of the usage of these parameters (e.g. going into the spread channels command at runtime), but the user does expect minimal spacing of 9 mm.
if forced to not give the user deeper insight into what the firmware returns via this parameter, I would prefer to use math.ceil which is conservative and safe, and always produces what the user expects -> 9 mm / 18 mm
There was a problem hiding this comment.
if that's what the method should method should return to be correct sure
otherwise the user can call round or ceil or whatever
| else: # probing_direction == "forward" | ||
| adjacent_idx = max(channel_idx - 1, 0) | ||
| if channel_idx == 0: # safe default | ||
| adjacent_y_pos = STARBackend.y_drive_increment_to_mm(13_714) + 9 # y-position=635 mm | ||
| else: # previous channel | ||
| adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) | ||
|
|
||
| max_safe_y_mov_dist_post_detection = ( | ||
| adjacent_y_pos - detected_material_y_pos - self._channel_minimum_y_spacing | ||
| adjacent_y_pos | ||
| - detected_material_y_pos | ||
| - self._min_spacing_between(channel_idx, adjacent_idx) |
There was a problem hiding this comment.
adjacent_idx = max(channel_idx - 1, 0) seems like a special case to me, not sure if we can just say adjacent_idx = 0 when channel_idx = 0 and expect the code to work.
strictly speaking _min_spacing_between should raise when this happens I think
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
588b1ec to
cd81935
Compare
cd81935 to
ed18f3d
Compare
|
we do not know what the minimum y position for 5mL channels in the front, but given that for 9mm channels it's 6, it'd probably be 15 |
…ackmost_channel_max_y Replace hardcoded 6/6.0/5.8/635 and y_drive_increment_to_mm(13_714) constants with methods that derive the limits from _channels_minimum_y_spacing and iswap state. Also simplify the y_search_channel anti-crash and post-detection logic to compute safe bounds directly rather than through fake self-spacing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9b27b68 to
cac49dd
Compare
Why 15? If Since a 5 mL channel is assumed to be double the diameter (radius=9 mm): |
…tations
- _backmost_channel_max_y: account for channel 0's spacing (symmetric with _frontmost)
- _min_spacing_between: match error message format (i={i}, j={j})
- position_channels_in_y_direction: cleaner validation using round(um) comparison
- move_channel_y: inline _frontmost_channel_min_y() call, no local var
- TestChannelsMinimumYSpacing: more thorough tests (both 9mm-pass AND 18mm-fail,
raw STARBackend for can_reach, chatterbox+JY verification for position_channels)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-pipette-orchestration Resolved conflicts keeping our orchestration architecture (pipette_orchestration.py, plan_batches, probe_liquid_heights) while incorporating PR PyLabRobot#915's spacing infrastructure. Both branches' per-channel spacing helpers were pre-aligned in the previous commit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
merged main into this branch, including new changes from #876 |
…channels_request_y_minimum_spacing override Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parse RM and QM command responses into typed dataclasses instead of raw dicts. All bit fields (kb, ka, ke, xl, xn, xr, xo) are parsed into descriptive boolean attributes. Positional values are converted from 0.1mm to mm. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nimum-y-spacing-adaptation
…g default Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove _frontmost_channel_min_y and _backmost_channel_max_y helpers, use extended_conf.left_arm_min_y_position and extended_conf.pip_maximal_y_position directly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Problem
All channel spacing in
STARBackendwas hardcoded to a single value (9mm / 8.9mm / 8990µm depending on the call site). This fails for:The constraint is per-channel and symmetric: each channel carries its own minimum spacing, and the safe distance between any two adjacent channels is the
max()of both.PR Content/Solution
Infrastructure (
STAR_backend.py)_channel_minimum_y_spacing: float→_channels_minimum_y_spacing: List[float](per-channel)_min_spacing_between(i, j)helper:round(max(spacing[i], spacing[j]), 1)channels_request_y_minimum_spacing(): parallelP<n>VYqueries viaasyncio.gatherat setupcan_reach_position,position_channels_in_y_direction,get_channels_y_positions,probe_liquid_heightssafe bounds, CoRe gripper assert, validationSimulation (
STAR_chatterbox.py)channels_minimum_y_spacingconstructor param (defaults to[9.0] * num_channels)channel_request_y_minimum_spacing()/channels_request_y_minimum_spacing()overrides — return stored values without firmware callsGeneric LH (
utils.py)min_spacingparameter added toget_wide_andget_tight_single_resource_liquid_op_offsets(default unchanged)GENERIC_LH_MIN_SPACING_BETWEEN_CHANNELSTests (
STAR_tests.py)NOTE: This PR is meant to bring NO BREAKING CHANGES :)