Week 03 HW: Lab Automation

cover image cover image

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.

volleyball volleyball

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()