annotate tools/align_back_trans/align_back_trans.py @ 0:6c6b16cab42a draft

Uploaded
author jasper
date Tue, 09 May 2017 13:22:02 -0400
parents
children 647866afd876
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
0
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
1 #!/usr/bin/env python
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
2 """Back-translate a protein alignment to nucleotides
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
3
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
4 This tool is a short Python script (using Biopython library functions) to
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
5 load a protein alignment, and matching nucleotide FASTA file of unaligned
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
6 sequences, in order to produce a codon aware nucleotide alignment - which
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
7 can be viewed as a back translation.
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
8
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
9 The development repository for this tool is here:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
10
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
11 * https://github.com/peterjc/pico_galaxy/tree/master/tools/align_back_trans
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
12
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
13 This tool is available with a Galaxy wrapper from the Galaxy Tool Shed at:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
14
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
15 * http://toolshed.g2.bx.psu.edu/view/peterjc/align_back_trans
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
16
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
17 See accompanying text file for licence details (MIT licence).
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
18 """
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
19
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
20 import sys
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
21 from Bio.Seq import Seq
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
22 from Bio.Alphabet import generic_protein
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
23 from Bio.Align import MultipleSeqAlignment
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
24 from Bio import SeqIO
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
25 from Bio import AlignIO
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
26 from Bio.Data.CodonTable import ambiguous_generic_by_id
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
27
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
28 if "-v" in sys.argv or "--version" in sys.argv:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
29 print "v0.0.7"
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
30 sys.exit(0)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
31
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
32
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
33 def check_trans(identifier, nuc, prot, table):
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
34 """Returns nucleotide sequence if works (can remove trailing stop)"""
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
35 if len(nuc) % 3:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
36 sys.exit("Nucleotide sequence for %s is length %i (not a multiple of three)"
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
37 % (identifier, len(nuc)))
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
38
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
39 p = str(prot).upper().replace("*", "X")
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
40 t = str(nuc.translate(table)).upper().replace("*", "X")
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
41 if len(t) == len(p) + 1:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
42 if str(nuc)[-3:].upper() in ambiguous_generic_by_id[table].stop_codons:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
43 # Allow this...
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
44 t = t[:-1]
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
45 nuc = nuc[:-3] # edit return value
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
46 if len(t) != len(p):
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
47 err = ("Inconsistent lengths for %s, ungapped protein %i, "
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
48 "tripled %i vs ungapped nucleotide %i." %
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
49 (identifier, len(p), len(p) * 3, len(nuc)))
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
50 if t.endswith(p):
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
51 err += "\nThere are %i extra nucleotides at the start." % (len(t) - len(p))
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
52 elif t.startswith(p):
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
53 err += "\nThere are %i extra nucleotides at the end." % (len(t) - len(p))
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
54 elif p in t:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
55 # TODO - Calculate and report the number to trim at start and end?
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
56 err += "\nHowever, protein sequence found within translated nucleotides."
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
57 elif p[1:] in t:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
58 err += "\nHowever, ignoring first amino acid, protein sequence found within translated nucleotides."
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
59 sys.exit(err)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
60
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
61 if t == p:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
62 return nuc
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
63 elif p.startswith("M") and "M" + t[1:] == p:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
64 # Close, was there a start codon?
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
65 if str(nuc[0:3]).upper() in ambiguous_generic_by_id[table].start_codons:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
66 return nuc
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
67 else:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
68 sys.exit("Translation check failed for %s\n"
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
69 "Would match if %s was a start codon (check correct table used)\n"
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
70 % (identifier, nuc[0:3].upper()))
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
71 else:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
72 # Allow * vs X here? e.g. internal stop codons
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
73 m = "".join("." if x == y else "!" for (x, y) in zip(p, t))
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
74 if len(prot) < 70:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
75 sys.stderr.write("Protein: %s\n" % p)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
76 sys.stderr.write(" %s\n" % m)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
77 sys.stderr.write("Translation: %s\n" % t)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
78 else:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
79 for offset in range(0, len(p), 60):
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
80 sys.stderr.write("Protein: %s\n" % p[offset:offset + 60])
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
81 sys.stderr.write(" %s\n" % m[offset:offset + 60])
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
82 sys.stderr.write("Translation: %s\n\n" % t[offset:offset + 60])
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
83 sys.exit("Translation check failed for %s\n" % identifier)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
84
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
85
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
86 def sequence_back_translate(aligned_protein_record, unaligned_nucleotide_record, gap, table=0):
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
87 # TODO - Separate arguments for protein gap and nucleotide gap?
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
88 if not gap or len(gap) != 1:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
89 raise ValueError("Please supply a single gap character")
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
90
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
91 alpha = unaligned_nucleotide_record.seq.alphabet
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
92 if hasattr(alpha, "gap_char"):
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
93 gap_codon = alpha.gap_char * 3
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
94 assert len(gap_codon) == 3
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
95 else:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
96 from Bio.Alphabet import Gapped
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
97 alpha = Gapped(alpha, gap)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
98 gap_codon = gap * 3
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
99
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
100 ungapped_protein = aligned_protein_record.seq.ungap(gap)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
101 ungapped_nucleotide = unaligned_nucleotide_record.seq
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
102 if table:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
103 ungapped_nucleotide = check_trans(aligned_protein_record.id, ungapped_nucleotide, ungapped_protein, table)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
104 elif len(ungapped_protein) * 3 != len(ungapped_nucleotide):
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
105 sys.exit("Inconsistent lengths for %s, ungapped protein %i, "
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
106 "tripled %i vs ungapped nucleotide %i" %
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
107 (aligned_protein_record.id,
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
108 len(ungapped_protein),
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
109 len(ungapped_protein) * 3,
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
110 len(ungapped_nucleotide)))
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
111
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
112 seq = []
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
113 nuc = str(ungapped_nucleotide)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
114 for amino_acid in aligned_protein_record.seq:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
115 if amino_acid == gap:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
116 seq.append(gap_codon)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
117 else:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
118 seq.append(nuc[:3])
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
119 nuc = nuc[3:]
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
120 assert not nuc, "Nucleotide sequence for %r longer than protein %r" \
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
121 % (unaligned_nucleotide_record.id, aligned_protein_record.id)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
122
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
123 aligned_nuc = unaligned_nucleotide_record[:] # copy for most annotation
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
124 aligned_nuc.letter_annotation = {} # clear this
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
125 aligned_nuc.seq = Seq("".join(seq), alpha) # replace this
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
126 assert len(aligned_protein_record.seq) * 3 == len(aligned_nuc)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
127 return aligned_nuc
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
128
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
129
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
130 def alignment_back_translate(protein_alignment, nucleotide_records, key_function=None, gap=None, table=0):
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
131 """Thread nucleotide sequences onto a protein alignment."""
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
132 # TODO - Separate arguments for protein and nucleotide gap characters?
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
133 if key_function is None:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
134 key_function = lambda x: x
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
135 if gap is None:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
136 gap = "-"
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
137
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
138 aligned = []
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
139 for protein in protein_alignment:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
140 try:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
141 nucleotide = nucleotide_records[key_function(protein.id)]
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
142 except KeyError:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
143 raise ValueError("Could not find nucleotide sequence for protein %r"
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
144 % protein.id)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
145 aligned.append(sequence_back_translate(protein, nucleotide, gap, table))
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
146 return MultipleSeqAlignment(aligned)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
147
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
148
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
149 if len(sys.argv) == 4:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
150 align_format, prot_align_file, nuc_fasta_file = sys.argv[1:]
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
151 nuc_align_file = sys.stdout
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
152 table = 0
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
153 elif len(sys.argv) == 5:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
154 align_format, prot_align_file, nuc_fasta_file, nuc_align_file = sys.argv[1:]
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
155 table = 0
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
156 elif len(sys.argv) == 6:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
157 align_format, prot_align_file, nuc_fasta_file, nuc_align_file, table = sys.argv[1:]
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
158 else:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
159 sys.exit("""This is a Python script for 'back-translating' a protein alignment,
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
160
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
161 It requires three, four or five arguments:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
162 - alignment format (e.g. fasta, clustal),
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
163 - aligned protein file (in specified format),
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
164 - unaligned nucleotide file (in fasta format).
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
165 - aligned nucleotiode output file (in same format), optional.
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
166 - NCBI translation table (0 for none), optional
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
167
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
168 The nucleotide alignment is printed to stdout if no output filename is given.
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
169
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
170 Example usage:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
171
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
172 $ python align_back_trans.py fasta demo_prot_align.fasta demo_nucs.fasta demo_nuc_align.fasta
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
173
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
174 Warning: If the output file already exists, it will be overwritten.
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
175
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
176 This script is available with sample data and a Galaxy wrapper here:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
177 https://github.com/peterjc/pico_galaxy/tree/master/tools/align_back_trans
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
178 http://toolshed.g2.bx.psu.edu/view/peterjc/align_back_trans
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
179 """)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
180
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
181 try:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
182 table = int(table)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
183 except ValueError:
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
184 sys.exit("Bad table argument %r" % table)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
185
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
186 prot_align = AlignIO.read(prot_align_file, align_format, alphabet=generic_protein)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
187 nuc_dict = SeqIO.index(nuc_fasta_file, "fasta")
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
188 nuc_align = alignment_back_translate(prot_align, nuc_dict, gap="-", table=table)
6c6b16cab42a Uploaded
jasper
parents:
diff changeset
189 AlignIO.write(nuc_align, nuc_align_file, align_format)