16S rRNA (bacteria and archaea)¶
16S is the default marker: config/config.yaml ships with amplicon.type: 16S.
What this page is, and what it is not
This page is documentation. It is not a settings file, it does not sit "above" or
"below" config/config.yaml, and nothing written here is in force on your machine.
Every YAML block below is an illustration of what to put in your own config.
Two real files decide how a 16S run behaves, and they do not overlap:
workflow/markers/16S.yaml— the marker pack. Facts about the marker: which databases to use, that the amplicon length is measured by in-silico PCR against SILVA, that Metaxa2 is the region extractor, that the rank model has seven slots (the last of which only the separate species database can fill). Data, not knobs. You normally never touch it.config/config.yaml, plus any run config merged on top — the choices about your experiment: primers, expected length, truncation, taxon filter, paths.
The pack is loaded into its own namespace and is never merged into the config, so
almost nothing can arrive from both. The one exception is a database path under
references:, where a config entry wins over the pack default — that is how you
point at a shared copy of SILVA instead of downloading your own. The full picture is
in which file a setting actually comes from.
Setting up a run¶
Set type: 16S and supply the forward and reverse primer FASTAs for the region
amplified. The rest of the block below reproduces the shipped defaults for the keys you
are most likely to want to set on a 16S run — not every key the run touches; cutadapt,
trunc_len and length_filter are covered further down and in the
configuration reference. You do not need to copy any
of it into your own config to get these values, since the template already provides them.
The two exceptions are keep and discard: those ship commented out, so the values
shown are the recommended 16S starting point rather than something already in effect.
amplicon:
type: 16S
primers:
fwd: /path/to/fwd_primer.fasta
rev: /path/to/rev_primer.fasta
orientation: fixed # fixed | mixed
expected_length: auto # in-silico PCR against SILVA
probe_length_stat:
16S: p95 # which statistic of the probe distribution to use
min_overlap: 12 # bp of R1/R2 overlap the truncLen pair must preserve
extraction:
enabled: true # Metaxa2
taxonomy:
method: sintax # rdp | sintax
min_boot: 80 # rdp only
sintax_cutoff: 0.8 # sintax only
try_rc: true # rdp only
filter:
enabled: true
keep: [k__Bacteria, k__Archaea] # recommended, ships commented out
discard: [o__Chloroplast, f__Mitochondria] # recommended, ships commented out
Note
amplicon.probe_length_stat.16S must be present for a 16S run, whether or not
expected_length is auto — MetaFlux checks the key at startup and exits with a
message naming it if it is missing. The shipped config/config.yaml already has it.
Reference databases¶
Three SILVA files are involved. None of them needs to be listed in the config: the
marker pack carries the filenames and download URLs, and MetaFlux fetches what the
run needs on first use into references.refdb_root (default refdb), where it stays
cached for later runs.
| Pack symbol | Path under refdb_root |
What it is used for | How it arrives |
|---|---|---|---|
silva_train |
silva/silva_nr99_v138.2_toGenus_trainset.fa.gz |
RDP training set and the in-silico PCR substrate | download from Zenodo (rule fetch_silva_train) |
silva_species |
silva/silva_v138.2_assignSpecies.fa.gz |
addSpecies reference (16S only) |
download from Zenodo (rule fetch_silva_species) |
silva_sintax |
silva/silva_nr99_v138.2_sintax.fa.gz |
SINTAX database for VSEARCH | built locally from silva_train (rule convert_silva_sintax) |
The three headers look like this — the differences matter for what each step can return:
silva_train >Bacteria;Pseudomonadota;Gammaproteobacteria;Enterobacterales;Vibrionaceae;Vibrio;
silva_species >AB000390.1.1428 Vibrio halioticoli
silva_sintax >1;tax=d:Bacteria,p:Pseudomonadota,c:Gammaproteobacteria,o:Enterobacterales,f:Vibrionaceae,g:Vibrio
The training set is a toGenus release: its lineages stop at genus, with no rank
prefixes and no species field. That single fact explains both the addSpecies step
and the rank depth of the two taxonomy methods, below.
An entry under references: in the run config is only needed to relocate a file that
already exists elsewhere, for instance on a shared filesystem:
references:
refdb_root: refdb
silva:
train: /shared/dbs/silva_nr99_v138.2_toGenus_trainset.fa.gz
species: /shared/dbs/silva_v138.2_assignSpecies.fa.gz
sintax: /shared/dbs/silva_nr99_v138.2_sintax.fa.gz
Amplicon length probe (probe_mode: pcr)¶
SILVA sequences are full-length 16S genes, so the length of the amplicon the primers
actually produce has to be worked out rather than read off the database. The
amplicon_probe rule does an in-silico PCR with two Cutadapt passes over
silva_train:
- keep the reference sequences in which the forward primer matches, trimming it and
everything upstream of it (
-g file:<fwd> --discard-untrimmed); - of those, keep the ones in which the reverse complement of the reverse primer
matches, trimming it and everything downstream
(
-a file:<rev_rc> --discard-untrimmed --minimum-length 30).
Neither match is anchored, and that is the point: SILVA holds whole 16S genes, so the
primers bind inside them. Anchoring to the ends would match nothing. Each pass strips
the flanking gene sequence the primers would never have amplified, so what survives both
is the amplicon body itself. Primer mismatches are tolerated at
amplicon.cutadapt.max_error_rate (default 0.2). The rule then
writes the length distribution — n_amplicons, min, q1, median, q3, p95,
p99, max, mean, stdev — to a JSON file in the project-level cache:
refdb/cache/probe_16S_silva_v138.2_toGenus_<primer_hash>.json
refdb/cache/probe_16S_silva_v138.2_toGenus_<primer_hash>.amplicons.fa.gz
<primer_hash> is the first 12 hex characters of a SHA-256 over the two primer files.
Different primers give a different filename, so the probe re-runs; the same primers
hit the cache, which lives inside the repository and therefore survives across runs
and across output directories.
Two steps consume the probe:
pick_trunclentakesexpected_lengthfrom the statistic named inprobe_length_stat.16S(defaultp95) and uses it in the overlap constraint.dada_length_filterbuilds its ASV length window as[q1 − window_margin, p95 + window_margin], withwindow_margindefaulting to 50 bp.
The probe only runs when expected_length: auto. A fixed integer or a [min, max]
range skips it entirely.
Warning
If no reference sequence carries both primers, the probe stops with an error rather
than returning an empty distribution. The usual causes are a primer written in the
wrong orientation for the database, or a degenerate primer that was collapsed to a
single consensus sequence before being written to the FASTA — keep the IUPAC
ambiguity codes, Cutadapt matches them. A low cutadapt.max_error_rate on a
mismatch-tolerant primer has the same effect.
Truncation¶
16S uses the standard quality-based truncation picker: truncLen is cut just before the
position where the aggregated per-base lower quartile first falls below
trunc_len.q_threshold, subject to keeping the pair mergeable —
— with trunc_len.resolve_policy deciding what happens when quality and overlap
disagree.
Three checks run in a fixed sequence. First, a coverage gate caps each sample at the
length trunc_len.min_read_coverage_pct (95%) of its reads still reach, so the quality
search cannot pick a position most reads never get to. Only then is the quality
threshold applied, and only then the overlap constraint. See
Amplicon length and truncation for the full picture, and
Troubleshooting for what a run looks like when the cut still
lands past the real read length.
Region extraction with Metaxa2¶
With extraction.enabled: true (the default), the target_extract rule runs
Metaxa2 over the ASV sequences to keep
only the part of each ASV that is genuinely small-subunit rRNA. Anything that flanks
it — primer remnants, chimeric tails, non-ribosomal carry-over — is trimmed away, so
the ASV lengths that reach the length filter are directly comparable to the probe
distribution measured on SILVA.
| Input | 5.dada2/seqs.fasta and 5.dada2/seqtab_head_names.txt, from dada_seqtab |
| Command | metaxa2 -t all --fasta T --table T --plus T --silent T |
| Output | 5.dada2/seqs_extracted.fasta, 5.dada2/seqtab_extracted_head_names.txt, 5.dada2/metaxa2_extraction.results.txt |
| Raw tool output | kept under 5.dada2/metaxa2/ for inspection |
| Consumed by | dada_length_filter |
-t all means all domains are scanned and every extracted sequence is kept.
Metaxa2 is not used to decide what is bacterial: that decision is made later, by the
taxon filter, on the classified lineage. ASVs Metaxa2 cannot extract are dropped from
both the FASTA and the count table, and the drop is reported in the rule log.
For 16S the pre- and post-extraction length distributions usually look almost the
same, because a clean 16S amplicon is SSU from end to end. Both are plotted in
stats/dada2/asv_length_hist.png, which makes an unexpected difference easy to spot.
Metaxa2 reports origin twice, and only the second answer is the real one¶
Nothing in MetaFlux depends on this — extraction keeps every sequence whatever its
origin, and the taxon filter makes the real call later from SILVA. It matters when
you open the Metaxa2 files yourself to ask how much chloroplast and mitochondrial
carry-over a run had, because the raw output under 5.dada2/metaxa2/ answers that
question twice and the two answers disagree badly.
First pass — the HMM screen. Metaxa2 begins by sliding profile HMMs along each
sequence to decide, cheaply, whether it is small-subunit rRNA at all and where the
gene starts and stops. The job of this pass is to find the rRNA and mark its edges,
not to identify it. Its verdict is the single letter in column 3 of
seqs.extraction.results (B, A, E, C, M, O for bacteria, archaea,
eukaryota, chloroplast, mitochondria, other) and the block of seqs.summary.txt
headed "SSU rRNA sequences by preliminary origin". Metaxa2's own choice of word —
preliminary — is the warning.
Second pass — the BLAST refinement. The extracted regions are then aligned
against Metaxa2's reference database and classified on the alignment. This is the
result worth quoting. It is what lands in seqs.taxonomy.txt, in the per-origin
FASTAs (seqs.bacteria.fasta, seqs.mitochondria.fasta, and so on), and in the
block of seqs.summary.txt headed "Number of SSU rRNA sequences assigned to each
origin".
The distance between them is not a rounding difference. On the 16S test set, of the 231 ASVs Metaxa2 recognised as SSU rRNA:
| Origin | First pass, HMM screen | Second pass, after BLAST |
|---|---|---|
| Bacteria | 70 | 199 |
| Chloroplast | 56 | 0 |
| Mitochondria | 105 | 28 |
| Uncertain | — | 4 |
Taken from the first pass, this library reads as 45% mitochondrial and 24% chloroplast — a sample swamped by plant material, and a result that would send you looking for a problem with the primers. Taken from the second, it is 86% bacterial with no chloroplast at all.
The second reading is the correct one, and the disagreement is expected rather than a malfunction. Chloroplasts and mitochondria descend from free-living bacteria and their SSU genes still look bacterial, so the organellar HMM profiles and the bacterial ones overlap heavily. A fast profile scan cannot separate them; an alignment against full-length references can. The screen is deliberately permissive because its purpose is to avoid missing rRNA, not to name it.
These are ASV counts, not read counts
Every number above counts distinct sequences. A run can be 12% mitochondrial by
ASV and a very different figure by abundance, since organellar ASVs are often
numerous but individually rare, or the reverse in a root or leaf sample. To weight
by reads, join the per-origin FASTA headers back to 6.taxonomy/asv_table.txt.
Taxonomy¶
amplicon.taxonomy.method picks one of two classifiers. Both read the
length-filtered ASVs (5.dada2/seqs_lenfilt.fasta plus the matching
seqtab_lenfilt_head_names.txt), both apply the same taxon filter, and both write the
same three tables into 6.taxonomy/.
rdp |
sintax |
|
|---|---|---|
| Tool | DADA2 assignTaxonomy, then addSpecies |
vsearch --sintax |
| Reference | silva_train + silva_species |
silva_sintax |
| Confidence setting | min_boot: 80 |
sintax_cutoff: 0.8 |
| Reverse complement | try_rc: true |
--strand both, always |
| Deepest rank returned | Species, as a binomial | Genus |
| Conda environment | workflow/envs/taxonomy.yaml |
workflow/envs/vsearch.yaml |
| Reproducibility | fully fixed by amplicon.seed |
per-rank confidence can drift above 1 thread |
The RDP path classifies the unique ASV sequences and then expands the result back so every ASV ID gets its own row — extraction can leave two ASVs with an identical trimmed sequence, and this keeps them from being classified twice.
VSEARCH SINTAX needs about 2–3 GB regardless of how many ASVs are in the table, which
makes it the practical choice on large runs. Its bootstrap confidence is not
byte-reproducible when it runs multithreaded: VSEARCH races several threads on one
random-number stream, so a handful of per-rank confidence values near the cutoff can
shift between otherwise identical runs. Setting
resources.threads.assign_taxonomy: 1 removes that at the cost of speed.
The species step (16S only)¶
silva_train stops at genus, so the RDP path alone cannot name a species. 16S is the
only marker whose pack declares a taxonomy_species_db, and for it the rdp branch of
assign_taxonomy runs DADA2's addSpecies against silva_species — a separate,
species-annotated SILVA file — immediately after assignTaxonomy. MetaFlux calls it
with DADA2's own defaults, so a species is added only on an unambiguous exact match;
ASVs with no match, or with more than one, keep an empty species slot.
addSpecies returns the bare epithet (subtilis), not the full binomial. The 16S
pack sets prefix_style: bare, which tells the taxonomy-string builder two things:
add the rank prefixes itself, and render the last slot as a Genus + species binomial
by gluing the genus back on. A finished 16S lineage therefore reads:
Under sintax there is no species rank at all. The SINTAX database is converted from
the same toGenus training set and carries only the six ranks d: to g:, so the
Species column of taxon_seq_table.txt stays empty and no s__ segment appears in
the taxonomy string. Species-level 16S calls require method: rdp.
Tip
Species assignment from a single 16S region is exact-match only and often absent — that is the method, not a failure of the run. Where species resolution is the point of the experiment, the two protein-coding markers are the alternative: gyrB has higher intra-genus resolution than 16S, and rpoB assigns far more ASVs to species than 16S V3–V4 does.
Note that the first rank is written k__ under both methods, even though SINTAX
labels it d: internally: the prefixes are keyed to what the rank means, not to the
letter the database happens to use. A keep: [k__Bacteria] token therefore behaves
identically whichever method is selected.
Keeping and discarding taxa¶
There is no built-in filter default — the keep and discard lists in the shipped config are commented out on purpose, so a run that forgets them does no filtering rather than silently applying another marker's tokens. The recommended 16S starting point:
taxonomy:
filter:
enabled: true
keep: [k__Bacteria, k__Archaea]
discard: [o__Chloroplast, f__Mitochondria]
keepruns first and defines what survives.[k__Bacteria, k__Archaea]keeps prokaryotes and drops both off-target eukaryotic hits and ASVs that stayed unclassified at kingdom level.discardthen prunes what is left. Chloroplast and mitochondrial 16S are amplified by most universal primers from any plant, algal or host material in the sample, and they are ribosomal enough to pass Metaxa2 — the filter is where they go.- Lab and kit contaminants a negative control revealed can be added to
discardas further genus tokens, e.g.[o__Chloroplast, f__Mitochondria, g__Ralstonia].
Each token is matched against a whole rank segment of the taxonomy string, so
g__Bacillus matches a genus named exactly that and never a substring somewhere else
in the lineage. If the filter removes every ASV, MetaFlux stops with an error instead
of writing an empty table — that combination nearly always means the tokens belong to
a different marker. Full behaviour on
Keeping and discarding taxa.
What a 16S run produces¶
The final tables are in 6.taxonomy/:
| File | Rows | Contents |
|---|---|---|
asv_table.txt |
ASV_# IDs |
per-sample counts plus a taxonomy column |
asv_table_seqs.txt |
ASV sequences | the same table, keyed by sequence |
taxon_seq_table.txt |
ASV_# IDs |
one column per rank (Kingdom…Species) plus the sequence |
Alongside them, stats/trunclen.json records the truncation decision,
stats/dada2/asv_length_stats.json and asv_length_hist.png record the ASV length
distributions before extraction, after extraction and after the length filter, and
stats/read_tracking.txt shows how many reads each sample kept at every stage. The
full layout is in Output files.