Skip to content

Commit cf05579

Browse files
authored
Merge pull request #43 from FOI-Bioinformatics/version_update_v0.4.2
Version update v0.4.2
2 parents cf99c8d + de1f6d2 commit cf05579

16 files changed

Lines changed: 224 additions & 91 deletions

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ build*
33
test*
44
*.dmp
55
.ctbd
6-
*.db
7-
*.txt
6+
#*.db
7+
#*.txt
88
*egg*
99
dist*
1010
logs*

flextaxd/create_databases.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def __init__(self, message,expression=""):
8484
parser = argparse.ArgumentParser()
8585
basic = parser.add_argument_group('basic', 'Basic commands')
8686
basic.add_argument('-o', '--outdir',metavar="", default=".", help="Output directory (same directory as custom_taxonomy_databases dump)")
87-
basic.add_argument('-db', '--database',metavar="", type=str, default=".ctdb" , help="Custom taxonomy sqlite3 database file")
87+
basic.add_argument('-db', '--database', '--db' ,metavar="", type=str, default=".ctdb" , help="Custom taxonomy sqlite3 database file")
8888

8989
### Download options, process local directory and potentially download files
9090
download_opts = parser.add_argument_group('download_opts', "Download and file handling")
@@ -116,6 +116,10 @@ def __init__(self, message,expression=""):
116116

117117
parser.add_argument("--version", action='store_true', help=argparse.SUPPRESS)
118118

119+
if len(sys.argv)==1:
120+
parser.print_help(sys.stderr)
121+
sys.exit(1)
122+
119123
args = parser.parse_args()
120124

121125
if not os.path.exists(args.database):
@@ -185,7 +189,7 @@ def __init__(self, message,expression=""):
185189
if args.download or args.representative:
186190
download = dynamic_import("modules", "DownloadGenomes")
187191
download_obj = download(args.processes,outdir=args.outdir,force=args.force_download,download_path=args.genomes_path)
188-
new_genome_path, missing = download_obj.run(missing,args.rep_path)
192+
new_genome_path, missing = download_obj.run(missing,representative=args.representative,url=args.rep_path)
189193
if not new_genome_path:
190194
still_missing = missing
191195
if len(still_missing) > 0: print("Not able to download: {nr}".format(nr=len(still_missing)))
@@ -209,6 +213,7 @@ def __init__(self, message,expression=""):
209213
limit = 10
210214
'''Use the genome -> path dictionary to build database'''
211215
if not skip:
216+
logger.info("Get genomes from input directory!")
212217
genomes = process_directory_obj.get_genome_path_dict()
213218
else: genomes=False
214219
if args.skip:

