Week 3 — Lab Automation

Assignment: Python Script for Opentrons Artwork — DUE BY YOUR LAB TIME! Your task this week is to Create a Python file to run on an Opentrons liquid handling robot.

Review this week’s recitation and this week’s lab for details on the Opentrons and programming it. Generate an artistic design using the GUI at opentrons-art.rcdonovan.com. 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. You may use AI assistance for this coding — Google Gemini is integrated into Colab (see the stylized star bottom center); it will do a good job writing functional Python, while you probably need to take charge of the art concept. If you’re a proficient programmer and you’d rather code something mathematical or algorithmic instead of using your GUI coordinates, you may do that instead. Ask for help early! If you are having any trouble with scripting, contact your TAs as soon as possible for help. Do not wait until your scheduled robot time slot or you may not be able to complete this assignment!

If the Python component is proving too problematic even with AI and human assistance, download the full Python script from the GUI website and submit that: Use the download icon pointed to by the red arrow in this diagram. Use the download icon pointed to by the red arrow in this diagram.

If you use AI to help complete this homework or lab, document how you used AI and which models made contributions. Sign up for a robot time slot if you are at MIT/Harvard/Wellesley or at a Node offering Opentrons automation. The Python script you created will be run on the robot to produce your work of art! At MIT/Harvard? Lab times are on Thursday Feb.19 between 10AM and 6PM. At other Nodes? Please coordinate with your Node. Submit your Python file via this form.

Hello again, friend. I hope you’ve been enjoying what I’ve been doing week by week. In this first part of WH3, I’ll be showcasing the art that can be created using both Python code and Opentrons.

First, let’s start with the artwork I created in OpenTrons. I really enjoyed making this piece because it reminds me of pixel art. What I drew is the Pokémon Charizard sleeping with a Luxury Ball beside it. It’s a design I enjoyed creating. If you’d like to see it more clearly and check the coordinates and fonts I used, you can find it under the name SleepingCharizard.

cover image cover image

I also tried to do it in Python code so a bot could recreate it in my Node lab. I wrote the code on Google Colab. I used an AI called ChatGPT for help with the code. I know there are better AIs to use, but all I needed were some coordinate points for my variables, so ChatGPT was sufficient for that part of the code. The first block of code is this:

from opentrons import types

