Skip to content

Support variable channel spacing via per-channel minimum Y spacing firmware retrieval#915

Open
BioCam wants to merge 15 commits intoPyLabRobot:mainfrom
BioCam:expose-per-channel-minimum-y-spacing-adaptation
Open

Support variable channel spacing via per-channel minimum Y spacing firmware retrieval#915
BioCam wants to merge 15 commits intoPyLabRobot:mainfrom
BioCam:expose-per-channel-minimum-y-spacing-adaptation

Conversation

@BioCam
Copy link
Collaborator

@BioCam BioCam commented Mar 1, 2026

The Problem

All channel spacing in STARBackend was hardcoded to a single value (9mm / 8.9mm / 8990µm depending on the call site). This fails for:

  • 4-channel single-rail machines with 18mm spacing; reported PLR user configuration in Config for min channel spacing that's not 9 mm #822
  • Mixed 1mL + 5mL channel configurations where adjacent channels have different physical constraints (e.g. 8.98mm between two 1mL channels, 17.96mm between a 1mL and 5mL channel) -> preparation for the new X1 channels arriving to consumers
mixed_channel_spacing

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(): parallel P<n>VY queries via asyncio.gather at setup
  • All 11 consumer sites updated: can_reach_position, position_channels_in_y_direction, get_channels_y_positions,
    probe_liquid_heights safe bounds, CoRe gripper assert, validation

Simulation (STAR_chatterbox.py)

  • channels_minimum_y_spacing constructor param (defaults to [9.0] * num_channels)
  • channel_request_y_minimum_spacing() / channels_request_y_minimum_spacing() overrides — return stored values without firmware calls

Generic LH (utils.py)

  • min_spacing parameter added to get_wide_ and get_tight_single_resource_liquid_op_offsets (default unchanged)
  • Renamed constant to GENERIC_LH_MIN_SPACING_BETWEEN_CHANNELS

Tests (STAR_tests.py)

  • 4 behavioral tests comparing 4ch/18mm vs 8ch/9mm: reachability bounds, validation rejection, and JY command payload differences

NOTE: This PR is meant to bring NO BREAKING CHANGES :)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +1568 to +1570
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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Collaborator Author

@BioCam BioCam Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if that's what the method should method should return to be correct sure

otherwise the user can call round or ceil or whatever

Comment on lines +10168 to +10178
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@rickwierenga rickwierenga force-pushed the expose-per-channel-minimum-y-spacing-adaptation branch from 588b1ec to cd81935 Compare March 2, 2026 20:48
@rickwierenga rickwierenga force-pushed the expose-per-channel-minimum-y-spacing-adaptation branch from cd81935 to ed18f3d Compare March 2, 2026 20:53
@rickwierenga
Copy link
Member

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>
@rickwierenga rickwierenga force-pushed the expose-per-channel-minimum-y-spacing-adaptation branch from 9b27b68 to cac49dd Compare March 2, 2026 21:33
@BioCam
Copy link
Collaborator Author

BioCam commented Mar 3, 2026

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

Why 15?

If y = 6 mm is the center of 1 mL channel index=-1 (radius=4.5 mm) then the frontfacing inaccessible y-distance is 1.5 mm.

Since a 5 mL channel is assumed to be double the diameter (radius=9 mm): 9 mm + 1.5 = 10.5 mm

BioCam added a commit to BioCam/pylabrobot that referenced this pull request Mar 4, 2026
…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>
BioCam added a commit to BioCam/pylabrobot that referenced this pull request Mar 4, 2026
…-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>
@rickwierenga
Copy link
Member

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>
rickwierenga and others added 4 commits March 5, 2026 12:01
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>
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants