Skip to content

Commit

Permalink
feat(api): Mark calibrations as bad when determined they exceed thres…
Browse files Browse the repository at this point in the history
…hold (#6918)
  • Loading branch information
Laura-Danielle authored Nov 9, 2020
1 parent 22fc36a commit ac3a866
Show file tree
Hide file tree
Showing 16 changed files with 336 additions and 85 deletions.
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, \
# '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 @@ -1567,6 +1567,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

0 comments on commit ac3a866

Please sign in to comment.