meta_eukaryome_detect 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023, Anthony Fullam
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.1
2
+ Name: meta_eukaryome_detect
3
+ Version: 0.1.0
4
+ Summary: Pathogen, Parasite, Eukaryote and Virus detection in metagenomes..
5
+ Home-page: https://git.embl.de/grp-bork/meta-eukaryome-detect
6
+ License: MIT
7
+ Author: Anthony Fullam
8
+ Author-email: anthony.fullam@embl.de
9
+ Requires-Python: >=3.11,<4.0
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Natural Language :: English
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Provides-Extra: dev
18
+ Provides-Extra: doc
19
+ Provides-Extra: test
20
+ Requires-Dist: black (>=23.10.1,<24.0.0) ; extra == "test"
21
+ Requires-Dist: bump2version (>=1.0.1,<2.0.0) ; extra == "dev"
22
+ Requires-Dist: flake8 (>=3.9.2,<4.0.0) ; extra == "test"
23
+ Requires-Dist: flake8-docstrings (>=1.6.0,<2.0.0) ; extra == "test"
24
+ Requires-Dist: flask (>=3.0.0,<4.0.0)
25
+ Requires-Dist: isort (>=5.8.0,<6.0.0) ; extra == "test"
26
+ Requires-Dist: jinja2 (>=3.1.2,<4.0.0)
27
+ Requires-Dist: mkdocs (>=1.5.3,<2.0.0) ; extra == "doc"
28
+ Requires-Dist: mkdocs-autorefs (>=0.5.0,<0.6.0) ; extra == "doc"
29
+ Requires-Dist: mkdocs-include-markdown-plugin (>=6.0.3,<7.0.0) ; extra == "doc"
30
+ Requires-Dist: mkdocs-material (>=6.1.7,<7.0.0) ; extra == "doc"
31
+ Requires-Dist: mkdocs-material-extensions (>=1.0.1,<2.0.0)
32
+ Requires-Dist: mkdocstrings (>=0.23.0,<0.24.0) ; extra == "doc"
33
+ Requires-Dist: mkdocstrings-python (>=1.7.3,<2.0.0)
34
+ Requires-Dist: mypy (>=0.900,<0.901) ; extra == "test"
35
+ Requires-Dist: pandas (>=2.2.3,<3.0.0)
36
+ Requires-Dist: pip (>=20.3.1,<21.0.0) ; extra == "dev"
37
+ Requires-Dist: pre-commit (>=2.12.0,<3.0.0) ; extra == "dev"
38
+ Requires-Dist: pytest (>=6.2.4,<7.0.0) ; extra == "test"
39
+ Requires-Dist: pytest-cov (>=2.12.0,<3.0.0) ; extra == "test"
40
+ Requires-Dist: requests (>=2.31.0,<3.0.0)
41
+ Requires-Dist: toml (>=0.10.2,<0.11.0) ; extra == "dev"
42
+ Requires-Dist: tox (>=3.20.1,<4.0.0) ; extra == "dev"
43
+ Requires-Dist: twine (>=3.3.0,<4.0.0) ; extra == "dev"
44
+ Requires-Dist: types-requests (>=2.31.0.10,<3.0.0.0)
45
+ Requires-Dist: virtualenv (>=20.2.2,<21.0.0) ; extra == "dev"
46
+ Description-Content-Type: text/markdown
47
+
48
+ # meta-eukaryome-detect
49
+
50
+
51
+ [![pypi](https://img.shields.io/pypi/v/meta-eukaryome-detect.svg)](https://pypi.org/project/meta-eukaryome-detect/)
52
+ [![licence](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
53
+
54
+
55
+ Pathogen, Parasite, Eukaryote and Virus detection in metagenomes.
56
+
57
+
58
+ * Documentation: <https://grp-bork.embl-community.io/grp-microbiome-astrology/meta_eukaryome_detect/>
59
+ * GitHub: <https://git.embl.de/grp-bork/meta_eukaryome_detect>
60
+ * PyPI: <https://pypi.org/project/meta-eukaryome-detect/>
61
+ * Free software: MIT
62
+
@@ -0,0 +1,14 @@
1
+ # meta-eukaryome-detect
2
+
3
+
4
+ [![pypi](https://img.shields.io/pypi/v/meta-eukaryome-detect.svg)](https://pypi.org/project/meta-eukaryome-detect/)
5
+ [![licence](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+
8
+ Pathogen, Parasite, Eukaryote and Virus detection in metagenomes.
9
+
10
+
11
+ * Documentation: <https://grp-bork.embl-community.io/grp-microbiome-astrology/meta_eukaryome_detect/>
12
+ * GitHub: <https://git.embl.de/grp-bork/meta_eukaryome_detect>
13
+ * PyPI: <https://pypi.org/project/meta-eukaryome-detect/>
14
+ * Free software: MIT
@@ -0,0 +1,5 @@
1
+ """Top-level package for meta-eukaryome-detect."""
2
+
3
+ __author__ = """Anthony Fullam"""
4
+ __email__ = 'anthony.fullam@embl.de'
5
+ __version__ = '0.1.0'
@@ -0,0 +1,61 @@
1
+ import sys
2
+ from pathlib import Path
3
+
4
+
5
+ def get_min_identity_pc(reference_type):
6
+ match reference_type:
7
+ case 'plastid':
8
+ return '98'
9
+ case 'mito':
10
+ return '96'
11
+ case 'pr2':
12
+ return '97'
13
+ case 'virulence':
14
+ return '95'
15
+ case 'viruses':
16
+ return '95'
17
+ case _:
18
+ sys.exit(f'{reference_type} not a valid reference type!')
19
+
20
+
21
+ def get_ngless_filtering_template(reference_type: str, temp_dir: Path, result_dir: Path):
22
+ if reference_type == 'preprocess':
23
+ return f"""
24
+ ngless "1.1"
25
+ import "mocat" version "0.0"
26
+ input = load_mocat_sample(ARGV[1])
27
+
28
+ input = preprocess(input, keep_singles=True) using |read|:
29
+ read = smoothtrim(read, min_quality=20, window=4)
30
+ if len(read) < 70:
31
+ discard
32
+
33
+ write(input, ofile='{temp_dir}/preprocessed/preprocessed.fq' )
34
+ """
35
+ else:
36
+ min_identity_pc = get_min_identity_pc(reference_type)
37
+ return f"""
38
+ ngless "1.1"
39
+ import "parallel" version "1.0"
40
+ import "mocat" version "0.0"
41
+ import "samtools" version "0.0"
42
+
43
+ input = load_mocat_sample(ARGV[1])
44
+
45
+ mapped = map(input, fafile=ARGV[2])
46
+ write(samtools_sort(mapped), ofile='{temp_dir}/{reference_type}_unfiltered.bam' )
47
+
48
+ mapped = select(mapped) using |mr|:
49
+ mr = mr.filter(min_match_size=70, min_identity_pc={min_identity_pc}, action={{drop}})
50
+ write(samtools_sort(mapped), ofile='{result_dir}/{reference_type}_filtered.bam' )
51
+ """
52
+
53
+
54
+ def write_templates(out_dir, temp_dir, result_dir):
55
+ templates = {}
56
+ for template in ['preprocess', 'plastid', 'mito', 'pr2', 'virulence', 'viruses']:
57
+ out_file = Path(out_dir) / f'{template}.ngless'
58
+ templates[template] = out_file
59
+ with open(out_file, 'w') as f:
60
+ f.write(get_ngless_filtering_template(template, temp_dir, result_dir))
61
+ return templates
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env python3
2
+ import gzip
3
+ from pathlib import Path
4
+
5
+ import pandas as pd
6
+
7
+ output_col_order = {
8
+ 'plastid': [
9
+ "sample_name",
10
+ "ref_id",
11
+ "ref_length",
12
+ "avgdepth_cov",
13
+ "breadth_cov",
14
+ "breadth_fraction",
15
+ "ani_cluster_id",
16
+ "full_taxonomy",
17
+ "bin_names_in_cluster",
18
+ "organism_groups_all",
19
+ 'eukaryote_type1',
20
+ 'eukaryote_type2',
21
+ 'short_name',
22
+ 'possible_contaminant_in_human_gut',
23
+ 'other_notes',
24
+ 'eukaryote_type3',
25
+ ],
26
+ 'mito': [
27
+ "sample_name",
28
+ "ref_id",
29
+ "ref_length",
30
+ "avgdepth_cov",
31
+ "breadth_cov",
32
+ "breadth_fraction",
33
+ "ani_cluster_id",
34
+ "full_taxonomy",
35
+ "bin_names_in_cluster",
36
+ "organism_groups_all",
37
+ 'eukaryote_type1',
38
+ 'eukaryote_type2',
39
+ 'short_name',
40
+ 'possible_contaminant_in_human_gut',
41
+ 'other_notes',
42
+ 'eukaryote_type3',
43
+ ],
44
+ 'pr2': [
45
+ "sample_name",
46
+ "ref_id",
47
+ "ref_length",
48
+ "avgdepth_cov",
49
+ "breadth_cov",
50
+ "breadth_fraction",
51
+ "reference_type",
52
+ "gene_cluster_id",
53
+ ],
54
+ 'virulence': [
55
+ "sample_name",
56
+ "ref_id",
57
+ "ref_length",
58
+ "avgdepth_cov",
59
+ "breadth_cov",
60
+ "breadth_fraction",
61
+ "gene_cluster_id",
62
+ "SpName_x",
63
+ ],
64
+ 'viruses': [
65
+ "sample_name",
66
+ "ref_id",
67
+ "ref_length",
68
+ "avgdepth_cov",
69
+ "breadth_cov",
70
+ "breadth_fraction",
71
+ "Species",
72
+ "Genus",
73
+ "Family",
74
+ "Molecule_type",
75
+ "Host",
76
+ "Isolation_Source",
77
+ "Collection_Date",
78
+ "BioSample",
79
+ "GenBank_Title",
80
+ ],
81
+ }
82
+
83
+ breadth_coverages = {'plastid': 500, 'mito': 500, 'pr2': 100, 'virulence': 50, 'viruses': 100}
84
+
85
+ metadata_files = {
86
+ 'plastid': 'mito_plastid_metadata',
87
+ 'mito': 'mito_plastid_metadata',
88
+ 'pr2': 'pr2_metadata',
89
+ 'virulence': 'virulence_metadata',
90
+ 'viruses': 'viruses_metadata',
91
+ }
92
+
93
+
94
+ def get_depth_summary(out_dir: Path, reference: str, depth_file: Path, DB: Path, sample_name: str):
95
+ depths = pd.read_csv(depth_file, sep="\t", header=None, usecols=[0, 2], names=["ref_id", "depth_coverage"])
96
+ out_file = out_dir / f"{reference}.depth_summary.tsv.gz"
97
+ if len(depths) != 0:
98
+ output = depths.groupby("ref_id").mean().rename(columns={"depth_coverage": "avgdepth_cov"})
99
+ output["breadth_cov"] = depths.groupby("ref_id").count()
100
+ output = output[output["breadth_cov"] > breadth_coverages[reference]].sort_values(
101
+ by="breadth_cov", ascending=False
102
+ )
103
+
104
+ contigLen = pd.read_csv(
105
+ DB / metadata_files[reference],
106
+ sep="\t",
107
+ header=0,
108
+ )
109
+ output = pd.merge(output, contigLen, how="left", left_index=True, right_on=["ref_id"])
110
+
111
+ output["breadth_fraction"] = output["breadth_cov"] / output["ref_length"]
112
+ output["sample_name"] = sample_name
113
+ output[output_col_order[reference]].round(4).to_csv(out_file, sep="\t", index=False, compression="gzip")
114
+
115
+ else:
116
+ with gzip.open(out_file, "wb") as f:
117
+ f.write("\t".join(output_col_order[reference]).encode())
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env python3
2
+ import shutil
3
+ import subprocess
4
+ import sys
5
+ from pathlib import Path
6
+
7
+
8
+ def check_if_tool_exists(tool_name):
9
+ """Check if tool is available."""
10
+ return shutil.which(tool_name) is not None
11
+
12
+
13
+ def ngless(template: Path, cpu_count: str, temp_dir: Path, sample_folder: Path, verbose: bool, DB_path='') -> None:
14
+ """Run ngless.
15
+
16
+ Arguments:
17
+ template (Path): full path of gene calls
18
+ cpu_count (str): number of threads to use
19
+ temp_dir (Path): path of directory to use for tmp files
20
+ sample_folder (Path): full path ti sample directory
21
+ verbose (bool): Verbose output
22
+ DB_path (str): Path to directory containing metaeuk db
23
+
24
+ """
25
+ if verbose:
26
+ ngless_verbosity = '--trace'
27
+ else:
28
+ ngless_verbosity = ''
29
+ try:
30
+ subprocess.check_output(
31
+ [
32
+ "ngless",
33
+ "--temporary-directory",
34
+ temp_dir,
35
+ template,
36
+ "--jobs",
37
+ cpu_count,
38
+ sample_folder,
39
+ DB_path,
40
+ ngless_verbosity,
41
+ ],
42
+ universal_newlines=True,
43
+ )
44
+ except subprocess.CalledProcessError as e:
45
+ sys.exit(f"[ERROR] Failed to run ngless on {sample_folder} \n {e.output}")
46
+
47
+
48
+ def samtools_unique(filtered_bam: Path, unique_bam: Path, min_map_qual: str, verbose: bool) -> None:
49
+ """
50
+ Run samtools unique.
51
+
52
+ Arguments:
53
+
54
+ """
55
+ try:
56
+ subprocess.check_output(
57
+ [
58
+ "samtools",
59
+ "view",
60
+ "-bq",
61
+ min_map_qual,
62
+ filtered_bam,
63
+ "-o",
64
+ unique_bam,
65
+ ],
66
+ universal_newlines=True,
67
+ )
68
+ except subprocess.CalledProcessError as e:
69
+ sys.exit(f"[ERROR] Failed to run samtools view on {filtered_bam} \n {e.output}")
70
+
71
+
72
+ def samtools_depth(unique_bam: Path, unique_depth: Path, verbose: bool) -> None:
73
+ """Run samtools.
74
+
75
+ Arguments:
76
+
77
+ """
78
+ try:
79
+ subprocess.check_output(
80
+ [
81
+ "samtools",
82
+ "depth",
83
+ unique_bam,
84
+ "-o",
85
+ unique_depth,
86
+ ],
87
+ universal_newlines=True,
88
+ )
89
+ except subprocess.CalledProcessError as e:
90
+ sys.exit(f"[ERROR] Failed to run samtools view on {unique_bam} \n {e.output}")
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env python3
2
+ import gzip
3
+ import hashlib
4
+ import logging
5
+ import os
6
+ import shutil
7
+ import sys
8
+
9
+ import requests
10
+
11
+
12
+ def md5sum_file(file):
13
+ """Computes MD5 sum of file.
14
+
15
+ Arguments:
16
+ file (str): Path of file to md5sum
17
+
18
+ Returns:
19
+ str: md5sum
20
+ """
21
+ block_size = 8192
22
+ m = hashlib.md5()
23
+ with open(file, "rb") as f:
24
+ while True:
25
+ data = f.read(block_size)
26
+ if not data:
27
+ return m.hexdigest()
28
+ m.update(data)
29
+
30
+
31
+ def download_file(file_url, out_file):
32
+ """Download a file to disk
33
+
34
+ Streams a file from URL to disk.
35
+
36
+ Arguments:
37
+ file_url (str): URL of file to download
38
+ out_file (str): Target file path
39
+ """
40
+ with requests.get(file_url, stream=True) as r:
41
+ with open(out_file, "wb") as f:
42
+ shutil.copyfileobj(r.raw, f)
43
+
44
+
45
+ def decompress_gzip_file(gz_file, out_file):
46
+ """Decompress a gzip file.
47
+
48
+ Uncompressed given gzip file to out_file.
49
+
50
+ Arguments:
51
+ gz_file (str): Path of gzip file
52
+ out_file (str): Path of target uncompressed out file
53
+ """
54
+ with gzip.open(gz_file, "rb") as f_in:
55
+ with open(out_file, "wb") as f_out:
56
+ shutil.copyfileobj(f_in, f_out)
57
+
58
+
59
+ def get_md5_file(file_url):
60
+ """Read md5sums from url
61
+
62
+ Arguments:
63
+ file_url (str): URL of file
64
+
65
+ Returns:
66
+ dict: {file: md5sum}
67
+ """
68
+ md5s = requests.get(f"{file_url}").text.split("\n")
69
+ return {x.split(' ')[1]: x.split(' ')[0] for x in md5s if x}
70
+
71
+
72
+ def check_md5(expected_md5, file_path):
73
+ """Check md5 and remove file if incorect.
74
+
75
+ Arguments:
76
+ expected_md5 (str): Expected md5 of file
77
+ file_path (str): Path of file
78
+ """
79
+ downloaded_md5 = md5sum_file(file_path)
80
+ if downloaded_md5 != expected_md5:
81
+ os.unlink(file_path)
82
+ return False
83
+ else:
84
+ return True
85
+
86
+
87
+ def get_db(base_dir):
88
+ """Download GUNC DB.
89
+
90
+ Arguments:
91
+ base_dir (str): Path of output directory
92
+ """
93
+ logging.basicConfig(
94
+ format="%(asctime)s : %(message)s",
95
+ datefmt="%H:%M:%S",
96
+ level=logging.INFO,
97
+ )
98
+ logger = logging.getLogger(__name__)
99
+ if not os.path.isdir(base_dir):
100
+ sys.exit(f"[ERROR] Output Directory {base_dir} doesnt exist.")
101
+ base_url = "https://swifter.embl.de/~fullam/meta_eukaryome_database/"
102
+ files_to_download = get_md5_file(f"{base_url}MD5SUM_gzipped")
103
+ uncompressed_md5s = get_md5_file(f"{base_url}MD5SUM")
104
+ count_files_to_download = len(files_to_download)
105
+ logger.info("[INFO] DB downloading...")
106
+ for i, file_name in enumerate(files_to_download, 1):
107
+ logger.info(f"[INFO] Downloading {i}/{count_files_to_download} {file_name}")
108
+ gz_file_url = f"{base_url}{file_name}"
109
+ gz_file_path = os.path.join(base_dir, file_name)
110
+ out_file = gz_file_path.replace(".gz", "")
111
+ out_file_name = file_name.replace(".gz", "")
112
+
113
+ retry = 3
114
+ while retry:
115
+ download_file(gz_file_url, gz_file_path)
116
+ if not check_md5(files_to_download[file_name], gz_file_path):
117
+ logger.error(f"[ERROR] MD5 check failed, removing {gz_file_path}")
118
+ retry -= 1
119
+ continue
120
+ decompress_gzip_file(gz_file_path, out_file)
121
+ if not check_md5(uncompressed_md5s[out_file_name], out_file):
122
+ logger.error(f"[ERROR] MD5 check failed, removing {out_file}")
123
+ retry -= 1
124
+ continue
125
+ else:
126
+ break
127
+ else:
128
+ if retry == 0:
129
+ sys.exit('Downloading database files failed too many times')
130
+ os.unlink(gz_file_path)
131
+
132
+ logger.info("[INFO] DB download successful.")
133
+ logger.info(f"[INFO] DB path: {os.path.abspath(base_dir)}")
@@ -0,0 +1,292 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import logging
4
+ import multiprocessing
5
+ import os
6
+ import shutil
7
+ import sys
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+
11
+ from . import create_ngless_template, depth_summary, external_tools, meta_eukaryome_database
12
+
13
+ logger = logging.getLogger("meta_eukaryome_detect.py")
14
+
15
+
16
+ def parse_args(args):
17
+ """Parse Arguments
18
+
19
+ Arguments:
20
+ args: List of args supplied to script.
21
+
22
+ Returns:
23
+ Namespace: assigned args
24
+
25
+ """
26
+ description = "Pathogen, Parasite, Eukaryote and Virus detection in metagenomes.\n"
27
+ formatter = lambda prog: argparse.HelpFormatter(prog, max_help_position=155)
28
+ parser = argparse.ArgumentParser(description=description, formatter_class=formatter)
29
+ subparsers = parser.add_subparsers(title="meta_eukaryome_detect subcommands", metavar="", dest="cmd")
30
+ run = subparsers.add_parser("run", help="Run Meta-Eukaryome-Detection.", formatter_class=formatter)
31
+ run_group = run.add_mutually_exclusive_group(required=True)
32
+ advanced_run = run.add_argument_group('Advanced Options')
33
+ download_db = subparsers.add_parser(
34
+ "download_db", help="Download meta_eukaryome_detect db.", formatter_class=formatter
35
+ )
36
+ run.add_argument(
37
+ "-d",
38
+ "--db_dir",
39
+ help="meta_eukaryome_database directory. Default: META_EUKARYOME_DETECT_DB_DIR envvar",
40
+ default=os.environ.get("META_EUKARYOME_DETECT_DB_DIR"),
41
+ metavar="",
42
+ )
43
+ run_group.add_argument(
44
+ "-i",
45
+ "--input_sample_dir",
46
+ help="""Input directory contining fasta files for a sample
47
+ (suffixes of the form _1.fq.gz .2.fq.gz single.fg.gz allowed)""",
48
+ metavar="",
49
+ )
50
+ run.add_argument(
51
+ "-s",
52
+ "--sample_name",
53
+ help="Name to give to output files, Default: Name of input dir",
54
+ default=None,
55
+ metavar="",
56
+ )
57
+ advanced_run.add_argument(
58
+ "--min_map_qual_plastid",
59
+ help="Minimum Mapping quality to use for plastid reference.",
60
+ default='1',
61
+ metavar="",
62
+ )
63
+ advanced_run.add_argument(
64
+ "--min_map_qual_mito",
65
+ help="Minimum Mapping quality to use for mitochondria reference.",
66
+ default='1',
67
+ metavar="",
68
+ )
69
+ advanced_run.add_argument(
70
+ "--min_map_qual_pr2",
71
+ help="Minimum Mapping quality to use for pr2 reference.",
72
+ default='20',
73
+ metavar="",
74
+ )
75
+ advanced_run.add_argument(
76
+ "--min_map_qual_virulence",
77
+ help="Minimum Mapping quality to use for virulence reference.",
78
+ default='20',
79
+ metavar="",
80
+ )
81
+ advanced_run.add_argument(
82
+ "--min_map_qual_virus",
83
+ help="Minimum Mapping quality to use for virus reference.",
84
+ default='0',
85
+ metavar="",
86
+ )
87
+ run.add_argument(
88
+ "-t",
89
+ "--threads",
90
+ help="number of CPU threads. Default: 4",
91
+ default="4",
92
+ metavar="",
93
+ )
94
+ run.add_argument(
95
+ "-p",
96
+ "--parallel",
97
+ help="Run all mappings in parallel (threads will be split between each of the 5 references)",
98
+ action="store_true",
99
+ default=False,
100
+ )
101
+ run.add_argument(
102
+ "-o",
103
+ "--out_dir",
104
+ help="Output dir. Default: cwd",
105
+ default=os.getcwd(),
106
+ metavar="",
107
+ )
108
+ run.add_argument(
109
+ "--temp_dir",
110
+ help="Directory to store temp files. Default: cwd",
111
+ default=None,
112
+ metavar="",
113
+ )
114
+ run.add_argument(
115
+ "--keep_temp_files",
116
+ help="Do not remove temproary files",
117
+ action="store_true",
118
+ default=False,
119
+ )
120
+ run.add_argument(
121
+ "-v",
122
+ "--verbose",
123
+ help="Verbose output for debugging",
124
+ action="store_true",
125
+ default=False,
126
+ )
127
+ download_db.add_argument("path", help="Download database to given directory.", metavar="dest_path")
128
+ if not args:
129
+ parser.print_help(sys.stderr)
130
+ sys.exit(1)
131
+ download_db.add_argument(
132
+ "-v",
133
+ "--verbose",
134
+ help="Verbose output for debugging",
135
+ action="store_true",
136
+ default=False,
137
+ )
138
+ args = parser.parse_args(args)
139
+ return args
140
+
141
+
142
+ def create_dir(path):
143
+ """Create a directory
144
+
145
+ Will create a directory if it doesnt already exist.
146
+
147
+ Arguments:
148
+ path (str): directory path
149
+ """
150
+ if not os.path.exists(path):
151
+ logger.debug(f'Creating dir: {path}')
152
+ os.makedirs(path)
153
+
154
+
155
+ def start_checks(args):
156
+ """Checks if tool dependencies are available."""
157
+ if not external_tools.check_if_tool_exists("ngless"):
158
+ logger.error("Ngless not found.")
159
+ sys.exit(1)
160
+ if not external_tools.check_if_tool_exists("samtools"):
161
+ logger.error("Samtools not found.")
162
+ sys.exit(1)
163
+ if not os.path.isdir(args.out_dir):
164
+ logger.error(f"Output Directory {args.out_dir} doesnt exist.")
165
+ sys.exit(1)
166
+
167
+
168
+ def run(args):
169
+ input_dir = Path(args.input_sample_dir)
170
+ if not args.sample_name:
171
+ sample_name = input_dir.name
172
+ else:
173
+ sample_name = args.sample_name
174
+
175
+ if args.temp_dir:
176
+ ngless_temp_dir = Path(args.temp_dir) / f'{sample_name}_ngless_temp_dir'
177
+ else:
178
+ ngless_temp_dir = Path(args.out_dir) / f'{sample_name}_ngless_temp_dir'
179
+
180
+ create_dir(ngless_temp_dir)
181
+ ngless_template_dir = ngless_temp_dir / 'ngless_templates'
182
+ create_dir(ngless_template_dir)
183
+ result_dir = Path(args.out_dir) / f'{sample_name}_metaeuk_output'
184
+ create_dir(result_dir)
185
+ templates = create_ngless_template.write_templates(ngless_template_dir, ngless_temp_dir, result_dir)
186
+ logger.info('Running Preprocessing of input files.')
187
+ logger.debug(f'Running template: {templates["preprocess"]}')
188
+ preprocessed_temp_dir = ngless_temp_dir / 'preprocessed'
189
+ create_dir(preprocessed_temp_dir)
190
+ external_tools.ngless(templates['preprocess'], args.threads, preprocessed_temp_dir, input_dir, args.verbose)
191
+ read_count_file = ngless_template_dir / 'preprocess.ngless.output_ngless' / 'fq.tsv'
192
+ shutil.copyfile(read_count_file, result_dir / 'readcounts.tsv')
193
+ mapping_qualities = {
194
+ 'plastid': args.min_map_qual_plastid,
195
+ 'mito': args.min_map_qual_mito,
196
+ 'pr2': args.min_map_qual_pr2,
197
+ 'virulence': args.min_map_qual_virulence,
198
+ 'viruses': args.min_map_qual_virus,
199
+ }
200
+ meta = []
201
+ threads = int(args.threads)
202
+ if threads > 7 and args.parallel:
203
+ if threads < 21:
204
+ jobs = int(threads / 4)
205
+ threads = int(threads / jobs)
206
+ else:
207
+ jobs = 5
208
+ threads = int(threads / jobs)
209
+ else:
210
+ jobs = 1
211
+ for template in ['plastid', 'mito', 'pr2', 'virulence', 'viruses']:
212
+ meta.append(
213
+ (
214
+ template,
215
+ templates[template],
216
+ mapping_qualities[template],
217
+ args,
218
+ str(threads),
219
+ result_dir,
220
+ ngless_temp_dir,
221
+ sample_name,
222
+ )
223
+ )
224
+ p = multiprocessing.Pool(jobs)
225
+ p.map(run_detection, meta)
226
+ if not args.keep_temp_files:
227
+ shutil.rmtree(ngless_temp_dir)
228
+
229
+
230
+ def run_detection(meta):
231
+ template, template_path, mapping_quality, args, threads, result_dir, ngless_temp_dir, sample_name = meta
232
+ logger.debug(f'Running reference: {template} with args: {meta}')
233
+ DB_path = Path(args.db_dir) / f'{template}_reference'
234
+ logger.debug(f'DB Path: {DB_path}')
235
+ external_tools.ngless(
236
+ template_path,
237
+ threads,
238
+ ngless_temp_dir,
239
+ ngless_temp_dir / 'preprocessed',
240
+ args.verbose,
241
+ DB_path,
242
+ )
243
+ unfiltered_bam = ngless_temp_dir / f"{template}_unfiltered.bam"
244
+ filtered_bam = result_dir / f"{template}_filtered.bam"
245
+ unique_bam = ngless_temp_dir / f"{template}_unique.bam"
246
+ unique_depth = ngless_temp_dir / f"{template}_unique.depth"
247
+ external_tools.samtools_unique(filtered_bam, unique_bam, mapping_quality, args.verbose)
248
+ external_tools.samtools_depth(unique_bam, unique_depth, args.verbose)
249
+ depth_summary.get_depth_summary(result_dir, template, unique_depth, Path(args.db_dir), sample_name)
250
+ if not args.keep_temp_files:
251
+ unfiltered_bam.unlink()
252
+ # filtered_bam.unlink()
253
+ unique_depth.unlink()
254
+ logger.info(f'Finished running reference: {template}')
255
+
256
+
257
+ def main():
258
+ args = parse_args(sys.argv[1:])
259
+ if args.verbose:
260
+ logging.basicConfig(
261
+ format="%(asctime)s : [%(levelname)7s] : %(name)s:%(lineno)s %(funcName)20s() : %(message)s",
262
+ datefmt="%H:%M:%S",
263
+ level=logging.DEBUG,
264
+ )
265
+ else:
266
+ logging.basicConfig(
267
+ format="%(asctime)s : %(message)s",
268
+ datefmt="%H:%M:%S",
269
+ level=logging.INFO,
270
+ )
271
+ logger = logging.getLogger("meta_eukaryome_detect.py")
272
+ logger.debug(args)
273
+ start_time = datetime.now()
274
+ logger.info(f'START {start_time.strftime("%Y-%m-%d")}')
275
+ if args.cmd == "download_db":
276
+ meta_eukaryome_database.get_db(args.path)
277
+ if args.cmd == "run":
278
+ start_checks(args)
279
+ if not args.db_dir:
280
+ logger.error("Database_dir argument missing.")
281
+ sys.exit(1)
282
+ else:
283
+ if not os.path.isdir(args.db_dir):
284
+ logger.error("Database_dir not found.")
285
+ sys.exit(1)
286
+ run(args)
287
+ end_time = datetime.now()
288
+ logger.info(f'END {end_time.strftime("%Y-%m-%d")}')
289
+
290
+
291
+ if __name__ == "__main__":
292
+ main()
@@ -0,0 +1,110 @@
1
+ [tool]
2
+ [tool.poetry]
3
+ name = "meta_eukaryome_detect"
4
+ version = "0.1.0"
5
+ homepage = "https://git.embl.de/grp-bork/meta-eukaryome-detect"
6
+ description = "Pathogen, Parasite, Eukaryote and Virus detection in metagenomes.."
7
+ authors = ["Anthony Fullam <anthony.fullam@embl.de>"]
8
+ readme = "README.md"
9
+ license = "MIT"
10
+ classifiers=[
11
+ 'Development Status :: 2 - Pre-Alpha',
12
+ 'Intended Audience :: Developers',
13
+ 'License :: OSI Approved :: MIT License',
14
+ 'Natural Language :: English',
15
+ 'Programming Language :: Python :: 3',
16
+ 'Programming Language :: Python :: 3.11',
17
+ ]
18
+ packages = [
19
+ { include = "meta_eukaryome_detect" },
20
+ { include = "tests", format = "sdist" },
21
+ ]
22
+
23
+ [tool.poetry.dependencies]
24
+ python = ">=3.11,<4.0"
25
+ black = "^23.10.1"
26
+ isort = { version = "^5.8.0", optional = true}
27
+ flake8 = { version = "^3.9.2", optional = true}
28
+ flake8-docstrings = { version = "^1.6.0", optional = true }
29
+ mypy = {version = "^0.900", optional = true}
30
+ pytest = { version = "^6.2.4", optional = true}
31
+ pytest-cov = { version = "^2.12.0", optional = true}
32
+ tox = { version = "^3.20.1", optional = true}
33
+ virtualenv = { version = "^20.2.2", optional = true}
34
+ pip = { version = "^20.3.1", optional = true}
35
+ mkdocs = "^1.5.3"
36
+ mkdocs-include-markdown-plugin = "^6.0.3"
37
+ mkdocs-material = { version = "^6.1.7", optional = true}
38
+ mkdocstrings = "^0.23.0"
39
+ mkdocs-material-extensions = { version = "^1.0.1", optional = true}
40
+ twine = { version = "^3.3.0", optional = true}
41
+ mkdocs-autorefs = "^0.5.0"
42
+ pre-commit = {version = "^2.12.0", optional = true}
43
+ toml = {version = "^0.10.2", optional = true}
44
+ bump2version = {version = "^1.0.1", optional = true}
45
+ flask = "^3.0.0"
46
+ jinja2 = "^3.1.2"
47
+ mkdocstrings-python = "^1.7.3"
48
+ requests = "^2.31.0"
49
+ types-requests = "^2.31.0.10"
50
+ pandas = "^2.2.3"
51
+
52
+ [tool.poetry.extras]
53
+ test = [
54
+ "pytest",
55
+ "black",
56
+ "isort",
57
+ "mypy",
58
+ "flake8",
59
+ "flake8-docstrings",
60
+ "pytest-cov"
61
+ ]
62
+
63
+ dev = ["tox", "pre-commit", "virtualenv", "pip", "twine", "toml", "bump2version"]
64
+
65
+ doc = [
66
+ "mkdocs",
67
+ "mkdocs-include-markdown-plugin",
68
+ "mkdocs-material",
69
+ "mkdocstrings",
70
+ "mkdocs-material-extension",
71
+ "mkdocs-autorefs"
72
+ ]
73
+
74
+ [tool.poetry.scripts]
75
+ meta-eukaryome-detect = 'meta_eukaryome_detect.meta_eukaryome_detect:main'
76
+
77
+ [tool.black]
78
+ line-length = 120
79
+ skip-string-normalization = true
80
+ target-version = ['py311']
81
+ include = '\.pyi?$'
82
+ exclude = '''
83
+ /(
84
+ \.eggs
85
+ | \.git
86
+ | \.hg
87
+ | \.mypy_cache
88
+ | \.tox
89
+ | \.venv
90
+ | _build
91
+ | buck-out
92
+ | build
93
+ | dist
94
+ )/
95
+ '''
96
+
97
+ [tool.isort]
98
+ multi_line_output = 3
99
+ include_trailing_comma = true
100
+ force_grid_wrap = 0
101
+ use_parentheses = true
102
+ ensure_newline_before_comments = true
103
+ line_length = 120
104
+ skip_gitignore = true
105
+ # you can skip files as below
106
+ #skip_glob = docs/conf.py
107
+
108
+ [build-system]
109
+ requires = ["poetry-core>=1.0.0"]
110
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1 @@
1
+ """Unit test package for meta-eukaryome-detect."""
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env python
2
+ """Tests for `meta_eukaryome_detect` package."""