Week 03 HW: Lab Automation
Your task this week is to Create a Python file to run on an Opentrons liquid handling robot.
1. Generate an artistic design using the GUI at opentrons-art.rcdonovan.com.

2. Using the coordinates from the GUI, follow the instructions in the HTGAA26 Opentrons Colab to write your own Python script which draws your design using the Opentrons.
Link to cell: https://colab.research.google.com/drive/1-f-vpwBCOx1gmlD5qXbW5z-7sLun1xwP?authuser=2#scrollTo=pczDLwsq64mk&line=1&uniqifier=1
from opentrons import types
metadata = { # see https://docs.opentrons.com/v2/tutorial.html#tutorial-metadata
'author': 'Paula Mariana Carrodeguas González',
'protocolName': 'Volleyball inspired agArt',
'description': 'Volleyball design using Blue, Yellow, and Red fluorescent E. coli on black agar.',
'source': 'HTGAA 2026 Opentrons Lab',
'apiLevel': '2.20'
}
##############################################################################
### Robot deck setup constants - don't change these
##############################################################################
TIP_RACK_DECK_SLOT = 9
COLORS_DECK_SLOT = 6
AGAR_DECK_SLOT = 5
PIPETTE_STARTING_TIP_WELL = 'A1'
well_colors = {
'A1' : 'Red',
'B1' : 'Yellow',
'C1' : 'Green',
'D1' : 'Cyan',
'E1' : 'Blue' # if in a 24-well plate, this needs to be moved to e.g. D2
}
def run(protocol):
##############################################################################
### Load labware, modules and pipettes
##############################################################################
# Tips
tips_20ul = protocol.load_labware('opentrons_96_tiprack_20ul', TIP_RACK_DECK_SLOT, 'Opentrons 20uL Tips')
# Pipettes
pipette_20ul = protocol.load_instrument("p20_single_gen2", "right", [tips_20ul])
# Modules
temperature_module = protocol.load_module('temperature module gen2', COLORS_DECK_SLOT)
# Temperature Module Plate
temperature_plate = temperature_module.load_labware('opentrons_96_aluminumblock_generic_pcr_strip_200ul',
'Cold Plate')
# Choose where to take the colors from
color_plate = temperature_plate
# Agar Plate
agar_plate = protocol.load_labware('htgaa_agar_plate', AGAR_DECK_SLOT, 'Agar Plate') ## TA MUST CALIBRATE EACH PLATE!
# Get the top-center of the plate, make sure the plate was calibrated before running this
center_location = agar_plate['A1'].top()
pipette_20ul.starting_tip = tips_20ul.well(PIPETTE_STARTING_TIP_WELL)
##############################################################################
### Patterning
##############################################################################
###
### Helper functions for this lab
###
# pass this e.g. 'Red' and get back a Location which can be passed to aspirate()
def location_of_color(color_string):
for well,color in well_colors.items():
if color.lower() == color_string.lower():
return color_plate[well]
raise ValueError(f"No well found with color {color_string}")
# For this lab, instead of calling pipette.dispense(1, loc) use this: dispense_and_detach(pipette, 1, loc)
def dispense_and_detach(pipette, volume, location):
"""
Move laterally 5mm above the plate (to avoid smearing a drop); then drop down to the plate,
dispense, move back up 5mm to detach drop, and stay high to be ready for next lateral move.
5mm because a 4uL drop is 2mm diameter; and a 2deg tilt in the agar pour is >3mm difference across a plate.
"""
assert(isinstance(volume, (int, float)))
above_location = location.move(types.Point(z=location.point.z + 5)) # 5mm above
pipette.move_to(above_location) # Go to 5mm above the dispensing location
pipette.dispense(volume, location) # Go straight downwards and dispense
pipette.move_to(above_location) # Go straight up to detach drop and stay high
###
# 1. Defining my coordinate lists
azurite_points = [(-12.1, 18.7),(-9.9, 18.7),(-16.5, 16.5),(-14.3, 16.5),(-12.1, 16.5),(3.3, 16.5),(-20.9, 14.3),(-18.7, 14.3),(-16.5, 14.3),(-14.3, 14.3),(5.5, 14.3),(7.7, 14.3),(-20.9, 12.1),(-18.7, 12.1),(-16.5, 12.1),(7.7, 12.1),(-23.1, 9.9),(-20.9, 9.9),(-18.7, 9.9),(9.9, 9.9),(-23.1, 7.7),(-20.9, 7.7),(-5.5, 7.7),(-3.3, 7.7),(-1.1, 7.7),(9.9, 7.7),(-25.3, 5.5),(-23.1, 5.5),(-7.7, 5.5),(-5.5, 5.5),(-3.3, 5.5),(-1.1, 5.5),(9.9, 5.5),(12.1, 5.5),(-25.3, 3.3),(-9.9, 3.3),(-7.7, 3.3),(-5.5, 3.3),(-3.3, 3.3),(-1.1, 3.3),(9.9, 3.3),(12.1, 3.3),(-12.1, 1.1),(-9.9, 1.1),(-7.7, 1.1),(-5.5, 1.1),(9.9, 1.1),(12.1, 1.1),(-14.3, -1.1),(-12.1, -1.1),(-9.9, -1.1),(-7.7, -1.1),(7.7, -1.1),(9.9, -1.1),(12.1, -1.1),(-16.5, -3.3),(-14.3, -3.3),(-12.1, -3.3),(-9.9, -3.3),(-5.5, -3.3),(9.9, -3.3),(12.1, -3.3),(-18.7, -5.5),(-16.5, -5.5),(-14.3, -5.5),(-12.1, -5.5),(-7.7, -5.5),(-5.5, -5.5),(-3.3, -5.5),(-20.9, -7.7),(-18.7, -7.7),(-16.5, -7.7),(-14.3, -7.7),(-7.7, -7.7),(-5.5, -7.7),(-3.3, -7.7),(-1.1, -7.7),(-20.9, -9.9),(-18.7, -9.9),(-16.5, -9.9),(-5.5, -9.9),(-3.3, -9.9),(-1.1, -9.9),(1.1, -9.9),(-20.9, -12.1),(-3.3, -12.1),(-1.1, -12.1),(1.1, -12.1),(3.3, -12.1),(-1.1, -14.3),(1.1, -14.3),(3.3, -14.3),(5.5, -14.3),(1.1, -16.5),(3.3, -16.5),(-12.1, -18.7)]
mko2_points = [(-5.5, 18.7),(-3.3, 18.7),(-1.1, 18.7),(-3.3, 16.5),(-1.1, 16.5),(-9.9, 14.3),(-7.7, 14.3),(-5.5, 14.3),(-1.1, 14.3),(1.1, 14.3),(-12.1, 12.1),(-9.9, 12.1),(-7.7, 12.1),(-5.5, 12.1),(-3.3, 12.1),(1.1, 12.1),(3.3, 12.1),(-14.3, 9.9),(-12.1, 9.9),(-9.9, 9.9),(-7.7, 9.9),(1.1, 9.9),(3.3, 9.9),(5.5, 9.9),(-16.5, 7.7),(-14.3, 7.7),(-12.1, 7.7),(-9.9, 7.7),(3.3, 7.7),(5.5, 7.7),(-18.7, 5.5),(-16.5, 5.5),(-14.3, 5.5),(-12.1, 5.5),(3.3, 5.5),(5.5, 5.5),(-20.9, 3.3),(-18.7, 3.3),(-16.5, 3.3),(-14.3, 3.3),(3.3, 3.3),(5.5, 3.3),(-23.1, 1.1),(-20.9, 1.1),(-18.7, 1.1),(-16.5, 1.1),(1.1, 1.1),(5.5, 1.1),(-25.3, -1.1),(-23.1, -1.1),(-20.9, -1.1),(-18.7, -1.1),(-3.3, -1.1),(-1.1, -1.1),(1.1, -1.1),(3.3, -1.1),(-25.3, -3.3),(-23.1, -3.3),(-20.9, -3.3),(-1.1, -3.3),(1.1, -3.3),(3.3, -3.3),(5.5, -3.3),(-23.1, -5.5),(1.1, -5.5),(3.3, -5.5),(5.5, -5.5),(7.7, -5.5),(3.3, -7.7),(5.5, -7.7),(7.7, -7.7),(9.9, -7.7),(-12.1, -9.9),(-9.9, -9.9),(5.5, -9.9),(7.7, -9.9),(9.9, -9.9),(-14.3, -12.1),(-12.1, -12.1),(-9.9, -12.1),(-7.7, -12.1),(7.7, -12.1),(-18.7, -14.3),(-12.1, -14.3),(-9.9, -14.3),(-7.7, -14.3),(-5.5, -14.3),(-16.5, -16.5),(-9.9, -16.5),(-7.7, -16.5),(-5.5, -16.5),(-3.3, -16.5),(-7.7, -18.7),(-5.5, -18.7),(-3.3, -18.7)]
mrfp1_points = [(18.7, 9.9),(20.9, 9.9),(25.3, 9.9),(27.5, 9.9),(18.7, 7.7),(20.9, 7.7),(23.1, 7.7),(25.3, 7.7),(27.5, 7.7),(20.9, 5.5),(23.1, 5.5),(25.3, 5.5),(23.1, 3.3),(18.7, -1.1),(20.9, -1.1),(25.3, -1.1),(27.5, -1.1),(18.7, -3.3),(20.9, -3.3),(23.1, -3.3),(25.3, -3.3),(27.5, -3.3),(20.9, -5.5),(23.1, -5.5),(25.3, -5.5),(23.1, -7.7),(18.7, -12.1),(20.9, -12.1),(25.3, -12.1),(27.5, -12.1),(18.7, -14.3),(20.9, -14.3),(23.1, -14.3),(25.3, -14.3),(27.5, -14.3),(20.9, -16.5),(23.1, -16.5),(25.3, -16.5),(23.1, -18.7)]
# 2. Assign colors to the points
design_layers = [
('Blue', azurite_points),
('Yellow', mko2_points),
('Red', mrfp1_points)
]
# 3. Robot execution loop
for color_name, points in design_layers:
source_loc = location_of_color(color_name)
pipette_20ul.pick_up_tip()
# The p20 pipette can hold max 20uL, so it should aspirate 15uL at a time for safety.
for i in range(0, len(points), 15):
batch = points[i : i + 15]
pipette_20ul.aspirate(len(batch), source_loc)
for x, y in batch:
# Move relative to the calibrated center of the agar plate
target = center_location.move(types.Point(x=x, y=y))
dispense_and_detach(pipette_20ul, 1, target)
pipette_20ul.drop_tip()
# --- END OF CUSTOM DESIGN ---
###
# Don't forget to end with a drop_tip()