-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBaseCap.py
More file actions
executable file
·210 lines (169 loc) · 7.27 KB
/
Copy pathBaseCap.py
File metadata and controls
executable file
·210 lines (169 loc) · 7.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: camilla eldridge
"""
''' Trims low qc bases, primers(if found) and output trimmed sequence and its '.qual file '''
import regex
import os
import sys
from typing import List, Tuple, Optional
def reverse_comp(seq: str) -> str:
""" Return reverse complement """
result = ""
seq = seq.lower()
comp = seq.replace("a", "T").replace("t", "A").replace("c", "G").replace("g", "C").lower()
result = "".join(comp[::-1])
return result
def find_variable_seq(sequence: str, primer: str, error_n: int) -> Optional[regex.Match]:
""" Return variable string match allowing n errors (del &/or subst) """
p = str("(" + primer.lower() + ")" + "{e<=" + str(error_n) + "}")
q = regex.search(p, sequence.lower())
return q
def index_phd(phd: str) -> str:
""" Index phd file - adds column of base pos """
indexed = ""
phd = filter(None, phd.split("\n"))
for x, value in enumerate(phd, 1):
indexed = indexed + str(x) + " " + str(value) + "\n"
return indexed
class Main(object):
""" trim low quality bases and primers from .Phd file, output qual and trimmed sequence """
def __init__(self, phd_file: str, ID: str, primers: str, threshold: int) -> None:
self.phd_file = phd_file
self.ID = ID
self.primers = primers
self.threshold = threshold
self.sequence: str = ""
self.trim_pos: str = ""
self.check: int = 1
self.trim_start: int = 0
self.trim_stop: int = 0
self.start: int = 0
self.stop: int = 0
self.primer_pos: Optional[regex.Match] = None
self.search: Optional[regex.Match] = None
self.primz: List[str] = []
self.trimmed_seq: str = ""
self.trimmed_phd: List[str] = []
self.seq_cap: str = ""
self.scores: str = ""
self.phd2: str = ""
def phd_to_sequence(self) -> str:
""" Get sequence from phd_file """
with open(self.phd_file) as self.phd1:
self.phd2 = self.phd1.read()
self.phd = index_phd(self.phd2.split("BEGIN_DNA")[1].split("END_DNA")[0].rstrip())
self.sequence = self.sequence + "".join(self.phd.split()[1::4])
return self.sequence
def trim_pos_ttuner(self) -> Tuple[int, int, int]:
""" Find trim positions from base caller ttuner """
self.trim_pos = ""
for line in self.phd2.split("\n"):
if "TRIM:" in line:
self.trim_pos = self.trim_pos + line
self.trim_pos = self.trim_pos.split()[1:3]
self.trim_start = int(self.trim_pos[0])
self.trim_stop = int(self.trim_pos[1])
if self.trim_stop < 0 and self.trim_start < 0:
self.check = 0
else:
self.check = 1
return self.trim_start, self.trim_stop, self.check
def trim_check(self) -> Tuple[int, int]:
""" See if trimming is recommended by ttuner """
if self.trim_start < 0:
self.trim_start = 0
if self.trim_stop < 0:
self.trim_stop = int(len(self.sequence))
return self.trim_start, self.trim_stop
def reverse_primers(self) -> List[str]:
""" Reverse complement both primers """
with open(self.primers) as primerz:
primer_list = primerz.read().split("\n")[1::2]
self.primz = primer_list + [reverse_comp(prim_seq) for prim_seq in primer_list]
return self.primz
def primer_search(self) -> Optional[regex.Match]:
""" Search for each primer (forward and reverse) - expecting one hit """
for p in self.primz:
self.search = find_variable_seq(self.sequence, p, 3)
if self.search is not None:
self.primer_pos = self.search
return self.primer_pos
return None
def locate_primer(self) -> Tuple[int, int]:
""" Locate which primer hit (assuming orient not known beforehand) """
midpoint = int(len(self.sequence)) // 2
if self.primer_pos.span()[0] > midpoint:
self.primer_start = 0
self.primer_stop = int(self.primer_pos.span()[0])
else:
self.primer_start = int(self.primer_pos.span()[1])
self.primer_stop = int(len(self.sequence))
return self.primer_start, self.primer_stop
def compare_trim(self) -> Tuple[int, int]:
""" Compare primer and trim positions """
self.start = max(self.trim_start, self.primer_start)
self.stop = min(self.trim_stop, self.primer_stop)
return self.start, self.stop
def trim_phd_and_seq(self) -> Tuple[str, List[str]]:
""" Trim phd and sequence """
self.trimmed_seq = self.sequence[int(self.start):int(self.stop)]
self.trimmed_phd = self.phd.split("\n")[int(self.start):int(self.stop)]
return self.trimmed_seq, self.trimmed_phd
def cap_the_bases(self) -> Tuple[str, str]:
""" Capitalise the low scoring bases and get scores for qual """
self.trimmed_phd2 = [ele for ele in self.trimmed_phd if ele.strip()]
self.seq_cap = ""
self.scores = ""
for j in self.trimmed_phd2:
parts = j.split()
if len(parts) >= 3:
score = int(parts[2])
base = parts[1]
self.scores += f"{score} "
self.seq_cap += base.upper() if score < self.threshold else base.lower()
return self.seq_cap, self.scores
def write_out(self) -> None:
""" Write out trimmed and capped sequence """
with open(f"{self.ID}.qual", "w") as qual_file:
qual_file.write(f">{self.ID}\n{self.scores}")
with open(f"{self.ID}.fasta", "w") as seq_file:
seq_file.write(f">{self.ID}\n{self.seq_cap}")
def call_methods(self) -> str:
""" Call initial methods """
methods1 = ["phd_to_sequence", "trim_pos_ttuner", "reverse_primers", "primer_search", "trim_check"]
for method in methods1:
getattr(self, method)()
if self.primer_pos is None:
self.start, self.stop = self.trim_start, self.trim_stop
else:
self.locate_primer()
self.compare_trim()
if self.check == 0:
self.trimmed_seq = self.sequence
self.trimmed_phd = self.phd.split("\n")
else:
self.trim_phd_and_seq()
self.cap_the_bases()
self.write_out()
search_output = self.search.group() if self.search else "None"
trimmed_length = len(self.trimmed_seq) if self.check else len(self.sequence)
summary_vars = [
self.ID, self.check,len(self.sequence), trimmed_length,
self.trim_start, self.trim_stop, search_output
]
summary = ",".join(map(str, summary_vars))
return summary
if __name__ == "__main__":
Directory: str = sys.argv[1]
Primers: str = sys.argv[2]
Threshold: int = int(sys.argv[3])
header_list: List[str] = ["ID", "trimmed(0/1)", "read_len", "trimmed_read_len", "trim_start", "trim_stop", "primer_hit"]
print(",".join(header_list))
for filename in os.listdir(Directory):
f = os.path.join(Directory, filename)
ID = str(filename.split(".")[0])
if filename.endswith(".phd.1"):
Z = Main(f, ID, Primers, Threshold)
print(Z.call_methods())