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): Mark calibrations as bad when determined they exceed threshold #6918

Merged
merged 9 commits into from
Nov 9, 2020
22 changes: 14 additions & 8 deletions api/src/opentrons/calibration_storage/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
file_operators as io, helpers, migration, modify)
if typing.TYPE_CHECKING:
from opentrons_shared_data.labware.dev_types import LabwareDefinition
from .dev_types import (
TipLengthCalibration, CalibrationIndexDict, CalibrationDict)
from .dev_types import CalibrationIndexDict, CalibrationDict


def _format_calibration_type(
data: 'CalibrationDict') -> local_types.CalibrationTypes:
data: 'CalibrationDict') -> local_types.LabwareCalibrationTypes:
offset = local_types.OffsetData(
value=data['default']['offset'],
last_modified=data['default']['lastModified']
Expand All @@ -27,7 +26,7 @@ def _format_calibration_type(
# the labware calibraiton file. We should
# have a follow-up PR to grab tip lengths
# based on the loaded pips + labware
return local_types.CalibrationTypes(
return local_types.LabwareCalibrationTypes(
offset=offset,
tip_length=local_types.TipLengthData())

Expand Down Expand Up @@ -77,12 +76,19 @@ def get_all_calibrations() -> typing.List[local_types.CalibrationInformation]:

def _get_tip_length_data(
pip_id: str, labware_hash: str, labware_load_name: str
) -> 'TipLengthCalibration':
) -> local_types.TipLengthCalibration:
try:
pip_tip_length_path = config.get_tip_length_cal_path()/f'{pip_id}.json'
tip_length_data =\
tip_rack_data =\
io.read_cal_file(str(pip_tip_length_path))
return tip_length_data[labware_hash]
tip_length_info = tip_rack_data[labware_hash]
return local_types.TipLengthCalibration(
tip_length=tip_length_info['tipLength'],
pipette=pip_id,
tiprack=labware_hash,
last_modified=tip_length_info['lastModified'],
source=_get_calibration_source(tip_length_info),
status=_get_calibration_status(tip_length_info))
except (FileNotFoundError, KeyError):
raise local_types.TipLengthCalNotFound(
f'Tip length of {labware_load_name} has not been '
Expand Down Expand Up @@ -118,7 +124,7 @@ def get_labware_calibration(
def load_tip_length_calibration(
pip_id: str,
definition: 'LabwareDefinition',
parent: str) -> 'TipLengthCalibration':
parent: str) -> local_types.TipLengthCalibration:
"""
Function used to grab the current tip length associated
with a particular tiprack.
Expand Down
85 changes: 65 additions & 20 deletions api/src/opentrons/calibration_storage/modify.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import typing
from pathlib import Path

from dataclasses import asdict
from opentrons import config
from opentrons.types import Mount
from opentrons.types import Mount, Point

from opentrons.util.helpers import utc_now

from . import (
Expand All @@ -22,7 +24,6 @@
TipLengthCalibration, PipTipLengthCalibration,
DeckCalibrationData, PipetteCalibrationData, CalibrationStatusDict)
from opentrons_shared_data.labware.dev_types import LabwareDefinition
from opentrons.types import Point


def _add_to_index_offset_file(parent: str, slot: str, uri: str, lw_hash: str):
Expand Down Expand Up @@ -76,7 +77,7 @@ def add_existing_labware_to_index_file(
def save_labware_calibration(
labware_path: local_types.StrPath,
definition: 'LabwareDefinition',
delta: 'Point',
delta: Point,
slot: str = '',
parent: str = ''):
"""
Expand Down Expand Up @@ -106,7 +107,9 @@ def save_labware_calibration(
def create_tip_length_data(
definition: 'LabwareDefinition',
parent: str,
length: float) -> 'PipTipLengthCalibration':
length: float,
cal_status: local_types.CalibrationStatus = None
) -> 'PipTipLengthCalibration':
"""
Function to correctly format tip length data.

Expand All @@ -121,22 +124,25 @@ def create_tip_length_data(
# assert labware._is_tiprack, \
Copy link
Member

Choose a reason for hiding this comment

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

remove

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This commented out assert is part of the comment block above. I want to keep it in as a reminder for what we had previously utilized in that method.

# 'cannot save tip length for non-tiprack labware'
labware_hash = helpers.hash_labware_def(definition)
status: 'CalibrationStatusDict' =\
helpers.convert_to_dict( # type: ignore
local_types.CalibrationStatus())
if cal_status:
status = cal_status
else:
status = local_types.CalibrationStatus()
status_dict: 'CalibrationStatusDict' =\
helpers.convert_to_dict(status) # type: ignore

tip_length_data: 'TipLengthCalibration' = {
'tipLength': length,
'lastModified': utc_now(),
'source': local_types.SourceType.user,
'status': status
'status': status_dict
}

data = {labware_hash + parent: tip_length_data}
return data


def _helper_offset_data_format(filepath: str, delta: 'Point') -> dict:
def _helper_offset_data_format(filepath: str, delta: Point) -> dict:
if not Path(filepath).is_file():
calibration_data = {
"default": {
Expand Down Expand Up @@ -197,20 +203,25 @@ def save_robot_deck_attitude(
transform: local_types.AttitudeMatrix,
pip_id: typing.Optional[str],
lw_hash: typing.Optional[str],
source: local_types.SourceType = None):
source: local_types.SourceType = None,
cal_status: local_types.CalibrationStatus = None):
robot_dir = config.get_opentrons_path('robot_calibration_dir')
robot_dir.mkdir(parents=True, exist_ok=True)
gantry_path = robot_dir/'deck_calibration.json'
status: 'CalibrationStatusDict' = \
helpers.convert_to_dict( # type: ignore
local_types.CalibrationStatus())
if cal_status:
status = cal_status
else:
status = local_types.CalibrationStatus()
status_dict: 'CalibrationStatusDict' =\
helpers.convert_to_dict(status) # type: ignore

gantry_dict: 'DeckCalibrationData' = {
'attitude': transform,
'pipette_calibrated_with': pip_id,
'last_modified': utc_now(),
'tiprack': lw_hash,
'source': source or local_types.SourceType.user,
'status': status
'status': status_dict
}
io.save_to_file(gantry_path, gantry_dict)

Expand All @@ -233,23 +244,57 @@ def _add_to_pipette_offset_index_file(pip_id: str, mount: Mount):


def save_pipette_calibration(
offset: 'Point',
offset: Point,
pip_id: str, mount: Mount,
tiprack_hash: str, tiprack_uri: str):
tiprack_hash: str, tiprack_uri: str,
cal_status: local_types.CalibrationStatus = None):
pip_dir = config.get_opentrons_path(
'pipette_calibration_dir') / mount.name.lower()
pip_dir.mkdir(parents=True, exist_ok=True)
status: 'CalibrationStatusDict' =\
helpers.convert_to_dict( # type: ignore
local_types.CalibrationStatus())
if cal_status:
status = cal_status
else:
status = local_types.CalibrationStatus()
status_dict: 'CalibrationStatusDict' =\
helpers.convert_to_dict(status) # type: ignore
offset_path = pip_dir/f'{pip_id}.json'
offset_dict: 'PipetteCalibrationData' = {
'offset': [offset.x, offset.y, offset.z],
'tiprack': tiprack_hash,
'uri': tiprack_uri,
'last_modified': utc_now(),
'source': local_types.SourceType.user,
'status': status
'status': status_dict
}
io.save_to_file(offset_path, offset_dict)
_add_to_pipette_offset_index_file(pip_id, mount)


@typing.overload
def mark_bad(
calibration: local_types.DeckCalibration,
source_marked_bad: local_types.SourceType
) -> local_types.DeckCalibration: ...


@typing.overload
def mark_bad(
calibration: local_types.PipetteOffsetCalibration,
source_marked_bad: local_types.SourceType
) -> local_types.PipetteOffsetCalibration: ...


@typing.overload
def mark_bad(
calibration: local_types.TipLengthCalibration,
source_marked_bad: local_types.SourceType
) -> local_types.TipLengthCalibration: ...


def mark_bad(calibration, source_marked_bad: local_types.SourceType):
caldict = asdict(calibration)
# remove current status key
del caldict['status']
status = local_types.CalibrationStatus(
markedBad=True, source=source_marked_bad, markedAt=utc_now())
return type(calibration)(**caldict, status=status)
6 changes: 3 additions & 3 deletions api/src/opentrons/calibration_storage/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class ParentOptions:


@dataclass
class CalibrationTypes:
class LabwareCalibrationTypes:
"""
Class to categorize what calibration
data might be stored for a labware.
Expand All @@ -90,7 +90,7 @@ class CalibrationInformation:
Class to store important calibration
info for labware.
"""
calibration: CalibrationTypes
calibration: LabwareCalibrationTypes
parent: ParentOptions
labware_id: str
uri: str
Expand Down Expand Up @@ -119,7 +119,7 @@ class DeckCalibration:
@dataclass
class PipetteOffsetByPipetteMount:
"""
Class to store pipette offset without pipette and monut info
Class to store pipette offset without pipette and mount info
"""
offset: PipetteOffset
source: SourceType
Expand Down
7 changes: 2 additions & 5 deletions api/src/opentrons/legacy_api/containers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from collections import OrderedDict
import itertools
import logging
from typing import TYPE_CHECKING
from opentrons.config import CONFIG
from opentrons.data_storage import database
from opentrons.util.vector import Vector
Expand All @@ -23,11 +22,9 @@
get,
helpers as cal_helpers,
file_operators as io,
types as cal_types,
modify)

if TYPE_CHECKING:
from opentrons.calibration_storage.dev_types import TipLengthCalibration


__all__ = [
'Deck',
Expand Down Expand Up @@ -290,7 +287,7 @@ def load_new_labware_def(definition):


def load_tip_length_calibration(
pip_id: str, location) -> 'TipLengthCalibration':
pip_id: str, location) -> cal_types.TipLengthCalibration:
placeable, _ = unpack_location(location)
lw = placeable.get_parent()
return get._get_tip_length_data(
Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/protocol_api/instrument_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,6 @@ def _build_length_from_overlap() -> float:
return get.load_tip_length_calibration(
self.hw_pipette['pipette_id'],
tiprack._implementation.get_definition(),
parent)['tipLength']
parent).tip_length
except TipLengthCalNotFound:
return _build_length_from_overlap()
7 changes: 7 additions & 0 deletions api/src/opentrons/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ class Mount(enum.Enum):
def __str__(self):
return self.name

@classmethod
def string_to_mount(cls, mount: str) -> 'Mount':
if mount == 'right':
return cls.RIGHT
else:
return cls.LEFT


class TransferTipPolicy(enum.Enum):
ONCE = enum.auto()
Expand Down
9 changes: 8 additions & 1 deletion api/tests/opentrons/protocol_api/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -856,7 +856,14 @@ def test_tip_length_for_caldata(loop, monkeypatch, use_new_calibration):
instr = ctx.load_instrument('p20_single_gen2', 'left')
tiprack = ctx.load_labware('geb_96_tiprack_10ul', '1')
mock_tip_length = mock.Mock()
mock_tip_length.return_value = {'tipLength': 2}
mock_tip_length.return_value =\
cs_types.TipLengthCalibration(
tip_length=2,
pipette='fake id',
tiprack='fake_hash',
last_modified='some time',
source=cs_types.SourceType.user,
status=cs_types.CalibrationStatus(markedBad=False))
monkeypatch.setattr(get, 'load_tip_length_calibration', mock_tip_length)
instr._tip_length_for.cache_clear()
assert instr._tip_length_for(tiprack) == 2
Expand Down
11 changes: 9 additions & 2 deletions api/tests/opentrons/protocol_api/test_offsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,15 @@ def test_load_tip_length_calibration_data(monkeypatch, clear_tlc_calibration):
modify.save_tip_length_calibration(PIPETTE_ID, test_data)
result = get.load_tip_length_calibration(
PIPETTE_ID, minimalLabwareDef, parent)

assert result == test_data[MOCK_HASH]
expected = cs_types.TipLengthCalibration(
tip_length=tip_length,
pipette=PIPETTE_ID,
source=cs_types.SourceType.user,
status=cs_types.CalibrationStatus(markedBad=False),
tiprack=MOCK_HASH,
last_modified=test_data[MOCK_HASH]['lastModified']
)
assert result == expected


def test_clear_tip_length_calibration_data(monkeypatch):
Expand Down
12 changes: 10 additions & 2 deletions app/src/components/CheckCalibration/ResultsSummary.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ const LOOKING_FOR_DATA = 'Looking for your detailed calibration data?'
const DOWNLOAD_SUMMARY = 'Download JSON summary'

export function ResultsSummary(props: CalibrationPanelProps): React.Node {
const { comparisonsByPipette, instruments, cleanUpAndExit } = props
const {
comparisonsByPipette,
instruments,
checkBothPipettes,
cleanUpAndExit,
} = props

if (!comparisonsByPipette || !instruments) {
return null
Expand Down Expand Up @@ -93,7 +98,10 @@ export function ResultsSummary(props: CalibrationPanelProps): React.Node {
},
}

const deckCalibrationResult = comparisonsByPipette.first.deck?.status ?? null
const getDeckCalibration = checkBothPipettes
? comparisonsByPipette.second.deck?.status
: comparisonsByPipette.first.deck?.status
const deckCalibrationResult = getDeckCalibration ?? null

return (
<>
Expand Down
Loading