Week 3: Lab Automation

XLAbLogo XLAbLogo
**Assignment: Python Script for Opentrons Artwork**

**Protocol for Opentron Artwork: Max_XLAB_pattern**

'author': 'MaxMatus',
'protocolName': 'Max_XLab_pattern',
'description': 'XLab_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

##############################################################################

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

center_location = agar_plate[‘A1’].top()

cell_well = color_plate[‘A1’] # Changed ‘F1’ to ‘A1’ because F1 was not configured to have a color.

Aspirate

pipette_20ul.pick_up_tip()

X_LABpattern = [ # X (0, 4), (2, 4), # Fila superior (1, 3), # Diagonal bajando (1, 2), # Centro (0, 1), (2, 1), # Fila inferior

# First dot after X - moved from (3,1) to (4,1)
(4, 1),
# Second dot for separation - moved from (4,1) to (5,1)
(5, 1),

# L (shifted by 2 units from previous adjustment: 5+2=7)
(7, 4),
(7, 3),
(7, 2),
(7, 1), (8, 1), (9, 1),

# Espacio entre letras (col 10 vacía)

# A (shifted by 2 units from previous adjustment: 9+2=11)
(11, 1), (11, 2), (11, 3), (11, 4),
(12, 4),
(13, 4), (13, 3), (13, 2), (13, 1),
(12, 2),

# Espacio entre letras (col 14 vacía)

# b (minúscula) (shifted by 2 units from previous adjustment: 13+2=15)
(15, 1), (15, 2), (15, 3), (15, 4),
(16, 2),
(16, 1),
(17, 2),
(17, 1),

]

Determine pattern dimensions for scaling and centering

x_coords = [p[0] for p in X_LABpattern] y_coords = [p[1] for p in X_LABpattern]

min_x, max_x = min(x_coords), max(x_coords) min_y, max_y = min(y_coords), max(y_coords)

Calculate the center of the pattern in its own coordinate system

center_pattern_x_unit = (min_x + max_x) / 2 center_pattern_y_unit = (min_y + max_y) / 2

Scaling factor: Adjust this to control the size of your pattern on the plate

Each unit in X_LABpattern will represent ‘scale_factor’ mm on the plate.

For a 30mm wide pattern with the current 15 unit width: 30 / (15 - 0) = 2

scale_factor = 2.0 # mm per unit

Volume to dispense per dot

dispense_volume = 1 # uL

Aspiration tracking

aspirated_volume = 0 max_aspirate_volume = 20 # uL (matching the example’s approach)

for i, (px, py) in enumerate(X_LABpattern): # If the pipette is empty or low on liquid, aspirate more # We aspirate max_aspirate_volume or whatever is needed for the remaining dots if aspirated_volume < dispense_volume: volume_to_aspirate = min(max_aspirate_volume, (len(X_LABpattern) - i) * dispense_volume - aspirated_volume) if volume_to_aspirate > 0: pipette_20ul.aspirate(volume_to_aspirate, cell_well) aspirated_volume += volume_to_aspirate

# Shift and scale the pattern coordinates
# The agar_plate['A1'].top() is the center, so we shift relative to that.
shifted_px = (px - center_pattern_x_unit) * scale_factor
shifted_py = (py - center_pattern_y_unit) * scale_factor

# Calculate the absolute position on the agar plate
adjusted_location = center_location.move(types.Point(x=shifted_px, y=shifted_py))

# Dispense the liquid using the helper function
dispense_and_detach(pipette_20ul, dispense_volume, adjusted_location)
aspirated_volume -= dispense_volume

Drop the tip at the end

pipette_20ul.drop_tip()

“”"### Protocolo Max_XLab_pattern

Este protocolo utiliza la plataforma Opentrons para patronar un diseño ‘XLab’ en una placa de agar. A continuación, se detalla el código completo:

