From 7cd73652648c443113e6597c5fb95ab063c558fe Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Thu, 19 Sep 2024 17:53:12 -0400 Subject: [PATCH 1/3] disable tip presence on 8ch pipette 1 thru 3 nozzles --- .../protocol_engine/execution/tip_handler.py | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/api/src/opentrons/protocol_engine/execution/tip_handler.py b/api/src/opentrons/protocol_engine/execution/tip_handler.py index 937e4abf9d8..11ce83586b3 100644 --- a/api/src/opentrons/protocol_engine/execution/tip_handler.py +++ b/api/src/opentrons/protocol_engine/execution/tip_handler.py @@ -302,12 +302,26 @@ async def verify_tip_presence( This function will raise an exception if the specified tip presence status isn't matched. """ + nozzle_configuration = ( + self._state_view.pipettes.state.nozzle_configuration_by_id[pipette_id] + ) + + # Configuration metrics by which tip presence checking is ignored + unsupported_pipette_types = [8, 96] + unsupported_layout_types = [ + NozzleConfigurationType.SINGLE, + NozzleConfigurationType.COLUMN, + ] + supported_partial_nozzle_minimum = 4 + if ( - self._state_view.pipettes.get_nozzle_layout_type(pipette_id) - == NozzleConfigurationType.SINGLE - and self._state_view.pipettes.get_channels(pipette_id) == 96 + nozzle_configuration is not None + and self._state_view.pipettes.get_channels(pipette_id) + in unsupported_pipette_types + and nozzle_configuration.configuration in unsupported_layout_types + and len(nozzle_configuration.map_store) < supported_partial_nozzle_minimum ): - # Tip presence sensing is not supported for single tip pick up on the 96ch Flex Pipette + # Tip presence sensing is not supported for single tip pick up on the 96ch Flex Pipette, nor with single and some partial layous of the 8ch Flex Pipette return try: ot3api = ensure_ot3_hardware(hardware_api=self._hardware_api) From b5ca3732df27c44063253f72383c6c8fb12de7c4 Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Fri, 20 Sep 2024 11:10:47 -0400 Subject: [PATCH 2/3] update tip handler test cases --- .../execution/test_tip_handler.py | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/api/tests/opentrons/protocol_engine/execution/test_tip_handler.py b/api/tests/opentrons/protocol_engine/execution/test_tip_handler.py index dfd02e9dfd5..b49f48bdb21 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_tip_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_tip_handler.py @@ -3,10 +3,10 @@ from decoy import Decoy from mock import AsyncMock, patch -from typing import Dict, ContextManager, Optional +from typing import Dict, ContextManager, Optional, OrderedDict from contextlib import nullcontext as does_not_raise -from opentrons.types import Mount, MountType +from opentrons.types import Mount, MountType, Point from opentrons.hardware_control import API as HardwareAPI from opentrons.hardware_control.types import TipStateType from opentrons.hardware_control.protocols.types import OT2RobotType, FlexRobotType @@ -25,6 +25,8 @@ VirtualTipHandler, create_tip_handler, ) +from opentrons.hardware_control.nozzle_manager import NozzleMap +from opentrons_shared_data.pipette.pipette_definition import ValidNozzleMaps @pytest.fixture @@ -53,6 +55,17 @@ def tip_rack_definition() -> LabwareDefinition: return LabwareDefinition.construct(namespace="test", version=42) # type: ignore[call-arg] +MOCK_MAP = NozzleMap.build( + physical_nozzles=OrderedDict({"A1": Point(0, 0, 0)}), + physical_rows=OrderedDict({"A": ["A1"]}), + physical_columns=OrderedDict({"1": ["A1"]}), + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A1", + valid_nozzle_maps=ValidNozzleMaps(maps={"Full": ["A1"]}), +) + + async def test_create_tip_handler( decoy: Decoy, mock_state_view: StateView, @@ -102,6 +115,9 @@ async def test_flex_pick_up_tip_state( decoy.when(mock_state_view.pipettes.get_mount("pipette-id")).then_return( MountType.LEFT ) + decoy.when(mock_state_view.pipettes.state.nozzle_configuration_by_id).then_return( + {"pipette-id": MOCK_MAP} + ) decoy.when( mock_state_view.geometry.get_nominal_tip_geometry( pipette_id="pipette-id", @@ -171,6 +187,10 @@ async def test_pick_up_tip( MountType.LEFT ) + decoy.when(mock_state_view.pipettes.state.nozzle_configuration_by_id).then_return( + {"pipette-id": MOCK_MAP} + ) + decoy.when( mock_state_view.geometry.get_nominal_tip_geometry( pipette_id="pipette-id", @@ -225,6 +245,9 @@ async def test_drop_tip( decoy.when(mock_state_view.pipettes.get_mount("pipette-id")).then_return( MountType.RIGHT ) + decoy.when(mock_state_view.pipettes.state.nozzle_configuration_by_id).then_return( + {"pipette-id": MOCK_MAP} + ) await subject.drop_tip(pipette_id="pipette-id", home_after=True) @@ -499,6 +522,11 @@ async def test_verify_tip_presence_on_ot3( decoy.when(mock_state_view.pipettes.get_mount("pipette-id")).then_return( MountType.LEFT ) + + decoy.when( + mock_state_view.pipettes.state.nozzle_configuration_by_id + ).then_return({"pipette-id": MOCK_MAP}) + await subject.verify_tip_presence("pipette-id", expected, None) decoy.verify( From 41d0c461dad81c4136854601eb41a70d5580a960 Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Fri, 20 Sep 2024 11:17:37 -0400 Subject: [PATCH 3/3] comments detailing tip presence disabling qualifications --- api/src/opentrons/protocol_engine/execution/tip_handler.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_engine/execution/tip_handler.py b/api/src/opentrons/protocol_engine/execution/tip_handler.py index 11ce83586b3..af36137c842 100644 --- a/api/src/opentrons/protocol_engine/execution/tip_handler.py +++ b/api/src/opentrons/protocol_engine/execution/tip_handler.py @@ -312,6 +312,7 @@ async def verify_tip_presence( NozzleConfigurationType.SINGLE, NozzleConfigurationType.COLUMN, ] + # NOTE: (09-20-2024) Current on multi-channel pipettes, utilizing less than 4 nozzles risks false positives on the tip presence sensor supported_partial_nozzle_minimum = 4 if ( @@ -321,7 +322,9 @@ async def verify_tip_presence( and nozzle_configuration.configuration in unsupported_layout_types and len(nozzle_configuration.map_store) < supported_partial_nozzle_minimum ): - # Tip presence sensing is not supported for single tip pick up on the 96ch Flex Pipette, nor with single and some partial layous of the 8ch Flex Pipette + # Tip presence sensing is not supported for single tip pick up on the 96ch Flex Pipette, nor with single and some partial layous of the 8ch Flex Pipette. + # This is due in part to a press distance tolerance which creates a risk case for false positives. In the case of single tip, the mechanical tolerance + # for presses with 100% success is below the minimum average achieved press distance for a given multi channel pipette in that configuration. return try: ot3api = ensure_ot3_hardware(hardware_api=self._hardware_api)