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

docs(api): Python Protocol API 2.18 #14611

Merged
merged 10 commits into from
May 24, 2024
Merged
3 changes: 2 additions & 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.17
.. |apiLevel| replace:: 2.18
.. |release| replace:: {release}
"""

Expand Down Expand Up @@ -444,5 +444,6 @@
("py:class", r".*protocol_api\.deck.*"),
("py:class", r".*protocol_api\.config.*"),
("py:class", r".*opentrons_shared_data.*"),
("py:class", r".*protocol_api._parameters.Parameters.*"),
("py:class", r'.*AbstractLabware|APIVersion|LabwareLike|LoadedCoreMap|ModuleTypes|NoneType|OffDeckType|ProtocolCore|WellCore'), # laundry list of not fully qualified things
]
3 changes: 0 additions & 3 deletions api/docs/v2/deck_slots.rst
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,6 @@ Starting in API version 2.16, you must load trash bin fixtures in your protocol

.. versionadded:: 2.16

.. note::
The :py:class:`.TrashBin` class doesn't have any callable methods, so you don't have to save the result of ``load_trash_bin()`` to a variable, especially if your protocol only loads a single trash container. Being able to reference the trash bin by name is useful when dealing with multiple trash containers.

Call ``load_trash_bin()`` multiple times to add more than one bin. See :ref:`pipette-trash-containers` for more information on using pipettes with multiple trash bins.

.. _configure-waste-chute:
Expand Down
1 change: 1 addition & 0 deletions api/docs/v2/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Welcome
new_atomic_commands
new_complex_commands
robot_position
runtime_parameters
new_advanced_running
new_examples
adapting_ot2_flex
Expand Down
50 changes: 47 additions & 3 deletions api/docs/v2/new_advanced_running.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@ Since a typical protocol only `defines` the ``run`` function but doesn't `call`
Setting Labware Offsets
-----------------------

All positions relative to labware are adjusted automatically based on labware offset data. When you're running your code in Jupyter Notebook or with ``opentrons_execute``, you need to set your own offsets because you can't perform run setup and Labware Position Check in the Opentrons App or on the Flex touchscreen. For these applications, do the following to calculate and apply labware offsets:
All positions relative to labware are adjusted automatically based on labware offset data. When you're running your code in Jupyter Notebook or with ``opentrons_execute``, you need to set your own offsets because you can't perform run setup and Labware Position Check in the Opentrons App or on the Flex touchscreen.

Creating a Dummy Protocol
^^^^^^^^^^^^^^^^^^^^^^^^^

For advanced control applications, do the following to calculate and apply labware offsets:

1. Create a "dummy" protocol that loads your labware and has each used pipette pick up a tip from a tip rack.
2. Import the dummy protocol to the Opentrons App.
Expand Down Expand Up @@ -118,11 +123,50 @@ This automatically generated code uses generic names for the loaded labware. If

Once you've executed this code in Jupyter Notebook, all subsequent positional calculations for this reservoir in slot 2 will be adjusted 0.1 mm to the right, 0.2 mm to the back, and 0.3 mm up.

Remember, you should only add ``set_offset()`` commands to protocols run outside of the Opentrons App. And you should follow the behavior of Labware Position Check, i.e., *do not* reuse offset measurements unless they apply to the *same labware* in the *same deck slot* on the *same robot*.
Keep in mind that ``set_offset()`` commands will override any labware offsets set by running Labware Position Check in the Opentrons App. And you should follow the behavior of Labware Position Check, i.e., *do not* reuse offset measurements unless they apply to the *same labware type* in the *same deck slot* on the *same robot*.

.. warning::

Improperly reusing offset data may cause your robot to move to an unexpected position or crash against labware, which can lead to incorrect protocol execution or damage your equipment. The same applies when running protocols with ``set_offset()`` commands in the Opentrons App. When in doubt: run Labware Position Check again and update your code!
Improperly reusing offset data may cause your robot to move to an unexpected position or crash against labware, which can lead to incorrect protocol execution or damage your equipment. When in doubt: run Labware Position Check again and update your code!

.. _labware-offset-behavior:

Labware Offset Behavior
^^^^^^^^^^^^^^^^^^^^^^^

How the API applies labware offsets varies depending on the API level of your protocol. This section describes the latest behavior. For details on how offsets work in earlier API versions, see the API reference entry for :py:meth:`.set_offset`.

In the latest API version, offsets apply to labware type–location combinations. For example, if you use ``set_offset()`` on a tip rack, use all the tips, and replace the rack with a fresh one of the same type in the same location, the offsets will apply to the fresh tip rack::

tiprack = protocol.load_labware(
load_name="opentrons_flex_96_tiprack_1000ul", location="D3"
)
tiprack2 = protocol.load_labware(
load_name="opentrons_flex_96_tiprack_1000ul",
location=protocol_api.OFF_DECK,
)
tiprack.set_offset(x=0.1, y=0.1, z=0.1)
protocol.move_labware(
labware=tiprack, new_location=protocol_api.OFF_DECK
) # tiprack has no offset while off-deck
protocol.move_labware(
labware=tiprack2, new_location="D3"
) # tiprack2 now has offset 0.1, 0.1, 0.1

Because offsets apply to combinations of labware type and location, if you want an offset to apply to a piece of labware as it moves around the deck, call ``set_offset()`` again after each movement::

plate = protocol.load_labware(
load_name="corning_96_wellplate_360ul_flat", location="D2"
)
plate.set_offset(
x=-0.1, y=-0.2, z=-0.3
) # plate now has offset -0.1, -0.2, -0.3
protocol.move_labware(
labware=plate, new_location="D3"
) # plate now has offset 0, 0, 0
plate.set_offset(
x=-0.1, y=-0.2, z=-0.3
) # plate again has offset -0.1, -0.2, -0.3

Using Custom Labware
--------------------
Expand Down
4 changes: 4 additions & 0 deletions api/docs/v2/new_labware.rst
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ To use these optional methods, first create a liquid object with :py:meth:`.Prot

Let's examine how these two methods work. The following examples demonstrate how to define colored water samples for a well plate and reservoir.

.. _defining-liquids:

Defining Liquids
================

Expand All @@ -291,6 +293,8 @@ This example uses ``define_liquid`` to create two liquid objects and instantiate

The ``display_color`` parameter accepts a hex color code, which adds a color to that liquid's label when you import your protocol into the Opentrons App. The ``define_liquid`` method accepts standard 3-, 4-, 6-, and 8-character hex color codes.

.. _loading-liquids:

Labeling Wells and Reservoirs
=============================

Expand Down
4 changes: 3 additions & 1 deletion api/docs/v2/new_protocol_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Protocols

.. autoclass:: opentrons.protocol_api.ProtocolContext
:members:
:exclude-members: location_cache, cleanup, clear_commands, params
:exclude-members: location_cache, cleanup, clear_commands

Instruments
===========
Expand All @@ -35,8 +35,10 @@ Labware
signatures, since users should never construct these directly.

.. autoclass:: opentrons.protocol_api.TrashBin()
:members:

.. autoclass:: opentrons.protocol_api.WasteChute()
:members:

Wells and Liquids
=================
Expand Down
53 changes: 53 additions & 0 deletions api/docs/v2/parameters/choosing.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
:og:description: Advice on choosing effective parameters in Opentrons Python protocols.

.. _good-rtps:

************************
Choosing Good Parameters
************************

The first decision you need to make when adding parameters to your protocol is "What should be parameterized?" Your goals in adding parameters should be the following:

1. **Add flexibility.** Accommodate changes from run to run or from lab to lab.
2. **Work efficiently.** Don't burden run setup with too many choices or confusing options.
3. **Avoid errors.** Ensure that every combination of parameters produces an analyzable, runnable protocol.

The trick to choosing good parameters is reasoning through the choices the protocol's users may make. If any of them lead to nonsensical outcomes or errors, adjust the parameters — or how your protocol :ref:`uses parameter values <using-rtp>` — to avoid those situations.

Build on a Task
===============

Consider what scientific task is at the heart of your protocol, and build parameters that contribute to, rather than diverge from it.

For example, it makes sense to add a parameter for number of samples to a DNA prep protocol that uses a particular reagent kit. But it wouldn't make sense to add a parameter for *which reagent kit* to use for DNA prep. That kind of parameter would affect so many aspects of the protocol that it would make more sense to maintain a separate protocol for each kit.

Also consider how a small number of parameters can combine to produce many useful outputs. Take the serial dilution task from the :ref:`tutorial` as an example. We could add just three parameters to it: number of dilutions, dilution factor, and number of rows. Now that single protocol can produce a whole plate that gradually dilutes, a 2×4 grid that rapidly dilutes, and *thousands* of other combinations.

Consider Contradictions
=======================

Here's a common time-saving use of parameters: your protocol requires a 1-channel pipette and an 8-channel pipette, but it doesn't matter which mount they're attached to. Without parameters, you would have to assign the mounts in your protocol. Then if the robot is set up in the reverse configuration, you'd have to either physically swap the pipettes or modify your protocol.

One way to get this information is to ask which mount the 1-channel pipette is on, and which mount the 8-channel pipette is on. But if a technician answers "left" to both questions — even by accident — the API will raise an error, because you can't load two pipettes on a single mount. It's no better to flip things around by asking which pipette is on the left mount, and which pipette is on the right mount. Now the technician can say that both mounts have a 1-channel pipette. This is even more dangerous, because it *might not* raise any errors in analysis. The protocol could run "successfully" on a robot with two 1-channel pipettes, but produce completely unintended results.

The best way to avoid these contradictions is to collapse the two questions into one, with limited choices. Where are the pipettes mounted? Either the 1-channel is on the left and the 8-channel on the right, or the 8-channel is on the left and the 1-channel is on the right. This approach is best for several reasons:

- It avoids analysis errors.
- It avoids potentially dangerous execution errors.
- It only requires answering one question instead of two.
- The :ref:`phrasing of the question and answer <rtp-style>` makes it clear that the protocol requires exactly one of each pipette type.

Set Boundaries
==============

Numerical parameters support minimum and maximum values, which you should set to avoid incorrect inputs that are outside of your protocol's possibile actions.

Consider our earlier example of parameterizing serial dilution. Each of the three numerical parameters have logical upper and lower bounds, which we need to enforce to get sensible results.

- *Number of dilutions* must be between 0 and 11 on a 96-well plate. And it may make sense to require at least 1 dilution.
- *Dilution factor* is a ratio, which we can express as a decimal number that must be between 0 and 1.
- *Number of rows* must be between 1 and 8 on a 96-well plate.

What if you wanted to perform a dilution with 20 repetitions? It's possible with two 96-well plates, or with a 384-well plate. You could set the maximum for the number of dilutions to 24 and allow for these possibilities — either switching the plate type or loading an additional plate based on the provided value.

But what if the technician wanted to do just 8 repetitions on a 384-well plate? That would require an additional parameter, an additional choice by the technician, and additional logic in your protocol code. It's up to you as the protocol author to decide if adding more parameters will make protocol setup overly difficult. Sometimes it's more efficient to work with two or three simple protocols rather than one that's long and complex.
181 changes: 181 additions & 0 deletions api/docs/v2/parameters/defining.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
:og:description: Define and set possible values for parameters in Opentrons Python protocols.

.. _defining-rtp:

*******************
Defining Parameters
*******************

To use parameters, you need to define them in :ref:`a separate function <add-parameters>` within your protocol. Each parameter definition has two main purposes: to specify acceptable values, and to inform the protocol user what the parameter does.

Depending on the :ref:`type of parameter <rtp-types>`, you'll need to specify some or all of the following.

.. list-table::
:header-rows: 1

* - Attribute
- Details
* - ``variable_name``
-
- A unique name for :ref:`referencing the parameter value <using-rtp>` elsewhere in the protocol.
- Must meet the usual requirements for `naming objects in Python <https://docs.python.org/3/reference/lexical_analysis.html#identifiers>`__.
* - ``display_name``
-
- A label for the parameter shown in the Opentrons App or on the touchscreen.
- Maximum 30 characters.
* - ``description``
-
- An optional longer explanation of what the parameter does, or how its values will affect the execution of the protocol.
- Maximum 100 characters.
* - ``default``
-
- The value the parameter will have if the technician makes no changes to it during run setup.
* - ``minimum`` and ``maximum``
-
- For numeric parameters only.
- Allows free entry of any value within the range (inclusive).
- Both values are required.
- Can't be used at the same time as ``choices``.
* - ``choices``
-
- For numeric or string parameters.
- Provides a fixed list of values to choose from.
- Each choice has its own display name and value.
- Can't be used at the same time as ``minimum`` and ``maximum``.
* - ``units``
-
- Optional, for numeric parameters with ``minimum`` and ``maximum`` only.
- Displays after the number during run setup.
- Does not affect the parameter's value or protocol execution.
- Maximum 10 characters.



.. _add-parameters:

The ``add_parameters()`` Function
=================================

All parameter definitions are contained in a Python function, which must be named ``add_parameters`` and takes a single argument. Define ``add_parameters()`` before the ``run()`` function that contains protocol commands.

The examples on this page assume the following definition, which uses the argument name ``parameters``. The type specification of the argument is optional.

.. code-block::

def add_parameters(parameters: protocol_api.Parameters):

Within this function definition, call methods on ``parameters`` to define parameters. The next section demonstrates how each type of parameter has its own method.

.. _rtp-types:

Types of Parameters
===================

The API supports four types of parameters: Boolean (:py:class:`bool`), integer (:py:class:`int`), floating point number (:py:class:`float`), and string (:py:class:`str`). It is not possible to mix types within a single parameter.

Boolean Parameters
------------------

Boolean parameters are ``True`` or ``False`` only.

.. code-block::

parameters.add_bool(
variable_name="dry_run",
display_name="Dry Run",
description="Skip incubation delays and shorten mix steps.",
default=False
)

During run setup, the technician can toggle between the two values. In the Opentrons App, Boolean parameters appear as a toggle switch. On the touchscreen, they appear as *On* or *Off*, for ``True`` and ``False`` respectively.

.. versionadded:: 2.18

Integer Parameters
------------------

Integer parameters either accept a range of numbers or a list of numbers. You must specify one or the other; you can't create an open-ended prompt that accepts any integer.

To specify a range, include ``minimum`` and ``maximum``.

.. code-block::

parameters.add_int(
variable_name="volume",
display_name="Aspirate volume",
description="How much to aspirate from each sample.",
default=20,
minimum=10,
maximum=100,
unit="µL"
)

During run setup, the technician can enter any integer value from the minimum up to the maximum. Entering a value outside of the range will show an error. At that point, they can correct their custom value or restore the default value.

To specify a list of numbers, include ``choices``. Each choice is a dictionary with entries for display name and value. The display names let you briefly explain the effect each choice will have.

.. code-block::

parameters.add_int(
variable_name="volume",
display_name="Aspirate volume",
description="How much to aspirate from each sample.",
default=20,
choices=[
{"display_name": "Low (10 µL)", "value": 10},
{"display_name": "Medium (20 µL)", "value": 20},
{"display_name": "High (50 µL)", "value": 50},
]
)

During run setup, the technician can choose from a menu of the provided choices.

.. versionadded:: 2.18

Float Parameters
----------------

Float parameters either accept a range of numbers or a list of numbers. You must specify one or the other; you can't create an open-ended prompt that accepts any floating point number.

Specifying a range or list is done exactly the same as in the integer examples above. The only difference is that all values must be floating point numbers.

.. code-block::

parameters.add_float(
variable_name="volume",
display_name="Aspirate volume",
description="How much to aspirate from each sample.",
default=5.0,
choices=[
{"display_name": "Low (2.5 µL)", "value": 2.5},
{"display_name": "Medium (5 µL)", "value": 5.0},
{"display_name": "High (10 µL)", "value": 10.0},
]
)

.. versionadded:: 2.18

String Parameters
-----------------

String parameters only accept a list of values. You can't currently prompt for free text entry of a string value.

To specify a list of strings, include ``choices``. Each choice is a dictionary with entries for display name and value. Only the display name will appear during run setup.

A common use for string display names is to provide an easy-to-read version of an API load name. You can also use them to briefly explain the effect each choice will have.

.. code-block::

parameters.add_str(
variable_name="pipette",
display_name="Pipette type",
choices=[
{"display_name": "1-Channel 50 µL", "value": "flex_1channel_50"},
{"display_name": "8-Channel 50 µL", "value": "flex_8channel_50"},
],
default="flex_1channel_50",
)

During run setup, the technician can choose from a menu of the provided choices.

.. versionadded:: 2.18
Loading
Loading