metadata = {    # see https://docs.opentrons.com/v2/tutorial.html#tutorial-metadata
    'author': 'Sergio Cuiza',
    'protocolName': 'WH3: Art Laboratory',
    'description': 'Draw a bitmap pattern on the agar plate using different colors for each pixel, leaving everything to your imagination.',
    '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'
}


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
  ###
  azurite_points = [
    (-8.8, 8.8),
    (-6.6, 6.6),
    (-4.4, 4.4),
    (-2.2, 2.2),
    (0, 0),
    (2.2, -2.2),
    (4.4, -4.4),
    (6.6, -6.6),
    (8.8, -8.8),

    (-8.8, -8.8),
    (-6.6, -6.6),
    (-4.4, -4.4),
    (-2.2, -2.2),
    (2.2, 2.2),
    (4.4, 4.4),
    (6.6, 6.6),
    (8.8, 8.8)
  ]

  mtagbfp2_points = [
    (-2.2, 4.4),
    (2.2, 4.4),
    (-4.4, -2.2),
    (4.4, -2.2)
  ]

  mplum_points = [
    (0, 8.8),
    (2.2, 8.8),
    (-2.2, 8.8)
  ]

  mlychee_tf_points = [
    (-6.6, 4.4),
    (6.6, 4.4),
    (-6.6, -2.2),
    (6.6, -2.2)
  ]

  mruby2_points = [
    (8.8, 2.2),
    (8.8, 0),
    (8.8, -2.2)
  ]

  mko2_points = [
    (-8.8, 2.2),
    (-8.8, 0),
    (-8.8, -2.2)
  ]

  eqfp578_points = [
    (0, 11),
    (-2.2, 11),
    (2.2, 11)
  ]

  mrfp1_points = [
    (4.4, 8.8),
    (6.6, 6.6)
  ]

  mcherry_points = [
    (-4.4, 8.8),
    (-6.6, 6.6)
  ]

  mkate2_points = [
    (0, -8.8),
    (0, -11)
  ]

  # =========================
  # AZURITE (Red)
  # =========================
  pipette_20ul.pick_up_tip()
  pipette_20ul.aspirate(len(azurite_points), location_of_color('Red'))

  for x_coord, y_coord in azurite_points:
    target_location = center_location.move(types.Point(x=x_coord, y=y_coord))
    dispense_and_detach(pipette_20ul, 1, target_location)

  pipette_20ul.drop_tip()

  # =========================
  # mTagBFP2 (Green)
  # =========================
  pipette_20ul.pick_up_tip()
  pipette_20ul.aspirate(len(mtagbfp2_points), location_of_color('Green'))

  for x_coord, y_coord in mtagbfp2_points:
    target_location = center_location.move(types.Point(x=x_coord, y=y_coord))
    dispense_and_detach(pipette_20ul, 1, target_location)

  pipette_20ul.drop_tip()

  # =========================
  # mPlum (Orange)
  # =========================
  pipette_20ul.pick_up_tip()
  pipette_20ul.aspirate(len(mplum_points), location_of_color('Orange'))

  for x_coord, y_coord in mplum_points:
    target_location = center_location.move(types.Point(x=x_coord, y=y_coord))
    dispense_and_detach(pipette_20ul, 1, target_location)

  pipette_20ul.drop_tip()
  # =========================
  # mPlum (Orange)
  # =========================
  pipette_20ul.pick_up_tip()
  pipette_20ul.aspirate(len(mplum_points), location_of_color('Orange'))

  for x_coord, y_coord in mplum_points:
    target_location = center_location.move(types.Point(x=x_coord, y=y_coord))
    dispense_and_detach(pipette_20ul, 1, target_location)

  pipette_20ul.drop_tip()
  # =========================
  # mPlum (Orange)
  # =========================
  pipette_20ul.pick_up_tip()
  pipette_20ul.aspirate(len(mplum_points), location_of_color('Orange'))

  for x_coord, y_coord in mplum_points:
    target_location = center_location.move(types.Point(x=x_coord, y=y_coord))
    dispense_and_detach(pipette_20ul, 1, target_location)

  pipette_20ul.drop_tip()
    # =========================

  # mLychee_tf (Red)

  # =========================

  pipette_20ul.pick_up_tip()

  pipette_20ul.aspirate(len(mlychee_tf_points) * 1, location_of_color('Red'))



  for x_coord, y_coord in mlychee_tf_points:

      target_location = center_location.move(types.Point(x=x_coord, y=y_coord))

      dispense_and_detach(pipette_20ul, 1, target_location)



  pipette_20ul.drop_tip()



  # =========================

  # mRuby2 (Green)

  # =========================

  pipette_20ul.pick_up_tip()

  pipette_20ul.aspirate(len(mruby2_points) * 1, location_of_color('Green'))



  for x_coord, y_coord in mruby2_points:

      target_location = center_location.move(types.Point(x=x_coord, y=y_coord))

      dispense_and_detach(pipette_20ul, 1, target_location)



  pipette_20ul.drop_tip()



  # =========================

  # mKO2 (Orange)

  # =========================

  pipette_20ul.pick_up_tip()

  pipette_20ul.aspirate(len(mko2_points) * 1, location_of_color('Orange'))



  for x_coord, y_coord in mko2_points:

      target_location = center_location.move(types.Point(x=x_coord, y=y_coord))

      dispense_and_detach(pipette_20ul, 1, target_location)



  pipette_20ul.drop_tip()



  # =========================

  # eqFP578 (Red)

  # =========================

  pipette_20ul.pick_up_tip()

  pipette_20ul.aspirate(len(eqfp578_points) * 1, location_of_color('Red'))



  for x_coord, y_coord in eqfp578_points:

      target_location = center_location.move(types.Point(x=x_coord, y=y_coord))

      dispense_and_detach(pipette_20ul, 1, target_location)



  pipette_20ul.drop_tip()



  # =========================

  # mRFP1 (Green)

  # =========================

  pipette_20ul.pick_up_tip()

  pipette_20ul.aspirate(len(mrfp1_points) * 1, location_of_color('Green'))



  for x_coord, y_coord in mrfp1_points:

      target_location = center_location.move(types.Point(x=x_coord, y=y_coord))

      dispense_and_detach(pipette_20ul, 1, target_location)



  pipette_20ul.drop_tip()



  # =========================

  # mCherry (Orange)

  # =========================

  pipette_20ul.pick_up_tip()

  pipette_20ul.aspirate(len(mcherry_points) * 1, location_of_color('Orange'))



  for x_coord, y_coord in mcherry_points:

      target_location = center_location.move(types.Point(x=x_coord, y=y_coord))

      dispense_and_detach(pipette_20ul, 1, target_location)



  pipette_20ul.drop_tip()



  # =========================

  # mKate2 (Red)

  # =========================

  pipette_20ul.pick_up_tip()

  pipette_20ul.aspirate(len(mkate2_points) * 1, location_of_color('Red'))



  for x_coord, y_coord in mkate2_points:

      target_location = center_location.move(types.Point(x=x_coord, y=y_coord))

      dispense_and_detach(pipette_20ul, 1, target_location)



  pipette_20ul.drop_tip()
  # Don't forget to end with a drop_tip()

