Skip to content

Commit

Permalink
Merge branch 'edge' into app_localization-feature-flag
Browse files Browse the repository at this point in the history
  • Loading branch information
brenthagen committed Sep 26, 2024
2 parents d794477 + 7100b00 commit baa0e07
Show file tree
Hide file tree
Showing 2,231 changed files with 14,733 additions and 6,536 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
'prettier',
'plugin:json/recommended',
'plugin:storybook/recommended',
'plugin:react/jsx-runtime',
],

plugins: ['react', 'react-hooks', 'json', 'testing-library'],
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/opentrons-ai-server-lint-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Setup Python
uses: 'actions/setup-python@v5'
with:
python-version: '3.12.4'
python-version: '3.12'
cache: 'pipenv'
cache-dependency-path: opentrons-ai-server/Pipfile.lock
- name: Setup
Expand Down
1 change: 0 additions & 1 deletion .storybook/preview.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react'
import { I18nextProvider } from 'react-i18next'
import { GlobalStyle } from '../app/src/atoms/GlobalStyle'
import { i18n } from '../app/src/i18n'
Expand Down
Binary file added api/docs/img/partial-pickup-deck-extents.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 7 additions & 7 deletions api/docs/v2/pipettes/characteristics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,18 +158,18 @@ These flow rate properties operate independently. This means you can specify dif
Let's tell the robot to aspirate, dispense, and blow out the liquid using default flow rates. Notice how you don't need to specify a ``flow_rate`` attribute to use the defaults::

pipette.aspirate(200, plate["A1"]) # 160 µL/s
pipette.dispense(200, plate["A2"]) # 160 µL/s
pipette.blow_out() # 80 µL/s
pipette.aspirate(200, plate["A1"]) # 716 µL/s
pipette.dispense(200, plate["A2"]) # 716 µL/s
pipette.blow_out() # 716 µL/s

Now let's change the flow rates for each action::

pipette.flow_rate.aspirate = 50
pipette.flow_rate.dispense = 100
pipette.flow_rate.blow_out = 75
pipette.flow_rate.blow_out = 300
pipette.aspirate(200, plate["A1"]) # 50 µL/s
pipette.dispense(200, plate["A2"]) # 100 µL/s
pipette.blow_out() # 75 µL/s
pipette.blow_out() # 300 µL/s
These flow rates will remain in effect until you change the ``flow_rate`` attribute again *or* call ``configure_for_volume()``. Calling ``configure_for_volume()`` always resets all pipette flow rates to the defaults for the mode that it sets.

Expand All @@ -184,7 +184,7 @@ These flow rates will remain in effect until you change the ``flow_rate`` attrib
Flex Pipette Flow Rates
-----------------------

The default flow rates for Flex pipettes depend on the maximum volume of the pipette and the capacity of the currently attached tip. For each pipette–tip configuration, the default flow rate is the same for aspirate, dispense, and blowout actions.
Flex pipette flow rates depend on pipette volume and tip capacity. Each pipette–tip combination has a default flow rate for aspirating, dispensing, and blowing out liquid. When using a 50 µL pipette, you should only use 50 µL tips.

.. list-table::
:header-rows: 1
Expand All @@ -193,7 +193,7 @@ The default flow rates for Flex pipettes depend on the maximum volume of the pip
- Tip Capacity (µL)
- Flow Rate (µL/s)
* - 50 µL (1- and 8-channel)
- All capacities
- 50
- 57
* - 1000 µL (1-, 8-, and 96-channel)
- 50
Expand Down
113 changes: 83 additions & 30 deletions api/docs/v2/pipettes/partial_tip_pickup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,10 @@ The ``start`` parameter sets the first and only nozzle used in the configuration
- | Back to front, left to right
| (A1 through H1, A2 through H2, …)
Since they follow the same pickup order as a single-channel pipette, Opentrons recommends using the following configurations:
.. warning::
In certain conditions, tips in adjacent columns may cling to empty nozzles during single-tip pickup. You can avoid this by overriding automatic tip tracking to pick up tips row by row, rather than column by column. The code sample below demonstrates how to pick up tips this way.

- For 8-channel pipettes, ``start="H1"``.
- For 96-channel pipettes, ``start="H12"``.
However, as with all partial tip layouts, be careful that you don't place the pipette in a position where it overlaps more tips than intended.

Here is the start of a protocol that imports the ``SINGLE`` and ``ALL`` layout constants, loads an 8-channel pipette, and sets it to pick up a single tip.

Expand All @@ -210,29 +210,30 @@ Here is the start of a protocol that imports the ``SINGLE`` and ``ALL`` layout c
)
pipette.configure_nozzle_layout(
style=SINGLE,
start="H12",
tip_racks=[partial_rack]
start="H1"
)
.. versionadded:: 2.20

Since this configuration uses ``start="H12"``, it will pick up tips in the usual order::
To pick up tips row by row, first construct a list of all wells in the tip rack ordered from A1, A2 … H11, H12. One way to do this is to use :py:func:`sum` to flatten the list of lists returned by :py:meth:`.Labware.rows`::

