diff --git a/.devcontainer.json b/.devcontainer.json deleted file mode 100644 index c67cc25..0000000 --- a/.devcontainer.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - // workspace setup - "name": "enzyme screening platform", - - // setup a container to develop an OT2 protocol - "dockerFile": "containers/Dockerfile", - "context": ".", - - // Set *default* container specific settings.json values. - "settings": { - - "terminal.integrated.profiles.linux": { - "bash": { - "path": "/bin/bash", - }, - "zsh": { - "path": "/bin/zsh", - } - }, - "terminal.integrated.defaultProfile.windows": "bash", - "python.pythonPath": "/usr/local/bin/python", - "python.formatting.provider": "black", - "python.formatting.blackPath": "/usr/local/bin/black", - "[python]": { - "editor.rulers": [ - 90 - ] - }, - }, - - // extensions for vscode - "extensions": [ - "ms-python.python", - "stkb.rewrap", - "donjayamanne.githistory", - "aaron-bond.better-comments", - "gruntfuggly.todo-tree", - "mhutchie.git-graph" - ], - -} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..a756b2e --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,51 @@ +{ + // workspace setup + "name": "enzyme screening platform", + + // setup a container to develop an OT2 protocol + "dockerFile": "../containers/Dockerfile", + "context": ".", + + // Set *default* container specific settings.json values. + // vs set code specific options + "customizations": { + "vscode": { + "extensions": [ + "stkb.rewrap", + "christian-kohler.path-intellisense", + "streetsidesoftware.code-spell-checker", + "aaron-bond.better-comments", + "gruntfuggly.todo-tree", + "mhutchie.git-graph", + "donjayamanne.githistory", + "github.vscode-github-actions", + "ms-azuretools.vscode-docker", + "ms-python.python", + "ms-python.black-formatter", + "njpwerner.autodocstring", + "nextflow.nextflow", + "timonwong.shellcheck" + ], + "settings": { + "editor.tabSize": 4, + "terminal.integrated.defaultProfile.linux": "bash", + "terminal.integrated.profiles.linux": { + "bash": { + "path": "/bin/bash", + "icon": "terminal-bash" + } + }, + "python.formatting.provider": "none", + "source.organizeImports": true, + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": [ + "source.organizeImports" + ] + } + } + } + } + +} diff --git a/OT2-setup/setup/protocol-templates/spotting-template.py b/OT2-setup/setup/protocol-templates/spotting-template.py deleted file mode 100644 index c5e128b..0000000 --- a/OT2-setup/setup/protocol-templates/spotting-template.py +++ /dev/null @@ -1,109 +0,0 @@ -from opentrons import protocol_api -import math -import csv -import json -import collections - - -metadata = { - 'apiLevel': '2.15', - 'protocolName': 'agar-plate-spotting', - 'description': 'Protocol for agar plate spotting.', - 'author': 'Martyna Kasprzyk' -} - - -def get_parameters(json_file: str) -> dict: - """ - This function parses the json file with the parameters for the experiment and returns them as a dictionary. - """ - parameters = json.loads(json_file) # load and parse the paraemter json file - return parameters - -def read_csv_file(csv_file): - - csv_data = csv_file.splitlines()[1:] # Discard the blank first line. - csv_reader = csv.DictReader(csv_data) - - - # if multi channel - seen_numbers = {} - result_rows = [] - - for row in csv_reader: - well = row['source_well'] - letter, number = well[0], well[1:] - - # Check if the number is already associated with a different letter - if number in seen_numbers and seen_numbers[number] != letter: - continue - - result_rows.append(row) - seen_numbers[number] = letter - - plate_locations = [] - source_wells = [] - destination_wells = [] - spotting_volume = [] - agar_plate_weight = [] - - for row in result_rows: - - if row['plate_location'] not in plate_locations: - plate_locations.append(int(row['plate_location'])) - - source_wells.append(row['source_well']) - destination_wells.append(row['destination_well']) - spotting_volume.append(int(row['spotting_volume'])) - agar_plate_weight.append(float(row['plate_weight'])) - - # print(f"{plate_locations},{source_wells},{destination_wells},{spotting_volume}") - csv_parameters = collections.namedtuple("csv_parameters", ["plate_locations", "source_wells", "destination_wells", "spotting_volume", "agar_plate_weight"]) - - return csv_parameters(plate_locations, source_wells, destination_wells, spotting_volume, agar_plate_weight) - -def agar_height(agar_plate_weight, just_plate, agar_density,spotting_height): - bottom_area = math.pi*(83.5/2)**2 - agar_weight = agar_plate_weight - float(just_plate) - agar_height = agar_weight/(bottom_area*agar_density) - spotting_height = agar_height + spotting_height - print(agar_weight) - print(spotting_height) - - return spotting_height - -# run protocol -def run(protocol: protocol_api.ProtocolContext): - """ - Main function for running the protocol. - """ - - params = get_parameters(parameters_json) # load the parameters from the json file - csv_params = read_csv_file(csv_file) - - # loading hardware and labware - pipette_tipracks = [protocol.load_labware(load_name=params["pipette_tiprack_name"], location=i) for i in params["pipette_tiprack_slots"]] - pipette = protocol.load_instrument(instrument_name=params["pipette_name"], mount=params["pipette_mount"], tip_racks=pipette_tipracks) # p20 pipette - - # source plate located in the thermocycler - thermocycler_mod = protocol.load_module("thermocycler") # thermocycler module, takes location 7,8,10,11 - transformation_plate = thermocycler_mod.load_labware(params["transformation_plate_name"]) # plate where transformations take place, loaded onto thermocycler module - - # Extract the plate locations into a list - agar_plates = [protocol.load_labware(load_name=params["agar_plate_name"], - location=slot, - label=f"Agar Plate {str(i+1)}") - for i, slot in enumerate(csv_params.plate_locations)] - - # protocol.comment(f"{agar_plates}") - thermocycler_mod.open_lid() - ######### SPOTTING ########## - for plate, source, destination, volume, weight in zip(agar_plates, csv_params.source_wells, csv_params.destination_wells, csv_params.spotting_volume, csv_params.agar_plate_weight): - pipette.well_bottom_clearance.dispense = 1 - pipette.pick_up_tip() - pipette.mix(repetitions=3, volume=5, location=transformation_plate[source], rate=2) - pipette.aspirate(volume=volume + params["dead_volume"], location=transformation_plate[source], rate=2) - pipette.well_bottom_clearance.dispense = agar_height(weight, params["plate_weight"], params["agar_density"], params["spotting_height"]) - pipette.dispense(volume=volume, location=plate[destination], rate=4) - protocol.delay(seconds=5) - pipette.drop_tip() \ No newline at end of file diff --git a/OT2-setup/setup/protocol-templates/transformation-template.py b/OT2-setup/setup/protocol-templates/transformation-template.py deleted file mode 100644 index 316fb09..0000000 --- a/OT2-setup/setup/protocol-templates/transformation-template.py +++ /dev/null @@ -1,166 +0,0 @@ -from opentrons import protocol_api -import json -import csv -import collections - -metadata = { - "apiLevel": "2.13", - "protocolName": "E. coli heatshock transformation", - "description": "OT-2 protocol for standard E. coli heatshock transformation.", - "author": "Martyna Kasprzyk", -} - -def read_csv_file(csv_file): - - csv_data = csv_file.splitlines()[1:] # Discard the blank first line. - csv_reader = csv.DictReader(csv_data) - - # if multi channel - seen_numbers = {} - result_rows = [] - - for row in csv_reader: - well = row['dna_well'] - letter, number = well[0], well[1:] - - # Check if the number is already associated with a different letter - if number in seen_numbers and seen_numbers[number] != letter: - continue - - result_rows.append(row) - seen_numbers[number] = letter - - cells_source_wells = [] - cells_volume = [] - dna_source_wells = [] - dna_volume = [] - soc_source_wells = [] - soc_volume = [] - transformation_well = [] - - for row in result_rows: - cells_source_wells.append(row['cells_well']) - cells_volume.append(int(row["cells_volume"])) - dna_source_wells.append(row['dna_well']) - dna_volume.append(int(row["dna_volume"])) - soc_source_wells.append(row['soc_well']) - soc_volume.append(int(row["soc_volume"])) - transformation_well.append(row["transformation_well"]) - - csv_parameters = collections.namedtuple("csv_parameters", ["cells_source_wells", "cells_volume", "dna_source_wells", "dna_volume", "soc_source_wells", "soc_volume", "transformation_well"]) - - return csv_parameters(cells_source_wells, cells_volume, dna_source_wells, dna_volume, soc_source_wells, soc_volume, transformation_well) - - -def get_parameters(json_file: str) -> dict: - """ - This function parses the json file with the parameters for the experiment and returns them as a dictionary. - """ - parameters = json.loads(json_file) # load and parse the paraemter json file - return parameters - -def run(protocol: protocol_api.ProtocolContext): - """ - Main function for running the protocol. - """ - - params = get_parameters(parameters_json) # load the parameters from the json file - csv_params = read_csv_file(csv_file) - - # loading hardware and labware - source_plate = protocol.load_labware(params["dna_plate_name"], params["dna_plate_slot"]) # plate containing dna to be transformed - thermocycler_mod = protocol.load_module("thermocycler") # thermocycler module, takes location 7,8,10,11 - transformation_plate = thermocycler_mod.load_labware(params["transformation_plate_name"]) # plate where transformations take place, loaded onto thermocycler module - - pipette_1_tipracks = [protocol.load_labware(load_name=params["pipette_1_tiprack_name"], location=i) for i in params["pipette_1_tiprack_slots"]] - pipette_1 = protocol.load_instrument(instrument_name=params["pipette_1_name"], mount=params["pipette_1_mount"], tip_racks=pipette_1_tipracks) # p20 pipette - - pipette_2_tipracks = [protocol.load_labware(load_name=params["pipette_2_tiprack_name"], location=i) for i in params["pipette_2_tiprack_slots"]] - pipette_2 = protocol.load_instrument(instrument_name=params["pipette_2_name"], mount=params["pipette_2_mount"], tip_racks=pipette_2_tipracks) # p300 pipette - - # loading liquid handling settings - pipette_2.flow_rate.aspirate = 10 # aspirate speed of the pipette - pipette_2.flow_rate.dispense = 10 # dispense speed of the pipette - - ########## HEAT-SHOCK TRANSFORMATION ########## - protocol.comment("Starting heat-shock transformation.") - protocol.set_rail_lights(True) - - # step 1: Preheat the module and open lid. - thermocycler_mod.set_block_temperature(temperature=params["init_temp"]) - thermocycler_mod.open_lid() - - # step 2: Transferring competent cells into the transformation plate. - protocol.pause("Put plate on the thermocycler module and click 'resume'.") # wait for operator to put the plate - - pipette_2.distribute(volume=csv_params.cells_volume, # volume of competent cells to be transferred - source=[source_plate.wells_by_name()[well] for well in csv_params.cells_source_wells], # transfer from the specified well in the resevoir containing competent cells - dest=[transformation_plate.wells_by_name()[well] for well in csv_params.transformation_well], # transfer to transformation plate in the thermocycler - mix_before=(1,50)) # resuspend the competent cells once before transfering into the transformation plate with the total volume to be trasfered, volume not specified sets the default to the max volume of pipette - - # step 3: Transfer DNA into wells of the transformation plate. - pipette_1.well_bottom_clearance.aspirate = 0.5 # aspiration height of the pipette (when volume of DNA is low) - - for i in zip(csv_params.dna_source_wells, csv_params.transformation_well, csv_params.dna_volume, csv_params.cells_volume): - pipette_1.pick_up_tip() - pipette_1.well_bottom_clearance.aspirate = 0.5 - pipette_1.aspirate(volume=i[2], location=source_plate.wells_by_name()[i[0]]) - pipette_1.well_bottom_clearance.aspirate = 1 - pipette_1.dispense(volume=i[2], location=transformation_plate.wells_by_name()[i[1]]) - mixing_volume = (i[2]+i[3])/2 - pipette_1.aspirate(volume=mixing_volume, location=transformation_plate.wells_by_name()[i[1]]) - pipette_1.dispense(volume=mixing_volume, location=transformation_plate.wells_by_name()[i[1]]) - pipette_1.aspirate(volume=mixing_volume, location=transformation_plate.wells_by_name()[i[1]]) - pipette_1.dispense(volume=mixing_volume, location=transformation_plate.wells_by_name()[i[1]]) - pipette_1.blow_out(location=transformation_plate.wells_by_name()[i[1]]) - pipette_1.move_to(transformation_plate.wells_by_name()[i[1]].bottom()) - pipette_1.drop_tip() - - # step 4: Close the thermocycler lid and cool the thermocycler down for specified temperature and time, deafult: 4 degrees celcius and 20 minutes - thermocycler_mod.close_lid() - thermocycler_mod.set_block_temperature(temperature=params["init_temp"], - hold_time_minutes=params["init_time"]) - - # step 5: Perform heat-shock transformation for specified temperature and time, deafult: 42 degrees celcius and 1 minute - protocol.comment("Starting heat-shock transformation.") - thermocycler_mod.set_block_temperature(temperature=params["heat_temp"], - hold_time_seconds=params["heat_time"]) - - # step 6: Cool down thermocycler for specified temperature and time, default: 4 degrees celcius and 2 minutes. - protocol.comment("Cool down thermocycler to 4C post heatshock") - thermocycler_mod.set_block_temperature(temperature=params["cool_temp"], - hold_time_minutes=params["cool_time"]) - - pipette_2.flow_rate.aspirate = 15 # aspirate speed of the pipette - pipette_2.flow_rate.dispense = 15 # dispense speed of the pipette - - # step 7: Open the thermocycler lid and transfer SOC media to transformed cells. - thermocycler_mod.open_lid() - - for i in zip(csv_params.soc_source_wells, csv_params.transformation_well, csv_params.soc_volume, csv_params.dna_volume, csv_params.cells_volume): - pipette_2.pick_up_tip() - pipette_2.aspirate(volume=i[2], location=source_plate.wells_by_name()[i[0]]) - pipette_2.dispense(volume=i[2], location=transformation_plate.wells_by_name()[i[1]]) - mixing_volume = (i[2]+i[3]+i[4])/2 - for _ in range(3): - pipette_2.aspirate(volume=mixing_volume, location=transformation_plate.wells_by_name()[i[1]]) - pipette_2.dispense(volume=mixing_volume, location=transformation_plate.wells_by_name()[i[1]]) - - pipette_2.blow_out(location=transformation_plate.wells_by_name()[i[1]]) - pipette_2.move_to(transformation_plate.wells_by_name()[i[1]].bottom()) - pipette_2.drop_tip() - - # step 8: Close the thermocycler lid and heat up both thermocycler block and lid for specified temperature and time, default: 37 celcius degrees and 60 minutes - thermocycler_mod.close_lid() - thermocycler_mod.set_lid_temperature(temperature=params["inc_temp"]) - thermocycler_mod.set_block_temperature(temperature=params["inc_temp"], - hold_time_minutes=params["inc_time"]) - protocol.comment("Starting incubation.") - protocol.set_rail_lights(False) - - # step 9: Finish incubation and deactivate the thermocycler module. - protocol.comment("Incubation finished.") - thermocycler_mod.deactivate_lid() - thermocycler_mod.deactivate() - - protocol.comment("Run complete!") \ No newline at end of file diff --git a/OT2-setup/setup/create-labware/labware/armadillo_96_wellplate_200ul_pcr_full_skirt.json b/assets/labware/armadillo_96_wellplate_200ul_pcr_full_skirt.json similarity index 100% rename from OT2-setup/setup/create-labware/labware/armadillo_96_wellplate_200ul_pcr_full_skirt.json rename to assets/labware/armadillo_96_wellplate_200ul_pcr_full_skirt.json diff --git a/OT2-setup/setup/create-labware/labware/biorad_384_wellplate_50ul.json b/assets/labware/biorad_384_wellplate_50ul.json similarity index 100% rename from OT2-setup/setup/create-labware/labware/biorad_384_wellplate_50ul.json rename to assets/labware/biorad_384_wellplate_50ul.json diff --git a/OT2-setup/setup/create-labware/labware/biorad_96_wellplate_200ul_pcr.json b/assets/labware/biorad_96_wellplate_200ul_pcr.json similarity index 100% rename from OT2-setup/setup/create-labware/labware/biorad_96_wellplate_200ul_pcr.json rename to assets/labware/biorad_96_wellplate_200ul_pcr.json diff --git a/OT2-setup/setup/create-labware/labware/corning_24_wellplate_3.4ml_flat.json b/assets/labware/corning_24_wellplate_3.4ml_flat.json similarity index 100% rename from OT2-setup/setup/create-labware/labware/corning_24_wellplate_3.4ml_flat.json rename to assets/labware/corning_24_wellplate_3.4ml_flat.json diff --git a/OT2-setup/setup/create-labware/labware/corning_96_wellplate_360ul_flat.json b/assets/labware/corning_96_wellplate_360ul_flat.json similarity index 100% rename from OT2-setup/setup/create-labware/labware/corning_96_wellplate_360ul_flat.json rename to assets/labware/corning_96_wellplate_360ul_flat.json diff --git a/OT2-setup/setup/create-labware/labware/nest_96_wellplate_2ml_deep.json b/assets/labware/nest_96_wellplate_2ml_deep.json similarity index 100% rename from OT2-setup/setup/create-labware/labware/nest_96_wellplate_2ml_deep.json rename to assets/labware/nest_96_wellplate_2ml_deep.json diff --git a/nunc_singlewell_plate_90ml_4x6_grid.json b/assets/labware/nunc_singlewell_plate_90ml_4x6_grid.json similarity index 100% rename from nunc_singlewell_plate_90ml_4x6_grid.json rename to assets/labware/nunc_singlewell_plate_90ml_4x6_grid.json diff --git a/OT2-setup/setup/create-labware/labware/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical.json b/assets/labware/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical.json similarity index 100% rename from OT2-setup/setup/create-labware/labware/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical.json rename to assets/labware/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical.json diff --git a/OT2-setup/setup/create-labware/labware/petridish.json b/assets/labware/petridish.json similarity index 100% rename from OT2-setup/setup/create-labware/labware/petridish.json rename to assets/labware/petridish.json diff --git a/OT2-setup/setup/create-labware/labware/usascientific_96_wellplate_2.4ml_deep.json b/assets/labware/usascientific_96_wellplate_2.4ml_deep.json similarity index 100% rename from OT2-setup/setup/create-labware/labware/usascientific_96_wellplate_2.4ml_deep.json rename to assets/labware/usascientific_96_wellplate_2.4ml_deep.json diff --git a/assets/protocols/spotting-template.py b/assets/protocols/spotting-template.py new file mode 100644 index 0000000..75083a4 --- /dev/null +++ b/assets/protocols/spotting-template.py @@ -0,0 +1,175 @@ +from opentrons import protocol_api +import math +import csv +import json +import collections + +############################################## +##### PROTOCOL-COMPILER: DO NOT CHANGE ##### +############################################## + +# PROTOCOL CONFIGURATION FILE +INPUT_JSON_FILE = """ +{{INPUT_JSON_FILE}} +""" + +# PROTOCOL INPUT MATERIAL +INPUT_CSV_FILE = """ +{{INPUT_CSV_FILE}} +""" + +############################################## + + +metadata = { + "apiLevel": "2.15", + "protocolName": "agar-plate-spotting", + "description": "Protocol for agar plate spotting.", + "author": "Martyna Kasprzyk", +} + + +def get_parameters(json_file: str) -> dict: + """ + This function parses the json file with the parameters for + the experiment and returns them as a dictionary. + """ + parameters = json.loads(json_file) # load and parse the paraemter json file + return parameters + + +def read_csv_file(csv_file): + + csv_data = csv_file.splitlines()[1:] # Discard the blank first line. + csv_reader = csv.DictReader(csv_data) + + # if multi channel + seen_numbers = {} + result_rows = [] + + for row in csv_reader: + well = row["source_well"] + letter, number = well[0], well[1:] + + # Check if the number is already associated with a different letter + if number in seen_numbers and seen_numbers[number] != letter: + continue + + result_rows.append(row) + seen_numbers[number] = letter + + plate_locations = [] + source_wells = [] + destination_wells = [] + spotting_volume = [] + agar_plate_weight = [] + + for row in result_rows: + + if row["plate_location"] not in plate_locations: + plate_locations.append(int(row["plate_location"])) + + source_wells.append(row["source_well"]) + destination_wells.append(row["destination_well"]) + spotting_volume.append(int(row["spotting_volume"])) + agar_plate_weight.append(float(row["plate_weight"])) + + # print(f"{plate_locations},{source_wells},{destination_wells},{spotting_volume}") + csv_parameters = collections.namedtuple( + "csv_parameters", + [ + "plate_locations", + "source_wells", + "destination_wells", + "spotting_volume", + "agar_plate_weight", + ], + ) + + return csv_parameters( + plate_locations, + source_wells, + destination_wells, + spotting_volume, + agar_plate_weight, + ) + + +def agar_height(agar_plate_weight, just_plate, agar_density, spotting_height): + bottom_area = math.pi * (83.5 / 2) ** 2 + agar_weight = agar_plate_weight - float(just_plate) + agar_height = agar_weight / (bottom_area * agar_density) + spotting_height = agar_height + spotting_height + print(agar_weight) + print(spotting_height) + + return spotting_height + + +# run protocol +def run(protocol: protocol_api.ProtocolContext): + """ + Main function for running the protocol. + """ + + params = get_parameters(INPUT_JSON_FILE) # load the parameters from the json file + csv_params = read_csv_file(INPUT_CSV_FILE) + + # loading hardware and labware + pipette_tipracks = [ + protocol.load_labware(load_name=params["pipette_tiprack_name"], location=i) + for i in params["pipette_tiprack_slots"] + ] + pipette = protocol.load_instrument( + instrument_name=params["pipette_name"], + mount=params["pipette_mount"], + tip_racks=pipette_tipracks, + ) # p20 pipette + + # source plate located in the thermocycler + thermocycler_mod = protocol.load_module( + "thermocycler" + ) # thermocycler module, takes location 7,8,10,11 + transformation_plate = thermocycler_mod.load_labware( + params["transformation_plate_name"] + ) # plate where transformations take place, loaded onto thermocycler module + + # Extract the plate locations into a list + agar_plates = [ + protocol.load_labware( + load_name=params["agar_plate_name"], + location=slot, + label=f"Agar Plate {str(i+1)}", + ) + for i, slot in enumerate(csv_params.plate_locations) + ] + + # protocol.comment(f"{agar_plates}") + thermocycler_mod.open_lid() + ######### SPOTTING ########## + for plate, source, destination, volume, weight in zip( + agar_plates, + csv_params.source_wells, + csv_params.destination_wells, + csv_params.spotting_volume, + csv_params.agar_plate_weight, + ): + pipette.well_bottom_clearance.dispense = 1 + pipette.pick_up_tip() + pipette.mix( + repetitions=3, volume=5, location=transformation_plate[source], rate=2 + ) + pipette.aspirate( + volume=volume + params["dead_volume"], + location=transformation_plate[source], + rate=2, + ) + pipette.well_bottom_clearance.dispense = agar_height( + weight, + params["plate_weight"], + params["agar_density"], + params["spotting_height"], + ) + pipette.dispense(volume=volume, location=plate[destination], rate=4) + protocol.delay(seconds=5) + pipette.drop_tip() diff --git a/assets/protocols/transformation-template.py b/assets/protocols/transformation-template.py new file mode 100644 index 0000000..905e744 --- /dev/null +++ b/assets/protocols/transformation-template.py @@ -0,0 +1,268 @@ +from opentrons import protocol_api +import json +import csv +import collections + +metadata = { + "apiLevel": "2.13", + "protocolName": "E. coli heatshock transformation", + "description": "OT-2 protocol for standard E. coli heatshock transformation.", + "author": "Martyna Kasprzyk", +} + + +############################################## +##### PROTOCOL-COMPILER: DO NOT CHANGE ##### +############################################## + +# PROTOCOL CONFIGURATION FILE +INPUT_JSON_FILE = """ +{{INPUT_JSON_FILE}} +""" + +# PROTOCOL INPUT MATERIAL +INPUT_CSV_FILE = """ +{{INPUT_CSV_FILE}} +""" + +############################################## + + +def read_csv_file(csv_file): + + csv_data = csv_file.splitlines()[1:] # Discard the blank first line. + csv_reader = csv.DictReader(csv_data) + + # if multi channel + seen_numbers = {} + result_rows = [] + + for row in csv_reader: + well = row["dna_well"] + letter, number = well[0], well[1:] + + # Check if the number is already associated with a different letter + if number in seen_numbers and seen_numbers[number] != letter: + continue + + result_rows.append(row) + seen_numbers[number] = letter + + cells_source_wells = [] + cells_volume = [] + dna_source_wells = [] + dna_volume = [] + soc_source_wells = [] + soc_volume = [] + transformation_well = [] + + for row in result_rows: + cells_source_wells.append(row["cells_well"]) + cells_volume.append(int(row["cells_volume"])) + dna_source_wells.append(row["dna_well"]) + dna_volume.append(int(row["dna_volume"])) + soc_source_wells.append(row["soc_well"]) + soc_volume.append(int(row["soc_volume"])) + transformation_well.append(row["transformation_well"]) + + csv_parameters = collections.namedtuple( + "csv_parameters", + [ + "cells_source_wells", + "cells_volume", + "dna_source_wells", + "dna_volume", + "soc_source_wells", + "soc_volume", + "transformation_well", + ], + ) + + return csv_parameters( + cells_source_wells, + cells_volume, + dna_source_wells, + dna_volume, + soc_source_wells, + soc_volume, + transformation_well, + ) + + +def get_parameters(json_file: str) -> dict: + """ + This function parses the json file with the parameters for the experiment and returns them as a dictionary. + """ + parameters = json.loads(json_file) # load and parse the paraemter json file + return parameters + + +def run(protocol: protocol_api.ProtocolContext): + """ + Main function for running the protocol. + """ + + params = get_parameters(INPUT_JSON_FILE) # load the parameters from the json file + csv_params = read_csv_file(INPUT_CSV_FILE) + + # loading hardware and labware + source_plate = protocol.load_labware( + params["dna_plate_name"], params["dna_plate_slot"] + ) # plate containing dna to be transformed + thermocycler_mod = protocol.load_module( + "thermocycler" + ) # thermocycler module, takes location 7,8,10,11 + transformation_plate = thermocycler_mod.load_labware( + params["transformation_plate_name"] + ) # plate where transformations take place, loaded onto thermocycler module + + pipette_1_tipracks = [ + protocol.load_labware(load_name=params["pipette_1_tiprack_name"], location=i) + for i in params["pipette_1_tiprack_slots"] + ] + pipette_1 = protocol.load_instrument( + instrument_name=params["pipette_1_name"], + mount=params["pipette_1_mount"], + tip_racks=pipette_1_tipracks, + ) # p20 pipette + + pipette_2_tipracks = [ + protocol.load_labware(load_name=params["pipette_2_tiprack_name"], location=i) + for i in params["pipette_2_tiprack_slots"] + ] + pipette_2 = protocol.load_instrument( + instrument_name=params["pipette_2_name"], + mount=params["pipette_2_mount"], + tip_racks=pipette_2_tipracks, + ) # p300 pipette + + # loading liquid handling settings + pipette_2.flow_rate.aspirate = 10 # aspirate speed of the pipette + pipette_2.flow_rate.dispense = 10 # dispense speed of the pipette + + ########## HEAT-SHOCK TRANSFORMATION ########## + protocol.comment("Starting heat-shock transformation.") + protocol.set_rail_lights(True) + + # step 1: Preheat the module and open lid. + thermocycler_mod.set_block_temperature(temperature=params["init_temp"]) + thermocycler_mod.open_lid() + + # step 2: Transferring competent cells into the transformation plate. + protocol.pause( + "Put plate on the thermocycler module and click 'resume'." + ) # wait for operator to put the plate + + pipette_2.distribute( + volume=csv_params.cells_volume, # volume of competent cells to be transferred + source=[ + source_plate.wells_by_name()[well] for well in csv_params.cells_source_wells + ], # transfer from the specified well in the resevoir containing competent cells + dest=[ + transformation_plate.wells_by_name()[well] + for well in csv_params.transformation_well + ], # transfer to transformation plate in the thermocycler + mix_before=(1, 50), + ) # resuspend the competent cells once before transfering into the transformation plate with the total volume to be trasfered, volume not specified sets the default to the max volume of pipette + + # step 3: Transfer DNA into wells of the transformation plate. + pipette_1.well_bottom_clearance.aspirate = ( + 0.5 # aspiration height of the pipette (when volume of DNA is low) + ) + + for i in zip( + csv_params.dna_source_wells, + csv_params.transformation_well, + csv_params.dna_volume, + csv_params.cells_volume, + ): + pipette_1.pick_up_tip() + pipette_1.well_bottom_clearance.aspirate = 0.5 + pipette_1.aspirate(volume=i[2], location=source_plate.wells_by_name()[i[0]]) + pipette_1.well_bottom_clearance.aspirate = 1 + pipette_1.dispense( + volume=i[2], location=transformation_plate.wells_by_name()[i[1]] + ) + mixing_volume = (i[2] + i[3]) / 2 + pipette_1.aspirate( + volume=mixing_volume, location=transformation_plate.wells_by_name()[i[1]] + ) + pipette_1.dispense( + volume=mixing_volume, location=transformation_plate.wells_by_name()[i[1]] + ) + pipette_1.aspirate( + volume=mixing_volume, location=transformation_plate.wells_by_name()[i[1]] + ) + pipette_1.dispense( + volume=mixing_volume, location=transformation_plate.wells_by_name()[i[1]] + ) + pipette_1.blow_out(location=transformation_plate.wells_by_name()[i[1]]) + pipette_1.move_to(transformation_plate.wells_by_name()[i[1]].bottom()) + pipette_1.drop_tip() + + # step 4: Close the thermocycler lid and cool the thermocycler down for specified temperature and time, deafult: 4 degrees celcius and 20 minutes + thermocycler_mod.close_lid() + thermocycler_mod.set_block_temperature( + temperature=params["init_temp"], hold_time_minutes=params["init_time"] + ) + + # step 5: Perform heat-shock transformation for specified temperature and time, deafult: 42 degrees celcius and 1 minute + protocol.comment("Starting heat-shock transformation.") + thermocycler_mod.set_block_temperature( + temperature=params["heat_temp"], hold_time_seconds=params["heat_time"] + ) + + # step 6: Cool down thermocycler for specified temperature and time, default: 4 degrees celcius and 2 minutes. + protocol.comment("Cool down thermocycler to 4C post heatshock") + thermocycler_mod.set_block_temperature( + temperature=params["cool_temp"], hold_time_minutes=params["cool_time"] + ) + + pipette_2.flow_rate.aspirate = 15 # aspirate speed of the pipette + pipette_2.flow_rate.dispense = 15 # dispense speed of the pipette + + # step 7: Open the thermocycler lid and transfer SOC media to transformed cells. + thermocycler_mod.open_lid() + + for i in zip( + csv_params.soc_source_wells, + csv_params.transformation_well, + csv_params.soc_volume, + csv_params.dna_volume, + csv_params.cells_volume, + ): + pipette_2.pick_up_tip() + pipette_2.aspirate(volume=i[2], location=source_plate.wells_by_name()[i[0]]) + pipette_2.dispense( + volume=i[2], location=transformation_plate.wells_by_name()[i[1]] + ) + mixing_volume = (i[2] + i[3] + i[4]) / 2 + for _ in range(3): + pipette_2.aspirate( + volume=mixing_volume, + location=transformation_plate.wells_by_name()[i[1]], + ) + pipette_2.dispense( + volume=mixing_volume, + location=transformation_plate.wells_by_name()[i[1]], + ) + + pipette_2.blow_out(location=transformation_plate.wells_by_name()[i[1]]) + pipette_2.move_to(transformation_plate.wells_by_name()[i[1]].bottom()) + pipette_2.drop_tip() + + # step 8: Close the thermocycler lid and heat up both thermocycler block and lid for specified temperature and time, default: 37 celcius degrees and 60 minutes + thermocycler_mod.close_lid() + thermocycler_mod.set_lid_temperature(temperature=params["inc_temp"]) + thermocycler_mod.set_block_temperature( + temperature=params["inc_temp"], hold_time_minutes=params["inc_time"] + ) + protocol.comment("Starting incubation.") + protocol.set_rail_lights(False) + + # step 9: Finish incubation and deactivate the thermocycler module. + protocol.comment("Incubation finished.") + thermocycler_mod.deactivate_lid() + thermocycler_mod.deactivate() + + protocol.comment("Run complete!") diff --git a/bin/protocol-compiler.py b/bin/protocol-compiler.py new file mode 100755 index 0000000..2e41f65 --- /dev/null +++ b/bin/protocol-compiler.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +"""protocol-compiler.py + + Build a protocol for the Opentrons OT2 using a template file, json params, and CSV file. + + Usage: + protocol-compiler.py [-d --template-dir ] [-o --output-file ] + + Options: + -d, --template-dir Directory containing protocol templates [default: templates/] + -o, --output-file Output file [default: compiled_protocol.py]. + -h --help Show this screen. + --version Show version. +""" + + +from docopt import docopt +from jinja2 import Environment, FileSystemLoader + +if __name__ == "__main__": + # parse commad line arguments + arguments = docopt(__doc__, version="plots") + + # loading templates + environment = Environment(loader=FileSystemLoader(arguments[""])) + template = environment.get_template(arguments[""]) + + # read files directly in memory + json_file = open(arguments[""]).read() + csv_file = open(arguments[""]).read() + + # render customized template + content = template.render(INPUT_JSON_FILE=json_file, INPUT_CSV_FILE=csv_file) + + # save template to file + with open(arguments[""], mode="w", encoding="utf-8") as compiled_protocol: + compiled_protocol.write(content) diff --git a/containers/requirements.txt b/containers/requirements.txt index c245bf0..6e923b5 100644 --- a/containers/requirements.txt +++ b/containers/requirements.txt @@ -1,2 +1,4 @@ black -opentrons \ No newline at end of file +opentrons +jinja2 +docopt \ No newline at end of file