flextaxd/custom_taxonomy_databases.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
each column (not <tab>|<tab>).
2121
'''
2222

23-
__version__ = "0.4.1"
23+
__version__ = "0.4.2"
2424
__author__ = "David Sundell"
2525
__credits__ = ["David Sundell"]
2626
__license__ = "GPLv3"
@@ -147,6 +147,10 @@ def __init__(self, message,expression=""):
147147

148148
parser.add_argument("--version", action='store_true', help=argparse.SUPPRESS)
149149

150+
if len(sys.argv)==1:
151+
parser.print_help(sys.stderr)
152+
sys.exit(1)
153+
150154
args = parser.parse_args()
151155

152156
if args.version:
@@ -187,6 +191,7 @@ def __init__(self, message,expression=""):
187191
if args.force:
188192
force = True
189193

194+
190195
### Run pipeline
191196

192197
'''If database is not given, and input data is not given raise error'''

flextaxd/modules/CreateKrakenDatabase.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ def __init__(self, database, kraken_database, genome_names, outdir,verbose=False
5050
if genome_names:
5151
self.genome_names = list(genome_names.keys()) ## List for multiprocessing
5252
self.genome_path = genome_names ## genome_id to path dictionary
53+
else:
54+
logger.error("Genome names are missing")
5355
self.accession_to_taxid = self.database.get_genomes(self.database)
5456
self.files = []
5557
self.params = params

flextaxd/modules/DownloadGenomes.py

Lines changed: 62 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from modules.functions import download_genomes
1010
from multiprocessing import Process,Manager,Pool
1111
from subprocess import Popen,PIPE
12-
import os
12+
import os,time
1313

1414
class DownloadGenomes(object):
1515
"""DownloadGenomes takes a list of genome names (GCF or GCA) and download (if possible) files from NCBI refseq and or genbank"""
@@ -85,7 +85,7 @@ def download_represenatives(self,genome_path,url="https://data.ace.uq.edu.au/pub
8585
logger.debug(" ".join(args))
8686
logger.info("Waiting for download process to finish (this may take a while)!")
8787
p = Popen(args, stdout=PIPE)
88-
(output, err) = p.communicate()
88+
(output, err) = p.communicate()
8989
p_status = p.wait()
9090
return input_file_name
9191

@@ -114,52 +114,65 @@ def download_files(self,files):
114114
Returns
115115
list - list of files not downloaded
116116
'''
117-
logger.info("Downloading {files} files".format(files=len(files)))
118-
if len(files) < self.processes:
119-
self.processes = len(files)
120-
self.download_map = self._split(files,self.processes)
121-
logger.info("Using {np} parallel processes to download files".format(np=self.processes))
122-
123-
'''function to run download of genomes in paralell'''
124-
jobs = []
125-
manager = Manager()
126-
added = manager.Queue()
127-
fpath = manager.Queue()
128-
missing = manager.Queue()
129-
for i in range(self.processes):
130-
p = Process(target=download_genomes, args=(self.download_map[i],added,fpath,missing,self.force))
131-
p.daemon=True
132-
p.start()
133-
jobs.append(p)
134-
for job in jobs:
135-
job.join()
136-
self.added = added.qsize()
137-
if not any(proc.is_alive() for proc in jobs):
138-
logger.info('All download processes completed')
139-
elif len(added.qsize()) % 100 == 0:
140-
print("{n} downloaded".format(added.qsize()),end="\r")
141-
142-
count = 0
143-
while True:
144-
if added.qsize() == 0: ## Check if no genome was succesfully downlaoded if no break
145-
logger.info("None of listed genomes could be downloaded! Files not downloaded will be printed to {outdir}/FlexTaxD.missing".format(outdir=self.outdir.rstrip("/")))
146-
self.write_missing(files)
147-
break
148-
l = len(self.genome_names)
149-
gen = added.get()
150-
if gen:
151-
self.genome_names += gen
152-
self.genome_path[gen] = fpath.get()
153-
if l == len(self.genome_names):
117+
try:
118+
logger.info("Downloading {files} files".format(files=len(files)))
119+
if len(files) < self.processes:
120+
self.processes = len(files)
121+
self.download_map = self._split(files,self.processes)
122+
logger.info("Using {np} parallel processes to download files".format(np=self.processes))
123+
124+
'''function to run download of genomes in paralell'''
125+
jobs = []
126+
manager = Manager()
127+
added = manager.Queue()
128+
fpath = manager.Queue()
129+
missing = manager.Queue()
130+
for i in range(self.processes):
131+
p = Process(target=download_genomes, args=(self.download_map[i],added,fpath,missing,self.force))
132+
p.daemon=True
133+
p.start()
134+
jobs.append(p)
135+
for job in jobs:
136+
job.join()
137+
self.added = added.qsize()
138+
if not any(proc.is_alive() for proc in jobs):
139+
logger.info('All download processes completed')
140+
elif len(added.qsize()) % 100 == 0:
141+
print("{n} downloaded".format(added.qsize()),end="\r")
142+
143+
count = 0
144+
while True:
145+
if self.added == 0: ## Check if no genome was succesfully downlaoded if no break
146+
logger.info("None of listed genomes could be downloaded! Files not downloaded will be printed to {outdir}/FlexTaxD.missing".format(outdir=self.outdir.rstrip("/")))
147+
self.write_missing(files)
154148
break
155-
else:
156-
self.not_downloaded += missing.get()
157-
count+=1
158-
if len(self.not_downloaded) > 0:
159-
self.write_missing(self.not_downloaded)
149+
l = len(self.genome_names)
150+
gen = added.get()
151+
if gen:
152+
self.genome_names += gen
153+
self.genome_path[gen] = fpath.get()
154+
if l == len(self.genome_names):
155+
break
156+
else:
157+
self.not_downloaded += missing.get()
158+
count+=1
159+
if added.qsize():
160+
break
161+
if len(self.not_downloaded) > 0:
162+
self.write_missing(self.not_downloaded)
163+
except KeyboardInterrupt:
164+
logger.info("Program was interrupted by user: cleaning up subprocesses!")
165+
finally: ## Make sure all sub-processes are ended even if program is forced to quit
166+
if any(proc.is_alive() for proc in jobs):
167+
for p in jobs:
168+
print(p)
169+
p.kill()
170+
time.sleep(1)
171+
if any(proc.is_alive() for proc in jobs):
172+
logger.error("Could not stop all subprocesses check your process manager and end them manually!")
160173
return self.not_downloaded
161174

