Skip to content

Latest commit

 

History

History
1114 lines (901 loc) · 63.7 KB

README.md

File metadata and controls

1114 lines (901 loc) · 63.7 KB

MetaManifold

License: AGPL-3.0 Julia 1.12 R >= 4.0 CI codecov

Fork governance (hyperpolymath/MetaManifold-WebUI only; nothing here is part of upstream): Governance (fork) DEED syntax K9 contracts Hypatia grid The grid grades the worst finding each reporter has (purple critical, red high, yellow medium, blue minor, green clean), with a scanner that did not run in orange and a fleet member not enrolled for this repository in grey; enrolment is in .gitbot-fleet/enrolment.json.

MetaManifold wraps standard amplicon sequencing workflows into a single configurable Julia orchestrator: from raw paired-end Next Generation Sequencing reads through denoising, taxonomy assignment and taxonomic filtering, with interactive configuration and analysis in the browser.

A run page showing per-stage status, launch controls, run-level configuration and the read funnel

Overview

MetaManifold consists of a Julia backend (pipeline engine + REST API) and a TypeScript/React frontend. The pipeline runs FastQC, MultiQC, cutadapt, DADA2, swarm, vsearch, and cd-hit-est under the hood, and places ASVs on reference trees with MAFFT, trimAl, IQ-TREE, RAxML and gappa; results are stored in per-run DuckDB databases and served to the frontend as interactive Plotly charts and filterable tables. Pipeline configuration is editable in the web UI at every cascade level (see Configuration). For analysis-method status and planned work, see ROADMAP.md; the full occupancy follow-up is tracked in issue #205.

Pipeline stages