In the second block the code was already predetermined:

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

The result I got from running the code is this:

=== VOLUME TOTALS BY COLOR ===
	Green:		 aspirated 9	 dispensed 9
	Red:		 aspirated 26	 dispensed 26
	Orange:		 aspirated 14	 dispensed 14
	[all colors]:	[aspirated 49]	[dispensed 49]
=== TIP COUNT ===
	 Used 12 tip(s)  (ideally exactly one per unique color)
cover image cover image

I’m not really sure what I wanted to do, because I wanted to try making something like a radiation mask or something similar. I don’t really see much of a resemblance, but I couldn’t try any further due to lack of time. I also wanted to do what I had done in Opentrons, but since the pipette only accepts 20µL, I couldn’t do it. I know that by using more variables it could be achieved, but I didn’t have the time. Here is the link to my Google Collab project.

The code is not yet running on the robot because my node’s labs will only be held this week 4.

Post-Lab Questions — DUE BY START OF FEB 24 LECTURE One of the great parts about having an automated robot is being able to precisely mix, deposit, and run reactions without much intervention, and design and deploy experiments remotely.

For this week, we’d like for you to do the following:

Find and describe a published paper that utilizes the Opentrons or an automation tool to achieve novel biological applications. Write a description about what you intend to do with automation tools for your final project. You may include example pseudocode, Python scripts, 3D printed holders, a plan for how to use Ginkgo Nebula, and more. You may reference this week’s recitation slide deck for lab automation details. While your description/project idea doesn’t need to be set in stone, we would like to see core details of what you would automate. This is due at the start of lecture and does not need to be tested on the Opentrons yet.

Example 1: You are creating a custom fabric, and want to deposit art onto specific parts that need to be intertwined in odd ways. You can design a 3D printed holder to attach this fabric to it, and be able to deposit bio art on top. Check out the Opentrons 3D Printing Directory.

Example 2: You are using the cloud laboratory to screen an array of biosensor constructs that you design, synthesize, and express using cell-free protein synthesis.

Echo transfer biosensor constructs and any required cofactors into specified wells. Bravo stamp in CPFS reagent master mix into all wells of a 96-well / 384-well plate. Multiflo dispense the CFPS lysate to all wells to start protein expression. PlateLoc seal the plate. Inheco incubate the plate at 37°C while the biosensor proteins are synthesized. XPeel remove the seal. PHERAstar measure fluorescence to compare biosensor responses.

