Week 3: Lab Automation

**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:
- Carga de laboratorio: Se configuran las puntas, la pipeta y el módulo de temperatura, junto con las placas de colores y agar.
- Funciones auxiliares: Se definen
location_of_colorpara obtener la ubicación de un color específico ydispense_and_detachpara una dispensación limpia. - 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’. - Cálculo de escalado y centrado: El código calcula las dimensiones del patrón para escalarlo y centrarlo en la placa de agar.
- 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_detachpara evitar manchas. - 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()