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

refactor(api): Allow aspirate, dispense and blow-out in place in PE and PAPI #12105

Merged
merged 89 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
0d7ee6a
raising error when using args that are deprecated and added to versio…
TamarZanzouri Jan 30, 2023
e4d70f4
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Jan 30, 2023
4c4fb2f
Update api/docs/v2/versioning.rst
TamarZanzouri Jan 30, 2023
6a2b20b
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Jan 30, 2023
4bc29d2
fixed docstrings and formatting
TamarZanzouri Jan 31, 2023
c5231a2
WIP started PE aspirate_in_place.py
TamarZanzouri Jan 31, 2023
964a5a9
added to pipetting aspirate_in_place
TamarZanzouri Feb 1, 2023
e963f9e
blow out in place pe command
TamarZanzouri Feb 1, 2023
a9f68c9
added sync client in-place
TamarZanzouri Feb 1, 2023
4fa3341
engine core asprirate in place
TamarZanzouri Feb 2, 2023
622b1f2
all engine core implementation. removed move_to_well arg
TamarZanzouri Feb 2, 2023
bb0736a
check if location is not the last location before move_to_coordinates
TamarZanzouri Feb 6, 2023
1501ca0
Merge branch 'edge' into RCORE-540-implement-in-place-commands
TamarZanzouri Feb 6, 2023
f7298ce
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 7, 2023
6879d11
Update api/src/opentrons/protocol_engine/commands/aspirate_in_place.py
TamarZanzouri Feb 7, 2023
70540d2
Update api/src/opentrons/protocol_engine/commands/blow_out_in_place.py
TamarZanzouri Feb 7, 2023
b2ffa4b
fixed move_to_well removal
TamarZanzouri Feb 7, 2023
e1c9c06
updated commands schema
TamarZanzouri Feb 7, 2023
1d2e505
linting js
TamarZanzouri Feb 7, 2023
80eecbd
fixed failing test in aspirate in place
TamarZanzouri Feb 7, 2023
ca8fcc9
fixed legacy simulator test failing
TamarZanzouri Feb 7, 2023
d252985
cnanged location to optional and added exception when not ready to as…
TamarZanzouri Feb 9, 2023
f4f7ea8
set last location
TamarZanzouri Feb 9, 2023
0f26c9d
Update api/src/opentrons/protocol_api/core/engine/instrument.py
TamarZanzouri Feb 9, 2023
7bc74cf
Update api/src/opentrons/protocol_api/core/engine/instrument.py
TamarZanzouri Feb 9, 2023
b98967d
Update api/src/opentrons/protocol_api/core/legacy_simulator/legacy_in…
TamarZanzouri Feb 9, 2023
949ca7e
Update api/src/opentrons/protocol_engine/execution/pipetting.py
TamarZanzouri Feb 9, 2023
c10ea27
Update api/tests/opentrons/protocol_engine/execution/test_pipetting_h…
TamarZanzouri Feb 9, 2023
94bbbe3
Update api/src/opentrons/protocol_engine/execution/pipetting.py
TamarZanzouri Feb 10, 2023
0c91bb7
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 10, 2023
c24f9f1
linting
TamarZanzouri Feb 9, 2023
502d464
Merge branch 'edge' into RCORE-540-implement-in-place-commands
TamarZanzouri Feb 10, 2023
e6d1c9e
moved aspirate_in_place logic into command impl, pr fixes
TamarZanzouri Feb 10, 2023
e3c5437
fixed failing tests
TamarZanzouri Feb 13, 2023
993469f
formatting
TamarZanzouri Feb 13, 2023
058c1ac
WIP refactor instrument_context.py
TamarZanzouri Feb 14, 2023
4221d68
reverted changs for the legacy core and added logic for PointTarget
TamarZanzouri Feb 14, 2023
bc0e584
fixed engine core
TamarZanzouri Feb 14, 2023
efc16cc
WIP dispense in public context. reverted optional location and revert…
TamarZanzouri Feb 14, 2023
dea7290
blow out and fixed validation method as_well
TamarZanzouri Feb 15, 2023
520b3a2
linting fixes
TamarZanzouri Feb 15, 2023
dedd7d8
merge conflicts
TamarZanzouri Feb 15, 2023
1fd6b66
Update api/src/opentrons/protocol_api/core/legacy/legacy_instrument_c…
TamarZanzouri Feb 15, 2023
ca44f97
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 15, 2023
f921902
Update api/src/opentrons/protocol_api/validation.py
TamarZanzouri Feb 15, 2023
f4f3dd1
Update api/src/opentrons/protocol_api/validation.py
TamarZanzouri Feb 15, 2023
c88e07d
merge conflicts and docstring
TamarZanzouri Feb 15, 2023
bd19514
Update api/src/opentrons/protocol_api/core/legacy_simulator/legacy_in…
TamarZanzouri Feb 16, 2023
ed3c8c0
Update api/src/opentrons/protocol_api/core/legacy_simulator/legacy_in…
TamarZanzouri Feb 16, 2023
d77b0ea
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 16, 2023
cc8c11f
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 16, 2023
479a859
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 16, 2023
accc27e
Update api/src/opentrons/protocol_api/validation.py
TamarZanzouri Feb 16, 2023
a6f3131
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 16, 2023
f4f234b
removed default arg for in_place. pr feedback
TamarZanzouri Feb 16, 2023
2c26a88
changed aspirate/dispense/blow_out to check against in_place. changed…
TamarZanzouri Feb 16, 2023
321d38b
added in_place to wellTarget
TamarZanzouri Feb 16, 2023
a59d08f
Update api/src/opentrons/protocol_api/core/engine/instrument.py
TamarZanzouri Feb 16, 2023
24ed944
Update api/src/opentrons/protocol_api/core/engine/instrument.py
TamarZanzouri Feb 16, 2023
6b518c4
Update api/src/opentrons/protocol_api/core/engine/instrument.py
TamarZanzouri Feb 16, 2023
1bfdaa0
Update api/src/opentrons/protocol_api/core/legacy_simulator/legacy_in…
TamarZanzouri Feb 16, 2023
aa57d8e
simulator dispense fix
TamarZanzouri Feb 16, 2023
2f877b9
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 17, 2023
c773f4b
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 17, 2023
a73c6c4
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 17, 2023
6cfd709
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 17, 2023
5ab87ce
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 17, 2023
5a692fd
Update api/tests/opentrons/protocol_api/test_validation.py
TamarZanzouri Feb 17, 2023
5443d9c
Update api/tests/opentrons/protocol_api/core/engine/test_instrument_c…
TamarZanzouri Feb 17, 2023
9020b30
Update api/tests/opentrons/protocol_api/core/engine/test_instrument_c…
TamarZanzouri Feb 17, 2023
bf33a18
Update api/tests/opentrons/protocol_api/test_validation.py
TamarZanzouri Feb 17, 2023
1d97ce3
Update api/tests/opentrons/protocol_api_old/core/simulator/test_instr…
TamarZanzouri Feb 17, 2023
4b5ac65
Update api/tests/opentrons/protocol_api/test_validation.py
TamarZanzouri Feb 17, 2023
4cafd32
Update api/tests/opentrons/protocol_api/core/engine/test_instrument_c…
TamarZanzouri Feb 17, 2023
dd97a14
Update api/tests/opentrons/protocol_api/core/engine/test_instrument_c…
TamarZanzouri Feb 17, 2023
e38b474
Update api/tests/opentrons/protocol_api/test_instrument_context.py
TamarZanzouri Feb 17, 2023
6c152fa
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 17, 2023
2601ded
Update api/src/opentrons/protocol_engine/commands/aspirate_in_place.py
TamarZanzouri Feb 17, 2023
51841b5
Update api/src/opentrons/protocol_engine/commands/blow_out_in_place.py
TamarZanzouri Feb 17, 2023
f720ee7
Update api/src/opentrons/protocol_api/validation.py
TamarZanzouri Feb 17, 2023
a7ad08a
Update api/src/opentrons/protocol_api/validation.py
TamarZanzouri Feb 17, 2023
2b7865b
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 17, 2023
51e0101
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 17, 2023
99f4bb1
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 17, 2023
a9a4ae1
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 17, 2023
6b42ec4
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 17, 2023
8a7d24b
Update api/src/opentrons/protocol_api/instrument_context.py
TamarZanzouri Feb 17, 2023
c2d62a0
pr fixes
TamarZanzouri Feb 17, 2023
16b6fba
pr fixes
TamarZanzouri Feb 17, 2023
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
Prev Previous commit
Next Next commit
WIP dispense in public context. reverted optional location and revert…
…ed legacy changes
  • Loading branch information