1. Published Paper Using Opentrons / Lab Automation

A published paper that utilizes automation tools similar to Opentrons for novel biological applications is “Programming a Low-Cost, Open-Source Robot for High-Throughput Biology”. In this study, researchers implemented an open-source liquid-handling robot to automate repetitive laboratory tasks such as pipetting, reagent mixing, and plate preparation.

The automation system allowed the researchers to conduct high-throughput biological experiments with improved accuracy and reproducibility compared to manual lab work. By programming the robot using Python-based protocols, the team was able to standardize workflows including serial dilutions, reaction setup, and sample transfers across 96-well plates.

A key novel biological application demonstrated in the paper is the scaling of experimental workflows in synthetic biology and molecular biology. Automation reduced human error, increased experimental consistency, and enabled remote experiment execution. This is especially useful for screening large numbers of biological samples, which would otherwise be time-consuming and prone to variability if performed manually.

Overall, the paper shows that open-source automation tools like Opentrons can significantly enhance experimental precision, accessibility, and scalability in modern biological research, making them valuable for applications such as biosensor screening, protein expression experiments, and automated assay development.

2. Description of What I Intend to Automate for My Final Project

For my final project, I intend to use lab automation tools to systematically investigate radiation-protective biological mechanisms inspired by extremophiles, including melanized fungi discovered in high-radiation environments and protective proteins such as Dsup.

The core idea is to automate a comparative experimental workflow that evaluates how biological samples (e.g., protein systems or biomaterial coatings) respond to simulated stress conditions, including oxidative and radiation-like damage proxies.

What I Would Automate

The automation system (e.g., Opentrons OT-2 + cloud lab tools like Ginkgo Nebula) would be used to:

-Precisely prepare reagent mixtures

-Dispense samples into multi-well plates

-Run parallel stress-condition assays

-Standardize incubation and measurement steps

-Collect reproducible quantitative data

This is particularly useful because my node did not perform a physical laboratory experiment, so automation provides a conceptual framework for how the experimental design could be executed remotely and reproducibly.

3. Proposed Automated Workflow (Conceptual)

First, I would design a 3D-printed holder to stabilize specialized sample substrates (such as coated slides or biomaterial samples) so the robot can deposit reagents with spatial precision. This ensures consistent sample positioning and minimizes mechanical variation during automated pipetting.

Then, the automated workflow would proceed as follows:

Transfer prepared biomaterial or protein samples into a 96-well plate using calibrated pipetting protocols.

Dispense controlled concentrations of stress-inducing reagents (e.g., oxidative agents that simulate radiation-induced damage).

Add protective components inspired by extremophile systems (such as melanin analogs or Dsup-related protein constructs).

Seal and incubate the plate under standardized temperature conditions.

Measure fluorescence, absorbance, or structural stability metrics using automated plate readers.

Export and analyze the dataset to compare protective efficiency across conditions.

4. Example Pseudocode (Opentrons-Style Automation Script)

from opentrons import protocol_api

metadata = {
    'protocolName': 'Automated Stress Response Assay',
    'author': 'Student Project',
    'description': 'Automated preparation of stress-response samples',
    'apiLevel': '2.13'
}

def run(protocol: protocol_api.ProtocolContext):

    # Labware setup
    plate = protocol.load_labware('corning_96_wellplate_360ul_flat', '1')
    tiprack = protocol.load_labware('opentrons_96_tiprack_300ul', '2')
    reservoir = protocol.load_labware('nest_12_reservoir_15ml', '3')

    pipette = protocol.load_instrument('p300_single', 'right', tip_racks=[tiprack])

    # Reagent locations
    sample = reservoir.wells()[0]
    stress_agent = reservoir.wells()[1]
    protective_solution = reservoir.wells()[2]

    # Automated distribution loop
    for well in plate.wells()[:24]:
        pipette.pick_up_tip()
        pipette.transfer(50, sample, well)
        pipette.transfer(20, stress_agent, well)
        pipette.transfer(20, protective_solution, well, mix_after=(3, 50))
        pipette.drop_tip()

