SeqMux is a fast, portable FASTQ demultiplexer written in Rust.
It demultiplexes single-end and paired-end FASTQ using a sample barcode table (dual or single barcode), with quality/adapter trimming and multi-threaded streaming I/O. No Python, Conda, pigz, or SLURM required.
- Single-end and paired-end FASTQ (
.fastq/.fq/.gz) - SeqMux sample table CSV (
SampleNumber,Barcode1,Barcode2, …) - Dual-barcode demux (PE: both R1/R2 orientations; SE: R1 5′ + R1 3′)
- Single-barcode demux (Barcode1 only at R1 5′)
- Optional
Nbases in barcodes as UMI (written to header asrbc:) - Hamming-distance matching with mismatch tolerance; ties → unassigned
- Quality trimming (BWA-style) and 3′ adapter trimming
- Multi-threaded chunked pipeline with ordered per-sample output
- Gzip output without shelling out to external tools
- Summary TSV + stderr report
git clone https://github.com/Caizhaohui/SeqMux.git
cd SeqMux
cargo build --release
# binary: target/release/seqmuxRequirements: Rust stable (1.74+). No external runtime tools.
Paired-end (recommended for dual barcode):
seqmux demux \
-i mix_R1.fastq.gz \
-I mix_R2.fastq.gz \
-b sample_barcodes.csv \
-o results \
-t 8Single-end:
seqmux demux \
-i reads.fastq.gz \
-b sample_barcodes.csv \
-o results \
-t 8Validate FASTQ:
seqmux validate -i reads.fastq.gz
seqmux validate -i r1.fastq.gz -I r2.fastq.gzSeqMux reads a CSV with a header row. This is the only supported barcode input.
SampleNumber,Barcode1,Barcode2
I464469-A1,AAGTCCAA,GGAGTACT
I464469-A2,GACCTGAA,GGACTTGG
I464469-A3,GGCTTAAG,TGGATCGASame layout as experimental tables such as I464-469erdai_barcode_and_name.csv:
SampleNumber,Barcode1,Barcode2,PCR_product,rawdata1,rawdata2,library_round,F_primer,R_primer
I464469-A1,AAGTCCAA,GGAGTACT,atgcca...,mix_1.fq.gz,mix_2.fq.gz,A,fwd...,rev...
I464469-A2,GACCTGAA,GGACTTGG,agcgcg...,mix_1.fq.gz,mix_2.fq.gz,A,fwd...,rev...A trimmed example file is included at examples/sample_barcodes.csv.
| Column | Required | Description |
|---|---|---|
SampleNumber |
Yes | Sample ID / output name (unique) |
Barcode1 |
Yes | First barcode (DNA: A/C/G/T/N) |
Barcode2 |
Optional* | Second barcode for dual-index demux |
*If any row has Barcode2, all rows must have Barcode2.
If Barcode2 is omitted entirely, SeqMux runs in single-barcode mode.
| Logical field | Accepted headers |
|---|---|
| Sample | SampleNumber, Sample, Sample_Name, Sample_ID, Name |
| Barcode1 | Barcode1, Barcode_1, BC1, Index1, i7 |
| Barcode2 | Barcode2, Barcode_2, BC2, Index2, i5 |
Extra columns (PCR_product, rawdata1, rawdata2, library_round, F_primer, R_primer, …) are ignored and may remain in the file for laboratory records.
- Header row is required
- Sample names must be unique
- Barcode combinations
(Barcode1, Barcode2)must be unique - Bases: only
A/C/G/T/N(auto-uppercased);N= UMI position - Empty lines are skipped via the CSV reader
| Mode | When | Matching |
|---|---|---|
| Dual barcode + PE | Barcode2 present and -I given |
Both orientations: Barcode1@R1 5′ + Barcode2@R2 5′, and Barcode2@R1 5′ + Barcode1@R2 5′. Swapped mates are rotated so output R1 has Barcode1 (unless --no-canonicalize). |
| Dual barcode + SE | Barcode2 present, no -I |
Barcode1 @ R1 5′, Barcode2 @ R1 3′ |
| Single barcode | no Barcode2 column / all empty |
Barcode1 @ R1 5′ only |
After assignment (unless --keep-barcodes):
- PE dual: trim Barcode1 from R1 5′ and Barcode2 from R2 5′
- SE dual: trim Barcode1 from 5′ and Barcode2 from 3′
- Single: trim Barcode1 from 5′
Mismatch thresholds: --mismatches-1 / --mismatches-2. Best unique score wins; ties go to unassigned. Default is exact match (0). Use 1 only after checking docs/MISMATCH_QC.md.
Barcode matching uses the original 5′ sequence, then barcodes are trimmed, then quality/adapter trim. 5′ quality trimming therefore cannot eat the barcode. 3′ Illumina adapter trimming is on by default; use --no-adapter to turn it off.
Orientation vs the lab Python demux: SeqMux canonicalizes so output R1 carries Barcode1. The I395/I464 Python scripts wrote the Barcode2-bearing mate as R1. Assignment counts match; R1/R2 file contents are swapped relative to those scripts unless --no-canonicalize is set. See docs/COMPATIBILITY.md.
| Option | Description |
|---|---|
-i / --input |
R1 or single-end FASTQ |
-I / --input2 |
R2 FASTQ |
-b / --barcodes |
Sample barcode table CSV |
-o / --out-dir |
Output directory |
-p / --prefix |
Output prefix (default seqmux) |
-t / --threads |
Worker threads |
--max-reads |
Stop after N reads/pairs (0 = all) |
--skip-reads |
Skip N reads/pairs before processing |
--counts-only |
Count only; do not write FASTQ |
--mismatches-1 |
Allowed mismatches for Barcode1 |
--mismatches-2 |
Allowed mismatches for Barcode2 |
--orientation |
Dual PE: both (default), canonical, swapped |
--no-canonicalize |
Do not rotate swapped mates to Barcode1-on-R1 |
--keep-barcodes |
Do not trim barcode bases |
--discard-unassigned |
Drop unassigned reads |
-a / --adapter-r1 |
3′ adapter for R1 (default Illumina AGATCGGAAGAGCACACGTCTGAA) |
--adapter-r2 |
3′ adapter for R2 (default Illumina AGATCGGAAGAGCGTCGTG) |
--no-adapter |
Disable 3′ adapter trimming |
-q / --quality-cutoff-3 |
3′ Phred quality cutoff |
-l / --min-length |
Minimum length after trim |
--force |
Overwrite existing outputs |
--no-gzip |
Write plain FASTQ |
Run seqmux demux --help for the full list.
Single-end:
<prefix>_<SampleNumber>.fastq.gz
<prefix>_unassigned.fastq.gz
<prefix>.summary.tsv
Paired-end:
<prefix>_<SampleNumber>_R1.fastq.gz
<prefix>_<SampleNumber>_R2.fastq.gz
<prefix>_unassigned_R1.fastq.gz
<prefix>_unassigned_R2.fastq.gz
Example: sample I464469-A1 → seqmux_I464469-A1_R1.fastq.gz.
- Pipeline:
Reader → bounded chunks → N workers → ordered writer - Workers never open output files; writer alone finishes gzip streams
- No temporary
_tmp_thread_*files and no finalcatmerge
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all
cargo build --releaseMIT — see LICENSE and NOTICE.md.
Pipeline and trimming ideas were informed by demultiplexing practice in the NGS community, including Ultraplex.