Skip to content

Commit

Permalink
Merge branch 'edge' into fix_text-area-style-issue
Browse files Browse the repository at this point in the history
  • Loading branch information
koji committed Nov 21, 2024
2 parents 2533b58 + 84a57ce commit 4cc3fb0
Show file tree
Hide file tree
Showing 60 changed files with 1,115 additions and 128 deletions.
52 changes: 38 additions & 14 deletions analyses-snapshot-testing/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ CACHEBUST ?= $(shell date +%s)
ANALYSIS_REF ?= edge
PROTOCOL_NAMES ?= all
OVERRIDE_PROTOCOL_NAMES ?= all
OPENTRONS_VERSION ?= edge
LOCAL_IMAGE_TAG ?= local
ANALYZER_IMAGE_NAME ?= opentrons-analysis

export OPENTRONS_VERSION # used for server
export ANALYSIS_REF # used for analysis and snapshot test
export PROTOCOL_NAMES # used for the snapshot test
export OVERRIDE_PROTOCOL_NAMES # used for the snapshot test
export ANALYSIS_REF # tag, branch or commit for the opentrons repository. Used as the image tag for the analyzer image
export PROTOCOL_NAMES # tell the test which protocols to run
export OVERRIDE_PROTOCOL_NAMES # tell the test which override protocols to run

ifeq ($(CI), true)
PYTHON=python
Expand Down Expand Up @@ -93,23 +93,47 @@ build-base-image:

.PHONY: build-opentrons-analysis
build-opentrons-analysis:
@echo "Building docker image for $(ANALYSIS_REF)"
@echo "The image will be named opentrons-analysis:$(ANALYSIS_REF)"
@echo "If you want to build a different version, run 'make build-opentrons-analysis ANALYSIS_REF=<version>'"
docker build --build-arg BASE_IMAGE_NAME=$(BASE_IMAGE_NAME) --build-arg ANALYSIS_REF=$(ANALYSIS_REF) --build-arg CACHEBUST=$(CACHEBUST) -t opentrons-analysis:$(ANALYSIS_REF) -f citools/Dockerfile.analyze citools/.
@echo "Building docker image for opentrons repository reference$(ANALYSIS_REF)"
@echo "The image will be named $(ANALYZER_IMAGE_NAME):$(ANALYSIS_REF)"
@echo "If you want to build a different version, run 'make build-opentrons-analysis ANALYSIS_REF=<tag, branch, or commit>'"
docker build --build-arg BASE_IMAGE_NAME=$(BASE_IMAGE_NAME) --build-arg ANALYSIS_REF=$(ANALYSIS_REF) --build-arg CACHEBUST=$(CACHEBUST) -t $(ANALYZER_IMAGE_NAME):$(ANALYSIS_REF) -f citools/Dockerfile.analyze citools/.

.PHONY: local-build
local-build:
.PHONY: build-local
build-local:
@echo "Building docker image for your local opentrons code"
@echo "The image will be named opentrons-analysis:local"
@echo "For a fresh build, run 'make local-build NO_CACHE=1'"
docker build --build-arg BASE_IMAGE_NAME=$(BASE_IMAGE_NAME) $(BUILD_FLAGS) -t opentrons-analysis:local -f citools/Dockerfile.local .. || true
@echo "This image will be named $(ANALYZER_IMAGE_NAME):$(LOCAL_IMAGE_TAG)"
docker build --build-arg BASE_IMAGE_NAME=$(BASE_IMAGE_NAME) -t $(ANALYZER_IMAGE_NAME):$(LOCAL_IMAGE_TAG) -f citools/Dockerfile.local ..
@echo "Build complete"

