Week 3 homework
Lab automation 🦾
Python script for Opentrons artwork
Consistent with this week’s highly automated and digitized theme, for this assignment, I drew inspiration from an image popularized by the Internet, KC Green’s web comic strip “On Fire”, which, in 2014, became a famous -and my personal favorite- online meme (Figure 3.1). As many other people from all over the world, I deeply relate to this meme, which, I feel, accurately describes my life.
Figure 3.1 Panel from KC Green’s web comic strip “On Fire”, which generated the popular “This is fine” online meme in 2014.
As a first step, I fed the right part of the meme into Ronan’s automation art interface, however, the generated artwork required a lot of additional manual processing to resemble the original image (Figure 3.2).
Figure 3.2 (A) Initial image produced by inserting the right-hand ptych of the “This is fine” meme into Ronan’s automation art interface. (B) Final artwork generated after manual rendering of the initial image.
After composing the final artwork (Figure 3.2B), I imported the nine bacterial dyes I utilized (mClover3, mLychee_TF, mWatermelon, Ultramarine, mKO2, dsRed, mScarlet_I, mCherry, mKate2) along with their respective coordinates into Gemini 2.5 Flash (which was incorporated into my personal copy of HTGAA26 Opentrons Colab notebook) and asked it to write a Python script that would generate the “This is fine”-inspired artwork on a petri dish with the Opentrons system. The Pyhton script obtained from this prompt, slightly augmented with some basic tweaking, including the addition of comments at several steps and functions, as well as renaming the bacterial dyes with their corresponding Hex codes, can be found below:
# Optional, for importing the needed libraries
import subprocess, sys
subprocess.check_call([sys.executable, "-m", "pip", "install", "numpy", "pandas", "opentrons"])
from opentrons import types
metadata = { # see https://docs.opentrons.com/v2/tutorial.html#tutorial-metadata
'Sofia_Oikonomou': '',
'This_is_fine_meme_Opentrons_artwork': '',
'Generate_a_representation_of_the_This_is_fine_meme_with_9_different_bacteria-synthesized_dyes': '',
'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' : '#409945',
'A2' : '#40223D',
'A3' : '#E45741',
'A4' : '#B9474B',
'A5' : '#B39223',
'A6' : '#0F7449',
'A7' : '#0F184C',
'A8' : '#45203E',
'A9' : '#7C463E'
}
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
# YOUR CODE HERE to create your design
# Define all points for each dye
all_dye_points = {
#mClover3-Background wall lower
'#409945': [(-11, 11),(-9, 11),(-31, 9),(-9, 9),(-3, 9),(-1, 9),(1, 9),(5, 9),(7, 9),(9, 9),(15, 9),(17, 9),(21, 9),(23, 9),(33, 9),(35, 9),(-39, 7),(-29, 7),(-27, 7),(-25, 7),(-7, 7),(-5, 7),(-3, 7),(-1, 7),(1, 7),(3, 7),(5, 7),(7, 7),(9, 7),(11, 7),(13, 7),(15, 7),(17, 7),(19, 7),(21, 7),(23, 7),(35, 7),(37, 7),(39, 7),(-39, 5),(-5, 5),(-3, 5),(-1, 5),(1, 5),(3, 5),(5, 5),(7, 5),(9, 5),(11, 5),(13, 5),(17, 5),(19, 5),(21, 5),(37, 5),(39, 5),(-5, 3),(-3, 3),(-1, 3),(1, 3),(3, 3),(5, 3),(7, 3),(9, 3),(11, 3),(19, 3),(21, 3),(37, 3),(39, 3),(-7, 1),(-5, 1),(-3, 1),(-1, 1),(1, 1),(3, 1),(5, 1),(7, 1),(9, 1),(19, 1),(39, 1),(-3, -1),(-1, -1),(1, -1),(3, -1),(5, -1),(7, -1),(9, -1),(-1, -3),(1, -3),(3, -3),(5, -3),(7, -3),(1, -5),(3, -5),(5, -5),(7, -5),(1, -7),(3, -7),(5, -7),(7, -7),(1, -9),(3, -9),(5, -9),(-1, -27),(1, -27),(3, -27),(-1, -29),(1, -29),(3, -29),(1, -31)],
#mCherry-Dog outline
'#40223D': [(-29, 25),(-27, 25),(-25, 15),(-23, 15),(-13, 15),(-11, 9),(-9, 7),(-27, 5),(-25, 5),(-7, 5),(-27, 3),(-7, 3),(-25, 1),(-23, 1),(-21, 1),(-19, 1),(-17, 1),(-15, 1),(-13, 1),(-11, 1),(-9, 1),(-7, -3),(-5, -3),(-3, -3),(-19, -5),(-17, -5),(-15, -5),(-9, -5),(-1, -5),(-21, -7),(-13, -7),(-11, -7),(-1, -7),(-21, -9),(-11, -9),(-1, -9),(7, -9),(-21, -11),(-11, -11),(-1, -11),(1, -11),(3, -11),(5, -11),(-21, -13),(-11, -13),(-9, -13),(-3, -13),(-1, -13),(1, -13),(-21, -15),(-11, -15),(-7, -15),(-5, -15),(-19, -17),(-11, -17),(-17, -19),(-15, -19),(-13, -19),(13, -19),(11, -21),(3, -23),(5, -23),(7, 23),(9, -23),(-9, -25),(-7, -25),(-5, -25),(-3, -25),(-1, -25),(1, -25),(3, -25),(-3, -27),(-3, -29),(-11, -31),(-3, -31),(-9, -33),(-3, -33),(-7, -35),(-5, -35)],
#mLychee_TF-Dog and hat
'#E45741': [(-21, 15),(-19, 15),(-17, 15),(-15, 15),(-23, 13),(-21, 13),(-19, 13),(-17, 13),(-15, 13),(-13, 13),(-23, 11),(-21, 11),(-19, 11),(-17, 11),(-15, 11),(-13, 11),(-23, 9),(-21, 9),(-19, 9),(-17, 9),(-15, 9),(-13, 9),(-11, 7),(-11, 5),(-9, 5),(-25, 3),(-23, 3),(-21, 3),(-19, 3),(-17, 3),(-15, 3),(-13, 3),(-11, 3),(-9, 3),(-23, -1),(-21, -1),(-19, -1),(-17, -1),(-15, -1),(-13, -1),(-11, -1),(-9, -1),(-7, -1),(-5, -1),(-25, -3),(-23, -3),(-21, -3),(-19, -3),(-17, -3),(-15, -3),(-13, -3),(-11, -3),(-9, -3),(-27, -5),(-25, -5),(-23, -5),(-21, -5),(-13, -5),(-11, -5),(-25, -7),(-23, -7),(-25, -9),(-23, -9),(-25, -11),(-23, -11),(-25, -13),(-23, -13),(3, -13),(5, -13),(-25, -15),(-23, -15),(-9, -15),(-3, -15),(-1, -15),(1, -15),(3, -15),(5, -15),(-25, -17),(-23, -17),(-21, -17),(-9, -17),(-7, -17),(-5, -17),(-3, -17),(-1, -17),(1, -17),(3, -17), (5, -17),(7, -17),(-25, -19),(-23, -19),(-21, -19),(-19, -19),(-11, -19),(-9, -19),(-7, -19),(-5, -19),(-3, -19),(-1, -19),(1, -19),(3, -19),(5, -19),(7, -19),(9, -19),(11, -19),(-25, -21),(-23, -21),(-21, -21),(-19, -21),(-17, -21),(-15, -21),(-13, -21),(-11, -21),(-9, -21),(-7, -21),(-5, -21),(-3, -21),(-1, -21),(1, -21),(3, -21),(5, -21),(7, -21),(9, -21),(-25, -23),(-23, -23),(-21, -23),(-19, -23),(-17, -23),(-13, -23),(-11, -23),(-9, -23),(-7, -23),(-5, -23),(-3, -23),(-1, -23),(1, -23),(-25, -25),(-23, -25),(-21, -25),(-19, -25),(-17, -25),(-27, -27),(-25, -27),(-23, -27),(-21, -27),(-19, -27),(-17, -27),(-13, -27),(-11, -27),(-9, -27),(-27, -29),(-25, -29),(-23, -29),(-21, -29),(-19, -29),(-17, -29),(-15, -29),(-11, -29),(-9, -29),(-7, -29),(-25, -31),(-23, -31),(-21, -31),(-19, -31),(-17, -31),(-15, -31),(-13, -31),(-9, -31),(-7, -31),(-5, -31),(-21, -33),(-19, -33),(-17, -33),(-15, -33),(-13, -33),(-11, -33),(-7, -33),(-5, -33),(-19, -35),(-17, -35),(-15, -35),(-13, -35),(-11, -35),(-9, -35),(-3, -35),(-15, -37),(-13, -37),(-11, -37),(-9, -37),(-7, -37),(-5, -37),(-3, -37),(-7, -39),(-5, -39),(-3, -39)],
#mScarlet_I-Flame outline
'#B9474B': [(27, 13),(29, 13),(-35, 11),(25, 11),(31, 11),(-35, 9),(-33, 9),(25, 9),(31, 9),(-37, 7),(-31, 7),(25, 7),(33, 7),(-37, 5),(-29, 5),(15, 5),(23, 5),(35, 5),(-39, 3),(13, 3),(17, 3),(23, 3),(35, 3),(11, 1),(17, 1),(21, 1),(37, 1),(11, -1),(19, -1),(39, -1),(9, -3),(9, -5),(9, -7),(5, -25),(5, -27),(5, -29),(-1, -31),(3, -31),(-1, -33),(1, -33)],
#mKO2-Flame body
'#B39223': [(27, 11),(29, 11),(27, 9),(29, 9),(-35, 7),(-33, 7),(27, 7),(29, 7),(31, 7),(-35, 5),(-33, 5),(-31, 5),(25, 5),(27, 5),(29, 5),(31, 5),(33, 5),(-37, 3),(-35, 3),(-33, 3),(-31, 3),(-29, 3),(15, 3),(25, 3),(27, 3),(29, 3),(31, 3),(33, 3),(-39, 1),(-37, 1),(-35, 1),(-33, 1),(-31, 1),(-29, 1),(-27, 1),(13, 1),(15, 1),(23, 1),(25, 1),(27, 1),(29, 1),(31, 1),(33, 1),(35, 1),(-39, -1),(-37, -1),(-35, -1),(-33, -1),(-31, -1),(-29, -1),(-27, -1),(-25, -1),(13, -1),(15, -1),(17, -1),(21, -1),(23, -1),(25, -1),(27, -1),(29, -1),(31, -1),(33, -1),(35, -1),(37, -1),(-39, -3),(-37, -3),(-35, -3),(-33, -3),(-31, -3),(-29, -3),(-27, -3),(11, -3),(13, -3),(15, -3),(17, -3),(19, -3),(21, -3),(23, -3),(25, -3),(27, -3),(29, -3),(31, -3),(33, -3),(35, -3),(37, -3),(39, -3),(-39, -5),(-37, -5),(-35, -5),(-33, -5),(-31, -5),(-29, -5),(11, -5),(13, -5),(15, -5),(17, -5),(19, -5),(21, -5),(23, -5),(25, -5),(27, -5),(29, -5),(31, -5),(33, -5),(35, -5),(37, -5),(39, -5),(-39, -7),(-37, -7),(-35, -7),(-33, -7),(-31, -7),(11, -7),(13, -7),(15, -7),(17, -7),(19, -7),(21, -7),(23, -7),(25, -7),(27, -7),(29, -7),(31, -7),(33, -7),(35, -7),(37, -7),(39, -7),(-37, -9),(-35, -9),(-33, -9),(15, -9),(17, -9),(19, -9),(21, -9),(23, -9),(25, -9),(27, -9),(29, -9),(31, -9),(33, -9),(35, -9),(37, -9),(-37, -11),(-35, -11),(17, -11),(19, -11),(21, -11),(23, -11),(25, -11),(27, -11),(29, -11),(31, -11),(33, -11),(35, -11),(37, -11),(-37, -13),(-35, -13),(17, -13),(19, -13),(21, -13),(23, -13),(25, -13),(27, -13),(29, -13),(31, -13),(33, -13),(35, -13),(37, -13),(-37, -15),(17, -15),(19, -15),(21, -15),(23, -15),(25, -15),(27, -15),(29, -15),(31, -15),(33, -15),(35, -15),(37, -15),(15, -17),(17, -17),(19, -17),(21, -17),(23, -17),(25, -17),(27, -17),(29, -17),(31, -17),(33, -17),(35, -17),(15, -19),(17, -19),(19, -19),(21, -19),(23, -19),(25, -19),(27, -19),(29, -19),(31, -19),(33, -19),(35, -19),(13, -21),(15, -21),(17, -21),(19, -21),(21, -21),(23, -21),(25, -21),(27, -21),(29, -21),(31, -21),(33, -21),(11, -23),(13, -23),(15, -23),(17, -23),(19, -23),(21, -23),(23, -23),(25, -23),(27, -23),(29, -23),(31, -23),(7, -25),(9, -25),(11, -25),(13, -25),(15, -25),(17, -25),(19, -25),(21, -25),(23, -25),(25, -25),(27, -25),(29, -25),(31, -25),(-29, -27),(7, -27),(9, -27),(11, -27),(13, -27),(15, -27),(17, -27),(19, -27),(21, -27),(23, -27),(25, -27),(27, -27),(29, -27),(7, -29),(21, -29),(23, -29),(25, -29),(27, -29),(5, -31),(7, -31),(19, -31),(23, -31),(25, -31),(3, -33),(5, -33),(7, -33),(19, -33),(-1, -35),(1, -35),(3, -35),(5, -35),(7, -35),(19, -35),(-1, -37),(1, -37),(3, -37),(5, -37),(7, -37),(-1, -39),(1, -39),(3, -39),(5, -39),(7, -39)],
#mWatermelon-Background wall upper
'#0F7449': [(-37, 13),(-35, 13),(-33, 13),(-31, 13),(-11, 13),(-9, 13),(-1, 13),(1, 13),(7, 13),(9, 13),(11, 13),(13, 13),(15, 13),(17, 13),(25, 13),(31, 13),(33, 13),(35, 13),(37, 13),(-37, 11),(-33, 11),(-31, 11),(-29, 11),(-27, 11),(-25, 11),(-7, 11),(-5, 11),(-3, 11),(-1, 11),(1, 11),(5, 11),(7, 11),(9, 11),(11, 11),(13, 11),(15, 11),(17, 11),(19, 11),(21, 11),(23, 11),(33, 11),(35, 11),(37, 11),(-37, 9),(-29, 9),(-27, 9),(-25, 9),(-7, 9),(-5, 9),(11, 9),(13, 9),(19, 9),(37, 9)],
#Ultramarine-Dark blue elements
'#0F184C': [(-25, 31),(-23, 31),(-21, 31),(-19, 31),(-15, 31),(-13, 31),(-11, 31),(-9, 31),(-7, 31),(-3, 31),(-1, 31),(1, 31),(3, 31),(7, 31),(9, 31),(11, 31),(13, 31),(15, 31),(21, 31),(23, 31),(25, 31),(-23, 29),(-19, 29),(-15, 29),(-13, 29),(-11, 29),(-3, 29),(-1, 29),(7, 29),(13, 29),(15, 29),(17, 29),(21, 29),(23, 29),(-23, 27),(-19, 27),(-17, 27),(-15, 27),(-13, 27),(-11, 27),(-9, 27),(-7, 27),(-3, 27),(-1, 27),(1, 27),(3, 27),(7, 27),(9, 27),(11, 27),(13, 27),(15, 27),(19, 27),(21, 27),(23, 27),(25, 27),(27, 27),(-23, 25),(-19, 25),(-15, 25),(-13, 25),(-7, 25),(-3, 25),(3, 25),(7, 25),(13, 25),(15, 25),(21, 25),(23, 25),(-23, 23),(-19, 23),(-15, 23),(-13, 23),(-11, 23),(-9, 23),(-7, 23),(-3, 23),(-1, 23),(1, 23),(3, 23),(7, 23),(13, 23),(15, 23),(21, 23),(23, 23),(25, 23),(27, 23),(-27, 21),(31, 21),(-33, 19),(-31, 19),(-29, 19),(33, 19),(35, 19),(-37, 15),(-35, 15),(-33, 15),(-31, 15),(-11, 15),(-9, 15),(-1, 15),(1, 15),(9, 15),(11, 15),(13, 15),(15, 15),(17, 15),(25, 15),(27, 15),(29, 15),(31, 15),(-29, 13),(-27, 13),(-25, 13),(-7, 13),(-5, 13),(-3, 13),(19, 13),(21, 13),(23, 13),(-23, 7),(-21, 7),(-19, 7),(-17, 7),(-15, 7),(-13, 7),(-23, 5),(-21, 5),(-19, 5),(-17, 5),(-15, 5),(-13, 5),(-29, -7),(-27, -7),(-7, -7),(-5, -7),(-3, -7),(-31, -9),(-29, -9),(-27, -9),(-17, -9),(-15, -9),(-7, -9),(-5, -9),(-3, -9),(9, -9),(11, -9),(13, -9),(-33, -11),(-31, -11),(-29, -11),(-27, -11),(-17, -11),(-15, -11),(-13, -11),(-7, -11),(-5, -11),(-3, -11),(7, -11),(9, -11),(11, -11),(13, -11),(15, -11),(-33, -13),(-31, -13),(-29, -13),(-27, -13),(-17, -13),(-15, -13),(-13, -13),(-5, -13),(7, -13),(9, -13),(11, -13),(13, -13),(15, -13),(-35, -15),(-33, -15),(-31, -15),(-29, -15),(-27, -15),(-17, -15),(-15, -15),(-13, -15),(7, -15),(9, -15),(11, -15),(13, -15),(15, -15),(-35, -17),(-33, -17),(-31, -17),(-29, -17),(-27, -17),(-17, -17),(-15, -17),(-13, -17),(9, -17),(11, -17),(13, -17),(-35, -19),(-33, -19),(-31, -19),(-29, -19),(-27, -19),(-33, -21),(-31, -21),(-29, -21),(-27, -21),(-31, -23),(-29, -23),(-27, -23),(-31, -25),(-29, -25),(-27, -25),(-7, -27),(-5, -27),(-5, -29),(11, -29),(13, -29),(15, -29),(17, -29)],
#mKate2-Puff of smoke
'#45203E': [(-7, 39),(-5, 39),(-3, 39),(-1, 39),(1, 39),(3, 39),(5, 39),(7, 39),(-15, 37),(-13, 37),(-11, 37),(11, 37),(13, 37),(15, 37),(-19, 35),(-27, 29),(-29, 27),(-27, 27),(-31, 25),(31, 25),(-31, 23),(-29, 23),(-27, 23),(31, 23),(-33, 21),(-31, 21),(-29, 21),(-25, 21),(29, 21),(33, 21),(-35, 19),(-27, 19),(-25, 19),(-23, 19),(27, 19),(29, 19),(31, 19),(-35, 17),(-33, 17),(-31, 17),(-29, 17),(-27, 17),(-25, 17),(-23, 17),(-21, 17),(-19, 17),(-17, 17),(-15, 17),(-13, 17),(-11, 17),(-9, 17),(-7, 17),(-5, 17),(-3, 17),(-1, 17),(1, 17),(9, 17),(11, 17),(13, 17),(15, 17),(17, 17),(19, 17),(21, 17),(23, 17),(25, 17),(27, 17),(29, 17),(31, 17),(33, 17),(35, 17),(-29, 15),(-27, 15),(-7, 15),(-5, 15),(-3, 15),(19, 15),(21, 15),(23, 15),(33, 15),(35, 15),(37, 15),(-15, -23),(-15, -25),(-13, -25),(-11, -25),(-15, -27),(-13, -29)],
#dsRed-Coffee mug
'#7C463E': [(9, -29),(19, -29),(9, -31),(11, -31),(13, -31),(15, -31),(17, -31),(21, -31),(9, -33),(11, -33),(13, -33),(15, -33),(17, -33),(21, -33),(9, -35),(11, -35),(13, -35),(15, -35),(17, -35),(9, -37),(11, -37),(13, -37),(15, -37)]
}
for dye_name, points_list in all_dye_points.items():
if not points_list: # Skip if no points for this dye
continue
color_source_well = location_of_color(dye_name)
pipette_20ul.pick_up_tip()
# Aspirate in batches to ensure enough liquid
for i, (x_coord, y_coord) in enumerate(points_list):
if i % 20 == 0 or pipette_20ul.current_volume < 1: # Re-aspirate if volume is low or every 20 points
# Aspirate enough for remaining points or 20uL, whichever is less
volume_to_aspirate = min(20, len(points_list) - i)
if volume_to_aspirate > 0:
pipette_20ul.aspirate(volume_to_aspirate, color_source_well)
adjusted_location = center_location.move(types.Point(x=x_coord, y=y_coord))
dispense_and_detach(pipette_20ul, 1, adjusted_location)
pipette_20ul.drop_tip()
# Don't forget to end with a drop_tip()
# Execute Simulation / Visualization -- don't change this code block
protocol = OpentronsMock(well_colors)
run(protocol)
protocol.visualize()
Lastly, when I simulated the artwork, the Python script presented above did produce the designed image (Figure 3.3).
Figure 3.3 Simulation of the “This is fine” artwork generated by running the Python script displayed above.