This script demonstrates how automation ensures precise volume control, repeatability, and scalable experimentation.

5. Role of Cloud Automation (Ginkgo Nebula)

Using a cloud laboratory platform like Ginkgo Nebula would allow remote experiment deployment without needing a physical lab setup. I could upload experimental designs, specify reagent combinations, and run high-throughput assays in parallel. This aligns with the project constraints, since the experimental work in my node was conceptual rather than physically executed.

Cloud automation would also:

Enable large-scale parameter screening

Reduce human error in pipetting and timing

Provide standardized datasets for analysis

Allow iterative experimental optimization based on previous results

6. Why Automation is Critical for This Project

Automation directly supports the scientific objectives by improving experimental precision, reproducibility, and scalability. In projects related to radiation tolerance, stress-response biology, or protective biomolecules, small inconsistencies in reagent handling or incubation can produce misleading results. Automated robotic systems eliminate much of this variability and allow controlled, repeatable experimental design.

Additionally, automation enables remote experimentation and systematic testing of multiple protective conditions, which is especially relevant when investigating biological mechanisms inspired by extremophiles and radiation-resistant systems. This makes the project more rigorous, technically feasible, and aligned with modern synthetic biology and bioengineering workflows.

Final Project Ideas — DUE BY START OF FEB 24 LECTURE As explained in this week’s recitation, add 1-3 slides in your Node’s section of this slide deck with 3 ideas you have for an Individual Final Project. Be sure to put your name, city, and country on your slide!

Slide 1 — Ideas For My Fianl Project

Individual Final Project Ideas – GammaShroom Automation Name: Sergio Cuiza City, Country: Cochabamba, Bolivia Node: SynBio USFQ

My project focuses on GammaShroom, a concept inspired by radiation-resistant fungi and extremophile biology, and how lab automation could optimize experimental design, reproducibility, and remote testing workflows even without direct wet-lab execution.

Slide 2 — Idea 1: Automated Growth Condition Screening for GammaShroom

My first project idea is to design an automated workflow to screen different growth conditions for a radiation-resistant fungal model (GammaShroom concept). Using an Opentrons liquid-handling robot, the system would prepare multiple media compositions, dispense samples into 96-well plates, and standardize experimental setups.

The automation would:

Precisely distribute media with different nutrient concentrations

Control replicates to reduce variability

Enable parallel condition testing

Improve reproducibility of extremophile growth experiments

Even if my node did not perform the wet lab, the protocol design could be deployed remotely in a cloud lab environment, allowing scalable experimentation without manual pipetting errors.

Slide 3— Idea 2: Automated Bio-Pigment Production Screening (Melanin & Radioprotection)

Many radiation-resistant fungi produce melanin-like pigments that may contribute to radiotolerance. My project proposes using automation to screen pigment production efficiency under different environmental conditions.

Automated workflow:

Robotically prepare multiple culture media compositions

Dispense fungal samples into microplates

Incubate under controlled conditions

Measure pigmentation changes using plate reader absorbance

This would connect directly to the GammaShroom concept by exploring the biological mechanisms that could explain radiation resistance in fungal systems.

Slide 4 — Idea 3: Custom 3D-Printed Holder for Non-Standard Fungal Samples

My third idea is to design a custom 3D-printed holder compatible with the Opentrons deck to stabilize unconventional sample containers used for fungal or bio-inspired materials like GammaShroom substrates.

The holder would:

Secure irregular culture containers

Allow precise reagent deposition

Maintain positional accuracy during automated pipetting

Enable consistent spatial experimental layouts

This hardware + automation integration is especially useful for bio-inspired projects where standard lab plates may not match the experimental material format.