.PHONY: snapshot-test-local
snapshot-test-local: ANALYSIS_REF=$(LOCAL_IMAGE_TAG)
snapshot-test-local: build-base-image build-local
@echo "This target is overriding the ANALYSIS_REF to the LOCAL_IMAGE_TAG: $(LOCAL_IMAGE_TAG)"
@echo "ANALYSIS_REF is $(ANALYSIS_REF). The the test maps this env variable to the image tag."
@echo "The image the test will use is $(ANALYZER_IMAGE_NAME):$(LOCAL_IMAGE_TAG)"
@echo "PROTOCOL_NAMES is $(PROTOCOL_NAMES)"
@echo "OVERRIDE_PROTOCOL_NAMES is $(OVERRIDE_PROTOCOL_NAMES)"
$(PYTHON) -m pipenv run pytest -k analyses_snapshot_test -vv

.PHONY: snapshot-test-update-local
snapshot-test-update-local: ANALYSIS_REF=$(LOCAL_IMAGE_TAG)
snapshot-test-update-local: build-base-image build-local
@echo "This target is overriding the ANALYSIS_REF to the LOCAL_IMAGE_TAG: $(LOCAL_IMAGE_TAG)"
@echo "ANALYSIS_REF is $(ANALYSIS_REF). The the test maps this env variable to the image tag."
@echo "The image the test will use is $(ANALYZER_IMAGE_NAME):$(LOCAL_IMAGE_TAG)"
@echo "PROTOCOL_NAMES is $(PROTOCOL_NAMES)"
@echo "OVERRIDE_PROTOCOL_NAMES is $(OVERRIDE_PROTOCOL_NAMES)"
$(PYTHON) -m pipenv run pytest -k analyses_snapshot_test --snapshot-update

.PHONY: generate-protocols
generate-protocols:
$(PYTHON) -m pipenv run python -m automation.data.protocol_registry

# Tools for running the robot server in a container

OPENTRONS_VERSION ?= edge
export OPENTRONS_VERSION # used for the robot server image as the tag, branch or commit for the opentrons repository

.PHONY: build-rs
build-rs:
@echo "Building docker image for opentrons-robot-server:$(OPENTRONS_VERSION)"
Expand Down
17 changes: 12 additions & 5 deletions analyses-snapshot-testing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

1. Follow the instructions in [DEV_SETUP.md](../DEV_SETUP.md)
1. `cd analyses-snapshot-testing`
1. use pyenv to install python 3.12 and set it as the local python version for this directory
1. use pyenv to install python 3.13 and set it as the local python version for this directory
1. `make setup`
1. Have docker installed and ready

Expand Down Expand Up @@ -72,10 +72,17 @@ cd analyses-snapshot-testing \

> This copies in your local code to the container and runs the analyses battery against it.

1. `make build-base-image`
1. `make build-local`
1. `make local-snapshot-test`
`cd PYENV_ROOT && git pull` - make sure pyenv is up to date so you may install python 3.13.0
`pyenv install 3.13.0` - install python 3.13.0
`cd <OPENTRONS_REPO_ROOT>/analyses-snapshot-testing` - navigate to the analyses-snapshot-testing directory
`pyenv local 3.13.0` - set the local python version to 3.13.0
`make setup` - install the requirements
`make snapshot-test-local` - this target builds the base image, builds the local code into the base image, then runs the analyses battery against the image you just created

