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
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
# Performing multiple tree interpretation
|
|
2
|
+
from ete3 import Tree
|
|
3
|
+
from funvip.src import tree_interpretation
|
|
4
|
+
from funvip.src.tool import initialize_path, get_genus_species
|
|
5
|
+
from funvip.src.hasher import encode, decode
|
|
6
|
+
from funvip.src.reporter import Singlereport
|
|
7
|
+
from copy import deepcopy
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import re
|
|
10
|
+
import sys, os
|
|
11
|
+
import shutil
|
|
12
|
+
import logging
|
|
13
|
+
import multiprocessing as mp
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### For single dataset
|
|
17
|
+
# Input : out, group, gene, V, path, opt
|
|
18
|
+
def pipe_module_tree_interpretation(
|
|
19
|
+
out,
|
|
20
|
+
group,
|
|
21
|
+
gene,
|
|
22
|
+
V,
|
|
23
|
+
path,
|
|
24
|
+
opt,
|
|
25
|
+
):
|
|
26
|
+
# To reduce memory usage in multithreaded performance, copy necessary objects and then remove V
|
|
27
|
+
funinfo_dict = V.dict_hash_FI
|
|
28
|
+
funinfo_list = V.list_FI
|
|
29
|
+
hash_dict = V.dict_hash_name
|
|
30
|
+
query_list = V.dict_dataset[group][gene].list_qr_FI
|
|
31
|
+
outgroup = V.dict_dataset[group][gene].list_og_FI
|
|
32
|
+
partition = V.partition[group]
|
|
33
|
+
|
|
34
|
+
# for unexpectively included sequence during clustering
|
|
35
|
+
db_list = list(
|
|
36
|
+
set([FI for FI in V.list_FI if FI.datatype == "db"])
|
|
37
|
+
- set(outgroup)
|
|
38
|
+
- set(query_list)
|
|
39
|
+
)
|
|
40
|
+
genus_list = V.tup_genus
|
|
41
|
+
|
|
42
|
+
del V
|
|
43
|
+
|
|
44
|
+
# For get_genus_species
|
|
45
|
+
initialize_path(path)
|
|
46
|
+
|
|
47
|
+
# Tree name selection for tree construction software
|
|
48
|
+
tree_name = f"{path.out_tree}/hash/hash_{opt.runname}_{group}_{gene}.nwk"
|
|
49
|
+
logging.debug(f"{tree_name} entered tree interpretation")
|
|
50
|
+
|
|
51
|
+
# Check validity of file while importing
|
|
52
|
+
if os.path.isfile(tree_name):
|
|
53
|
+
try:
|
|
54
|
+
# If iqtree, missing supports are not shown
|
|
55
|
+
if opt.method.tree.lower() == "iqtree":
|
|
56
|
+
Tree(tree_name, format=0)
|
|
57
|
+
else:
|
|
58
|
+
Tree(tree_name, format=2)
|
|
59
|
+
except:
|
|
60
|
+
logging.error(f"[DEVELOPMENTAL ERROR] Failed on importing tree {tree_name}")
|
|
61
|
+
raise Exception
|
|
62
|
+
else:
|
|
63
|
+
logging.error(f"Cannot find {tree_name}")
|
|
64
|
+
raise Exception
|
|
65
|
+
|
|
66
|
+
# initialize before analysis
|
|
67
|
+
Tree_style = tree_interpretation.Tree_style()
|
|
68
|
+
|
|
69
|
+
# Read tree
|
|
70
|
+
tree_info = tree_interpretation.Tree_information(
|
|
71
|
+
tree_name, Tree_style, group, gene, opt
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Give necessary variables parsed from dataset
|
|
75
|
+
tree_info.db_list = db_list
|
|
76
|
+
tree_info.query_list = query_list
|
|
77
|
+
tree_info.outgroup = outgroup
|
|
78
|
+
tree_info.funinfo_dict = funinfo_dict
|
|
79
|
+
|
|
80
|
+
# Main phase
|
|
81
|
+
# calculate zero distance with alignment
|
|
82
|
+
if gene == "concatenated":
|
|
83
|
+
tree_info.calculate_zero(
|
|
84
|
+
alignment_file=f"{path.out_alignment}/hash/{opt.runname}_hash_trimmed_{group}_{gene}.fasta",
|
|
85
|
+
gene=gene,
|
|
86
|
+
partition_dict=partition,
|
|
87
|
+
)
|
|
88
|
+
else:
|
|
89
|
+
tree_info.calculate_zero(
|
|
90
|
+
alignment_file=f"{path.out_alignment}/hash/{opt.runname}_hash_trimmed_{group}_{gene}.fasta",
|
|
91
|
+
gene=gene,
|
|
92
|
+
partition_dict=None,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Reroot outgroup and save original tree into image
|
|
96
|
+
tree_info.reroot_outgroup(
|
|
97
|
+
f"{path.out_tree}/hash_{opt.runname}_{group}_{gene}_original.svg"
|
|
98
|
+
)
|
|
99
|
+
# Decode hash of image
|
|
100
|
+
# Should work more on non-safe characters
|
|
101
|
+
tree_hash_dict = encode(funinfo_list, newick=True)
|
|
102
|
+
|
|
103
|
+
decode(
|
|
104
|
+
tree_hash_dict,
|
|
105
|
+
f"{path.out_tree}/hash_{opt.runname}_{group}_{gene}_original.svg",
|
|
106
|
+
f"{path.out_tree}/{opt.runname}_{group}_{gene}_original.svg",
|
|
107
|
+
newick=True,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# In validation mode, use original sp. number
|
|
111
|
+
if opt.mode == "validation":
|
|
112
|
+
tree_info.reserve_sp()
|
|
113
|
+
|
|
114
|
+
# Reconstruct flat branches if option given
|
|
115
|
+
if opt.solveflat is True:
|
|
116
|
+
tree_info.t = tree_info.reconstruct(
|
|
117
|
+
clade=tree_info.t.copy("newick"), gene=gene, opt=opt
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# reorder tree for pretty look
|
|
121
|
+
tree_info.t.ladderize(direction=1)
|
|
122
|
+
|
|
123
|
+
# save current status into save version of tree
|
|
124
|
+
tree_info.t_publish = deepcopy(tree_info.t)
|
|
125
|
+
|
|
126
|
+
# Search tree and delimitate species
|
|
127
|
+
tree_info.tree_search(tree_info.t, gene)
|
|
128
|
+
|
|
129
|
+
# Move original newick and replace with adjusted ones
|
|
130
|
+
shutil.move(
|
|
131
|
+
f"{path.out_tree}/{opt.runname}_{group}_{gene}.nwk",
|
|
132
|
+
f"{path.out_tree}/{opt.runname}_{group}_{gene}_original.nwk",
|
|
133
|
+
)
|
|
134
|
+
tree_info.t.write(
|
|
135
|
+
format=0, outfile=f"{path.out_tree}/{opt.runname}_{group}_{gene}.nwk"
|
|
136
|
+
)
|
|
137
|
+
decode(
|
|
138
|
+
tree_hash_dict,
|
|
139
|
+
f"{path.out_tree}/{opt.runname}_{group}_{gene}.nwk",
|
|
140
|
+
f"{path.out_tree}/{opt.runname}_{group}_{gene}.nwk",
|
|
141
|
+
newick=True,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return tree_info
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
### synchronize sp. numbers from multiple dataset
|
|
148
|
+
# to use continuous sp numbers over trees
|
|
149
|
+
# Seperated from multithreading, because this step should traverse over multiple trees, therefore cannot be done simultaniously
|
|
150
|
+
def synchronize(V, path, tree_info_list):
|
|
151
|
+
# Gets hash dict, and returns taxon name of hash_dict
|
|
152
|
+
# Generate final taxon name for synchronizing
|
|
153
|
+
def get_new_taxon(hash_list, hash_taxon_dict):
|
|
154
|
+
# Get candidate taxons from hash_taxon_dict
|
|
155
|
+
taxon_candidates = set()
|
|
156
|
+
for _hash in hash_list:
|
|
157
|
+
if _hash in hash_taxon_dict:
|
|
158
|
+
taxon_candidates.add(hash_taxon_dict[_hash])
|
|
159
|
+
else:
|
|
160
|
+
logging.debug(
|
|
161
|
+
f"{_hash} does not seems to be analyzed from concatenated dataset"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
list_taxon_candidates = sorted(list(taxon_candidates))
|
|
165
|
+
|
|
166
|
+
# Merge genus
|
|
167
|
+
genus = "/".join(sorted(list(set(t[0][0] for t in list_taxon_candidates))))
|
|
168
|
+
|
|
169
|
+
# Merge species
|
|
170
|
+
species_list = []
|
|
171
|
+
|
|
172
|
+
clade_cnt_set = set()
|
|
173
|
+
for t in list_taxon_candidates:
|
|
174
|
+
# If the taxon is unique
|
|
175
|
+
if not (t[0], 2) in hash_taxon_dict.values():
|
|
176
|
+
species_list.append(t[0][1])
|
|
177
|
+
else:
|
|
178
|
+
species_list.append(f"{t[0][1]} {t[1]}")
|
|
179
|
+
clade_cnt_set.add(t[1])
|
|
180
|
+
|
|
181
|
+
# Work with species with numbers
|
|
182
|
+
dict_species = {}
|
|
183
|
+
for s in species_list:
|
|
184
|
+
splited_species = s.split(" ")
|
|
185
|
+
try:
|
|
186
|
+
# Collect with numbers
|
|
187
|
+
int(splited_species[-1])
|
|
188
|
+
if not (" ".join(splited_species[:-1]) in dict_species):
|
|
189
|
+
dict_species[" ".join(splited_species[:-1])] = [
|
|
190
|
+
int(splited_species[-1])
|
|
191
|
+
]
|
|
192
|
+
else:
|
|
193
|
+
dict_species[" ".join(splited_species[:-1])].append(
|
|
194
|
+
int(splited_species[-1])
|
|
195
|
+
)
|
|
196
|
+
except:
|
|
197
|
+
dict_species[s] = [0]
|
|
198
|
+
|
|
199
|
+
species = ""
|
|
200
|
+
|
|
201
|
+
for key in sorted(list(dict_species.keys())):
|
|
202
|
+
if len(set(dict_species[key]) - set([0])) == 0:
|
|
203
|
+
species += key
|
|
204
|
+
species += "/"
|
|
205
|
+
else:
|
|
206
|
+
species_numbers = [
|
|
207
|
+
str(x) for x in sorted(list(set(dict_species[key]) - set([0])))
|
|
208
|
+
]
|
|
209
|
+
species += key
|
|
210
|
+
species += " "
|
|
211
|
+
species += "/".join(species_numbers)
|
|
212
|
+
|
|
213
|
+
# Remove last slash
|
|
214
|
+
if species.endswith("/"):
|
|
215
|
+
species = species[:-1]
|
|
216
|
+
|
|
217
|
+
if len(clade_cnt_set) == 1:
|
|
218
|
+
clade_cnt = list(clade_cnt_set)[0]
|
|
219
|
+
else:
|
|
220
|
+
clade_cnt = 0
|
|
221
|
+
|
|
222
|
+
return (genus, species), clade_cnt
|
|
223
|
+
### End of get_new_taxon
|
|
224
|
+
|
|
225
|
+
## Initialize
|
|
226
|
+
# get available groups per genus
|
|
227
|
+
tree_info_dict = {}
|
|
228
|
+
# hash : corresponding taxon
|
|
229
|
+
hash_taxon_dict = {}
|
|
230
|
+
# genus : cnt, counting sp. numbers
|
|
231
|
+
sp_cnt_dict = {}
|
|
232
|
+
|
|
233
|
+
# To synchronize sp. number by genus, generate by-group dataset
|
|
234
|
+
for tree_info in tree_info_list:
|
|
235
|
+
if not (tree_info.group in tree_info_dict):
|
|
236
|
+
tree_info_dict[tree_info.group] = {tree_info.gene: tree_info}
|
|
237
|
+
elif not (tree_info.gene in tree_info_dict[tree_info.group]):
|
|
238
|
+
tree_info_dict[tree_info.group][tree_info.gene] = tree_info
|
|
239
|
+
else:
|
|
240
|
+
logging.error("DEVELOPMENTAL ERROR, DUPLICATED TREE_INFO")
|
|
241
|
+
raise Exception
|
|
242
|
+
|
|
243
|
+
# Memoize iterative calling
|
|
244
|
+
# For each group list
|
|
245
|
+
valid_hash_dict = {}
|
|
246
|
+
# DB
|
|
247
|
+
for group in tree_info_dict:
|
|
248
|
+
valid_hash_dict[group] = [
|
|
249
|
+
_hash
|
|
250
|
+
for _hash in V.dict_hash_FI
|
|
251
|
+
if V.dict_hash_FI[_hash].datatype == "db"
|
|
252
|
+
and V.dict_hash_FI[_hash].adjusted_group == group
|
|
253
|
+
]
|
|
254
|
+
# Query
|
|
255
|
+
query_hash_list = [
|
|
256
|
+
_hash for _hash in V.dict_hash_FI if V.dict_hash_FI[_hash].datatype == "query"
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
## Starting with concatenated
|
|
260
|
+
# In priority, count corresponding group taxa first
|
|
261
|
+
for group in tree_info_dict:
|
|
262
|
+
if "concatenated" in tree_info_dict[group]:
|
|
263
|
+
# Catch concatenated tree
|
|
264
|
+
tree_info = tree_info_dict[group]["concatenated"]
|
|
265
|
+
# Get list of hash in interest
|
|
266
|
+
valid_hash_list = valid_hash_dict[group]
|
|
267
|
+
for taxon in tree_info.collapse_dict:
|
|
268
|
+
# Get all monophyletic clades from tree and make it list
|
|
269
|
+
clade_list = tree_info.collapse_dict[taxon]
|
|
270
|
+
for n, clade in enumerate(clade_list):
|
|
271
|
+
# list of hash in clade
|
|
272
|
+
hash_list = [leaf[0] for leaf in clade.leaf_list]
|
|
273
|
+
# If any of the leaf consisting clade is included to valid_hash_list
|
|
274
|
+
if any(_h in valid_hash_list for _h in hash_list):
|
|
275
|
+
for _hash in hash_list:
|
|
276
|
+
if not (_hash) in hash_taxon_dict:
|
|
277
|
+
if len(clade_list) == 1:
|
|
278
|
+
hash_taxon_dict[_hash] = (taxon, 0)
|
|
279
|
+
else:
|
|
280
|
+
hash_taxon_dict[_hash] = (taxon, n + 1)
|
|
281
|
+
elif _hash in hash_taxon_dict and hash_taxon_dict[
|
|
282
|
+
_hash
|
|
283
|
+
] != (taxon, n):
|
|
284
|
+
logging.debug(
|
|
285
|
+
f"{_hash} collided while putting in hash_taxon_dict"
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
# If leaf consisting with only queries, that won't collide with other groups
|
|
289
|
+
# This is about new species clade
|
|
290
|
+
if all(_h in query_hash_list for _h in hash_list):
|
|
291
|
+
# If this clade is first sp. species for the genus, start counting sp. number
|
|
292
|
+
if not (taxon[0] in sp_cnt_dict):
|
|
293
|
+
sp_cnt_dict[taxon[0]] = 1
|
|
294
|
+
for _hash in hash_list:
|
|
295
|
+
if not (_hash) in hash_taxon_dict:
|
|
296
|
+
hash_taxon_dict[_hash] = (
|
|
297
|
+
(
|
|
298
|
+
taxon[0],
|
|
299
|
+
f"sp. {sp_cnt_dict[taxon[0]]}",
|
|
300
|
+
),
|
|
301
|
+
0,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
elif (
|
|
305
|
+
_hash in hash_taxon_dict
|
|
306
|
+
and hash_taxon_dict[_hash] != taxon
|
|
307
|
+
):
|
|
308
|
+
logging.debug(
|
|
309
|
+
f"{_hash} collided while putting in hash_taxon_dict"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
sp_cnt_dict[taxon[0]] += 1
|
|
313
|
+
|
|
314
|
+
# Next, taxa that doesn't belongs to any of the group
|
|
315
|
+
# concatenated first
|
|
316
|
+
for group in tree_info_dict:
|
|
317
|
+
if "concatenated" in tree_info_dict[group]:
|
|
318
|
+
tree_info = tree_info_dict[group]["concatenated"]
|
|
319
|
+
|
|
320
|
+
all_hash = [
|
|
321
|
+
_hash
|
|
322
|
+
for _hash in V.dict_hash_FI
|
|
323
|
+
if V.dict_hash_FI[_hash].datatype == "db"
|
|
324
|
+
and V.dict_hash_FI[_hash].adjusted_group != group
|
|
325
|
+
]
|
|
326
|
+
|
|
327
|
+
# Get list of hash not in interest
|
|
328
|
+
invalid_hash_list = list(set(all_hash) - set(valid_hash_dict[group]))
|
|
329
|
+
for taxon in tree_info.collapse_dict:
|
|
330
|
+
clade_list = tree_info.collapse_dict[taxon]
|
|
331
|
+
for n, clade in enumerate(clade_list):
|
|
332
|
+
hash_list = [leaf[0] for leaf in clade.leaf_list]
|
|
333
|
+
# If the hash has not been counted in any of the tree,
|
|
334
|
+
if not (any(_h in invalid_hash_list for _h in hash_list)):
|
|
335
|
+
for _hash in hash_list:
|
|
336
|
+
if not (_hash in hash_taxon_dict):
|
|
337
|
+
logging.debug(
|
|
338
|
+
f"New hash: {_hash} {_hash.adjusted_group} from {group}"
|
|
339
|
+
)
|
|
340
|
+
hash_taxon_dict[_hash] = (taxon, n)
|
|
341
|
+
elif _hash in hash_taxon_dict and hash_taxon_dict[
|
|
342
|
+
_hash
|
|
343
|
+
] != (taxon, n):
|
|
344
|
+
logging.debug(
|
|
345
|
+
f"{_hash} collided while putting in hash_taxon_dict. Tried to put {(taxon, n+1)}, but existing {hash_taxon_dict[_hash]}"
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# Then, non-concatenated
|
|
349
|
+
for group in tree_info_dict:
|
|
350
|
+
for gene in tree_info_dict[group]:
|
|
351
|
+
if gene != "concatenated":
|
|
352
|
+
tree_info = tree_info_dict[group]["concatenated"]
|
|
353
|
+
# Get list of hash in interest
|
|
354
|
+
valid_hash_list = valid_hash_dict[group]
|
|
355
|
+
for taxon in tree_info.collapse_dict:
|
|
356
|
+
clade_list = tree_info.collapse_dict[taxon]
|
|
357
|
+
for n, clade in enumerate(clade_list):
|
|
358
|
+
hash_list = [leaf[0] for leaf in clade.leaf_list]
|
|
359
|
+
# If the hash has not been counted in any of the tree,
|
|
360
|
+
if not (any(_h in valid_hash_list for _h in hash_list)):
|
|
361
|
+
for _hash in hash_list:
|
|
362
|
+
if not (_hash in hash_taxon_dict):
|
|
363
|
+
hash_taxon_dict[_hash] = (taxon, n + 1)
|
|
364
|
+
elif _hash in hash_taxon_dict and hash_taxon_dict[
|
|
365
|
+
_hash
|
|
366
|
+
] != (taxon, n + 1):
|
|
367
|
+
logging.debug(
|
|
368
|
+
f"{_hash} collided while putting in hash_taxon_dict. Tried to put {(taxon, n+1)}, but existing {hash_taxon_dict[_hash]}"
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
# print(group, _hash, taxon, n + 1)
|
|
372
|
+
|
|
373
|
+
"""
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
# Now update from concatenated
|
|
377
|
+
# Remove original taxon, and add by clade taxon
|
|
378
|
+
for group in tree_info_dict:
|
|
379
|
+
for gene in tree_info_dict[group]:
|
|
380
|
+
if gene == "concatenated":
|
|
381
|
+
tree_info = tree_info_dict[group][gene]
|
|
382
|
+
# Before taxon, after taxon update list
|
|
383
|
+
remove_list = [] # [taxon1, taxon2, taxon3 ...]
|
|
384
|
+
add_list = {} # [taxon1 : [clade1], taxon2 : [clade2] ...]
|
|
385
|
+
for taxon in tree_info.collapse_dict:
|
|
386
|
+
clade_list = tree_info.collapse_dict[taxon]
|
|
387
|
+
remove_list.append(taxon)
|
|
388
|
+
for clade in clade_list:
|
|
389
|
+
hash_list = [leaf[0] for leaf in clade.leaf_list]
|
|
390
|
+
new_taxon, clade_cnt = get_new_taxon(hash_list, hash_taxon_dict)
|
|
391
|
+
clade.taxon = new_taxon
|
|
392
|
+
clade.clade_cnt = clade_cnt
|
|
393
|
+
if not (new_taxon in add_list):
|
|
394
|
+
add_list[new_taxon] = [clade]
|
|
395
|
+
else:
|
|
396
|
+
add_list[new_taxon].append(clade)
|
|
397
|
+
|
|
398
|
+
# Remove previous taxon
|
|
399
|
+
for taxon in remove_list:
|
|
400
|
+
tree_info.collapse_dict.pop(taxon)
|
|
401
|
+
|
|
402
|
+
# Add synchronized taxon
|
|
403
|
+
for taxon in add_list:
|
|
404
|
+
tree_info.collapse_dict[taxon] = add_list[taxon]
|
|
405
|
+
else:
|
|
406
|
+
tree_info = tree_info_dict[group][gene]
|
|
407
|
+
# Before taxon, after taxon update list
|
|
408
|
+
remove_list = [] # [taxon1, taxon2, taxon3 ...]
|
|
409
|
+
add_list = {} # [taxon1 : [clade1], taxon2 : [clade2] ...]
|
|
410
|
+
for taxon in tree_info.collapse_dict:
|
|
411
|
+
clade_list = tree_info.collapse_dict[taxon]
|
|
412
|
+
remove_list.append(taxon)
|
|
413
|
+
for clade in clade_list:
|
|
414
|
+
hash_list = [leaf[0] for leaf in clade.leaf_list]
|
|
415
|
+
new_taxon, clade_cnt = get_new_taxon(hash_list, hash_taxon_dict)
|
|
416
|
+
clade.taxon = new_taxon
|
|
417
|
+
clade.clade_cnt = clade_cnt
|
|
418
|
+
if not (new_taxon in add_list):
|
|
419
|
+
add_list[new_taxon] = [clade]
|
|
420
|
+
else:
|
|
421
|
+
add_list[new_taxon].append(clade)
|
|
422
|
+
|
|
423
|
+
# Remove previous taxon
|
|
424
|
+
for taxon in remove_list:
|
|
425
|
+
tree_info.collapse_dict.pop(taxon)
|
|
426
|
+
|
|
427
|
+
# Add synchronized taxon
|
|
428
|
+
for taxon in add_list:
|
|
429
|
+
tree_info.collapse_dict[taxon] = add_list[taxon]
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
"""
|
|
433
|
+
|
|
434
|
+
# raise Exception
|
|
435
|
+
|
|
436
|
+
# Return sp number fixed tree_info_list
|
|
437
|
+
return tree_info_list
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
### Visualization after synchronization
|
|
441
|
+
def pipe_module_tree_visualization(
|
|
442
|
+
tree_info,
|
|
443
|
+
V,
|
|
444
|
+
path,
|
|
445
|
+
opt,
|
|
446
|
+
):
|
|
447
|
+
######### Fix collapse_dict.keys()
|
|
448
|
+
|
|
449
|
+
group = tree_info.group
|
|
450
|
+
gene = tree_info.gene
|
|
451
|
+
genus_list = list(V.tup_genus)
|
|
452
|
+
genus_list.append("AMBIGUOUSGENUS")
|
|
453
|
+
genus_list = tuple(genus_list)
|
|
454
|
+
|
|
455
|
+
# Collapse tree branches for visualization
|
|
456
|
+
taxon_string_dict = tree_info.collapse_tree()
|
|
457
|
+
|
|
458
|
+
# print(f"taxon_string_list | {group} {gene}:\n {taxon_string_list}\n")
|
|
459
|
+
|
|
460
|
+
# Polish tree image
|
|
461
|
+
tree_info.polish_image(
|
|
462
|
+
f"{path.out_tree}/{opt.runname}_{group}_{gene}.svg",
|
|
463
|
+
taxon_string_dict,
|
|
464
|
+
genus_list,
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# sort taxon order
|
|
468
|
+
list_taxon_1 = [
|
|
469
|
+
taxon
|
|
470
|
+
for taxon in tree_info.collapse_dict.keys()
|
|
471
|
+
if not (taxon[1].startswith("sp."))
|
|
472
|
+
]
|
|
473
|
+
|
|
474
|
+
list_taxon_2 = [
|
|
475
|
+
taxon for taxon in tree_info.collapse_dict.keys() if taxon[1].startswith("sp.")
|
|
476
|
+
]
|
|
477
|
+
list_taxon_1.sort(key=lambda x: x[1])
|
|
478
|
+
list_taxon_2.sort(key=lambda x: x[1])
|
|
479
|
+
list_taxon = list_taxon_1 + list_taxon_2
|
|
480
|
+
|
|
481
|
+
# Declare report collection
|
|
482
|
+
report_list = []
|
|
483
|
+
for taxon in list_taxon:
|
|
484
|
+
# If only one taxon exists, enumerate does not work properly
|
|
485
|
+
if len(tree_info.collapse_dict[taxon]) <= 1:
|
|
486
|
+
collapse_info = tree_info.collapse_dict[taxon][0]
|
|
487
|
+
# Get each of the leaf result to report
|
|
488
|
+
for leaf in collapse_info.leaf_list:
|
|
489
|
+
report = Singlereport()
|
|
490
|
+
report.id = V.dict_hash_FI[leaf[0]].original_id
|
|
491
|
+
report.hash = V.dict_hash_FI[leaf[0]].hash
|
|
492
|
+
report.update_group(V.dict_hash_FI[leaf[0]].adjusted_group)
|
|
493
|
+
report.update_group_analysis(group)
|
|
494
|
+
report.update_gene(gene)
|
|
495
|
+
report.update_species_original(
|
|
496
|
+
get_genus_species(leaf[2], genus_list=genus_list)
|
|
497
|
+
)
|
|
498
|
+
# joining genus and species
|
|
499
|
+
report.update_species_assigned(" ".join(taxon))
|
|
500
|
+
# report.update_species_assigned(taxon[1])
|
|
501
|
+
report.ambiguous = collapse_info.clade_cnt
|
|
502
|
+
report.flat = collapse_info.flat
|
|
503
|
+
|
|
504
|
+
report_list.append(report)
|
|
505
|
+
|
|
506
|
+
# If more than one taxon exists,
|
|
507
|
+
else:
|
|
508
|
+
for n, collapse_info in enumerate(tree_info.collapse_dict[taxon]):
|
|
509
|
+
for leaf in collapse_info.leaf_list:
|
|
510
|
+
report = Singlereport()
|
|
511
|
+
report.id = V.dict_hash_FI[leaf[0]].original_id
|
|
512
|
+
report.hash = V.dict_hash_FI[leaf[0]].hash
|
|
513
|
+
report.update_group(V.dict_hash_FI[leaf[0]].adjusted_group)
|
|
514
|
+
report.update_group_analysis(group)
|
|
515
|
+
report.update_gene(gene)
|
|
516
|
+
report.update_species_original(
|
|
517
|
+
get_genus_species(leaf[2], genus_list=genus_list)
|
|
518
|
+
)
|
|
519
|
+
report.update_species_assigned((f"{taxon[0]} {taxon[1]} {n+1}"))
|
|
520
|
+
|
|
521
|
+
report.ambiguous = collapse_info.clade_cnt
|
|
522
|
+
report.flat = collapse_info.flat
|
|
523
|
+
|
|
524
|
+
report_list.append(report)
|
|
525
|
+
|
|
526
|
+
return report_list
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
### For all datasets, multiprocessing part
|
|
530
|
+
def pipe_tree_interpretation(V, path, opt):
|
|
531
|
+
# Generate tree_interpretation opt to run
|
|
532
|
+
tree_interpretation_opt = []
|
|
533
|
+
|
|
534
|
+
# Reset bygene_species for rerun
|
|
535
|
+
for key in V.dict_hash_FI: # funinfo_dict
|
|
536
|
+
for gene in V.dict_hash_FI[key].bygene_species:
|
|
537
|
+
V.dict_hash_FI[key].bygene_species[gene] = V.dict_hash_FI[key].ori_species
|
|
538
|
+
|
|
539
|
+
for group in V.dict_dataset:
|
|
540
|
+
for gene in V.dict_dataset[group]:
|
|
541
|
+
logging.debug(f"pipe_tree_interpretation {group} {gene}")
|
|
542
|
+
# Condition 1 : draw all trees
|
|
543
|
+
cond1 = opt.queryonly is False
|
|
544
|
+
# Condition 2 : When query included
|
|
545
|
+
cond2 = len(V.dict_dataset[group][gene].list_qr_FI) > 0
|
|
546
|
+
# Condition 3 : When any of the branches of the tree is valid in concatenated analysis
|
|
547
|
+
cond3 = (len(V.dict_dataset[group]["concatenated"].list_qr_FI) > 0) and any(
|
|
548
|
+
FI.hash
|
|
549
|
+
in [
|
|
550
|
+
x.hash
|
|
551
|
+
for x in V.dict_dataset[group][gene].list_qr_FI
|
|
552
|
+
+ V.dict_dataset[group][gene].list_db_FI
|
|
553
|
+
+ V.dict_dataset[group][gene].list_og_FI
|
|
554
|
+
]
|
|
555
|
+
for FI in V.dict_dataset[group]["concatenated"].list_qr_FI
|
|
556
|
+
+ V.dict_dataset[group]["concatenated"].list_db_FI
|
|
557
|
+
+ V.dict_dataset[group]["concatenated"].list_og_FI
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
# Interpret tree when valid condition
|
|
561
|
+
if cond1 or cond2 or cond3:
|
|
562
|
+
if len(V.dict_dataset[group][gene].list_og_FI) > 0:
|
|
563
|
+
# Generating tree_interpretation opts for multithreading support
|
|
564
|
+
tree_interpretation_opt.append(
|
|
565
|
+
(
|
|
566
|
+
f"{opt.runname}_{group}_{gene}",
|
|
567
|
+
group,
|
|
568
|
+
gene,
|
|
569
|
+
V,
|
|
570
|
+
path,
|
|
571
|
+
opt,
|
|
572
|
+
)
|
|
573
|
+
)
|
|
574
|
+
# However, if outgroup does not exists, warn it
|
|
575
|
+
else:
|
|
576
|
+
logging.warning(
|
|
577
|
+
f"Failed interpreting tree {group} {gene} because no outgroup available"
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
## Tree interpretation - outgroup, reconstruction(solve_flat), collapsing
|
|
581
|
+
if opt.verbose < 3:
|
|
582
|
+
p = mp.Pool(opt.thread)
|
|
583
|
+
tree_info_list = p.starmap(
|
|
584
|
+
pipe_module_tree_interpretation, tree_interpretation_opt
|
|
585
|
+
)
|
|
586
|
+
p.close()
|
|
587
|
+
p.join()
|
|
588
|
+
|
|
589
|
+
else:
|
|
590
|
+
# non-multithreading mode for debugging
|
|
591
|
+
tree_info_list = [
|
|
592
|
+
pipe_module_tree_interpretation(*option)
|
|
593
|
+
for option in tree_interpretation_opt
|
|
594
|
+
]
|
|
595
|
+
|
|
596
|
+
# Gather flat branch issues
|
|
597
|
+
for tree_info in tree_info_list:
|
|
598
|
+
for flat_hash in tree_info.flat_clades:
|
|
599
|
+
FI = V.dict_hash_FI[flat_hash]
|
|
600
|
+
FI.issues.add(f"flat:{tree_info.gene}")
|
|
601
|
+
|
|
602
|
+
synchronized_tree_info_list = synchronize(V, path, tree_info_list)
|
|
603
|
+
tree_info_list = synchronized_tree_info_list
|
|
604
|
+
# Generate visualization option to run
|
|
605
|
+
tree_visualization_opt = []
|
|
606
|
+
for tree_info in tree_info_list:
|
|
607
|
+
tree_visualization_opt.append((tree_info, V, path, opt))
|
|
608
|
+
|
|
609
|
+
## Tree visualization
|
|
610
|
+
if opt.verbose < 3:
|
|
611
|
+
p = mp.Pool(opt.thread)
|
|
612
|
+
tree_visualization_result = p.starmap(
|
|
613
|
+
pipe_module_tree_visualization, tree_visualization_opt
|
|
614
|
+
)
|
|
615
|
+
p.close()
|
|
616
|
+
p.join()
|
|
617
|
+
|
|
618
|
+
else:
|
|
619
|
+
# non-multithreading mode for debugging
|
|
620
|
+
tree_visualization_result = [
|
|
621
|
+
pipe_module_tree_visualization(*option) for option in tree_visualization_opt
|
|
622
|
+
]
|
|
623
|
+
|
|
624
|
+
### Collect identifiation result to V for reporting
|
|
625
|
+
# Merge report list
|
|
626
|
+
all_report_list = [
|
|
627
|
+
x for report_list in tree_visualization_result for x in report_list
|
|
628
|
+
]
|
|
629
|
+
|
|
630
|
+
# hash_dict_analysis to prevent overwrite analysis from other tree
|
|
631
|
+
# hash : group_analysis
|
|
632
|
+
hash_dict_analysis = {}
|
|
633
|
+
|
|
634
|
+
# Add final identification result
|
|
635
|
+
# Concatenated first
|
|
636
|
+
for singlereport in all_report_list:
|
|
637
|
+
FI = V.dict_hash_FI[singlereport.hash]
|
|
638
|
+
# Concatenated
|
|
639
|
+
if singlereport.gene == "concatenated":
|
|
640
|
+
cond = 0
|
|
641
|
+
# If the strain has not been reported
|
|
642
|
+
if not singlereport.hash in hash_dict_analysis:
|
|
643
|
+
cond = 1
|
|
644
|
+
# If the strain has reported, but not in major tree
|
|
645
|
+
else:
|
|
646
|
+
if hash_dict_analysis[singlereport.hash] != singlereport.group:
|
|
647
|
+
cond = 2
|
|
648
|
+
|
|
649
|
+
if cond > 0:
|
|
650
|
+
FI.final_species = singlereport.species_assigned
|
|
651
|
+
FI.species_identifier = singlereport.ambiguous
|
|
652
|
+
|
|
653
|
+
if singlereport.ambiguous > 0:
|
|
654
|
+
FI.issues.add("polyphyly:concatenated")
|
|
655
|
+
|
|
656
|
+
if singlereport.flat is True:
|
|
657
|
+
FI.flat.append("concatenated")
|
|
658
|
+
|
|
659
|
+
hash_dict_analysis[singlereport.hash] = singlereport.group_analysis
|
|
660
|
+
|
|
661
|
+
# Non-concatenated
|
|
662
|
+
for singlereport in all_report_list:
|
|
663
|
+
FI = V.dict_hash_FI[singlereport.hash]
|
|
664
|
+
if singlereport.gene != "concatenated":
|
|
665
|
+
if singlereport.hash in hash_dict_analysis:
|
|
666
|
+
# In each gene tree, follow the group which taxon analyzed from concatenated tree
|
|
667
|
+
if hash_dict_analysis[singlereport.hash] == singlereport.group_analysis:
|
|
668
|
+
FI.bygene_species[singlereport.gene] = singlereport.species_assigned
|
|
669
|
+
|
|
670
|
+
if singlereport.ambiguous > 0:
|
|
671
|
+
FI.issues.add(f"polyphyly:{singlereport.gene}")
|
|
672
|
+
|
|
673
|
+
if singlereport.flat is True:
|
|
674
|
+
FI.flat.append(singlereport.gene)
|
|
675
|
+
else:
|
|
676
|
+
# If not found, FI would be additional database sequences outside the group added for ambiguities.
|
|
677
|
+
pass
|
|
678
|
+
|
|
679
|
+
return V, path, opt
|