diff --git a/Primer_Design_SDM.py b/Primer_Design_SDM.py index d064059..3574b2a 100644 --- a/Primer_Design_SDM.py +++ b/Primer_Design_SDM.py @@ -2,7 +2,8 @@ from Bio import SeqUtils from Bio.Seq import Seq from Bio.SeqRecord import SeqRecord -from primer3 import calcTm +from Bio.Data import CodonTable +from primer3 import calc_tm # as calcTm - calcTm is deprecated import pandas as pd from itertools import combinations @@ -12,6 +13,7 @@ import re import math from pathlib import Path +import argparse # Design primers in 96-well format for Site-directed mutagenesis. One mutation at a time for this script. @@ -25,40 +27,43 @@ ### INPUTS ########################### ###################################### -CODON_TABLE_FILE = 'Primer_Design/Codon_Table_Standard.csv' +#CODON_TABLE_FILE = 'Primer_Design/Codon_Table_Standard.csv' #ORF_FILE = 'Phytase_II.txt' # This file has intentional mistakes to test the script -ORF_FILE = 'Primer_Design/HMT.txt' ## Change input ORF filename -MUTATION_LIST_FILE = 'Primer_Design/HMT_Plate2.csv' ## Change input mutation csv filename. column name should be "Mutations" +#ORF_FILE = 'Primer_Design/HMT.txt' ## Change input ORF filename +#MUTATION_LIST_FILE = 'Primer_Design/HMT_Plate2.csv' ## Change input mutation csv filename. column name should be "Mutations" ### OUTPUTS ########################### ###################################### -BASE_DIR = Path.cwd() -print(BASE_DIR) -OUTPUT_DIR = 'Primer_Design/Primers_HMT_Plate2' # Change output folder -PRIMER_OUTPUT_FILE = 'HMT_Designed_primers.csv' # Change output file 1 -Forward_Primers_FILE = 'HMT_Forward_Primers_Plate2.csv' # Change output file 2 -Reverse_Primers_FILE = 'HMT_Reverse_Primers_Plate2.csv' # Change output file 3 +#BASE_DIR = Path.cwd() +#print(BASE_DIR) +#OUTPUT_DIR = 'Primer_Design/Primers_HMT_Plate2' # Change output folder +#PRIMER_OUTPUT_FILE = 'HMT_Designed_primers.csv' # Change output file 1 +#Forward_Primers_FILE = 'HMT_Forward_Primers_Plate2.csv' # Change output file 2 +#Reverse_Primers_FILE = 'HMT_Reverse_Primers_Plate2.csv' # Change output file 3 # Create the full path to the file ########################### ###################################### -Output_Path = BASE_DIR / OUTPUT_DIR / PRIMER_OUTPUT_FILE -Output_Path.parent.mkdir(parents=True, exist_ok=True) -Output_Path_Fwd = BASE_DIR / OUTPUT_DIR / Forward_Primers_FILE -Output_Path_Rev = BASE_DIR / OUTPUT_DIR / Reverse_Primers_FILE +def process_outputs(output_dir,primer_output_file,fwd_primers_file,rev_primers_file): + base_dir=Path.cwd() + Output_Path = base_dir / output_dir / primer_output_file + Output_Path.parent.mkdir(parents=True, exist_ok=True) + Output_Path_Fwd = base_dir / output_dir / fwd_primers_file + Output_Path_Rev = base_dir / output_dir / rev_primers_file + return Output_Path, Output_Path_Fwd, Output_Path_Rev ###################################### ###################################### -def remind_user_to_check_constants(): +def remind_user_to_check_constants(mut_file,out_path,orf_file,codon_file_id): """Reminds the user to review constants and make changes if needed.""" - print("\n⚠️ Reminder: Please check the following constants: \n") - print(f" - MUTATION_FILE: {MUTATION_LIST_FILE}\n") - print(f" - PRIMER_OUTPUT: {Output_Path}\n") - print(f" - ORF_FILE: {ORF_FILE}\n") - print(f" - CODON_TABLE: {CODON_TABLE_FILE}\n") - print("Modify these and more relevant values at the top of the script if necessary.\n") + print("\n⚠️ Reminder: Please check the following inputs for correctness: \n") + print(f" - MUTATION_FILE: {mut_file}\n") + print(f" - PRIMER_OUTPUT: {out_path}\n") + print(f" - ORF_FILE: {orf_file}\n") + print(f" - CODON_TABLE: {CodonTable.unambiguous_dna_by_id[int(codon_file_id)]}\n") + print("Modify these and other relevant values in CLI arguments if necessary. Run with -h for help.\n") def find_repeated_kmers(seq, k=16): @@ -71,12 +76,12 @@ def find_repeated_kmers(seq, k=16): if repeats: print(f'\nWarning: {repeats} repeats of {k}bp or more in the ORF. This will affect PCR & SDM.') -def read_orf_and_mutation_list(): +def read_orf_and_mutation_list(orf_file,mutation_list_file,codon_table_file_id): """Read the ORF sequence, mutation list, and codon table.""" - orf_seq = Seq(Path(ORF_FILE).read_text().strip()) - mutation_list = pd.read_csv(MUTATION_LIST_FILE) - codon_table = pd.read_csv(CODON_TABLE_FILE) - return orf_seq, mutation_list, codon_table + orf_seq = Seq(Path(orf_file).read_text().strip()) + mutation_list = pd.read_csv(mutation_list_file) + #codon_table = CodonTable.unambiguous_dna_by_id[codon_table_file_id].tolist() #pd.read_csv(codon_table_file) + return orf_seq, mutation_list #, codon_table def validate_position(amino_acid_pos, protein_len): @@ -97,7 +102,7 @@ def validate_mutations(mutation_list, orf_seq): sys.exit(f'ERROR: at null value in csv file. Format the csv file.') -def design_primers(orf_seq, mutations, codon_table): +def design_primers(orf_seq, mutations, codon_table_id): """Main primer design function.""" primers = [] @@ -105,15 +110,18 @@ def design_primers(orf_seq, mutations, codon_table): pos = int(re.search(r'\d+', mutation).group()) - 1 validate_position(pos + 1, len(orf_seq) // 3) + # Get all possible translation table combinations for the aa resiudes in question original_aa, target_aa = mutation[0], mutation[-1] - codons = codon_table[codon_table['SingleLetter'] == target_aa]['Codon'].tolist() + codon_table = CodonTable.unambiguous_dna_by_id[int(codon_table_id)].forward_table + codons = [key for key, value in codon_table.items() if value == target_aa] + #codons = codon_table[codon_table['SingleLetter'] == target_aa]['Codon'].tolist() new_codon = find_optimal_codon(orf_seq, pos, codons) mutated_seq = orf_seq[:pos * 3] + new_codon + orf_seq[(pos + 1) * 3:] primer = extract_primer(mutated_seq, pos * 3) - tm = int(math.ceil(calcTm(str(primer), dv_conc=2, tm_method='santalucia', salt_corrections_method='owczarzy'))) + tm = int(math.ceil(calc_tm(str(primer), dv_conc=2, tm_method='santalucia', salt_corrections_method='owczarzy'))) - primers.append((mutation, primer, tm, int(SeqUtils.GC(primer)), len(primer))) + primers.append((mutation, primer, tm, int(SeqUtils.gc_fraction(primer)*100.0), len(primer))) #SeqUtils.GC is deprecated after Biopython 1.82 return pd.DataFrame(primers, columns=['Name', 'Sequence', 'Tm', 'GC', 'Length']) @@ -146,7 +154,7 @@ def create_primer_order_file(primers): return pd.DataFrame(primer_order, columns=['Name', 'Sequence', 'Tm', 'GC', 'Length']) -def separate_primers_by_type(primers): +def separate_primers_by_type(primers, out_path_fwd, out_path_rev): """Separate forward and reverse primers into different CSV files.""" # Create forward and reverse primer DataFrames safely @@ -161,8 +169,8 @@ def separate_primers_by_type(primers): rev_primers.loc[:, 'Well'] = wells[:len(rev_primers)] # Save to CSV files - fwd_primers.to_csv(Output_Path_Fwd, index=False) - rev_primers.to_csv(Output_Path_Rev, index=False) + fwd_primers.to_csv(out_path_fwd, index=False) + rev_primers.to_csv(out_path_rev, index=False) print("\nPrimers separated for IDT order in 96-well plate.") @@ -176,20 +184,44 @@ def Validate_primer_length(primer_length): if __name__ == '__main__': start_time = time.time() - remind_user_to_check_constants() + + parser = argparse.ArgumentParser() + # MUTATION_LIST_FILE = 'Primer_Design/HMT_Plate2.csv' + parser.add_argument('-m', '--Mutation_List', default='Primer_Design/HMT_Plate2.csv', help="List of Mutation Names mapped to well positions") + # 'Primer_Design/Primers_HMT_Plate2' + parser.add_argument('-o', '--Output_Directory', default='Primer_Design/Primers_HMT_Plate2', help="Output directory") + # 'HMT_Designed_primers.csv' + parser.add_argument('-f', '--Primer_Output_File', default='HMT_Designed_primers.csv', help="Output file for primers assoc. characteristic data") + # 'HMT_Forward_Primers_Plate2.csv' + parser.add_argument('-fwd', '--Forward_Primers_File', default='HMT_Forward_Primers_Plate2.csv', help="Output file for Forward primers") + # Reverse_Primers_FILE = 'HMT_Reverse_Primers_Plate2.csv + parser.add_argument('-rev', '--Reverse_Primers_File', default='HMT_Reverse_Primers_Plate2.csv', help="Output file for Reverse primers") + + # CODON_TABLE_FILE = 'Primer_Design/Codon_Table_Standard.csv' + #parser.add_argument('-c', '--Codon_Table_File', default='Primer_Design/Codon_Table_Standard.csv', help="Codon Translation Table Mapping File") + + parser.add_argument('-c','--NCBI_Codon_Table_Value', default=1,help="NCBI Codon Translation Table ID Value") + # ORF_FILE = 'Primer_Design/HMT.txt' + parser.add_argument('-orf', '--ORF_File', default='Primer_Design/HMT.txt', help="File containing Open Reading Frame Sequence") + + args = parser.parse_args() + + out_path, path_fwd, path_rev = process_outputs(args.Output_Directory,args.Primer_Output_File,args.Forward_Primers_File,args.Reverse_Primers_File) + + remind_user_to_check_constants(args.Mutation_List,args.Output_Directory,args.ORF_File,args.NCBI_Codon_Table_Value)#args.Codon_Table_File) print(f'Working Directory: {os.getcwd()} \nProcessing...') - orf_seq, mutations, codon_table = read_orf_and_mutation_list() + orf_seq, mutations = read_orf_and_mutation_list(args.ORF_File,args.Mutation_List,args.NCBI_Codon_Table_Value) find_repeated_kmers(orf_seq) #check if provided mutations align with translation validate_mutations(mutations['Mutations'].tolist(), orf_seq) - primers = design_primers(orf_seq, mutations, codon_table) + primers = design_primers(orf_seq, mutations, args.NCBI_Codon_Table_Value) Validate_primer_length(primers["Length"].tolist()) primer_order = create_primer_order_file(primers) - primer_order.to_csv(Output_Path, index=False) + primer_order.to_csv(out_path, index=False) - separate_primers_by_type(primer_order) + separate_primers_by_type(primer_order,path_fwd,path_rev) print(f"\nFinished in {time.time() - start_time:.6f} seconds.") diff --git a/README.md b/README.md index 530fb95..86c54ac 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,13 @@ This repository contains a set of Python scripts designed for efficient primer d - Python 3.x - Libraries: `pandas`, `itertools`, `collections`, `pathlib`, `os`, `sys`, `time` +### Setup and Package Versions +We recommend using a python virtual environment or conda environment to manage python package versions + +To setup with the appropriate versions, run the command: + +#### pip install requirements.txt + ### Reference
If you use this tool, please cite us: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7afc736 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +biopython==1.83 +numpy==1.24.4 +pandas==2.0.3 +primer3-py==2.2.0 +python-dateutil==2.9.0.post0 +pytz==2025.2 +six==1.17.0 +tzdata==2025.2