FunVIP 0.3.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- FunVIP-0.3.20.dist-info/LICENSE +674 -0
- FunVIP-0.3.20.dist-info/METADATA +32 -0
- FunVIP-0.3.20.dist-info/RECORD +36 -0
- FunVIP-0.3.20.dist-info/WHEEL +5 -0
- FunVIP-0.3.20.dist-info/entry_points.txt +3 -0
- FunVIP-0.3.20.dist-info/top_level.txt +3 -0
- data/__init__.py +0 -0
- external/BLAST_Windows/bin/cleanup-blastdb-volumes.py +162 -0
- external/__init__.py +0 -0
- src/__init__.py +0 -0
- src/align.py +124 -0
- src/cluster.py +510 -0
- src/command.py +360 -0
- src/concatenate.py +356 -0
- src/dataset.py +716 -0
- src/ext.py +448 -0
- src/hasher.py +98 -0
- src/initialize.py +335 -0
- src/logger.py +69 -0
- src/logics.py +104 -0
- src/modeltest.py +443 -0
- src/ncbi.py +160 -0
- src/opt_generator.py +72 -0
- src/patch.py +261 -0
- src/reporter.py +875 -0
- src/save.py +181 -0
- src/search.py +440 -0
- src/tool.py +309 -0
- src/tree.py +222 -0
- src/tree_interpretation.py +1379 -0
- src/tree_interpretation_pipe.py +679 -0
- src/trim.py +118 -0
- src/validate_input.py +846 -0
- src/validate_option.py +1609 -0
- src/validation.py +38 -0
- src/version.py +337 -0
src/tool.py
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import time, re
|
|
2
|
+
from Bio import SeqIO
|
|
3
|
+
from Bio.Seq import Seq
|
|
4
|
+
|
|
5
|
+
# from .logger import Mes
|
|
6
|
+
import logging
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
import sys, os
|
|
9
|
+
import shutil
|
|
10
|
+
import platform
|
|
11
|
+
from unidecode import unidecode
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Maybe we should move this to /funvip/src/toolbox/ and split these to multiple files
|
|
15
|
+
def initialize_path(path):
|
|
16
|
+
global genus_file
|
|
17
|
+
genus_file = path.genusdb
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def mkdir(path):
|
|
21
|
+
if os.path.exists(path) == False:
|
|
22
|
+
os.makedirs(path)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def union_funinfo_list(funinfo_list1, funinfo_list2):
|
|
26
|
+
hash_list = [x.hash for x in funinfo_list1]
|
|
27
|
+
for funinfo in funinfo_list2:
|
|
28
|
+
if not funinfo.hash in hash_list:
|
|
29
|
+
funinfo_list1.append(funinfo)
|
|
30
|
+
|
|
31
|
+
return funinfo_list1
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# if string is not ascii, automatically solve it with unicode library
|
|
35
|
+
def manage_unicode(string, column="", row=""):
|
|
36
|
+
try:
|
|
37
|
+
string.encode("ascii")
|
|
38
|
+
return string
|
|
39
|
+
except:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
if row != "":
|
|
43
|
+
row_string = f"{row}th row "
|
|
44
|
+
else:
|
|
45
|
+
row_string = ""
|
|
46
|
+
|
|
47
|
+
if column != "":
|
|
48
|
+
column_string = f"in {column} column, "
|
|
49
|
+
else:
|
|
50
|
+
column_string = ""
|
|
51
|
+
|
|
52
|
+
logging.info(
|
|
53
|
+
f"Illegal unicode character found {column_string}{row_string}: {unidecode(string)}. Trying flexible solve"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
return unidecode(string)
|
|
58
|
+
except:
|
|
59
|
+
logging.error(
|
|
60
|
+
f"Flexible solve failed to {string}. Please change the cell with available ascii strings"
|
|
61
|
+
)
|
|
62
|
+
raise Exception
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@lru_cache(maxsize=10000)
|
|
66
|
+
def get_genus_species(
|
|
67
|
+
string,
|
|
68
|
+
endwords=[
|
|
69
|
+
"small",
|
|
70
|
+
"18S",
|
|
71
|
+
"ribosomal",
|
|
72
|
+
"internal",
|
|
73
|
+
"gene",
|
|
74
|
+
"ITS",
|
|
75
|
+
"5.8S",
|
|
76
|
+
"voucher",
|
|
77
|
+
"strain",
|
|
78
|
+
"beta",
|
|
79
|
+
"tubulin",
|
|
80
|
+
],
|
|
81
|
+
genus_list=None,
|
|
82
|
+
):
|
|
83
|
+
return_genus = ""
|
|
84
|
+
return_species = ""
|
|
85
|
+
|
|
86
|
+
# en for enumeratable object (splited string)
|
|
87
|
+
if genus_list is None:
|
|
88
|
+
with open(genus_file, "r") as f:
|
|
89
|
+
genus_list = f.read().splitlines()
|
|
90
|
+
|
|
91
|
+
en = string.replace(" ", "_").split("_")
|
|
92
|
+
|
|
93
|
+
for genus in genus_list:
|
|
94
|
+
for n, i in enumerate(en):
|
|
95
|
+
if i == genus: # or i == genus[0] or i == genus[0]+".":
|
|
96
|
+
return_genus = i
|
|
97
|
+
try:
|
|
98
|
+
if en[n + 2] in ["var", "var.", "f", "f.", "nom", "nom."]:
|
|
99
|
+
try:
|
|
100
|
+
en[n + 3]
|
|
101
|
+
if not (en[n + 3] in endwords):
|
|
102
|
+
return_species = " ".join(
|
|
103
|
+
[en[n + 1], en[n + 2], en[n + 3]]
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
return_species = " ".join([en[n + 1], en[n + 2]])
|
|
107
|
+
except:
|
|
108
|
+
return_species = " ".join([en[n + 1], en[n + 2]])
|
|
109
|
+
|
|
110
|
+
else:
|
|
111
|
+
if en[n + 1] in ["aff", "aff.", "cf", "cf."]:
|
|
112
|
+
try:
|
|
113
|
+
if not (en[n + 2] in endwords):
|
|
114
|
+
return_species = " ".join([en[n + 1], en[n + 2]])
|
|
115
|
+
else:
|
|
116
|
+
return_species = en[n + 1]
|
|
117
|
+
except:
|
|
118
|
+
return_species = en[n + 1]
|
|
119
|
+
|
|
120
|
+
elif en[n + 1] in ["sp", "sp."]:
|
|
121
|
+
try:
|
|
122
|
+
if not (en[n + 2] in endwords):
|
|
123
|
+
try:
|
|
124
|
+
int(en[n + 2])
|
|
125
|
+
return_species = " ".join(
|
|
126
|
+
[en[n + 1], en[n + 2]]
|
|
127
|
+
)
|
|
128
|
+
except:
|
|
129
|
+
return_species = en[n + 1]
|
|
130
|
+
else:
|
|
131
|
+
return_species = en[n + 1]
|
|
132
|
+
except:
|
|
133
|
+
return_species = en[n + 1]
|
|
134
|
+
|
|
135
|
+
else:
|
|
136
|
+
if not (en[n + 1] in endwords):
|
|
137
|
+
return_species = en[n + 1]
|
|
138
|
+
else:
|
|
139
|
+
return_species = "NaN"
|
|
140
|
+
|
|
141
|
+
except:
|
|
142
|
+
try:
|
|
143
|
+
if en[n + 1] in ["sp", "sp.", "aff", "aff."]:
|
|
144
|
+
try:
|
|
145
|
+
en[n + 2]
|
|
146
|
+
if not (en[n + 2] in endwords):
|
|
147
|
+
return_species = " ".join([en[n + 1], en[n + 2]])
|
|
148
|
+
else:
|
|
149
|
+
return_species = en[n + 1]
|
|
150
|
+
except:
|
|
151
|
+
return_species = en[n + 1]
|
|
152
|
+
else:
|
|
153
|
+
if not (en[n + 1] in endwords):
|
|
154
|
+
return_species = en[n + 1]
|
|
155
|
+
else:
|
|
156
|
+
return_species = "NaN"
|
|
157
|
+
except:
|
|
158
|
+
return_species = "NaN"
|
|
159
|
+
|
|
160
|
+
return return_genus, return_species
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# get id from string by regex match
|
|
164
|
+
# return message as error, not print it
|
|
165
|
+
@lru_cache(maxsize=10000)
|
|
166
|
+
def get_id(string, id_list):
|
|
167
|
+
id_set = set()
|
|
168
|
+
id_ = ""
|
|
169
|
+
for regex in id_list:
|
|
170
|
+
if re.search(regex, string):
|
|
171
|
+
id_set.add(re.search(regex, string).group(0))
|
|
172
|
+
# longest match as a best match
|
|
173
|
+
if len(re.search(regex, string).group(0)) > len(id_):
|
|
174
|
+
id_ = re.search(regex, string).group(0)
|
|
175
|
+
else:
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
if len(id_set) >= 2:
|
|
179
|
+
logging.warning(
|
|
180
|
+
f"[Warning] Ambiguous regex match found. {id_} selected among {id_set}"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if id_ == "":
|
|
184
|
+
logging.warning(
|
|
185
|
+
f"[Warning] Cannot find regex match from {string}, using default fasta name"
|
|
186
|
+
)
|
|
187
|
+
id_ = string
|
|
188
|
+
|
|
189
|
+
id_ = str(id_)
|
|
190
|
+
return id_
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# select FI with specific datatype from FI_list
|
|
194
|
+
def select(funinfo_list, datatype):
|
|
195
|
+
tmp_list = []
|
|
196
|
+
for funinfo in funinfo_list:
|
|
197
|
+
if funinfo.datatype == datatype:
|
|
198
|
+
tmp_list.append(funinfo)
|
|
199
|
+
|
|
200
|
+
return tmp_list
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def excel_sheetname_legal(string):
|
|
204
|
+
newick_illegal = ["'", "[", "]", ":", "*", "?", "/", "\\"]
|
|
205
|
+
for i in newick_illegal:
|
|
206
|
+
string = string.replace(i, "")
|
|
207
|
+
|
|
208
|
+
string = string.replace(" ", " ")
|
|
209
|
+
string = string.replace(" ", "_")
|
|
210
|
+
|
|
211
|
+
return string
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# moves newick files generated in tree building process to appropriate location
|
|
215
|
+
def cleanup_tree(path):
|
|
216
|
+
# move result files to each of the folders
|
|
217
|
+
mkdir(f"{path.out_tree}/hash")
|
|
218
|
+
hash_files = [
|
|
219
|
+
f
|
|
220
|
+
for f in os.listdir(f"{path.out_tree}")
|
|
221
|
+
if f.startswith("hash_") and f.endswith(".nwk")
|
|
222
|
+
]
|
|
223
|
+
for file in hash_files:
|
|
224
|
+
try:
|
|
225
|
+
shutil.move(f"{path.out_tree}/{file}", f"{path.out_tree}/hash/{file}")
|
|
226
|
+
except:
|
|
227
|
+
os.remove(f"{path.out_tree}/hash/{file}")
|
|
228
|
+
shutil.move(f"{path.out_tree}/{file}", f"{path.out_tree}/hash/{file}")
|
|
229
|
+
|
|
230
|
+
mkdir(f"{path.out_tree}/original")
|
|
231
|
+
hash_files = [
|
|
232
|
+
f for f in os.listdir(f"{path.out_tree}") if f.endswith("_original.nwk")
|
|
233
|
+
]
|
|
234
|
+
for file in hash_files:
|
|
235
|
+
try:
|
|
236
|
+
shutil.move(f"{path.out_tree}/{file}", f"{path.out_tree}/original/{file}")
|
|
237
|
+
except:
|
|
238
|
+
os.remove(f"{path.out_tree}/original/{file}")
|
|
239
|
+
shutil.move(f"{path.out_tree}/{file}", f"{path.out_tree}/original/{file}")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# moves svg files generated in CAT-V to appropriate location
|
|
243
|
+
def cleanup_tree_image(path):
|
|
244
|
+
# move result files to each of the folders
|
|
245
|
+
mkdir(f"{path.out_tree}/hash")
|
|
246
|
+
hash_files = [
|
|
247
|
+
f
|
|
248
|
+
for f in os.listdir(f"{path.out_tree}")
|
|
249
|
+
if f.startswith("hash_") and f.endswith(".svg")
|
|
250
|
+
]
|
|
251
|
+
for file in hash_files:
|
|
252
|
+
try:
|
|
253
|
+
shutil.move(f"{path.out_tree}/{file}", f"{path.out_tree}/hash/{file}")
|
|
254
|
+
except:
|
|
255
|
+
os.remove(f"{path.out_tree}/hash/{file}")
|
|
256
|
+
shutil.move(f"{path.out_tree}/{file}", f"{path.out_tree}/hash/{file}")
|
|
257
|
+
|
|
258
|
+
mkdir(f"{path.out_tree}/original")
|
|
259
|
+
original_files = [
|
|
260
|
+
f for f in os.listdir(f"{path.out_tree}") if f.endswith("_original.svg")
|
|
261
|
+
]
|
|
262
|
+
for file in original_files:
|
|
263
|
+
try:
|
|
264
|
+
shutil.move(f"{path.out_tree}/{file}", f"{path.out_tree}/original/{file}")
|
|
265
|
+
except:
|
|
266
|
+
os.remove(f"{path.out_tree}/original/{file}")
|
|
267
|
+
shutil.move(f"{path.out_tree}/{file}", f"{path.out_tree}/original/{file}")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# Change step string into numbered step
|
|
271
|
+
def index_step(step):
|
|
272
|
+
# Declaring available steps
|
|
273
|
+
step_list = [
|
|
274
|
+
"setup",
|
|
275
|
+
"search",
|
|
276
|
+
"cluster",
|
|
277
|
+
"align",
|
|
278
|
+
"trim",
|
|
279
|
+
"concatenate",
|
|
280
|
+
"modeltest",
|
|
281
|
+
"tree",
|
|
282
|
+
"visualize",
|
|
283
|
+
"report",
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
return step_list.index(step.lower().strip())
|
|
288
|
+
except:
|
|
289
|
+
logging.error(f"DEVELOPMENTAL ERROR, INVALID STEP {step} USED")
|
|
290
|
+
raise Exception
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def check_avx():
|
|
294
|
+
return platform.machine() == "x86_64" or platform.machine() == "AMD64"
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# Return logging
|
|
298
|
+
def get_level(level):
|
|
299
|
+
if level == 3:
|
|
300
|
+
return logging.DEBUG
|
|
301
|
+
elif level == 2:
|
|
302
|
+
return logging.INFO
|
|
303
|
+
elif level == 1:
|
|
304
|
+
return logging.WARNING
|
|
305
|
+
elif level == 0:
|
|
306
|
+
return logging.ERROR
|
|
307
|
+
else:
|
|
308
|
+
print(f"DEVELOPMENTAL ERROR ON MANAGING VERBOSE LEVEL {level}")
|
|
309
|
+
raise Exception
|
src/tree.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from funvip.src import ext, hasher
|
|
2
|
+
from funvip.src.opt_generator import opt_generator
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
import multiprocessing as mp
|
|
5
|
+
import logging
|
|
6
|
+
import shutil
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def pipe_tree(V, path, opt, model_dict):
|
|
11
|
+
# for tree, use hash dict with genus and species information
|
|
12
|
+
tree_hash_dict = hasher.encode(V.list_FI, newick=True)
|
|
13
|
+
|
|
14
|
+
# remove tree files already exists to prevent error
|
|
15
|
+
try:
|
|
16
|
+
for file in [f for f in os.listdir(path.tmp) if f.endswith(".nwk")]:
|
|
17
|
+
os.remove(f"{path.tmp}/{file}")
|
|
18
|
+
except:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
for file in [f for f in os.listdir(path.out_tree) if f.endswith(".nwk")]:
|
|
23
|
+
os.remove(f"{path.out_tree}/{file}")
|
|
24
|
+
except:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
for file in [
|
|
29
|
+
f for f in os.listdir(f"{path.out_tree}/original/") if f.endswith(".nwk")
|
|
30
|
+
]:
|
|
31
|
+
os.remove(f"{path.out_tree}/original/{file}")
|
|
32
|
+
except:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
for file in [
|
|
37
|
+
f for f in os.listdir(f"{path.out_tree}/hash/") if f.endswith(".nwk")
|
|
38
|
+
]:
|
|
39
|
+
os.remove(f"{path.out_tree}/hash/{file}")
|
|
40
|
+
except:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
fasttree_opt = [] # for multiprocessing on fasttree
|
|
44
|
+
|
|
45
|
+
tree_dataset = deepcopy(V.dict_dataset)
|
|
46
|
+
|
|
47
|
+
# Before drawing tree, finalize datasets
|
|
48
|
+
remove_dataset = []
|
|
49
|
+
for group in tree_dataset:
|
|
50
|
+
for gene in tree_dataset[group]:
|
|
51
|
+
# draw tree only when outgroup sequence exists
|
|
52
|
+
if tree_dataset[group][gene].list_og_FI == 0:
|
|
53
|
+
logging.warning(
|
|
54
|
+
f"Passing tree construction of {group} {gene} dataset because no outgroup available"
|
|
55
|
+
)
|
|
56
|
+
# tree_dataset[group].pop(gene, None)
|
|
57
|
+
remove_dataset.append((group, gene))
|
|
58
|
+
|
|
59
|
+
for group, gene in remove_dataset:
|
|
60
|
+
tree_dataset[group].pop(gene, None)
|
|
61
|
+
|
|
62
|
+
# To indicate single genes for each group
|
|
63
|
+
singlegene_dict = {}
|
|
64
|
+
|
|
65
|
+
# Draw phylogenetic trees for each dataset
|
|
66
|
+
for group in tree_dataset:
|
|
67
|
+
singlegene_flag = 0
|
|
68
|
+
singlegene = ""
|
|
69
|
+
# For single gene, we don't have to draw tree multiple times
|
|
70
|
+
if len(tree_dataset[group].keys()) == 2:
|
|
71
|
+
singlegene_flag = 1
|
|
72
|
+
singlegene = list(set(tree_dataset[group].keys()) - {"concatenated"})[0]
|
|
73
|
+
singlegene_dict[group] = deepcopy(singlegene)
|
|
74
|
+
|
|
75
|
+
for gene in tree_dataset[group]:
|
|
76
|
+
# For single gene, skip concatenated
|
|
77
|
+
if gene == "concatenated" and singlegene_flag == 1:
|
|
78
|
+
logging.info(
|
|
79
|
+
f"Passing tree construction of {group} {gene} because single gene detected. Will copy from concatenated"
|
|
80
|
+
)
|
|
81
|
+
# Else for each gene
|
|
82
|
+
elif gene != "concatenated":
|
|
83
|
+
# If not trimming use this
|
|
84
|
+
if opt.method.tree.lower() == "raxml":
|
|
85
|
+
ext.RAxML(
|
|
86
|
+
fasta=f"{path.out_alignment}/{opt.runname}_trimmed_{group}_{gene}.fasta",
|
|
87
|
+
out=f"{opt.runname}_{group}_{gene}.nwk",
|
|
88
|
+
hash_dict=tree_hash_dict,
|
|
89
|
+
path=path,
|
|
90
|
+
thread=opt.thread,
|
|
91
|
+
bootstrap=opt.bootstrap,
|
|
92
|
+
model=model_dict[group][gene],
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
elif opt.method.tree.lower() == "iqtree":
|
|
96
|
+
ext.IQTREE(
|
|
97
|
+
fasta=f"{path.out_alignment}/{opt.runname}_trimmed_{group}_{gene}.fasta",
|
|
98
|
+
out=f"{opt.runname}_{group}_{gene}.nwk",
|
|
99
|
+
hash_dict=tree_hash_dict,
|
|
100
|
+
path=path,
|
|
101
|
+
memory=opt.memory,
|
|
102
|
+
thread=opt.thread,
|
|
103
|
+
bootstrap=opt.bootstrap,
|
|
104
|
+
model=model_dict[group][gene],
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# if fasttree, append to opt to perform multiprocessing by each tree
|
|
108
|
+
else:
|
|
109
|
+
if not (opt.method.tree.lower() == "fasttree"):
|
|
110
|
+
logging.warning(
|
|
111
|
+
"Tree construction method not selected, working for default option, FastTree"
|
|
112
|
+
)
|
|
113
|
+
fasttree_opt.append(
|
|
114
|
+
(
|
|
115
|
+
f"{path.out_alignment}/{opt.runname}_trimmed_{group}_{gene}.fasta",
|
|
116
|
+
f"{opt.runname}_{group}_{gene}.nwk",
|
|
117
|
+
tree_hash_dict,
|
|
118
|
+
path,
|
|
119
|
+
model_dict[group][gene],
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
# For concatenated datasets, use partition files
|
|
123
|
+
else:
|
|
124
|
+
if opt.method.tree.lower() == "raxml":
|
|
125
|
+
ext.RAxML(
|
|
126
|
+
fasta=f"{path.out_alignment}/{opt.runname}_trimmed_{group}_{gene}.fasta",
|
|
127
|
+
out=f"{opt.runname}_{group}_{gene}.nwk",
|
|
128
|
+
hash_dict=tree_hash_dict,
|
|
129
|
+
path=path,
|
|
130
|
+
thread=opt.thread,
|
|
131
|
+
bootstrap=opt.bootstrap,
|
|
132
|
+
partition=f"{path.out_alignment}/{opt.runname}_{group}.partition",
|
|
133
|
+
model=model_dict[group][gene],
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
elif opt.method.tree.lower() == "iqtree":
|
|
137
|
+
ext.IQTREE(
|
|
138
|
+
fasta=f"{path.out_alignment}/{opt.runname}_trimmed_{group}_{gene}.fasta",
|
|
139
|
+
out=f"{opt.runname}_{group}_{gene}.nwk",
|
|
140
|
+
hash_dict=tree_hash_dict,
|
|
141
|
+
path=path,
|
|
142
|
+
memory=opt.memory,
|
|
143
|
+
thread=opt.thread,
|
|
144
|
+
bootstrap=opt.bootstrap,
|
|
145
|
+
partition=f"{path.out_alignment}/{opt.runname}_{group}.partition",
|
|
146
|
+
model=model_dict[group][gene],
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
else:
|
|
150
|
+
if not (opt.method.tree.lower() == "fasttree"):
|
|
151
|
+
logging.warning(
|
|
152
|
+
"Tree method not selected, working for default option, FastTree"
|
|
153
|
+
)
|
|
154
|
+
fasttree_opt.append(
|
|
155
|
+
(
|
|
156
|
+
f"{path.out_alignment}/{opt.runname}_trimmed_{group}_{gene}.fasta",
|
|
157
|
+
f"{opt.runname}_{group}_{gene}.nwk",
|
|
158
|
+
tree_hash_dict,
|
|
159
|
+
path,
|
|
160
|
+
model_dict[group][gene],
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# for fasttree, perform multiprocessing
|
|
165
|
+
if opt.method.tree.lower() == "fasttree":
|
|
166
|
+
# run multiprocessing start
|
|
167
|
+
if opt.verbose < 3:
|
|
168
|
+
p = mp.Pool(opt.thread)
|
|
169
|
+
fasttree_result = p.starmap(ext.FastTree, fasttree_opt)
|
|
170
|
+
p.close()
|
|
171
|
+
p.join()
|
|
172
|
+
|
|
173
|
+
else:
|
|
174
|
+
# non-multithreading mode for debugging
|
|
175
|
+
fasttree_result = []
|
|
176
|
+
for option in fasttree_opt:
|
|
177
|
+
fasttree_result.append(ext.FastTree(*option))
|
|
178
|
+
|
|
179
|
+
for group in singlegene_dict:
|
|
180
|
+
# copy concatenated result to single gene
|
|
181
|
+
shutil.copy(
|
|
182
|
+
f"{path.out_tree}/{opt.runname}_{group}_{singlegene_dict[group]}.nwk",
|
|
183
|
+
f"{path.out_tree}/{opt.runname}_{group}_concatenated.nwk",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
shutil.copy(
|
|
187
|
+
f"{path.out_tree}/hash_{opt.runname}_{group}_{singlegene_dict[group]}.nwk",
|
|
188
|
+
f"{path.out_tree}/hash_{opt.runname}_{group}_concatenated.nwk",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
## Decode alignments and trimmed alignments for each gene after building tree
|
|
192
|
+
# If the code has been mature enough, move this to right after modeltest
|
|
193
|
+
for group in tree_dataset:
|
|
194
|
+
for gene in tree_dataset[group]:
|
|
195
|
+
try:
|
|
196
|
+
if gene != "concatenated":
|
|
197
|
+
os.rename(
|
|
198
|
+
f"{path.out_alignment}/{opt.runname}_MAFFT_{group}_{gene}.fasta",
|
|
199
|
+
f"{path.out_alignment}/hash/{opt.runname}_hash_MAFFT_{group}_{gene}.fasta",
|
|
200
|
+
)
|
|
201
|
+
hasher.decode(
|
|
202
|
+
tree_hash_dict,
|
|
203
|
+
f"{path.out_alignment}/hash/{opt.runname}_hash_MAFFT_{group}_{gene}.fasta",
|
|
204
|
+
f"{path.out_alignment}/{opt.runname}_MAFFT_{group}_{gene}.fasta",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
os.rename(
|
|
208
|
+
f"{path.out_alignment}/{opt.runname}_trimmed_{group}_{gene}.fasta",
|
|
209
|
+
f"{path.out_alignment}/hash/{opt.runname}_hash_trimmed_{group}_{gene}.fasta",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
hasher.decode(
|
|
213
|
+
tree_hash_dict,
|
|
214
|
+
f"{path.out_alignment}/hash/{opt.runname}_hash_trimmed_{group}_{gene}.fasta",
|
|
215
|
+
f"{path.out_alignment}/{opt.runname}_trimmed_{group}_{gene}.fasta",
|
|
216
|
+
)
|
|
217
|
+
except:
|
|
218
|
+
logging.warning(
|
|
219
|
+
f"Tried decoding alignments of {group} {gene} but failed"
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return V, path, opt
|