maggic-wand 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.
- maggic_wand-0.1.0/.gitignore +61 -0
- maggic_wand-0.1.0/PKG-INFO +21 -0
- maggic_wand-0.1.0/maggic_wand/__init__.py +24 -0
- maggic_wand-0.1.0/maggic_wand/cli.py +419 -0
- maggic_wand-0.1.0/maggic_wand/constants.py +144 -0
- maggic_wand-0.1.0/maggic_wand/data.py +264 -0
- maggic_wand-0.1.0/maggic_wand/helpers.py +100 -0
- maggic_wand-0.1.0/maggic_wand/logging_utils.py +30 -0
- maggic_wand-0.1.0/maggic_wand/plots/__init__.py +22 -0
- maggic_wand-0.1.0/maggic_wand/plots/amr.py +240 -0
- maggic_wand-0.1.0/maggic_wand/plots/contigscatter.py +144 -0
- maggic_wand-0.1.0/maggic_wand/plots/diversity.py +360 -0
- maggic_wand-0.1.0/maggic_wand/plots/heatmap.py +283 -0
- maggic_wand-0.1.0/maggic_wand/plots/histogram.py +102 -0
- maggic_wand-0.1.0/maggic_wand/plots/mge.py +438 -0
- maggic_wand-0.1.0/maggic_wand/plots/quality.py +90 -0
- maggic_wand-0.1.0/pyproject.toml +49 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Python development
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
uv*
|
|
6
|
+
*.ruff_cache
|
|
7
|
+
|
|
8
|
+
# Distribution
|
|
9
|
+
.Python
|
|
10
|
+
build/
|
|
11
|
+
dist/
|
|
12
|
+
*.egg-info/
|
|
13
|
+
nosetests.xml
|
|
14
|
+
coverage.xml
|
|
15
|
+
*.cover
|
|
16
|
+
*.square
|
|
17
|
+
*container*
|
|
18
|
+
|
|
19
|
+
# Virtual environments
|
|
20
|
+
.venv/
|
|
21
|
+
venv/
|
|
22
|
+
ENV/
|
|
23
|
+
env/
|
|
24
|
+
|
|
25
|
+
# Testing
|
|
26
|
+
.pytest_cache/
|
|
27
|
+
.tox/
|
|
28
|
+
.coverage
|
|
29
|
+
htmlcov/
|
|
30
|
+
*png
|
|
31
|
+
*ank*
|
|
32
|
+
*plan*
|
|
33
|
+
*docs*
|
|
34
|
+
.maggic-wand
|
|
35
|
+
|
|
36
|
+
# Pipeline outputs and temporary files
|
|
37
|
+
IN-LS/
|
|
38
|
+
work/
|
|
39
|
+
.nextflow.log*
|
|
40
|
+
.nextflow/
|
|
41
|
+
slurm-*.out
|
|
42
|
+
job_scripts/
|
|
43
|
+
resolve-fastq/
|
|
44
|
+
results_*/
|
|
45
|
+
CPIPES-bettercallsal/
|
|
46
|
+
bettercallsal/
|
|
47
|
+
fastq_json_cache/
|
|
48
|
+
bactree-tests/__pycache*
|
|
49
|
+
*rules*
|
|
50
|
+
|
|
51
|
+
# IDEs and Editors
|
|
52
|
+
.vscode/
|
|
53
|
+
.idea/
|
|
54
|
+
*.swp
|
|
55
|
+
*.swo
|
|
56
|
+
.DS_Store
|
|
57
|
+
|
|
58
|
+
# Local environment/config
|
|
59
|
+
.env
|
|
60
|
+
.env.*
|
|
61
|
+
*bank/*
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: maggic-wand
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Publication-quality plots from MAGGIC pipeline results
|
|
5
|
+
Author-email: Kranti Konganti <Kranti.Konganti@fda.hhs.gov>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: beartype>=0.18
|
|
9
|
+
Requires-Dist: matplotlib>=3.8
|
|
10
|
+
Requires-Dist: pandas>=2.0
|
|
11
|
+
Requires-Dist: rich>=13.0
|
|
12
|
+
Requires-Dist: scipy>=1.11
|
|
13
|
+
Requires-Dist: seaborn>=0.13
|
|
14
|
+
Requires-Dist: typer>=0.9
|
|
15
|
+
Requires-Dist: wordcloud>=1.9
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: black; extra == 'dev'
|
|
18
|
+
Requires-Dist: flynt; extra == 'dev'
|
|
19
|
+
Requires-Dist: isort; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Kranti Konganti
|
|
2
|
+
#
|
|
3
|
+
# 6/1/2026
|
|
4
|
+
# (C) HFP, FDA.
|
|
5
|
+
|
|
6
|
+
"""Package init and version for maggic-wand."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
11
|
+
|
|
12
|
+
from beartype import beartype
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@beartype
|
|
16
|
+
def _get_version() -> str:
|
|
17
|
+
"""Return the installed package version."""
|
|
18
|
+
try:
|
|
19
|
+
return version("maggic-wand")
|
|
20
|
+
except PackageNotFoundError:
|
|
21
|
+
return "0.0.0-dev"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__version__ = _get_version()
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Kranti Konganti
|
|
3
|
+
#
|
|
4
|
+
# 6/1/2026
|
|
5
|
+
# (C) HFP, FDA.
|
|
6
|
+
|
|
7
|
+
"""CLI entry point for maggic-wand."""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
from beartype import beartype
|
|
16
|
+
|
|
17
|
+
import maggic_wand.data as data
|
|
18
|
+
from maggic_wand.helpers import print_package_info
|
|
19
|
+
from maggic_wand.logging_utils import log
|
|
20
|
+
|
|
21
|
+
ALL_PLOT_TYPES = frozenset(
|
|
22
|
+
(
|
|
23
|
+
"amr-donut",
|
|
24
|
+
"amr-wordcloud",
|
|
25
|
+
"contigscatter",
|
|
26
|
+
"diversity",
|
|
27
|
+
"heatmap",
|
|
28
|
+
"mge-radar",
|
|
29
|
+
"quality",
|
|
30
|
+
"quality-ecdf",
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
app = typer.Typer(
|
|
35
|
+
name="maggic-wand",
|
|
36
|
+
help="Publication-quality plots from MAGGIC results",
|
|
37
|
+
add_completion=False,
|
|
38
|
+
rich_markup_mode="rich",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.command()
|
|
43
|
+
@beartype
|
|
44
|
+
def plot_diversity(
|
|
45
|
+
results: Path = typer.Argument(..., help="Path to maggic-results.tsv"),
|
|
46
|
+
abundance: Path = typer.Argument(..., help="Path to maggic-globalabundance.tsv"),
|
|
47
|
+
output: Path = typer.Option(
|
|
48
|
+
Path("genus_diversity.png"),
|
|
49
|
+
"--output",
|
|
50
|
+
"-o",
|
|
51
|
+
help="Output PNG path for genus plot (species uses same stem)",
|
|
52
|
+
),
|
|
53
|
+
top_n: int = typer.Option(
|
|
54
|
+
10,
|
|
55
|
+
"--top-n",
|
|
56
|
+
"-n",
|
|
57
|
+
help="Number of top taxa to display",
|
|
58
|
+
),
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Generate diversity bar plots at genus and species level."""
|
|
61
|
+
log.info("[royal_blue1]Loading results...[/royal_blue1]")
|
|
62
|
+
results_df = data.load_maggic_results(results)
|
|
63
|
+
|
|
64
|
+
log.info("[royal_blue1]Loading abundance...[/royal_blue1]")
|
|
65
|
+
abundance_df = data.load_globalabundance(abundance)
|
|
66
|
+
|
|
67
|
+
log.info("[royal_blue1]Enriching with taxonomy...[/royal_blue1]")
|
|
68
|
+
enriched = data.enrich_with_taxonomy(abundance_df, results_df)
|
|
69
|
+
|
|
70
|
+
from maggic_wand.plots.diversity import (
|
|
71
|
+
genus_stacked_bar,
|
|
72
|
+
species_stacked_bar,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Genus stacked bar
|
|
76
|
+
genus_output = output.parent / "genus_diversity.png"
|
|
77
|
+
log.info(
|
|
78
|
+
f"[royal_blue1]Generating genus stacked bar[/royal_blue1]"
|
|
79
|
+
f" (top {top_n} genera)..."
|
|
80
|
+
)
|
|
81
|
+
_ = genus_stacked_bar(enriched, results_df, genus_output, top_n=top_n)
|
|
82
|
+
log.info(f"[green]+ {genus_output}[/green]")
|
|
83
|
+
|
|
84
|
+
# Species stacked bar
|
|
85
|
+
species_output = output.parent / "species_diversity.png"
|
|
86
|
+
log.info("[royal_blue1]Generating species stacked bar...[/royal_blue1]")
|
|
87
|
+
result = species_stacked_bar(enriched, results_df, species_output)
|
|
88
|
+
if result is None:
|
|
89
|
+
log.warning(" [yellow]skip[/yellow] (< 2 unique species)")
|
|
90
|
+
else:
|
|
91
|
+
log.info(f"[green]+ {species_output}[/green]")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@app.command()
|
|
95
|
+
@beartype
|
|
96
|
+
def plot_quality(
|
|
97
|
+
results: Path = typer.Argument(..., help="Path to maggic-results.tsv"),
|
|
98
|
+
output: Path = typer.Option(
|
|
99
|
+
Path("quality_scatter.png"),
|
|
100
|
+
"--output",
|
|
101
|
+
"-o",
|
|
102
|
+
help="Output PNG path",
|
|
103
|
+
),
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Generate quality scatter (Completeness vs Contamination)."""
|
|
106
|
+
log.info("[royal_blue1]Loading results...[/royal_blue1]")
|
|
107
|
+
results_df = data.load_maggic_results(results)
|
|
108
|
+
|
|
109
|
+
from maggic_wand.plots.quality import (
|
|
110
|
+
quality_scatter,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
log.info("[royal_blue1]Generating quality scatter chart...[/royal_blue1]")
|
|
114
|
+
_ = quality_scatter(results_df, output)
|
|
115
|
+
log.info(f"[green]+ {output}[/green]")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@app.command(name="plot-heatmap")
|
|
119
|
+
@beartype
|
|
120
|
+
def plot_heatmap(
|
|
121
|
+
results: Path = typer.Argument(..., help="Path to maggic-results.tsv"),
|
|
122
|
+
abundance: Path = typer.Argument(..., help="Path to maggic-globalabundance.tsv"),
|
|
123
|
+
output: Path = typer.Option(
|
|
124
|
+
Path("genus_heatmap.png"),
|
|
125
|
+
"--output",
|
|
126
|
+
"-o",
|
|
127
|
+
help="Output PNG path for genus plot (species uses same stem)",
|
|
128
|
+
),
|
|
129
|
+
top_n: int | None = typer.Option(
|
|
130
|
+
30,
|
|
131
|
+
"--top-n",
|
|
132
|
+
"-n",
|
|
133
|
+
help="Number of top taxa to display (None for all)",
|
|
134
|
+
),
|
|
135
|
+
) -> None:
|
|
136
|
+
"""Generate genus and species heatmaps with hierarchical clustering."""
|
|
137
|
+
log.info("[royal_blue1]Loading results...[/royal_blue1]")
|
|
138
|
+
results_df = data.load_maggic_results(results)
|
|
139
|
+
|
|
140
|
+
log.info("[royal_blue1]Loading abundance...[/royal_blue1]")
|
|
141
|
+
abundance_df = data.load_globalabundance(abundance)
|
|
142
|
+
|
|
143
|
+
log.info("[royal_blue1]Enriching with taxonomy...[/royal_blue1]")
|
|
144
|
+
enriched = data.enrich_with_taxonomy(abundance_df, results_df)
|
|
145
|
+
|
|
146
|
+
from maggic_wand.plots.heatmap import genus_heatmap, species_heatmap
|
|
147
|
+
|
|
148
|
+
# Genus heatmap
|
|
149
|
+
genus_output = output.parent / "genus_heatmap.png"
|
|
150
|
+
log.info(
|
|
151
|
+
f"[royal_blue1]Generating genus heatmap[/royal_blue1]" f" (top {top_n} taxa)..."
|
|
152
|
+
)
|
|
153
|
+
_ = genus_heatmap(enriched, results_df, genus_output, top_n=top_n)
|
|
154
|
+
log.info(f"[green]+ {genus_output}[/green]")
|
|
155
|
+
|
|
156
|
+
# Species heatmap
|
|
157
|
+
species_output = output.parent / "species_heatmap.png"
|
|
158
|
+
log.info("[royal_blue1]Generating species heatmap...[/royal_blue1]")
|
|
159
|
+
result = species_heatmap(enriched, results_df, species_output)
|
|
160
|
+
if result is None:
|
|
161
|
+
log.warning(" [yellow]skip[/yellow] (< 2 unique species)")
|
|
162
|
+
else:
|
|
163
|
+
log.info(f"[green]+ {species_output}[/green]")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# End-to-end pipeline helpers
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@beartype
|
|
170
|
+
def _discover_files(results_dir: Path) -> tuple[Path, Path | None]:
|
|
171
|
+
"""Discover maggic-results.tsv and maggic-globalabundance.tsv in a directory.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Tuple of (results_path, abundance_path|None).
|
|
175
|
+
"""
|
|
176
|
+
results = results_dir / "maggic-results.tsv"
|
|
177
|
+
abundance = results_dir / "maggic-globalabundance.tsv"
|
|
178
|
+
|
|
179
|
+
if not results.exists():
|
|
180
|
+
raise typer.BadParameter(f"maggic-results.tsv not found in {results_dir}")
|
|
181
|
+
|
|
182
|
+
abund_path: Path | None = None
|
|
183
|
+
if abundance.exists():
|
|
184
|
+
abund_path = abundance
|
|
185
|
+
return (results, abund_path)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# Commands
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@app.command(name="plot")
|
|
192
|
+
@beartype
|
|
193
|
+
def plot_all(
|
|
194
|
+
results_dir: Path = typer.Option(
|
|
195
|
+
...,
|
|
196
|
+
"--maggic-results-dir",
|
|
197
|
+
"-d",
|
|
198
|
+
exists=True,
|
|
199
|
+
file_okay=False,
|
|
200
|
+
dir_okay=True,
|
|
201
|
+
resolve_path=True,
|
|
202
|
+
help=(
|
|
203
|
+
"MAGGIC results directory (contains "
|
|
204
|
+
"maggic-results.tsv, maggic-globalabundance.tsv)"
|
|
205
|
+
),
|
|
206
|
+
),
|
|
207
|
+
output_dir: Path = typer.Option(
|
|
208
|
+
None,
|
|
209
|
+
"--output-dir",
|
|
210
|
+
"-o",
|
|
211
|
+
help="Output directory for plots (default: results_dir/plots/)",
|
|
212
|
+
),
|
|
213
|
+
plot_type: str = typer.Option(
|
|
214
|
+
"all",
|
|
215
|
+
"--plot-type",
|
|
216
|
+
"-p",
|
|
217
|
+
help=(
|
|
218
|
+
"Comma-separated list of plots to generate: "
|
|
219
|
+
"diversity,quality,heatmap (default: all)"
|
|
220
|
+
),
|
|
221
|
+
),
|
|
222
|
+
top_n: int = typer.Option(
|
|
223
|
+
10,
|
|
224
|
+
"--top-n",
|
|
225
|
+
"-n",
|
|
226
|
+
help="Number of top genera for diversity and heatmap plots",
|
|
227
|
+
),
|
|
228
|
+
) -> None:
|
|
229
|
+
"""Generate all plots from MAGGIC results directory.
|
|
230
|
+
|
|
231
|
+
Discovers maggic-results.tsv and maggic-globalabundance.tsv in the
|
|
232
|
+
specified directory, then generates all requested plot types.
|
|
233
|
+
"""
|
|
234
|
+
# Parse plot types
|
|
235
|
+
requested = {t.strip() for t in plot_type.split(",")}
|
|
236
|
+
if requested == {"all"}:
|
|
237
|
+
requested = set(ALL_PLOT_TYPES)
|
|
238
|
+
invalid = requested - ALL_PLOT_TYPES
|
|
239
|
+
if invalid:
|
|
240
|
+
raise typer.BadParameter(
|
|
241
|
+
f"Unknown plot type(s): {', '.join(sorted(invalid))}. "
|
|
242
|
+
f"Valid types: {', '.join(sorted(ALL_PLOT_TYPES))}"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Resolve output directory
|
|
246
|
+
out_dir: Path = output_dir or (results_dir / "plots")
|
|
247
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
248
|
+
|
|
249
|
+
# Discover input files
|
|
250
|
+
results_path, abundance_path = _discover_files(results_dir)
|
|
251
|
+
has_abundance = abundance_path is not None
|
|
252
|
+
|
|
253
|
+
log.info(f"[royal_blue1]Input dir:[/royal_blue1] {results_dir}")
|
|
254
|
+
log.info(f"[royal_blue1]Output dir:[/royal_blue1] {out_dir}")
|
|
255
|
+
log.info(f"[royal_blue1]Plots:[/royal_blue1] {', '.join(sorted(requested))}")
|
|
256
|
+
|
|
257
|
+
# Load shared data
|
|
258
|
+
results_df = data.load_maggic_results(results_path)
|
|
259
|
+
|
|
260
|
+
enriched = (
|
|
261
|
+
None
|
|
262
|
+
if not has_abundance
|
|
263
|
+
else data.enrich_with_taxonomy(
|
|
264
|
+
data.load_globalabundance(abundance_path), results_df
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Generate plots
|
|
269
|
+
for plot_name in sorted(requested):
|
|
270
|
+
if plot_name == "quality":
|
|
271
|
+
from maggic_wand.plots.quality import (
|
|
272
|
+
quality_scatter,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
output = out_dir / "quality_scatter.png"
|
|
276
|
+
log.info(f"[royal_blue1]Generating {plot_name}...[/royal_blue1]")
|
|
277
|
+
quality_scatter(results_df, output)
|
|
278
|
+
log.info(f" [green]+ {output}[/green]")
|
|
279
|
+
|
|
280
|
+
elif plot_name == "diversity":
|
|
281
|
+
if enriched is None:
|
|
282
|
+
log.warning(f" [yellow]skip {plot_name}[/yellow] (no abundance data)")
|
|
283
|
+
continue
|
|
284
|
+
|
|
285
|
+
from maggic_wand.plots.diversity import (
|
|
286
|
+
genus_stacked_bar,
|
|
287
|
+
species_stacked_bar,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Genus stacked bar
|
|
291
|
+
genus_output = out_dir / "genus_diversity.png"
|
|
292
|
+
log.info(f"[royal_blue1]Generating genus {plot_name}...[/royal_blue1]")
|
|
293
|
+
genus_stacked_bar(enriched, results_df, genus_output, top_n=top_n)
|
|
294
|
+
log.info(f" [green]+ {genus_output}[/green]")
|
|
295
|
+
|
|
296
|
+
# Species stacked bar
|
|
297
|
+
species_output = out_dir / "species_diversity.png"
|
|
298
|
+
log.info("[royal_blue1]Generating species diversity...[/royal_blue1]")
|
|
299
|
+
result = species_stacked_bar(enriched, results_df, species_output)
|
|
300
|
+
if result is None:
|
|
301
|
+
log.warning(" [yellow]skip species[/yellow] (< 2 unique species)")
|
|
302
|
+
else:
|
|
303
|
+
log.info(f" [green]+ {species_output}[/green]")
|
|
304
|
+
|
|
305
|
+
elif plot_name == "heatmap":
|
|
306
|
+
if enriched is None:
|
|
307
|
+
log.warning(f" [yellow]skip {plot_name}[/yellow] (no abundance data)")
|
|
308
|
+
continue
|
|
309
|
+
|
|
310
|
+
from maggic_wand.plots.heatmap import (
|
|
311
|
+
genus_heatmap,
|
|
312
|
+
species_heatmap,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# Genus heatmap
|
|
316
|
+
genus_output = out_dir / "genus_heatmap.png"
|
|
317
|
+
log.info(f"[royal_blue1]Generating genus {plot_name}...[/royal_blue1]")
|
|
318
|
+
genus_heatmap(enriched, results_df, genus_output, top_n=30)
|
|
319
|
+
log.info(f" [green]+ {genus_output}[/green]")
|
|
320
|
+
|
|
321
|
+
# Species heatmap
|
|
322
|
+
species_output = out_dir / "species_heatmap.png"
|
|
323
|
+
log.info("[royal_blue1]Generating species heatmap...[/royal_blue1]")
|
|
324
|
+
result = species_heatmap(enriched, results_df, species_output)
|
|
325
|
+
if result is None:
|
|
326
|
+
log.warning(" [yellow]skip species[/yellow] (< 2 unique species)")
|
|
327
|
+
else:
|
|
328
|
+
log.info(f" [green]+ {species_output}[/green]")
|
|
329
|
+
|
|
330
|
+
elif plot_name == "amr-wordcloud":
|
|
331
|
+
from maggic_wand.plots.amr import (
|
|
332
|
+
amr_wordcloud,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
output = out_dir / "amr_wordcloud.png"
|
|
336
|
+
log.info(f"[royal_blue1]Generating {plot_name}...[/royal_blue1]")
|
|
337
|
+
amr_wordcloud(results_df, output)
|
|
338
|
+
log.info(f" [green]+ {output}[/green]")
|
|
339
|
+
|
|
340
|
+
elif plot_name == "amr-donut":
|
|
341
|
+
from maggic_wand.plots.amr import (
|
|
342
|
+
amr_class_donut,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
output = out_dir / "amr_class_donut.png"
|
|
346
|
+
log.info(f"[royal_blue1]Generating {plot_name}...[/royal_blue1]")
|
|
347
|
+
amr_class_donut(results_df, output)
|
|
348
|
+
log.info(f" [green]+ {output}[/green]")
|
|
349
|
+
|
|
350
|
+
elif plot_name == "mge-radar":
|
|
351
|
+
from maggic_wand.plots.mge import mge_radar
|
|
352
|
+
|
|
353
|
+
output = out_dir / "mge_radar.png"
|
|
354
|
+
log.info(f"[royal_blue1]Generating {plot_name}...[/royal_blue1]")
|
|
355
|
+
mge_radar(results_df, output)
|
|
356
|
+
log.info(f" [green]+ {output}[/green]")
|
|
357
|
+
|
|
358
|
+
elif plot_name == "quality-ecdf":
|
|
359
|
+
from maggic_wand.plots.histogram import (
|
|
360
|
+
quality_ecdf,
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
quality_path = results_dir / "ALL_REFINED_BINS_QUALITY_REPORT.tsv"
|
|
364
|
+
if not quality_path.exists():
|
|
365
|
+
log.warning(
|
|
366
|
+
f" [yellow]skip {plot_name}[/yellow]"
|
|
367
|
+
f" (no Binette quality report)"
|
|
368
|
+
)
|
|
369
|
+
continue
|
|
370
|
+
|
|
371
|
+
output = out_dir / "quality_ecdf.png"
|
|
372
|
+
log.info(f"[royal_blue1]Generating {plot_name}...[/royal_blue1]")
|
|
373
|
+
quality_df = data.load_binette_quality_report(quality_path)
|
|
374
|
+
quality_ecdf(quality_df, output)
|
|
375
|
+
log.info(f" [green]+ {output}[/green]")
|
|
376
|
+
|
|
377
|
+
elif plot_name == "contigscatter":
|
|
378
|
+
from maggic_wand.plots.contigscatter import (
|
|
379
|
+
completeness_contig_scatter,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
output = out_dir / "completeness_contig_scatter.png"
|
|
383
|
+
log.info(f"[royal_blue1]Generating {plot_name}...[/royal_blue1]")
|
|
384
|
+
completeness_contig_scatter(results_df, output)
|
|
385
|
+
log.info(f" [green]+ {output}[/green]")
|
|
386
|
+
|
|
387
|
+
log.info("")
|
|
388
|
+
log.info(f"[bold yellow]Plots written to[/bold yellow] {out_dir}")
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@app.command(name="version")
|
|
392
|
+
@beartype
|
|
393
|
+
def version() -> None:
|
|
394
|
+
"""Print package version."""
|
|
395
|
+
from maggic_wand import __version__
|
|
396
|
+
|
|
397
|
+
log.info(f"maggic-wand v{__version__}")
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
VERSION_FLAGS = frozenset(("-v", "--version"))
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
@beartype
|
|
404
|
+
def main() -> None:
|
|
405
|
+
"""Entry point."""
|
|
406
|
+
# Show package info banner when invoked without a subcommand or with -h/--help/-v/--version
|
|
407
|
+
has_help = "-h" in sys.argv or "--help" in sys.argv
|
|
408
|
+
has_version = any(flag in sys.argv for flag in VERSION_FLAGS)
|
|
409
|
+
no_subcommand = len(sys.argv) == 1
|
|
410
|
+
|
|
411
|
+
if no_subcommand:
|
|
412
|
+
print_package_info(help_flag="-h")
|
|
413
|
+
elif has_help:
|
|
414
|
+
print_package_info(help_flag="--help")
|
|
415
|
+
elif has_version:
|
|
416
|
+
print_package_info(help_flag="-h")
|
|
417
|
+
return None
|
|
418
|
+
|
|
419
|
+
app()
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Kranti Konganti
|
|
2
|
+
#
|
|
3
|
+
# 6/1/2026
|
|
4
|
+
# (C) HFP, FDA.
|
|
5
|
+
|
|
6
|
+
"""Global constants for maggic-wand."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Final
|
|
11
|
+
|
|
12
|
+
# Package
|
|
13
|
+
PKG_NAME: Final[str] = "maggic-wand"
|
|
14
|
+
PKG_TAGLINE: Final[str] = "Publication-quality plots from MAGGIC results"
|
|
15
|
+
|
|
16
|
+
# CLI colors (rich markup)
|
|
17
|
+
COLOR_PKG: Final[str] = "light_sea_green"
|
|
18
|
+
COLOR_INFO: Final[str] = "bold blue"
|
|
19
|
+
COLOR_SUCCESS: Final[str] = "bold green"
|
|
20
|
+
COLOR_WARN: Final[str] = "bold yellow"
|
|
21
|
+
|
|
22
|
+
# Unassigned / fallback
|
|
23
|
+
UNASSIGNED: Final[str] = "Unassigned"
|
|
24
|
+
COLOR_UNASSIGNED: Final[str] = "#cccccc"
|
|
25
|
+
COLOR_GREY: Final[str] = "#999999"
|
|
26
|
+
COLOR_GREEN: Final[str] = "#1b9e77"
|
|
27
|
+
|
|
28
|
+
# Composite phylum palette: 79 distinctive colors from tab20/20b/20c/Paired/Accent
|
|
29
|
+
PHYLUM_COLORS = (
|
|
30
|
+
"#1f77b4",
|
|
31
|
+
"#aec7e8",
|
|
32
|
+
"#ff7f0e",
|
|
33
|
+
"#ffbb78",
|
|
34
|
+
"#2ca02c",
|
|
35
|
+
"#98df8a",
|
|
36
|
+
"#d62728",
|
|
37
|
+
"#ff9896",
|
|
38
|
+
"#9467bd",
|
|
39
|
+
"#c5b0d5",
|
|
40
|
+
"#8c564b",
|
|
41
|
+
"#c49c94",
|
|
42
|
+
"#e377c2",
|
|
43
|
+
"#f7b6d2",
|
|
44
|
+
"#7f7f7f",
|
|
45
|
+
"#c7c7c7",
|
|
46
|
+
"#bcbd22",
|
|
47
|
+
"#dbdb8d",
|
|
48
|
+
"#17becf",
|
|
49
|
+
"#9edae5",
|
|
50
|
+
"#393b79",
|
|
51
|
+
"#5254a3",
|
|
52
|
+
"#6b6ecf",
|
|
53
|
+
"#9c9ede",
|
|
54
|
+
"#637939",
|
|
55
|
+
"#8ca252",
|
|
56
|
+
"#b5cf6b",
|
|
57
|
+
"#cedb9c",
|
|
58
|
+
"#8c6d31",
|
|
59
|
+
"#bd9e39",
|
|
60
|
+
"#e7ba52",
|
|
61
|
+
"#e7cb94",
|
|
62
|
+
"#843c39",
|
|
63
|
+
"#ad494a",
|
|
64
|
+
"#d6616b",
|
|
65
|
+
"#e7969c",
|
|
66
|
+
"#7b4173",
|
|
67
|
+
"#a55194",
|
|
68
|
+
"#ce6dbd",
|
|
69
|
+
"#de9ed6",
|
|
70
|
+
"#3182bd",
|
|
71
|
+
"#6baed6",
|
|
72
|
+
"#9ecae1",
|
|
73
|
+
"#c6dbef",
|
|
74
|
+
"#e6550d",
|
|
75
|
+
"#fd8d3c",
|
|
76
|
+
"#fdae6b",
|
|
77
|
+
"#fdd0a2",
|
|
78
|
+
"#31a354",
|
|
79
|
+
"#74c476",
|
|
80
|
+
"#a1d99b",
|
|
81
|
+
"#c7e9c0",
|
|
82
|
+
"#756bb1",
|
|
83
|
+
"#9e9ac8",
|
|
84
|
+
"#bcbddc",
|
|
85
|
+
"#dadaeb",
|
|
86
|
+
"#636363",
|
|
87
|
+
"#969696",
|
|
88
|
+
"#bdbdbd",
|
|
89
|
+
"#d9d9d9",
|
|
90
|
+
"#a6cee3",
|
|
91
|
+
"#1f78b4",
|
|
92
|
+
"#b2df8a",
|
|
93
|
+
"#33a02c",
|
|
94
|
+
"#fb9a99",
|
|
95
|
+
"#e31a1c",
|
|
96
|
+
"#fdbf6f",
|
|
97
|
+
"#ff7f00",
|
|
98
|
+
"#cab2d6",
|
|
99
|
+
"#6a3d9a",
|
|
100
|
+
"#ffff99",
|
|
101
|
+
"#b15928",
|
|
102
|
+
"#7fc97f",
|
|
103
|
+
"#beaed4",
|
|
104
|
+
"#fdc086",
|
|
105
|
+
"#386cb0",
|
|
106
|
+
"#f0027f",
|
|
107
|
+
"#bf5b16",
|
|
108
|
+
"#666666",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Bin-type palette (quality scatter)
|
|
112
|
+
BIN_TYPE_COLOURS = {
|
|
113
|
+
"Chromosome_MAG": "#1b9e77",
|
|
114
|
+
"Plasmid_MAG": "#d95f02",
|
|
115
|
+
"Virus_MAG": "#7570b3",
|
|
116
|
+
"Mixed_MAG": "#e7298a",
|
|
117
|
+
"Skeletal_MAG": "#66a61e",
|
|
118
|
+
"Fragments": "#808080",
|
|
119
|
+
"Low_Quality": "#666666",
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# Genus color pool (tab20 derived)
|
|
123
|
+
GENUS_COLOR_POOL: tuple[str, ...] = (
|
|
124
|
+
"#1f77b4",
|
|
125
|
+
"#ff7f0e",
|
|
126
|
+
"#2ca02c",
|
|
127
|
+
"#d62728",
|
|
128
|
+
"#9467bd",
|
|
129
|
+
"#8c564b",
|
|
130
|
+
"#e377c2",
|
|
131
|
+
"#7f7f7f",
|
|
132
|
+
"#bcbd22",
|
|
133
|
+
"#17becf",
|
|
134
|
+
"#aec7e8",
|
|
135
|
+
"#ffbb78",
|
|
136
|
+
"#98df8a",
|
|
137
|
+
"#ff9896",
|
|
138
|
+
"#c5b0d5",
|
|
139
|
+
"#c49c94",
|
|
140
|
+
"#f7b6d2",
|
|
141
|
+
"#c7c7c7",
|
|
142
|
+
"#dbdb8d",
|
|
143
|
+
"#f7c5a8",
|
|
144
|
+
)
|