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/ext.py
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
# for running external programs
|
|
2
|
+
from sys import platform
|
|
3
|
+
from Bio import SeqIO
|
|
4
|
+
import logging
|
|
5
|
+
import os, subprocess
|
|
6
|
+
import shutil
|
|
7
|
+
import psutil
|
|
8
|
+
from copy import deepcopy
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from funvip.src.save import save_tree
|
|
11
|
+
from funvip.src.tool import mkdir
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Search methods
|
|
15
|
+
# BLAST
|
|
16
|
+
def blast(query, db, out, path, opt):
|
|
17
|
+
path_blast = Path(f"{path.sys_path}/external/BLAST_Windows/bin/blastn.exe")
|
|
18
|
+
|
|
19
|
+
# quotations make errors on windows platform when space does not exists
|
|
20
|
+
if platform == "win32":
|
|
21
|
+
if " " in out:
|
|
22
|
+
out = f'"{out}"'
|
|
23
|
+
if " " in query:
|
|
24
|
+
query = f'"{query}"'
|
|
25
|
+
if " " in db:
|
|
26
|
+
db = f'"{db}"'
|
|
27
|
+
|
|
28
|
+
CMD = f"{path_blast} -out {out} -query {query} -outfmt 6 -db {db} -word_size {opt.cluster.wordsize} -evalue {opt.cluster.evalue} -num_threads {opt.thread}"
|
|
29
|
+
else:
|
|
30
|
+
CMD = f"blastn -out '{out}' -query '{query}' -outfmt 6 -db '{db}' -word_size {opt.cluster.wordsize} -evalue {opt.cluster.evalue} -num_threads {opt.thread}"
|
|
31
|
+
|
|
32
|
+
logging.info(CMD)
|
|
33
|
+
Run = subprocess.call(CMD, shell=True)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# mmseqs
|
|
37
|
+
def mmseqs(query, db, out, tmp, path, opt):
|
|
38
|
+
path_mmseqs = f"{path.sys_path}/external/mmseqs_Windows/mmseqs.bat"
|
|
39
|
+
|
|
40
|
+
if platform == "win32":
|
|
41
|
+
if " " in out:
|
|
42
|
+
out = f'"{out}"'
|
|
43
|
+
if " " in query:
|
|
44
|
+
query = f'"{query}"'
|
|
45
|
+
if " " in db:
|
|
46
|
+
db = f'"{db}"'
|
|
47
|
+
if " " in tmp:
|
|
48
|
+
tmp = f'"{tmp}"'
|
|
49
|
+
CMD = f"{path_mmseqs} easy-search {query} {db} {out} {tmp} --threads {opt.thread} -k {opt.cluster.wordsize} --search-type 3 -e {opt.cluster.evalue} --dbtype 2"
|
|
50
|
+
else:
|
|
51
|
+
CMD = f"mmseqs easy-search '{query}' '{db}' '{out}' '{tmp}' --threads {opt.thread} -k {opt.cluster.wordsize} --search-type 3 -e {opt.cluster.evalue} --dbtype 2"
|
|
52
|
+
|
|
53
|
+
logging.info(CMD)
|
|
54
|
+
Run = subprocess.call(CMD, shell=True)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# DB building methods
|
|
58
|
+
def makeblastdb(fasta, db, path):
|
|
59
|
+
path_makeblastdb = f"{path.sys_path}/external/BLAST_Windows/bin/makeblastdb.exe"
|
|
60
|
+
|
|
61
|
+
# To prevent makeblastdb error in windows, run it on temporary directory and move it
|
|
62
|
+
if platform == "win32":
|
|
63
|
+
# Save original path
|
|
64
|
+
ori_path = deepcopy(os.getcwd())
|
|
65
|
+
makeblastdb_path = f"{path.tmp}\\makeblastdb\\"
|
|
66
|
+
# remove temporary path if exists
|
|
67
|
+
if os.path.exists(makeblastdb_path):
|
|
68
|
+
shutil.rmtree(makeblastdb_path)
|
|
69
|
+
# make new temp makeblastdb directory
|
|
70
|
+
mkdir(makeblastdb_path)
|
|
71
|
+
os.chdir(makeblastdb_path)
|
|
72
|
+
|
|
73
|
+
# Move makeblastdb.exe and destination file to temporate directory
|
|
74
|
+
shutil.copy(fasta, makeblastdb_path)
|
|
75
|
+
|
|
76
|
+
# Remove disk seperator to prevent error
|
|
77
|
+
fasta_tmp = fasta.replace("\\", "/").split("/")[-1]
|
|
78
|
+
db_tmp = db.replace("\\", "/").split("/")[-1]
|
|
79
|
+
|
|
80
|
+
# run make blast db
|
|
81
|
+
CMD = f"{path_makeblastdb} -in {fasta_tmp} -blastdb_version 4 -title {db_tmp} -dbtype nucl"
|
|
82
|
+
logging.info(CMD)
|
|
83
|
+
Run = subprocess.call(CMD, shell=True)
|
|
84
|
+
# Change db names
|
|
85
|
+
shutil.move(fasta_tmp + ".nsq", db + ".nsq")
|
|
86
|
+
shutil.move(fasta_tmp + ".nin", db + ".nin")
|
|
87
|
+
shutil.move(fasta_tmp + ".nhr", db + ".nhr")
|
|
88
|
+
# return to original path
|
|
89
|
+
os.chdir(ori_path)
|
|
90
|
+
# remove temporary path
|
|
91
|
+
if os.path.exists(makeblastdb_path):
|
|
92
|
+
shutil.rmtree(makeblastdb_path)
|
|
93
|
+
else:
|
|
94
|
+
CMD = f"makeblastdb -in '{fasta}' -blastdb_version 4 -title '{db}' -dbtype nucl"
|
|
95
|
+
logging.info(CMD)
|
|
96
|
+
return_code = subprocess.call(CMD, shell=True)
|
|
97
|
+
|
|
98
|
+
if return_code != 0:
|
|
99
|
+
logging.error(f"Make blast_db failed!!")
|
|
100
|
+
install_flag = 1
|
|
101
|
+
|
|
102
|
+
# Change db names
|
|
103
|
+
shutil.move(fasta + ".nsq", db + ".nsq")
|
|
104
|
+
shutil.move(fasta + ".nin", db + ".nin")
|
|
105
|
+
shutil.move(fasta + ".nhr", db + ".nhr")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def makemmseqsdb(fasta, db, path):
|
|
109
|
+
path_makemmseqsdb = f"{path.sys_path}/external/mmseqs_Windows/mmseqs.bat"
|
|
110
|
+
|
|
111
|
+
if " " in fasta:
|
|
112
|
+
fasta = f'"{fasta}"'
|
|
113
|
+
|
|
114
|
+
if " " in db:
|
|
115
|
+
db = f'"{db}"'
|
|
116
|
+
|
|
117
|
+
if platform == "win32":
|
|
118
|
+
CMD = f"{path_makemmseqsdb} createdb {fasta} {db} --createdb-mode 0 --dbtype 2"
|
|
119
|
+
else:
|
|
120
|
+
CMD = f"mmseqs createdb '{fasta}' '{db}' --createdb-mode 0 --dbtype 2"
|
|
121
|
+
logging.info(CMD)
|
|
122
|
+
Run = subprocess.call(CMD, shell=True)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# Alignments
|
|
126
|
+
def MAFFT(
|
|
127
|
+
fasta,
|
|
128
|
+
out,
|
|
129
|
+
path,
|
|
130
|
+
thread=1,
|
|
131
|
+
algorithm="localpair",
|
|
132
|
+
adjust="adjustdirection",
|
|
133
|
+
maxiterate=1000,
|
|
134
|
+
op=1.3,
|
|
135
|
+
ep=0.1,
|
|
136
|
+
):
|
|
137
|
+
# validate if there are only 1 sequence
|
|
138
|
+
seqlist = list(SeqIO.parse(fasta, "fasta"))
|
|
139
|
+
if len(seqlist) == 1:
|
|
140
|
+
logging.warning(
|
|
141
|
+
f"{fasta} has only one sequence. Using original sequence as alignment"
|
|
142
|
+
)
|
|
143
|
+
shutil.copy(fasta, out)
|
|
144
|
+
else:
|
|
145
|
+
if platform == "win32":
|
|
146
|
+
if " " in out:
|
|
147
|
+
out = f'"{out}"'
|
|
148
|
+
if " " in fasta:
|
|
149
|
+
fasta = f'"{fasta}"'
|
|
150
|
+
|
|
151
|
+
CMD = f"{path.sys_path}/external/MAFFT_Windows/mafft-win/mafft.bat --thread {thread} --{algorithm} --maxiterate {maxiterate} --{adjust} --op {op} --ep {ep} --quiet {fasta} > {out}"
|
|
152
|
+
else:
|
|
153
|
+
CMD = f"mafft --thread {thread} --{algorithm} --maxiterate {maxiterate} --{adjust} --op {op} --ep {ep} --quiet '{fasta}' > '{out}'"
|
|
154
|
+
|
|
155
|
+
logging.info(CMD)
|
|
156
|
+
try:
|
|
157
|
+
Run = subprocess.call(CMD, shell=True)
|
|
158
|
+
except:
|
|
159
|
+
logging.error(f"Failed on {CMD}")
|
|
160
|
+
raise Exception
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# Trimming
|
|
164
|
+
def Gblocks(fasta, out, path):
|
|
165
|
+
if platform == "win32":
|
|
166
|
+
if " " in fasta:
|
|
167
|
+
fasta = f'"{fasta}"'
|
|
168
|
+
CMD = f"{path.sys_path}/external/Gblocks_Windows_0.91b/Gblocks_0.91b/Gblocks.exe {fasta} -t=d -b4=2 -b5=a -e=.gb -p=t"
|
|
169
|
+
else:
|
|
170
|
+
CMD = f"Gblocks '{fasta}' -t=d -b4=2 -b5=a -e=.gb -p=t"
|
|
171
|
+
|
|
172
|
+
logging.info(CMD)
|
|
173
|
+
Run = subprocess.call(CMD, shell=True)
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
shutil.move(f"{fasta}.gb", out)
|
|
177
|
+
except: # when only one sequence and Gblocks failed
|
|
178
|
+
shutil.move(fasta, out)
|
|
179
|
+
|
|
180
|
+
# Parse and return column statistics
|
|
181
|
+
with open(f"{fasta}.gb.txt", "r") as f:
|
|
182
|
+
lines = f.readlines()
|
|
183
|
+
for line in lines:
|
|
184
|
+
if line.startswith("Flanks:"):
|
|
185
|
+
flank_log = line
|
|
186
|
+
flank_log = (
|
|
187
|
+
flank_log.replace("Flanks:", "")
|
|
188
|
+
.replace(" ", " ")
|
|
189
|
+
.replace("[", "")
|
|
190
|
+
.replace("]", "")
|
|
191
|
+
.strip()
|
|
192
|
+
)
|
|
193
|
+
flank_log = flank_log.split(" ")
|
|
194
|
+
print(flank_log)
|
|
195
|
+
try:
|
|
196
|
+
flank_log = [int(x) for x in flank_log]
|
|
197
|
+
start_pos = flank_log[0]
|
|
198
|
+
end_pos = flank_log[-1] - 1
|
|
199
|
+
except:
|
|
200
|
+
start_pos = -1
|
|
201
|
+
end_pos = -1
|
|
202
|
+
|
|
203
|
+
print(f"start_pos: {start_pos}, end_pos: {end_pos}")
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
shutil.move(f"{fasta}.gb.txt", path.extlog)
|
|
207
|
+
except:
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
return (start_pos, end_pos)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def Trimal(fasta, out, path, algorithm="gt", threshold=0.2):
|
|
214
|
+
if algorithm == "gt":
|
|
215
|
+
algorithm = f"{algorithm} {threshold}"
|
|
216
|
+
|
|
217
|
+
if platform == "win32":
|
|
218
|
+
if " " in fasta:
|
|
219
|
+
fasta = f'"{fasta}"'
|
|
220
|
+
if " " in out:
|
|
221
|
+
out_dir = f'"{out}"'
|
|
222
|
+
out_colnumbering = f'"{out}.colnumbering"'
|
|
223
|
+
else:
|
|
224
|
+
out_dir = out
|
|
225
|
+
out_colnumbering = f"{out}.colnumbering"
|
|
226
|
+
|
|
227
|
+
CMD = f"{path.sys_path}/external/trimal.v1.4/trimAl/bin/trimal.exe -in {fasta} -out {out_dir} -{algorithm} -terminalonly -colnumbering > {out_colnumbering}"
|
|
228
|
+
|
|
229
|
+
else:
|
|
230
|
+
CMD = f"trimal -in {fasta} -out {out} -{algorithm} -terminalonly -colnumbering > {out}.colnumbering"
|
|
231
|
+
|
|
232
|
+
logging.info(CMD)
|
|
233
|
+
Run = subprocess.call(CMD, shell=True)
|
|
234
|
+
|
|
235
|
+
# to remove unexpected hash included - maybe not needed after stabilization
|
|
236
|
+
fasta_list = list(SeqIO.parse(out, "fasta"))
|
|
237
|
+
|
|
238
|
+
for seq in fasta_list:
|
|
239
|
+
# if " " in seq.description:
|
|
240
|
+
seq.id = seq.description.split(" ")[0]
|
|
241
|
+
seq.description = ""
|
|
242
|
+
|
|
243
|
+
SeqIO.write(fasta_list, out, "fasta")
|
|
244
|
+
|
|
245
|
+
# Parse and return column statistics
|
|
246
|
+
with open(f"{out}.colnumbering", "r") as f:
|
|
247
|
+
line = f.read()
|
|
248
|
+
cols = line.replace("#ColumnsMap", "").strip().split(", ")
|
|
249
|
+
try:
|
|
250
|
+
cols = [int(x) for x in cols]
|
|
251
|
+
start_pos = cols[0]
|
|
252
|
+
end_pos = cols[-1]
|
|
253
|
+
except:
|
|
254
|
+
start_pos = -2
|
|
255
|
+
end_pos = -2
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
shutil.move(f"{out}.colnumbering", path.extlog)
|
|
259
|
+
except:
|
|
260
|
+
pass
|
|
261
|
+
|
|
262
|
+
# Trimal uses 0 based position, return with +1
|
|
263
|
+
return (start_pos + 1, end_pos + 1)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# Modeltest
|
|
267
|
+
def Modeltest_ng(fasta, out, models, thread):
|
|
268
|
+
if platform == "win32":
|
|
269
|
+
logging.error("Modeltest-NG is not available in windows. Try IQTREE modeltest")
|
|
270
|
+
raise Exception
|
|
271
|
+
else:
|
|
272
|
+
CMD = f"modeltest-ng -i '{fasta}' -o '{out}' -t ml -p {thread} --disable-checkpoint {models}"
|
|
273
|
+
|
|
274
|
+
logging.info(CMD)
|
|
275
|
+
Run = subprocess.call(CMD, shell=True)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# IQTREE ModelFinder
|
|
279
|
+
def ModelFinder(fasta, opt, path, thread):
|
|
280
|
+
if opt.method.tree == "iqtree":
|
|
281
|
+
model_term = "-m MFP"
|
|
282
|
+
elif opt.method.tree == "raxml":
|
|
283
|
+
model_term = "-m MF --mset raxml"
|
|
284
|
+
elif opt.method.tree == "fasttree":
|
|
285
|
+
model_term = "-m MF --mset JC,JC+G4,GTR,GTR+G4"
|
|
286
|
+
else:
|
|
287
|
+
logging.error(
|
|
288
|
+
f"Modelterm cannot be selected to tree method {opt.method.tree} while running modelfinder"
|
|
289
|
+
)
|
|
290
|
+
raise Exception
|
|
291
|
+
|
|
292
|
+
if platform == "win32":
|
|
293
|
+
if " " in fasta:
|
|
294
|
+
fasta = f'"{fasta}"'
|
|
295
|
+
CMD = f"{path.sys_path}/external/iqtree/bin/iqtree2.exe --seqtype DNA -s {fasta} {model_term} -merit {opt.criterion} -T {thread} -mem {opt.memory}"
|
|
296
|
+
else:
|
|
297
|
+
# not final
|
|
298
|
+
CMD = f"iqtree --seqtype DNA -s '{fasta}' {model_term} -merit {opt.criterion} -T {thread} -mem {opt.memory}"
|
|
299
|
+
logging.info(CMD)
|
|
300
|
+
Run = subprocess.call(CMD, shell=True)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# Tree building
|
|
304
|
+
def RAxML(
|
|
305
|
+
fasta,
|
|
306
|
+
out,
|
|
307
|
+
hash_dict,
|
|
308
|
+
path,
|
|
309
|
+
thread=1,
|
|
310
|
+
bootstrap=100,
|
|
311
|
+
partition=None,
|
|
312
|
+
model="-m GTRGAMMA",
|
|
313
|
+
):
|
|
314
|
+
if model == "skip":
|
|
315
|
+
model = ""
|
|
316
|
+
|
|
317
|
+
# Because RAxML does not allows out location, change directory for running
|
|
318
|
+
path_ori = os.getcwd()
|
|
319
|
+
os.chdir(path.tmp)
|
|
320
|
+
|
|
321
|
+
if platform == "win32":
|
|
322
|
+
if " " in fasta:
|
|
323
|
+
fasta = f'"{fasta}"'
|
|
324
|
+
if " " in out:
|
|
325
|
+
out = f'"{out}"'
|
|
326
|
+
|
|
327
|
+
CMD = f"{path.sys_path}/external/RAxML_Windows/raxmlHPC-PTHREADS-AVX2.exe -s {fasta} -n {out} -p 1 -T {thread} -f a -# {bootstrap} -x 1 {model}"
|
|
328
|
+
elif platform == "darwin":
|
|
329
|
+
CMD = f"raxmlHPC-PTHREADS -s '{fasta}' -n '{out}' -p 1 -T {thread} -f a -# {bootstrap} -x 1 {model}"
|
|
330
|
+
else:
|
|
331
|
+
CMD = f"raxmlHPC-PTHREADS-AVX -s '{fasta}' -n '{out}' -p 1 -T {thread} -f a -# {bootstrap} -x 1 {model}"
|
|
332
|
+
|
|
333
|
+
if not (partition is None):
|
|
334
|
+
CMD += f" -q {partition}"
|
|
335
|
+
|
|
336
|
+
logging.info(CMD)
|
|
337
|
+
Run = subprocess.call(CMD, shell=True)
|
|
338
|
+
|
|
339
|
+
if Run != 0:
|
|
340
|
+
logging.error(f"RAxML Failed!")
|
|
341
|
+
raise Exception
|
|
342
|
+
|
|
343
|
+
# Return result to original directory
|
|
344
|
+
os.chdir(path_ori)
|
|
345
|
+
file = out.split("/")[-1]
|
|
346
|
+
out = f"RAxML_bipartitions.{out}"
|
|
347
|
+
save_tree(
|
|
348
|
+
out=f"{path.tmp}/{out}",
|
|
349
|
+
hash_dict=hash_dict,
|
|
350
|
+
hash_file_path=f"{path.out_tree}/hash_{file}",
|
|
351
|
+
decoded_file_path=f"{path.out_tree}/{file}",
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def FastTree(fasta, out, hash_dict, path, model=""):
|
|
356
|
+
if model == "skip":
|
|
357
|
+
model = ""
|
|
358
|
+
if platform == "win32":
|
|
359
|
+
if " " in model:
|
|
360
|
+
model = f'"{model}"'
|
|
361
|
+
if " " in fasta:
|
|
362
|
+
fasta = f'"{fasta}"'
|
|
363
|
+
if " " in path.tmp:
|
|
364
|
+
path_tmp = f'"{path.tmp}/fasttreelog"'
|
|
365
|
+
else:
|
|
366
|
+
path_tmp = f"{path.tmp}/fasttreelog"
|
|
367
|
+
if " " in path.tmp or " " in out:
|
|
368
|
+
path_out = f'"{path.tmp}/{out}"'
|
|
369
|
+
else:
|
|
370
|
+
path_out = f"{path.tmp}/{out}"
|
|
371
|
+
CMD = f"{path.sys_path}/external/FastTree_Windows/FastTree.exe -quiet -nt {model} -log {path_tmp} -seed 1 {fasta} > {path_out}"
|
|
372
|
+
else:
|
|
373
|
+
CMD = f"FastTree -quiet -nt {model} -log {path.tmp}/fasttreelog -seed 1 '{fasta}' > {path.tmp}/{out}"
|
|
374
|
+
|
|
375
|
+
logging.info(CMD)
|
|
376
|
+
Run = subprocess.call(CMD, shell=True)
|
|
377
|
+
file = out.split("/")[-1]
|
|
378
|
+
save_tree(
|
|
379
|
+
out=f"{path.tmp}/{out}",
|
|
380
|
+
hash_dict=hash_dict,
|
|
381
|
+
hash_file_path=f"{path.out_tree}/hash_{file}",
|
|
382
|
+
decoded_file_path=f"{path.out_tree}/{file}",
|
|
383
|
+
fix=True,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def IQTREE(
|
|
388
|
+
fasta,
|
|
389
|
+
out,
|
|
390
|
+
hash_dict,
|
|
391
|
+
path,
|
|
392
|
+
memory=f"{max(2, int(psutil.virtual_memory().total / (1024**3)))}G",
|
|
393
|
+
thread=1,
|
|
394
|
+
bootstrap=1000,
|
|
395
|
+
partition=None,
|
|
396
|
+
model="",
|
|
397
|
+
):
|
|
398
|
+
if model == "skip":
|
|
399
|
+
model = ""
|
|
400
|
+
|
|
401
|
+
if bootstrap < 1000:
|
|
402
|
+
logging.warning("IQTREE requires at least 1000 bootstrap, setting to 1000")
|
|
403
|
+
bootstrap = 1000
|
|
404
|
+
|
|
405
|
+
if platform == "win32":
|
|
406
|
+
# For working with space
|
|
407
|
+
if " " in fasta:
|
|
408
|
+
tmp_fasta = f'"{fasta}"'
|
|
409
|
+
else:
|
|
410
|
+
tmp_fasta = fasta
|
|
411
|
+
|
|
412
|
+
CMD = f"{path.sys_path}/external/iqtree/bin/iqtree2.exe -s {tmp_fasta} -B {bootstrap} -T {thread} {model}"
|
|
413
|
+
else:
|
|
414
|
+
CMD = f"iqtree -s {fasta} -B {bootstrap} -T {thread} {model}"
|
|
415
|
+
|
|
416
|
+
logging.info(f"partition: {partition}")
|
|
417
|
+
# Partitioned analysis cannot be used with memory option
|
|
418
|
+
if not (partition is None):
|
|
419
|
+
if " " in partition:
|
|
420
|
+
tmp_partition = f'"{partition}"'
|
|
421
|
+
else:
|
|
422
|
+
tmp_partition = partition
|
|
423
|
+
CMD += f" -q {tmp_partition}"
|
|
424
|
+
else:
|
|
425
|
+
CMD += f" -mem {memory}"
|
|
426
|
+
|
|
427
|
+
logging.info(CMD)
|
|
428
|
+
Run = subprocess.call(CMD, shell=True)
|
|
429
|
+
try:
|
|
430
|
+
if partition is None:
|
|
431
|
+
shutil.move(f"{fasta}.contree", f"{path.tmp}/{out}")
|
|
432
|
+
print(f"DEBUG Moved {fasta}.contree to {path.tmp}/{out}")
|
|
433
|
+
else:
|
|
434
|
+
shutil.move(f"{partition}.contree", f"{path.tmp}/{out}")
|
|
435
|
+
print(f"DEBUG Moved {partition}.contree to {path.tmp}/{out}")
|
|
436
|
+
|
|
437
|
+
except:
|
|
438
|
+
logging.error(
|
|
439
|
+
"IQTREE FAILED. Maybe due to memory problem if partitioned analysis included."
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
file = out.split("/")[-1]
|
|
443
|
+
save_tree(
|
|
444
|
+
out=f"{path.tmp}/{out}",
|
|
445
|
+
hash_dict=hash_dict,
|
|
446
|
+
hash_file_path=f"{path.out_tree}/hash_{file}",
|
|
447
|
+
decoded_file_path=f"{path.out_tree}/{file}",
|
|
448
|
+
)
|
src/hasher.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import os, shutil
|
|
2
|
+
import re
|
|
3
|
+
from Bio import SeqIO
|
|
4
|
+
import copy
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Remove all newick illegal strings
|
|
9
|
+
def newick_legal(string: str) -> str:
|
|
10
|
+
newick_illegal = ["(", ")", "{", "}", "[", "]", ":", ";", "'", '"', ",", "."]
|
|
11
|
+
for i in newick_illegal:
|
|
12
|
+
string = string.replace(i, "")
|
|
13
|
+
|
|
14
|
+
string = string.replace(" ", " ")
|
|
15
|
+
string = string.replace(" ", "_")
|
|
16
|
+
|
|
17
|
+
return str(string)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Encode funinfo_list and return hash dict
|
|
21
|
+
def encode(funinfo_list: list, newick: bool = False) -> dict:
|
|
22
|
+
hash_dict = {}
|
|
23
|
+
|
|
24
|
+
if newick is False:
|
|
25
|
+
for funinfo in funinfo_list:
|
|
26
|
+
hash_dict[funinfo.hash] = f"{funinfo.id}"
|
|
27
|
+
else:
|
|
28
|
+
for funinfo in funinfo_list:
|
|
29
|
+
try:
|
|
30
|
+
hash_dict[
|
|
31
|
+
funinfo.hash
|
|
32
|
+
] = f"{funinfo.id}_{funinfo.genus}_{funinfo.species}"
|
|
33
|
+
except:
|
|
34
|
+
hash_dict[
|
|
35
|
+
funinfo.hash
|
|
36
|
+
] = f"{funinfo.id}_{funinfo.genus}_{funinfo.ori_species}"
|
|
37
|
+
|
|
38
|
+
return hash_dict
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Decode given file with given hash_dict
|
|
42
|
+
def decode(hash_dict: dict, file: str, out: str, newick: bool = True) -> None:
|
|
43
|
+
with open(file, "rt") as fp:
|
|
44
|
+
line = fp.read()
|
|
45
|
+
if newick == True:
|
|
46
|
+
hash_dict = dict(
|
|
47
|
+
(re.escape(k), newick_legal(v)) for k, v in hash_dict.items()
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
hash_dict = dict((re.escape(k), v) for k, v in hash_dict.items())
|
|
51
|
+
|
|
52
|
+
pattern = re.compile("|".join(hash_dict.keys()))
|
|
53
|
+
line = pattern.sub(lambda m: hash_dict[re.escape(m.group(0))], line)
|
|
54
|
+
|
|
55
|
+
with open(out, "w") as fw:
|
|
56
|
+
fw.write(line)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# Decode given dataframe with given hash_dict
|
|
60
|
+
def decode_df(hash_dict: dict, df: pd.DataFrame) -> pd.DataFrame:
|
|
61
|
+
hash_dict = dict((re.escape(k), v) for k, v in hash_dict.items())
|
|
62
|
+
|
|
63
|
+
df_return = copy.deepcopy(df)
|
|
64
|
+
|
|
65
|
+
for column in df.columns:
|
|
66
|
+
df_return[column] = df_return[column].map(lambda x: hash_dict.get(x, x))
|
|
67
|
+
|
|
68
|
+
return df_return
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def hasher(funinfo_list: list, path, option, outgroup: bool = False):
|
|
72
|
+
"""
|
|
73
|
+
# main pipeline
|
|
74
|
+
Hash all
|
|
75
|
+
"""
|
|
76
|
+
group_result = {}
|
|
77
|
+
for group in group_list:
|
|
78
|
+
tmp_funinfo_list = []
|
|
79
|
+
for funinfo in funinfo_list:
|
|
80
|
+
if funinfo.adjusted_group == group:
|
|
81
|
+
tmp_funinfo_list.append(funinfo)
|
|
82
|
+
|
|
83
|
+
group_result[group] = (
|
|
84
|
+
f"Hashed_{group}.fasta",
|
|
85
|
+
encode(tmp_funinfo_list, f"{path.out_hash}/Hashed_{group}.fasta"),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return group_result
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def hash_funinfo_list(list_funinfo: list) -> list:
|
|
92
|
+
"""
|
|
93
|
+
Generate hash numbers
|
|
94
|
+
"""
|
|
95
|
+
for n, funinfo in enumerate(list_funinfo):
|
|
96
|
+
funinfo.update_hash(n)
|
|
97
|
+
|
|
98
|
+
return list_funinfo
|