Descripción general del funcionamiento:

  1. Carga de laboratorio: Se configuran las puntas, la pipeta y el módulo de temperatura, junto con las placas de colores y agar.
  2. Funciones auxiliares: Se definen location_of_color para obtener la ubicación de un color específico y dispense_and_detach para una dispensación limpia.
  3. Definición del patrón X_LABpattern: Una lista de coordenadas (x, y) que forman el diseño ‘X LAb’. Este patrón incluye la separación solicitada entre ‘X’, los puntos y ‘L’.
  4. Cálculo de escalado y centrado: El código calcula las dimensiones del patrón para escalarlo y centrarlo en la placa de agar.
  5. Aspiración y dispensación: Itera sobre cada punto del patrón. La pipeta aspira líquido (‘Red’ de la posición ‘A1’) en bloques de 20 uL y dispensa 1 uL en cada coordenada ajustada en la placa de agar, utilizando la función dispense_and_detach para evitar manchas.
  6. Descarte de punta: Al finalizar, la pipeta desecha la punta usada. """

from opentrons import types

metadata = { # see https://docs.opentrons.com/v2/tutorial.html#tutorial-metadata ‘author’: ‘MaxMatus’, ‘protocolName’: ‘Max_XLab_pattern’, ‘description’: ‘XLab_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

##############################################################################

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

center_location = agar_plate[‘A1’].top()

cell_well = color_plate[‘A1’] # Changed ‘F1’ to ‘A1’ because F1 was not configured to have a color.

Aspirate

pipette_20ul.pick_up_tip()

X_LABpattern = [ # Línea horizontal izquierda (puntos en fila central, columnas negativas) (-5, 2), (-4, 2), (-3, 2), (-2, 2),

# X
(0, 4), (2, 4),          # Fila superior
(1, 3),                   # Diagonal bajando
(1, 2),                   # Centro
(0, 1), (2, 1),          # Fila inferior

# First dot after X - moved from (3,1) to (4,1)
(4, 1),
# Second dot for separation - moved from (4,1) to (5,1)
(5, 1),

# L (shifted by 2 units from previous adjustment: 5+2=7)
(7, 4),
(7, 3),
(7, 2),
(7, 1), (8, 1), (9, 1),

# Espacio entre letras (col 10 vacía)

# A (shifted by 2 units from previous adjustment: 9+2=11)
(11, 1), (11, 2), (11, 3), (11, 4),
(12, 4),
(13, 4), (13, 3), (13, 2), (13, 1),
(12, 2),

# Espacio entre letras (col 14 vacía)

# b (minúscula) (shifted by 2 units from previous adjustment: 13+2=15)
(15, 1), (15, 2), (15, 3), (15, 4),
(16, 2),
(16, 1),
(17, 2),
(17, 1),

# Línea horizontal derecha (puntos en fila central, columnas después de la b)
(19, 2), (20, 2), (21, 2), (22, 2),

]

Determine pattern dimensions for scaling and centering

x_coords = [p[0] for p in X_LABpattern] y_coords = [p[1] for p in X_LABpattern]

min_x, max_x = min(x_coords), max(x_coords) min_y, max_y = min(y_coords), max(y_coords)

Calculate the center of the pattern in its own coordinate system

center_pattern_x_unit = (min_x + max_x) / 2 center_pattern_y_unit = (min_y + max_y) / 2

Scaling factor: Adjust this to control the size of your pattern on the plate

Each unit in X_LABpattern will represent ‘scale_factor’ mm on the plate.

For a 30mm wide pattern with the current 15 unit width: 30 / (15 - 0) = 2

scale_factor = 2.0 # mm per unit

Volume to dispense per dot

dispense_volume = 1 # uL

Aspiration tracking

aspirated_volume = 0 max_aspirate_volume = 20 # uL (matching the example’s approach)

for i, (px, py) in enumerate(X_LABpattern): # If the pipette is empty or low on liquid, aspirate more # We aspirate max_aspirate_volume or whatever is needed for the remaining dots if aspirated_volume < dispense_volume: volume_to_aspirate = min(max_aspirate_volume, (len(X_LABpattern) - i) * dispense_volume - aspirated_volume) if volume_to_aspirate > 0: pipette_20ul.aspirate(volume_to_aspirate, cell_well) aspirated_volume += volume_to_aspirate

# Shift and scale the pattern coordinates
# The agar_plate['A1'].top() is the center, so we shift relative to that.
shifted_px = (px - center_pattern_x_unit) * scale_factor
shifted_py = (py - center_pattern_y_unit) * scale_factor

# Calculate the absolute position on the agar plate
adjusted_location = center_location.move(types.Point(x=shifted_px, y=shifted_py))

# Dispense the liquid using the helper function
dispense_and_detach(pipette_20ul, dispense_volume, adjusted_location)
aspirated_volume -= dispense_volume

Drop the tip at the end

pipette_20ul.drop_tip()

Execute Simulation / Visualization – don’t change this code block

protocol = OpentronsMock(well_colors) run(protocol) protocol.visualize()