pipette.pick_up_tip() # picks up A1 from tip rack
pipette.drop_tip()
pipette.pick_up_tip() # picks up B1 from tip rack
tips_by_row = sum(partial_rack.rows(), [])

.. note::
Then ``pop`` items from the front of the list (index 0) and pass them as the ``location`` of :py:meth:`.pick_up_tip`::

You can pick up tips row by row, rather than column by column, by specifying a location for :py:meth:`.pick_up_tip` each time you use it in ``SINGLE`` configuration. However, as with all partial tip layouts, be careful that you don't place the pipette in a position where it overlaps more tips than intended.
# pick up A1 from tip rack
pipette.pick_up_tip(location=tips_by_row.pop(0))
pipette.drop_tip()
# pick up A2 from tip rack
pipette.pick_up_tip(location=tips_by_row.pop(0))


Partial Column Layout
---------------------

Partial column pickup is available on 8-channel pipettes only. Partial columns contain 2 to 7 consecutive tips in a single column. The pipette always picks up partial columns with its frontmost nozzles (``start="H1"``).

To specify the number of tips to pick up, add the ``end`` parameter when calling :py:meth:`.configure_nozzle_layout`. Use the chart below to determine the end row (G through B) for your desired number of tips. The end column should be the same as your start column (1 or 12).
To specify the number of tips to pick up, add the ``end`` parameter when calling :py:meth:`.configure_nozzle_layout`. Use the chart below to determine the ending nozzle (G1 through B1) for your desired number of tips.

.. list-table::
:stub-columns: 1
Expand All @@ -244,16 +245,21 @@ To specify the number of tips to pick up, add the ``end`` parameter when calling
- 5
- 6
- 7
* - ``end`` row
- G
- F
- E
- D
- C
- B
* - ``end`` nozzle
- G1
- F1
- E1
- D1
- C1
- B1

When picking up 3, 5, 6, or 7 tips, extra tips will be left at the front of each column. You can use these tips with a different nozzle configuration, or you can manually re-rack them at the end of your protocol for future use.

.. warning::
In certain conditions, tips in adjacent columns may cling to empty nozzles during partial-column pickup. You can avoid this by overriding automatic tip tracking to pick up tips row by row, rather than column by column. The code sample below demonstrates how to pick up tips this way.

However, as with all partial tip layouts, be careful that you don't place the pipette in a position where it overlaps more tips than intended.

Here is the start of a protocol that imports the ``PARTIAL_COLUMN`` and ``ALL`` layout constants, loads an 8-channel pipette, and sets it to pick up four tips:

.. code-block:: python
Expand All @@ -274,21 +280,24 @@ Here is the start of a protocol that imports the ``PARTIAL_COLUMN`` and ``ALL``
pipette.configure_nozzle_layout(
style=PARTIAL_COLUMN,
start="H1",
end="E1",
tip_racks=[partial_rack]
end="E1"
)
.. versionadded:: 2.20

This configuration will pick up tips from the back half of column 1, then the front half of column 1, then the back half of column 2, and so on::
When pipetting in partial column configuration, remember that *the frontmost channel of the pipette is its primary channel*. To pick up tips across the back half of the rack, then across the front half of the rack, construct a list of that includes all and only the wells in row D and row H::

pipette.pick_up_tip() # picks up A1-D1 from tip rack
pipette.drop_tip()
pipette.pick_up_tip() # picks up E1-H1 from tip rack
tips_by_row = partial_rack.rows_by_name()["D"] + partial_rack.rows_by_name()["H"]

Then ``pop`` items from the front of the list (index 0) and pass them as the ``location`` of :py:meth:`.pick_up_tip`::

# pick up A1-D1 from tip rack
pipette.pick_up_tip(location=tips_by_row.pop(0))
pipette.drop_tip()
pipette.pick_up_tip() # picks up A2-D2 from tip rack
# pick up A2-D2 from tip rack
pipette.pick_up_tip(location=tips_by_row.pop(0))

When handling liquids in partial column configuration, remember that *the frontmost channel of the pipette is its primary channel*. For example, to use the same configuration as above to transfer liquid from wells A1–D1 to wells A2–D2 on a plate, you must use the wells in row D as the source and destination targets::
To use the same configuration as above to transfer liquid from wells A1–D1 to wells A2–D2 on a plate, you must use the wells in row D as the source and destination targets::

