@opentrons/shared-data 0.1.2-alpha.2 → 0.1.2
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.
- package/package.json +1 -1
- package/js/__tests__/__snapshots__/pipettes.test.ts.snap +0 -5969
- package/js/__tests__/deckSchemas.test.ts +0 -87
- package/js/__tests__/errors.test.js +0 -21
- package/js/__tests__/getAreSlotsAdjacent.test.ts +0 -128
- package/js/__tests__/getWellNamePerMultiTip.test.ts +0 -162
- package/js/__tests__/labwareDefQuirks.test.ts +0 -38
- package/js/__tests__/labwareDefSchemaV1.test.ts +0 -82
- package/js/__tests__/labwareDefSchemaV2.test.ts +0 -298
- package/js/__tests__/moduleAccessors.test.ts +0 -50
- package/js/__tests__/moduleSpecsSchema.test.ts +0 -77
- package/js/__tests__/pipetteSchemaV2.test.ts +0 -107
- package/js/__tests__/pipetteSpecSchemas.test.ts +0 -48
- package/js/__tests__/pipettes.test.ts +0 -58
- package/js/__tests__/protocolSchemaV4.test.ts +0 -137
- package/js/__tests__/protocolSchemaV5.test.ts +0 -137
- package/js/__tests__/protocolSchemaV6.test.ts +0 -132
- package/js/__tests__/protocolSchemaV7.test.ts +0 -64
- package/js/__tests__/protocolValidation.test.ts +0 -83
- package/js/__tests__/sortWells.test.ts +0 -40
- package/js/__tests__/splitWellsOnColumn.test.ts +0 -21
- package/js/__tests__/validateErrors.test.js +0 -34
- package/js/constants.ts +0 -405
- package/js/cypressUtils.ts +0 -35
- package/js/errors.ts +0 -33
- package/js/fixtures.ts +0 -156
- package/js/getLabware.ts +0 -145
- package/js/gripper.ts +0 -29
- package/js/helpers/__tests__/getAdapterName.test.ts +0 -31
- package/js/helpers/__tests__/getDeckDefFromLoadedLabware.test.ts +0 -12
- package/js/helpers/__tests__/getSimplestFlexDeckConfig.test.ts +0 -176
- package/js/helpers/__tests__/getVectorDifference.test.ts +0 -25
- package/js/helpers/__tests__/getVectorSum.test.ts +0 -21
- package/js/helpers/__tests__/labwareInference.test.ts +0 -84
- package/js/helpers/__tests__/orderWells.test.ts +0 -87
- package/js/helpers/__tests__/parseProtocolData.test.ts +0 -165
- package/js/helpers/__tests__/volume.test.ts +0 -108
- package/js/helpers/__tests__/wellSets.test.ts +0 -352
- package/js/helpers/get96Channel384WellPlateWells.ts +0 -44
- package/js/helpers/getAddressableAreasInProtocol.ts +0 -98
- package/js/helpers/getLoadedLabwareDefinitionsByUri.ts +0 -30
- package/js/helpers/getModuleVizDims.ts +0 -118
- package/js/helpers/getOccludedSlotCountForModule.ts +0 -13
- package/js/helpers/getSimplestFlexDeckConfig.ts +0 -183
- package/js/helpers/getVectorDifference.ts +0 -12
- package/js/helpers/getVectorSum.ts +0 -12
- package/js/helpers/getWellNamePerMultiTip.ts +0 -86
- package/js/helpers/getWellTotalVolume.ts +0 -16
- package/js/helpers/index.ts +0 -348
- package/js/helpers/labwareInference.ts +0 -92
- package/js/helpers/orderWells.ts +0 -69
- package/js/helpers/parseProtocolData.ts +0 -141
- package/js/helpers/volume.ts +0 -35
- package/js/helpers/wellIsRect.ts +0 -6
- package/js/helpers/wellSets.ts +0 -155
- package/js/index.ts +0 -14
- package/js/labwareTools/__tests__/__snapshots__/createIrregularLabware.test.ts.snap +0 -3
- package/js/labwareTools/__tests__/__snapshots__/createLabware.test.ts.snap +0 -3
- package/js/labwareTools/__tests__/createDefaultDisplayName.test.ts +0 -111
- package/js/labwareTools/__tests__/createIrregularLabware.test.ts +0 -226
- package/js/labwareTools/__tests__/createLabware.test.ts +0 -147
- package/js/labwareTools/index.ts +0 -452
- package/js/modules.ts +0 -97
- package/js/pipettes.ts +0 -89
- package/js/protocols.ts +0 -290
- package/js/schema.ts +0 -80
- package/js/scripts/generateDeckLayersFromSVG.js +0 -46
- package/js/titleCase.ts +0 -30
- package/js/types.ts +0 -561
package/js/helpers/index.ts
DELETED
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
import assert from 'assert'
|
|
2
|
-
import uniq from 'lodash/uniq'
|
|
3
|
-
|
|
4
|
-
import { OPENTRONS_LABWARE_NAMESPACE } from '../constants'
|
|
5
|
-
import standardOt2DeckDef from '../../deck/definitions/4/ot2_standard.json'
|
|
6
|
-
import standardFlexDeckDef from '../../deck/definitions/4/ot3_standard.json'
|
|
7
|
-
import type {
|
|
8
|
-
DeckDefinition,
|
|
9
|
-
LabwareDefinition2,
|
|
10
|
-
ModuleModel,
|
|
11
|
-
RobotType,
|
|
12
|
-
ThermalAdapterName,
|
|
13
|
-
} from '../types'
|
|
14
|
-
|
|
15
|
-
export { getWellNamePerMultiTip } from './getWellNamePerMultiTip'
|
|
16
|
-
export { getWellTotalVolume } from './getWellTotalVolume'
|
|
17
|
-
export { wellIsRect } from './wellIsRect'
|
|
18
|
-
export { orderWells } from './orderWells'
|
|
19
|
-
export { get96Channel384WellPlateWells } from './get96Channel384WellPlateWells'
|
|
20
|
-
|
|
21
|
-
export * from './parseProtocolData'
|
|
22
|
-
export * from './volume'
|
|
23
|
-
export * from './wellSets'
|
|
24
|
-
export * from './getModuleVizDims'
|
|
25
|
-
export * from './getVectorDifference'
|
|
26
|
-
export * from './getVectorSum'
|
|
27
|
-
export * from './getLoadedLabwareDefinitionsByUri'
|
|
28
|
-
export * from './getOccludedSlotCountForModule'
|
|
29
|
-
export * from './labwareInference'
|
|
30
|
-
export * from './getAddressableAreasInProtocol'
|
|
31
|
-
export * from './getSimplestFlexDeckConfig'
|
|
32
|
-
|
|
33
|
-
export const getLabwareDefIsStandard = (def: LabwareDefinition2): boolean =>
|
|
34
|
-
def?.namespace === OPENTRONS_LABWARE_NAMESPACE
|
|
35
|
-
|
|
36
|
-
export const getLabwareDefURI = (def: LabwareDefinition2): string =>
|
|
37
|
-
constructLabwareDefURI(
|
|
38
|
-
def.namespace,
|
|
39
|
-
def.parameters.loadName,
|
|
40
|
-
String(def.version)
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
export const constructLabwareDefURI = (
|
|
44
|
-
namespace: string,
|
|
45
|
-
loadName: string,
|
|
46
|
-
version: string
|
|
47
|
-
): string => `${namespace}/${loadName}/${version}`
|
|
48
|
-
|
|
49
|
-
// Load names of "retired" labware
|
|
50
|
-
// TODO(mc, 2019-12-3): how should this correspond to LABWAREV2_DO_NOT_LIST?
|
|
51
|
-
// see shared-data/js/getLabware.js
|
|
52
|
-
const RETIRED_LABWARE = [
|
|
53
|
-
'geb_96_tiprack_10ul',
|
|
54
|
-
'geb_96_tiprack_1000ul',
|
|
55
|
-
'opentrons_1_trash_850ml_fixed',
|
|
56
|
-
'opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical_acrylic',
|
|
57
|
-
'opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap_acrylic',
|
|
58
|
-
'opentrons_24_tuberack_generic_0.75ml_snapcap_acrylic',
|
|
59
|
-
'opentrons_40_aluminumblock_eppendorf_24x2ml_safelock_snapcap_generic_16x0.2ml_pcr_strip',
|
|
60
|
-
'opentrons_96_aluminumblock_biorad_wellplate_200ul',
|
|
61
|
-
'tipone_96_tiprack_200ul',
|
|
62
|
-
'eppendorf_96_tiprack_1000ul_eptips',
|
|
63
|
-
'eppendorf_96_tiprack_10ul_eptips',
|
|
64
|
-
// Replaced by opentrons_96_wellplate_200ul_pcr_full_skirt
|
|
65
|
-
// https://opentrons.atlassian.net/browse/RLAB-230
|
|
66
|
-
'armadillo_96_wellplate_200ul_pcr_full_skirt',
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
export const getLabwareDisplayName = (
|
|
70
|
-
labwareDef: LabwareDefinition2
|
|
71
|
-
): string => {
|
|
72
|
-
const { displayName } = labwareDef.metadata
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
getLabwareDefIsStandard(labwareDef) &&
|
|
76
|
-
RETIRED_LABWARE.includes(labwareDef.parameters.loadName)
|
|
77
|
-
) {
|
|
78
|
-
return `(Retired) ${displayName}`
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return displayName
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export const getTiprackVolume = (labwareDef: LabwareDefinition2): number => {
|
|
85
|
-
assert(
|
|
86
|
-
labwareDef.parameters.isTiprack,
|
|
87
|
-
`getTiprackVolume expected a tiprack labware ${getLabwareDefURI(
|
|
88
|
-
labwareDef
|
|
89
|
-
)}, but 'isTiprack' isn't true`
|
|
90
|
-
)
|
|
91
|
-
// NOTE: Ian 2019-04-16 assuming all tips are the same volume across the rack
|
|
92
|
-
const volume = labwareDef.wells.A1.totalLiquidVolume
|
|
93
|
-
assert(
|
|
94
|
-
volume >= 0,
|
|
95
|
-
`getTiprackVolume expected tip volume to be at least 0, got ${volume}`
|
|
96
|
-
)
|
|
97
|
-
return volume
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function getLabwareHasQuirk(
|
|
101
|
-
labwareDef: LabwareDefinition2,
|
|
102
|
-
quirk: string
|
|
103
|
-
): boolean {
|
|
104
|
-
const quirks = labwareDef.parameters.quirks
|
|
105
|
-
return quirks ? quirks.includes(quirk) : false
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export const intToAlphabetLetter = (
|
|
109
|
-
i: number,
|
|
110
|
-
lowerCase: boolean = false
|
|
111
|
-
): string => String.fromCharCode((lowerCase ? 96 : 65) + i)
|
|
112
|
-
// These utils are great candidates for unit tests
|
|
113
|
-
export const toWellName = ({
|
|
114
|
-
rowNum,
|
|
115
|
-
colNum,
|
|
116
|
-
}: {
|
|
117
|
-
rowNum: number
|
|
118
|
-
colNum: number
|
|
119
|
-
}): string => String.fromCharCode(rowNum + 65) + (colNum + 1)
|
|
120
|
-
|
|
121
|
-
function _parseWell(well: string): [string, number] {
|
|
122
|
-
const res = well.match(/([A-Z]+)(\d+)/)
|
|
123
|
-
const letters = res && res[1]
|
|
124
|
-
const number = res && parseInt(res[2])
|
|
125
|
-
|
|
126
|
-
if (!letters || number == null || Number.isNaN(number)) {
|
|
127
|
-
console.warn(
|
|
128
|
-
`Could not parse well ${well}. Got letters: "${
|
|
129
|
-
letters || 'void'
|
|
130
|
-
}", number: "${number || 'void'}"`
|
|
131
|
-
)
|
|
132
|
-
return ['', NaN]
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return [letters, number]
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** A compareFunction for sorting an array of well names
|
|
139
|
-
* Goes down the columns (A1 to H1 on 96 plate)
|
|
140
|
-
* Then L to R across rows (1 to 12 on 96 plate)
|
|
141
|
-
*/
|
|
142
|
-
export function sortWells(a: string, b: string): number {
|
|
143
|
-
const [letterA, numberA] = _parseWell(a)
|
|
144
|
-
|
|
145
|
-
const [letterB, numberB] = _parseWell(b)
|
|
146
|
-
|
|
147
|
-
if (numberA !== numberB) {
|
|
148
|
-
return numberA > numberB ? 1 : -1
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (letterA.length !== letterB.length) {
|
|
152
|
-
// Eg 'B' vs 'AA'
|
|
153
|
-
return letterA.length > letterB.length ? 1 : -1
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return letterA > letterB ? 1 : -1
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function splitWellsOnColumn(sortedArray: string[]): string[][] {
|
|
160
|
-
return sortedArray.reduce<string[][]>((acc, curr) => {
|
|
161
|
-
const lastColumn = acc.slice(-1)
|
|
162
|
-
|
|
163
|
-
if (lastColumn === undefined || lastColumn.length === 0) {
|
|
164
|
-
return [[curr]]
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const lastEle = lastColumn[0].slice(-1)[0].slice(1)
|
|
168
|
-
|
|
169
|
-
if (Number(curr.slice(1)) > Number(lastEle)) {
|
|
170
|
-
return [...acc, [curr]]
|
|
171
|
-
} else {
|
|
172
|
-
return [...acc.slice(0, -1), [...lastColumn[0], curr]]
|
|
173
|
-
}
|
|
174
|
-
}, [])
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export const getWellDepth = (
|
|
178
|
-
labwareDef: LabwareDefinition2,
|
|
179
|
-
well: string
|
|
180
|
-
): number => labwareDef.wells[well].depth
|
|
181
|
-
|
|
182
|
-
// NOTE: this is used in PD for converting "offset from top" to "mm from bottom".
|
|
183
|
-
// Assumes all wells have same offset because multi-offset not yet supported.
|
|
184
|
-
// TODO: Ian 2019-07-13 return {[string: well]: offset} to support multi-offset
|
|
185
|
-
export const getWellsDepth = (
|
|
186
|
-
labwareDef: LabwareDefinition2,
|
|
187
|
-
wells: string[]
|
|
188
|
-
): number => {
|
|
189
|
-
const offsets = wells.map(well => getWellDepth(labwareDef, well))
|
|
190
|
-
|
|
191
|
-
if (uniq(offsets).length !== 1) {
|
|
192
|
-
console.warn(
|
|
193
|
-
`expected wells ${JSON.stringify(
|
|
194
|
-
wells
|
|
195
|
-
)} to all have same offset, but they were different. Labware def is ${getLabwareDefURI(
|
|
196
|
-
labwareDef
|
|
197
|
-
)}`
|
|
198
|
-
)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return offsets[0]
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export const getSlotHasMatingSurfaceUnitVector = (
|
|
205
|
-
deckDef: DeckDefinition,
|
|
206
|
-
addressableAreaName: string
|
|
207
|
-
): boolean => {
|
|
208
|
-
const matingSurfaceUnitVector = deckDef.locations.addressableAreas.find(
|
|
209
|
-
aa => aa.id === addressableAreaName
|
|
210
|
-
)?.matingSurfaceUnitVector
|
|
211
|
-
|
|
212
|
-
return Boolean(matingSurfaceUnitVector)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export const getAreSlotsHorizontallyAdjacent = (
|
|
216
|
-
slotNameA?: string | null,
|
|
217
|
-
slotNameB?: string | null
|
|
218
|
-
): boolean => {
|
|
219
|
-
if (slotNameA == null || slotNameB == null) {
|
|
220
|
-
return false
|
|
221
|
-
}
|
|
222
|
-
const slotANumber = parseInt(slotNameA)
|
|
223
|
-
const slotBNumber = parseInt(slotNameB)
|
|
224
|
-
if (isNaN(slotBNumber) || isNaN(slotANumber)) {
|
|
225
|
-
return false
|
|
226
|
-
}
|
|
227
|
-
const orderedSlots = standardOt2DeckDef.locations.cutouts
|
|
228
|
-
// intentionally not substracting by 1 because trash (slot 12) should not count
|
|
229
|
-
const numSlots = orderedSlots.length
|
|
230
|
-
if (slotBNumber > numSlots || slotANumber > numSlots) {
|
|
231
|
-
return false
|
|
232
|
-
}
|
|
233
|
-
const slotWidth = orderedSlots[1].position[0] - orderedSlots[0].position[0]
|
|
234
|
-
const slotAPosition = orderedSlots[slotANumber - 1].position
|
|
235
|
-
const slotBPosition = orderedSlots[slotBNumber - 1].position
|
|
236
|
-
|
|
237
|
-
const yPositionSlotA = slotAPosition[1]
|
|
238
|
-
const yPositionSlotB = slotBPosition[1]
|
|
239
|
-
|
|
240
|
-
const xPositionSlotA = slotAPosition[0]
|
|
241
|
-
const xPositionSlotB = slotBPosition[0]
|
|
242
|
-
|
|
243
|
-
const areSlotsHorizontallyAdjacent =
|
|
244
|
-
yPositionSlotA === yPositionSlotB &&
|
|
245
|
-
Math.abs(xPositionSlotA - xPositionSlotB) === slotWidth
|
|
246
|
-
|
|
247
|
-
return areSlotsHorizontallyAdjacent
|
|
248
|
-
}
|
|
249
|
-
export const getAreSlotsVerticallyAdjacent = (
|
|
250
|
-
slotNameA?: string | null,
|
|
251
|
-
slotNameB?: string | null
|
|
252
|
-
): boolean => {
|
|
253
|
-
if (slotNameA == null || slotNameB == null) {
|
|
254
|
-
return false
|
|
255
|
-
}
|
|
256
|
-
const slotANumber = parseInt(slotNameA)
|
|
257
|
-
const slotBNumber = parseInt(slotNameB)
|
|
258
|
-
|
|
259
|
-
if (isNaN(slotBNumber) || isNaN(slotANumber)) {
|
|
260
|
-
return false
|
|
261
|
-
}
|
|
262
|
-
const orderedSlots = standardOt2DeckDef.locations.cutouts
|
|
263
|
-
// intentionally not substracting by 1 because trash (slot 12) should not count
|
|
264
|
-
const numSlots = orderedSlots.length
|
|
265
|
-
|
|
266
|
-
if (slotBNumber > numSlots || slotANumber > numSlots) {
|
|
267
|
-
return false
|
|
268
|
-
}
|
|
269
|
-
// take the y coord of slot 4, and subtact from y coord of slot 1
|
|
270
|
-
const slotHeight = orderedSlots[3].position[1] - orderedSlots[0].position[1]
|
|
271
|
-
const slotAPosition = orderedSlots[slotANumber - 1].position
|
|
272
|
-
const slotBPosition = orderedSlots[slotBNumber - 1].position
|
|
273
|
-
|
|
274
|
-
const yPositionSlotA = slotAPosition[1]
|
|
275
|
-
const yPositionSlotB = slotBPosition[1]
|
|
276
|
-
|
|
277
|
-
const xPositionSlotA = slotAPosition[0]
|
|
278
|
-
const xPositionSlotB = slotBPosition[0]
|
|
279
|
-
|
|
280
|
-
const areSlotsVerticallyAdjacent =
|
|
281
|
-
xPositionSlotA === xPositionSlotB &&
|
|
282
|
-
Math.abs(yPositionSlotA - yPositionSlotB) === slotHeight
|
|
283
|
-
|
|
284
|
-
return areSlotsVerticallyAdjacent
|
|
285
|
-
}
|
|
286
|
-
// TODO(jr, 11/12/23): rename this utility to mention that it
|
|
287
|
-
// is only used in the OT-2, same with getAreSlotsHorizontallyAdjacent
|
|
288
|
-
// and getAreSlotsVerticallyAdjacent
|
|
289
|
-
export const getAreSlotsAdjacent = (
|
|
290
|
-
slotNameA?: string | null,
|
|
291
|
-
slotNameB?: string | null
|
|
292
|
-
): boolean =>
|
|
293
|
-
getAreSlotsHorizontallyAdjacent(slotNameA, slotNameB) ||
|
|
294
|
-
getAreSlotsVerticallyAdjacent(slotNameA, slotNameB)
|
|
295
|
-
|
|
296
|
-
export const getIsLabwareAboveHeight = (
|
|
297
|
-
labwareDef: LabwareDefinition2,
|
|
298
|
-
height: number
|
|
299
|
-
): boolean => labwareDef.dimensions.zDimension > height
|
|
300
|
-
|
|
301
|
-
export const getAdapterName = (labwareLoadname: string): ThermalAdapterName => {
|
|
302
|
-
let adapterName: ThermalAdapterName = 'Universal Flat Adapter'
|
|
303
|
-
|
|
304
|
-
if (
|
|
305
|
-
labwareLoadname ===
|
|
306
|
-
'opentrons_96_pcr_adapter_nest_wellplate_100ul_pcr_full_skirt'
|
|
307
|
-
) {
|
|
308
|
-
adapterName = 'PCR Adapter'
|
|
309
|
-
} else if (
|
|
310
|
-
labwareLoadname === 'opentrons_96_deep_well_adapter_nest_wellplate_2ml_deep'
|
|
311
|
-
) {
|
|
312
|
-
adapterName = 'Deep Well Adapter'
|
|
313
|
-
} else if (
|
|
314
|
-
labwareLoadname ===
|
|
315
|
-
'opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat'
|
|
316
|
-
) {
|
|
317
|
-
adapterName = '96 Flat Bottom Adapter'
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return adapterName
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
export const getCalibrationAdapterLoadName = (
|
|
324
|
-
moduleModel: ModuleModel
|
|
325
|
-
): string | null => {
|
|
326
|
-
switch (moduleModel) {
|
|
327
|
-
case 'heaterShakerModuleV1':
|
|
328
|
-
return 'opentrons_calibration_adapter_heatershaker_module'
|
|
329
|
-
case 'temperatureModuleV2':
|
|
330
|
-
return 'opentrons_calibration_adapter_temperature_module'
|
|
331
|
-
case 'thermocyclerModuleV2':
|
|
332
|
-
return 'opentrons_calibration_adapter_thermocycler_module'
|
|
333
|
-
default:
|
|
334
|
-
console.error(
|
|
335
|
-
`${moduleModel} does not have an associated calibration adapter`
|
|
336
|
-
)
|
|
337
|
-
return null
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
export const getDeckDefFromRobotType = (
|
|
342
|
-
robotType: RobotType
|
|
343
|
-
): DeckDefinition => {
|
|
344
|
-
// @ts-expect-error imported JSON not playing nice with TS. see https://github.com/microsoft/TypeScript/issues/32063
|
|
345
|
-
return robotType === 'OT-3 Standard'
|
|
346
|
-
? standardFlexDeckDef
|
|
347
|
-
: standardOt2DeckDef
|
|
348
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import isEqual from 'lodash/isEqual'
|
|
2
|
-
import uniqWith from 'lodash/uniqWith'
|
|
3
|
-
import round from 'lodash/round'
|
|
4
|
-
import sortedUniq from 'lodash/sortedUniq'
|
|
5
|
-
import uniq from 'lodash/uniq'
|
|
6
|
-
import type {
|
|
7
|
-
LabwareWell,
|
|
8
|
-
LabwareWellShapeProperties,
|
|
9
|
-
LabwareDefinition2,
|
|
10
|
-
LabwareWellGroupMetadata,
|
|
11
|
-
LabwareBrand,
|
|
12
|
-
} from '../types'
|
|
13
|
-
|
|
14
|
-
export interface LabwareWellGroupProperties {
|
|
15
|
-
xOffsetFromLeft: number
|
|
16
|
-
yOffsetFromTop: number
|
|
17
|
-
xSpacing: number | null
|
|
18
|
-
ySpacing: number | null
|
|
19
|
-
wellCount: number
|
|
20
|
-
shape: LabwareWellShapeProperties | null
|
|
21
|
-
depth: number | null
|
|
22
|
-
totalLiquidVolume: number | null
|
|
23
|
-
metadata: LabwareWellGroupMetadata
|
|
24
|
-
brand: LabwareBrand | null
|
|
25
|
-
}
|
|
26
|
-
const ROUNDING_PRECISION = 2
|
|
27
|
-
|
|
28
|
-
export function getUniqueWellProperties(
|
|
29
|
-
definition: LabwareDefinition2
|
|
30
|
-
): LabwareWellGroupProperties[] {
|
|
31
|
-
const { groups, wells, dimensions } = definition
|
|
32
|
-
|
|
33
|
-
return groups.map(group => {
|
|
34
|
-
const wellProps = group.wells.map(n => wells[n])
|
|
35
|
-
|
|
36
|
-
const wellDepths = wellProps.map<number>(w => w.depth)
|
|
37
|
-
const wellVolumes = wellProps.map<number>(w => w.totalLiquidVolume)
|
|
38
|
-
const wellShapes = wellProps.map<LabwareWellShapeProperties>(
|
|
39
|
-
(well: LabwareWell) =>
|
|
40
|
-
well.shape === 'circular'
|
|
41
|
-
? { shape: well.shape, diameter: well.diameter }
|
|
42
|
-
: {
|
|
43
|
-
shape: well.shape,
|
|
44
|
-
xDimension: well.xDimension,
|
|
45
|
-
yDimension: well.yDimension,
|
|
46
|
-
}
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
const xStart = wellProps[0]?.x ?? 0
|
|
50
|
-
const yStart = wellProps[0]?.y ?? 0
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
metadata: group.metadata,
|
|
54
|
-
brand: group.brand || null,
|
|
55
|
-
xSpacing: getSpacingIfUniform(wellProps, 'x'),
|
|
56
|
-
ySpacing: getSpacingIfUniform(wellProps, 'y'),
|
|
57
|
-
xOffsetFromLeft: xStart,
|
|
58
|
-
yOffsetFromTop: round(dimensions.yDimension - yStart, ROUNDING_PRECISION),
|
|
59
|
-
wellCount: wellProps.length,
|
|
60
|
-
depth: getIfConsistent(wellDepths),
|
|
61
|
-
totalLiquidVolume: getIfConsistent(wellVolumes),
|
|
62
|
-
shape: getIfConsistent(wellShapes),
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function getIfConsistent<T>(items: T[]): T | null {
|
|
68
|
-
return uniqWith(items, isEqual).length === 1 ? items[0] : null
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// returning null means "spacing is irregular"; returning 0 means "there is only 1 well along the given axis"
|
|
72
|
-
export function getSpacingIfUniform(
|
|
73
|
-
wells: LabwareWell[],
|
|
74
|
-
axis: 'x' | 'y'
|
|
75
|
-
): number | null {
|
|
76
|
-
const wellPositions = sortedUniq(uniq(wells.map(well => well[axis])))
|
|
77
|
-
if (wellPositions.length < 2) return 0
|
|
78
|
-
|
|
79
|
-
const initialSpacing = round(
|
|
80
|
-
wellPositions[1] - wellPositions[0],
|
|
81
|
-
ROUNDING_PRECISION
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
for (let i = 2; i < wellPositions.length; i++) {
|
|
85
|
-
const pos = wellPositions[i]
|
|
86
|
-
const prevWellPos = wellPositions[i - 1]
|
|
87
|
-
const spacing = round(pos - prevWellPos, ROUNDING_PRECISION)
|
|
88
|
-
if (spacing !== initialSpacing) return null
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return Math.abs(initialSpacing)
|
|
92
|
-
}
|
package/js/helpers/orderWells.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import zipWith from 'lodash/zipWith'
|
|
2
|
-
import uniq from 'lodash/uniq'
|
|
3
|
-
import compact from 'lodash/compact'
|
|
4
|
-
import flatten from 'lodash/flatten'
|
|
5
|
-
|
|
6
|
-
export type WellOrderOption = 'l2r' | 'r2l' | 't2b' | 'b2t'
|
|
7
|
-
|
|
8
|
-
// labware definitions in shared-data have an ordering
|
|
9
|
-
// attribute which is an Array of Arrays of wells. Each inner
|
|
10
|
-
// Array represents a physical column of wells on the labware
|
|
11
|
-
// this function takes that 2d Array of columns and returns
|
|
12
|
-
// the same wells in a 2d Array where each inner Array represents
|
|
13
|
-
// a physical row of wells on the labware.
|
|
14
|
-
// e.g.
|
|
15
|
-
// [ \ [
|
|
16
|
-
// ['A1', 'B1'], === \ ['A1', 'A2']
|
|
17
|
-
// ['A2', 'B2'] === / ['B1', 'B2']
|
|
18
|
-
// ] / ]
|
|
19
|
-
//
|
|
20
|
-
const _orderingColsToRows = (ordering: string[][]): string[][] =>
|
|
21
|
-
zipWith(...ordering, (...col) => compact(uniq(col)))
|
|
22
|
-
|
|
23
|
-
// given a default "ordering" 2d array from labware definitions
|
|
24
|
-
// where each inner array represents a physical column of wells
|
|
25
|
-
// and well order option selections (e.g. 'l2r', 'r2l', 't2b', 'b2t')
|
|
26
|
-
// this function returns a 1d array of wells from the labware
|
|
27
|
-
// ordered by the given traversal technique
|
|
28
|
-
export const orderWells = (
|
|
29
|
-
defaultOrdering: string[][],
|
|
30
|
-
first: WellOrderOption,
|
|
31
|
-
second: WellOrderOption
|
|
32
|
-
): string[] => {
|
|
33
|
-
let orderedWells: string[][] = []
|
|
34
|
-
|
|
35
|
-
if (first === 't2b') {
|
|
36
|
-
if (second === 'l2r') {
|
|
37
|
-
orderedWells = defaultOrdering
|
|
38
|
-
} else if (second === 'r2l') {
|
|
39
|
-
orderedWells = defaultOrdering.slice().reverse()
|
|
40
|
-
}
|
|
41
|
-
} else if (first === 'b2t') {
|
|
42
|
-
if (second === 'l2r') {
|
|
43
|
-
orderedWells = defaultOrdering.map(col => col.slice().reverse())
|
|
44
|
-
} else if (second === 'r2l') {
|
|
45
|
-
orderedWells = defaultOrdering
|
|
46
|
-
.slice()
|
|
47
|
-
.reverse()
|
|
48
|
-
.map(col => col.slice().reverse())
|
|
49
|
-
}
|
|
50
|
-
} else if (first === 'l2r') {
|
|
51
|
-
if (second === 't2b') {
|
|
52
|
-
orderedWells = _orderingColsToRows(defaultOrdering)
|
|
53
|
-
} else if (second === 'b2t') {
|
|
54
|
-
orderedWells = _orderingColsToRows(defaultOrdering).slice().reverse()
|
|
55
|
-
}
|
|
56
|
-
} else if (first === 'r2l') {
|
|
57
|
-
if (second === 't2b') {
|
|
58
|
-
orderedWells = _orderingColsToRows(defaultOrdering).map(col =>
|
|
59
|
-
col.slice().reverse()
|
|
60
|
-
)
|
|
61
|
-
} else if (second === 'b2t') {
|
|
62
|
-
orderedWells = _orderingColsToRows(defaultOrdering)
|
|
63
|
-
.slice()
|
|
64
|
-
.reverse()
|
|
65
|
-
.map(col => col.slice().reverse())
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return flatten(orderedWells)
|
|
69
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import Ajv from 'ajv'
|
|
2
|
-
import labwareV2Schema from '../../labware/schemas/2.json'
|
|
3
|
-
import protocolSchemaV1 from '../../protocol/schemas/1.json'
|
|
4
|
-
import protocolSchemaV2 from '../../protocol/schemas/2.json'
|
|
5
|
-
import protocolSchemaV3 from '../../protocol/schemas/3.json'
|
|
6
|
-
import protocolSchemaV4 from '../../protocol/schemas/4.json'
|
|
7
|
-
import protocolSchemaV5 from '../../protocol/schemas/5.json'
|
|
8
|
-
|
|
9
|
-
import type { ErrorObject } from 'ajv'
|
|
10
|
-
import type { JsonProtocolFile } from '../../protocol'
|
|
11
|
-
|
|
12
|
-
export type ProtocolParseErrorKey = 'INVALID_FILE_TYPE' | 'INVALID_JSON_FILE'
|
|
13
|
-
|
|
14
|
-
interface ProtocolParseErrorDetails {
|
|
15
|
-
rawError?: string
|
|
16
|
-
schemaErrors?: ErrorObject[] | null
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export type ProtocolParseErrorHandler = (
|
|
20
|
-
errorKey: ProtocolParseErrorKey,
|
|
21
|
-
errorDetails?: ProtocolParseErrorDetails
|
|
22
|
-
) => unknown
|
|
23
|
-
|
|
24
|
-
type ProtocolSchema =
|
|
25
|
-
| typeof protocolSchemaV1
|
|
26
|
-
| typeof protocolSchemaV2
|
|
27
|
-
| typeof protocolSchemaV3
|
|
28
|
-
| typeof protocolSchemaV4
|
|
29
|
-
| typeof protocolSchemaV5
|
|
30
|
-
|
|
31
|
-
const SCHEMA_BY_VERSION: { [version: string]: ProtocolSchema } = {
|
|
32
|
-
'1': protocolSchemaV1,
|
|
33
|
-
'2': protocolSchemaV2,
|
|
34
|
-
'3': protocolSchemaV3,
|
|
35
|
-
'4': protocolSchemaV4,
|
|
36
|
-
'5': protocolSchemaV5,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface PythonProtocolMetadata {
|
|
40
|
-
source?: string
|
|
41
|
-
[key: string]: unknown
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// data may be a full JSON protocol or just a metadata dict from Python
|
|
45
|
-
export type ProtocolData =
|
|
46
|
-
| JsonProtocolFile
|
|
47
|
-
| { metadata: PythonProtocolMetadata }
|
|
48
|
-
|
|
49
|
-
export function parseProtocolData(
|
|
50
|
-
file: File,
|
|
51
|
-
contents: string,
|
|
52
|
-
handleError?: ProtocolParseErrorHandler,
|
|
53
|
-
metadata?: PythonProtocolMetadata | null // optional Python protocol metadata
|
|
54
|
-
): ProtocolData | null {
|
|
55
|
-
if (fileExtensionIsJson(file.name)) {
|
|
56
|
-
return validateJsonProtocolFileContents(contents, handleError)
|
|
57
|
-
} else if (fileExtensionIsPython(file.name)) {
|
|
58
|
-
// grab Python protocol metadata, if any
|
|
59
|
-
return metadata != null ? { metadata } : null
|
|
60
|
-
} else if (fileExtensionIsZip(file.name)) {
|
|
61
|
-
return null
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
handleError && handleError('INVALID_FILE_TYPE')
|
|
65
|
-
return null
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function fileExtensionIsPython(filename: string): boolean {
|
|
69
|
-
return Boolean(/\.py$/i.test(filename))
|
|
70
|
-
}
|
|
71
|
-
export function fileExtensionIsJson(filename: string): boolean {
|
|
72
|
-
return Boolean(/\.json$/i.test(filename))
|
|
73
|
-
}
|
|
74
|
-
export function fileExtensionIsZip(filename: string): boolean {
|
|
75
|
-
return Boolean(/\.zip$/i.test(filename))
|
|
76
|
-
}
|
|
77
|
-
export function fileIsBinary(file: File): boolean {
|
|
78
|
-
// bundles are always binary files, and currently nothing else is binary
|
|
79
|
-
return fileExtensionIsZip(file.name)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function validateJsonProtocolFileContents(
|
|
83
|
-
fileContents: string,
|
|
84
|
-
handleError?: ProtocolParseErrorHandler
|
|
85
|
-
): JsonProtocolFile | null {
|
|
86
|
-
try {
|
|
87
|
-
if (typeof fileContents !== 'string') {
|
|
88
|
-
handleError && handleError('INVALID_FILE_TYPE')
|
|
89
|
-
return null
|
|
90
|
-
} else {
|
|
91
|
-
const ajv = new Ajv({ allErrors: true, jsonPointers: true })
|
|
92
|
-
// protocol schema contains reference to v2 labware schema, so give AJV access to it
|
|
93
|
-
ajv.addSchema(labwareV2Schema)
|
|
94
|
-
|
|
95
|
-
const parsedProtocol = JSON.parse(fileContents) as any
|
|
96
|
-
let validateAgainstSchema
|
|
97
|
-
if ('protocol-schema' in parsedProtocol) {
|
|
98
|
-
// 'protocol-schema' key only present in V1
|
|
99
|
-
validateAgainstSchema = ajv.compile(protocolSchemaV1)
|
|
100
|
-
} else {
|
|
101
|
-
const { schemaVersion } = parsedProtocol
|
|
102
|
-
validateAgainstSchema = ajv.compile(
|
|
103
|
-
SCHEMA_BY_VERSION[String(schemaVersion)]
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (!validateAgainstSchema(parsedProtocol)) {
|
|
108
|
-
handleError &&
|
|
109
|
-
handleError('INVALID_JSON_FILE', {
|
|
110
|
-
schemaErrors: validateAgainstSchema.errors,
|
|
111
|
-
})
|
|
112
|
-
return null
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return parsedProtocol
|
|
116
|
-
}
|
|
117
|
-
} catch (error) {
|
|
118
|
-
handleError && handleError('INVALID_JSON_FILE', { rawError: error })
|
|
119
|
-
return null
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export function getProtocolDesignerApplicationName(
|
|
124
|
-
protocol: JsonProtocolFile
|
|
125
|
-
): string | null {
|
|
126
|
-
if (
|
|
127
|
-
'designerApplication' in protocol &&
|
|
128
|
-
protocol.designerApplication != null
|
|
129
|
-
) {
|
|
130
|
-
return 'name' in protocol.designerApplication &&
|
|
131
|
-
protocol.designerApplication.name != null
|
|
132
|
-
? protocol.designerApplication.name
|
|
133
|
-
: null
|
|
134
|
-
} else if (
|
|
135
|
-
'designer-application' in protocol &&
|
|
136
|
-
protocol['designer-application']['application-name'] != null
|
|
137
|
-
) {
|
|
138
|
-
return protocol['designer-application']['application-name']
|
|
139
|
-
}
|
|
140
|
-
return null
|
|
141
|
-
}
|
package/js/helpers/volume.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
// utilities for working with volumes in µL
|
|
2
|
-
import round from 'lodash/round'
|
|
3
|
-
|
|
4
|
-
import type { LabwareVolumeUnits } from '../types'
|
|
5
|
-
|
|
6
|
-
const SCALE_BY_UNITS = {
|
|
7
|
-
µL: 1,
|
|
8
|
-
mL: 1000,
|
|
9
|
-
L: 1000000,
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function getDisplayVolume(
|
|
13
|
-
volumeInMicroliters: number,
|
|
14
|
-
displayUnits: LabwareVolumeUnits = 'µL',
|
|
15
|
-
digits?: number
|
|
16
|
-
): string {
|
|
17
|
-
const volume = volumeInMicroliters / SCALE_BY_UNITS[displayUnits]
|
|
18
|
-
|
|
19
|
-
return `${typeof digits === 'number' ? round(volume, digits) : volume}`
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function getAsciiVolumeUnits(displayUnits: LabwareVolumeUnits): string {
|
|
23
|
-
if (displayUnits === 'µL') return 'uL'
|
|
24
|
-
|
|
25
|
-
return displayUnits
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function ensureVolumeUnits(
|
|
29
|
-
maybeUnits: string | null | undefined
|
|
30
|
-
): LabwareVolumeUnits {
|
|
31
|
-
if (maybeUnits === 'mL' || maybeUnits === 'ml') return 'mL'
|
|
32
|
-
if (maybeUnits === 'L' || maybeUnits === 'l') return 'L'
|
|
33
|
-
|
|
34
|
-
return 'µL'
|
|
35
|
-
}
|
package/js/helpers/wellIsRect.ts
DELETED