Skip to content

Commit

Permalink
test tip state and tips updates
Browse files Browse the repository at this point in the history
  • Loading branch information
CaseyBatten committed Mar 7, 2024
1 parent e43c5db commit 09e5bf3
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 57 deletions.
2 changes: 1 addition & 1 deletion api/src/opentrons/protocol_api/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -1083,7 +1083,7 @@ def select_tiprack_from_list(
elif starting_point:
first_well = starting_point
else:
first_well = first.wells()[0]
first_well = None

next_tip = first.next_tip(num_channels, first_well, nozzle_map)
if next_tip:
Expand Down
25 changes: 10 additions & 15 deletions api/src/opentrons/protocol_engine/state/tips.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def get_next_tip( # noqa: C901
starting_tip_name: Optional[str],
nozzle_map: Optional[NozzleMap],
) -> Optional[str]:
"""Get the next available clean tip."""
"""Get the next available clean tip. Does not support use of a starting tip if the pipette used is in a partial configuration."""
wells = self._state.tips_by_labware_id.get(labware_id, {})
columns = self._state.column_by_labware_id.get(labware_id, [])

Expand Down Expand Up @@ -215,10 +215,10 @@ def _identify_tip_cluster(
else:
return None
tip_cluster.append(well)

if any(well not in [*wells] for well in tip_cluster):
# If wells from this cluster have been dropped, return None to search for the next cluster
return None
# Return the list of tips to be analyzed

return tip_cluster

def _validate_tip_cluster(
Expand Down Expand Up @@ -269,7 +269,6 @@ def _cluster_search_A1(active_columns: int, active_rows: int) -> Optional[str]:
active_columns, active_rows, tip_cluster
)
if isinstance(result, str):
# The result is the critical tip to target
return result
if critical_row + active_rows < len(columns[0]):
critical_row = critical_row + active_rows
Expand All @@ -292,7 +291,6 @@ def _cluster_search_A12(active_columns: int, active_rows: int) -> Optional[str]:
active_columns, active_rows, tip_cluster
)
if isinstance(result, str):
# The result is the critical tip to target
return result
if critical_row + active_rows < len(columns[0]):
critical_row = critical_row + active_rows
Expand All @@ -315,7 +313,6 @@ def _cluster_search_H1(active_columns: int, active_rows: int) -> Optional[str]:
active_columns, active_rows, tip_cluster
)
if isinstance(result, str):
# The result is the critical tip to target
return result
if critical_row - active_rows > 0:
critical_row = critical_row - active_rows
Expand All @@ -338,7 +335,6 @@ def _cluster_search_H12(active_columns: int, active_rows: int) -> Optional[str]:
active_columns, active_rows, tip_cluster
)
if isinstance(result, str):
# The result is the critical tip to target
return result
if critical_row - active_rows > 0:
critical_row = critical_row - active_rows
Expand All @@ -351,29 +347,28 @@ def _cluster_search_H12(active_columns: int, active_rows: int) -> Optional[str]:
num_channels = len(nozzle_map.full_instrument_map_store)
num_nozzle_cols = len(nozzle_map.columns)
num_nozzle_rows = len(nozzle_map.rows)
# Each pipette's cluster search is determined by the point of entry for a given pipette/configuration:
# - Single channel pipettes always search a tiprack top to bottom, left to right
# - Eight channel pipettes will begin at the top if the primary nozzle is H1 and at the bottom if
# it is A1. The eight channel will always progress across the columns left to right.
# - 96 Channel pipettes will begin in the corner opposite their primary/starting nozzle (if starting nozzle = A1, enter tiprack at H12)
# The 96 channel will then progress towards the opposite corner, either going up or down, left or right depending on configuration.

if num_channels == 1:
# for a single channel pipette, always begin at A1 on the tiprack
return _cluster_search_A1(num_nozzle_cols, num_nozzle_rows)
elif num_channels == 8:
# perform 8 channel logic beginning from either H1 or A1 of the tiprack
if nozzle_map.starting_nozzle == "A1":
# Define the critical well by the position of the well relative to Tip Rack entry point H1
return _cluster_search_H1(num_nozzle_cols, num_nozzle_rows)
elif nozzle_map.starting_nozzle == "H1":
# Define the critical well by the position of the well relative to Tip Rack entry point A1
return _cluster_search_A1(num_nozzle_cols, num_nozzle_rows)
elif num_channels == 96:
if nozzle_map.starting_nozzle == "A1":
# Define the critical well by the position of the well relative to Tip Rack entry point H12
return _cluster_search_H12(num_nozzle_cols, num_nozzle_rows)
elif nozzle_map.starting_nozzle == "A12":
# Define the critical well by the position of the well relative to Tip Rack entry point H1
return _cluster_search_H1(num_nozzle_cols, num_nozzle_rows)
elif nozzle_map.starting_nozzle == "H1":
# Define the critical well by the position of the well relative to Tip Rack entry point A12
return _cluster_search_A12(num_nozzle_cols, num_nozzle_rows)
elif nozzle_map.starting_nozzle == "H12":
# Define the critical well by the position of the well relative to Tip Rack entry point A1
return _cluster_search_A1(num_nozzle_cols, num_nozzle_rows)
else:
raise ValueError(
Expand Down
77 changes: 36 additions & 41 deletions api/tests/opentrons/protocol_engine/state/test_tip_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ def test_get_next_tip_returns_none(
nozzle_offset_z=1.23,
home_position=4.56,
nozzle_map=get_default_nozzle_map(PipetteNameType.P1000_96),
back_left_corner_offset=Point(0, 0, 0),
front_right_corner_offset=Point(0, 0, 0),
),
)
subject.handle_action(
Expand Down Expand Up @@ -204,6 +206,8 @@ def test_get_next_tip_returns_first_tip(
nozzle_offset_z=1.23,
home_position=4.56,
nozzle_map=get_default_nozzle_map(pipette_name_type),
back_left_corner_offset=Point(0, 0, 0),
front_right_corner_offset=Point(0, 0, 0),
),
)
subject.handle_action(
Expand Down Expand Up @@ -259,6 +263,8 @@ def test_get_next_tip_used_starting_tip(
nozzle_offset_z=1.23,
home_position=4.56,
nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2),
back_left_corner_offset=Point(0, 0, 0),
front_right_corner_offset=Point(0, 0, 0),
),
)
subject.handle_action(
Expand Down Expand Up @@ -308,6 +314,7 @@ def test_get_next_tip_skips_picked_up_tip(
load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg]
result=commands.LoadPipetteResult(pipetteId="pipette-id")
)
channels_num = input_tip_amount
if input_starting_tip is not None:
pipette_name_type = PipetteNameType.P1000_96
if input_tip_amount == 1:
Expand All @@ -316,56 +323,38 @@ def test_get_next_tip_skips_picked_up_tip(
pipette_name_type = PipetteNameType.P300_MULTI_GEN2
else:
pipette_name_type = PipetteNameType.P1000_96
load_pipette_private_result = commands.LoadPipettePrivateResult(
pipette_id="pipette-id",
serial_number="pipette-serial",
config=LoadedStaticPipetteData(
channels=input_tip_amount,
max_volume=15,
min_volume=3,
model="gen a",
display_name="display name",
flow_rates=FlowRates(
default_aspirate={},
default_dispense={},
default_blow_out={},
),
tip_configuration_lookup_table={15: supported_tip_fixture},
nominal_tip_overlap={},
nozzle_offset_z=1.23,
home_position=4.56,
nozzle_map=get_default_nozzle_map(pipette_name_type),
),
)
else:
channels_num = get_next_tip_tips
pipette_name_type = PipetteNameType.P1000_96
if get_next_tip_tips == 1:
pipette_name_type = PipetteNameType.P300_SINGLE_GEN2
elif get_next_tip_tips == 8:
pipette_name_type = PipetteNameType.P300_MULTI_GEN2
else:
pipette_name_type = PipetteNameType.P1000_96
load_pipette_private_result = commands.LoadPipettePrivateResult(
pipette_id="pipette-id",
serial_number="pipette-serial",
config=LoadedStaticPipetteData(
channels=get_next_tip_tips,
max_volume=15,
min_volume=3,
model="gen a",
display_name="display name",
flow_rates=FlowRates(
default_aspirate={},
default_dispense={},
default_blow_out={},
),
tip_configuration_lookup_table={15: supported_tip_fixture},
nominal_tip_overlap={},
nozzle_offset_z=1.23,
home_position=4.56,
nozzle_map=get_default_nozzle_map(pipette_name_type),
load_pipette_private_result = commands.LoadPipettePrivateResult(
pipette_id="pipette-id",
serial_number="pipette-serial",
config=LoadedStaticPipetteData(
channels=channels_num,
max_volume=15,
min_volume=3,
model="gen a",
display_name="display name",
flow_rates=FlowRates(
default_aspirate={},
default_dispense={},
default_blow_out={},
),
)
tip_configuration_lookup_table={15: supported_tip_fixture},
nominal_tip_overlap={},
nozzle_offset_z=1.23,
home_position=4.56,
nozzle_map=get_default_nozzle_map(pipette_name_type),
back_left_corner_offset=Point(0, 0, 0),
front_right_corner_offset=Point(0, 0, 0),
),
)
subject.handle_action(
actions.UpdateCommandAction(
private_result=load_pipette_private_result, command=load_pipette_command
Expand Down Expand Up @@ -490,6 +479,8 @@ def test_get_next_tip_with_starting_tip_8_channel(
nozzle_offset_z=1.23,
home_position=4.56,
nozzle_map=get_default_nozzle_map(PipetteNameType.P300_MULTI_GEN2),
back_left_corner_offset=Point(0, 0, 0),
front_right_corner_offset=Point(0, 0, 0),
),
)
subject.handle_action(
Expand Down Expand Up @@ -563,6 +554,8 @@ def test_get_next_tip_with_starting_tip_out_of_tips(
nozzle_offset_z=1.23,
home_position=4.56,
nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2),
back_left_corner_offset=Point(0, 0, 0),
front_right_corner_offset=Point(0, 0, 0),
),
)
subject.handle_action(
Expand Down Expand Up @@ -636,6 +629,8 @@ def test_get_next_tip_with_column_and_starting_tip(
nozzle_offset_z=1.23,
home_position=4.56,
nozzle_map=get_default_nozzle_map(PipetteNameType.P300_MULTI_GEN2),
back_left_corner_offset=Point(0, 0, 0),
front_right_corner_offset=Point(0, 0, 0),
),
)
subject.handle_action(
Expand Down

0 comments on commit 09e5bf3

Please sign in to comment.