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.
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