Raw FASTQs  (data/{study}/[{group}/]{run}/*.fastq.gz)
      |
   cutadapt, primer trimming
      |
      +----------------------------+
      |                            |
   DADA2*, ASV;               swarm*, OTU;
   filter & trim              merge pairs
   learn error rates          dereplicate
   denoise + merge            chimera filter
   length filter              cluster OTUs
   chimera removal                 |
   taxonomy assign*                |
      |                            |
   cd-hit-est*, demultiplex        |
      |                            |
   vsearch*                   vsearch, global alignment
      |                            |
      +----------------------------+
      |
   merge_taxa;
   join ASV tables*
   join counts-taxonomy
   apply filters
      |
   DuckDB results store          Reference sequences
      |                                |
      |                          reference tree*;
      |                          MAFFT align
      |                          trimAl trim
      |                          IQ-TREE tree
      |                                |
      +---------------+----------------+
                      |
               placement*;
               MAFFT add queries
               trimAl trim
               RAxML EPA place
               gappa accumulate
                      |
               jplace trees

*optional

Analysis

Once a run completes, analysis is performed on request through the web UI, both per run and across runs within a study:

  • Alpha diversity (richness, Shannon, Simpson), per sample or as cross-run comparison boxplots with significance testing and optional lines joining paired samples
  • Taxonomic composition bar charts at a chosen rank, relative or absolute
  • Organism-composition charts that classify ASVs/OTUs into biological categories (a "Composition" view, e.g. protozoa, helminths, fungi, host)
  • Taxon overlap across runs as proportional Euler or UpSet plots
  • Pipeline stage read-count summaries
  • NMDS ordination (Bray-Curtis, via R/vegan)
  • PERMANOVA, with PERMDISP to check for differences in dispersion (via R/vegan)
  • Constrained ordinations — RDA, CCA, CAP, and dbRDA — with Condition(...) partial ordination, seeded vegan::anova.cca permutation tests, adjusted-$R^2$ variance partitioning (vegan::varpart), and biplot scalings 1 & 2 (see Constrained ordinations)
  • De novo ASV phylogenies with cumulative clade abundances, Faith's phylogenetic diversity, and weighted/unweighted UniFrac (CladeCumulus)

Counts may be normalised before analysis (none, rarefaction to a fixed or auto-resolved depth, or scaling with ranked subsampling (SRS), which reaches the depth by scaling counts and keeps more of the community structure), and contamination-flagged taxa may be included or excluded. Ordinations can also apply a Hellinger transform before the Bray-Curtis dissimilarity. Composition charts can be drawn as a grid faceted on any two of run, group and sub-group. All analysis charts are returned as Plotly JSON and rendered interactively in the browser.

Opt-in exact-count and high-precision descriptive summaries are available through a separate validated API without changing these ordinary workflows. Their numeric, zero-depth, DuckDB, persistence, and reproducibility contracts are documented in Validated descriptive statistics. The staged first-party JSON migration toward the I-JSON/JCS estate standard records its scope and compatibility exclusions separately.

Prerequisites

  • Julia 1.12 (installed by install.sh if missing, and pinned to the Manifest version for this directory)
  • R >= 4.0 (required for the DADA2 stage and NMDS/PERMANOVA analysis)
    • Ubuntu/Debian: sudo apt install r-base
    • macOS: brew install r or CRAN package
  • bun builds the frontend. The installer uses a bun on PATH at the version pinned in config/defaults/tool_versions.yml, or downloads that release into bin/.

Installation

git clone https://github.com/JoshuaJewell/MetaManifold-WebUI.git
cd MetaManifold-WebUI
bash install.sh

install.sh installs Julia (via juliaup) if missing, checks for R, then hands off to install.jl, which installs the Julia dependencies, locates or downloads each external tool (cutadapt, FastQC, MultiQC, vsearch, cd-hit-est, and the phylogeny tools MAFFT, trimAl, IQ-TREE, RAxML and gappa), and reproduces the R environment from renv.lock. It ends with a summary listing every dependency as OK, SKIPPED, or ACTION NEEDED. Tool paths can be set in config/tools.yml. MAFFT, IQ-TREE and RAxML can be skipped on a machine that sends those stages to a server (see Threads and remote execution).

Installing R and the system -dev headers the R packages compile against needs root. If R is missing, the installer stops and prints how to install it. If headers are missing, the summary prints the command that installs them; run it and rerun install.sh.

To update:

bash install.sh --update

Reproducing the R environment

The R-side dependencies (DADA2, vegan, and their transitive packages) are pinned with renv; renv.lock is the source of truth and the committed .Rprofile activates the project-local library on any R/Rscript invocation from the repository root. install.sh runs the restore; to run it by hand:

Rscript -e 'renv::restore(prompt = FALSE)'

To add or upgrade a package, install it inside the project (renv::install(...)) and record the change with renv::snapshot().

Tool paths

install.sh generates config/tools.yml, one local path per tool:

vsearch:
  path: "/home/user/software/vsearch"

config/defaults/tools.yml contains the full config format. Stages that run on a server are configured in the remote block of pipeline.yml.

Quick start

1. Place paired-end FASTQs under data/

Put .fastq.gz files into data/MyProject/run_A.

2. Start the server

bash start.sh

The installer builds the frontend into web/dist, and install.sh --update rebuilds it. start.sh rebuilds it whenever a frontend source is newer than the build; BUILD=1 bash start.sh forces a rebuild.

The web UI lets you create studies, configure pipeline parameters, launch runs, and explore results. All state lives in the filesystem under data/ and projects/.

Environment variable Default Description
JULIA_METAMANIFOLD_PORT 8080 Server port
JULIA_METAMANIFOLD_ROOT working directory Project root
JULIA_THREADS 8 Julia threads
JULIA_METAMANIFOLD_ALLOWED_ORIGINS none Extra browser origins allowed to call the API, comma-separated (the server's own origin and the Vite dev server at localhost:5173 are always allowed)

Or run the Julia server directly:

julia --project=. scripts/serve.jl

The server is part of the MetaManifold package, so the first start after an install or a source change compiles the package. That compile runs a workload of the requests a first page visit makes, which takes a few minutes and caches their native code. While developing, skip the workload with a LocalPreferences.toml (git-ignored) in the repository root:

[MetaManifold]
precompile_workload = false

The server then makes those requests itself in the background as it starts, so the first page visit does not wait on compilation.

Web interface

The browser interface is the primary way to drive MetaManifold. Beyond creating studies and launching runs, it offers the following:

Editing configuration

Every pipeline setting can be edited in the UI without touching a YAML file. Configuration is presented as collapsible accordion sections (study design, primer trimming, DADA2 denoising and taxonomy, OTU clustering, analysis) and can be set at any level of the cascade: the instance-wide defaults, a study, a group, or an individual run. Edits at a finer level override coarser ones (see Configuration for the cascade rules). When a setting changes, the affected pipeline stages are flagged as stale, with a tooltip listing the changed keys and their level.

Pipeline runs and outputs

A run's page is the working surface for that run. It is where the run-level configuration above is edited, where the full pipeline or any individual stage (including the DADA2 substages) is launched, and where each stage's status is shown. Long-running jobs report progress live through a server-sent event stream, and the jobs panel lets you watch or cancel them. As stages complete, their outputs become available through the views that follow: quality reports, the results table and composition.

QC

Raw-read QC (FastQC aggregated by MultiQC) is embedded in each run's page with a rerun control. The DADA2 quality, denoising, merging and taxonomy diagnostics sit beside their stage settings.

The MultiQC report embedded in a run's QC tab

Results explorer

Each run's merged taxonomy-and-count table, and any derived tables, can be browsed. The table supports per-column filtering (text search, numeric range, include/exclude lists) and a global text filter, column sorting, configurable pagination, and column visibility toggles including taxonomy-source presets (VSEARCH-only, DADA2-only, or all) and a switch for the per-sample count columns. Sequences carry BLAST links, and OTU rows can be expanded to their constituent sequences. Frequently used filters can be saved as named presets and reapplied; filtered tables can be saved back into the run or exported as .xlsx.

Results explorer table with per-column filters and taxonomy-source column presets

Phylogenetic placement

Reference trees (under SYSTEM) are built once from uploaded reference sequences and used by any study. A study's Trees tab places its ASVs on one of them: pick runs and, for pooled runs, subgroups, a results table, a rank and taxa, or upload a FASTA. Each step shows where it runs, its state and its log, and the align and trim steps show a QC view of the alignment. A finished placement adds the reference tree, the placement jplace and the accumulated jplace to the study's tree list. See Configuring phylogenetic placement.

Tree viewer

Newick and jplace files in a study's tree list open in an interactive viewer: rectangular or circular layouts, rerooting, ladderizing, collapsing and naming clades, per-clade colours and fonts, placement symbols sized by count, and support values carried over from another tree. The view is saved beside the tree and exports as SVG, PNG or Newick. Reference trees open in the same viewer from the library page.

The tree viewer showing a placement tree with collapsed clades, bootstrap support and placement symbols

CladeCumulus: ASV phylogenies and phylogenetic diversity

A study's Cladistics tab builds a de novo tree directly from ASV sequences, rather than treating taxonomic ranks as a phylogeny. Choose whole runs and a shared results table with a Sequence column, then set an ASV cap (500 by default, 2,000 maximum) and a minimum total-read threshold. The highest-abundance ASVs are selected deterministically, with sequence ties broken lexicographically. The build uses the existing MAFFT → trimAl → IQ-TREE workflow and its phylogeny.reference settings, including local/remote execution and bootstrap configuration.

The completed CladeCumulus view provides:

  • cumulative read counts and frequencies on every phylogenetic clade, with a collapsible tree and a link to the full Newick viewer;
  • Faith's phylogenetic diversity and retained reads per sample;
  • unweighted UniFrac and normalized weighted UniFrac heatmaps, with CSV export;
  • optional rarefaction or SRS before all three analyses, using the same count normalisation as the existing diversity views.

Each build is saved separately with its selected count matrix, ASV FASTA, Newick tree, source settings, count/tree SHA-256 hashes and the usual tool attestation. Tips use stable sequence-content hashes so their names are safe for external tools and reproducible across runs. Metrics share one O(nodes × samples) roll-up; pairwise UniFrac is O(samples² × nodes). Tree inference itself can be much more expensive, so keep the ASV cap appropriate for the dataset. Run julia --project=. bench/cladistics.jl for a no-I/O roll-up/UniFrac timing. A tree inferred from short amplicons is approximate, particularly along deep branches.

Composition

The composition view classifies each ASV/OTU into a biological category and renders per-sample or pooled stacked bar charts. Category sets live in config/composition.yml (the bundled default set covers protozoa, helminths, fungi, host, plants, and invertebrates); each category references a named taxonomic filter from the filters: library in that same file. Both are editable from the Compositions page under SYSTEM in the sidebar. A category summary precedes the chart, and a quality filter can cap the number of unresolved taxonomic placeholders admitted.

Organism-category composition of two runs as stacked bars

Differential abundance

In a study's analysis workspace, select two conditions (runs or pooled-run subgroups), a results table and a shared taxonomic rank. The first condition is the reference. Counts are aggregated by taxon after applicable analysis.exclude_categories filters. This workspace does not pass sample read-count bounds to the differential-abundance request, so those bounds do not exclude samples from this analysis.

analysis.differential.method chooses the method:

  • nb_glm (default): a negative-binomial GLM per taxon using MASS::glm.nb, with a library-size offset. The effect is log2 fold change; positive values indicate greater abundance in the second condition. offset selects tss (default) or rle; dispersion selects maximum-likelihood theta or a cross-taxon glmGamPoi trend. Both options Wald-test the group coefficient with the dispersion treated as known, so every ml result — and a glmGamPoi result whose smaller group has at most 12 samples — carries a calibration caveat in its diagnostics, shown with the result and written into its CSV export. The caveat changes no p-value; the null-simulation evidence and full replication protocol live in bench/null_calibration. The glmGamPoi path uses fixed-theta NB Wald fits, not a quasi-likelihood test.
  • zinb: a zero-inflated negative-binomial model with separate logistic zero and count components, a count-component library-size offset, a joint 2-df likelihood-ratio test, and BH correction.
  • hurdle_nb: a logistic occurrence model plus zero-truncated NB counts for positive observations. It uses the same joint test and offset convention.
  • clr_welch: Welch's t-test on centred log-ratios after zero replacement with zCompositions::cmultRepl. The effect is a CLR difference, not a fold change. replacement_method selects Bayesian-multiplicative (GBM, default) or multiplicative (CZM) replacement; replacement_delta sets the replacement fraction and optional bayesian_alpha overrides GBM's concentration. The offset is not used. See zero-handling notes.
  • ancom_bc: ANCOM-BC sampling-fraction bias-corrected log-linear model (Lin & Peddada 2020, Nat. Commun. 11:3514). Estimates within-group sample offsets and between-group sampling-fraction bias via an E-M Gaussian mixture on log(counts + 1), propagating bias-estimation variance into two-sided Wald tests. The effect is a bias-corrected log2 fold change; useful when asymmetric shifts or load changes would bias standard CLR centering (needs at least 3 tested taxa). The implementation reports its mixture and bias diagnostics. Its assumption that fewer than half of tested taxa are differential must be checked rather than treated as a guarantee.
  • aldex2: Dirichlet(count + 0.5) Monte-Carlo CLR model (Fernandes et al. 2013, 2014). Draws mc_samples (default 128, seeded by the study's seed) instances per sample and reports standardized CLR effect sizes with consistent-sign posterior predictive Welch p-values; suited to sparse or variable-depth cohorts where deterministic zero imputation is undesirable. The reported estimate is a standardized CLR effect size, not an absolute fold change.
  • songbird: compositional baseline-category multinomial regression with reference-frame invariant CLR-centred differentials summing to zero across fitted taxa (Morton et al. 2019, Nat. Commun. 10:2719; Zhang et al. 2017). Conditions on read totals without extra-multinomial overdispersion; taxa separated in one group are marked failed rather than diverging.
  • multinomial: one multinomial logistic model for the whole composition (Songbird's model in pure Julia), which needs no size factor and no zero replacement. The effect is a log2 fold change in relative abundance against mn_reference_taxon; mn_penalty adds an L2 ridge penalty, and a taxon with no reads in one group is refused rather than reported.
  • dirichlet_multinomial: the same joint model plus a Dirichlet prior on the composition (La Rosa et al. 2012), which absorbs between-sample overdispersion; dm_overdispersion_method picks mle (profiled) or mom. Both multinomial models bound their Newton fit with max_iter, tolerance and max_seconds, and a fit that does not converge is refused. Dirichlet- multinomial diagnostics report the concentration, theta, and boundary status.

ZINB and hurdle models can overfit sparse data; do not select ZINB solely because counts are sparse. Compare against NB diagnostics. The methods report a count-process effect and the zero-process coefficient; this is not proof of biological structural absence. The public MetaManifold.Differential.fit_occupancy lower-level estimator accepts repeated-visit detection histories (sites × visits × taxa), refuses single-visit histories and is not selectable in the standard analysis configuration or UI. It currently has no detection covariates. Do not use it as a substitute for the full workflow tracked in issue #205. See occupancy model notes.

The multinomial models are the count-based, compositionally coherent alternative to nb_glm (which fits each taxon independently and so ignores the constraint the composition imposes) and to clr_welch (which is Gaussian on log-ratios and ignores the count likelihood). They are fitted in Julia without R and without a pseudocount; the dense observed information limits a fit to 1000 taxa, above which raise min_prevalence or analyse a coarser rank.

Across the differential methods, min_prevalence (default 0) filters taxa by the fraction of samples with nonzero counts. Benjamini–Hochberg adjustment includes only taxa that produced a p-value. Failed or filtered taxa have no substitute p-value; inspect their status and note in the results table. Invalid input or an unavailable required R package produces an explicit error.

Implementation and reference tests: src/analysis/differential.jl, src/server/routes/analysis.jl, bench/compositional_differential/benchmark.jl, and test/unit/test_differential.jl.

Why a count model rather than a t-test

Sequencing measures a sample of the library, not an absolute quantity: a sample of twice the depth has twice every count, and one taxon can only increase at another's expense. Comparisons of raw or rarefied abundances therefore carry the compositionality problem (Gloor et al. 2017). The multinomial models fit the whole composition at once and condition on each sample's total, so each coefficient is the log ratio of two taxa. That ratio is unchanged when every count in a sample is multiplied by a constant, which is why no size factor and no pseudocount is needed. The intercept is the log-odds of the taxon against the reference at the reference group; the group coefficient is the change in that log-odds, i.e. a log fold change in relative abundance. Because a log ratio is the sum of log ratios through any third taxon, every reported effect is coherent under compositionality and can be re-expressed against any other taxon.

Choosing the reference taxon

Every reported effect is anchored on the reference taxon, so the choice matters for interpretability and for stability, but not for the fit: the likelihood and the log ratio between two non-reference taxa are the same whichever taxon is the reference. An empty mn_reference_taxon takes the most abundant taxon that passes the prevalence filter. An explicit one must be a taxon at the analysed rank with reads in at least half the samples, and reads in both groups; a taxon seen in fewer samples makes every ratio it anchors unstable, and a taxon absent from one group has no finite log fold change, so both are refused with the reason. Taxa with no reads in one group are removed from the composition and listed as failed with their note, rather than fitted: in a joint model a single diverging coefficient would otherwise pull the whole fit with it.

Overdispersion

The multinomial treats every read as independent, so its variance is the sampling variance and nothing else. Real samples vary more than that: the same community, sequenced twice, differs by more than resampling reads from one library. The Dirichlet-multinomial adds that between-sample variation as a concentration c, with theta = 1/(1+c) the model's intra-sample correlation; the count variance becomes N p (1 - p) (1 + (N - 1) theta). When the data are overdispersed the multinomial's standard errors are too small, and the Dirichlet-multinomial corrects them; when they are not, c runs to its upper bound, the fit collapses to the multinomial exactly, and the affected rows are marked boundary with a note saying so. The two models are otherwise the same model on the same scale, so their coefficients are directly comparable.

Which model to use

Data and question Method
Counts, two groups, few samples, expected overdispersion nb_glm (per-taxon negative binomial; the default)
Counts, many taxa and samples, a joint composition and a single reference multinomial (Songbird's model)
As above, with visible between-sample overdispersion dirichlet_multinomial
Composition already transformed, or a quick robust rank-based check clr_welch
Excess zeros beyond negative-binomial overdispersion, with evidence for them zinb or hurdle_nb (two-part; see the caution above)
Asymmetric shifts or a community-wide load change ancom_bc (bias-corrected)
Sparse or variable-depth cohorts, Monte-Carlo CLR aldex2
Many taxa, a joint multinomial fit, but through R and with CLR-centred effects songbird

nb_glm fits a negative-binomial GLM per taxon with a library-size offset: it tolerates overdispersion through its own dispersion parameter and is the safer choice with very few samples per group, but each taxon is fitted independently and the offsets are estimated, not modelled. The multinomial models borrow strength across taxa by fitting one model, and their effect has a direct compositional reading, at the cost of a joint fit that a single separated taxon can compromise (hence the separation check and the optional ridge penalty), and of a dense information matrix that caps a fit at 1000 taxa. Songbird's L1 sparsity path is not implemented: a lasso estimate has no valid Wald standard error, so this implements the L2-penalised model and Wald inference instead.

The multinomial models are fitted in Julia and need no R package. max_iter, tolerance and max_seconds bound one fit; a fit that runs out of time fails with an explicit error instead of holding the request open. Runtime and memory at 100, 1000 and 10000 taxa are measured by bench/compositional_scaling/scaling.jl, which writes results/scaling.csv.

Implementation and reference tests: src/analysis/compositional.jl (the two models), src/analysis/differential.jl, src/server/routes/analysis.jl, and test/unit/test_differential.jl. The reference values in the tests come from an independent implementation and are quoted to 12 digits; the special functions are cross-checked against mpmath at 40 digits.

Evidence Mode (API)

Evidence Mode turns a run's outputs into a citable epistemic record. A claim is a sentence a reader could disagree with, a scope naming the part of the data it is about (its fibre: the rows whose value at a rank, or whose category in a set, is one of the named labels), and a warrant saying why it follows. Claims are stored in projects/{study}/[<group>/]<run>/evidence/claims.yml and can be downloaded as that exact YAML.

The record is served entirely by the Julia backend; the browser surface is delivered by the Vue/Marid client, so this repository ships no Evidence UI.

Endpoint What it answers
GET .../runs/{run}/evidence The record plus the context a claim may cite (tables, ranks, categories, provenance)
POST .../evidence/claims Create, replace or retire a claim; ids are safe names of at most 64 characters
DELETE .../evidence/claims/{id} Retire one claim
GET .../evidence/claims.yml The record exactly as it sits on disk
POST .../evidence/fiber A fibre's share per sample, its largest members, and an echo: the same scope through another projection (the other classifier at the same rank, the parent rank)
POST .../evidence/residuals Where a fibre departs from a declared null — proportional (the fibre spread across samples as the library is) or a stated claim_share

Validation is enforced server-side, so every client sees the same rules. Scope is one projection (rank or category) of 1 to 50 values of at most 200 characters; claim text is at most 2000 characters; a claim carries at most 5 warrants and 20 caveats; a blank status is stored as draft. A claim cannot be marked supported without at least one measurement, citation or inference warrant — a definition alone supports nothing. Echo verdicts are concordant, minor divergence, partial agreement or discordant; containment is contained, inconsistent or disjoint. Residuals report Pearson residuals, a log2 ratio and a χ² statistic without a p-value, counting |r| > 2.

Implementation: src/core/evidence.jl (schema, residuals, verdicts), src/server/routes/evidence.jl (the SQL), test/unit/test_evidence.jl and test/unit/test_evidence_routes.jl. Longer notes on the definitions, the schema and the limits are kept in docs/evidence-mode.md (documentation under docs/ is kept local by policy — see .gitignore).

Figures and report

The Figures tab lays analysis charts out on a page (A4, letter or a journal column width), in lettered groups of panels with shared colours, fonts and sizes, and exports it as PDF, PNG or TIFF at a chosen resolution. The Publication Tables tab builds one table at a time: rows of taxa at a rank or of categories from a Compositions set, columns of runs, sub-groups or samples, and any of ASVs, reads and percentage per column, with an optional difference between two sub-groups. A table can be copied for Word or downloaded as .xlsx or .csv. Charts, figures, tables and trees can be added to the study's Report tab, which downloads them together as one ZIP with a captions file.

The figure builder with page settings on the left and a two-panel figure of alpha diversity comparisons

Configuration

Pipeline settings use a cascade: each level overrides the one above it, and any key you omit is inherited from the nearest ancestor. The merged result is written to each run's run_config.yml, which records the settings the run used.

Settings can be edited in the web UI (per-study, per-group, or per-run) or in the YAML files.

File Purpose
config/defaults/ Canonical defaults for every setting; do not edit
config/composition.yml Composition library: named taxonomic filters and the category sets that reference them
config/presets/ Saved table-view filter presets, written from the Tables view
config/databases.yml Database URIs and optional local paths. Editable from the Databases page under SYSTEM in the sidebar
config/primers.yml Primer sequences and pair definitions. Editable from the Primers page under SYSTEM in the sidebar
config/tools.yml Tool binary paths (cutadapt, FastQC, MultiQC, vsearch, cd-hit-est, MAFFT, trimAl, IQ-TREE, RAxML, gappa)
config/pipeline.yml Machine-level overrides (lowest user-editable precedence)
data/{name}/pipeline.yml Study-level overrides
data/{name}/{group}/pipeline.yml Group-level overrides (intermediate directories)
data/{name}/{run}/pipeline.yml Run-level overrides (highest precedence)
projects/{name}/{run}/run_config.yml Generated merged config (provenance); do not edit

Each pipeline.yml stub is created with a comment block explaining that level's role. Write only the keys you want to change; omit the rest.

Configuring databases (config/databases.yml)

This is the single place to manage DB URIs shared across all projects.

databases:
  dir: "./databases"
  pr2:
    dada2:
      uri: "https://..."       # DADA2-format FASTA (downloaded on first use)
      local: ~                 # set to a local path to skip download
    vsearch:
      uri: "https://..."       # vsearch-format FASTA
      local: ~

Edit this on the Databases page under SYSTEM in the sidebar, or in the YAML. The page edits the shared cache directory (dir) and, per database, the dada2 and vsearch source URIs, a local: override for a file already on disk, remote_path (dada2 only) for a file already present on the remote taxonomy host, the ordered taxonomy levels, the vsearch_format parser (pr2 or generic), and the taxonomy corrections. Adding and removing a database is supported.

Removing or renaming a database, or changing its levels, reports the studies that use it, including those that inherit it through the cascade.

Both formats of one database should come from the same reference release, because the consensus of the two classifiers compares their labels as text. The editor warns when the version numbers in the two URIs differ.

Defining primer pairs (config/primers.yml)

Maps primer names to sequences and defines which forward/reverse sequences constitute a pair:

Forward:
  PrimerF: "CCAGCASCYGCGGTAATTCC"

Reverse:
  Primer1R: "ACTTTCGTTCTTGATYRA"
  Primer2R: "DCTKTCGTYCTTGATYRA"

Pairs:
  - PrimerPair1:
      - PrimerF
      - Primer1R
  - PrimerPair2:
      - PrimerF
      - Primer2R

Store all primer pairs in here and reference whichever combinations you need per project. A primer shared by two pairs is passed to cutadapt once; to pass it twice, add the same sequence under a second name.

Edit this on the Primers page under SYSTEM in the sidebar, or in the YAML. The page adds and removes primers and composes pairs from them, checking each sequence against the IUPAC codes as you type. A pair naming a primer that does not exist is rejected on save.

Pair names are referenced by cutadapt.primer_pairs in pipeline.yml. Removing or renaming a pair reports the studies, groups and runs that use it. Renaming a primer updates the pairs that use it.

Configuring cutadapt (cutadapt: in pipeline.yml)

Selects which primer pairs to apply and controls trimming behaviour.

cutadapt:
  # Names must match keys in the Pairs section of config/primers.yml.
  primer_pairs:
    - PrimerPair1
    - PrimerPair2
  min_length: 200           # discard reads shorter than this after trimming (-m)
  discard_untrimmed: true   # drop reads where no adapter was found (--discard-untrimmed)
  cores: 0                  # parallel cores; 0 = auto-detect (-j)
  quality_cutoff: ~         # 3' quality trimming cutoff, null to disable (-q)
  error_rate: ~             # max adapter mismatch rate, null = cutadapt default (-e)
  overlap: ~                # min adapter overlap length, null = cutadapt default (-O)
  optional_args: ""         # additional flags passed verbatim to cutadapt

Threads and remote execution (top level of pipeline.yml)

Both settings sit outside the stages. Changing either does not mark completed work stale.

# Worker threads for the DADA2 stages learn_errors, denoise, chimera_removal
# and assign_taxonomy: true = every core, false = one, or a positive integer.
r_threads: 4

# DISCLAIMER: you are solely responsible for ensuring you are authorised to use
# the host configured here, and to place sequencing data on it.
remote:
  host: ~                        # user@hostname; ~ keeps every stage local
  rscript: "Rscript"             # Rscript on the server
  staging_dir: "/absolute/path/on/server"
  identity_file: ~               # SSH key; ~ uses the agent, then a password prompt
  threads: ~                     # threads on the server; ~ uses r_threads (DADA2) or phylogeny.threads
  stages: []                     # any of the stages below
  tools:                         # programs the phylogeny stages call on the server
    mafft: "mafft"
    iqtree: "iqtree"
    raxml: "raxmlHPC-PTHREADS-SSE3"

Each stage listed in remote.stages uploads its own inputs and downloads its own outputs, so any combination of stages can run on the server:

Stage Uploads Returns
learn_errors the reads within the dada2.dada.nbases budget ckpt_errors.RData and the error-rate plot
denoise filtered reads and ckpt_errors.RData ckpt_denoise.RData
chimera_removal two checkpoints a checkpoint
assign_taxonomy one checkpoint, and the database unless dada2.remote_path in config/databases.yml names a copy on the server a checkpoint
phylogeny_align the reference FASTA the reference alignment (MAFFT)
phylogeny_tree the trimmed reference alignment the reference tree (IQ-TREE)
phylogeny_add the queries and the trimmed reference alignment the combined alignment (MAFFT --addfragments)
phylogeny_place the trimmed combined alignment and the reference tree the placement jplace (RAxML EPA)

trimAl and gappa run on this machine. Reference trees belong to no study and read remote from config/pipeline.yml; placements read it through their study's cascade.

filter_trim runs on this machine with DADA2's own threading, so r_threads does not apply to it.

Each stage invocation stages its files in its own directory on the server and removes it when the stage ends. Directories left by an interrupted stage are removed on a later connection once they are a week old.

Migrating from dada2.taxonomy.multithread and dada2.taxonomy.remote: both still work for assign_taxonomy and are deprecated. After moving them to the top level, run this once to keep completed taxonomy work current:

julia --project=. -e 'include("scripts/migrate_r_threads.jl"); MigrateRThreads.run_migration("projects")'

Configuring DADA2 (dada2: in pipeline.yml)

dada2:
  file_patterns:
    mode: "paired"               # paired | forward | reverse

  # Filter and trim; DADA2's filterAndTrim():
  filter_trim:
    trunc_q: 2
    trunc_len: [220, 220]        # [forward, reverse]; first value used for single-end mode
    max_ee: [3, 3]               # maximum expected errors in F and R reads
    min_len: 175
    max_n: 0
    match_ids: true
    rm_phix: true

  # Denoising; learnErrors() and dada():
  dada:
    seed: 123
    nbases: 200000000
    max_consist: 15
    pool_method: "pseudo"        # none | pseudo | true

  # Merging; mergePairs(), paired mode only:
  merge:
    min_overlap: 20
    max_mismatch: 0
    trim_overhang: true

  # ASV length filtering and chimera removal:
  asv:
    band_size_min: 200           # null to skip length filtering
    band_size_max: 430
    denovo_method: "consensus"   # consensus | pooled | per-sample

  # Taxonomy; assignTaxonomy() against the configured database:
  taxonomy:
    database: pr2                # key into config/databases.yml
    min_boot: 0                  # minimum bootstrap confidence to retain (0-100)
    # Rank names come from `levels:` in config/databases.yml.

  # Output filename prefixes (all written to dada2/Tables/):
  output:
    seq_table_prefix: "seqtab_nochim"
    fasta_prefix: "asvs"
    taxa_prefix: "taxonomy"

Outputs written to projects/{name}/{run}/dada2/Tables/:

File Contents
seqtab_nochim.csv Chimera-free ASV count table (samples x ASVs)
asvs.fasta / asvs.csv ASV sequences with short identifiers (seq1, seq2, ...)
taxonomy.csv Taxonomy assignments per ASV
taxonomy_bootstraps.csv Bootstrap confidence values per rank
taxonomy_combined.csv Taxonomy + bootstrap columns combined
tax_counts.csv Taxonomy + per-sample counts
asv_counts.csv ASV sequences + per-sample counts (no taxonomy)
pipeline_stats.csv Read counts retained at each pipeline stage

Configuring vsearch (vsearch: in pipeline.yml)

Controls the alignment thresholds used when assigning taxonomy against the reference database. The same settings apply to ASVs and OTUs.

vsearch:
  identity: 0.75        # minimum sequence identity (--id)
  query_cov: 0.8        # minimum fraction of query covered (--query_cov)
  maxaccepts: ~         # stop after this many hits per query, null = vsearch default
  maxrejects: ~         # max rejected candidates, null = vsearch default
  strand: ~             # "plus" or "both"; null = vsearch default
  optional_args: ""     # additional flags passed verbatim to vsearch

Configuring cd-hit-est (cdhit: in pipeline.yml)

Optional clustering step that collapses near-identical ASVs before vsearch taxonomy assignment. Use it with multiplexed primers, where one template amplified by different primers yields near-identical ASVs.

cdhit:
  identity: 1           # sequence identity threshold (-c)
  threads: 0            # worker threads; 0 = all available (-T)
  optional_args: ""     # additional flags passed verbatim to cd-hit-est

Configuring swarm (swarm: in pipeline.yml)

OTU clustering pipeline run in parallel with DADA2. Produces an OTU count table and FASTA which are carried through vsearch taxonomy assignment and merge_taxa alongside the ASV outputs.

swarm:
  differences: 1          # -d: max differences between sequences in the same cluster
  threads: 0              # -t: worker threads; 0 = all available
  chimera_check: true     # run vsearch --uchime_denovo before clustering
  min_abundance: 2        # --minsize: discard singleton dereps before clustering
  fastq_minovlen: 20      # min overlap for paired-end merging
  identity: 0.97          # --id: threshold for mapping reads back to OTU seeds
  optional_args: ""       # additional flags passed verbatim to swarm

Configuring phylogenetic placement (phylogeny: in pipeline.yml)

Controls building reference trees (reference) and placing a study's sequences on them (placement). A reference tree or placement can override its own keys from its page.

phylogeny:
  threads: 4                  # MAFFT, IQ-TREE, RAxML and gappa on this machine
  reference:
    align:                    # MAFFT
      strategy: "localpair"   # auto | localpair | genafpair | globalpair | 6merpair
      maxiterate: 1000        # --maxiterate; 0 omits it
    trim:                     # trimAl
      method: "manual"
      gap_threshold: 0.3
    tree:                     # IQ-TREE
      model: "MFP"            # -m; MFP selects a model with ModelFinder
      bootstrap: "standard"   # standard (-b) | ultrafast (-bb, 1000 replicates or more)
      replicates: 100
  placement:
    align:                    # MAFFT --addfragments onto the trimmed reference alignment
      strategy: "auto"
      maxiterate: 0
    trim:                     # trimAl
      method: "manual"
      gap_threshold: 0.01
    place:                    # RAxML EPA
      model: "GTRCATI"        # -m
      heuristic: 0.2          # -G; ~ tries every branch
    accumulate:               # gappa edit accumulate
      threshold: 0.8          # --threshold, 0.5 to 1

Each step also takes optional_args, additional flags passed verbatim to its tool.

Configuring merge_taxa (merge_taxa: in pipeline.yml)

Controls which filter configs are applied when merging taxonomy and count tables. merged.csv (unfiltered) is always written; each entry in filters produces an additional filtered CSV.

merge_taxa:
  filters:
    - "protist_filter.yml"   # -> merged/protist_filter.csv

Each entry names a filter in the filters: library of config/composition.yml. Remove all entries (or set filters: []) to produce only the unfiltered merged.csv.

Configuring analysis (analysis: in pipeline.yml)

Controls the defaults applied to the analysis charts (alpha diversity, taxa bar, NMDS, etc.). Per-chart choices such as the taxonomic rank and relative or absolute abundance are made in the UI.

analysis:
  exclude_categories:            # composition categories to drop from figures; [] to keep all
    - {set: contamination, category: Contaminant, apply_to: [diversity, taxa, venn]}
                                 # apply_to surfaces: diversity | taxa | composition | venn
                                 # (omit apply_to to act on every surface)
  alpha:                         # richness, Shannon and Simpson
    normalisation: none          # none | rarefy | srs (scaling with ranked subsampling)
    depth: 0                     # rarefy/srs reads per sample; 0 = smallest non-empty sample
    rarefaction_iterations: 100  # rarefy draws averaged per value
    show_points: true            # overlay individual sample points on boxplots
    annotate_significance: false # annotate pairwise significance on grouped alpha
    pairwise_brackets: false     # draw significance brackets between groups
    paired_samples: false        # treat samples as paired in the significance test
    paired_lines: false          # join each sample to itself across groups with a line
    significance_test: "kruskal_wallis"  # test used for group comparison
  beta:                          # Bray-Curtis for NMDS and PERMANOVA
    normalisation: hellinger     # hellinger | none | rarefy | srs
    depth: 0                     # rarefy/srs reads per sample; 0 = smallest non-empty sample
  differential:
    method: nb_glm               # nb_glm | clr_welch | multinomial | dirichlet_multinomial
                                 # ancom_bc | aldex2 | songbird | zinb | hurdle_nb
    offset: tss                  # count models: tss | rle
    dispersion: ml               # nb_glm: ml | glmGamPoi
    min_prevalence: 0.0          # fraction of samples a taxon must be seen in
    replacement_method: bayesian_multiplicative
                                 # clr_welch: bayesian_multiplicative | multiplicative_replacement
    replacement_delta: 0.65      # clr_welch: strictly between 0 and 1
    bayesian_alpha: ~            # Bayesian replacement: null (GBM estimate) or a number > 0
    mn_reference_taxon: ~        # multinomial models: null selects the most abundant fitted taxon
    mn_penalty: 0.0              # multinomial models: non-negative L2 penalty
    dm_overdispersion_method: mle # dirichlet_multinomial: mle | mom
    max_iter: 500                # multinomial models: Newton iteration cap
    tolerance: 0.00000001        # multinomial models: convergence tolerance
    max_seconds: 300.0           # multinomial models: wall-clock budget
    mc_samples: 128              # aldex2: Dirichlet Monte-Carlo instances in [16, 4096]
  nmds:
    max_stress: 0.2              # warn if NMDS stress exceeds this value

Configuring taxonomic filtering (filters: in config/composition.yml)

Each named filter in the filters: library of config/composition.yml defines one biological group to extract from the merged table. A category set references these filters by name, and the same filters back the merge_taxa.filters stage, which produces one additional CSV per entry. Edit them on the Compositions page under SYSTEM in the sidebar, or in the YAML.

Saved table-view presets are a separate concern and live in config/presets/; the Tables view reads and writes them.

Database-specific filters

Each filter carries a databases: key so that it is only applied when the active database matches. The following filters ship in the library:

Category PR2 match
bacteria_archaea Domain = Bacteria|Archaea
environmental_protozoa Subdivision = Cercozoa|Gyrista|Ciliophora|Chrompodellids
fungi Subdivision = Fungi
helminths Class = Nematoda (excl. Miculenchus)
parasitic_protozoa Subdivision = Apicomplexa|Parabasalia|Fornicata|Bigyra
plants_invertebrates Exclusion-based (PR2 ranks)
protist Exclusion-based (PR2 ranks)
vertebrates Class = Craniata

Example:

# fungi.pr2.yml
databases: [pr2]

filters:
  - column: Subdivision
    pattern: Fungi
    action: keep        # keep rows matching the pattern (default action is exclude)

remove_empty:
  - Subdivision

Filter file format

databases: [pr2]          # omit to apply regardless of active database

mappings:                 # optional column remapping applied before filters
  - source_column: Division
    target_column: Supergroup
    values: { Rhizaria: Rhizaria, Alveolata: Alveolata }

filters:
  - column: Domain
    pattern: "Bacteria|Archaea"
    regex: true           # false (default) = substring match
    action: exclude       # exclude (default) | keep

remove_empty:             # remove rows where this column is blank or "NA"
  - Domain

Deployment

Local (single machine)

bash start.sh

Open http://localhost:8080. The backend serves the frontend.

Split deployment (frontend and API on different origins)

To serve the frontend from a different address than the API, set apiBase in frontend/public/config.json to the API's address, and list the frontend's address when starting the server:

JULIA_METAMANIFOLD_ALLOWED_ORIGINS=https://frontend.example bash start.sh

The server accepts browser requests from its own origin (http://localhost:<port> and http://127.0.0.1:<port>), from the Vite dev server (localhost:5173), and from the origins listed in JULIA_METAMANIFOLD_ALLOWED_ORIGINS, separated by commas. An origin has to match exactly, scheme and port included: https://frontend.example does not cover http://frontend.example or https://frontend.example:8443. A page on any other origin gets 403 Forbidden: origin not allowed on every request that carries an Origin header, which is every write and every cross-origin read.

This also applies to an SSH tunnel on a different local port (e.g. http://localhost:9000): the browser's origin is then http://localhost:9000, not the server's http://localhost:8080, so start the server with JULIA_METAMANIFOLD_ALLOWED_ORIGINS=http://localhost:9000.

Input data

Place paired-end FASTQ files under data/{project_name}/ following Illumina naming:

data/MyProject/SampleName_*_L001_R1_001.fastq.gz
data/MyProject/SampleName_*_L001_R2_001.fastq.gz

For multi-run projects, nest runs in subdirectories. The server detects any directory containing .fastq.gz files as a leaf run and creates a matching project directory under projects/{project_name}/.

Output structure

All outputs for a given run live under projects/{project_name}/{run}/:

projects/{project_name}/{run}/
|-- cutadapt/                    # Trimmed FASTQ pairs and logs
|   `-- logs/
|-- QC/
|   |-- fastqc/                  # Per-file FastQC HTML reports
|   |-- multiqc_report.html      # MultiQC summary across all samples
|   `-- logs/
|-- dada2/
|   |-- Tables/
|   |   |-- seqtab_nochim.csv    # ASV count table
|   |   |-- asvs.fasta           # ASV sequences
|   |   |-- asvs.csv             # ASV sequence index
|   |   |-- taxonomy.csv         # Taxonomy assignments
|   |   |-- taxonomy_bootstraps.csv
|   |   |-- taxonomy_combined.csv
|   |   |-- tax_counts.csv       # Taxonomy + per-sample counts
|   |   |-- asv_counts.csv       # Sequences + per-sample counts
|   |   `-- pipeline_stats.csv
|   |-- Figures/                 # Quality profile and error rate PDFs
|   |-- Checkpoints/             # RData checkpoints for stage resumption
|   `-- Logs/                    # Per-stage R logs
|-- cdhit/
|   |-- asvs.fasta               # Clustered ASV sequences
|   `-- asvs.fasta.clstr         # Cluster membership file
|-- swarm/
|   |-- otus.fasta               # OTU representative sequences
|   |-- otus.count_table.csv     # OTU count table (samples x OTUs)
|   `-- logs/
|-- vsearch/
|   |-- taxonomy.tsv             # Top-hit taxonomy assignments (ASV or OTU)
|   `-- logs/
`-- merged/
    |-- evidence/
    |   `-- claims.yml           # Evidence record: claims, fibres, warrants
    |-- merged.csv               # Merged taxonomy + counts (all taxa)
    |-- protist_filter.csv       # Filtered subset (one per merge_taxa.filters entry)
    `-- results.duckdb           # DuckDB database for API queries

Phylogeny outputs sit beside the runs and in the shared library:

projects/{project_name}/
|-- trees/                       # Newick and jplace files for the tree viewer
`-- phylogeny/{id}/              # One placement
    |-- placement.json           # Name, reference tree, queries, settings
    |-- queries.fasta
    |-- align/combined.aln.fasta # References plus queries (MAFFT --addfragments)
    |-- trim/                    # combined.trim.fasta and the kept columns
    |-- place/placement.jplace   # RAxML EPA, with RAxML's own files
    |-- accumulate/accumulated.jplace
    `-- qc/, logs/, status.json, attestation.yml

reference_trees/{id}/            # One reference tree
|-- reference.json               # Name, description, settings
|-- references.fasta
|-- align/reference.aln.fasta
|-- trim/                        # reference.trim.fasta and the kept columns
|-- tree/reference.treefile      # With IQ-TREE's report, log and consensus tree
`-- qc/, logs/, status.json, attestation.yml

REST API

The server exposes a REST API under /api/v1/. Key endpoint groups:

Group Endpoints Description
Studies GET/POST/DELETE /studies, POST .../rename List, create, rename, delete studies
Groups POST/DELETE /studies/{study}/groups, POST .../rename Create, rename, delete groups
Runs GET/POST/DELETE /studies/{study}/runs, POST .../rename List, create, rename, delete runs
Config GET/PATCH/DELETE .../config, GET .../config/overrides Read and edit config at any cascade level; list downstream overrides
Primers GET /primers, GET /primers/document, PUT /primers List pair names; read and replace the whole primers document (validated before writing)
Pipeline POST .../pipeline, POST .../stages/{stage} Launch full-study, single-run, or individual-stage jobs
Jobs GET /jobs, GET/DELETE /jobs/{id} Monitor and cancel running pipeline jobs
Events GET /events Server-sent event stream of real-time job and stage updates
Results GET/POST/DELETE .../results/tables/... List, query, filter, save, export (.xlsx), and delete tables; OTU member drill-down
QC GET .../results/qc, GET .../results/dada2 MultiQC report metadata and DADA2 figures, logs, stats
Analysis POST .../runs/{run}/analysis/{alpha,chart}, GET .../runs/{run}/analysis/{pipeline-stats,ranks} Per-run charts and rank discovery
Cross-run POST /studies/{study}/analysis/{alpha,chart,chart-facet,nmds,permanova,ordination,venn,publication-tables} Comparison, faceted charts, NMDS, PERMANOVA, constrained ordinations (RDA/CCA/CAP/dbRDA), taxon overlap and publication tables across runs
Composition POST .../runs/{run}/composition/summary, POST .../composition/{source}/query, POST .../composition/{source}/distinct/{column} Organism-composition summaries and tables for a run
Composition library GET /composition, POST/DELETE /composition/{filters,sets}/{name}, GET /category-sets, POST/DELETE /category-sets/{name} Edit the filters and category sets in config/composition.yml
Evidence GET .../runs/{run}/evidence, GET .../evidence/claims.yml, POST .../evidence/claims, DELETE .../evidence/claims/{id}, POST .../evidence/{fiber,residuals} Claims, fibres, echoes and residuals for a run; the record is exportable as YAML
Reference trees GET/POST /reference-trees, GET/PUT/DELETE /reference-trees/{id}, GET/PUT .../fasta, POST .../run, GET .../log/{step}, GET .../qc/{align,trim}, GET .../alignment/{raw,trimmed}, GET .../treefile, POST .../trim-preview Build and inspect the shared reference trees
Placements GET/POST /studies/{study}/placements, GET/PUT/DELETE .../placements/{id}, POST .../placements/preview, GET/PUT .../fasta/queries, POST .../run, GET .../log/{step}, GET .../qc/{align,trim}, GET .../alignment/{raw,trimmed}, POST .../trim-preview Place a study's sequences on a reference tree
Trees GET/POST /studies/{study}/trees, GET/DELETE /studies/{study}/trees/{file}, PUT /studies/{study}/trees/{file}/view Upload, read and delete Newick and jplace files; save each tree's view state
Filter presets GET/POST/DELETE /filter-presets Save, list, delete reusable table filters
Databases GET /databases, GET /databases/document, PUT /databases, POST /databases/{key}/download List and download taxonomy databases; read and replace the whole databases document (validated before writing, returns advisory warnings)
System POST /init, GET /capabilities Initialise project directories; report server capabilities (e.g. R availability)

All responses are JSON. Analysis endpoints return Plotly chart specifications.

Running tests

julia --project=. test/runtests.jl                  # unit tests
julia --project=. test/runtests.jl --integration    # adds the MiSeq SOP pipeline run
julia --project=. test/runtests.jl --server         # adds the HTTP server smoke tests

Set CI_SKIP_TAXONOMY=1 to skip taxonomy assignment in the integration run. The full suite loads R and every package and needs several GB of memory; on a small machine run a few files at a time. cd frontend && bun run test runs the frontend unit tests.

Name unit test files to run only those: julia --project=. test/runtests.jl routes trees. dev/dev.sh runs the development server, rebuilds and tests one job at a time, stopping and restarting the server around builds and tests (dev/dev.sh alone lists its commands). Deferred work is listed in dev/TODO.md.

Architecture

frontend/           TypeScript + React + Vite (SPA)
src/
  core/             Types, config cascade, validation, DuckDB store, logging
  pipeline/         Pipeline stages (cutadapt, dada2, swarm, vsearch, cd-hit-est, merge_taxa, phylogeny)
  analysis/         Diversity metrics + Plotly chart builders
  server/           Oxygen.jl HTTP server (MetaManifold.Server) and its precompile workload
    routes/         REST API route handlers
scripts/            serve.jl (server entry point) and one-off maintenance scripts
config/             Default configs, filters, CI fixtures
data/               Input FASTQs (user-managed)
projects/           Pipeline outputs (generated)

Each pipeline stage returns a typed result (TrimmedReads, ASVResult, OTUResult, TaxonomyHits, MergedTables) and is skipped when its outputs are up to date (mtime-based for files, content-hash-based for configuration). Rerunning after a config change only re-executes the minimum necessary stages.

Third-party tools

This project orchestrates the following tools. Each is fetched from its upstream source by install.sh and is subject to its own licence; no third-party binaries are included in this repository.

Tool Licence Source
cutadapt MIT PyPI
FastQC GPL v3 Babraham Bioinformatics
MultiQC GPL v3 PyPI
DADA2 LGPL v3 Bioconductor
swarm GPL v3 GitHub Releases
vsearch GPL v3 GitHub Releases
cd-hit GPL v2+ GitHub Releases / apt
MAFFT BSD Source / apt
trimAl GPL v3 GitHub Releases
IQ-TREE GPL v2 GitHub Releases
RAxML GPL v3 Source / apt
gappa GPL v3 Source

Acknowledgements

This pipeline draws on the following prior work:

  • Frédéric Mahé: Fred's metabarcoding pipeline informed the overall workflow architecture, namely the sequencing of primer trimming, swarm.jl, vsearch-based taxonomy assignment, and the final table merge/filter stages.
  • Benjamin J. Callahan et al.: DADA2 tutorial, used under CC BY 4.0, on which dada2.jl and its modules are based.

The following colleagues at the Department of Parasitology, Charles University (Faculty of Science, BIOCEV, Vestec, Czech Republic) contributed to this work:

  • Mgr. Jiří Novák (supervisor): scripts from which several modules and configurations were adapted.
  • doc. Mgr. Vladimír Hampl: provided laboratory access and resources.
  • Mgr. Paulína Pristašová: <3.

Licence

Copyright © 2026 Joshua Benjamin Jewell.

Source code is licensed under the GNU Affero General Public License v3.0.

This documentation (README.md) is licensed under CC BY-SA 4.0.