TamarZanzouri committed Feb 14, 2023
commit efc16cc2ac7335ec31cf2d0b562f4292b28012aa
3 changes: 2 additions & 1 deletion api/src/opentrons/protocol_api/core/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ def aspirate(
@abstractmethod
def dispense(
self,
location: Optional[types.Location],
location: types.Location,
well_core: Optional[WellCoreType],
volume: float,
rate: float,
flow_rate: float,
in_place: bool = False,
) -> 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 @@ -102,11 +102,12 @@ def aspirate(

def dispense(
self,
location: Optional[types.Location],
location: types.Location,
well_core: Optional[LegacyWellCore],
volume: float,
rate: float,
flow_rate: float,
in_place: bool = False,
) -> None:
"""Dispense a given volume of liquid into the specified location.
Args:
Expand All @@ -116,14 +117,13 @@ def dispense(
rate: The rate in µL/s to dispense at.
flow_rate: Not used in this core.
"""
if location:
self.move_to(location=location)
self.move_to(location=location)

self._protocol_interface.get_hardware().dispense(self._mount, volume, rate)

def blow_out(
self,
location: Optional[types.Location],
location: types.Location,
well_core: Optional[LegacyWellCore],
) -> None:
"""Blow liquid out of the tip.
Expand All @@ -132,7 +132,7 @@ def blow_out(
location: The location to blow out into.
well_core: Unused by legacy core.
"""
if location:
if location != self._protocol_interface.get_last_location():
self.move_to(location=location)
self._protocol_interface.get_hardware().blow_out(self._mount)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,23 +104,23 @@ def aspirate(

def dispense(
self,
location: Optional[types.Location],
location: types.Location,
well_core: Optional[LegacyWellCore],
volume: float,
rate: float,
flow_rate: float,
in_place: bool = False,
) -> None:
if location:
self.move_to(location=location, well_core=well_core)
self.move_to(location=location, well_core=well_core)
self._raise_if_no_tip(HardwareAction.DISPENSE.name)
self._update_volume(self.get_current_volume() - volume)

def blow_out(
self,
location: Optional[types.Location],
location: types.Location,
well_core: Optional[LegacyWellCore],
) -> None:
if location:
if location != self._protocol_interface.get_last_location():
TamarZanzouri marked this conversation as resolved.
Show resolved Hide resolved
self.move_to(location=location, well_core=well_core)
self._raise_if_no_tip(HardwareAction.BLOWOUT.name)
self._update_volume(0)
Expand Down
43 changes: 23 additions & 20 deletions api/src/opentrons/protocol_api/instrument_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,32 +289,34 @@ def dispense(
)
well: Optional[labware.Well]
last_location = self._get_last_location_by_api_version()

if isinstance(location, labware.Well):
well = location
if well.parent._core.is_fixed_trash():
move_to_location = location.top()
else:
move_to_location = location.bottom(
z=self._well_bottom_clearances.dispense
)
elif isinstance(location, types.Location):
move_to_location = location
_, well = move_to_location.labware.get_parent_labware_and_well()
elif location is not None:
raise TypeError(
f"location should be a Well or Location, but it is {location}"
)
elif last_location:
move_to_location = last_location
_, well = move_to_location.labware.get_parent_labware_and_well()
else:
dispense_in_place: bool = False
try:
target = validation.validate_location(location=location, last_location=last_location)
except validation.NoLocationError:
mcous marked this conversation as resolved.
Show resolved Hide resolved
raise RuntimeError(
"If dispense is called without an explicit location, another"
" method that moves to a location (such as move_to or "
"aspirate) must previously have been called so the robot "
"knows where it is."
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
)
) from e

except validation.LocationTypeError:
raise TypeError(
f"location should be a Well or Location, but it is {location}"
)

if isinstance(target, validation.WellTarget):
well = target.well
if well.parent._core.is_fixed_trash():
move_to_location = target.well.top()
else:
move_to_location = target.location or target.well.bottom(
z=self._well_bottom_clearances.dispense
)
TamarZanzouri marked this conversation as resolved.
Show resolved Hide resolved
if isinstance(target, validation.PointTarget):
move_to_location = target.location
_, well = target.location.labware.get_parent_labware_and_well()
dispense_in_place = target.in_place

if self.api_version >= APIVersion(2, 11):
instrument.validate_takes_liquid(
location=move_to_location,
Expand All @@ -341,6 +343,7 @@ def dispense(
location=move_to_location,
well_core=well._core if well is not None else None,
flow_rate=flow_rate,
in_place=dispense_in_place
)

return self
Expand Down
4 changes: 2 additions & 2 deletions api/src/opentrons/protocol_api/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,11 @@ def validate_location(
if isinstance(location, Well):
return WellTarget(well=location, location=None)
elif isinstance(location, Location):
if location.labware is None:
if location.labware.as_well() is None:
return PointTarget(location=location, in_place=False)
return WellTarget(well=location.labware.as_well(), location=location)
elif last_location and location is None:
if last_location.labware is None:
if last_location.labware.as_well() is None:
return PointTarget(location=last_location, in_place=True)
return WellTarget(well=last_location.labware.as_well(), location=last_location)

Expand Down
129 changes: 96 additions & 33 deletions api/tests/opentrons/protocol_api/test_instrument_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ def test_aspirate_from_coordinates(
mock_validation.validate_location(
location=input_location, last_location=last_location
)
).then_return(PointTarget(location=input_location, in_place=True))
).then_return(PointTarget(location=input_location, in_place=False))
decoy.when(mock_instrument_core.get_absolute_aspirate_flow_rate(1.23)).then_return(
5.67
)
Expand All @@ -283,7 +283,7 @@ def test_aspirate_from_coordinates(
mock_instrument_core.aspirate(
location=input_location,
well_core=None,
in_place=True,
in_place=False,
volume=42.0,
rate=1.23,
flow_rate=5.67,
Expand Down Expand Up @@ -631,87 +631,150 @@ def test_return_tip(


def test_dispense_with_location(
decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext
decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext, mock_protocol_core: ProtocolCore
) -> None:
"""It should dispense to a given location."""
mock_well = decoy.mock(cls=Well)
location = Location(point=Point(1, 2, 3), labware=mock_well)
input_location = Location(point=Point(2, 2, 2), labware=None)
last_location = Location(point=Point(9, 9, 9), labware=None)
decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT)

decoy.when(mock_instrument_core.get_absolute_dispense_flow_rate(1.0)).then_return(
3.0
decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(
last_location
)
decoy.when(
mock_validation.validate_location(
location=input_location, last_location=last_location
)
).then_return(PointTarget(location=input_location, in_place=False))
TamarZanzouri marked this conversation as resolved.
Show resolved Hide resolved
decoy.when(mock_instrument_core.get_absolute_dispense_flow_rate(1.23)).then_return(
5.67
)

subject.dispense(volume=42.0, location=location)
subject.dispense(volume=42.0, location=input_location, rate=1.23)

decoy.verify(
mock_instrument_core.dispense(
location=location,
well_core=mock_well._core,
location=input_location,
well_core=None,
in_place=False,
volume=42.0,
rate=1.0,
flow_rate=3.0,
rate=1.23,
flow_rate=5.67,
),
times=1,
)


def test_dispense_with_well_location(
decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext
decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext, mock_protocol_core: ProtocolCore
) -> None:
"""It should dispense to a well."""
mock_well = decoy.mock(cls=Well)
bottom_location = Location(point=Point(1, 2, 3), labware=mock_well)
input_location = Location(point=Point(2, 2, 2), labware=None)
last_location = Location(point=Point(9, 9, 9), labware=None)
decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT)

decoy.when(mock_well.bottom(1.0)).then_return(
Location(point=Point(1, 2, 3), labware=mock_well)
decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(
last_location
)

decoy.when(mock_instrument_core.get_absolute_dispense_flow_rate(1.0)).then_return(
3.0
decoy.when(
mock_validation.validate_location(
location=input_location, last_location=last_location
)
).then_return(WellTarget(well=mock_well, location=None))
decoy.when(mock_well.bottom(z=1.0)).then_return(bottom_location)
decoy.when(mock_instrument_core.get_absolute_dispense_flow_rate(1.23)).then_return(
5.67
)

subject.dispense(volume=42.0, location=mock_well)
subject.dispense(volume=42.0, location=input_location, rate=1.23)

decoy.verify(
mock_instrument_core.dispense(
location=Location(point=Point(1, 2, 3), labware=mock_well),
location=bottom_location,
well_core=mock_well._core,
in_place=False,
volume=42.0,
rate=1.0,
flow_rate=3.0,
rate=1.23,
flow_rate=5.67,
),
times=1,
)


def test_dispense_with_no_location(
def test_dispense_in_place(
TamarZanzouri marked this conversation as resolved.
Show resolved Hide resolved
decoy: Decoy,
mock_instrument_core: InstrumentCore,
subject: InstrumentContext,
mock_protocol_core: ProtocolCore,
) -> None:
"""It should dispense to a well."""
decoy.when(mock_protocol_core.get_last_location(Mount.LEFT)).then_return(
Location(point=Point(1, 2, 3), labware=None)
)
"""It should dispense in place."""
input_location = Location(point=Point(2, 2, 2), labware=None)
last_location = Location(point=Point(9, 9, 9), labware=None)
decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT)

decoy.when(mock_instrument_core.get_absolute_dispense_flow_rate(1.0)).then_return(
3.0
decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(
last_location
)
decoy.when(
mock_validation.validate_location(
location=input_location, last_location=last_location
)
).then_return(PointTarget(location=input_location, in_place=True))
decoy.when(mock_instrument_core.get_absolute_dispense_flow_rate(1.23)).then_return(
5.67
)

subject.dispense(volume=42.0)
subject.dispense(volume=42.0, location=input_location, rate=1.23)

decoy.verify(
mock_instrument_core.dispense(
location=Location(point=Point(1, 2, 3), labware=None),
location=input_location,
well_core=None,
in_place=True,
volume=42.0,
rate=1.0,
flow_rate=3.0,
rate=1.23,
flow_rate=5.67,
),
times=1,
)


def test_dispense_raises_no_location(
decoy: Decoy,
mock_instrument_core: InstrumentCore,
subject: InstrumentContext,
mock_protocol_core: ProtocolCore,
) -> None:

decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT)
decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None)

decoy.when(
mock_validation.validate_location(location=None, last_location=None)
).then_raise(mock_validation.NoLocationError())
with pytest.raises(RuntimeError):
subject.dispense(location=None)


def test_dispense_raises_wrong_location_value(
TamarZanzouri marked this conversation as resolved.
Show resolved Hide resolved
decoy: Decoy,
mock_instrument_core: InstrumentCore,
subject: InstrumentContext,
mock_protocol_core: ProtocolCore,
) -> None:

decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT)
decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None)

decoy.when(
mock_validation.validate_location(location=None, last_location=None)
).then_raise(mock_validation.LocationTypeError())
with pytest.raises(TypeError):
subject.dispense(location=None)


def test_touch_tip(
decoy: Decoy,
mock_instrument_core: InstrumentCore,
Expand Down
2 changes: 1 addition & 1 deletion api/tests/opentrons/protocol_api/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ def test_validate_in_place(decoy: Decoy) -> None:
input_last_location = Location(point=Point(x=1, y=1, z=2), labware=None)
expected_result = subject.PointTarget(location=input_last_location, in_place=True)

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

assert result == expected_result

Expand Down