Since many researchers are not familiar with with Python, we at Parhelia created StainWorks: a simple yet powerful web app that provides two essential features:
Templating
The ability to configure Python protocols without having to dig into the Python code. Instead, the relevant run parameters (i.e., number of samples, wash volumes, incubation times).
Global Library
A curated collection of Python scripts that implement various assays (CODEX, IHC, H&E etc). These protocols have been created, validated and maintained by the Parhelia team.
Labware layouts
Each template in StainWorks comes with a unique “Labware Layout” spreadsheet that is attached to the template. Labware Layout contains instructions on how to correctly fill reservoirs with reagents and place them onto the deck of an OT-2 robot for a particular protocol run. These spreadsheets have modifiable input fields (i.e., Omni-Stainer type, Number of samples) that generate a lot of information for how to set up the experiment.
Accessing StainWorks requires having a user account. If you purchased an S12 or C12 automation bundle, your StainWorks account will be set up automatically and a 90-day free trial of StainWorks is included with your purchase. You will receive a separate email from with your account credentials. You can learn more about exactly how to use StainWorks in the “Blue Dye Demo” tutorial below.
To continue with StainWorks-based Automation Setup, click here --> Labware Layouts
Some users may like to develop custom Omni-Stainer protocols on their own. Below is some information to assist in the user development of custom OT-2 protocols with the Omni-Stainer.
User Python Protocol Programming Outside StainWorks
Opentrons protocols are written using the Python programming language and make use of Opentrons’ Python API. This innovative feature of the OT-2 makes good use of Python’s combination of simplicity and expressive power, so you can easily program arbitrarily complex protocols even as a beginner.
global_functions.py
//## VERAO GLOBAL
## Copyright Parhelia Biosciences Corporation 2022-2023
### GLOBAL FUNCTIONS - AUTO-GENERATED - DO NOT MODIFY ###
from opentrons import protocol_api
import json
class Object:
pass
####################GENERAL SETUP################################
stats = Object()
stats.volume = 0
debug = False
####################FIXED RUN PARAMETERS#########################
default_flow_rate = 50
well_flow_rate = 5
sample_flow_rate = 0.2
extra_bottom_gap=0
def washSamples(pipette, sourceSolutionWell, samples, volume, num_repeats=1, height_offset=0, keep_tip=False):
try:
iter(samples)
except TypeError:
samples = [samples]
print('Samples are:')
print(samples)
if not pipette.has_tip:
pipette.pick_up_tip()
height_offset += extra_bottom_gap
for i in range(0, num_repeats):
for s in samples:
print(s)
print("Washing sample:" + str(s))
pipette.aspirate(volume, sourceSolutionWell.bottom(height_offset), rate=well_flow_rate)
pipette.dispense(volume, s.bottom(height_offset), rate=sample_flow_rate)
stats.volume += volume
if not keep_tip: pipette.drop_tip()
def puncture_wells(pipette, wells, height_offset=0, keep_tip=False):
try:
iter(wells)
except TypeError:
wells = [wells]
for well in wells:
washSamples(pipette, well, well, 1, 1, height_offset, keep_tip=True)
if not keep_tip: pipette.drop_tip()
def dilute_and_apply_fixative(pipette, sourceSolutionWell, dilutant_buffer_well, samples, volume, height_offset=0, keep_tip=False):
if not pipette.has_tip: pipette.pick_up_tip()
# Diluting fixative:
pipette.aspirate(volume, dilutant_buffer_well, rate=well_flow_rate)
pipette.dispense(volume, sourceSolutionWell, rate=well_flow_rate)
for iterator in range(0, 3):
pipette.aspirate(volume, sourceSolutionWell, rate=well_flow_rate)
pipette.dispense(volume, sourceSolutionWell, rate=well_flow_rate)
washSamples(pipette, sourceSolutionWell, samples, volume, 1, height_offset, keep_tip=keep_tip)
def getOmnistainerWellsList(omnistainer, num_samples):
sample_chambers = []
if (len(omnistainer.wells_by_name()) < num_samples):
raise Exception("number of wells in the Omni-Stainer less than num_samples")
wellslist = list(omnistainer.wells_by_name().keys())
wellslist = wellslist[1:num_samples + 1]
for well in wellslist:
sample_chambers.append(omnistainer.wells_by_name()[well])
print("omnistainer.wells_by_name are:")
print(omnistainer.wells_by_name())
print("sample_chambers are:")
print(sample_chambers)
return sample_chambers
def mix(pipette, sourceSolutionWell, volume, num_repeats):
if not pipette.has_tip: pipette.pick_up_tip()
for i in range(0, num_repeats):
pipette.aspirate(volume, sourceSolutionWell, rate=2)
pipette.dispense(volume, sourceSolutionWell, rate=2)
pipette.drop_tip()
def openShutter(protocol, pipette, covered_lbwr, keep_tip=False):
if not pipette.has_tip:
pipette.pick_up_tip()
pipette.move_to(covered_lbwr.wells()[len(covered_lbwr.wells()) - 2].bottom(0))
pipette.move_to(covered_lbwr.wells()[len(covered_lbwr.wells()) - 1].bottom(0), force_direct=True)
protocol.delay(seconds=1)
if not keep_tip: pipette.drop_tip()
def closeShutter(protocol, pipette, covered_lbwr, keep_tip=False):
if not pipette.has_tip:
pipette.pick_up_tip()
pipette.move_to(covered_lbwr.wells()[len(covered_lbwr.wells()) - 2].bottom(0))
pipette.move_to(covered_lbwr.wells()[len(covered_lbwr.wells()) - 3].bottom(0), force_direct=True)
protocol.delay(seconds=1)
if not keep_tip: pipette.drop_tip()
### END VERAO GLOBAL
Also, if you want to try a staining procedure we have not already made available, we do offer .
Follow this to learn more
Below are our global functions used throughout our protocols, it is available along with our latest public code updates at: