CAT-pack 7.0.0a0__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.
- CAT_pack/__init__.py +1 -0
- CAT_pack/__main__.py +11 -0
- CAT_pack/about.py +7 -0
- CAT_pack/classification.py +138 -0
- CAT_pack/cli.py +684 -0
- CAT_pack/cli_options.py +143 -0
- CAT_pack/config/__init__.py +15 -0
- CAT_pack/config/options.py +133 -0
- CAT_pack/config/settings.py +330 -0
- CAT_pack/config/validation.py +616 -0
- CAT_pack/io/__init__.py +22 -0
- CAT_pack/io/download.py +434 -0
- CAT_pack/io/parsers.py +546 -0
- CAT_pack/io/reference_mapping.py +107 -0
- CAT_pack/io/writers.py +116 -0
- CAT_pack/pipeline.py +365 -0
- CAT_pack/prepare.py +188 -0
- CAT_pack/progress.py +157 -0
- CAT_pack/reads.py +327 -0
- CAT_pack/results.py +78 -0
- CAT_pack/tax.py +479 -0
- CAT_pack/tools/__init__.py +19 -0
- CAT_pack/tools/aligner.py +56 -0
- CAT_pack/tools/bwa_mem.py +106 -0
- CAT_pack/tools/pyrodigal.py +98 -0
- CAT_pack/tools/tool_runner.py +47 -0
- CAT_pack/utils/__init__.py +7 -0
- CAT_pack/utils/check.py +180 -0
- CAT_pack/utils/errors.py +144 -0
- CAT_pack/utils/locking.py +40 -0
- CAT_pack/utils/logging.py +78 -0
- CAT_pack/utils/memory.py +32 -0
- CAT_pack/utils/typing.py +8 -0
- cat_pack-7.0.0a0.dist-info/METADATA +498 -0
- cat_pack-7.0.0a0.dist-info/RECORD +39 -0
- cat_pack-7.0.0a0.dist-info/WHEEL +5 -0
- cat_pack-7.0.0a0.dist-info/entry_points.txt +2 -0
- cat_pack-7.0.0a0.dist-info/licenses/LICENSE.md +7 -0
- cat_pack-7.0.0a0.dist-info/top_level.txt +1 -0
CAT_pack/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CAT pack 7 :)"""
|
CAT_pack/__main__.py
ADDED
CAT_pack/about.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Shared taxonomic classification engine for CAT and BAT"""
|
|
2
|
+
import logging
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
from itertools import chain
|
|
6
|
+
from typing import Mapping, Sequence
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from . import tax
|
|
11
|
+
from .io.parsers import AlignmentInput
|
|
12
|
+
from .results import Classification, ClassificationStatus
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger("CAT_pack")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# DEVNOTE: Add a flowchart in devdocs to make this more clear for non
|
|
18
|
+
# Numpy users
|
|
19
|
+
class ClassificationEngine:
|
|
20
|
+
"""Classify ORFs and combine their total support for one contig or bin."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self, taxonomy: tax.Taxonomy, fastaid2taxid: np.ndarray,
|
|
24
|
+
fraction: Decimal, threads: int = 1,
|
|
25
|
+
) -> None:
|
|
26
|
+
self.taxonomy = taxonomy
|
|
27
|
+
self.hit2taxon = fastaid2taxid
|
|
28
|
+
self.fraction = fraction
|
|
29
|
+
self.threads = threads
|
|
30
|
+
|
|
31
|
+
def classify(
|
|
32
|
+
self, entity_type: str, entity2ORFs: Mapping[str, Sequence[str]], alignment: AlignmentInput,
|
|
33
|
+
) -> Classification:
|
|
34
|
+
log.info(f"Starting classification calculation: {len(entity2ORFs)} {entity_type}.")
|
|
35
|
+
orf_entities_names = sorted(entity2ORFs)
|
|
36
|
+
n_entities = len(orf_entities_names)
|
|
37
|
+
|
|
38
|
+
# Flatten ORFs, ordered by contig/bin.
|
|
39
|
+
ORF_names = list(chain.from_iterable(entity2ORFs[name] for name in orf_entities_names))
|
|
40
|
+
n_ORFs = np.array([len(entity2ORFs[name]) for name in orf_entities_names], dtype=np.int64)
|
|
41
|
+
ORF_starts = np.concatenate(([0], np.cumsum(n_ORFs)))
|
|
42
|
+
ORF2contig_or_bin = np.repeat(np.arange(n_entities), n_ORFs)
|
|
43
|
+
ORF2alignment_index, ORF_has_hits, LCA_per_ORF, n_hits_per_ORF, top_bitscore_per_ORF = (self.per_ORF_stats(ORF_names, alignment))
|
|
44
|
+
|
|
45
|
+
# Combine the ORF lowest common ancestors per contig (or bin)
|
|
46
|
+
# and weighted by their tob bit score(s)
|
|
47
|
+
weighted_LCAs = tax.WeightedLCA.find(
|
|
48
|
+
self.taxonomy, ORF2contig_or_bin, LCA_per_ORF,
|
|
49
|
+
top_bitscore_per_ORF, self.fraction, n_entities)
|
|
50
|
+
log.info("Finished Weighted LCA")
|
|
51
|
+
assignment_starts = np.searchsorted(weighted_LCAs.assignment_entity, np.arange(n_entities + 1))
|
|
52
|
+
|
|
53
|
+
def count_per_entity(ORF_mask: np.ndarray) -> np.ndarray:
|
|
54
|
+
return np.bincount(ORF2contig_or_bin[ORF_mask], minlength=n_entities)
|
|
55
|
+
|
|
56
|
+
n_ORFs_with_hits = count_per_entity(ORF_has_hits)
|
|
57
|
+
n_ORFs_with_taxid = count_per_entity(LCA_per_ORF != tax.ORF.NO_TAXID)
|
|
58
|
+
n_assignments = np.diff(assignment_starts)
|
|
59
|
+
|
|
60
|
+
# DEVNOTE: For in the devdocs. np.select picks the first matching condition
|
|
61
|
+
classification_status = np.select(
|
|
62
|
+
[n_ORFs == 0, n_ORFs_with_hits == 0, n_ORFs_with_taxid == 0, n_assignments == 0],
|
|
63
|
+
[status.value for status in (
|
|
64
|
+
ClassificationStatus.NO_ORFS, ClassificationStatus.NO_HITS,
|
|
65
|
+
ClassificationStatus.NO_TAXIDS, ClassificationStatus.NO_LINEAGE_SUPPORT)],
|
|
66
|
+
default=ClassificationStatus.ASSIGNED.value,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return Classification(
|
|
70
|
+
entity_type=entity_type,
|
|
71
|
+
entities=orf_entities_names,
|
|
72
|
+
status=classification_status,
|
|
73
|
+
orf_offsets=ORF_starts,
|
|
74
|
+
orf_names=ORF_names,
|
|
75
|
+
orf_row=ORF2alignment_index,
|
|
76
|
+
orf_lca=LCA_per_ORF,
|
|
77
|
+
orf_n_hits=n_hits_per_ORF,
|
|
78
|
+
orf_top_bitscore=top_bitscore_per_ORF,
|
|
79
|
+
n_orfs_with_taxid=n_ORFs_with_taxid,
|
|
80
|
+
assignment_offsets=assignment_starts,
|
|
81
|
+
weighted=weighted_LCAs,
|
|
82
|
+
alignment=alignment,
|
|
83
|
+
taxonomy=self.taxonomy,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def per_ORF_stats(self, ORF_names: Sequence[str], alignment: AlignmentInput):
|
|
87
|
+
n_ORFs = len(ORF_names)
|
|
88
|
+
|
|
89
|
+
ORF2alignment_index = np.fromiter(
|
|
90
|
+
(alignment.orf_index.get(ORF, tax.ORF.NO_HIT) for ORF in ORF_names), dtype=np.int64, count=n_ORFs)
|
|
91
|
+
ORF_has_hits = ORF2alignment_index != tax.ORF.NO_HIT
|
|
92
|
+
|
|
93
|
+
alignment_indices = ORF2alignment_index[ORF_has_hits]
|
|
94
|
+
hits_start = alignment.orf_offsets[alignment_indices]
|
|
95
|
+
hits_end = alignment.orf_offsets[alignment_indices + 1]
|
|
96
|
+
|
|
97
|
+
hit_taxids = self.hit2taxon[alignment.hit_ids]
|
|
98
|
+
log.info(f"hit taxonomy lookup: {len(hit_taxids)}accepted hits")
|
|
99
|
+
n_alignment_ORFs = len(alignment.orf_names)
|
|
100
|
+
workers = min(self.threads, max(1, n_alignment_ORFs // 20_000))
|
|
101
|
+
|
|
102
|
+
log.info(f"Starting ORF LCA calculation: with {n_alignment_ORFs} alignment ORFs, "
|
|
103
|
+
f"{len(hit_taxids)} accepted hits, and {workers} workers.")
|
|
104
|
+
if workers > 1:
|
|
105
|
+
ORF_bounds = np.linspace(
|
|
106
|
+
0, n_alignment_ORFs, workers + 1, dtype=np.int64
|
|
107
|
+
)
|
|
108
|
+
hit_bounds = alignment.orf_offsets[ORF_bounds]
|
|
109
|
+
offset_batches = (
|
|
110
|
+
alignment.orf_offsets[start:end + 1] - hit_start
|
|
111
|
+
for start, end, hit_start in zip(
|
|
112
|
+
ORF_bounds[:-1], ORF_bounds[1:], hit_bounds[:-1]
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
hit_batches = (
|
|
116
|
+
hit_taxids[start:end]
|
|
117
|
+
for start, end in zip(hit_bounds[:-1], hit_bounds[1:])
|
|
118
|
+
)
|
|
119
|
+
with ThreadPoolExecutor(max_workers=workers) as pool:
|
|
120
|
+
alignment_ORF_LCAs = np.concatenate(list(pool.map(
|
|
121
|
+
self.taxonomy.find_LCA_for_ORF,
|
|
122
|
+
offset_batches, hit_batches)))
|
|
123
|
+
else:
|
|
124
|
+
alignment_ORF_LCAs = self.taxonomy.find_LCA_for_ORF(
|
|
125
|
+
alignment.orf_offsets, hit_taxids
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
LCA_per_ORF = np.full(n_ORFs, tax.ORF.NO_TAXID, dtype=np.int32)
|
|
129
|
+
LCA_per_ORF[ORF_has_hits] = alignment_ORF_LCAs[alignment_indices]
|
|
130
|
+
|
|
131
|
+
n_hits_per_ORF = np.zeros(n_ORFs, dtype=np.int64)
|
|
132
|
+
n_hits_per_ORF[ORF_has_hits] = hits_end - hits_start
|
|
133
|
+
|
|
134
|
+
top_bitscore_per_ORF = np.zeros(n_ORFs, dtype=np.int64)
|
|
135
|
+
top_bitscore_per_ORF[ORF_has_hits] = alignment.bitscores[hits_start]
|
|
136
|
+
|
|
137
|
+
return ORF2alignment_index, ORF_has_hits, LCA_per_ORF, n_hits_per_ORF, top_bitscore_per_ORF
|
|
138
|
+
|