Skip to content

Commit

Permalink
Merge branch 'chore_release-8.0.0' into 8.0.0-incremental-merge
Browse files Browse the repository at this point in the history
  • Loading branch information
mjhuff committed Aug 19, 2024
2 parents 177001f + 243633c commit 5a8244d
Show file tree
Hide file tree
Showing 136 changed files with 2,830 additions and 1,049 deletions.
2 changes: 0 additions & 2 deletions api-client/src/protocols/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,3 @@ export { getProtocolIds } from './getProtocolIds'
export { getProtocols } from './getProtocols'

export * from './types'
export * from './utils'
export * from './__fixtures__'
2 changes: 1 addition & 1 deletion api/docs/v2/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
# use rst_prolog to hold the subsitution
# update the apiLevel value whenever a new minor version is released
rst_prolog = f"""
.. |apiLevel| replace:: 2.19
.. |apiLevel| replace:: 2.20
.. |release| replace:: {release}
"""

Expand Down
6 changes: 1 addition & 5 deletions api/docs/v2/new_pipette.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ Pages in this section of the documentation cover:

- :ref:`Loading pipettes <loading-pipettes>` into your protocol.
- :ref:`Pipette characteristics <pipette-characteristics>`, such as how fast they can move liquid and how they move around the deck.
- The :ref:`partial tip pickup <partial-tip-pickup>` configuration for the Flex 96-Channel Pipette, which uses only 8 channels for pipetting. Full and partial tip pickup can be combined in a single protocol.
- :ref:`Partial tip pickup <partial-tip-pickup>` configurations for multi-channel pipettes. Full and partial tip pickup configurations can be combined in a single protocol.
- The :ref:`volume modes <pipette-volume-modes>` of Flex 50 µL pipettes, which must operate in low-volume mode to accurately dispense very small volumes of liquid.

For information about liquid handling, see :ref:`v2-atomic-commands` and :ref:`v2-complex-commands`.




2 changes: 1 addition & 1 deletion api/docs/v2/pipettes/characteristics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Multi-Channel Movement

All :ref:`building block <v2-atomic-commands>` and :ref:`complex commands <v2-complex-commands>` work with single- and multi-channel pipettes.

To keep the protocol API consistent when using single- and multi-channel pipettes, commands treat the back left channel of a multi-channel pipette as its *primary channel*. Location arguments of pipetting commands use the primary channel. The :py:meth:`.InstrumentContext.configure_nozzle_layout` method can change the pipette's primary channel, using its ``start`` parameter. See :ref:`partial-tip-pickup` for more information.
To keep the protocol API consistent when using single- and multi-channel pipettes, location arguments of pipetting commands use the pipette's *primary channel*. For multi-channel pipettes picking up tips with all of their channels, the back-left channel is considered primary. When using fewer channels, the ``start`` parameter of the :py:meth:`.InstrumentContext.configure_nozzle_layout` method can change the pipette's primary channel. See :ref:`partial-tip-pickup` for more information.

With a pipette's default settings, you can generally access the wells indicated in the table below. Moving to any other well may cause the pipette to crash.

Expand Down
285 changes: 255 additions & 30 deletions api/docs/v2/pipettes/partial_tip_pickup.rst

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion api/docs/v2/versioning.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ The maximum supported API version for your robot is listed in the Opentrons App

If you upload a protocol that specifies a higher API level than the maximum supported, your robot won't be able to analyze or run your protocol. You can increase the maximum supported version by updating your robot software and Opentrons App.

Opentrons robots running the latest software (7.3.0) support the following version ranges:
Opentrons robots running the latest software (8.0.0) support the following version ranges:

* **Flex:** version 2.15–|apiLevel|.
* **OT-2:** versions 2.0–|apiLevel|.
Expand All @@ -84,6 +84,8 @@ This table lists the correspondence between Protocol API versions and robot soft
+-------------+------------------------------+
| API Version | Introduced in Robot Software |
+=============+==============================+
| 2.20 | 8.0.0 |
+-------------+------------------------------+
| 2.19 | 7.3.1 |
+-------------+------------------------------+
| 2.18 | 7.3.0 |
Expand Down Expand Up @@ -135,6 +137,7 @@ Changes in API Versions
Version 2.20
------------

- :py:meth:`.configure_nozzle_layout` now accepts row, single, and partial column layout constants. See :ref:`partial-tip-pickup`.
- You can now call :py:obj:`.ProtocolContext.define_liquid()` without supplying a ``description`` or ``display_color``.