You have the option to specify one or many protocols to run the analyses on. This is also described above [Running the tests against specific protocols](#running-the-tests-against-specific-protocols)

- `make local-snapshot-test PROTOCOL_NAMES=Flex_S_v2_19_Illumina_DNA_PCR_Free OVERRIDE_PROTOCOL_NAMES=none`
- `make snapshot-test-local PROTOCOL_NAMES=Flex_S_v2_19_Illumina_DNA_PCR_Free OVERRIDE_PROTOCOL_NAMES=none`

### Updating the snapshots locally

- `make snapshot-test-update-local` - this target builds the base image, builds the local code into the base image, then runs the analyses battery against the image you just created, updating the snapshots by passing the `--update-snapshots` flag to the test
4 changes: 4 additions & 0 deletions api/docs/v2/versioning.rst
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ This table lists the correspondence between Protocol API versions and robot soft
Changes in API Versions
=======================

Version 2.21
------------
- :ref:`Liquid presence detection <lpd>` now only checks on the first aspiration of the :py:meth:`.mix` cycle.

Version 2.20
------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,7 @@ def _lookup_serial_key(pipette_name: FirmwarePipetteName) -> str:
lookup_name = {
FirmwarePipetteName.p1000_single: "P1KS",
FirmwarePipetteName.p1000_multi: "P1KM",
FirmwarePipetteName.p1000_multi_em: "P1KP",
FirmwarePipetteName.p50_single: "P50S",
FirmwarePipetteName.p50_multi: "P50M",
FirmwarePipetteName.p1000_96: "P1KH",
Expand Down
2 changes: 2 additions & 0 deletions api/src/opentrons/hardware_control/dev_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ class PipetteDict(InstrumentDict):
pipette_bounding_box_offsets: PipetteBoundingBoxOffsetDefinition
current_nozzle_map: NozzleMap
lld_settings: Optional[Dict[str, Dict[str, float]]]
plunger_positions: Dict[str, float]
shaft_ul_per_mm: float


class PipetteStateDict(TypedDict):
Expand Down
20 changes: 4 additions & 16 deletions api/src/opentrons/hardware_control/instruments/ot2/pipette.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
CommandPreconditionViolated,
)
from opentrons_shared_data.pipette.ul_per_mm import (
piecewise_volume_conversion,
calculate_ul_per_mm,
PIPETTING_FUNCTION_FALLBACK_VERSION,
PIPETTING_FUNCTION_LATEST_VERSION,
)
Expand Down Expand Up @@ -584,21 +584,9 @@ def get_nominal_tip_overlap_dictionary_by_configuration(
# want this to unbounded.
@functools.lru_cache(maxsize=100)
def ul_per_mm(self, ul: float, action: UlPerMmAction) -> float:
if action == "aspirate":
fallback = self._active_tip_settings.aspirate.default[
PIPETTING_FUNCTION_FALLBACK_VERSION
]
sequence = self._active_tip_settings.aspirate.default.get(
self._pipetting_function_version, fallback
)
else:
fallback = self._active_tip_settings.dispense.default[
PIPETTING_FUNCTION_FALLBACK_VERSION
]
sequence = self._active_tip_settings.dispense.default.get(
self._pipetting_function_version, fallback
)
return piecewise_volume_conversion(ul, sequence)
return calculate_ul_per_mm(
ul, action, self._active_tip_settings, self._pipetting_function_version
)

def __str__(self) -> str:
return "{} current volume {}ul critical point: {} at {}".format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,13 @@ def get_attached_instrument(self, mount: MountType) -> PipetteDict:
"pipette_bounding_box_offsets"
] = instr.config.pipette_bounding_box_offsets
result["lld_settings"] = instr.config.lld_settings
result["plunger_positions"] = {
"top": instr.plunger_positions.top,
"bottom": instr.plunger_positions.bottom,
"blow_out": instr.plunger_positions.blow_out,
"drop_tip": instr.plunger_positions.drop_tip,
}
result["shaft_ul_per_mm"] = instr.config.shaft_ul_per_mm
return cast(PipetteDict, result)

@property
Expand Down
27 changes: 9 additions & 18 deletions api/src/opentrons/hardware_control/instruments/ot3/pipette.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
InvalidInstrumentData,
)
from opentrons_shared_data.pipette.ul_per_mm import (
piecewise_volume_conversion,
calculate_ul_per_mm,
PIPETTING_FUNCTION_FALLBACK_VERSION,
PIPETTING_FUNCTION_LATEST_VERSION,
)
Expand Down Expand Up @@ -529,23 +529,13 @@ def tip_presence_responses(self) -> int:
# want this to unbounded.
@functools.lru_cache(maxsize=100)
def ul_per_mm(self, ul: float, action: UlPerMmAction) -> float:
if action == "aspirate":
fallback = self._active_tip_settings.aspirate.default[
PIPETTING_FUNCTION_FALLBACK_VERSION
]
sequence = self._active_tip_settings.aspirate.default.get(
self._pipetting_function_version, fallback
)
elif action == "blowout":
return self._config.shaft_ul_per_mm
else:
fallback = self._active_tip_settings.dispense.default[
PIPETTING_FUNCTION_FALLBACK_VERSION
]
sequence = self._active_tip_settings.dispense.default.get(
self._pipetting_function_version, fallback
)
return piecewise_volume_conversion(ul, sequence)
return calculate_ul_per_mm(
ul,
action,
self._active_tip_settings,
self._pipetting_function_version,
self._config.shaft_ul_per_mm,
)

