MetaPont 0.1.2__tar.gz → 0.1.4__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.
Files changed (31) hide show
  1. {metapont-0.1.2/src/MetaPont.egg-info → metapont-0.1.4}/PKG-INFO +53 -17
  2. {metapont-0.1.2 → metapont-0.1.4}/README.md +52 -16
  3. {metapont-0.1.2 → metapont-0.1.4}/pyproject.toml +1 -1
  4. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/__init__.py +1 -1
  5. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/analysis.py +15 -7
  6. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/cli.py +75 -12
  7. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/compat.py +1 -1
  8. metapont-0.1.4/src/MetaPont/pipeline.py +868 -0
  9. {metapont-0.1.2 → metapont-0.1.4/src/MetaPont.egg-info}/PKG-INFO +53 -17
  10. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont.egg-info/SOURCES.txt +2 -1
  11. metapont-0.1.4/tests/test_pipeline_v014.py +313 -0
  12. metapont-0.1.2/src/MetaPont/pipeline.py +0 -601
  13. {metapont-0.1.2 → metapont-0.1.4}/LICENSE +0 -0
  14. {metapont-0.1.2 → metapont-0.1.4}/setup.cfg +0 -0
  15. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/constants.py +0 -0
  16. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/layout.py +0 -0
  17. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/old/Contig_Coverage_Summary.py +0 -0
  18. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/old/Extract_By_Function.py +0 -0
  19. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/old/Extract_By_Taxa.py +0 -0
  20. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/old/Extract_Function_By_Taxa.py +0 -0
  21. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/old/Extract_Reads_By_Function.py +0 -0
  22. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/old/Function_By_Taxa.py +0 -0
  23. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/old/MetaPont_Combine.py +0 -0
  24. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/old/Report_Contig_Lineage.py +0 -0
  25. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/old/__init__.py +0 -0
  26. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/old/constants.py +0 -0
  27. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont/parsers.py +0 -0
  28. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont.egg-info/dependency_links.txt +0 -0
  29. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont.egg-info/entry_points.txt +0 -0
  30. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont.egg-info/requires.txt +0 -0
  31. {metapont-0.1.2 → metapont-0.1.4}/src/MetaPont.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MetaPont
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Integrated analysis of metagenomic assembly, readmapping, Kraken2 and eggNOG-mapper outputs.
5
5
  Author-email: Nicholas Dimonaco <nicholas@dimonaco.co.uk>
6
6
  License-Expression: GPL-3.0-or-later
@@ -20,7 +20,7 @@ Provides-Extra: test
20
20
  Requires-Dist: pytest>=7.0; extra == "test"
21
21
  Dynamic: license-file
22
22
 
23
- # MetaPont v0.1.2
23
+ # MetaPont v0.1.4
24
24
 
25
25
  MetaPont integrates existing metagenomic workflow outputs into per-sample and
26
26
  cross-sample tables for contigs, taxonomy, readmapping support, and eggNOG-mapper
@@ -79,10 +79,20 @@ sample directory for the relevant files. The main inputs are:
79
79
  | Contig FASTA | Contig inventory and lengths when a readmapping contig summary is absent |
80
80
 
81
81
  Inputs are discovered independently, so incomplete samples can still be
82
- included. Missing or stale outputs are reported as warnings; MetaPont does not
83
- rerun missing upstream analyses. Kraken2 assignments shorter than the selected
84
- minimum contig length are omitted from taxonomy attribution by default. This
85
- filter does not rerun Kraken2.
82
+ included. Missing or empty core inputs are reported as warnings; MetaPont does not
83
+ rerun missing upstream analyses. The inventory checks the files MetaPont
84
+ actually consumes: contigs, Pyrodigal GFF/protein FASTA, eggNOG annotation TSV,
85
+ Kraken2 per-contig output/report, and readmapping contig/CDS summaries. Optional
86
+ sidecars (for example BAM/index, eggNOG XLSX, MPA report, Pyrodigal NT/QC files)
87
+ do not make an otherwise usable MetaPont input set incomplete.
88
+
89
+ MetaPont deliberately does not use modification-time comparisons to decide
90
+ whether outputs are stale. Scratch-retention commands such as `touch` can
91
+ update timestamps without changing file contents, so timestamp ordering is not
92
+ a reliable freshness test. The inventory reports presence/completeness, not
93
+ whether an upstream tool was rerun against the latest source assembly. Kraken2
94
+ assignments shorter than the selected minimum contig length are omitted from
95
+ taxonomy attribution by default. This filter does not rerun Kraken2.
86
96
 
