opentrons 8.7.0a7__py3-none-any.whl → 8.8.0a7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/_version.py +2 -2
- opentrons/cli/analyze.py +4 -1
- opentrons/config/__init__.py +7 -0
- opentrons/drivers/asyncio/communication/serial_connection.py +8 -5
- opentrons/drivers/flex_stacker/driver.py +6 -1
- opentrons/drivers/vacuum_module/__init__.py +5 -0
- opentrons/drivers/vacuum_module/abstract.py +93 -0
- opentrons/drivers/vacuum_module/driver.py +208 -0
- opentrons/drivers/vacuum_module/errors.py +39 -0
- opentrons/drivers/vacuum_module/simulator.py +85 -0
- opentrons/drivers/vacuum_module/types.py +79 -0
- opentrons/execute.py +3 -0
- opentrons/hardware_control/backends/flex_protocol.py +2 -0
- opentrons/hardware_control/backends/ot3controller.py +35 -2
- opentrons/hardware_control/backends/ot3simulator.py +2 -0
- opentrons/hardware_control/backends/ot3utils.py +37 -0
- opentrons/hardware_control/module_control.py +23 -2
- opentrons/hardware_control/modules/mod_abc.py +1 -1
- opentrons/hardware_control/modules/types.py +1 -1
- opentrons/hardware_control/motion_utilities.py +6 -6
- opentrons/hardware_control/ot3api.py +62 -13
- opentrons/hardware_control/protocols/gripper_controller.py +1 -0
- opentrons/hardware_control/protocols/liquid_handler.py +6 -2
- opentrons/hardware_control/types.py +12 -0
- opentrons/legacy_commands/commands.py +58 -5
- opentrons/legacy_commands/module_commands.py +29 -0
- opentrons/legacy_commands/protocol_commands.py +33 -1
- opentrons/legacy_commands/types.py +75 -1
- opentrons/protocol_api/_transfer_liquid_validation.py +17 -2
- opentrons/protocol_api/_types.py +2 -0
- opentrons/protocol_api/core/engine/_default_labware_versions.py +1 -0
- opentrons/protocol_api/core/engine/deck_conflict.py +3 -1
- opentrons/protocol_api/core/engine/instrument.py +109 -26
- opentrons/protocol_api/core/engine/module_core.py +27 -3
- opentrons/protocol_api/core/engine/protocol.py +33 -1
- opentrons/protocol_api/core/engine/stringify.py +2 -0
- opentrons/protocol_api/core/instrument.py +19 -2
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +19 -2
- opentrons/protocol_api/core/legacy/legacy_module_core.py +15 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +12 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +19 -2
- opentrons/protocol_api/core/module.py +25 -2
- opentrons/protocol_api/core/protocol.py +12 -0
- opentrons/protocol_api/instrument_context.py +388 -2
- opentrons/protocol_api/labware.py +5 -2
- opentrons/protocol_api/module_contexts.py +133 -30
- opentrons/protocol_api/protocol_context.py +61 -17
- opentrons/protocol_api/robot_context.py +3 -4
- opentrons/protocol_api/validation.py +43 -2
- opentrons/protocol_engine/__init__.py +4 -0
- opentrons/protocol_engine/actions/__init__.py +2 -0
- opentrons/protocol_engine/actions/actions.py +9 -0
- opentrons/protocol_engine/commands/__init__.py +14 -0
- opentrons/protocol_engine/commands/absorbance_reader/read.py +22 -23
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +52 -19
- opentrons/protocol_engine/commands/capture_image.py +302 -0
- opentrons/protocol_engine/commands/command.py +1 -0
- opentrons/protocol_engine/commands/command_unions.py +13 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +56 -19
- opentrons/protocol_engine/commands/flex_stacker/common.py +35 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +7 -0
- opentrons/protocol_engine/commands/heater_shaker/set_shake_speed.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
- opentrons/protocol_engine/commands/move_labware.py +3 -4
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +1 -1
- opentrons/protocol_engine/commands/movement_common.py +29 -2
- opentrons/protocol_engine/commands/pipetting_common.py +48 -3
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +12 -9
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +17 -12
- opentrons/protocol_engine/commands/thermocycler/start_run_extended_profile.py +1 -1
- opentrons/protocol_engine/create_protocol_engine.py +12 -0
- opentrons/protocol_engine/engine_support.py +3 -0
- opentrons/protocol_engine/errors/__init__.py +8 -0
- opentrons/protocol_engine/errors/exceptions.py +64 -0
- opentrons/protocol_engine/execution/__init__.py +2 -0
- opentrons/protocol_engine/execution/command_executor.py +54 -1
- opentrons/protocol_engine/execution/create_queue_worker.py +4 -1
- opentrons/protocol_engine/execution/labware_movement.py +13 -4
- opentrons/protocol_engine/execution/pipetting.py +19 -25
- opentrons/protocol_engine/protocol_engine.py +62 -2
- opentrons/protocol_engine/resources/__init__.py +2 -0
- opentrons/protocol_engine/resources/camera_provider.py +110 -0
- opentrons/protocol_engine/resources/file_provider.py +133 -58
- opentrons/protocol_engine/slot_standardization.py +2 -0
- opentrons/protocol_engine/state/camera.py +54 -0
- opentrons/protocol_engine/state/commands.py +24 -4
- opentrons/protocol_engine/state/geometry.py +68 -10
- opentrons/protocol_engine/state/labware.py +10 -6
- opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +6 -1
- opentrons/protocol_engine/state/modules.py +9 -0
- opentrons/protocol_engine/state/preconditions.py +59 -0
- opentrons/protocol_engine/state/state.py +30 -0
- opentrons/protocol_engine/state/state_summary.py +2 -0
- opentrons/protocol_engine/state/update_types.py +10 -0
- opentrons/protocol_engine/types/__init__.py +14 -1
- opentrons/protocol_engine/types/command_preconditions.py +18 -0
- opentrons/protocol_engine/types/location.py +26 -2
- opentrons/protocol_engine/types/module.py +1 -1
- opentrons/protocol_runner/protocol_runner.py +14 -1
- opentrons/protocol_runner/run_orchestrator.py +31 -0
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +2 -2
- opentrons/simulate.py +3 -0
- opentrons/system/camera.py +333 -3
- opentrons/system/ffmpeg.py +110 -0
- {opentrons-8.7.0a7.dist-info → opentrons-8.8.0a7.dist-info}/METADATA +4 -4
- {opentrons-8.7.0a7.dist-info → opentrons-8.8.0a7.dist-info}/RECORD +109 -97
- {opentrons-8.7.0a7.dist-info → opentrons-8.8.0a7.dist-info}/WHEEL +0 -0
- {opentrons-8.7.0a7.dist-info → opentrons-8.8.0a7.dist-info}/entry_points.txt +0 -0
- {opentrons-8.7.0a7.dist-info → opentrons-8.8.0a7.dist-info}/licenses/LICENSE +0 -0
|
@@ -44,7 +44,7 @@ from .module_validation_and_errors import (
|
|
|
44
44
|
from .labware import Labware
|
|
45
45
|
from . import validation
|
|
46
46
|
from . import Task
|
|
47
|
-
|
|
47
|
+
from opentrons.drivers.thermocycler.driver import BLOCK_VOL_MIN, BLOCK_VOL_MAX
|
|
48
48
|
|
|
49
49
|
_MAGNETIC_MODULE_HEIGHT_PARAM_REMOVED_IN = APIVersion(2, 14)
|
|
50
50
|
|
|
@@ -667,16 +667,9 @@ class ThermocyclerContext(ModuleContext):
|
|
|
667
667
|
hold_time_minutes: Optional[float] = None,
|
|
668
668
|
ramp_rate: Optional[float] = None,
|
|
669
669
|
block_max_volume: Optional[float] = None,
|
|
670
|
-
) ->
|
|
670
|
+
) -> None:
|
|
671
671
|
"""Set the target temperature for the well block, in °C.
|
|
672
672
|
|
|
673
|
-
.. versionchanged::2.27
|
|
674
|
-
Returns a task object that represents concurrent preheating.
|
|
675
|
-
Pass the task object to :py:meth:`ProtocolContext.wait_for_tasks` to wait for
|
|
676
|
-
the preheat to complete.
|
|
677
|
-
|
|
678
|
-
On version 2.26 or below, this function returns ``None``.
|
|
679
|
-
|
|
680
673
|
:param temperature: A value between 4 and 99, representing the target
|
|
681
674
|
temperature in °C.
|
|
682
675
|
:param hold_time_minutes: The number of minutes to hold, after reaching
|
|
@@ -706,28 +699,77 @@ class ThermocyclerContext(ModuleContext):
|
|
|
706
699
|
)
|
|
707
700
|
if self._api_version >= APIVersion(2, 27) and block_max_volume is None:
|
|
708
701
|
block_max_volume = self._get_current_labware_max_vol()
|
|
709
|
-
|
|
702
|
+
self._core.set_target_block_temperature(
|
|
710
703
|
celsius=temperature,
|
|
711
704
|
hold_time_seconds=seconds,
|
|
712
705
|
block_max_volume=block_max_volume,
|
|
713
706
|
ramp_rate=ramp_rate,
|
|
714
707
|
)
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
708
|
+
self._core.wait_for_block_temperature()
|
|
709
|
+
|
|
710
|
+
@publish(command=cmds.thermocycler_start_set_block_temp)
|
|
711
|
+
@requires_version(2, 27)
|
|
712
|
+
def start_set_block_temperature(
|
|
713
|
+
self,
|
|
714
|
+
temperature: float,
|
|
715
|
+
ramp_rate: Optional[float] = None,
|
|
716
|
+
block_max_volume: Optional[float] = None,
|
|
717
|
+
) -> Task:
|
|
718
|
+
"""Starts to set the target temperature for the well block, in °C.
|
|
719
|
+
|
|
720
|
+
Returns a task object that represents concurrent preheating.
|
|
721
|
+
Pass the task object to :py:meth:`ProtocolContext.wait_for_tasks` to wait for
|
|
722
|
+
the preheat to complete.
|
|
723
|
+
|
|
724
|
+
:param temperature: A value between 4 and 99, representing the target
|
|
725
|
+
temperature in °C.
|
|
726
|
+
:param block_max_volume: The greatest volume of liquid contained in any
|
|
727
|
+
individual well of the loaded labware, in µL.
|
|
728
|
+
If not specified, the default is 25 µL.
|
|
729
|
+
After API version 2.27 it will attempt to use
|
|
730
|
+
the liquid tracking of the labware first and
|
|
731
|
+
then fall back to the 25 if there is no probed
|
|
732
|
+
or loaded liquid.
|
|
733
|
+
"""
|
|
734
|
+
|
|
735
|
+
if block_max_volume is None:
|
|
736
|
+
block_max_volume = self._get_current_labware_max_vol()
|
|
737
|
+
task = self._core.start_set_target_block_temperature(
|
|
738
|
+
celsius=temperature,
|
|
739
|
+
block_max_volume=block_max_volume,
|
|
740
|
+
ramp_rate=ramp_rate,
|
|
741
|
+
)
|
|
742
|
+
return Task(api_version=self._api_version, core=task)
|
|
719
743
|
|
|
720
744
|
@publish(command=cmds.thermocycler_set_lid_temperature)
|
|
721
745
|
@requires_version(2, 0)
|
|
722
|
-
def set_lid_temperature(self, temperature: float) ->
|
|
746
|
+
def set_lid_temperature(self, temperature: float) -> None:
|
|
723
747
|
"""Set the target temperature for the heated lid, in °C.
|
|
724
748
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
the preheat to complete.
|
|
749
|
+
Returns a task object that represents concurrent preheating.
|
|
750
|
+
Pass the task object to :py:meth:`ProtocolContext.wait_for_tasks` to wait for
|
|
751
|
+
the preheat to complete.
|
|
729
752
|
|
|
730
|
-
|
|
753
|
+
:param temperature: A value between 37 and 110, representing the target
|
|
754
|
+
temperature in °C.
|
|
755
|
+
|
|
756
|
+
.. note::
|
|
757
|
+
|
|
758
|
+
The Thermocycler will proceed to the next command immediately after
|
|
759
|
+
``temperature`` is reached.
|
|
760
|
+
|
|
761
|
+
"""
|
|
762
|
+
self._core.set_target_lid_temperature(celsius=temperature)
|
|
763
|
+
self._core.wait_for_lid_temperature()
|
|
764
|
+
|
|
765
|
+
@publish(command=cmds.thermocycler_start_set_lid_temperature)
|
|
766
|
+
@requires_version(2, 27)
|
|
767
|
+
def start_set_lid_temperature(self, temperature: float) -> Task:
|
|
768
|
+
"""Set the target temperature for the heated lid, in °C.
|
|
769
|
+
|
|
770
|
+
Returns a task object that represents concurrent preheating.
|
|
771
|
+
Pass the task object to :py:meth:`ProtocolContext.wait_for_tasks` to wait for
|
|
772
|
+
the preheat to complete.
|
|
731
773
|
|
|
732
774
|
:param temperature: A value between 37 and 110, representing the target
|
|
733
775
|
temperature in °C.
|
|
@@ -738,11 +780,8 @@ class ThermocyclerContext(ModuleContext):
|
|
|
738
780
|
``temperature`` is reached.
|
|
739
781
|
|
|
740
782
|
"""
|
|
741
|
-
task = self._core.
|
|
742
|
-
|
|
743
|
-
return Task(api_version=self._api_version, core=task)
|
|
744
|
-
else:
|
|
745
|
-
return cast(Task, None)
|
|
783
|
+
task = self._core.start_set_target_lid_temperature(celsius=temperature)
|
|
784
|
+
return Task(api_version=self._api_version, core=task)
|
|
746
785
|
|
|
747
786
|
@publish(command=cmds.thermocycler_execute_profile)
|
|
748
787
|
@requires_version(2, 0)
|
|
@@ -942,6 +981,10 @@ class ThermocyclerContext(ModuleContext):
|
|
|
942
981
|
# ignore simulated probe results
|
|
943
982
|
if isinstance(well_vol, float):
|
|
944
983
|
max_vol = max(max_vol, well_vol)
|
|
984
|
+
if max_vol > BLOCK_VOL_MAX:
|
|
985
|
+
max_vol = BLOCK_VOL_MAX
|
|
986
|
+
elif max_vol < BLOCK_VOL_MIN:
|
|
987
|
+
max_vol = BLOCK_VOL_MIN
|
|
945
988
|
return max_vol
|
|
946
989
|
|
|
947
990
|
|
|
@@ -1444,7 +1487,7 @@ class FlexStackerContext(ModuleContext):
|
|
|
1444
1487
|
def set_stored_labware_items(
|
|
1445
1488
|
self,
|
|
1446
1489
|
labware: list[Labware],
|
|
1447
|
-
stacking_offset_z: float | None,
|
|
1490
|
+
stacking_offset_z: float | None = None,
|
|
1448
1491
|
) -> None:
|
|
1449
1492
|
"""Configure the labware the Flex Stacker will store during a protocol by providing an initial list of stored labware objects. The start of the list represents the bottom of the Stacker,
|
|
1450
1493
|
and the end of the list represents the top of the Stacker.
|
|
@@ -1537,9 +1580,15 @@ class FlexStackerContext(ModuleContext):
|
|
|
1537
1580
|
:param adapter_namespace: Applies to ``adapter`` the same way that ``namespace``
|
|
1538
1581
|
applies to ``load_name``.
|
|
1539
1582
|
|
|
1583
|
+
.. versionchanged:: 2.26
|
|
1584
|
+
``adapter_namespace`` may now be specified explicitly. When you've specified ``namespace`` for ``load_name`` but not ``adapter_namespace``, ``adapter_namespace`` now independently follows the same search rules described in ``namespace``. Formerly, it took the exact ``namespace`` value.
|
|
1585
|
+
|
|
1540
1586
|
:param adapter_version: Applies to ``adapter`` the same way that ``version``
|
|
1541
1587
|
applies to ``load_name``.
|
|
1542
1588
|
|
|
1589
|
+
.. versionchanged:: 2.26
|
|
1590
|
+
``adapter_version`` may now be specified explictly. When unspecified, improved search rules prevent selecting a version that does not exist.
|
|
1591
|
+
|
|
1543
1592
|
:param lid: A lid to load the on top of the main labware. Accepts the same
|
|
1544
1593
|
values as the ``load_name`` parameter of :py:meth:`~.ProtocolContext.load_lid_stack`. The
|
|
1545
1594
|
lid will use the same namespace as the labware, and the API will
|
|
@@ -1548,9 +1597,18 @@ class FlexStackerContext(ModuleContext):
|
|
|
1548
1597
|
:param lid_namespace: Applies to ``lid`` the same way that ``namespace``
|
|
1549
1598
|
applies to ``load_name``.
|
|
1550
1599
|
|
|
1600
|
+
.. versionchanged:: 2.26
|
|
1601
|
+
``lid_namespace`` may now be specified explicitly.
|
|
1602
|
+
When you've specified ``namespace`` for ``load_name`` but not ``lid_namespace``,
|
|
1603
|
+
``lid_namespace`` now independently follows the same search rules
|
|
1604
|
+
described in ``namespace``. Formerly, it took the exact ``namespace`` value.
|
|
1605
|
+
|
|
1551
1606
|
:param lid_version: Applies to ``lid`` the same way that ``version``
|
|
1552
1607
|
applies to ``load_name``.
|
|
1553
1608
|
|
|
1609
|
+
.. versionchanged:: 2.26
|
|
1610
|
+
``lid_version`` may now be specified explicitly. When unspecified, improved search rules prevent selecting a version that does not exist.
|
|
1611
|
+
|
|
1554
1612
|
:param count: The number of labware that the Flex Stacker should store. If not specified, this will be the maximum amount of this kind of
|
|
1555
1613
|
labware that the Flex Stacker is capable of storing.
|
|
1556
1614
|
|
|
@@ -1573,16 +1631,61 @@ class FlexStackerContext(ModuleContext):
|
|
|
1573
1631
|
- Labware with lid and adapter: the adapter (bottom side) of the upper labware unit overlaps with the lid (top side) of the unit below.
|
|
1574
1632
|
"""
|
|
1575
1633
|
|
|
1634
|
+
if self._api_version < validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE:
|
|
1635
|
+
if adapter_namespace is not None:
|
|
1636
|
+
raise APIVersionError(
|
|
1637
|
+
api_element="The `adapter_namespace` parameter",
|
|
1638
|
+
until_version=str(
|
|
1639
|
+
validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE
|
|
1640
|
+
),
|
|
1641
|
+
current_version=str(self._api_version),
|
|
1642
|
+
)
|
|
1643
|
+
if adapter_version is not None:
|
|
1644
|
+
raise APIVersionError(
|
|
1645
|
+
api_element="The `adapter_version` parameter",
|
|
1646
|
+
until_version=str(
|
|
1647
|
+
validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE
|
|
1648
|
+
),
|
|
1649
|
+
current_version=str(self._api_version),
|
|
1650
|
+
)
|
|
1651
|
+
if lid_namespace is not None:
|
|
1652
|
+
raise APIVersionError(
|
|
1653
|
+
api_element="The `lid_namespace` parameter",
|
|
1654
|
+
until_version=str(
|
|
1655
|
+
validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE
|
|
1656
|
+
),
|
|
1657
|
+
current_version=str(self._api_version),
|
|
1658
|
+
)
|
|
1659
|
+
if lid_version is not None:
|
|
1660
|
+
raise APIVersionError(
|
|
1661
|
+
api_element="The `lid_version` parameter",
|
|
1662
|
+
until_version=str(
|
|
1663
|
+
validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE
|
|
1664
|
+
),
|
|
1665
|
+
current_version=str(self._api_version),
|
|
1666
|
+
)
|
|
1667
|
+
|
|
1668
|
+
if self._api_version < validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE:
|
|
1669
|
+
checked_adapter_namespace = namespace
|
|
1670
|
+
checked_adapter_version = version
|
|
1671
|
+
checked_lid_namespace = namespace
|
|
1672
|
+
checked_lid_version = version
|
|
1673
|
+
else:
|
|
1674
|
+
checked_adapter_namespace = adapter_namespace
|
|
1675
|
+
checked_adapter_version = adapter_version
|
|
1676
|
+
checked_lid_namespace = lid_namespace
|
|
1677
|
+
checked_lid_version = lid_version
|
|
1678
|
+
|
|
1576
1679
|
self._core.set_stored_labware(
|
|
1577
1680
|
main_load_name=load_name,
|
|
1578
1681
|
main_namespace=namespace,
|
|
1579
1682
|
main_version=version,
|
|
1580
1683
|
lid_load_name=lid,
|
|
1581
|
-
lid_namespace=
|
|
1582
|
-
lid_version=
|
|
1684
|
+
lid_namespace=checked_lid_namespace,
|
|
1685
|
+
lid_version=checked_lid_version,
|
|
1583
1686
|
adapter_load_name=adapter,
|
|
1584
|
-
adapter_namespace=
|
|
1585
|
-
adapter_version=
|
|
1687
|
+
adapter_namespace=checked_adapter_namespace,
|
|
1688
|
+
adapter_version=checked_adapter_version,
|
|
1586
1689
|
count=count,
|
|
1587
1690
|
stacking_offset_z=stacking_offset_z,
|
|
1588
1691
|
)
|
|
@@ -11,6 +11,7 @@ from typing import (
|
|
|
11
11
|
Union,
|
|
12
12
|
Mapping,
|
|
13
13
|
cast,
|
|
14
|
+
Tuple,
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
from opentrons_shared_data.labware.types import LabwareDefinition
|
|
@@ -468,17 +469,15 @@ class ProtocolContext(CommandPublisher):
|
|
|
468
469
|
|
|
469
470
|
.. versionchanged:: 2.26
|
|
470
471
|
``adapter_namespace`` may now be specified explicitly.
|
|
471
|
-
|
|
472
|
-
``adapter_namespace``
|
|
473
|
-
described in ``namespace``. Formerly, it took ``namespace``
|
|
472
|
+
When you've specified ``namespace`` for ``load_name`` but not ``adapter_namespace``,
|
|
473
|
+
``adapter_namespace`` now independently follows the same search rules
|
|
474
|
+
described in ``namespace``. Formerly, it took the exact ``namespace`` value.
|
|
474
475
|
|
|
475
476
|
:param adapter_version: The version of the adapter being loaded.
|
|
476
477
|
Applies to ``adapter`` the same way that ``version`` applies to ``load_name``.
|
|
477
478
|
|
|
478
479
|
.. versionchanged:: 2.26
|
|
479
|
-
``adapter_version`` may now be specified explicitly.
|
|
480
|
-
the algorithm to select a version automatically has improved to avoid
|
|
481
|
-
selecting versions that do not exist.
|
|
480
|
+
``adapter_version`` may now be specified explicitly. When unspecified, the API uses the newest version available for your protocol's API level.
|
|
482
481
|
|
|
483
482
|
:param lid: A lid to load on the top of the main labware. Accepts the same
|
|
484
483
|
values as the ``load_name`` parameter of :py:meth:`.load_lid_stack`. The
|
|
@@ -492,17 +491,15 @@ class ProtocolContext(CommandPublisher):
|
|
|
492
491
|
|
|
493
492
|
.. versionchanged:: 2.26
|
|
494
493
|
``lid_namespace`` may now be specified explicitly.
|
|
495
|
-
|
|
496
|
-
``lid_namespace``
|
|
497
|
-
described in ``namespace``. Formerly, it took ``namespace``
|
|
494
|
+
When you've specified ``namespace`` for ``load_name`` but not ``lid_namespace``,
|
|
495
|
+
``lid_namespace`` now independently follows the same search rules
|
|
496
|
+
described in ``namespace``. Formerly, it took the exact ``namespace`` value.
|
|
498
497
|
|
|
499
498
|
:param lid_version: The version of the adapter being loaded.
|
|
500
499
|
Applies to ``lid`` the same way that ``version`` applies to ``load_name``.
|
|
501
500
|
|
|
502
501
|
.. versionchanged:: 2.26
|
|
503
|
-
``lid_version`` may now be specified explicitly.
|
|
504
|
-
the algorithm to select a version automatically has improved to avoid
|
|
505
|
-
selecting versions that do not exist.
|
|
502
|
+
``lid_version`` may now be specified explicitly. When unspecified, the API uses the newest version available for your protocol's API level.
|
|
506
503
|
"""
|
|
507
504
|
|
|
508
505
|
if isinstance(location, OffDeckType) and self._api_version < APIVersion(2, 15):
|
|
@@ -1505,8 +1502,8 @@ class ProtocolContext(CommandPublisher):
|
|
|
1505
1502
|
- ``"water"``: an Opentrons-verified liquid class based on deionized water.
|
|
1506
1503
|
- ``"glycerol_50"``: an Opentrons-verified liquid class for viscous liquid. Based on 50% glycerol.
|
|
1507
1504
|
- ``"ethanol_80"``: an Opentrons-verified liquid class for volatile liquid. Based on 80% ethanol.
|
|
1508
|
-
:param version:
|
|
1509
|
-
protocol's API
|
|
1505
|
+
:param version: Version of the liquid class to retrieve. If left unspecified, defaults to the latest version for the
|
|
1506
|
+
protocol's API level.
|
|
1510
1507
|
|
|
1511
1508
|
:raises: ``LiquidClassDefinitionDoesNotExist``: if the specified liquid class does not exist.
|
|
1512
1509
|
|
|
@@ -1623,9 +1620,9 @@ class ProtocolContext(CommandPublisher):
|
|
|
1623
1620
|
|
|
1624
1621
|
.. versionchanged:: 2.26
|
|
1625
1622
|
``adapter_namespace`` may now be specified explicitly.
|
|
1626
|
-
|
|
1627
|
-
``adapter_namespace``
|
|
1628
|
-
described in ``namespace``. Formerly, it took ``namespace``
|
|
1623
|
+
When you've specified ``namespace`` for ``load_name`` but not ``adapter_namespace``,
|
|
1624
|
+
``adapter_namespace`` now independently follows the same search rules
|
|
1625
|
+
described in ``namespace``. Formerly, it took the exact ``namespace`` value.
|
|
1629
1626
|
|
|
1630
1627
|
:param adapter_version: The version of the adapter being loaded.
|
|
1631
1628
|
Applies to ``adapter`` the same way that ``version`` applies to ``load_name``.
|
|
@@ -1825,6 +1822,53 @@ class ProtocolContext(CommandPublisher):
|
|
|
1825
1822
|
)
|
|
1826
1823
|
return None
|
|
1827
1824
|
|
|
1825
|
+
@requires_version(2, 27)
|
|
1826
|
+
def capture_image(
|
|
1827
|
+
self,
|
|
1828
|
+
home_before: Optional[bool] = False,
|
|
1829
|
+
filename: Optional[str] = None,
|
|
1830
|
+
resolution: Optional[Tuple[int, int]] = None,
|
|
1831
|
+
zoom: Optional[float] = None,
|
|
1832
|
+
contrast: Optional[float] = None,
|
|
1833
|
+
brightness: Optional[float] = None,
|
|
1834
|
+
saturation: Optional[float] = None,
|
|
1835
|
+
) -> None:
|
|
1836
|
+
"""Capture an image using the camera. Captured images get saved as a result of the protocol run.
|
|
1837
|
+
|
|
1838
|
+
:param home_before: Boolean to home the pipette before capturing an image.
|
|
1839
|
+
:param filename: Filename to use when saving the captured image as a file.
|
|
1840
|
+
:param resolution: Width/height tuple to determine the resolution to use when capturing an image.
|
|
1841
|
+
:param zoom: Optional zoom level, with minimum/default of 1x zoom and maximum of 2x zoom.
|
|
1842
|
+
:param contrast: Contrast level to be applied to an image, range is 0% to 100%.
|
|
1843
|
+
:param brightness: Brightness level to be applied to an image, range is 0% to 100%.
|
|
1844
|
+
:param saturation: Saturation level to be applied to an image, range is 0% to 100%.
|
|
1845
|
+
|
|
1846
|
+
.. versionadded:: 2.27
|
|
1847
|
+
|
|
1848
|
+
"""
|
|
1849
|
+
if home_before is True:
|
|
1850
|
+
self._core.home()
|
|
1851
|
+
|
|
1852
|
+
with publish_context(
|
|
1853
|
+
broker=self.broker,
|
|
1854
|
+
command=cmds.capture_image(
|
|
1855
|
+
resolution=resolution,
|
|
1856
|
+
zoom=zoom,
|
|
1857
|
+
contrast=contrast,
|
|
1858
|
+
brightness=brightness,
|
|
1859
|
+
saturation=saturation,
|
|
1860
|
+
),
|
|
1861
|
+
):
|
|
1862
|
+
self._core.capture_image(
|
|
1863
|
+
filename=filename,
|
|
1864
|
+
resolution=resolution,
|
|
1865
|
+
zoom=zoom,
|
|
1866
|
+
contrast=contrast,
|
|
1867
|
+
brightness=brightness,
|
|
1868
|
+
saturation=saturation,
|
|
1869
|
+
)
|
|
1870
|
+
return None
|
|
1871
|
+
|
|
1828
1872
|
|
|
1829
1873
|
def _create_module_context(
|
|
1830
1874
|
module_core: Union[ModuleCore, NonConnectedModuleCore],
|
|
@@ -15,7 +15,6 @@ from opentrons.legacy_commands import publisher
|
|
|
15
15
|
from opentrons.hardware_control import SyncHardwareAPI
|
|
16
16
|
from opentrons.protocols.api_support.util import requires_version
|
|
17
17
|
from opentrons.protocols.api_support.types import APIVersion
|
|
18
|
-
from opentrons_shared_data.pipette.types import PipetteNameType
|
|
19
18
|
|
|
20
19
|
from . import validation
|
|
21
20
|
from .core.common import ProtocolCore, RobotCore
|
|
@@ -125,7 +124,7 @@ class RobotContext(publisher.CommandPublisher):
|
|
|
125
124
|
|
|
126
125
|
"""
|
|
127
126
|
instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT)
|
|
128
|
-
is_96_channel = instrument_on_left
|
|
127
|
+
is_96_channel = validation.is_pipette_96_channel(instrument_on_left)
|
|
129
128
|
axis_map = validation.ensure_axis_map_type(
|
|
130
129
|
axis_map, self._protocol_core.robot_type, is_96_channel
|
|
131
130
|
)
|
|
@@ -162,7 +161,7 @@ class RobotContext(publisher.CommandPublisher):
|
|
|
162
161
|
:param float speed: The maximum speed with which to move all axes in mm/s.
|
|
163
162
|
"""
|
|
164
163
|
instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT)
|
|
165
|
-
is_96_channel = instrument_on_left
|
|
164
|
+
is_96_channel = validation.is_pipette_96_channel(instrument_on_left)
|
|
166
165
|
|
|
167
166
|
axis_map = validation.ensure_axis_map_type(
|
|
168
167
|
axis_map, self._protocol_core.robot_type, is_96_channel
|
|
@@ -313,7 +312,7 @@ class RobotContext(publisher.CommandPublisher):
|
|
|
313
312
|
|
|
314
313
|
"""
|
|
315
314
|
instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT)
|
|
316
|
-
is_96_channel = instrument_on_left
|
|
315
|
+
is_96_channel = validation.is_pipette_96_channel(instrument_on_left)
|
|
317
316
|
|
|
318
317
|
return validation.ensure_axis_map_type(
|
|
319
318
|
axis_map, self._protocol_core.robot_type, is_96_channel
|
|
@@ -99,11 +99,18 @@ class InvalidFixtureLocationError(ValueError):
|
|
|
99
99
|
"""An error raised when attempting to load a fixture in an invalid cutout."""
|
|
100
100
|
|
|
101
101
|
|
|
102
|
+
def is_pipette_96_channel(pipette: Optional[PipetteNameType]) -> bool:
|
|
103
|
+
"""Return if this pipette type is a 96 channel."""
|
|
104
|
+
if pipette is not None:
|
|
105
|
+
return pipette in [PipetteNameType.P1000_96, PipetteNameType.P200_96]
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
|
|
102
109
|
def ensure_mount_for_pipette(
|
|
103
110
|
mount: Union[str, Mount, None], pipette: PipetteNameType
|
|
104
111
|
) -> Mount:
|
|
105
112
|
"""Ensure that an input value represents a valid mount, and is valid for the given pipette."""
|
|
106
|
-
if pipette
|
|
113
|
+
if is_pipette_96_channel(pipette):
|
|
107
114
|
# Always validate the raw mount input, even if the pipette is a 96-channel and we're not going
|
|
108
115
|
# to use the mount value.
|
|
109
116
|
if mount is not None:
|
|
@@ -370,7 +377,7 @@ def ensure_definition_is_not_lid_after_api_version(
|
|
|
370
377
|
and api_version >= LID_STACK_VERSION_GATE
|
|
371
378
|
):
|
|
372
379
|
raise APIVersionError(
|
|
373
|
-
f"Labware Lids cannot be loaded like standard labware in Protocols written with an API version
|
|
380
|
+
f"Labware Lids cannot be loaded like standard labware in Protocols written with an API version of {LID_STACK_VERSION_GATE} or higher."
|
|
374
381
|
)
|
|
375
382
|
|
|
376
383
|
|
|
@@ -570,6 +577,40 @@ class LocationTypeError(TypeError):
|
|
|
570
577
|
ValidTarget = Union[WellTarget, PointTarget, DisposalTarget]
|
|
571
578
|
|
|
572
579
|
|
|
580
|
+
def validate_dynamic_locations(
|
|
581
|
+
location: Optional[Union[Location, Well, TrashBin, WasteChute]],
|
|
582
|
+
end_location: Location,
|
|
583
|
+
) -> None:
|
|
584
|
+
"""Given that we have an end_location we check that they're a vaild dynamic pair."""
|
|
585
|
+
if location is None:
|
|
586
|
+
raise ValueError("Location must be supplied if using an End Location.")
|
|
587
|
+
if not isinstance(location, Location):
|
|
588
|
+
raise ValueError(
|
|
589
|
+
"Location must be a point within a well when dynamic pipetting."
|
|
590
|
+
)
|
|
591
|
+
# Shouldn't be true ever if using typing but a customer protocol may not check
|
|
592
|
+
if not isinstance(end_location, Location):
|
|
593
|
+
raise ValueError(
|
|
594
|
+
"End location must be a point within a well when dynamic pipetting."
|
|
595
|
+
)
|
|
596
|
+
if not location.labware.is_well:
|
|
597
|
+
raise ValueError("Start location must be within a well when dynamic pipetting")
|
|
598
|
+
if not end_location.labware.is_well:
|
|
599
|
+
raise ValueError("End location must be within a well when dynamic pipetting")
|
|
600
|
+
(
|
|
601
|
+
_,
|
|
602
|
+
start_well,
|
|
603
|
+
) = location.labware.get_parent_labware_and_well()
|
|
604
|
+
(
|
|
605
|
+
_,
|
|
606
|
+
end_well,
|
|
607
|
+
) = end_location.labware.get_parent_labware_and_well()
|
|
608
|
+
if start_well != end_well:
|
|
609
|
+
raise ValueError(
|
|
610
|
+
"Start and end locations must be within the same well when dynamic pipetting"
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
|
|
573
614
|
def validate_location(
|
|
574
615
|
location: Optional[Union[Location, Well, TrashBin, WasteChute]],
|
|
575
616
|
last_location: Optional[Union[Location, TrashBin, WasteChute]],
|
|
@@ -41,6 +41,8 @@ from .types import (
|
|
|
41
41
|
DeckType,
|
|
42
42
|
DeckSlotLocation,
|
|
43
43
|
InStackerHopperLocation,
|
|
44
|
+
WASTE_CHUTE_LOCATION,
|
|
45
|
+
AccessibleByGripperLocation,
|
|
44
46
|
ModuleLocation,
|
|
45
47
|
OnLabwareLocation,
|
|
46
48
|
AddressableAreaLocation,
|
|
@@ -120,6 +122,8 @@ __all__ = [
|
|
|
120
122
|
"ModuleLocation",
|
|
121
123
|
"OnLabwareLocation",
|
|
122
124
|
"AddressableAreaLocation",
|
|
125
|
+
"WASTE_CHUTE_LOCATION",
|
|
126
|
+
"AccessibleByGripperLocation",
|
|
123
127
|
"InStackerHopperLocation",
|
|
124
128
|
"OFF_DECK_LOCATION",
|
|
125
129
|
"SYSTEM_LOCATION",
|
|
@@ -21,6 +21,7 @@ from .actions import (
|
|
|
21
21
|
AddLabwareOffsetAction,
|
|
22
22
|
AddLabwareDefinitionAction,
|
|
23
23
|
AddLiquidAction,
|
|
24
|
+
AddCameraSettingsAction,
|
|
24
25
|
SetDeckConfigurationAction,
|
|
25
26
|
AddAddressableAreaAction,
|
|
26
27
|
AddModuleAction,
|
|
@@ -51,6 +52,7 @@ __all__ = [
|
|
|
51
52
|
"FailCommandAction",
|
|
52
53
|
"AddLabwareOffsetAction",
|
|
53
54
|
"AddLabwareDefinitionAction",
|
|
55
|
+
"AddCameraSettingsAction",
|
|
54
56
|
"AddLiquidAction",
|
|
55
57
|
"SetDeckConfigurationAction",
|
|
56
58
|
"AddAddressableAreaAction",
|
|
@@ -24,6 +24,7 @@ from ..error_recovery_policy import ErrorRecoveryPolicy, ErrorRecoveryType
|
|
|
24
24
|
from ..errors import ErrorOccurrence
|
|
25
25
|
from ..notes.notes import CommandNote
|
|
26
26
|
from ..state.update_types import StateUpdate
|
|
27
|
+
from ..resources.camera_provider import CameraSettings
|
|
27
28
|
from ..types import (
|
|
28
29
|
LabwareOffsetCreateInternal,
|
|
29
30
|
ModuleDefinition,
|
|
@@ -236,6 +237,13 @@ class AddLabwareDefinitionAction:
|
|
|
236
237
|
definition: LabwareDefinition
|
|
237
238
|
|
|
238
239
|
|
|
240
|
+
@dataclasses.dataclass(frozen=True)
|
|
241
|
+
class AddCameraSettingsAction:
|
|
242
|
+
"""Add Camera settings to be used in place of the Camera Provider accessible settings."""
|
|
243
|
+
|
|
244
|
+
enablement_settings: CameraSettings
|
|
245
|
+
|
|
246
|
+
|
|
239
247
|
@dataclasses.dataclass(frozen=True)
|
|
240
248
|
class AddLiquidAction:
|
|
241
249
|
"""Add a liquid, to apply to subsequent `LoadLiquid`s."""
|
|
@@ -305,6 +313,7 @@ Action = Union[
|
|
|
305
313
|
AddLabwareOffsetAction,
|
|
306
314
|
AddLabwareDefinitionAction,
|
|
307
315
|
AddModuleAction,
|
|
316
|
+
AddCameraSettingsAction,
|
|
308
317
|
SetDeckConfigurationAction,
|
|
309
318
|
AddAddressableAreaAction,
|
|
310
319
|
AddLiquidAction,
|
|
@@ -451,6 +451,14 @@ from .identify_module import (
|
|
|
451
451
|
IdentifyModuleCommandType,
|
|
452
452
|
)
|
|
453
453
|
|
|
454
|
+
from .capture_image import (
|
|
455
|
+
CaptureImage,
|
|
456
|
+
CaptureImageParams,
|
|
457
|
+
CaptureImageCreate,
|
|
458
|
+
CaptureImageResult,
|
|
459
|
+
CaptureImageCommandType,
|
|
460
|
+
)
|
|
461
|
+
|
|
454
462
|
__all__ = [
|
|
455
463
|
# command type unions
|
|
456
464
|
"Command",
|
|
@@ -796,4 +804,10 @@ __all__ = [
|
|
|
796
804
|
"WaitForTasksParams",
|
|
797
805
|
"WaitForTasksResult",
|
|
798
806
|
"WaitForTasksCommandType",
|
|
807
|
+
# capture image command bundle
|
|
808
|
+
"CaptureImage",
|
|
809
|
+
"CaptureImageCreate",
|
|
810
|
+
"CaptureImageParams",
|
|
811
|
+
"CaptureImageResult",
|
|
812
|
+
"CaptureImageCommandType",
|
|
799
813
|
]
|
|
@@ -7,14 +7,15 @@ from typing_extensions import Literal, Type
|
|
|
7
7
|
from pydantic import BaseModel, Field
|
|
8
8
|
from pydantic.json_schema import SkipJsonSchema
|
|
9
9
|
|
|
10
|
+
from opentrons_shared_data.data_files import MimeType
|
|
10
11
|
from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
|
|
11
|
-
from ...errors import CannotPerformModuleAction
|
|
12
|
+
from ...errors import CannotPerformModuleAction
|
|
12
13
|
from ...errors.error_occurrence import ErrorOccurrence
|
|
13
14
|
|
|
14
15
|
from ...resources.file_provider import (
|
|
15
16
|
PlateReaderData,
|
|
16
17
|
ReadData,
|
|
17
|
-
|
|
18
|
+
ReadCmdFileNameMetadata,
|
|
18
19
|
)
|
|
19
20
|
from ...resources import FileProvider
|
|
20
21
|
from ...state import update_types
|
|
@@ -93,21 +94,6 @@ class ReadAbsorbanceImpl(
|
|
|
93
94
|
"Absorbance Plate Reader can't read a plate with the lid open. Call `close_lid()` first."
|
|
94
95
|
)
|
|
95
96
|
|
|
96
|
-
# TODO: we need to return a file ID and increase the file count even when a moduel is not attached
|
|
97
|
-
if (
|
|
98
|
-
params.fileName is not None
|
|
99
|
-
and abs_reader_substate.configured_wavelengths is not None
|
|
100
|
-
):
|
|
101
|
-
# Validate that the amount of files we are about to generate does not put us higher than the limit
|
|
102
|
-
if (
|
|
103
|
-
self._state_view.files.get_filecount()
|
|
104
|
-
+ len(abs_reader_substate.configured_wavelengths)
|
|
105
|
-
> MAXIMUM_CSV_FILE_LIMIT
|
|
106
|
-
):
|
|
107
|
-
raise StorageLimitReachedError(
|
|
108
|
-
message=f"Attempt to write file {params.fileName} exceeds file creation limit of {MAXIMUM_CSV_FILE_LIMIT} files."
|
|
109
|
-
)
|
|
110
|
-
|
|
111
97
|
asbsorbance_result: Dict[int, Dict[str, float]] = {}
|
|
112
98
|
transform_results = []
|
|
113
99
|
# Handle the measurement and begin building data for return
|
|
@@ -172,15 +158,28 @@ class ReadAbsorbanceImpl(
|
|
|
172
158
|
)
|
|
173
159
|
|
|
174
160
|
if isinstance(plate_read_result, PlateReaderData):
|
|
161
|
+
this_cmd_id = self._state_view.commands.get_running_command_id()
|
|
162
|
+
prev_cmd = (
|
|
163
|
+
self._state_view.commands.get_most_recently_finalized_command()
|
|
164
|
+
)
|
|
165
|
+
prev_cmd_id = prev_cmd.command.id if prev_cmd is not None else None
|
|
166
|
+
|
|
175
167
|
# Write a CSV file for each of the measurements taken
|
|
176
168
|
for measurement in plate_read_result.read_results:
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
169
|
+
csv_bytes = plate_read_result.build_csv_bytes(
|
|
170
|
+
measurement=measurement,
|
|
171
|
+
)
|
|
172
|
+
file_info = await self._file_provider.write_file(
|
|
173
|
+
data=csv_bytes,
|
|
174
|
+
mime_type=MimeType.TEXT_CSV,
|
|
175
|
+
command_metadata=ReadCmdFileNameMetadata(
|
|
176
|
+
base_filename=params.fileName,
|
|
177
|
+
wavelength=measurement.wavelength,
|
|
178
|
+
command_id=this_cmd_id or "",
|
|
179
|
+
prev_command_id=prev_cmd_id or "",
|
|
180
|
+
),
|
|
182
181
|
)
|
|
183
|
-
file_ids.append(
|
|
182
|
+
file_ids.append(file_info.id)
|
|
184
183
|
|
|
185
184
|
state_update.files_added = update_types.FilesAddedUpdate(
|
|
186
185
|
file_ids=file_ids
|