Skip to content

Commit

Permalink
fixed Papi to not calculate meniscus location prior to PE execution
Browse files Browse the repository at this point in the history
  • Loading branch information
pmoegenburg committed Oct 17, 2024
1 parent fc0572f commit 81da6d0
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 40 deletions.
41 changes: 28 additions & 13 deletions api/src/opentrons/protocol_api/instrument_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,9 @@ def aspirate(
)
)

is_meniscus: Optional[bool] = None
well: Optional[labware.Well] = None
move_to_location: types.Location
well: Optional[labware.Well] = None
is_meniscus: Optional[bool] = None
last_location = self._get_last_location_by_api_version()
try:
target = validation.validate_location(
Expand All @@ -233,18 +233,13 @@ def aspirate(
"knows where it is."
) from e

if isinstance(target, validation.WellTarget):
move_to_location = target.location or target.well.bottom(
z=self._well_bottom_clearances.aspirate
)
well = target.well
is_meniscus = target.is_meniscus
if isinstance(target, validation.PointTarget):
move_to_location = target.location
if isinstance(target, (TrashBin, WasteChute)):
raise ValueError(
"Trash Bin and Waste Chute are not acceptable location parameters for Aspirate commands."
)
move_to_location, well, is_meniscus = self._handle_aspirate_target(
target=target
)
if self.api_version >= APIVersion(2, 11):
instrument.validate_takes_liquid(
location=move_to_location,
Expand Down Expand Up @@ -284,7 +279,7 @@ def aspirate(
rate=rate,
flow_rate=flow_rate,
in_place=target.in_place,
is_meniscus=is_meniscus if is_meniscus is not None else None,
is_meniscus=is_meniscus,
)

return self
Expand Down Expand Up @@ -406,7 +401,7 @@ def dispense( # noqa: C901
well = target.well
if target.location:
move_to_location = target.location
is_meniscus = target.is_meniscus
is_meniscus = target.location.is_meniscus
elif well.parent._core.is_fixed_trash():
move_to_location = target.well.top()
else:
Expand Down Expand Up @@ -451,7 +446,6 @@ def dispense( # noqa: C901
flow_rate=flow_rate,
in_place=False,
push_out=push_out,
is_meniscus=is_meniscus if is_meniscus is not None else None,
)
return self

Expand All @@ -473,6 +467,7 @@ def dispense( # noqa: C901
flow_rate=flow_rate,
in_place=target.in_place,
push_out=push_out,
is_meniscus=is_meniscus,
)

return self
Expand Down Expand Up @@ -2197,6 +2192,26 @@ def _raise_if_configuration_not_supported_by_pipette(
)
# SINGLE, QUADRANT and ALL are supported by all pipettes

def _handle_aspirate_target(
self, target: validation.ValidTarget
) -> tuple[types.Location, Optional[labware.Well], Optional[bool]]:
move_to_location: types.Location
well: Optional[labware.Well] = None
is_meniscus: Optional[bool] = None
if isinstance(target, validation.WellTarget):
well = target.well
if target.location:
move_to_location = target.location
is_meniscus = target.location.is_meniscus

else:
move_to_location = target.well.bottom(
z=self._well_bottom_clearances.aspirate
)
if isinstance(target, validation.PointTarget):
move_to_location = target.location
return (move_to_location, well, is_meniscus)


class AutoProbeDisable:
"""Use this class to temporarily disable automatic liquid presence detection."""
Expand Down
7 changes: 4 additions & 3 deletions api/src/opentrons/protocol_api/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,6 @@ class WellTarget(NamedTuple):
well: Well
location: Optional[Location]
in_place: bool
is_meniscus: Optional[bool] = None


class PointTarget(NamedTuple):
Expand All @@ -436,10 +435,13 @@ class LocationTypeError(TypeError):
"""Error representing that the location supplied is of different expected type."""


ValidTarget = Union[WellTarget, PointTarget, TrashBin, WasteChute]


def validate_location(
location: Union[Location, Well, TrashBin, WasteChute, None],
last_location: Optional[Location],
) -> Union[WellTarget, PointTarget, TrashBin, WasteChute]:
) -> ValidTarget:
"""Validate a given location for a liquid handling command.
Args:
Expand Down Expand Up @@ -481,7 +483,6 @@ def validate_location(
well=well,
location=target_location,
in_place=in_place,
is_meniscus=target_location.is_meniscus,
)
if well is not None
else PointTarget(location=target_location, in_place=in_place)
Expand Down
3 changes: 2 additions & 1 deletion api/src/opentrons/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ def __eq__(self, other: object) -> bool:
isinstance(other, Location)
and other._point == self._point
and other._labware == self._labware
and other._is_meniscus == self._is_meniscus
)

def move(self, point: Point) -> "Location":
Expand All @@ -179,7 +180,7 @@ def move(self, point: Point) -> "Location":
return Location(point=self.point + point, labware=self._given_labware)

def __repr__(self) -> str:
return f"Location(point={repr(self._point)}, labware={self._labware})"
return f"Location(point={repr(self._point)}, labware={self._labware}, is_meniscus={self._is_meniscus if self._is_meniscus is not None else False})"


# TODO(mc, 2020-10-22): use MountType implementation for Mount
Expand Down
11 changes: 6 additions & 5 deletions api/tests/opentrons/protocol_api/test_instrument_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,11 +402,7 @@ def test_aspirate_meniscus_well_location(
mock_validation.validate_location(
location=input_location, last_location=last_location
)
).then_return(
WellTarget(
well=mock_well, location=input_location, in_place=False, is_meniscus=True
)
)
).then_return(WellTarget(well=mock_well, location=input_location, in_place=False))
decoy.when(mock_instrument_core.get_aspirate_flow_rate(1.23)).then_return(5.67)

subject.aspirate(volume=42.0, location=input_location, rate=1.23)
Expand Down Expand Up @@ -970,6 +966,7 @@ def test_dispense_with_location(
rate=1.23,
flow_rate=5.67,
push_out=None,
is_meniscus=None,
),
times=1,
)
Expand Down Expand Up @@ -1008,6 +1005,7 @@ def test_dispense_with_well_location(
rate=1.23,
flow_rate=3.0,
push_out=7,
is_meniscus=None,
),
times=1,
)
Expand Down Expand Up @@ -1048,6 +1046,7 @@ def test_dispense_with_well(
rate=1.23,
flow_rate=5.67,
push_out=None,
is_meniscus=None,
),
times=1,
)
Expand Down Expand Up @@ -1302,6 +1301,7 @@ def test_dispense_0_volume_means_dispense_everything(
rate=1.23,
flow_rate=5.67,
push_out=None,
is_meniscus=None,
),
times=1,
)
Expand Down Expand Up @@ -1331,6 +1331,7 @@ def test_dispense_0_volume_means_dispense_nothing(
rate=1.23,
flow_rate=5.67,
push_out=None,
is_meniscus=None,
),
times=1,
)
Expand Down
15 changes: 0 additions & 15 deletions api/tests/opentrons/protocol_api/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,21 +489,6 @@ def test_validate_location_with_well(decoy: Decoy) -> None:
assert result == expected_result


def test_validate_location_with_meniscus_well(decoy: Decoy) -> None:
"""Should return a WellTarget with location."""
mock_well = decoy.mock(cls=Well)
input_location = Location(
point=Point(x=1, y=1, z=1), labware=mock_well, is_meniscus=True
)
expected_result = subject.WellTarget(
well=mock_well, location=input_location, in_place=False, is_meniscus=True
)

result = subject.validate_location(location=input_location, last_location=None)

assert result == expected_result


def test_validate_last_location(decoy: Decoy) -> None:
"""Should return a WellTarget with location."""
mock_well = decoy.mock(cls=Well)
Expand Down
9 changes: 6 additions & 3 deletions api/tests/opentrons/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_location_repr_labware(min_lw: Labware) -> None:
loc = Location(point=Point(x=1.1, y=2.1, z=3.5), labware=min_lw)
assert (
f"{loc}"
== "Location(point=Point(x=1.1, y=2.1, z=3.5), labware=minimal labware on deck)"
== "Location(point=Point(x=1.1, y=2.1, z=3.5), labware=minimal labware on deck, is_meniscus=False)"
)


Expand All @@ -38,14 +38,17 @@ def test_location_repr_well(min_lw: Labware) -> None:
loc = Location(point=Point(x=1, y=2, z=3), labware=min_lw.wells()[0])
assert (
f"{loc}"
== "Location(point=Point(x=1, y=2, z=3), labware=A1 of minimal labware on deck)"
== "Location(point=Point(x=1, y=2, z=3), labware=A1 of minimal labware on deck, is_meniscus=False)"
)


def test_location_repr_slot() -> None:
"""It should represent labware as a slot"""
loc = Location(point=Point(x=-1, y=2, z=3), labware="1")
assert f"{loc}" == "Location(point=Point(x=-1, y=2, z=3), labware=1)"
assert (
f"{loc}"
== "Location(point=Point(x=-1, y=2, z=3), labware=1, is_meniscus=False)"
)


@pytest.mark.parametrize(
Expand Down

0 comments on commit 81da6d0

Please sign in to comment.