87
97
  ## Commands
88
98
 
@@ -103,9 +113,10 @@ Useful options:
103
113
  | Option | Default | Description |
104
114
  | --- | --- | --- |
105
115
  | `--force` | Off | Regenerate outputs even when the input signature matches a cached run. |
106
- | `--no-heal-flags` | Off | Do not recreate missing upstream success flags when their outputs are complete and current. |
116
+ | `--no-heal-flags` | Off | Do not recreate missing upstream success flags when the required non-empty core outputs are present. |
107
117
  | `--min-contig-length N` | `2500` | Minimum Kraken2-assigned contig length retained for taxonomy attribution; use `0` to disable the filter. |
108
- | `--taxon-function-abundance` | Off | Build the expanded taxon-by-function table. This can use substantial memory and disk space. |
118
+ | `--taxon-function-abundance` | Off | Build the expanded taxon-by-function table using a disk-backed SQLite aggregation. This adds disk I/O and temporary disk usage. |
119
+ | `--signature-mode` | `sampled` | Cache input detection. `sampled` hashes seven evenly spaced 128 KiB blocks from each input and is insensitive to `touch`; `full` hashes every byte and is slower but provides whole-file SHA256 verification. |
109
120
  | `--plots` | Off | Write optional quick-look PNG plots. Requires the `plots` installation extra. |
110
121
 
111
122
  `metapont combine` is an alias for `metapont run`. The `metapont-combine`
@@ -117,9 +128,13 @@ command is also available for compatibility with the earlier combine command.
117
128
  metapont inventory --root-dir /path/to/workflow-output --prefix E
118
129
  ```
119
130
 
120
- The inventory reports discovered inputs, expected outputs, success flags, and
121
- warnings for each sample. Add `--heal-flags` to recreate a missing stage flag
122
- only when its corresponding output files are complete and current.
131
+ The inventory reports discovered inputs, core output availability, success
132
+ flags, and warnings for each sample. Add `--heal-flags` to recreate a missing
133
+ stage flag only when the full expected product set for that upstream stage is
134
+ present (including its QC/sidecar outputs where applicable). This is stricter
135
+ than MetaPont's integration-input check, so it does not mark an upstream stage
136
+ complete merely because MetaPont can consume a subset of its outputs. Timestamp
137
+ freshness is intentionally not inferred.
123
138
 
124
139
  ### Summarise readmapping coverage
125
140
 
@@ -140,7 +155,7 @@ Find taxa supporting a functional ID:
140
155
 
141
156
  ```bash
142
157
  metapont query-function \
143
- --root-dir /path/to/workflow-output \
158
+ --root-dir /path/to/MetaPont_Results \
144
159
  --function-id GO:0016597 \
145
160
  --category GO \
146
161
  --rank Genus \
@@ -157,7 +172,7 @@ Find the most supported features associated with a taxon:
157
172
 
158
173
  ```bash
159
174
  metapont query-taxon \
160
- --root-dir /path/to/workflow-output \
175
+ --root-dir /path/to/MetaPont_Results \
161
176
  --taxon Bacillus \
162
177
  --category GO \
163
178
  --rank Genus \
@@ -170,6 +185,20 @@ full lineage. `--category` is required for this query. Both query commands
170
185
  accept an explicit `--gene-table` path; plain TSV and gzip-compressed TSV files
171
186
  are supported.
172
187
 
188
+ Build the expanded taxon-by-function table **after** a normal MetaPont run,
189
+ without re-parsing the original eggNOG files or rerunning the integrated
190
+ analysis:
191
+
192
+ ```bash
193
+ metapont taxon-function-abundance \
194
+ --root-dir /path/to/workflow-output
195
+ ```
196
+
197
+ Alternatively, provide `--gene-table` to use an existing
198
+ `gene_annotations.tsv.gz`. This post-processing command uses a temporary
199
+ SQLite database to keep aggregation memory bounded. The output is written to
200
+ `MetaPont_Results/functional/taxon_function_abundance.tsv` by default.
201
+
173
202
  ## Results
174
203
 
175
204
  The default output tree is:
@@ -182,7 +211,7 @@ MetaPont_Results/
182
211
  ├── functional/
183
212
  │ ├── functional_abundance.tsv
184
213
  │ ├── matrices/
185
- │ └── taxon_function_abundance.tsv
214
+ │ └── taxon_function_abundance.tsv # optional; only when requested
186
215
  ├── inventory/
187
216
  │ └── sample_inventory.tsv
188
217
  ├── metadata/
@@ -212,9 +241,16 @@ Supported functional categories include `COG`, `eggNOG_OG`, `GO`, `EC`,
212
241
  is not a count of unique reads across genes. A read can support multiple genes
213
242
  or annotations, so functional support values are not mutually exclusive.
214
243
  Taxonomic relative abundance uses total mapped-read support as its denominator.
215
-
216
- Samples with missing or stale workflow outputs are retained where possible and
217
- listed in `summary/pipeline_warnings.tsv`. A clean run writes
244
+ The new `Percent_Mapped_Reads_on_Classified_Contigs` field in the sample summary
245
+ shows how much of that denominator lies on Kraken-classified contigs.
246
+
247
+ Samples with missing or empty core workflow inputs are retained where
248
+ possible and listed in `summary/pipeline_warnings.tsv`. The sample summary also
249
+ reports genes processed versus genes with at least one functional feature, and
250
+ readmapping support on Kraken-classified, unclassified, length-filtered and
251
+ Kraken-unassigned contigs. `Genes_Annotated` remains for compatibility and is
252
+ equivalent to `Genes_Processed`; use `Genes_With_Function` when you specifically
253
+ mean genes with a parsed functional feature. A clean run writes
218
254
  `MetaPont_analysis.success`; a run with warnings writes
219
255
  `completed_with_warnings.flag`.
220
256
 
@@ -1,4 +1,4 @@
1
- # MetaPont v0.1.2
1
+ # MetaPont v0.1.4
2
2
 
3
3
  MetaPont integrates existing metagenomic workflow outputs into per-sample and
4
4
  cross-sample tables for contigs, taxonomy, readmapping support, and eggNOG-mapper
@@ -57,10 +57,20 @@ sample directory for the relevant files. The main inputs are:
57
57
  | Contig FASTA | Contig inventory and lengths when a readmapping contig summary is absent |
58
58
 
59
59
  Inputs are discovered independently, so incomplete samples can still be
60
- included. Missing or stale outputs are reported as warnings; MetaPont does not
61
- rerun missing upstream analyses. Kraken2 assignments shorter than the selected
62
- minimum contig length are omitted from taxonomy attribution by default. This
63
- filter does not rerun Kraken2.
60
+ included. Missing or empty core inputs are reported as warnings; MetaPont does not
61
+ rerun missing upstream analyses. The inventory checks the files MetaPont
62
+ actually consumes: contigs, Pyrodigal GFF/protein FASTA, eggNOG annotation TSV,
63
+ Kraken2 per-contig output/report, and readmapping contig/CDS summaries. Optional
64
+ sidecars (for example BAM/index, eggNOG XLSX, MPA report, Pyrodigal NT/QC files)
65
+ do not make an otherwise usable MetaPont input set incomplete.
66
+
67
+ MetaPont deliberately does not use modification-time comparisons to decide
68
+ whether outputs are stale. Scratch-retention commands such as `touch` can
69
+ update timestamps without changing file contents, so timestamp ordering is not
70
+ a reliable freshness test. The inventory reports presence/completeness, not
71
+ whether an upstream tool was rerun against the latest source assembly. Kraken2
72
+ assignments shorter than the selected minimum contig length are omitted from
73
+ taxonomy attribution by default. This filter does not rerun Kraken2.
64
74
 
65
75
  ## Commands
66
76
 
@@ -81,9 +91,10 @@ Useful options:
81
91
  | Option | Default | Description |
82
92
  | --- | --- | --- |
83
93
  | `--force` | Off | Regenerate outputs even when the input signature matches a cached run. |
84
- | `--no-heal-flags` | Off | Do not recreate missing upstream success flags when their outputs are complete and current. |
94
+ | `--no-heal-flags` | Off | Do not recreate missing upstream success flags when the required non-empty core outputs are present. |
85
95
  | `--min-contig-length N` | `2500` | Minimum Kraken2-assigned contig length retained for taxonomy attribution; use `0` to disable the filter. |
86
- | `--taxon-function-abundance` | Off | Build the expanded taxon-by-function table. This can use substantial memory and disk space. |
96
+ | `--taxon-function-abundance` | Off | Build the expanded taxon-by-function table using a disk-backed SQLite aggregation. This adds disk I/O and temporary disk usage. |
97
+ | `--signature-mode` | `sampled` | Cache input detection. `sampled` hashes seven evenly spaced 128 KiB blocks from each input and is insensitive to `touch`; `full` hashes every byte and is slower but provides whole-file SHA256 verification. |
87
98
  | `--plots` | Off | Write optional quick-look PNG plots. Requires the `plots` installation extra. |
88
99
 
89
100
  `metapont combine` is an alias for `metapont run`. The `metapont-combine`
@@ -95,9 +106,13 @@ command is also available for compatibility with the earlier combine command.
95
106
  metapont inventory --root-dir /path/to/workflow-output --prefix E
96
107
  ```
97
108
 
98
- The inventory reports discovered inputs, expected outputs, success flags, and
99
- warnings for each sample. Add `--heal-flags` to recreate a missing stage flag
100
- only when its corresponding output files are complete and current.
109
+ The inventory reports discovered inputs, core output availability, success
110
+ flags, and warnings for each sample. Add `--heal-flags` to recreate a missing
111
+ stage flag only when the full expected product set for that upstream stage is
112
+ present (including its QC/sidecar outputs where applicable). This is stricter
113
+ than MetaPont's integration-input check, so it does not mark an upstream stage
114
+ complete merely because MetaPont can consume a subset of its outputs. Timestamp
115
+ freshness is intentionally not inferred.
101
116
 
102
117
  ### Summarise readmapping coverage
103
118
 
@@ -118,7 +133,7 @@ Find taxa supporting a functional ID:
118
133
 
119
134
  ```bash
120
135
  metapont query-function \
121
- --root-dir /path/to/workflow-output \
136
+ --root-dir /path/to/MetaPont_Results \
122
137
  --function-id GO:0016597 \
123
138
  --category GO \
124
139
  --rank Genus \
@@ -135,7 +150,7 @@ Find the most supported features associated with a taxon:
135
150
 
136
151
  ```bash
137
152
  metapont query-taxon \
138
- --root-dir /path/to/workflow-output \
153
+ --root-dir /path/to/MetaPont_Results \
139
154
  --taxon Bacillus \
140
155
  --category GO \
141
156
  --rank Genus \
@@ -148,6 +163,20 @@ full lineage. `--category` is required for this query. Both query commands
148
163
  accept an explicit `--gene-table` path; plain TSV and gzip-compressed TSV files
149
164
  are supported.
150
165
 
166
+ Build the expanded taxon-by-function table **after** a normal MetaPont run,
167
+ without re-parsing the original eggNOG files or rerunning the integrated
168
+ analysis:
169
+
170
+ ```bash
171
+ metapont taxon-function-abundance \
172
+ --root-dir /path/to/workflow-output
173
+ ```
174
+
175
+ Alternatively, provide `--gene-table` to use an existing
176
+ `gene_annotations.tsv.gz`. This post-processing command uses a temporary
177
+ SQLite database to keep aggregation memory bounded. The output is written to
178
+ `MetaPont_Results/functional/taxon_function_abundance.tsv` by default.
179
+
151
180
  ## Results
152
181
 
153
182
  The default output tree is:
@@ -160,7 +189,7 @@ MetaPont_Results/
160
189
  ├── functional/
161
190
  │ ├── functional_abundance.tsv
162
191
  │ ├── matrices/
163
- │ └── taxon_function_abundance.tsv
192
+ │ └── taxon_function_abundance.tsv # optional; only when requested
164
193
  ├── inventory/
165
194
  │ └── sample_inventory.tsv
166
195
  ├── metadata/
@@ -190,9 +219,16 @@ Supported functional categories include `COG`, `eggNOG_OG`, `GO`, `EC`,
190
219
  is not a count of unique reads across genes. A read can support multiple genes
191
220
  or annotations, so functional support values are not mutually exclusive.
192
221
  Taxonomic relative abundance uses total mapped-read support as its denominator.
193
-
194
- Samples with missing or stale workflow outputs are retained where possible and
195
- listed in `summary/pipeline_warnings.tsv`. A clean run writes
222
+ The new `Percent_Mapped_Reads_on_Classified_Contigs` field in the sample summary
223
+ shows how much of that denominator lies on Kraken-classified contigs.
224
+
225
+ Samples with missing or empty core workflow inputs are retained where
226
+ possible and listed in `summary/pipeline_warnings.tsv`. The sample summary also
227
+ reports genes processed versus genes with at least one functional feature, and
228
+ readmapping support on Kraken-classified, unclassified, length-filtered and
229
+ Kraken-unassigned contigs. `Genes_Annotated` remains for compatibility and is
230
+ equivalent to `Genes_Processed`; use `Genes_With_Function` when you specifically
231
+ mean genes with a parsed functional feature. A clean run writes
196
232
  `MetaPont_analysis.success`; a run with warnings writes
197
233
  `completed_with_warnings.flag`.
198
234
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "MetaPont"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  authors = [
9
9
  { name = "Nicholas Dimonaco", email = "nicholas@dimonaco.co.uk" }
10
10
  ]
@@ -1,6 +1,6 @@
1
1
  """MetaPont: integrated analysis of metagenomic workflow outputs."""
2
2
 
3
- __version__ = "0.1.2"
3
+ __version__ = "0.1.4"
4
4
  MetaPont_Version = f"v{__version__}"
5
5
 
6
6
  __all__ = ["__version__", "MetaPont_Version"]
@@ -11,7 +11,7 @@ from typing import Any
11
11
 
12
12
  from .constants import FEATURE_COLUMNS
13
13
  from .layout import discover_samples
14
- from .parsers import open_text, parse_contig_summary
14
+ from .parsers import open_text, parse_contig_summary, split_features
15
15
  from .pipeline import write_tsv
16
16
 
17
17
 
@@ -25,12 +25,15 @@ def _read_gene_table(path: str | Path):
25
25
  def coverage_summary(root_dir: str | Path, prefixes: str, output: str | Path | None = None,
26
26
  read_length: int | None = None) -> tuple[Path, int]:
27
27
  root = Path(root_dir).expanduser().resolve()
28
+ if read_length is not None and read_length <= 0:
29
+ raise ValueError("read_length must be a positive integer.")
28
30
  out = Path(output).expanduser().resolve() if output else root / "MetaPont_Results" / "summary" / "contig_coverage_summary.tsv"
29
31
  samples = discover_samples(root, prefixes)
30
32
  rows: list[dict[str, Any]] = []
31
33
  for sample in samples:
32
- if sample.contig_summary is None:
33
- rows.append({"Sample": sample.name, "Status": "missing_contig_summary"})
34
+ if sample.contig_summary is None or not sample.contig_summary.is_file() or sample.contig_summary.stat().st_size == 0:
35
+ rows.append({"Sample": sample.name, "Status": "missing_or_empty_contig_summary",
36
+ "Contig_Summary": str(sample.contig_summary or "")})
34
37
  continue
35
38
  n_contigs = total_mapped = total_reported = 0
36
39
  mapped_counts: list[int] = []
