Labs

Lab writeups:

  • Week 1 Lab: Pipetting

  • Week 3 Lab: Opentrons Fluorescent Bacteria Pixel Artwork

    Opentrons Bio-Art Lab Program the Opentrons OT-2 pipetting robot to create glowing designs by depositing genetically engineered E. coli onto black (charcoal) agar plates. Fluorescent proteins form bio-art that comes alive under UV light. Lab Overview This two-day lab combines synthetic biology, automation, and art:

Subsections of Labs

Week 1 Lab: Pipetting

cover image cover image

Week 3 Lab: Opentrons Fluorescent Bacteria Pixel Artwork

cover image cover image

Opentrons Bio-Art Lab

Program the Opentrons OT-2 pipetting robot to create glowing designs by depositing genetically engineered E. coli onto black (charcoal) agar plates. Fluorescent proteins form bio-art that comes alive under UV light.


Lab Overview

This two-day lab combines synthetic biology, automation, and art:

  • Opentrons OT-2 โ€” liquid handling robot for precise pipetting
  • Fluorescent E. coli โ€” R/G/B/Orange/YFP bacteria on black agar
  • Your design โ€” custom Python protocol to deposit any pattern

Workflow: Paper Protocol โ†’ Opentrons Protocol โ†’ Compiled Protocol

  1. Paper Protocol โ€” plain-language steps (e.g., “pipette 100 ยตL Green into well A1”)
  2. Opentrons Protocol โ€” Python code the robot understands
  3. Compiled Protocol โ€” validated and run on the OT-2

Key Actions

ActionFunction
Pick up tippick_up_tip()
Aspirateaspirate(volume, location_of_color('Green'))
Dispensedispense_and_detach(pipette, volume, location)
Drop tipdrop_tip()

Design: T-Rex QR Code

This lab implements a QR code with an embedded T-Rex โ€” a 33ร—33 pixel grid where black modules are deposited as fluorescent bacteria. Under UV light, the QR code glows and remains scannable.

T-Rex QR code design โ€” monochrome pixel art combining QR code structure with a central dinosaur silhouette T-Rex QR code design โ€” monochrome pixel art combining QR code structure with a central dinosaur silhouette

Lab Components

Part 1: Protocol Script

The Python protocol that maps the QR code pixel grid to agar plate coordinates and deposits fluorescent bacteria at each black pixel. Uses a single color (Green) for the monochrome design.


Submission & Running

  • Submit your protocol to your TA or publish to GinkgoArtworks
  • Sign up for a robot time slot (MIT/Harvard: during Lab hours)
  • Submit at least one day before your robot slot via the course Form

Post-Lab (All Students)

  1. Automation for final project โ€” Describe what you intend to automate (procedures, 3D-printed holders, pseudocode)
  2. Published paper โ€” Find and describe a paper using Opentrons or similar automation for novel biological applications (e.g., automated PACE)

Subsections of Week 3 Lab: Opentrons Fluorescent Bacteria Pixel Artwork

Part 1: Protocol Script

Part 1: Opentrons Protocol โ€” T-Rex QR Code

Python protocol for the Opentrons OT-2 that deposits fluorescent bacteria to reproduce the T-Rex QR code on a black agar plate. Black pixels in the 33ร—33 grid are mapped to plate coordinates; one color (Green) is used for the monochrome design.


Protocol Code Block 1 โ€” Main Protocol

Copy this into the first code block of the HTGAA26 Opentrons Colab notebook (after the prerequisite setup):

from opentrons import types

metadata = {
    'author': 'James Utley',
    'protocolName': 'T-Rex QR Code Bio-Art',
    'description': 'Deposits fluorescent E. coli to create a scannable QR code with T-Rex 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': 'Green',
    'C1': 'Orange'
}

# 33x33 QR code pixel grid (1 = black/deposit, 0 = white/skip)
# Extracted from T-Rex QR code image
QR_PIXELS = [
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
    [0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0],
    [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
    [0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0],
    [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0],
    [0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0],
    [0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0],
    [0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
]


def run(protocol):
  ##############################################################################
  ###   Load labware, modules and pipettes
  ##############################################################################

  tips_20ul = protocol.load_labware('opentrons_96_tiprack_20ul', TIP_RACK_DECK_SLOT, 'Opentrons 20uL Tips')
  pipette_20ul = protocol.load_instrument("p20_single_gen2", "right", [tips_20ul])
  temperature_module = protocol.load_module('temperature module gen2', COLORS_DECK_SLOT)
  temperature_plate = temperature_module.load_labware('opentrons_96_aluminumblock_generic_pcr_strip_200ul', 'Cold Plate')
  color_plate = temperature_plate
  agar_plate = protocol.load_labware('htgaa_agar_plate', AGAR_DECK_SLOT, 'Agar Plate')
  center_location = agar_plate['A1'].top()
  pipette_20ul.starting_tip = tips_20ul.well(PIPETTE_STARTING_TIP_WELL)

  ##############################################################################
  ###   Helper functions
  ##############################################################################

  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}")

  def dispense_and_detach(pipette, volume, location):
    above_location = location.move(types.Point(z=location.point.z + 5))
    pipette.move_to(above_location)
    pipette.dispense(volume, location)
    pipette.move_to(above_location)

  ##############################################################################
  ###   T-Rex QR Code patterning
  ##############################################################################

  PIXEL_SIZE_MM = 2.0   # spacing between pixels (mm)
  VOLUME_UL = 0.5       # 0.5 uL per drop (~117 uL total, fits in 200uL PCR strip well)
  COLOR = 'Green'

  # Collect (row, col) for all black pixels
  black_pixels = []
  for row in range(len(QR_PIXELS)):
    for col in range(len(QR_PIXELS[row])):
      if QR_PIXELS[row][col] == 1:
        black_pixels.append((row, col))

  # One tip, one color - aspirate in batches of 20uL (max pipette volume)
  pipette_20ul.pick_up_tip()
  source = location_of_color(COLOR)

  for i in range(0, len(black_pixels), 20):
    batch = black_pixels[i:i+20]
    vol = min(len(batch) * VOLUME_UL, 20)
    pipette_20ul.aspirate(vol, source)

    for row, col in batch:
      # Map pixel (row,col) to plate coords: center at (16,16), 2mm per pixel
      x_mm = (col - 16) * PIXEL_SIZE_MM
      y_mm = (row - 16) * PIXEL_SIZE_MM
      loc = center_location.move(types.Point(x=x_mm, y=y_mm))
      dispense_and_detach(pipette_20ul, VOLUME_UL, loc)

  pipette_20ul.drop_tip()

Protocol Code Block 2 โ€” Simulation / Visualization

Copy this into the second code block (runs after the first):

# Execute Simulation / Visualization -- don't change this code block
protocol = OpentronsMock(well_colors)
run(protocol)
protocol.visualize()

Notes

  • 233 black pixels โ€” uses ~117 ยตL total (0.5 ยตL per drop); aspirated in batches of 20 ยตL; fits in 200 ยตL PCR strip well.
  • Single color (Green) โ€” monochrome design; change COLOR to 'Red' or 'Orange' if desired
  • 2 mm pixel spacing โ€” fits within ~64 mm on a 90 mm plate
  • One tip per color โ€” avoids cross-contamination; only one color used here

Standalone Protocol File

For Opentrons App validation or direct upload: trex_qr_protocol.py

Google Colab Notebook

HTGAA26 Opentrons Colab โ€” make a copy and paste the protocol code blocks above into the notebook.