# pipette in 4-nozzle partial column layout
pipette.transfer(
Expand Down Expand Up @@ -361,14 +370,58 @@ This keeps tip tracking consistent across each type of pickup. And it reduces th
Tip Pickup and Conflicts
========================

During partial tip pickup, pipettes move into spaces above adjacent slots. To avoid crashes, the API prevents you from performing partial tip pickup when there is tall labware in these spaces. The current nozzle layout determines which labware can safely occupy adjacent slots.
During partial tip pickup, the pipette moves into spaces above adjacent slots. To avoid crashes, the API prevents you from performing partial tip pickup in locations where the pipette could collide with the outer edges of the robot or labware in the working area. The current nozzle layout, pickup or pipetting location, and adjacent labware determine whether a particular pipetting action is safe to perform.

The API will raise errors for potential labware crashes when using a partial nozzle configuration. Nevertheless, it's a good idea to do the following when working with partial tip pickup:
The API will raise errors for potential crashes when using a partial nozzle configuration. Nevertheless, it's a good idea to do the following when working with partial tip pickup:

- Plan your deck layout carefully. Make a diagram and visualize everywhere the pipette will travel.
- Simulate your protocol and compare the run preview to your expectations of where the pipette will travel.
- Simulate your protocol and compare the output to your expectations of where the pipette will travel.
- Perform a dry run with only tip racks on the deck. Have the Emergency Stop Pendant handy in case you see an impending crash.

Deck Extents
------------

When using partial nozzle configurations around the back, right, and front edges of the deck, there are limitations on how far the pipette can move beyond the outer edge of the deck slot. The API will raise an error if you try to pipette beyond these outer `extents` of the working area.

.. tip::
There are no extents-related limitations on slots B1, B2, C1, and C2. When performing partial pickup and pipetting in these slots, you only have to consider :ref:`possible labware conflicts <partial-labware-conflicts>`.

One way to think of deck extents is in terms of where you can pick up tips or pipette to a 96-well plate loaded in a given slot. These limitations only apply when using a layout that places the pipette further towards the windows of the robot than an ``ALL`` layout would. For example, using a ``ROW`` layout with the frontmost nozzles of the 96-channel pipette, it will never move farther forward than the H row of a labware in slots D1–D3. But using a ``ROW`` layout with the backmost nozzles would bring it farther forward — it could collide with the front window, except that the API prevents it.

The following table summarizes the limitations in place along each side of the deck.

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

* - Deck slots
- Nozzle configuration
- Inaccessible wells
* - A1–D1 (left edge)
- Rightmost column
- None (all wells accessible)
* - A1–D3 (back edge)
- Frontmost row
- Rows A–G
* - A3–D3 (right edge)
- Leftmost column
- Columns 11–12
* - D1–D3 (front edge)
- Backmost row
- Rows F–H

To visualize these limitations, the below deck map shades all wells that have a single limitation in light blue, and all wells that have two limitations in dark blue.

.. image:: ../../img/partial-pickup-deck-extents.png

Multiple limitations occur when you use a ``SINGLE`` configuration that uses the innermost corner nozzle, with respect to the pipette's position on the deck. For example, using nozzle A1 on the 96-channel pipette has multiple limitations in slot D3.

Additionally, column A of plates loaded on a Thermocycler Module is inaccessible by the rightmost nozzles of the 96-channel pipette. Although the API treats such plates as being in slot A1, the physical location of a plate on the Thermocycler is slightly further left than a plate loaded directly on the slot.

.. _partial-labware-conflicts:

Arranging Labware
-----------------

For column pickup, Opentrons recommends using the nozzles in column 12 of the pipette::

pipette.configure_nozzle_layout(
Expand Down
6 changes: 6 additions & 0 deletions api/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ Welcome to the v8.0.0 release of the Opentrons robot software!
- Provides more partial tip pickup configurations. All multi-channel pipettes now support single and partial column pickup, and the Flex 96-channel pipette now supports row pickup.
- Improves homing behavior when a Flex protocol completes or is canceled with liquid-filled tips attached to the pipette.

### Known Issues

- During single-tip or partial-column pickup with a multi-channel pipette, tips in adjacent columns may cling to empty nozzles. Pick up tips row by row, rather than column by column, to avoid this.
- Protocol analysis and `opentrons_simulate` do not raise an error when a protocol tries to detect liquid with a pipette nozzle configuration that doesn't contain a pressure sensor (single-tip pickup with A12 or H1). Avoid using the A12 and H1 nozzles for single-tip pickup if you need to detect liquid presence within wells.
- `opentrons_simulate` describes motion to wells only with respect to the primary channel, regardless of the current pipette nozzle configuration.

---

## Opentrons Robot Software Changes in 7.5.0
Expand Down
14 changes: 11 additions & 3 deletions api/src/opentrons/drivers/absorbance_reader/abstract.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from abc import ABC, abstractmethod
from typing import Dict, List, Tuple
from typing import Dict, List, Optional, Tuple
from opentrons.drivers.types import (
ABSMeasurementMode,
AbsorbanceReaderLidStatus,
AbsorbanceReaderDeviceState,
AbsorbanceReaderPlatePresence,
Expand Down Expand Up @@ -32,11 +33,18 @@ async def get_available_wavelengths(self) -> List[int]:
...

@abstractmethod
async def get_single_measurement(self, wavelength: int) -> List[float]:
async def initialize_measurement(
self,
wavelengths: List[int],
mode: ABSMeasurementMode = ABSMeasurementMode.SINGLE,
reference_wavelength: Optional[int] = None,
) -> None:
"""Initialize measurement for the device in single or multi mode for the given wavelengths"""
...

@abstractmethod
async def initialize_measurement(self, wavelength: int) -> None:
async def get_measurement(self) -> List[List[float]]:
"""Gets one or more measurements based on the current configuration."""
...

@abstractmethod
Expand Down
Loading

0 comments on commit baa0e07

Please sign in to comment.