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): add WellVolumeOffset to WellLocation #16302

Merged
merged 56 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
dd74c58
initial implementation
pmoegenburg Sep 19, 2024
e210544
update
pmoegenburg Sep 19, 2024
0deed9a
updated get_well_position
pmoegenburg Sep 19, 2024
8f713df
updated implementation
pmoegenburg Sep 19, 2024
64fc6e2
update schema 9
pmoegenburg Sep 19, 2024
1bcc578
updated docstring
pmoegenburg Sep 20, 2024
33e6108
updated schema
pmoegenburg Sep 20, 2024
fd93d0e
updated docstring
pmoegenburg Sep 20, 2024
a40b66b
updated schema and formatted
pmoegenburg Sep 20, 2024
add6638
fleshed out height-to-volume-to-height
pmoegenburg Sep 23, 2024
f2b6bc7
created command-specific WellLocations and move_to_well() subfunctions
pmoegenburg Sep 23, 2024
087caaf
added LiquidHandlingWellLocation and PickUpTipWellLocation
pmoegenburg Sep 24, 2024
3d53137
split get_well_position into submethods
pmoegenburg Sep 24, 2024
68981a3
test fixes
pmoegenburg Sep 25, 2024
a9b6dfb
updated typescript
pmoegenburg Sep 25, 2024
8c0f5ce
schema and test fixes
pmoegenburg Sep 25, 2024
2bc1d66
revert typescript work
pmoegenburg Sep 25, 2024
8731a19
cleaned up comments
pmoegenburg Sep 25, 2024
32760c3
updated command schema and formatted
pmoegenburg Sep 25, 2024
957c573
removed comment
pmoegenburg Sep 25, 2024
df07616
added validate_well_position()
pmoegenburg Sep 26, 2024
843a5b5
Merge branch 'edge' into EXEC-643-add-WellVolumeOffset
pmoegenburg Sep 26, 2024
da7f479
added TODO
pmoegenburg Sep 26, 2024
a9ca313
removed TODO and fixed robot-server tavern tests
pmoegenburg Sep 27, 2024
22d2c39
added test, need to fix. added well_location guardrails
pmoegenburg Sep 30, 2024
17ce9b1
refactored geometry methods and added helper function in labware
pmoegenburg Sep 30, 2024
9d559a6
refactored geometry and added validate_dispense_volume_into_well
pmoegenburg Sep 30, 2024
54205b3
updated long doc strings
pmoegenburg Sep 30, 2024
d2af54e
cleaned up comments and doc strings
pmoegenburg Sep 30, 2024
c2a8271
Merge branch 'edge' into EXEC-643-add-WellVolumeOffset
pmoegenburg Sep 30, 2024
ad334cc
updated command schema 9
pmoegenburg Sep 30, 2024
a671aad
refactored geometry and test
pmoegenburg Oct 2, 2024
726ffc1
Merge branch 'edge' into EXEC-643-add-WellVolumeOffset
pmoegenburg Oct 2, 2024
910d940
updated tests
pmoegenburg Oct 9, 2024
a3ac146
Merge branch 'edge' into EXEC-643-add-WellVolumeOffset
pmoegenburg Oct 9, 2024
38afbc3
update needed after merging in edge
pmoegenburg Oct 9, 2024
b688aac
follow-up format and lint
pmoegenburg Oct 9, 2024
f03bcb4
fixed up frustum_helpers and tests
pmoegenburg Oct 10, 2024
67d53a4
Merge branch 'edge' into EXEC-643-add-WellVolumeOffset
pmoegenburg Oct 10, 2024
053a47c
updated validate_well_position and command schema 10
pmoegenburg Oct 10, 2024
05a075c
added tiprack check to move_to_well
pmoegenburg Oct 10, 2024
97858d8
updates to relax WellLocation contraints based on PR review
pmoegenburg Oct 11, 2024
4e6a6ba
reverted command schema 9
pmoegenburg Oct 11, 2024
f364bc9
linted, removed test, reverted protocol/models/ work
pmoegenburg Oct 11, 2024
029ecea
eliminated test that doesn't do anything
pmoegenburg Oct 11, 2024
38235fe
Created PickUpTipWellOrigin without MENISCUS enum and updated BlowOut…
pmoegenburg Oct 14, 2024
cedc8b4
added Papi support for Well.meniscus Location
pmoegenburg Oct 15, 2024
7fe0451
reduce PE constraints
pmoegenburg Oct 16, 2024
fc0572f
Papi refactor to eliminate calculations prior to PE execution
pmoegenburg Oct 16, 2024
81da6d0
fixed Papi to not calculate meniscus location prior to PE execution
pmoegenburg Oct 17, 2024
b9aba1f
fixed simulation/analysis
pmoegenburg Oct 17, 2024
618e92f
Hid Location.is_meniscus from docs
pmoegenburg Oct 17, 2024
fb8a20c
added geometry test
pmoegenburg Oct 17, 2024
6feaff0
tidied up
pmoegenburg Oct 17, 2024
936b91c
addressed TODO to more appropriately name exceptions
pmoegenburg Oct 17, 2024
665e815
eliminated unneeded method
pmoegenburg Oct 17, 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
34 changes: 18 additions & 16 deletions api/src/opentrons/protocol_api/core/engine/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ def aspirate(
rate: float,
flow_rate: float,
in_place: bool,
is_meniscus: Optional[bool] = None,
) -> None:
"""Aspirate a given volume of liquid from the specified location.
Args:
Expand Down Expand Up @@ -146,12 +147,11 @@ def aspirate(
well_name = well_core.get_name()
labware_id = well_core.labware_id

well_location = (
self._engine_client.state.geometry.get_relative_well_location(
labware_id=labware_id,
well_name=well_name,
absolute_point=location.point,
)
well_location = self._engine_client.state.geometry.get_relative_liquid_handling_well_location(
labware_id=labware_id,
well_name=well_name,
absolute_point=location.point,
is_meniscus=is_meniscus,
)
pipette_movement_conflict.check_safe_for_pipette_movement(
engine_state=self._engine_client.state,
Expand Down Expand Up @@ -182,6 +182,7 @@ def dispense(
flow_rate: float,
in_place: bool,
push_out: Optional[float],
is_meniscus: Optional[bool] = None,
) -> None:
"""Dispense a given volume of liquid into the specified location.
Args:
Expand Down Expand Up @@ -237,12 +238,11 @@ def dispense(
well_name = well_core.get_name()
labware_id = well_core.labware_id

well_location = (
self._engine_client.state.geometry.get_relative_well_location(
labware_id=labware_id,
well_name=well_name,
absolute_point=location.point,
)
well_location = self._engine_client.state.geometry.get_relative_liquid_handling_well_location(
labware_id=labware_id,
well_name=well_name,
absolute_point=location.point,
is_meniscus=is_meniscus,
)
pipette_movement_conflict.check_safe_for_pipette_movement(
engine_state=self._engine_client.state,
Expand Down Expand Up @@ -416,10 +416,12 @@ def pick_up_tip(
well_name = well_core.get_name()
labware_id = well_core.labware_id

well_location = self._engine_client.state.geometry.get_relative_well_location(
labware_id=labware_id,
well_name=well_name,
absolute_point=location.point,
well_location = (
self._engine_client.state.geometry.get_relative_pick_up_tip_well_location(
labware_id=labware_id,
well_name=well_name,
absolute_point=location.point,
)
)
pipette_movement_conflict.check_safe_for_tip_pickup_and_return(
engine_state=self._engine_client.state,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
DeckSlotLocation,
OnLabwareLocation,
WellLocation,
LiquidHandlingWellLocation,
PickUpTipWellLocation,
DropTipWellLocation,
)
from opentrons.protocol_engine.types import (
Expand Down Expand Up @@ -66,7 +68,12 @@ def check_safe_for_pipette_movement(
pipette_id: str,
labware_id: str,
well_name: str,
well_location: Union[WellLocation, DropTipWellLocation],
well_location: Union[
WellLocation,
LiquidHandlingWellLocation,
PickUpTipWellLocation,
DropTipWellLocation,
],
) -> None:
"""Check if the labware is safe to move to with a pipette in partial tip configuration.

Expand Down
11 changes: 0 additions & 11 deletions api/src/opentrons/protocol_api/core/engine/well.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,17 +125,6 @@ def get_center(self) -> Point:
well_location=WellLocation(origin=WellOrigin.CENTER),
)

def get_meniscus(self, z_offset: float) -> Point:
"""Get the coordinate of the well's meniscus, with a z-offset."""
return self._engine_client.state.geometry.get_well_position(
well_name=self._name,
labware_id=self._labware_id,
well_location=WellLocation(
origin=WellOrigin.MENISCUS,
offset=WellOffset(x=0, y=0, z=z_offset),
),
)

def load_liquid(
self,
liquid: Liquid,
Expand Down
2 changes: 2 additions & 0 deletions api/src/opentrons/protocol_api/core/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def aspirate(
rate: float,
flow_rate: float,
in_place: bool,
is_meniscus: Optional[bool] = None,
) -> None:
"""Aspirate a given volume of liquid from the specified location.
Args:
Expand All @@ -55,6 +56,7 @@ def dispense(
flow_rate: float,
in_place: bool,
push_out: Optional[float],
is_meniscus: Optional[bool] = None,
) -> None:
"""Dispense a given volume of liquid into the specified location.
Args:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def aspirate(
rate: float,
flow_rate: float,
in_place: bool,
is_meniscus: Optional[bool] = None,
) -> None:
"""Aspirate a given volume of liquid from the specified location.
Args:
Expand Down Expand Up @@ -122,6 +123,7 @@ def dispense(
flow_rate: float,
in_place: bool,
push_out: Optional[float],
is_meniscus: Optional[bool] = None,
) -> None:
"""Dispense a given volume of liquid into the specified location.
Args:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,6 @@ def get_center(self) -> Point:
"""Get the coordinate of the well's center."""
return self._geometry.center()

def get_meniscus(self, z_offset: float) -> Point:
"""This will never be called because it was added in API 2.21."""
assert False, "get_meniscus only supported in API 2.21 & later"

def load_liquid(
self,
liquid: Liquid,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def aspirate(
rate: float,
flow_rate: float,
in_place: bool,
is_meniscus: Optional[bool] = None,
) -> None:
if self.get_current_volume() == 0:
# Make sure we're at the top of the labware and clear of any
Expand Down Expand Up @@ -132,6 +133,7 @@ def dispense(
flow_rate: float,
in_place: bool,
push_out: Optional[float],
is_meniscus: Optional[bool] = None,
) -> None:
if isinstance(location, (TrashBin, WasteChute)):
raise APIVersionError(
Expand Down
4 changes: 0 additions & 4 deletions api/src/opentrons/protocol_api/core/well.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,6 @@ def get_bottom(self, z_offset: float) -> Point:
def get_center(self) -> Point:
"""Get the coordinate of the well's center."""

@abstractmethod
def get_meniscus(self, z_offset: float) -> Point:
"""Get the coordinate of the well's meniscus, with an z-offset."""

@abstractmethod
def load_liquid(
self,
Expand Down
37 changes: 29 additions & 8 deletions api/src/opentrons/protocol_api/instrument_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,9 @@ def aspirate(
)
)

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 @@ -232,17 +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
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 @@ -282,6 +279,7 @@ def aspirate(
rate=rate,
flow_rate=flow_rate,
in_place=target.in_place,
is_meniscus=is_meniscus,
)

return self
Expand Down Expand Up @@ -384,6 +382,7 @@ def dispense( # noqa: C901
)
)
well: Optional[labware.Well] = None
is_meniscus: Optional[bool] = None
last_location = self._get_last_location_by_api_version()

try:
Expand All @@ -402,6 +401,7 @@ def dispense( # noqa: C901
well = target.well
if target.location:
move_to_location = target.location
is_meniscus = target.location.is_meniscus
elif well.parent._core.is_fixed_trash():
move_to_location = target.well.top()
else:
Expand Down Expand Up @@ -467,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 @@ -2191,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
8 changes: 4 additions & 4 deletions api/src/opentrons/protocol_api/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,13 @@ def meniscus(self, z: float = 0.0) -> Location:
"""
:param z: An offset on the z-axis, in mm. Positive offsets are higher and
negative offsets are lower.
:return: A :py:class:`~opentrons.types.Location` corresponding to the
absolute position of the meniscus-center of the well, plus the ``z`` offset
(if specified).
:return: A :py:class:`~opentrons.types.Location` that indicates location is meniscus and that holds the ``z`` offset in its point.z field.

:meta private:
"""
return Location(self._core.get_meniscus(z_offset=z), self)
return Location(
point=Point(x=0, y=0, z=z), labware=self, _ot_internal_is_meniscus=True
)

@requires_version(2, 8)
def from_center_cartesian(self, x: float, y: float, z: float) -> Point:
Expand Down
5 changes: 4 additions & 1 deletion api/src/opentrons/protocol_api/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,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
6 changes: 6 additions & 0 deletions api/src/opentrons/protocol_engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@
LoadedPipette,
MotorAxis,
WellLocation,
LiquidHandlingWellLocation,
PickUpTipWellLocation,
DropTipWellLocation,
WellOrigin,
DropTipWellOrigin,
PickUpTipWellOrigin,
WellOffset,
ModuleModel,
ModuleDefinition,
Expand Down Expand Up @@ -109,9 +112,12 @@
"LoadedPipette",
"MotorAxis",
"WellLocation",
"LiquidHandlingWellLocation",
"PickUpTipWellLocation",
"DropTipWellLocation",
"WellOrigin",
"DropTipWellOrigin",
"PickUpTipWellOrigin",
"WellOffset",
"ModuleModel",
"ModuleDefinition",
Expand Down
11 changes: 8 additions & 3 deletions api/src/opentrons/protocol_engine/commands/aspirate.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
PipetteIdMixin,
AspirateVolumeMixin,
FlowRateMixin,
WellLocationMixin,
LiquidHandlingWellLocationMixin,
BaseLiquidHandlingResult,
DestinationPositionResult,
)
Expand Down Expand Up @@ -38,7 +38,7 @@


class AspirateParams(
PipetteIdMixin, AspirateVolumeMixin, FlowRateMixin, WellLocationMixin
PipetteIdMixin, AspirateVolumeMixin, FlowRateMixin, LiquidHandlingWellLocationMixin
):
"""Parameters required to aspirate from a specific well."""

Expand Down Expand Up @@ -112,12 +112,17 @@ async def execute(self, params: AspirateParams) -> _ExecuteReturn:
well_name=well_name,
)

well_location = params.wellLocation
if well_location.origin == WellOrigin.MENISCUS:
well_location.volumeOffset = "operationVolume"

position = await self._movement.move_to_well(
pipette_id=pipette_id,
labware_id=labware_id,
well_name=well_name,
well_location=params.wellLocation,
well_location=well_location,
current_well=current_well,
operation_volume=-params.volume,
)
deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z)
state_update.set_pipette_location(
Expand Down
Loading
Loading