def __str__(self) -> str:
return "{} current volume {}ul critical point: {} at {}".format(
Expand Down Expand Up @@ -585,6 +575,7 @@ def as_dict(self) -> "Pipette.DictType":
"versioned_tip_overlap": self.tip_overlap,
"back_compat_names": self._config.pipette_backcompat_names,
"supported_tips": self.liquid_class.supported_tips,
"shaft_ul_per_mm": self._config.shaft_ul_per_mm,
}
)
return self._config_as_dict
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,13 @@ def get_attached_instrument(self, mount: OT3Mount) -> PipetteDict:
"pipette_bounding_box_offsets"
] = instr.config.pipette_bounding_box_offsets
result["lld_settings"] = instr.config.lld_settings
result["plunger_positions"] = {
"top": instr.plunger_positions.top,
"bottom": instr.plunger_positions.bottom,
"blow_out": instr.plunger_positions.blow_out,
"drop_tip": instr.plunger_positions.drop_tip,
}
result["shaft_ul_per_mm"] = instr.config.shaft_ul_per_mm
return cast(PipetteDict, result)

@property
Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ async def _update_position_estimation(
"""
Function to update motor estimation for a set of axes
"""

await self._backend.update_motor_status()
if axes:
checked_axes = [ax for ax in axes if ax in Axis]
else:
Expand Down
21 changes: 20 additions & 1 deletion api/src/opentrons/protocol_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,16 @@
)
from .disposal_locations import TrashBin, WasteChute
from ._liquid import Liquid, LiquidClass
from ._types import OFF_DECK
from ._types import (
OFF_DECK,
PLUNGER_BLOWOUT,
PLUNGER_TOP,
PLUNGER_BOTTOM,
PLUNGER_DROPTIP,
ASPIRATE_ACTION,
DISPENSE_ACTION,
BLOWOUT_ACTION,
)
from ._nozzle_layout import (
COLUMN,
PARTIAL_COLUMN,
Expand Down Expand Up @@ -69,12 +78,22 @@
"Liquid",
"LiquidClass",
"Parameters",
# Partial Tip types
"COLUMN",
"PARTIAL_COLUMN",
"SINGLE",
"ROW",
"ALL",
# Deck location types
"OFF_DECK",
# Pipette plunger types
"PLUNGER_BLOWOUT",
"PLUNGER_TOP",
"PLUNGER_BOTTOM",
"PLUNGER_DROPTIP",
"ASPIRATE_ACTION",
"DISPENSE_ACTION",
"BLOWOUT_ACTION",
"RuntimeParameterRequiredError",
"CSVParameter",
# For internal Opentrons use only:
Expand Down
24 changes: 24 additions & 0 deletions api/src/opentrons/protocol_api/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,27 @@ class OffDeckType(enum.Enum):
See :ref:`off-deck-location` for details on using ``OFF_DECK`` with :py:obj:`ProtocolContext.move_labware()`.
"""


class PlungerPositionTypes(enum.Enum):
PLUNGER_TOP = "top"
PLUNGER_BOTTOM = "bottom"
PLUNGER_BLOWOUT = "blow_out"
PLUNGER_DROPTIP = "drop_tip"


PLUNGER_TOP: Final = PlungerPositionTypes.PLUNGER_TOP
PLUNGER_BOTTOM: Final = PlungerPositionTypes.PLUNGER_BOTTOM
PLUNGER_BLOWOUT: Final = PlungerPositionTypes.PLUNGER_BLOWOUT
PLUNGER_DROPTIP: Final = PlungerPositionTypes.PLUNGER_DROPTIP


class PipetteActionTypes(enum.Enum):
ASPIRATE_ACTION = "aspirate"
DISPENSE_ACTION = "dispense"
BLOWOUT_ACTION = "blowout"


ASPIRATE_ACTION: Final = PipetteActionTypes.ASPIRATE_ACTION
DISPENSE_ACTION: Final = PipetteActionTypes.DISPENSE_ACTION
BLOWOUT_ACTION: Final = PipetteActionTypes.BLOWOUT_ACTION
54 changes: 51 additions & 3 deletions api/src/opentrons/protocol_api/core/engine/robot.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
from typing import Optional, Dict
from typing import Optional, Dict, Union
from opentrons.hardware_control import SyncHardwareAPI

from opentrons.types import Mount, MountType, Point, AxisType, AxisMapType
from opentrons_shared_data.pipette import types as pip_types
from opentrons.protocol_api._types import PipetteActionTypes, PlungerPositionTypes
from opentrons.protocol_engine import commands as cmd
from opentrons.protocol_engine.clients import SyncClient as EngineClient
from opentrons.protocol_engine.types import DeckPoint, MotorAxis

from opentrons.protocol_api.core.robot import AbstractRobot


_AXIS_TYPE_TO_MOTOR_AXIS = {
AxisType.X: MotorAxis.X,
AxisType.Y: MotorAxis.Y,
Expand Down Expand Up @@ -39,12 +42,57 @@ def __init__(
def _convert_to_engine_mount(self, axis_map: AxisMapType) -> Dict[MotorAxis, float]:
return {_AXIS_TYPE_TO_MOTOR_AXIS[ax]: dist for ax, dist in axis_map.items()}

def get_pipette_type_from_engine(self, mount: Mount) -> Optional[str]:
def get_pipette_type_from_engine(
self, mount: Union[Mount, str]
) -> Optional[pip_types.PipetteNameType]:
"""Get the pipette attached to the given mount."""
engine_mount = MountType[mount.name]
if isinstance(mount, Mount):
engine_mount = MountType[mount.name]
else:
if mount.lower() == "right":
engine_mount = MountType.RIGHT
else:
engine_mount = MountType.LEFT
maybe_pipette = self._engine_client.state.pipettes.get_by_mount(engine_mount)
return maybe_pipette.pipetteName if maybe_pipette else None

def get_plunger_position_from_name(
self, mount: Mount, position_name: PlungerPositionTypes
) -> float:
engine_mount = MountType[mount.name]
maybe_pipette = self._engine_client.state.pipettes.get_by_mount(engine_mount)
if not maybe_pipette:
return 0.0
return self._engine_client.state.pipettes.lookup_plunger_position_name(
maybe_pipette.id, position_name.value
)

def get_plunger_position_from_volume(
self, mount: Mount, volume: float, action: PipetteActionTypes, robot_type: str
) -> float:
engine_mount = MountType[mount.name]
maybe_pipette = self._engine_client.state.pipettes.get_by_mount(engine_mount)
if not maybe_pipette:
raise RuntimeError(
f"Cannot load plunger position as no pipette is attached to {mount}"
)
convert_volume = (
self._engine_client.state.pipettes.lookup_volume_to_mm_conversion(
maybe_pipette.id, volume, action.value
)
)
plunger_bottom = (
self._engine_client.state.pipettes.lookup_plunger_position_name(
maybe_pipette.id, "bottom"
)
)
mm = volume / convert_volume
if robot_type == "OT-2 Standard":
position = plunger_bottom + mm
else:
position = plunger_bottom - mm
return round(position, 6)

def move_to(self, mount: Mount, destination: Point, speed: Optional[float]) -> None:
engine_mount = MountType[mount.name]
engine_destination = DeckPoint(
Expand Down
Loading

0 comments on commit 4cc3fb0

Please sign in to comment.