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

feat(api, shared-data): Add support for labware lids and publish tc lid seal labware #16345

Open
wants to merge 13 commits into
base: edge
Choose a base branch
from

Conversation

CaseyBatten
Copy link
Contributor

@CaseyBatten CaseyBatten commented Sep 24, 2024

Overview

Covers PLAT-356, PLAT-264, PLAT-540

Publicizes new "opentrons_tough_pcr_auto_sealing_lid" labware. Implements the new labware allowedRole "lid" for labware, alongside "lidOffsets" subcategory of the gripper offsets in labware definition. Introduces multi-labware stacking logic to allow for stacks of up to 5 lids. Enforces existing behavior of no more than two traditional labware per stack, unless specified in labware quirks. Handles lid offset behavior during move labware position calculations.

Test Plan and Hands on Testing

  • Ensure existing behavior of labware stacking on adapters and labware on adapters on modules is maintained
  • Ensure lid will be placed on a plate in a thermocycler with a large margin in the X direction. It was found that when the z offset was set to -1, x offsets > 2 mm and less than -1.5 mm would cause the lid to be placed at an angle and prevent the thermocycler lid from closing. This resulted in choosing a thermocycler drop offset of (0.5,0,-1):
"""Protocol to Test the Stacking and Movement of Tough Auto Seal Lid."""
from typing import List, Union
from opentrons.protocol_api import (
    ParameterContext,
    ProtocolContext,
    Labware,
)
from opentrons.protocol_api.module_contexts import (
    ThermocyclerContext,
)

metadata = {"protocolName": "Tough Auto Seal Lid to Thermocycler - with x offset change"}
requirements = {"robotType": "Flex", "apiLevel": "2.20"}

def add_parameters(parameters: ParameterContext) -> None:
    """Add parameters."""
    parameters.add_int(
        variable_name="num_of_stack_ups",
        display_name="Number of Stack Ups",
        choices=[
            {"display_name": "1", "value": 1},
            {"display_name": "10", "value": 10},
            {"display_name": "20", "value": 20},
            {"display_name": "30", "value": 30},
            {"display_name": "40", "value": 40},
        ],
        default=20,
    )

def run(protocol: ProtocolContext) -> None:
    """Runs protocol that moves lids and stacks them."""
    # Load Parameters
    iterations = protocol.params.num_of_stack_ups  # type: ignore[attr-defined]
    # Thermocycler
    thermocycler: ThermocyclerContext = protocol.load_module(
        "thermocyclerModuleV2"
    )  # type: ignore[assignment]
    plate_in_cycler = thermocycler.load_labware(
        "armadillo_96_wellplate_200ul_pcr_full_skirt"
    )
    thermocycler.open_lid()

    lids: List[Labware] = [
        protocol.load_labware("opentrons_tough_pcr_auto_sealing_lid", "D2")
    ]
    for i in range(4):
        lids.append(lids[-1].load_labware("opentrons_tough_pcr_auto_sealing_lid"))
    lids.reverse()
    top_lid = lids[0]
    second_from_top = lids[1]
    
    slot = 0
    x_offset = [-1.5, 0.5, 2.0]
    for iteration in range(iterations - 1):
        for offset in x_offset:
            OFFSET_GUIDES = {
                "drop": {"x": offset, "y": 0, "z": -1},
            }
            protocol.comment(f"Stack up {iteration}, x offset {offset}")
            # move lid to plate in thermocycler
            protocol.move_labware(top_lid, plate_in_cycler, use_gripper=True, drop_offset=OFFSET_GUIDES["drop"])
            # move lid to deck slot
            thermocycler.close_lid()
            thermocycler.open_lid()
            # move lid back to stack
            protocol.move_labware(top_lid, second_from_top, use_gripper=True)
  • Ensure the following protocol runs, moving lids from deck to labware on thermocycler, back to deck in clean stacks:
from typing import List, Dict, Any, Optional
from opentrons.protocol_api import ProtocolContext, Labware

metadata = {"protocolName": "Opentrons Tough Auto-Sealing Lid Test"}
requirements = {"robotType": "Flex", "apiLevel": "2.20"}


"""
Setup:
 - 1-5x lids are stacked in deck D2
 - Thermocycler installed

Run:
 - For each lid in the stack (1-5x)
   - Move lid in D2 to Thermocycler
     - Remove top-most lid
     - PAUSE, wait for tester to press continue
   - Move lid from Thermocycler to new slot C2
     - Stacked onto any previously placed lids
"""

LID_STARTING_SLOT = "D2"
LID_ENDING_SLOT = "C2"
LID_COUNT = 5
LID_DEFINITION = "opentrons_tough_pcr_auto_sealing_lid"
LID_BOTTOM_DEFINITION = "opentrons_tough_pcr_auto_sealing_lid"

