Notes

Negative-control enrichment filter

ViralUnity uses interpretable enrichment statistics to filter taxa that could represent contamination bleed-in from negative controls. The filter compares each sample’s metric (RPKM when --viral-genomes is configured, otherwise RPM) to the distribution observed across negative-control samples.

Decision tiers:

n_negative_controls

neg_decision

Gate

0

none

No filter; neg_pass = NA (treated as keep)

1

log10_ratio

log10((sample + pc) / (control + pc)) --log10-ratio-threshold

≥ 2

z_score

(sample control_mean) / control_sd --z-score-threshold

≥ 2, SD = 0

log10_ratio_fallback

Same as single-control gate

All metrics are recorded in the summary at the .neg step (the negative-control table in the cumulative chain, e.g. *_RPKM.nr.bleed.neg.tsv) for full traceability: fold_enrichment, log10_ratio, z_score, control_mean, control_sd, control_median, control_max, neg_metric, neg_decision, neg_pass, plus the thresholds and pseudocount used.

Taxa absent from the negative-control rows are treated as having a zero background (control_mean = 0) — they pass the enrichment gate easily, which is the conservative choice. The lineage-aware Krona filter (filter_krona_by_pass_taxids.py) also treats NA neg_pass as keep, so increasing the number of controls can only make the filter stricter, never accidentally drop a taxon.

RPKM and genome length: when --viral-genomes points to a RefSeq FASTA (with a corresponding --viral-taxids genome2taxid mapping), ViralUnity builds a per-taxon genome-length table at all ranks (family, genus, species) using the median length across all accessions under each node. RPKM at higher ranks (genus, family) is an approximation based on this median.

Segmented viruses

ViralUnity natively supports the assembly of segmented viral genomes. Instead of running the pipeline multiple times, pass multiple segment references with --segmented-reference:

viralunity consensus illumina \
    --sample-sheet samples.csv \
    --config-file config_segmented.yml \
    --output /path/to/output \
    --segmented-reference S=/path/to/s_segment.fasta \
    --segmented-reference L=/path/to/l_segment.fasta

When this argument is detected, the pipeline dispatches a specialised modular workflow that processes each segment independently in parallel. Results are organised under samples/{sample_name}/{segment_name}/.

Nanopore data

The metagenomics pipeline supports Nanopore data via viralunity meta nanopore. There is no fastp-based QC step; optional dehosting, MEGAHIT assembly, and Racon/Medaka polishing are available. Sample sheets have two columns (sample ID, path to one FASTQ/FASTA per sample).

Reference header sanitization

The nanopore consensus workflow automatically sanitizes reference FASTA headers before use. Special characters (/, \, |, ,, ~, and spaces) in sequence identifiers are replaced with underscores (_). This prevents issues with downstream tools such as clair3 that use the sequence ID to create output directories.

Dynamic reference assembly

When --run-reference-assembly is enabled, the metagenomics pipeline adds a post-classification step that automatically selects reference genomes and runs consensus assembly for each sample that meets the hit threshold.

How it works

  1. Selection checkpoint — after taxonomic classification, a Snakemake checkpoint reads the taxa summary TSVs and selects candidate references. The --method and --source flags control which classification results are used (e.g., kraken2 on reads). Only samples with at least --reads-count reads (or --contigs-count contigs) assigned to one of the --families are selected.

  2. Reference extraction — for each selected sample, a unique assembly key (ref_key) is constructed from the matched family and the reference accession ({family}_{accession}). The corresponding genome is extracted from --viral-genomes. The selection strategy controls how the reference accession is chosen (see below).

  3. Consensus assembly — uses the same alignment + consensus rules as viralunity consensus, with the dynamically-selected reference. Output goes to assembly/{ref_key}/.

Reference selection strategies

--reference-selection-strategy taxid (default)

For each sample that has classification hits to a target family, every taxid from the summary (Kraken2 or Diamond) is looked up in --viral-taxids (genome2taxid.tsv). The lookup is performed in two steps:

  1. Exact match — the taxid value from the summary row is searched directly in genome2taxid.tsv. If one or more accessions are found, all of them are used as reference targets.

  2. Species-level fallback — if the exact taxid is absent (e.g. the classifier assigned a strain or subspecies taxid that is below species in the NCBI taxonomy), the species-level ancestor is resolved via --taxdump and retried against genome2taxid.tsv.

--taxdump is used only to (a) validate that the matched taxid belongs to a target family and (b) build the reference key label — it is not used to normalise the rank of the lookup itself. A warning is emitted once per family if no accession could be found for that family in the sample.

When to use: when your classifier database and your --viral-genomes database were both built from the same RefSeq release. The taxid linkage is direct and requires no sequence comparison. Fast and deterministic.

Limitation: a taxid match does not guarantee the reference is the closest relative in the database — it only guarantees taxonomic identity. If you need the closest possible reference rather than any representative of the species, use similarity instead.

Required databases: --viral-genomes, --viral-taxids, and --taxdump (all produced by viralunity get-databases virus-genome / NCBI taxdump).

viralunity meta illumina \
    --sample-sheet samples.csv \
    --config-file config.yaml \
    --output /path/to/output \
    --kraken2-database /path/to/kraken2_db \
    --krona-database /path/to/krona_taxonomy \
    --taxdump /path/to/taxdump \
    --run-reference-assembly \
    --reference-selection-strategy taxid \
    --method kraken2 \
    --source reads \
    --families Coronaviridae,Orthomyxoviridae \
    --reads-count 100 \
    --viral-genomes databases/virus_genomes/viral.genomes.fasta \
    --viral-taxids databases/virus_genomes/genome2taxid.tsv \
    --threads 4 --threads-total 8

--reference-selection-strategy similarity

De novo assembled contigs from each sample are BLASTed against --viral-genomes using blastn. For each contig, BLAST returns hits sorted by bitscore (best first). The first hit per contig that passes both --blast-qcov and --blast-pident thresholds is selected; subsequent hits for the same contig are ignored. The selected accession is then validated against --viral-taxids and --taxdump: only hits whose taxid (at any rank — strain, species, genus, or family) traces back to a target family are kept as reference targets.

When to use: when sequence identity to a specific genome matters more than taxonomic label — for example, when working with divergent or novel strains where taxid-based lookup may return a reference that is genetically distant. Requires de novo assembly (--run-denovo-assembly) to produce the contigs used as BLAST queries.

Limitation: requires a pre-built BLAST index alongside --viral-genomes (created automatically by viralunity get-databases virus-genome). If no contigs pass the identity/coverage thresholds, no reference assembly is triggered for that sample.

Required databases: --viral-genomes with its BLAST index, --viral-taxids, and --taxdump (produced by viralunity get-databases virus-genome / NCBI taxdump).

viralunity meta illumina \
    --sample-sheet samples.csv \
    --config-file config.yaml \
    --output /path/to/output \
    --kraken2-database /path/to/kraken2_db \
    --krona-database /path/to/krona_taxonomy \
    --taxdump /path/to/taxdump \
    --run-denovo-assembly \
    --run-reference-assembly \
    --reference-selection-strategy similarity \
    --method kraken2 \
    --source reads \
    --families Coronaviridae,Orthomyxoviridae \
    --reads-count 100 \
    --blast-qcov 80 \
    --blast-pident 80 \
    --viral-genomes databases/virus_genomes/viral.genomes.fasta \
    --threads 4 --threads-total 8

Strategy comparison

taxid

similarity

Requires de novo assembly

No

Yes

Requires --viral-taxids

Yes

Yes (family validation)

Requires --taxdump

Yes (family validation + species fallback)

Yes (family validation)

Requires BLAST index

No

Yes

Selection basis

Taxonomic ID match (exact → species fallback)

Best BLAST hit per contig

Speed

Fast

Slower (BLAST per sample)

Best for

Known strains with good RefSeq coverage

Divergent or novel strains

Required databases

Option

File produced by

Used by

--viral-genomes

viralunity get-databases virus-genome

Both strategies

--viral-taxids

viralunity get-databases virus-genome

Both (taxid: primary lookup; similarity: family validation)

--taxdump

NCBI taxdump

Both (family validation; species-level fallback for taxid)

BLAST index (.nhr/.nin/.nsq)

viralunity get-databases virus-genome

similarity only

Running tests

pytest -v test