Skip to content
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

feat(api): Tip tracking for all 96ch configurations #14488

Merged
merged 32 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6623510
inclusion of nozzle map into tip tracking
CaseyBatten Feb 9, 2024
893909c
tip cluster inspection framework
CaseyBatten Feb 9, 2024
8232a71
Dynamic automatic tip tracking logic for four tiprack entry permutations
CaseyBatten Feb 12, 2024
00050c9
addition of set used logic for tip clusters
CaseyBatten Feb 12, 2024
dc61394
Merge branch 'edge' into tip_tracking_for_alternative_configurations
CaseyBatten Feb 12, 2024
4ab15fa
adjustment of column and row offset for relevant cases
CaseyBatten Feb 12, 2024
1a3fa4e
Nozzle map in used tips updates
CaseyBatten Feb 13, 2024
13e5d86
update to utilize command result nozzle map and
CaseyBatten Feb 13, 2024
31e2754
addition of all 96ch configurations to tip tracking available check
CaseyBatten Feb 13, 2024
808d232
range corrections
CaseyBatten Feb 13, 2024
aa630f9
Improvement of validation for tip cluster search
CaseyBatten Feb 14, 2024
90fd772
todo regarding used wells when no nozzle map present
CaseyBatten Feb 14, 2024
7fe6dc6
iteration logic cleanup
CaseyBatten Feb 21, 2024
0d1c8c7
Merge branch 'edge' into tip_tracking_for_alternative_configurations
CaseyBatten Feb 22, 2024
94b7106
utilize loaded static pipette
CaseyBatten Feb 22, 2024
195076a
inclusion of 1/8 channel pipette and starting tip logic
CaseyBatten Feb 28, 2024
013ed2d
test case call fixes and set used update
CaseyBatten Feb 28, 2024
c60f124
logic condensing and tip state test fixes
CaseyBatten Mar 1, 2024
d91df8e
test fixes
CaseyBatten Mar 1, 2024
3579c17
gravimetric test fix
CaseyBatten Mar 4, 2024
324baa2
hw testing fixes
CaseyBatten Mar 4, 2024
149f6c5
refactor to support automatic tip tracking on partial config when no …
CaseyBatten Mar 6, 2024
e43c5db
Merge branch 'edge' into tip_tracking_for_alternative_configurations
CaseyBatten Mar 6, 2024
09e5bf3
test tip state and tips updates
CaseyBatten Mar 7, 2024
87ebf81
nozzle map and first well split for select tiprack from list
CaseyBatten Mar 7, 2024
e1b89a6
select_tiprack_from_list fix
CaseyBatten Mar 7, 2024
09ab2bc
set used tips updates
CaseyBatten Mar 11, 2024
25a635c
addition of multiple partial tip pickup test
CaseyBatten Mar 11, 2024
054a7eb
fixes to instrument context test
CaseyBatten Mar 11, 2024
cdc469b
comment cleanup
CaseyBatten Mar 12, 2024
38c69ed
remove nozzle_map property from instrument context and update other f…
CaseyBatten Mar 12, 2024
6940253
modify instrument context tests to account for version when using noz…
CaseyBatten Mar 12, 2024
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
8 changes: 1 addition & 7 deletions api/src/opentrons/protocol_api/core/engine/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,13 +703,7 @@ def is_tip_tracking_available(self) -> bool:
return True
else:
if self.get_channels() == 96:
# SINGLE configuration with H12 nozzle is technically supported by the
# current tip tracking implementation but we don't do any deck conflict
# checks for it, so we won't provide full support for it yet.
return (
self.get_nozzle_configuration() == NozzleConfigurationType.COLUMN
and primary_nozzle == "A12"
)
return True
if self.get_channels() == 8:
return (
self.get_nozzle_configuration() == NozzleConfigurationType.SINGLE
Expand Down
32 changes: 22 additions & 10 deletions api/src/opentrons/protocol_api/core/engine/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@

from opentrons_shared_data.labware.labware_definition import LabwareRole

from opentrons.protocol_engine import LoadedPipette
from opentrons.protocol_engine.errors import LabwareNotOnDeckError, ModuleNotOnDeckError
from opentrons.protocol_engine.clients import SyncClient as ProtocolEngineClient
from opentrons.types import DeckSlotName, Point
from opentrons.types import DeckSlotName, Point, Mount, MountType

from ..labware import AbstractLabware, LabwareLoadParams
from .well import WellCore
Expand Down Expand Up @@ -122,17 +123,28 @@ def reset_tips(self) -> None:
raise TypeError(f"{self.get_display_name()} is not a tip rack.")

def get_next_tip(
self, num_tips: int, starting_tip: Optional[WellCore]
self,
mount: Mount,
SyntaxColoring marked this conversation as resolved.
Show resolved Hide resolved
num_tips: int,
starting_tip: Optional[WellCore],
) -> Optional[str]:
return self._engine_client.state.tips.get_next_tip(
labware_id=self._labware_id,
num_tips=num_tips,
starting_tip_name=(
starting_tip.get_name()
if starting_tip and starting_tip.labware_id == self._labware_id
else None
),
pipette = self._engine_client.state.pipettes.get_by_mount(
MountType.from_hw_mount(mount)
)
if isinstance(pipette, LoadedPipette):
pipette_id = pipette.id
return self._engine_client.state.tips.get_next_tip(
pipette_id=pipette_id,
labware_id=self._labware_id,
num_tips=num_tips,
starting_tip_name=(
starting_tip.get_name()
if starting_tip and starting_tip.labware_id == self._labware_id
else None
),
)
else:
raise ValueError(f"No valid Pipette found for Mount {mount.name}")

def get_well_columns(self) -> List[List[str]]:
"""Get the all well names, organized by column, from the labware's definition."""
Expand Down
7 changes: 5 additions & 2 deletions api/src/opentrons/protocol_api/core/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
LabwareDefinition as LabwareDefinitionDict,
)

from opentrons.types import DeckSlotName, Point
from opentrons.types import DeckSlotName, Point, Mount

from .well import WellCoreType

Expand Down Expand Up @@ -110,7 +110,10 @@ def reset_tips(self) -> None:

@abstractmethod
def get_next_tip(
self, num_tips: int, starting_tip: Optional[WellCoreType]
self,
mount: Mount,
num_tips: int,
starting_tip: Optional[WellCoreType],
) -> Optional[str]:
"""Get the name of the next available tip(s) in the rack, if available."""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from opentrons.protocols.geometry.labware_geometry import LabwareGeometry
from opentrons.protocols.api_support.tip_tracker import TipTracker

from opentrons.types import DeckSlotName, Location, Point
from opentrons.types import DeckSlotName, Location, Point, Mount
from opentrons_shared_data.labware.dev_types import LabwareParameters, LabwareDefinition

from ..labware import AbstractLabware, LabwareLoadParams
Expand Down Expand Up @@ -153,7 +153,10 @@ def reset_tips(self) -> None:
well.set_has_tip(True)

def get_next_tip(
self, num_tips: int, starting_tip: Optional[LegacyWellCore]
self,
mount: Mount,
num_tips: int,
starting_tip: Optional[LegacyWellCore],
) -> Optional[str]:
next_well = self._tip_tracker.next_tip(num_tips, starting_tip)
return next_well.get_name() if next_well else None
Expand Down
16 changes: 13 additions & 3 deletions api/src/opentrons/protocol_api/instrument_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -872,15 +872,20 @@ def pick_up_tip( # noqa: C901
)

if location is None:
if not self._core.is_tip_tracking_available():
# TODO (cb, 02-29-2024): Remove the 96 channel specific gating here once starting tip is supported in tip-cluster based automatic tip tracking
SyntaxColoring marked this conversation as resolved.
Show resolved Hide resolved
if not self._core.is_tip_tracking_available() or (
self.starting_tip is not None
and self._core.get_channels() == 96
and (self._core.get_active_channels() not in [96, 8])
):
raise CommandPreconditionViolated(
"Automatic tip tracking is not available for the current pipette"
" nozzle configuration. We suggest switching to a configuration"
" that supports automatic tip tracking or specifying the exact tip"
" to pick up."
)

tip_rack, well = labware.next_available_tip(
mount=self._core.get_mount(),
starting_tip=self.starting_tip,
tip_racks=self.tip_racks,
channels=active_channels,
Expand All @@ -892,6 +897,7 @@ def pick_up_tip( # noqa: C901

elif isinstance(location, labware.Labware):
tip_rack, well = labware.next_available_tip(
mount=self._core.get_mount(),
starting_tip=None,
tip_racks=[location],
channels=active_channels,
Expand All @@ -907,6 +913,7 @@ def pick_up_tip( # noqa: C901

elif maybe_tip_rack is not None:
tip_rack, well = labware.next_available_tip(
mount=self._core.get_mount(),
starting_tip=None,
tip_racks=[maybe_tip_rack],
channels=active_channels,
Expand Down Expand Up @@ -1322,7 +1329,10 @@ def transfer( # noqa: C901

if new_tip != types.TransferTipPolicy.NEVER:
tr, next_tip = labware.next_available_tip(
self.starting_tip, self.tip_racks, active_channels
self._core.get_mount(),
self.starting_tip,
self.tip_racks,
active_channels,
)
max_volume = min(next_tip.max_volume, self.max_volume)
else:
Expand Down
29 changes: 21 additions & 8 deletions api/src/opentrons/protocol_api/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from opentrons_shared_data.labware.dev_types import LabwareDefinition, LabwareParameters

from opentrons.types import Location, Point
from opentrons.types import Location, Point, Mount
from opentrons.protocols.api_support.types import APIVersion
from opentrons.protocols.api_support.util import requires_version, APIVersionError

Expand Down Expand Up @@ -883,7 +883,10 @@ def tip_length(self, length: float) -> None:

# TODO(mc, 2022-11-09): implementation detail; deprecate public method
def next_tip(
self, num_tips: int = 1, starting_tip: Optional[Well] = None
self,
mount: Mount,
num_tips: int = 1,
starting_tip: Optional[Well] = None,
SyntaxColoring marked this conversation as resolved.
Show resolved Hide resolved
) -> Optional[Well]:
"""
Find the next valid well for pick-up.
Expand All @@ -902,6 +905,7 @@ def next_tip(
assert num_tips > 0, f"num_tips must be positive integer, but got {num_tips}"

well_name = self._core.get_next_tip(
mount=mount,
num_tips=num_tips,
starting_tip=starting_tip._core if starting_tip else None,
)
Expand Down Expand Up @@ -1061,7 +1065,10 @@ def split_tipracks(tip_racks: List[Labware]) -> Tuple[Labware, List[Labware]]:

# TODO(mc, 2022-11-09): implementation detail, move to core
def select_tiprack_from_list(
tip_racks: List[Labware], num_channels: int, starting_point: Optional[Well] = None
mount: Mount,
tip_racks: List[Labware],
num_channels: int,
starting_point: Optional[Well] = None,
) -> Tuple[Labware, Well]:
try:
first, rest = split_tipracks(tip_racks)
Expand All @@ -1077,11 +1084,11 @@ def select_tiprack_from_list(
else:
first_well = first.wells()[0]

next_tip = first.next_tip(num_channels, first_well)
next_tip = first.next_tip(mount, num_channels, first_well)
if next_tip:
return first, next_tip
else:
return select_tiprack_from_list(rest, num_channels)
return select_tiprack_from_list(mount, rest, num_channels)


# TODO(mc, 2022-11-09): implementation detail, move to core
Expand All @@ -1093,14 +1100,20 @@ def filter_tipracks_to_start(

# TODO(mc, 2022-11-09): implementation detail, move to core
def next_available_tip(
starting_tip: Optional[Well], tip_racks: List[Labware], channels: int
mount: Mount,
starting_tip: Optional[Well],
tip_racks: List[Labware],
channels: int,
) -> Tuple[Labware, Well]:
start = starting_tip
if start is None:
return select_tiprack_from_list(tip_racks, channels)
return select_tiprack_from_list(mount, tip_racks, channels)
else:
return select_tiprack_from_list(
filter_tipracks_to_start(start, tip_racks), channels, start
mount,
filter_tipracks_to_start(start, tip_racks),
channels,
start,
)


Expand Down
Loading
Loading