Skip to content

Commit 4178853

Browse files
authored
Merge branch 'edge' into stacker-labware-offsets
2 parents 45d94c8 + 0616695 commit 4178853

File tree

251 files changed

+10010
-3661
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

251 files changed

+10010
-3661
lines changed

.github/workflows/api-test-lint-deploy.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ jobs:
120120
run: make -C api test-cov
121121
- name: Ensure assets build
122122
run: make -C api sdist wheel
123+
- name: Check stacking regression tests
124+
run: make -C api test-integration
123125
- name: Upload coverage report
124126
uses: 'codecov/codecov-action@v3'
125127
with:
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"ordering": [],
3+
"brand": {
4+
"brand": "Millipore",
5+
"brandId": []
6+
},
7+
"metadata": {
8+
"displayName": "Millipore 24 Ball Magnet",
9+
"displayCategory": "adapter",
10+
"displayVolumeUnits": "µL",
11+
"tags": []
12+
},
13+
"dimensions": {
14+
"xDimension": 127.8,
15+
"yDimension": 85.6,
16+
"zDimension": 11.15
17+
},
18+
"wells": {},
19+
"groups": [
20+
{
21+
"metadata": {},
22+
"wells": []
23+
}
24+
],
25+
"parameters": {
26+
"format": "96Standard",
27+
"quirks": [],
28+
"isTiprack": false,
29+
"isMagneticModuleCompatible": false,
30+
"loadName": "millipore_24_ball_magnet"
31+
},
32+
"namespace": "custom_beta",
33+
"version": 2,
34+
"schemaVersion": 2,
35+
"allowedRoles": ["adapter"],
36+
"cornerOffsetFromSlot": {
37+
"x": 0,
38+
"y": 0,
39+
"z": 0
40+
},
41+
"gripperOffsets": {
42+
"default": {
43+
"pickUpOffset": {
44+
"x": 0,
45+
"y": 0,
46+
"z": 0
47+
},
48+
"dropOffset": {
49+
"x": 0,
50+
"y": 0,
51+
"z": 0.5
52+
}
53+
}
54+
}
55+
}
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
"""Duolink Day 1 Multiwell Round Well Protocol."""
2+
from opentrons.types import Point
3+
from opentrons.protocol_api import ProtocolContext, ParameterContext, Well
4+
from typing import List
5+
from opentrons.protocol_api.module_contexts import HeaterShakerContext
6+
7+
8+
metadata = {
9+
"protocolName": """Duolink PLA for Microscopy (Multiwell Plate Assay with
10+
96 Round Well Culture Plate) - Day 1""",
11+
"author": "Opentrons Science Team",
12+
}
13+
14+
requirements = {
15+
"robotType": "Flex",
16+
"apiLevel": "2.23",
17+
}
18+
19+
VOL_BLOCK = 40
20+
VOL_AB = 40
21+
22+
MIN_BLOCK = 60
23+
24+
H_DISCARD = 0.7
25+
D_1K = -2.3
26+
D_200 = -2.1
27+
28+
DEFAULT_RATE = 700
29+
SLOW = 100 # speed up
30+
31+
32+
def add_parameters(parameters: ParameterContext) -> None:
33+
"""Add parameters to the protocol context."""
34+
parameters.add_int(
35+
variable_name="num_sample",
36+
display_name="Number of Samples",
37+
description="Number of samples to be processed (up to 96)",
38+
default=96,
39+
minimum=1,
40+
maximum=96,
41+
)
42+
parameters.add_bool(
43+
variable_name="dry_run",
44+
display_name="Dry Run",
45+
description="All incubation steps skipped and tips returned to tipracks",
46+
default=False,
47+
)
48+
parameters.add_bool(
49+
variable_name="heat_on_deck",
50+
display_name="Incubation on Deck",
51+
description="Use Heater-Shaker Module for 37 degree C incubation?",
52+
default=True,
53+
)
54+
parameters.add_bool(
55+
variable_name="use_lid",
56+
display_name="Use Plate Lid",
57+
description="Use a lid to cover assay plate during incubation?",
58+
default=True,
59+
)
60+
61+
62+
def run(ctx: ProtocolContext) -> None:
63+
"""Run the protocol."""
64+
num_sample = ctx.params.num_sample # type: ignore[attr-defined]
65+
dry_run = ctx.params.dry_run # type: ignore[attr-defined]
66+
heat_on_deck = ctx.params.heat_on_deck # type: ignore[attr-defined]
67+
use_lid = ctx.params.use_lid # type: ignore[attr-defined]
68+
69+
num_col_full = int(num_sample // 8)
70+
num_well_last_col = num_sample % 8
71+
if num_well_last_col > 0:
72+
num_col_total = num_col_full + 1
73+
else:
74+
num_col_total = num_col_full
75+
76+
# deck layout
77+
if heat_on_deck:
78+
hs: HeaterShakerContext = ctx.load_module(
79+
"heaterShakerModuleV1", "D1"
80+
) # type: ignore[assignment]
81+
hs_adapter = hs.load_adapter("opentrons_universal_flat_adapter")
82+
83+
working_plate = ctx.load_labware(
84+
"corning_96_wellplate_360ul_flat", "C2", "ASSAY PLATE"
85+
)
86+
87+
reagent_plate = ctx.load_labware("nest_96_wellplate_2ml_deep", "C1", "REAGENTS")
88+
waste_res = ctx.load_labware("nest_1_reservoir_290ml", "D2", "LIQUID WASTE")
89+
waste = waste_res.wells()[0]
90+
91+
ctx.load_trash_bin("A3")
92+
93+
tips_1k = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B3", "1000uL TIPS")
94+
tips_200 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "B2", "200uL TIPS")
95+
96+
p1k_8 = ctx.load_instrument("flex_8channel_1000", "left")
97+
p1k_1 = ctx.load_instrument("flex_1channel_1000", "right")
98+
99+
p1k_8.flow_rate.aspirate = DEFAULT_RATE
100+
p1k_8.flow_rate.dispense = DEFAULT_RATE
101+
p1k_1.flow_rate.aspirate = DEFAULT_RATE
102+
p1k_1.flow_rate.dispense = DEFAULT_RATE
103+
104+
# liquid location
105+
rxn_total = working_plate.rows()[0][:num_col_total]
106+
rxn_full = working_plate.rows()[0][:num_col_full]
107+
if num_well_last_col > 0:
108+
rxn_remainder = working_plate.wells()[
109+
num_col_full * 8 : num_col_full * 8 + num_well_last_col
110+
]
111+
112+
ab = reagent_plate.rows()[0][0]
113+
ab_remainder = reagent_plate.wells()[:num_well_last_col]
114+
block = reagent_plate.rows()[0][1]
115+
block_remainder = reagent_plate.wells()[8 : 8 + num_well_last_col]
116+
117+
# volume info
118+
vol_ab = 40 * num_col_full + 40
119+
vol_ab_plus_one = 40 * (num_col_full + 1) + 40
120+
def_ab = ctx.define_liquid(
121+
name="ANTIBODY SOLUTION", description="", display_color="#98FB98"
122+
) # green
123+
if num_well_last_col > 0:
124+
[
125+
reagent_plate.rows()[row][0].load_liquid(
126+
liquid=def_ab, volume=vol_ab_plus_one
127+
)
128+
for row in range(num_well_last_col)
129+
]
130+
[
131+
reagent_plate.rows()[row][0].load_liquid(liquid=def_ab, volume=vol_ab)
132+
for row in range(num_well_last_col, 8)
133+
]
134+
135+
vol_re = 40 * num_col_total + 40
136+
def_block = ctx.define_liquid(
137+
name="BLOCKING SOLUTION", description="", display_color="#FFC300"
138+
) # yellow
139+
[
140+
reagent_plate.rows()[row][1].load_liquid(liquid=def_block, volume=vol_re)
141+
for row in range(8)
142+
]
143+
144+
if use_lid:
145+
146+
ctx.load_lid_stack("corning_96_wellplate_360ul_lid", "C4", 1)
147+
148+
def cover_plate() -> None:
149+
"""Cover the plate with a lid."""
150+
ctx.move_lid(
151+
"C4",
152+
working_plate,
153+
use_gripper=True,
154+
pick_up_offset={"x": 0, "y": 0, "z": 0},
155+
drop_offset={"x": 0, "y": 0, "z": 0},
156+
)
157+
158+
def remove_lid() -> None:
159+
"""Remove the lid from the plate."""
160+
ctx.move_lid(
161+
working_plate,
162+
"C4",
163+
use_gripper=True,
164+
pick_up_offset={"x": 0, "y": 0, "z": 0},
165+
drop_offset={"x": 0, "y": 0, "z": 0},
166+
)
167+
168+
def heat(min: float) -> None:
169+
"""Heat the plate on the Heater-Shaker."""
170+
hs.set_and_wait_for_temperature(37)
171+
hs.open_labware_latch()
172+
ctx.move_labware(
173+
labware=working_plate,
174+
new_location=hs_adapter,
175+
use_gripper=True,
176+
pick_up_offset={"x": 0, "y": 0, "z": -7},
177+
drop_offset={"x": 0, "y": 0, "z": -7},
178+
)
179+
180+
hs.close_labware_latch()
181+
hs.open_labware_latch()
182+
183+
ctx.delay(minutes=min)
184+
185+
ctx.move_labware(
186+
labware=working_plate,
187+
new_location="C2",
188+
use_gripper=True,
189+
pick_up_offset={"x": 0, "y": 0, "z": -7},
190+
drop_offset={"x": 0, "y": 0, "z": -7},
191+
)
192+
193+
def transfer(start: Well, vol: float) -> None:
194+
"""Transfer liquid from start wells to reaction wells."""
195+
p1k_8.tip_racks = [tips_1k]
196+
p1k_8.pick_up_tip()
197+
p1k_8.aspirate(vol * len(rxn_full), start)
198+
p1k_8.air_gap(10)
199+
p1k_8.dispense(10, rxn_full[0].top(z=0))
200+
p1k_8.flow_rate.dispense = SLOW
201+
for col in rxn_full:
202+
p1k_8.move_to(col.top(z=0))
203+
p1k_8.dispense(vol, col.top(z=-2).move(Point(x=D_1K, y=D_1K)))
204+
p1k_8.move_to(col.top(z=0))
205+
p1k_8.blow_out()
206+
if dry_run:
207+
p1k_8.return_tip()
208+
else:
209+
p1k_8.drop_tip()
210+
p1k_8.flow_rate.dispense = DEFAULT_RATE
211+
212+
def transfer_remainder(start: List[Well], vol: float) -> None:
213+
"""Transfer remaining liquid from start wells to reaction wells."""
214+
p1k_1.tip_racks = [tips_1k]
215+
p1k_1.pick_up_tip()
216+
for well_start in start:
217+
p1k_1.aspirate(vol, well_start)
218+
p1k_1.air_gap(10)
219+
p1k_1.dispense(10, rxn_remainder[0].top(z=0))
220+
p1k_1.flow_rate.dispense = SLOW
221+
for well_end in rxn_remainder:
222+
p1k_1.move_to(well_end.top(z=0))
223+
p1k_1.dispense(vol, well_end.top(z=-2).move(Point(x=D_1K, y=D_1K)))
224+
p1k_1.move_to(well_end.top(z=0))
225+
p1k_1.blow_out()
226+
if dry_run:
227+
p1k_1.return_tip()
228+
else:
229+
p1k_1.drop_tip()
230+
p1k_1.flow_rate.dispense = DEFAULT_RATE
231+
232+
def discard(vol: float) -> None:
233+
"""Discard liquid from reaction wells."""
234+
for col in rxn_total:
235+
p1k_8.tip_racks = [tips_200]
236+
p1k_8.pick_up_tip()
237+
p1k_8.flow_rate.aspirate = SLOW
238+
p1k_8.move_to(col.top(z=0))
239+
p1k_8.aspirate(
240+
vol + 20, col.bottom(z=H_DISCARD).move(Point(x=D_200, y=D_200))
241+
)
242+
ctx.delay(seconds=2)
243+
p1k_8.move_to(col.top(z=0))
244+
p1k_8.flow_rate.aspirate = DEFAULT_RATE
245+
p1k_8.dispense(vol + 20, waste.top(z=-5))
246+
p1k_8.blow_out()
247+
if dry_run:
248+
p1k_8.return_tip()
249+
else:
250+
p1k_8.drop_tip()
251+
252+
ctx.comment(" ")
253+
ctx.comment("***********************************")
254+
ctx.comment(" Transferring Blocking Solution ")
255+
ctx.comment("***********************************")
256+
ctx.comment(" ")
257+
258+
transfer(block, VOL_BLOCK)
259+
if num_well_last_col > 0:
260+
transfer_remainder(block_remainder, VOL_BLOCK)
261+
262+
ctx.comment(" ")
263+
ctx.comment("***********************************")
264+
ctx.comment(" Incubating on the Heater-Shaker ")
265+
ctx.comment("***********************************")
266+
ctx.comment(" ")
267+
268+
if use_lid:
269+
cover_plate()
270+
else:
271+
ctx.pause("Please place seal on plate.")
272+
273+
if heat_on_deck:
274+
heat(MIN_BLOCK if not dry_run else 0.1)
275+
else:
276+
ctx.pause(
277+
"Incubation at 37 degree C for 1 hour - remove seal and return plate to slot C2"
278+
)
279+
280+
if use_lid:
281+
remove_lid()
282+
283+
ctx.comment(" ")
284+
ctx.comment("***********************************")
285+
ctx.comment(" Removing Blocking Solution ")
286+
ctx.comment("***********************************")
287+
ctx.comment(" ")
288+
289+
discard(VOL_BLOCK)
290+
291+
ctx.comment(" ")
292+
ctx.comment("***********************************")
293+
ctx.comment(" Transferring Primary Antibody ")
294+
ctx.comment("***********************************")
295+
ctx.comment(" ")
296+
297+
transfer(ab, VOL_AB)
298+
if num_well_last_col > 0:
299+
transfer_remainder(ab_remainder, VOL_AB)
300+
301+
if use_lid:
302+
cover_plate()
303+
else:
304+
ctx.pause("Please place seal on plate and incubate at 4 degree C overnight")

0 commit comments

Comments
 (0)