Skip to content

Commit

Permalink
feat(api): support flow rate (uL/sec) in JSON protocols (#2123)
Browse files Browse the repository at this point in the history
* feat(api): support flow rate (uL/sec) in JSON protocols

- update JSON protocol schema def in shared-data
- add "default-values" field to PD saved files
- support flow rate in JSON protocol executor
  • Loading branch information
IanLondon authored Sep 3, 2018
1 parent ac778ea commit b0f944e
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 16 deletions.
58 changes: 57 additions & 1 deletion api/opentrons/protocols/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,31 +56,87 @@ def load_labware(protocol_data):

def get_location(command_params, loaded_labware):
labwareId = command_params.get('labware')
if not labwareId:
# not all commands use labware param
return None
well = command_params.get('well')
return loaded_labware.get(labwareId, {}).get(well)
labware = loaded_labware.get(labwareId)
if not labware:
raise ValueError(
'Command tried to use labware "{}", but that ID does not exist ' +
'in protocol\'s "labware" section'.format(labwareId))
return labware.wells(well)


def get_pipette(command_params, loaded_pipettes):
pipetteId = command_params.get('pipette')
return loaded_pipettes.get(pipetteId)


# TODO (Ian 2018-08-22) once Pipette has more sensible way of managing
# flow rate value (eg as an argument in aspirate/dispense fns), remove this
def set_flow_rate(
pipette_model, pipette, command_type, params, default_values):
"""
Set flow rate in uL/mm, to value obtained from command's params,
or if unspecified in command params, then from protocol's "default-values".
"""
default_aspirate = default_values.get(
'aspirate-flow-rate', {}).get(pipette_model)

default_dispense = default_values.get(
'dispense-flow-rate', {}).get(pipette_model)

flow_rate_param = params.get('flow-rate')

if flow_rate_param is not None:
if command_type == 'aspirate':
pipette.set_flow_rate(
aspirate=flow_rate_param,
dispense=default_dispense)
return
if command_type == 'dispense':
pipette.set_flow_rate(
aspirate=default_aspirate,
dispense=flow_rate_param)
return

pipette.set_flow_rate(
aspirate=default_aspirate,
dispense=default_dispense
)


# C901 code complexity is due to long elif block, ok in this case (Ian+Ben)
def dispatch_commands(protocol_data, loaded_pipettes, loaded_labware): # noqa: C901 E501
subprocedures = [
p.get('subprocedure', [])
for p in protocol_data.get('procedure', [])]

default_values = protocol_data.get('default-values', {})
flat_subs = chain.from_iterable(subprocedures)

for command_item in flat_subs:
command_type = command_item.get('command')
params = command_item.get('params', {})

pipette = get_pipette(params, loaded_pipettes)
pipette_model = protocol_data\
.get('pipettes', {})\
.get(params.get('pipette'), {})\
.get('model')

location = get_location(params, loaded_labware)
volume = params.get('volume')

if pipette:
# Aspirate/Dispense flow rate must be set each time for commands
# which use pipettes right now.
# Flow rate is persisted inside the Pipette object
# and is settable but not easily gettable
set_flow_rate(
pipette_model, pipette, command_type, params, default_values)

if command_type == 'delay':
wait = params.get('wait', 0)
if wait is True:
Expand Down
27 changes: 26 additions & 1 deletion api/tests/opentrons/protocols/test_load_json_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def test_blank_protocol():
def test_dispatch_commands(monkeypatch):
robot.reset()
cmd = []
flow_rates = []

def mock_sleep(seconds):
cmd.append(("sleep", seconds))
Expand All @@ -80,6 +81,9 @@ def mock_aspirate(volume, location):
def mock_dispense(volume, location):
cmd.append(("dispense", volume, location))

def mock_set_flow_rate(aspirate, dispense):
flow_rates.append((aspirate, dispense))

pipette = instruments.P10_Single('left')

loaded_pipettes = {
Expand All @@ -96,9 +100,24 @@ def mock_dispense(volume, location):

monkeypatch.setattr(pipette, 'aspirate', mock_aspirate)
monkeypatch.setattr(pipette, 'dispense', mock_dispense)
monkeypatch.setattr(pipette, 'set_flow_rate', mock_set_flow_rate)
monkeypatch.setattr(protocols, '_sleep', mock_sleep)

protocol_data = {
"default-values": {
"aspirate-flow-rate": {
"p300_single_v1": 101
},
"dispense-flow-rate": {
"p300_single_v1": 102
}
},
"pipettes": {
"pipetteId": {
"mount": "left",
"model": "p300_single_v1"
}
},
"procedure": [
{
"subprocedure": [
Expand All @@ -108,7 +127,8 @@ def mock_dispense(volume, location):
"pipette": "pipetteId",
"labware": "sourcePlateId",
"well": "A1",
"volume": 5
"volume": 5,
"flow-rate": 123
}
},
{
Expand Down Expand Up @@ -139,3 +159,8 @@ def mock_dispense(volume, location):
("sleep", 42),
("dispense", 4.5, dest_plate['B1'])
]

assert flow_rates == [
(123, 102),
(101, 102)
]
8 changes: 8 additions & 0 deletions protocol-designer/src/file-data/selectors/fileCreator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @flow
import {createSelector} from 'reselect'
import mapValues from 'lodash/mapValues'
import {getPropertyAllPipettes} from '@opentrons/shared-data'
import {fileMetadata} from './fileFields'
import {getInitialRobotState, robotStateTimeline} from './commands'
import {selectors as dismissSelectors} from '../../dismiss'
Expand All @@ -14,6 +15,11 @@ import type {LabwareData, PipetteData} from '../../step-generation'
const protocolSchemaVersion = '1.0.0'
const applicationVersion = process.env.OT_PD_VERSION || 'unknown version'

const executionDefaults = {
'aspirate-flow-rate': getPropertyAllPipettes('aspirateFlowRate'),
'dispense-flow-rate': getPropertyAllPipettes('dispenseFlowRate')
}

export const createFile: BaseState => ProtocolFile = createSelector(
fileMetadata,
getInitialRobotState,
Expand Down Expand Up @@ -73,6 +79,8 @@ export const createFile: BaseState => ProtocolFile = createSelector(
tags: []
},

'default-values': executionDefaults,

'designer-application': {
'application-name': 'opentrons/protocol-designer',
'application-version': applicationVersion,
Expand Down
12 changes: 11 additions & 1 deletion protocol-designer/src/file-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import type {RootState as DismissRoot} from './dismiss'

type MsSinceEpoch = number
type VersionString = string // eg '1.0.0'
type PipetteModel = string // TODO Ian 2018-05-11 use pipette-definitions model types enum

export type FilePipette = {
mount: Mount,
model: string // TODO Ian 2018-05-11 use pipette-definitions model types
model: PipetteModel
}

export type FileLabware = {
Expand All @@ -19,6 +20,10 @@ export type FileLabware = {
'display-name': string
}

export type FlowRateForPipettes = {
[PipetteModel]: number
}

export type PDMetadata = {
// pipetteId to tiprackModel. may be unassigned
pipetteTiprackAssignments: {[pipetteId: string]: ?string},
Expand Down Expand Up @@ -48,6 +53,11 @@ export type ProtocolFile = {
tags: Array<string>
},

'default-values': {
'aspirate-flow-rate': FlowRateForPipettes,
'dispense-flow-rate': FlowRateForPipettes
},

'designer-application': {
'application-name': 'opentrons/protocol-designer',
'application-version': VersionString,
Expand Down
5 changes: 5 additions & 0 deletions shared-data/js/pipettes.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @flow
import mapValues from 'lodash/mapValues'
import pipetteConfigByModel from '../robot-data/pipette-config.json'

export type PipetteChannels = 1 | 8
Expand Down Expand Up @@ -94,3 +95,7 @@ function comparePipettes (sortBy: Array<SortableProps>) {
return 0
}
}

export function getPropertyAllPipettes (propertyName: $Keys<PipetteConfig>) {
return mapValues(pipetteConfigByModel, config => config[propertyName])
}
73 changes: 60 additions & 13 deletions shared-data/protocol-json-schema/protocol-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,55 @@
"$schema": "http://json-schema.org/draft-07/schema#",

"definitions": {
"pipette-model": {
"description": "Special keyword specifying the model of the pipette. TODO finalize these model names they are still TBD",
"type": "string",
"enum": [
"p10_single_v1",
"p10_multi_v1",
"p50_single_v1",
"p50_multi_v1",
"p300_single_v1",
"p300_multi_v1",
"p1000_single_v1",
"p1000_multi_v1",

"p10_single_v1.3",
"p10_multi_v1.3",
"p50_single_v1.3",
"p50_multi_v1.3",
"p300_single_v1.3",
"p300_multi_v1.3",
"p1000_single_v1.3",
"p1000_multi_v1.3"
]
},

"mm-offset": {
"description": "Millimeters for pipette location offsets",
"type": "number"
},

"flow-rate-for-pipettes": {
"description": "Flow rate in mm/sec for each pipette model used in the protocol",
"type": "object",
"propertyNames": {"$ref": "#/definitions/pipette-model"},
"patternProperties": {".*": {"type": "number"}},
"additionalProperties": false
},

"flow-rate-params": {
"properties": {
"flow-rate": {
"description": "Flow rate for aspirate/dispense. If omitted, defaults to the corresponding values in \"default-values\"",
"type": "number"
}
}
},

"well-position-params": {
"description": "Optional params for well position offsets",
"type": "object",
"properties": {
"position": {
"required": ["anchor"],
Expand Down Expand Up @@ -61,6 +103,7 @@
"additionalProperties": false,
"required": [
"protocol-schema",
"default-values",
"metadata",
"robot",
"pipettes",
Expand Down Expand Up @@ -118,6 +161,19 @@
}
},

"default-values": {
"description": "Default values required for protocol execution",
"type": "object",
"required": [
"aspirate-flow-rate",
"dispense-flow-rate"
],
"properties": {
"aspirate-flow-rate": {"$ref": "#/definitions/flow-rate-for-pipettes"},
"dispense-flow-rate": {"$ref": "#/definitions/flow-rate-for-pipettes"}
}
},

"designer-application": {
"description": "Optional data & metadata not required to execute the protocol, used by the application that created this protocol",
"type": "object",
Expand Down Expand Up @@ -163,18 +219,7 @@
"enum": ["left", "right"]
},
"model": {
"description": "Special keyword specifying the model of the pipette. TODO finalize these model names they are still TBD",
"type": "string",
"enum": [
"p10_single_v1",
"p10_multi_v1",
"p50_single_v1",
"p50_multi_v1",
"p300_single_v1",
"p300_multi_v1",
"p1000_single_v1",
"p1000_multi_v1"
]
"$ref": "#/definitions/pipette-model"
}
}
}
Expand Down Expand Up @@ -232,7 +277,8 @@
"subprocedure": {
"type": "array",
"items": {
"anyOf": [{
"anyOf": [
{
"description": "Aspirate / dispense / air gap commands",
"type": "object",
"required": ["command", "params"],
Expand All @@ -243,6 +289,7 @@
},
"params": {
"allOf": [
{"$ref": "#/definitions/flow-rate-params"},
{"$ref": "#/definitions/pipette-access-params"},
{"$ref": "#/definitions/volume-params"},
{"$ref": "#/definitions/well-position-params"}
Expand Down

0 comments on commit b0f944e

Please sign in to comment.