Version 2.19
Expand Down
3 changes: 1 addition & 2 deletions api/src/opentrons/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@ def _create_live_context_pe(
create_protocol_engine_in_thread(
hardware_api=hardware_api_wrapped,
config=_get_protocol_engine_config(),
deck_configuration=entrypoint_util.get_deck_configuration(),
error_recovery_policy=error_recovery_policy.never_recover,
drop_tips_after_run=False,
post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE,
Expand Down Expand Up @@ -689,8 +690,6 @@ def _get_protocol_engine_config() -> Config:
# We deliberately omit ignore_pause=True because, in the current implementation of
# opentrons.protocol_api.core.engine, that would incorrectly make
# ProtocolContext.is_simulating() return True.
use_simulated_deck_config=True,
# TODO the above is not correct for this and it should use the robot's actual config
)


Expand Down
9 changes: 3 additions & 6 deletions api/src/opentrons/hardware_control/instruments/ot3/pipette.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
InvalidLiquidClassName,
CommandPreconditionViolated,
PythonException,
InvalidInstrumentData,
)
from opentrons_shared_data.pipette.ul_per_mm import (
piecewise_volume_conversion,
Expand Down Expand Up @@ -652,12 +653,8 @@ def set_tip_type(self, tip_type: pip_types.PipetteTipType) -> None:
try:
new_tips = self._liquid_class.supported_tips[tip_type]
except KeyError as e:
raise InvalidLiquidClassName(
message=f"There is no configuration for {tip_type.name} in liquid class {str(self._liquid_class_name)} on a {self._config.display_name}",
detail={
"current-liquid-class": str(self._liquid_class_name),
"requested-type": tip_type.name,
},
raise InvalidInstrumentData(
message=f"There is no configuration for {tip_type.name} in the pick up tip configurations for a {self._config.display_name}",
wrapping=[PythonException(e)],
) from e

Expand Down
6 changes: 4 additions & 2 deletions api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
from concurrent.futures import Future
import contextlib
from copy import deepcopy
from functools import partial, lru_cache, wraps
from dataclasses import replace
import logging
Expand Down Expand Up @@ -2683,9 +2684,10 @@ async def liquid_probe(
instrument, HardwareAction.LIQUID_PROBE, checked_mount
)
if not probe_settings:
probe_settings = self.config.liquid_sense
probe_settings = deepcopy(self.config.liquid_sense)

# We need to significantly slow down the 96 channel liquid probe
# We need to significatly slow down the 96 channel liquid probe
# TODO: (sigler) add LLD plunger-speed to pipette definitions
if self.gantry_load == GantryLoad.HIGH_THROUGHPUT:
max_plunger_speed = self.config.motion_settings.max_speed_discontinuity[
GantryLoad.HIGH_THROUGHPUT
Expand Down
6 changes: 6 additions & 0 deletions api/src/opentrons/protocol_api/core/engine/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,12 @@ def configure_nozzle_layout(
primaryNozzle=cast(PRIMARY_NOZZLE_LITERAL, primary_nozzle)
)
elif style == NozzleLayout.QUADRANT or style == NozzleLayout.PARTIAL_COLUMN:
assert (
# We make sure to set these nozzles in the calling function
# if using QUADRANT or PARTIAL_COLUMN. Asserting only for type verification here.
front_right_nozzle is not None
and back_left_nozzle is not None
), f"Both front right and back left nozzles are required for {style} configuration."
configuration_model = QuadrantNozzleLayoutConfiguration(
primaryNozzle=cast(PRIMARY_NOZZLE_LITERAL, primary_nozzle),
frontRightNozzle=front_right_nozzle,
Expand Down
192 changes: 148 additions & 44 deletions api/src/opentrons/protocol_api/instrument_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -1983,7 +1983,7 @@ def prepare_to_aspirate(self) -> None:
self._core.prepare_to_aspirate()

@requires_version(2, 16)
def configure_nozzle_layout( # noqa: C901
def configure_nozzle_layout(
self,
style: NozzleLayout,
start: Optional[str] = None,
Expand All @@ -2005,16 +2005,19 @@ def configure_nozzle_layout( # noqa: C901
tips from a tip rack that is in an adapter, the API will raise an error.
:param style: The shape of the nozzle layout.
You must :ref:`import the layout constant <nozzle-layouts>` in order to use it.
- ``SINGLE`` sets the pipette to use 1 nozzle. This corresponds to a single of well on labware.
- ``COLUMN`` sets the pipette to use 8 nozzles, aligned from front to back
with respect to the deck. This corresponds to a column of wells on labware.
- ``PARTIAL_COLUMN`` sets the pipette to use 2-7 nozzles, aligned from front to back
with respect to the deck.
- ``ROW`` sets the pipette to use 12 nozzles, aligned from left to right
with respect to the deck. This corresponds to a row of wells on labware.
- ``ALL`` resets the pipette to use all of its nozzles. Calling
``configure_nozzle_layout`` with no arguments also resets the pipette.
- ``COLUMN`` sets a 96-channel pipette to use 8 nozzles, aligned from front to back
with respect to the deck. This corresponds to a column of wells on labware.
For 8-channel pipettes, use ``ALL`` instead.
- ``PARTIAL_COLUMN`` sets an 8-channel pipette to use 2--7 nozzles, aligned from front to back
with respect to the deck. Not compatible with the 96-channel pipette.
- ``ROW`` sets a 96-channel pipette to use 12 nozzles, aligned from left to right
with respect to the deck. This corresponds to a row of wells on labware.
Not compatible with 8-channel pipettes.
- ``SINGLE`` sets the pipette to use 1 nozzle. This corresponds to a single well on labware.
:type style: ``NozzleLayout`` or ``None``
:param start: The primary nozzle of the layout, which the robot uses
Expand All @@ -2032,16 +2035,15 @@ def configure_nozzle_layout( # noqa: C901
should be of the same format used when identifying wells by name.
Required when setting ``style=PARTIAL_COLUMN``.
.. note::
Nozzle layouts numbering between 2-7 nozzles, account for the distance from
``start``. For example, 4 nozzles would require ``start="H1"`` and ``end="E1"``.
:type end: str or ``None``
:param tip_racks: Behaves the same as setting the ``tip_racks`` parameter of
:py:meth:`.load_instrument`. If not specified, the new configuration resets
:py:obj:`.InstrumentContext.tip_racks` and you must specify the location
every time you call :py:meth:`~.InstrumentContext.pick_up_tip`.
:type tip_racks: List[:py:class:`.Labware`]
.. versionchanged:: 2.20
Added partial column, row, and single layouts.
"""
# TODO: add the following back into the docstring when QUADRANT is supported
#
Expand All @@ -2057,55 +2059,69 @@ def configure_nozzle_layout( # noqa: C901
NozzleLayout.QUADRANT,
]
if style in disabled_layouts:
raise ValueError(
f"Nozzle layout configuration of style {style.value} is currently unsupported."
raise UnsupportedAPIError(
message=f"Nozzle layout configuration of style {style.value} is currently unsupported."
)

original_enabled_layouts = [NozzleLayout.COLUMN, NozzleLayout.ALL]
if (
self._api_version
< _PARTIAL_NOZZLE_CONFIGURATION_SINGLE_ROW_PARTIAL_COLUMN_ADDED_IN
) and (style not in original_enabled_layouts):
raise ValueError(
raise APIVersionError(
f"Nozzle layout configuration of style {style.value} is unsupported in API Versions lower than {_PARTIAL_NOZZLE_CONFIGURATION_SINGLE_ROW_PARTIAL_COLUMN_ADDED_IN}."
)

if style != NozzleLayout.ALL:
if start is None:
raise ValueError(
f"Cannot configure a nozzle layout of style {style.value} without a starting nozzle."
front_right_resolved = front_right
back_left_resolved = back_left
validated_start: Optional[str] = None
match style:
case NozzleLayout.SINGLE:
validated_start = _check_valid_start_nozzle(style, start)
_raise_if_has_end_or_front_right_or_back_left(
style, end, front_right, back_left
)
if start not in types.ALLOWED_PRIMARY_NOZZLES:
raise ValueError(
f"Starting nozzle specified is not one of {types.ALLOWED_PRIMARY_NOZZLES}"
case NozzleLayout.COLUMN | NozzleLayout.ROW:
self._raise_if_configuration_not_supported_by_pipette(style)
validated_start = _check_valid_start_nozzle(style, start)
_raise_if_has_end_or_front_right_or_back_left(
style, end, front_right, back_left
)
if style == NozzleLayout.QUADRANT:
if front_right is None and back_left is None:
raise ValueError(
"Cannot configure a QUADRANT layout without a front right or back left nozzle."
case NozzleLayout.PARTIAL_COLUMN:
self._raise_if_configuration_not_supported_by_pipette(style)
validated_start = _check_valid_start_nozzle(style, start)
validated_end = _check_valid_end_nozzle(validated_start, end)
_raise_if_has_front_right_or_back_left_for_partial_column(
front_right, back_left
)
elif not (front_right is None and back_left is None):
raise ValueError(
f"Parameters 'front_right' and 'back_left' cannot be used with {style.value} Nozzle Configuration Layout."
)

front_right_resolved = front_right
back_left_resolved = back_left
if style == NozzleLayout.PARTIAL_COLUMN:
if end is None:
raise ValueError(
"Parameter 'end' is required for Partial Column Nozzle Configuration Layout."
# Convert 'validated_end' to front_right or back_left as appropriate
if validated_start == "H1" or validated_start == "H12":
back_left_resolved = validated_end
front_right_resolved = validated_start
elif start == "A1" or start == "A12":
front_right_resolved = validated_end
back_left_resolved = validated_start
case NozzleLayout.QUADRANT:
validated_start = _check_valid_start_nozzle(style, start)
_raise_if_has_end_nozzle_for_quadrant(end)
_raise_if_no_front_right_or_back_left_for_quadrant(
front_right, back_left
)

# Determine if 'end' will be configured as front_right or back_left
if start == "H1" or start == "H12":
back_left_resolved = end
elif start == "A1" or start == "A12":
front_right_resolved = end
if front_right is None:
front_right_resolved = validated_start
elif back_left is None:
back_left_resolved = validated_start
case NozzleLayout.ALL:
validated_start = start
if any([start, end, front_right, back_left]):
_log.warning(
"Parameters 'start', 'end', 'front_right', 'back_left' specified"
" for ALL nozzle configuration will be ignored."
)

self._core.configure_nozzle_layout(
style,
primary_nozzle=start,
primary_nozzle=validated_start,
front_right_nozzle=front_right_resolved,
back_left_nozzle=back_left_resolved,
)
Expand Down Expand Up @@ -2152,3 +2168,91 @@ def measure_liquid_height(self, well: labware.Well) -> float:
self._96_tip_config_valid()
height = self._core.liquid_probe_without_recovery(well._core, loc)
return height

def _raise_if_configuration_not_supported_by_pipette(
self, style: NozzleLayout
) -> None:
match style:
case NozzleLayout.COLUMN | NozzleLayout.ROW:
if self.channels != 96:
raise ValueError(
f"{style.value} configuration is only supported on 96-Channel pipettes."
)
case NozzleLayout.PARTIAL_COLUMN:
if self.channels != 8:
raise ValueError(
"Partial column configuration is only supported on 8-Channel pipettes."
)
# SINGLE, QUADRANT and ALL are supported by all pipettes


def _raise_if_has_end_or_front_right_or_back_left(
style: NozzleLayout,
end: Optional[str],
front_right: Optional[str],
back_left: Optional[str],
) -> None:
if any([end, front_right, back_left]):
raise ValueError(
f"Parameters 'end', 'front_right' and 'back_left' cannot be used with "
f"the {style.name} nozzle configuration."
)


def _check_valid_start_nozzle(style: NozzleLayout, start: Optional[str]) -> str:
if start is None:
raise ValueError(
f"Cannot configure a nozzle layout of style {style.value} without a starting nozzle."
)
if start not in types.ALLOWED_PRIMARY_NOZZLES:
raise ValueError(
f"Starting nozzle specified is not one of {types.ALLOWED_PRIMARY_NOZZLES}."
)
return start


def _check_valid_end_nozzle(start: str, end: Optional[str]) -> str:
if end is None:
raise ValueError("Partial column configurations require the 'end' parameter.")
if start[0] in end:
raise ValueError(
"The 'start' and 'end' parameters of a partial column configuration cannot be in the same row."
)
if start == "H1" or start == "H12":
if "A" in end:
raise ValueError(
f"A partial column configuration with 'start'={start} cannot have its 'end' parameter be in row A. Use `ALL` configuration to utilize all nozzles."
)
elif start == "A1" or start == "A12":
if "H" in end:
raise ValueError(
f"A partial column configuration with 'start'={start} cannot have its 'end' parameter be in row H. Use `ALL` configuration to utilize all nozzles."
)
return end


def _raise_if_no_front_right_or_back_left_for_quadrant(
front_right: Optional[str], back_left: Optional[str]
) -> None:
if front_right is None and back_left is None:
raise ValueError(
"Cannot configure a QUADRANT layout without a front right or back left nozzle."
)


def _raise_if_has_end_nozzle_for_quadrant(end: Optional[str]) -> None:
if end is not None:
raise ValueError(
"Parameter 'end' is not supported for QUADRANT configuration."
" Use 'front_right' and 'back_left' arguments to specify the quadrant nozzle map instead."
)


def _raise_if_has_front_right_or_back_left_for_partial_column(
front_right: Optional[str], back_left: Optional[str]
) -> None:
if any([front_right, back_left]):
raise ValueError(
"Parameters 'front_right' and 'back_left' cannot be used with "
"the PARTIAL_COLUMN configuration."
)
Loading

0 comments on commit 5a8244d

Please sign in to comment.