Skip to content

support non-full tip racks with 96 head pickup #548

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 11 additions & 5 deletions pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -2181,9 +2181,15 @@ async def pick_up_tips96(
"""Pick up tips using the 96 head."""
assert self.core96_head_installed, "96 head must be installed"
tip_spot_a1 = pickup.resource.get_item("A1")
tip_a1 = tip_spot_a1.get_tip()
assert isinstance(tip_a1, HamiltonTip), "Tip type must be HamiltonTip."
ttti = await self.get_or_assign_tip_type_index(tip_a1)
prototypical_tip = None
for tip_spot in pickup.resource.get_all_items():
if tip_spot.has_tip():
prototypical_tip = tip_spot.get_tip()
break
if prototypical_tip is None:
raise ValueError("No tips found in the tip rack.")
assert isinstance(prototypical_tip, HamiltonTip), "Tip type must be HamiltonTip."
ttti = await self.get_or_assign_tip_type_index(prototypical_tip)
position = tip_spot_a1.get_absolute_location() + tip_spot_a1.center() + pickup.offset
z_deposit_position += round(pickup.offset.z * 10)

Expand Down Expand Up @@ -2213,8 +2219,8 @@ async def drop_tips96(
"""Drop tips from the 96 head."""
assert self.core96_head_installed, "96 head must be installed"
if isinstance(drop.resource, TipRack):
tip_a1 = drop.resource.get_item("A1")
position = tip_a1.get_absolute_location() + tip_a1.center() + drop.offset
tip_spot_a1 = drop.resource.get_item("A1")
position = tip_spot_a1.get_absolute_location() + tip_spot_a1.center() + drop.offset
else:
position = self._position_96_head_in_resource(drop.resource) + drop.offset

Expand Down
3 changes: 3 additions & 0 deletions pylabrobot/liquid_handling/backends/hamilton/STAR_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
Lid,
ResourceStack,
no_volume_tracking,
set_tip_tracking,
)
from pylabrobot.resources.hamilton import STF, STARLetDeck

Expand Down Expand Up @@ -264,6 +265,8 @@ def __init__(self, name: str):
self.STAR._iswap_parked = True
await self.lh.setup()

set_tip_tracking(enabled=False)

async def asyncTearDown(self):
await self.lh.stop()

Expand Down
12 changes: 9 additions & 3 deletions pylabrobot/liquid_handling/backends/hamilton/vantage_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -946,9 +946,15 @@ async def pick_up_tips96(
):
# assert self.core96_head_installed, "96 head must be installed"
tip_spot_a1 = pickup.resource.get_item("A1")
tip_a1 = tip_spot_a1.get_tip()
assert isinstance(tip_a1, HamiltonTip), "Tip type must be HamiltonTip."
ttti = await self.get_or_assign_tip_type_index(tip_a1)
prototypical_tip = None
for tip_spot in pickup.resource.get_all_items():
if tip_spot.has_tip():
prototypical_tip = tip_spot.get_tip()
break
if prototypical_tip is None:
raise ValueError("No tips found in the tip rack.")
assert isinstance(prototypical_tip, HamiltonTip), "Tip type must be HamiltonTip."
ttti = await self.get_or_assign_tip_type_index(prototypical_tip)
position = tip_spot_a1.get_absolute_location() + tip_spot_a1.center() + pickup.offset
offset_z = pickup.offset.z

Expand Down
3 changes: 3 additions & 0 deletions pylabrobot/liquid_handling/backends/hamilton/vantage_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
TIP_CAR_480_A00,
Coordinate,
Cor_96_wellplate_360ul_Fb,
set_tip_tracking,
)
from pylabrobot.resources.hamilton import VantageDeck

Expand Down Expand Up @@ -259,6 +260,8 @@ async def asyncSetUp(self):

await self.lh.setup()

set_tip_tracking(enabled=False)

async def asyncTearDown(self):
await self.lh.stop()

Expand Down
10 changes: 8 additions & 2 deletions pylabrobot/liquid_handling/liquid_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1243,8 +1243,11 @@ async def pick_up_tips96(
for i, tip_spot in enumerate(tip_rack.get_all_items()):
if not does_tip_tracking() and self.head96[i].has_tip:
self.head96[i].remove_tip()
self.head96[i].add_tip(tip_spot.get_tip(), origin=tip_spot, commit=False)
if does_tip_tracking() and not tip_spot.tracker.is_disabled:
# only add tips where there is one present.
# it's possible only some tips are present in the tip rack.
if tip_spot.has_tip():
self.head96[i].add_tip(tip_spot.get_tip(), origin=tip_spot, commit=False)
if does_tip_tracking() and not tip_spot.tracker.is_disabled and tip_spot.has_tip():
tip_spot.tracker.remove_tip()

pickup_operation = PickupTipRack(resource=tip_rack, offset=offset)
Expand Down Expand Up @@ -1302,6 +1305,9 @@ async def drop_tips96(

# queue operation on all tip trackers
for i in range(96):
# it's possible not every channel on this head has a tip.
if not self.head96[i].has_tip:
continue
tip = self.head96[i].get_tip()
if tip.tracker.get_used_volume() > 0 and not allow_nonzero_volume:
error = f"Cannot drop tip with volume {tip.tracker.get_used_volume()} on channel {i}"
Expand Down
16 changes: 16 additions & 0 deletions pylabrobot/liquid_handling/liquid_handler_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,22 @@ async def test_aspirate_single_reservoir(self):
await self.lh.pick_up_tips96(self.tip_rack)
await self.lh.aspirate96(reagent_reservoir.get_item("A1"), volume=100)

async def test_pick_up_tips96_incomplete_rack(self):
set_tip_tracking(enabled=True)

# Test that picking up tips from an incomplete rack works
self.tip_rack.fill()
self.tip_rack.get_item("A1").tracker.remove_tip()

await self.lh.pick_up_tips96(self.tip_rack)

# Check that the tips were picked up correctly
self.assertFalse(self.lh.head96[0].has_tip)
for i in range(1, 96):
self.assertTrue(self.lh.head96[i].has_tip)

set_tip_tracking(enabled=False)


class TestLiquidHandlerVolumeTracking(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self):
Expand Down