USING_THERMOCYCLER = True

OFFSET_DECK = {
    "pick-up": {"x": 0, "y": 0, "z": 0},
    "drop": {"x": 0, "y": 0, "z": 0},
}
OFFSET_THERMOCYCLER = {
    "pick-up": {"x": 0, "y": 0, "z": 0},
    "drop": {"x": 0, "y": 0, "z": 0},
}


def _move_labware_with_offset_and_pause(
    protocol: ProtocolContext,
    labware: Labware,
    destination: Any,
    pick_up_offset: Optional[Dict[str, float]] = None,
    drop_offset: Optional[Dict[str, float]] = None,
) -> None:
    protocol.move_labware(
        labware,
        destination,
        use_gripper=True,
        pick_up_offset=pick_up_offset,
        drop_offset=drop_offset,
    )


def run(protocol: ProtocolContext):
    # SETUP
    lids: List[Labware] = [protocol.load_labware(LID_BOTTOM_DEFINITION, LID_STARTING_SLOT)]
    for i in range(LID_COUNT - 1):
        lids.append(lids[-1].load_labware(LID_DEFINITION))
    lids.reverse()  # NOTE: reversing to more easily loop through lids from top-to-bottom
    if USING_THERMOCYCLER:
        # TODO: confirm if we need to load 96-well adapter onto Thermocycler
        thermocycler = protocol.load_module("thermocyclerModuleV2")
        thermocycler.open_lid()
        plate_in_cycler = thermocycler.load_labware(
            "armadillo_96_wellplate_200ul_pcr_full_skirt"
        )
    else:
        plate_in_cycler = None

    # RUN
    prev_moved_lid: Optional[Labware] = None
    for lid in lids:
        if USING_THERMOCYCLER:
            _move_labware_with_offset_and_pause(
                protocol,
                lid,
                plate_in_cycler,
                pick_up_offset=OFFSET_DECK["pick-up"],
                drop_offset=OFFSET_THERMOCYCLER["drop"],
            )
            _move_labware_with_offset_and_pause(
                protocol,
                lid,
                prev_moved_lid if prev_moved_lid else LID_ENDING_SLOT,
                pick_up_offset=OFFSET_THERMOCYCLER["pick-up"],
                drop_offset=OFFSET_DECK["drop"],
            )
        else:
            _move_labware_with_offset_and_pause(
                protocol,
                lid,
                prev_moved_lid if prev_moved_lid else LID_ENDING_SLOT,
                pick_up_offset=OFFSET_DECK["pick-up"],
                drop_offset=OFFSET_DECK["drop"],
            )
        prev_moved_lid = lid

Changelog

  • Addition of new "lid" allowed role and engine logic to account for it
  • Addition of labware stacking quirk for tc lids. Other stacking quirks not added to js tests but accounted for in engine in case of future need.
  • TC Lid labware is excluded from the LPC flow for clients.
  • TC Lid offset changes for pickup and drop onto deck and thermocycler

Review requests

Risk assessment

Low - new behavior is being gated to this specific use case currently, both by the labware type and the intended module for use. Existing labware stacking behavior has been maintained and is confirmed through test coverage.

Copy link

codecov bot commented Sep 26, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 79.27%. Comparing base (f793e65) to head (ffc8e4e).
Report is 38 commits behind head on edge.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##             edge   #16345       +/-   ##
===========================================
- Coverage   92.43%   79.27%   -13.16%     
===========================================
  Files          77      118       +41     
  Lines        1283     4276     +2993     
===========================================
+ Hits         1186     3390     +2204     
- Misses         97      886      +789     
Flag Coverage Δ
g-code-testing 92.43% <ø> (ø)
shared-data 73.63% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...pentrons_shared_data/labware/labware_definition.py 100.00% <100.00%> (ø)

... and 40 files with indirect coverage changes

@CaseyBatten CaseyBatten marked this pull request as ready for review September 26, 2024 19:30
@CaseyBatten CaseyBatten requested review from a team as code owners September 26, 2024 19:30
@CaseyBatten CaseyBatten requested review from smb2268 and Lucajscarano and removed request for a team September 26, 2024 19:30
@@ -15,6 +15,8 @@ import { getLabwareDefinitionsFromCommands } from '/app/molecules/Command/utils/

import type { RobotType } from '@opentrons/shared-data'

const filtered_labware = ['opentrons_tough_pcr_auto_sealing_lid']
Copy link
Contributor

@smb2268 smb2268 Oct 9, 2024

Choose a reason for hiding this comment

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

This snake case variable will likely fail our linter - if you change to all caps or camel case it should work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants