Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d8babe3
Encapsulate pipette batch scheduling into dedicated module
BioCam Mar 12, 2026
8eece6f
make x_grouping_tolerance required argument
BioCam Mar 13, 2026
cbee224
Merge branch 'PyLabRobot:main' into encapsulate-pipette-batch-scheduling
BioCam Mar 16, 2026
fa39d12
Merge branch 'PyLabRobot:main' into encapsulate-pipette-batch-scheduling
BioCam Mar 20, 2026
a032cd5
Fix spacings list sizing in plan_batches when num_channels exceeds ma…
BioCam Mar 20, 2026
4310973
Remove detection parameter exposure from probe_liquid_heights (defer …
BioCam Mar 20, 2026
607565b
explain dual exception handling
BioCam Mar 20, 2026
ae9cc13
Merge branch 'main' into encapsulate-pipette-batch-scheduling
BioCam Mar 21, 2026
b3b326b
Merge branch 'PyLabRobot:main' into encapsulate-pipette-batch-scheduling
BioCam Mar 22, 2026
6d53765
Refactor plan_batches to accept containers, add execute_batched, fix …
BioCam Mar 22, 2026
564f36a
create `print_batches`
BioCam Mar 22, 2026
f6e8690
fix linting
BioCam Mar 22, 2026
9c3448a
`make format`
BioCam Mar 22, 2026
8c3b851
Merge branch 'main' into encapsulate-pipette-batch-scheduling
rickwierenga Mar 22, 2026
5bf7bb6
Merge branch 'main' into encapsulate-pipette-batch-scheduling
BioCam Mar 23, 2026
5147f11
update docstrings
BioCam Mar 23, 2026
5e46375
Merge branch 'main' into encapsulate-pipette-batch-scheduling
BioCam Mar 24, 2026
d143bdf
Merge branch 'main' into encapsulate-pipette-batch-scheduling
BioCam Mar 24, 2026
c7e18ff
Merge branch 'main' into encapsulate-pipette-batch-scheduling
BioCam Mar 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
535 changes: 211 additions & 324 deletions pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py
Copy link
Member

Choose a reason for hiding this comment

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

with the removal of execute_batched, the code becomes a lot less modular. and we do want to use execute_batched for other commands in the future

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

interesting, the current execute_batched seemed very limited to probing actions, which is why I removed it, but you're saying that it actually acts as an abstraction layer for any function that is meant to be called after the channels have moved to their target locations - I really like this

I will work on an implementation

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I returned it and tried to make it even more adaptive:

Screenshot 2026-03-22 at 20 15 18

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

with small adjustment to this workflow we can use it for...

  • probing (ztouch, cLLD, pLLD)
  • aspirate
  • dispense

Large diffs are not rendered by default.

84 changes: 84 additions & 0 deletions pylabrobot/liquid_handling/backends/hamilton/STAR_chatterbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,18 @@
MachineConfiguration,
STARBackend,
)
from pylabrobot.liquid_handling.pipette_batch_scheduling import (
plan_batches,
print_batches,
validate_channel_selections,
)
from pylabrobot.resources.container import Container
from pylabrobot.resources.coordinate import Coordinate
from pylabrobot.resources.well import Well

# Type alias for nested enum (for cleaner signatures)
LLDMode = STARBackend.LLDMode

_DEFAULT_MACHINE_CONFIGURATION = MachineConfiguration(
pip_type_1000ul=True,
kb_iswap_installed=True,
Expand Down Expand Up @@ -213,6 +223,10 @@ async def channel_request_y_minimum_spacing(self, channel_idx: int) -> float:
)
return self._channels_minimum_y_spacing[channel_idx]

async def channels_request_y_minimum_spacing(self) -> List[float]:
"""Return mock per-channel minimum Y spacings for all channels."""
return list(self._channels_minimum_y_spacing)

async def move_channel_y(self, channel: int, y: float):
print(f"moving channel {channel} to y: {y}")

Expand Down Expand Up @@ -316,3 +330,73 @@ async def position_channels_in_y_direction(self, ys, make_space=True):

async def request_pip_height_last_lld(self):
return list(range(12))

async def probe_liquid_heights(
self,
containers: List[Container],
use_channels: Optional[List[int]] = None,
resource_offsets: Optional[List[Coordinate]] = None,
lld_mode: LLDMode = LLDMode.GAMMA,
search_speed: float = 10.0,
n_replicates: int = 1,
move_to_z_safety_after: bool = True,
min_traverse_height_at_beginning_of_command: Optional[float] = None,
min_traverse_height_during_command: Optional[float] = None,
z_position_at_end_of_command: Optional[float] = None,
x_grouping_tolerance: Optional[float] = None,
) -> List[float]:
"""Probe liquid heights by computing from tracked container volumes.

Instead of simulating hardware LLD, this mock computes liquid heights directly from
each container's volume tracker using ``container.compute_height_from_volume()``.

Args:
containers: List of Container objects to probe, one per channel.
use_channels: Channel indices to use (0-indexed). Defaults to ``[0, ..., len(containers)-1]``.
resource_offsets: Passed to ``plan_batches`` for auto-spreading. See ``plan_batches``.
All other parameters: Accepted for API compatibility but unused in mock.

Returns:
Liquid heights in mm from cavity bottom for each container, computed from tracked volumes.

Raises:
ValueError: If ``use_channels`` is empty, contains out-of-range indices, or if
``containers`` and ``use_channels`` have different lengths.
NoTipError: If any specified channel lacks a tip.
"""
if x_grouping_tolerance is None:
x_grouping_tolerance = self._x_grouping_tolerance_mm

use_channels = validate_channel_selections(
containers=containers,
use_channels=use_channels,
num_channels=self.num_channels,
)

# Validate tip presence using tip tracker
for ch in use_channels:
self.head[ch].get_tip() # Raises NoTipError if no tip

batches = plan_batches(
use_channels=use_channels,
targets=containers,
channel_spacings=self._channels_minimum_y_spacing,
x_tolerance=x_grouping_tolerance,
wrt_resource=self.deck,
resource_offsets=resource_offsets,
)

print_batches(batches, use_channels, containers, label="probe_liquid_heights plan")

# Compute heights from volume trackers
heights: List[float] = []
for container in containers:
volume = container.tracker.get_used_volume()
if volume == 0:
heights.append(0.0)
else:
height = container.compute_height_from_volume(volume)
heights.append(height)

print(f" heights: {[f'{h:.2f}' for h in heights]} mm")
return heights
Loading
Loading