Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 70 additions & 38 deletions Primer_Design_SDM.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -97,23 +102,26 @@ 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 = []

for mutation in mutations['Mutations']:
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'])

Expand Down Expand Up @@ -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
Expand All @@ -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.")

Expand All @@ -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.")
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<details>
<summary>If you use this tool, please cite us:</summary>
Expand Down
8 changes: 8 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -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