162-
def run(self, files, representatives=False):
175+
def run(self, files, representative=False,url=""):
163176
'''Download list of GCF and or GCA files from NCBI or download represenative genomes
164177
165178
Parameters
@@ -169,12 +182,14 @@ def run(self, files, representatives=False):
169182
Returns
170183
list - list of files not downloaded
171184
'''
172-
if representatives:
173-
tarfile = self.download_represenatives(genome_path=self.download_path, url=representatives)
185+
if representative:
186+
logger.info("Download GTDB Representative genomes")
187+
tarfile = self.download_represenatives(genome_path=self.download_path, url=url)
174188
folder_path = self.parse_representatives(tarfile)
175189
'''Process the downloaded folder'''
176190
return folder_path,[]
177191
else:
178192
if len(files) > 0:
193+
logger.info("Download missing genomes")
179194
not_downloaded = self.download_files(files)
180195
return False,not_downloaded

flextaxd/modules/ModifyTree.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ def __init__(self, database=".taxonomydb", mod_database=False, mod_file=False, c
6868
self.ncbi_order = True
6969
self.mod_genomes =False
7070

71+
self.fast_clean = True
72+
7173
### Connect to or create database
7274
self.taxonomydb = ModifyFunctions(database,verbose=verbose)
7375
self.rank= self.taxonomydb.get_rank(col=2)
@@ -398,7 +400,7 @@ def clean_database(self, ncbi=False):
398400
self.all_nodes = set(self.taxonomydb.get_nodes(col=1).keys())
399401
'''Add parents to all nodes that may not have annotations'''
400402
logger.info("Retrieve all parents of annotated nodes")
401-
self.annotated_nodes |= self.taxonomydb.get_parents(self.annotated_nodes,find_all=True)
403+
self.annotated_nodes |= self.taxonomydb.get_parents(self.annotated_nodes,find_all=True,ncbi=ncbi)
402404
logger.info("Parents added: {an}".format(an=len(self.annotated_nodes)-an))
403405
if ncbi:
404406
logger.info("Keep main nodes of the NCBI taxonomy (parents on level 3 and above)")
@@ -415,7 +417,12 @@ def clean_database(self, ncbi=False):
415417
logger.debug("Nodes remaining {nnodes}".format(nnodes=len(self.annotated_nodes)))
416418
logger.info("Clean annotations related to removed nodes")
417419
if len(self.clean_links) < len(self.all_links) and len(self.clean_links) > 0:
418-
self.taxonomydb.fast_delete_links(self.clean_links)
420+
logger.info("Cleaning {nlinks} links".format(nlinks=len(self.clean_links)))
421+
if self.fast_clean:
422+
self.taxonomydb.fast_delete_links(self.clean_links)
423+
else:
424+
logger.info("Cleaning tree (this will take a long time for large trees)")
425+
self.taxonomydb.delete_links(self.clean_links)
419426
if len(self.clean_nodes) < len(self.all_nodes) and len(self.clean_nodes) > 0:
420427
self.taxonomydb.delete_nodes(self.clean_nodes)
421428
if not ncbi:

flextaxd/modules/NewickTree.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,13 @@ def print(self,type="newick"):
147147
exists = importlib.find_loader('Bio')
148148
if not exists:
149149
raise VisualisationError("Visualisations other than newick requires biopython package (conda install biopython)!")
150-
151150
from Bio import Phylo
152151
self.phylo = Phylo.read(StringIO(self.newickTree), "newick")
153152
if type == "newick_vis":
154153
Phylo.draw_ascii(self.phylo)
154+
exists = importlib.find_loader('matplotlib')
155+
if not exists:
156+
raise VisualisationError("Visualisations using newick tree requires the matplotlib package (conda install matplotlib)!")
155157
import matplotlib.pylab as pylab
156158
if type == "tree":
157159
Phylo.draw(self.phylo)

flextaxd/modules/ReadTaxonomy.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ def read_nodes(self, treefile=False):
109109
with self.zopen(treefile, "r") as _treefile:
110110
headers = _treefile.readline().strip().split(self.sep)
111111
if "parent" not in headers or "child" not in headers:
112+
logger.debug("Headers: {h} separator [{sep}]".format(h=headers,sep=self.sep))
112113
raise InputError("Your input tree file does not contain the headers to specify child and parent!")
113114
if headers[0] == "parent":
114115
swap = True

flextaxd/modules/ReadTaxonomyNCBI.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from .ReadTaxonomy import ReadTaxonomy
88
from gzip import open as zopen
9+
import zlib
910
import os
1011
import logging
1112
logger = logging.getLogger(__name__)
@@ -98,18 +99,30 @@ def parse_genomeid2taxid(self, genomes_path,annotation_file):
9899
if not annotation_file.endswith("accession2taxid.gz"):
99100
raise TypeError("The supplied annotation file does not seem to be the ncbi nucl_gb.accession2taxid.gz")
100101
annotated_genome = set()
101-
with zopen(annotation_file,"r") as f:
102-
headers = f.readline().split(b"\t")
103-
for row in f:
104-
if row.strip() != "": ## If there are trailing empty lines in the file
105-
refseqid,taxid = row.split(b"\t")[1:3]
106-
try:
107-
genebankid = self.refseqid_to_GCF[refseqid]
108-
self.database.add_genome(genome=genebankid,_id=taxid.decode("utf-8"))
109-
annotated_genome.add(refseqid)
110-
except:
111-
pass
112-
self.database.commit()
102+
try:
103+
with zopen(annotation_file,"r") as f:
104+
headers = f.readline().split(b"\t")
105+
for row in f:
106+
if row.strip() != "": ## If there are trailing empty lines in the file
107+
if len(row.split(b"\t")) > 2:
108+
try:
109+
refseqid,taxid = row.split(b"\t")[1:3]
110+
except:
111+
logger.info(row)
112+
logger.info(row.split(b"\t"))
113+
if len(annotated_genome) > 0:
114+
logger.info("Potential error in last row?")
115+
else:
116+
logger.info("Error on first line in annotation file, check format!")
117+
try:
118+
genebankid = self.refseqid_to_GCF[refseqid]
119+
self.database.add_genome(genome=genebankid,_id=taxid.decode("utf-8"))
120+
annotated_genome.add(refseqid)
121+
except KeyError:
122+
pass
123+
self.database.commit()
124+
except zlib.error as e:
125+
logger.info("Error in annotation file {e}".format(e=e))
113126
missing = set(self.refseqid_to_GCF.keys()) - annotated_genome
114127
missing = [self.refseqid_to_GCF[m] for m in missing] ## Translate to GCF ids
115128
if logging.root.level <=20: ## Equal to --verbose

0 commit comments

Comments
 (0)