@@ -88,8 +91,9 @@ def coverage_summary(root_dir: str | Path, prefixes: str, output: str | Path | N
88
91
  return out, len(rows)
89
92
 
90
93
 
91
- def _feature_values(cell: str) -> list[str]:
92
- return [part.strip() for part in cell.replace(",", "|").replace(";", "|").split("|") if part.strip() and part.strip() != "-"]
94
+ def _feature_values(cell: str, category: str) -> list[str]:
95
+ """Use the same feature parsing rules as the main annotation pipeline."""
96
+ return split_features(cell, category)
93
97
 
94
98
 
95
99
  def query_function(gene_table: str | Path, function_id: str, output: str | Path,
@@ -98,6 +102,8 @@ def query_function(gene_table: str | Path, function_id: str, output: str | Path,
98
102
  path = Path(gene_table)
99
103
  if not path.is_file():
100
104
  raise FileNotFoundError(f"Gene annotation table not found: {path}")
105
+ if top_n is not None and top_n < 1:
106
+ raise ValueError("top_n must be a positive integer.")
101
107
  cat_norm = category.upper().replace("-", "_") if category else None
102
108
  cat_aliases = {k.upper(): k for k in FEATURE_COLUMNS}
103
109
  if cat_norm and cat_norm not in cat_aliases:
@@ -110,7 +116,7 @@ def query_function(gene_table: str | Path, function_id: str, output: str | Path,
110
116
  denominator: dict[str, int] = defaultdict(int)
111
117
  categories = [chosen_cat] if chosen_cat else list(FEATURE_COLUMNS)
112
118
  for row in _read_gene_table(path):
113
- found = any(function_id in _feature_values(row.get(cat, "")) for cat in categories)
119
+ found = any(function_id in _feature_values(row.get(cat, ""), cat) for cat in categories)
114
120
  if not found:
115
121
  continue
116
122
  sample = row.get("Sample", "")
@@ -151,6 +157,8 @@ def query_taxon(gene_table: str | Path, taxon_query: str, output: str | Path,
151
157
  path = Path(gene_table)
152
158
  if not path.is_file():
153
159
  raise FileNotFoundError(f"Gene annotation table not found: {path}")
160
+ if top_n < 1:
161
+ raise ValueError("top_n must be a positive integer.")
154
162
  category_key = category.upper().replace("-", "_")
155
163
  aliases = {key.upper(): key for key in FEATURE_COLUMNS}
156
164
  if category_key not in aliases:
@@ -167,7 +175,7 @@ def query_taxon(gene_table: str | Path, taxon_query: str, output: str | Path,
167
175
  continue
168
176
  sample = row.get("Sample", "")
169
177
  support = int(float(row.get("Gene_Read_Support", 0) or 0))
170
- for feature in _feature_values(row.get(category_name, "")):
178
+ for feature in _feature_values(row.get(category_name, ""), category_name):
171
179
  counts[(sample, value, lineage, feature)][0] += 1
172
180
  counts[(sample, value, lineage, feature)][1] += support
173
181
  per_sample: dict[str, list[dict[str, Any]]] = defaultdict(list)
@@ -10,7 +10,7 @@ from pathlib import Path
10
10
  from . import MetaPont_Version, __version__
11
11
  from .analysis import coverage_summary, query_function, query_taxon
12
12
  from .layout import discover_samples
13
- from .pipeline import OUTPUT_DIRNAME, run_analysis, write_inventory
13
+ from .pipeline import OUTPUT_DIRNAME, build_taxon_function_abundance, run_analysis, write_inventory
14
14
 
15
15
 
16
16
  def _add_root_prefix(parser: argparse.ArgumentParser) -> None:
@@ -22,11 +22,19 @@ def _add_root_prefix(parser: argparse.ArgumentParser) -> None:
22
22
  help=f"Results directory (default: ROOT/{OUTPUT_DIRNAME}).")
23
23
 
24
24
 
25
- def _gene_table(root_dir: str, output_dir: str | None, supplied: str | None) -> Path:
25
+ def _output_root(root_dir: str | None, output_dir: str | None) -> Path:
26
+ if output_dir:
27
+ return Path(output_dir).expanduser().resolve()
28
+ if not root_dir:
29
+ raise ValueError("Provide --root-dir, --output-dir or --gene-table.")
30
+ root = Path(root_dir).expanduser().resolve()
31
+ return root if root.name == OUTPUT_DIRNAME else root / OUTPUT_DIRNAME
32
+
33
+
34
+ def _gene_table(root_dir: str | None, output_dir: str | None, supplied: str | None) -> Path:
26
35
  if supplied:
27
36
  return Path(supplied).expanduser().resolve()
28
- output_root = Path(output_dir).expanduser().resolve() if output_dir else Path(root_dir).expanduser().resolve() / OUTPUT_DIRNAME
29
- return output_root / "combined" / "gene_annotations.tsv.gz"
37
+ return _output_root(root_dir, output_dir) / "combined" / "gene_annotations.tsv.gz"
30
38
 
31
39
 
32
40
  def _create_plots(output_root: Path) -> list[str]:
@@ -91,20 +99,24 @@ def main(argv: list[str] | None = None) -> int:
91
99
  run_p = sub.add_parser("run", aliases=["combine"], help="Build the integrated sample inventory, contig table and abundance summaries.")
92
100
  _add_root_prefix(run_p)
93
101
  run_p.add_argument("--force", action="store_true", help="Regenerate outputs even when the recorded input signature is unchanged.")
94
- run_p.add_argument("--no-heal-flags", action="store_true", help="Do not recreate missing pipeline flags when all corresponding outputs exist.")
102
+ run_p.add_argument("--no-heal-flags", action="store_true", help="Do not recreate missing upstream flags, even when the full expected stage output set is present.")
95
103
  run_p.add_argument("--min-contig-length", type=int, default=2500, help="Minimum contig length to retain an existing Kraken2 assignment; classifications are not rerun.")
96
104
  run_p.add_argument("--plots", action="store_true", help="Generate optional quick-look PNG plots (requires matplotlib).")
105
+ run_p.add_argument(
106
+ "--signature-mode", choices=("sampled", "full"), default="sampled",
107
+ help="Input change detection: sampled hashes seven evenly spaced blocks (touch-safe, default), or full SHA256 of every input byte (slower).",
108
+ )
97
109
  run_p.add_argument(
98
110
  "--taxon-function-abundance",
99
111
  action="store_true",
100
112
  help=(
101
- "Generate the expanded taxon-function abundance table. "
102
- "Can require substantial memory and disk space."
113
+ "Generate the expanded taxon-function abundance table using a disk-backed aggregation. "
114
+ "This requires additional disk I/O and temporary space."
103
115
  ),
104
116
  )
105
117
  inv_p = sub.add_parser("inventory", help="Audit per-sample inputs, pipeline outputs and success flags.")
106
118
  _add_root_prefix(inv_p)
107
- inv_p.add_argument("--heal-flags", action="store_true", help="Recreate missing flags only when the relevant output set is complete and current.")
119
+ inv_p.add_argument("--heal-flags", action="store_true", help="Recreate missing upstream flags only when the full expected stage output set is present; timestamps are not used.")
108
120
 
109
121
  cov_p = sub.add_parser("coverage", help="Summarise per-contig readmapping statistics by sample.")
110
122
  _add_root_prefix(cov_p)
@@ -114,8 +126,9 @@ def main(argv: list[str] | None = None) -> int:
114
126
  qf_p = sub.add_parser("query-function", help="Find taxa supporting a particular eggNOG functional ID.")
115
127
  qf_p.add_argument("--root-dir", "-d", default=None,
116
128
  help="Workflow root used to find the default gene table; not needed with --gene-table or --output-dir.")
129
+ qf_p.add_argument("--gene-table", default=None,
130
+ help="Gene annotation TSV/TSV.GZ; defaults to the integrated output table.")
117
131
  qf_p.add_argument("--output-dir", default=None)
118
- qf_p.add_argument("--gene-table", default=None, help="Gene annotation TSV/TSV.GZ; defaults to the integrated output table.")
119
132
  qf_p.add_argument("--function-id", "-f", required=True, help="Exact function ID, e.g. GO:0016597 or EC:2.7.11.1.")
120
133
  qf_p.add_argument("--category", "-c", default=None, help="Optional annotation category, e.g. GO, EC, KEGG_KO, PFAM.")
121
134
  qf_p.add_argument("--rank", default="Genus", help="Taxonomic rank for attribution (default: Genus).")
@@ -123,15 +136,25 @@ def main(argv: list[str] | None = None) -> int:
123
136
  qf_p.add_argument("--output", "-o", required=True)
124
137
 
125
138
  qt_p = sub.add_parser("query-taxon", help="Report the most supported functions for a taxon.")
126
- qt_p.add_argument("--root-dir", "-d", required=True)
139
+ qt_p.add_argument("--root-dir", "-d", default=None)
140
+ qt_p.add_argument("--gene-table", default=None,
141
+ help="Gene annotation TSV/TSV.GZ; defaults to the integrated output table.")
127
142
  qt_p.add_argument("--output-dir", default=None)
128
- qt_p.add_argument("--gene-table", default=None)
129
143
  qt_p.add_argument("--taxon", "-t", required=True, help="Taxon name or lineage token, e.g. Bacillus or g__Bacillus.")
130
144
  qt_p.add_argument("--category", "-c", required=True, help="Annotation category, e.g. GO, EC, KEGG_KO.")
131
145
  qt_p.add_argument("--rank", default="Genus")
132
146
  qt_p.add_argument("--top", type=int, default=20)
133
147
  qt_p.add_argument("--output", "-o", required=True)
134
148
 
149
+ tf_p = sub.add_parser(
150
+ "taxon-function-abundance", aliases=["taxon-function"],
151
+ help="Build the expanded taxon-by-function table from an existing gene annotation table without rerunning the main analysis.",
152
+ )
153
+ tf_p.add_argument("--root-dir", "-d", default=None, help="Workflow root or MetaPont_Results directory.")
154
+ tf_p.add_argument("--gene-table", default=None, help="Existing gene_annotations.tsv or .tsv.gz file.")
155
+ tf_p.add_argument("--output-dir", default=None, help="MetaPont results directory (default derived from --root-dir).")
156
+ tf_p.add_argument("--output", "-o", default=None, help="Output TSV path (default: OUTPUT_DIR/functional/taxon_function_abundance.tsv).")
157
+
135
158
  args = parser.parse_args(argv)
136
159
  try:
137
160
  if args.command in {"run", "combine"}:
@@ -141,6 +164,7 @@ def main(argv: list[str] | None = None) -> int:
141
164
  heal_flags=not args.no_heal_flags,
142
165
  force=args.force,
143
166
  include_taxon_function_abundance=args.taxon_function_abundance,
167
+ signature_mode=args.signature_mode,
144
168
  )
145
169
  if args.plots:
146
170
  output_root = Path(result["output_dir"])
@@ -160,19 +184,58 @@ def main(argv: list[str] | None = None) -> int:
160
184
  print(f"Healed {len(events)} flags.")
161
185
  return 0
162
186
  if args.command == "coverage":
163
- out, count = coverage_summary(args.root_dir, args.prefix, args.output, args.read_length)
187
+ supplied_root = Path(args.root_dir).expanduser().resolve()
188
+ samples_root = supplied_root.parent if supplied_root.name == OUTPUT_DIRNAME else supplied_root
189
+ default_output_root = (
190
+ Path(args.output_dir).expanduser().resolve() if args.output_dir
191
+ else supplied_root if supplied_root.name == OUTPUT_DIRNAME
192
+ else supplied_root / OUTPUT_DIRNAME
193
+ )
194
+ default_output = default_output_root / "summary" / "contig_coverage_summary.tsv"
195
+ out, count = coverage_summary(
196
+ samples_root, args.prefix, args.output or default_output, args.read_length,
197
+ )
164
198
  print(f"Coverage summary written for {count} samples: {out}")
165
199
  return 0
166
200
  if args.command == "query-function":
201
+ if args.root_dir is None and args.gene_table is None and args.output_dir is None:
202
+ raise ValueError("Provide --root-dir, --output-dir or --gene-table.")
203
+
204
+ # A MetaPont_Results directory can be supplied directly as root-dir.
205
+ if args.root_dir and Path(args.root_dir).expanduser().resolve().name == OUTPUT_DIRNAME and not args.output_dir:
206
+ args.output_dir = args.root_dir
207
+
167
208
  table = _gene_table(args.root_dir, args.output_dir, args.gene_table)
168
209
  count = query_function(table, args.function_id, args.output, args.category, args.rank, args.top)
169
210
  print(f"Wrote {count} contributing taxon rows to {args.output}")
170
211
  return 0
212
+
171
213
  if args.command == "query-taxon":
214
+ if args.root_dir is None and args.gene_table is None and args.output_dir is None:
215
+ raise ValueError("Provide --root-dir, --output-dir or --gene-table.")
216
+
217
+ # A MetaPont_Results directory can be supplied directly as root-dir.
218
+ if args.root_dir and Path(args.root_dir).expanduser().resolve().name == OUTPUT_DIRNAME and not args.output_dir:
219
+ args.output_dir = args.root_dir
220
+
172
221
  table = _gene_table(args.root_dir, args.output_dir, args.gene_table)
173
222
  count = query_taxon(table, args.taxon, args.output, args.category, args.top, args.rank)
223
+
174
224
  print(f"Wrote {count} taxon-associated function rows to {args.output}")
175
225
  return 0
226
+ if args.command in {"taxon-function-abundance", "taxon-function"}:
227
+ if args.gene_table is None and args.root_dir is None and args.output_dir is None:
228
+ raise ValueError("Provide --gene-table, --root-dir or --output-dir.")
229
+ table = _gene_table(args.root_dir, args.output_dir, args.gene_table)
230
+ if args.output:
231
+ output = Path(args.output).expanduser().resolve()
232
+ elif args.output_dir or args.root_dir:
233
+ output = _output_root(args.root_dir, args.output_dir) / "functional" / "taxon_function_abundance.tsv"
234
+ else:
235
+ output = table.parent.parent / "functional" / "taxon_function_abundance.tsv"
236
+ count = build_taxon_function_abundance(table, output)
237
+ print(f"Wrote {count} taxon/function rows to {output}")
238
+ return 0
176
239
  except (FileNotFoundError, RuntimeError, ValueError, OSError) as error:
177
240
  print(f"ERROR: {error}", file=sys.stderr)
178
241
  return 2
@@ -186,7 +186,7 @@ def function_by_taxa_main() -> int:
186
186
  root = args.root_dir or args.directory
187
187
  if not root:
188
188
  parser.error("provide the sample root as a positional argument or with -d")
189
- result = run_analysis(root, args.prefix, args.output_dir, heal_flags=True, force=False)
189
+ result = run_analysis(root, args.prefix, args.output_dir, heal_flags=True, force=False, include_taxon_function_abundance=True)
190
190
  print(f"Function-by-taxon results written to {result['output_dir']}/functional/taxon_function_abundance.tsv")
191
191
  return 0
192
192