Week 3 HW: Opentrons & Lab Automation

Assignment 1: Python Script for Opentrons Artwork

I designed an Indonesian cloud pattern called Megamendung using the Opentrons pipetting robot. The pattern uses two colors — red (mRFP1) and green (Azurite) — arranged as interlocking arcs to replicate the traditional Javanese batik motif.

Simulated output of the Python script:

Megamendung pattern output Megamendung pattern output

Python script:

from opentrons import types
metadata = {    # see https://docs.opentrons.com/v2/tutorial.html#tutorial-metadata
    'author': 'Aditya Retnanto',
    'protocolName': 'Megamendung',
    'description': 'Indonesian Cloud Pattern',
    '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
    ##############################################################################

    # 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.
        """
        assert(isinstance(volume, (int, float)))
        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)

    dispense_amount = 0.5
    mrfp1_points = [(-4, 16),(4, 16),(-16, 12),(-8, 12),(8, 12),(16, 12),(-24, 4),(-12, 4),(-4, 4),(4, 4),(12, 4),(24, 4),(-24, -4),(24, -4),(-16, -12),(-8, -12),(8, -12),(16, -12),(-4, -16),(4, -16)]
    azurite_points = [(0, 16),(-12, 12),(12, 12),(-20, 8),(-4, 8),(4, 8),(20, 8),(-28, 0),(-8, 0),(8, 0),(28, 0),(-20, -8),(20, -8),(-12, -12),(12, -12),(0, -16)]

    pipette_20ul.pick_up_tip()
    total_aspiration = len(mrfp1_points) * dispense_amount
    pipette_20ul.aspirate(total_aspiration, location_of_color('Red'))
    for i in range(len(mrfp1_points)):
        adjusted_location = center_location.move(types.Point(x=mrfp1_points[i][0], y=mrfp1_points[i][1]))
        dispense_and_detach(pipette_20ul, dispense_amount, adjusted_location)
    pipette_20ul.drop_tip()

    pipette_20ul.pick_up_tip()
    total_aspiration = len(azurite_points) * dispense_amount
    pipette_20ul.aspirate(total_aspiration, location_of_color('Green'))
    for i in range(len(azurite_points)):
        adjusted_location = center_location.move(types.Point(x=azurite_points[i][0], y=azurite_points[i][1]))
        dispense_and_detach(pipette_20ul, dispense_amount, adjusted_location)
    pipette_20ul.drop_tip()

Assignment 2: Post-Lab Questions

Published Paper on Lab Automation

Automation for Final Project

Assignment 3: Final Project Ideas