Skip to content

Commit

Permalink
docs(api): Absorbance Plate Reader in Python API docs (#16668)
Browse files Browse the repository at this point in the history
# Overview

Adds a docs page and publishes API Reference entries for the Absorbance
Plate Reader.

Addresses RTC-488, RTC-508.

## Test Plan and Hands on Testing


[Sandbox](http://sandbox.docs.opentrons.com/docs-plate-reader/v2/modules/absorbance_plate_reader.html)

## Changelog

- Adds a new page, Absorbance Plate Reader Module, under Hardware
Modules
- Links to new page from Hardware Modules landing page
- Adds Absorbance Plate Reader to Module Setup page
- Adds H3s for modules in API Reference for easier navigation
- Edits to `AbsorbanceReaderContext` docstrings in `module_contexts.py`
- Cleaned up malformed note syntax _for Thermocycler_ in
`module_contexts.py`
- Removed nitpick rule from `conf.py` now that `AbsorbanceReaderContext`
is published
- 🆕 Instruct authors not to detect CSV dialect when using plate reader
output as RTP.

## Review requests

- Check that all descriptions are true to how the plate reader behaves.
- Double check that my code simulates — I have done this once.
- Do we need more information in any area, especially for CSV files?
  - Should we provide a sample? If so, where to put it?
- ✅ Should we give more concrete guidance on parsing the CSV other than
"you know how to write code, yeah"?
- 🆕 Do we need to list the four `wavelengths` values that actually
function on a default HW config?

## Risk assessment

v low, docs only
  • Loading branch information
ecormany authored Nov 18, 2024
1 parent 57ea4ae commit 91bab8c
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 32 deletions.
1 change: 0 additions & 1 deletion api/docs/v2/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,6 @@
("py:class", r".*protocol_api\.config.*"),
("py:class", r".*opentrons_shared_data.*"),
("py:class", r".*protocol_api._parameters.Parameters.*"),
("py:class", r".*AbsorbanceReaderContext"),
("py:class", r".*RobotContext"), # shh it's a secret (for now)
("py:class", r'.*AbstractLabware|APIVersion|LabwareLike|LoadedCoreMap|ModuleTypes|NoneType|OffDeckType|ProtocolCore|WellCore'), # laundry list of not fully qualified things
]
147 changes: 147 additions & 0 deletions api/docs/v2/modules/absorbance_plate_reader.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
:og:description: How to use the Absorbance Plate Reader Module in a Python protocol.

.. _absorbance-plate-reader-module:

******************************
Absorbance Plate Reader Module
******************************

The Absorbance Plate Reader Module is an on-deck microplate spectrophotometer that works with the Flex robot only. The module uses light absorbance to determine sample concentrations in 96-well plates.

The Absorbance Plate Reader is represented in code by an :py:class:`.AbsorbanceReaderContext` object, which has methods for moving the module lid with the Flex Gripper, initializing the module to read at a single wavelength or multiple wavelengths, and reading a plate. With the Python Protocol API, you can process plate reader data immediately in your protocol or export it to a CSV for post-run use.

This page explains the actions necessary for using the Absorbance Plate Reader. These combine to form the typical reader workflow:

1. Close the lid with no plate inside
2. Initialize the reader
3. Open the lid
4. Move a plate onto the module
5. Close the lid
6. Read the plate


Loading and Deck Slots
======================

The Absorbance Plate Reader can only be loaded in slots A3–D3. If you try to load it in any other slot, the API will raise an error. The module's caddy is designed such that the detection unit is in deck column 3 and the special staging area for the lid/illumination unit is in deck column 4. You can't load or move other labware on the Absorbance Plate Reader caddy in deck column 4, even while the lid is in the closed position (on top of the detection unit in deck column 3).

The examples in this section will use an Absorbance Plate Reader Module loaded as follows::

pr_mod = protocol.load_module(
module_name="absorbanceReaderV1",
location="D3"
)

.. versionadded:: 2.21

Lid Control
===========

Flex uses the gripper to move the lid between its two positions.

- :py:meth:`~.AbsorbanceReaderContext.open_lid()` moves the lid to the righthand side of the caddy, in deck column 4.
- :py:meth:`~.AbsorbanceReaderContext.close_lid()` moves the lid onto the detection unit, in deck column 3.

If you call ``open_lid()`` or ``close_lid()`` and the lid is already in the corresponding position, the method will succeed immediately. You can also check the position of the lid with :py:meth:`~.AbsorbanceReaderContext.is_lid_on()`.

You need to call ``close_lid()`` before initializing the reader, even if the reader was in the closed position at the start of the protocol.

.. warning::
Do not move the lid manually, during or outside of a protocol. The API does not allow manual lid movement because there is a risk of damaging the module.

.. _absorbance-initialization:

Initialization
==============

Initializing the reader prepares it to read a plate later in your protocol. The :py:meth:`.AbsorbanceReaderContext.initialize` method accepts parameters for the number of readings you want to take, the wavelengths to read, and whether you want to compare the reading to a reference wavelength. In the default hardware configuration, the supported wavelengths are 450 nm (blue), 562 nm (green), 600 nm (orange), and 650 nm (red).

The module uses these parameters immediately to perform the physical initialization. Additionally, the API preserves these values and uses them when you read the plate later in your protocol.

Let's take a look at examples of how to combine these parameters to prepare different types of readings. The simplest reading measures one wavelength, with no reference wavelength::

pr_mod.initialize(mode="single", wavelengths=[450])

.. versionadded:: 2.21

Now the reader is prepared to read at 450 nm. Note that the ``wavelengths`` parameter always takes a list of integer wavelengths, even when only reading a single wavelength.

This example can be extended by adding a reference wavelength::

pr_mod.initialize(
mode="single", wavelengths=[450], reference_wavelength=[562]
)

When configured this way, the module will read twice. In the :ref:`output data <plate-reader-data>`, the values read for ``reference_wavelength`` will be subtracted from the values read for the single member of ``wavelengths``. This is useful for normalization, or to correct for background interference in wavelength measurements.

The reader can also be initialized to take multiple measurements. When ``mode="multi"``, the ``wavelengths`` list can have up to six elements. This example will initialize the reader to read at three wavelengths::

pr_mod.initialize(mode="multi", wavelengths=[450, 562, 600])

You can't use a reference wavelength when performing multiple measurements.


Reading a Plate
===============

Use :py:meth:`.AbsorbanceReaderContext.read` to have the module read the plate, using the parameters that you specified during initialization::

pr_data = pr_mod.read()

.. versionadded:: 2.21

The ``read()`` method returns the results in a dictionary, which the above example saves to the variable ``pr_data``.

If you need to access this data after the conclusion of your protocol, add the ``export_filename`` parameter to instruct the API to output a CSV file, which is available in the Opentrons App by going to your Flex and viewing Recent Protocol Runs::

pr_data = pr_mod.read(export_filename="plate_data")

In the above example, the API both saves the data to a variable and outputs a CSV file. If you only need the data post-run, you can omit the variable assignment.

.. _plate-reader-data:

Using Plate Reader Data
=======================

There are two ways to use output data from the Absorbance Plate Reader:

- Within your protocol as a nested dictionary object.
- Outside of your protocol, as a tabular CSV file.

The two formats are structured differently, even though they contain the same measurement data.

Dictionary Data
---------------

The dictionary object returned by ``read()`` has two nested levels. The keys at the top level are the wavelengths you provided to ``initialize()``. The keys at the second level are string names of each of the 96 wells, ``"A1"`` through ``"H12"``. The values at the second level are the measured values for each wells. These values are floating point numbers, representing the optical density (OD) of the samples in each well. OD ranges from 0.0 (low sample concentration) to 4.0 (high sample concentration).

The nested dictionary structure allows you to access results by index later in your protocol. This example initializes a multiple read and then accesses different portions of the results::

# initializing and reading
pr_mod.initialize(mode="multi", wavelengths=[450, 600])
pr_mod.open_lid()
protocol.move_labware(plate, pr_mod, use_gripper=True)
pr_mod.close_lid()
pr_data = pr_mod.read()

# accessing results
pr_data[450]["A1"] # value for well A1 at 450 nm
pr_data[600]["H12"] # value for well H12 at 600 nm
pr_data[450] # dict of all wells at 450 nm

You can write additional code to transform this data in any way that you need. For example, you could use a list comprehension to create a list of only the 450 nm values for column 1, ordered by well from A1 to H1::

[pr_data[450][w.well_name] for w in plate.columns()[0]]

.. _absorbance-csv:

CSV data
--------

The CSV exported when specifying ``export_filename`` consists of tabular data followed by additional information. Each measurement produces 9 rows in the CSV file, representing the layout of the well plate that has been read. These rows form a table with numeric labels in the first row and alphabetic labels in the first column, as you would see on physical labware. Each "cell" of the table contains the measured OD value for the well (0.0–4.0) in the corresponding position on the plate.

Additional information, starting with one blank labware grid, is output at the end of the file. The last few lines of the file list the sample wavelengths, serial number of the module, and timestamps for when measurement started and finished.

Each output file for your protocol is available in the Opentrons App by going to your Flex and viewing Recent Protocol Runs. After downloading the file from your Flex, you can read it with any software that reads CSV files, and you can write additional code to parse and act upon its contents.

You can also select the output CSV as the value of a CSV runtime parameter in a subsequent protocol. When you :ref:`parse the CSV data <rtp-csv-data>`, make sure to set ``detect_dialect=False``, or the API will raise an error.
7 changes: 5 additions & 2 deletions api/docs/v2/modules/setup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Available Modules
The first parameter of :py:meth:`.ProtocolContext.load_module` is the module's *API load name*. The load name tells your robot which module you're going to use in a protocol. The table below lists the API load names for the currently available modules.

.. table::
:widths: 4 5 2
:widths: 4 4 2

+--------------------+-------------------------------+---------------------------+
| Module | API Load Name | Introduced in API Version |
Expand Down Expand Up @@ -95,6 +95,9 @@ The first parameter of :py:meth:`.ProtocolContext.load_module` is the module's
| Magnetic Block | ``magneticBlockV1`` | 2.15 |
| GEN1 | | |
+--------------------+-------------------------------+---------------------------+
| Absorbance Plate | ``absorbanceReaderV1`` | 2.21 |
| Reader Module | | |
+--------------------+-------------------------------+---------------------------+

Some modules were added to our Python API later than others, and others span multiple hardware generations. When writing a protocol that requires a module, make sure your ``requirements`` or ``metadata`` code block specifies an :ref:`API version <v2-versioning>` high enough to support all the module generations you want to use.

Expand Down Expand Up @@ -124,7 +127,7 @@ Any :ref:`custom labware <v2-custom-labware>` added to your Opentrons App is als
Module and Labware Compatibility
--------------------------------

It's your responsibility to ensure the labware and module combinations you load together work together. The Protocol API won't raise a warning or error if you load an unusual combination, like placing a tube rack on a Thermocycler. See `What labware can I use with my modules? <https://support.opentrons.com/s/article/What-labware-can-I-use-with-my-modules>`_ for more information about labware/module combinations.
It's your responsibility to ensure the labware and module combinations you load together work together. The API generally won't raise a warning or error if you load an unusual combination, like placing a tube rack on a Thermocycler. The API will raise an error if you try to load a labware on an unsupported adapter. When working with custom labware and module adapters, be sure to add stacking offsets for the adapter to your custom labware definition.


Additional Labware Parameters
Expand Down
4 changes: 3 additions & 1 deletion api/docs/v2/new_modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Hardware Modules

.. toctree::
modules/setup
modules/absorbance_plate_reader
modules/heater_shaker
modules/magnetic_block
modules/magnetic_module
Expand All @@ -17,13 +18,14 @@ Hardware Modules

Hardware modules are powered and unpowered deck-mounted peripherals. The Flex and OT-2 are aware of deck-mounted powered modules when they're attached via a USB connection and used in an uploaded protocol. The robots do not know about unpowered modules until you use one in a protocol and upload it to the Opentrons App.

Powered modules include the Heater-Shaker Module, Magnetic Module, Temperature Module, and Thermocycler Module. The 96-well Magnetic Block is an unpowered module.
Powered modules include the Absorbance Plate Reader Module, Heater-Shaker Module, Magnetic Module, Temperature Module, and Thermocycler Module. The 96-well Magnetic Block is an unpowered module.

Pages in this section of the documentation cover:

- :ref:`Setting up modules and their labware <module-setup>`.
- Working with the module contexts for each type of module.

- :ref:`Absorbance Plate Reader Module <absorbance-plate-reader-module>`
- :ref:`Heater-Shaker Module <heater-shaker-module>`
- :ref:`Magnetic Block <magnetic-block>`
- :ref:`Magnetic Module <magnetic-module>`
Expand Down
26 changes: 25 additions & 1 deletion api/docs/v2/new_protocol_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,29 +53,53 @@ Wells and Liquids
Modules
=======

Absorbance Plate Reader
-----------------------

.. autoclass:: opentrons.protocol_api.AbsorbanceReaderContext
:members:
:exclude-members: broker, geometry, load_labware_object, load_adapter, load_adapter_from_definition
:inherited-members:


Heater-Shaker
-------------

.. autoclass:: opentrons.protocol_api.HeaterShakerContext
:members:
:exclude-members: broker, geometry, load_labware_object
:inherited-members:

Magnetic Block
--------------

.. autoclass:: opentrons.protocol_api.MagneticBlockContext
:members:
:exclude-members: broker, geometry, load_labware_object
:inherited-members:

Magnetic Module
---------------

.. autoclass:: opentrons.protocol_api.MagneticModuleContext
:members:
:exclude-members: calibrate, broker, geometry, load_labware_object
:inherited-members:

Temperature Module
------------------

.. autoclass:: opentrons.protocol_api.TemperatureModuleContext
:members:
:exclude-members: start_set_temperature, await_temperature, broker, geometry, load_labware_object
:inherited-members:

Thermocycler
------------

.. autoclass:: opentrons.protocol_api.ThermocyclerContext
:members:
:exclude-members: total_step_count, current_cycle_index, total_cycle_count, hold_time, ramp_rate, current_step_index, broker, geometry, load_labware_object
:exclude-members: total_step_count, current_cycle_index, total_cycle_count, hold_time, ramp_rate, current_step_index, broker, geometry, load_labware_object, load_adapter, load_adapter_from_definition
:inherited-members:


Expand Down
Loading

0 comments on commit 91bab8c

Please sign in to comment.