fqtk

June 15, 2026 · View on GitHub

Build Status license Version info Install with bioconda DOI

A toolkit for working with FASTQ files, written in Rust.

Fulcrum Genomics

Visit us at Fulcrum Genomics to learn more about how we can power your Bioinformatics with fqtk and beyond.

Email Fulcrum Genomics Visit Fulcrum Genomics

fqtk provides several tools for working with FASTQ files:

  • demux — demultiplex one or more FASTQ files into per-sample FASTQs using sample barcodes at fixed positions within the reads.
  • shard — split one or more matched FASTQs (e.g. R1/R2) into N shards, assigning reads round-robin so each input read ends up in exactly one output FASTQ.
  • subsample — randomly subsample reads from one or more synchronized FASTQs.

All tools are highly efficient and multi-threaded for high performance.

fqtk demux

fqtk demux demultiplexes one or more FASTQ files (e.g. a set of R1, R2 and I1 FASTQ files) with any number of sample barcodes at fixed locations within the reads.

Usage for fqtk demux follows:


Performs sample demultiplexing on FASTQs.

The sample barcode for each sample in the metadata TSV will be compared against the sample
barcode bases extracted from the FASTQs, to assign each read to a sample.  Reads that do not
match any sample within the given error tolerance will be placed in the ``unmatched_prefix``
file.

FASTQs and associated read structures for each sub-read should be given:

- a single fragment read (with inline index) should have one FASTQ and one read structure
- paired end reads should have two FASTQs and two read structures
- a dual-index sample with paired end reads should have four FASTQs and four read structures
  given: two for the two index reads, and two for the template reads.

If multiple FASTQs are present for each sub-read, then the FASTQs for each sub-read should be
concatenated together prior to running this tool
(e.g. `zcat s_R1_L001.fq.gz s_R1_L002.fq.gz | bgzip -c > s_R1.fq.gz`).

(Read structures)[<https://github.com/fulcrumgenomics/fgbio/wiki/Read-Structures>] are made up of
`<number><operator>` pairs much like the `CIGAR` string in BAM files.
Five kinds of operators are recognized:

1. `T` identifies a template read
2. `B` identifies a sample barcode read
3. `M` identifies a unique molecular index read
4. `C` identifies a unique cellular barcode read
5. `S` identifies a set of bases that should be skipped or ignored

The last `<number><operator>` pair may be specified using a `+` sign instead of number to
denote "all remaining bases". This is useful if, e.g., fastqs have been trimmed and contain
reads of varying length. Both reads must have template bases.

Metadata about the samples should be given as a headered metadata TSV file with at least the
following two columns present:

1. `sample_id` - the id of the sample or library.
2. `barcode` - the expected barcode sequence associated with the `sample_id`.

For reads containing multiple barcodes (such as dual-indexed reads), all barcodes should be
concatenated together in the order they are read and stored in the `barcode` field.

IUPAC bases are supported in the (expected) `barcode` column.  An observed IUPAC base must be
at least as specific as the corresponding base in the expected sample barcode.  E.g. If the
observed base is an N, it will only match expected sample barcrods with an N.  And if the
observed base is an R, it will match R, V, D, and N, since the latter IUPAC codes allow both
A and G (R/V/D/N are a superset of the bases compare to R).

The read structures will be used to extract the observed sample barcode, template bases,
molecular identifiers, and cellular barcodes from each read.  The observed sample barcode will
be matched to the sample barcodes extracted from the bases in the sample metadata and associated
read structures.

An observed barcode matches an expected barcode if all the following are true:
1. The number of mismatches (edits/substitutions) is less than or equal to the maximum
   mismatches (see `--max-mismatches`).
2. The difference between number of mismatches in the best and second best barcodes is greater
   than or equal to the minimum mismatch delta (`--min-mismatch-delta`).

The expected barcode sequence may contains Ns, which are not counted as mismatches regardless
of the observed base (e.g. the expected barcode `AAN` will have zero mismatches relative to
both the observed barcodes `AAA` and `AAN`).

## Per-sample read structures

In addition to the global `--read-structures`, the metadata TSV may include optional columns
`read_structure_1`, `read_structure_2`, ..., `read_structure_<N>` (where `N` is the number of
input FASTQs).  When any cell is non-empty for a sample, the per-sample read structures
replace the global `--read-structures` for that sample — both for matching and for output
extraction.

Per-sample structures support per-cell fall-back to the global `--read-structures`:

- A blank `read_structure_<i>` cell uses `--read-structures[i-1]` for that sample's input *i*.
- A row whose `read_structure_<n>` cells are all blank uses `--read-structures` entirely for
  that sample (equivalent to omitting the columns for that sample only).
- The unmatched pseudo-sample always uses the global `--read-structures`.

Constraints:

1. The number of `read_structure_<n>` columns must equal `--read-structures.len()`.
2. The concatenated `B`-segment length must equal the `barcode` column length for every
   sample (computed from each sample's *effective* read structures, i.e. with fall-backs
   applied).

Different samples may have different per-input `(T, B, M, C)` segment counts (and hence
produce different sets of output files).  This supports protocols with sample-dependent
read structures (e.g. CODEC, where each sample may include a stagger spacer of varying
length so that the position of the constant ligation base shifts per sample).

During matching, each sample's expected pattern is constructed from its effective read
structure by filling `B`-segment positions from the `barcode` column and treating
`M`/`S`/`C` segment positions as `N` wildcards.  All patterns are padded with trailing `N`s
to a per-input matching window equal to the longest pre-template prefix across samples.

Example metadata TSV (CODEC stagger):

```text
sample_id  barcode         read_structure_1   read_structure_2
S1         GATTACAGATTACA  3M7B1S+T           3M7B1S+T
S2         TTTTTTTTTTTTTT  3M1S7B1S+T         3M1S7B1S+T
```

## Outputs

All outputs are generated in the provided `--output` directory.  For each sample plus the
unmatched reads, FASTQ files are written for each read segment (specified in the read
structures) of one of the types supplied to `--output-types`.  FASTQ files have names
of the format:

```bash
{sample_id}.{segment_type}{read_num}.fq.gz
```

where `segment_type` is one of `R`, `I`, `U`, and `C` (for template, sample barcode/index,
molecular barcode/UMI, and cellular barcode reads, respectively) and `read_num` is a number
starting at 1 for each segment type.

In addition a `demux-metrics.txt` file is written that is a tab-delimited file with counts
of how many reads were assigned to each sample and derived metrics.

## Example Command Line

As an example, if the sequencing run was 2x100bp (paired end) with two 8bp index reads both
reading a sample barcode, as well as an in-line 8bp sample barcode in read one, the command
line would be:

```bash
fqtk demux \
    --inputs r1.fq.gz i1.fq.gz i2.fq.gz r2.fq.gz \
    --read-structures 8B92T 8B 8B 100T \
    --sample-metadata metadata.tsv \
    --output output_folder
```

Usage: fqtk demux [OPTIONS] --inputs <INPUTS>... --read-structures <READ_STRUCTURES>... --sample-metadata <SAMPLE_METADATA> --output <OUTPUT>

Options:
  -i, --inputs <INPUTS>...
          One or more input FASTQ files each corresponding to a sequencing read (e.g. R1, I1)

  -r, --read-structures <READ_STRUCTURES>...
          The read structures, one per input FASTQ in the same order.

          Per-sample read structures (see the `read_structure_<n>` metadata columns) take precedence for each matched sample, and a blank cell falls back to the corresponding `--read-structures` entry.  The unmatched pseudo-sample always uses `--read-structures` for its output extraction.  The number of `read_structure_<n>` columns must equal `--read-structures.len()`.

  -b, --output-types <OUTPUT_TYPES>...
          The read structure types to write to their own files (Must be one of T, B, M, or C for template reads, sample barcode reads, molecular barcode reads, or cellular barcode reads).

          Multiple output types may be specified as a space-delimited list.

          [default: T]

  -s, --sample-metadata <SAMPLE_METADATA>
          A file containing the metadata about the samples

  -o, --output <OUTPUT>
          The output directory into which to write per-sample FASTQs

  -u, --unmatched-prefix <UNMATCHED_PREFIX>
          Output prefix for FASTQ file(s) for reads that cannot be matched to a sample

          [default: unmatched]

      --max-mismatches <MAX_MISMATCHES>
          Maximum mismatches for a barcode to be considered a match

          [default: 1]

  -d, --min-mismatch-delta <MIN_MISMATCH_DELTA>
          Minimum difference between number of mismatches in the best and second best barcodes for a barcode to be considered a match

          [default: 2]

  -t, --threads <THREADS>
          The number of threads to use. Cannot be less than 3

          [default: 8]

  -c, --compression-level <COMPRESSION_LEVEL>
          The level of compression to use to compress outputs

          [default: 5]

  -S, --skip-reasons <SKIP_REASONS>
          Skip demultiplexing reads for any of the following reasons, otherwise panic.

          1. `too-few-bases`: there are too few bases or qualities to extract given the read structures.  For example, if a read is 8bp long but the read structure is `10B`, or if a read is empty and the read structure is `+T`.

      --template-types <TEMPLATE_TYPES>...
          The read structure types to include in the template FASTQ output files.

          By default, only template (T) segments are included. To include additional segment types (e.g. to preserve UMIs in the output read bases), specify them here. For example, `--template-types M T` will concatenate the molecular barcode and template segments.

          To output the full original reads (all segments), specify all segment types present in your read structure (e.g. `--template-types B M T`).

          Segments are only merged *within the same physical read*: a non-`T` segment is folded into the template bases only when it is co-located with a `T` in the same read structure (e.g. `8M84T`). A segment on a separate read (e.g. a UMI on its own index read) is never merged into a template on another read; route it via `--output-types` instead, or leave it in the read header. When a UMI (M) is included here it is written into the template bases and is therefore omitted from the read header (it is not written in both places).

          Note: If `--template-types` includes any non-`T` type, `T` must be included in `--output-types`; each requested non-`T` type must be co-located with a `T` in every read structure where it appears; and each read structure must contain at most one `T` segment.

          [default: T]

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

fqtk shard

fqtk shard splits one or more matched FASTQs (e.g. R1 and R2) into N output shards, assigning reads to shards on a round-robin basis so that each input read ends up in exactly one output FASTQ. This is useful for splitting large FASTQs into evenly sized pieces for parallel downstream processing.

Usage for fqtk shard follows:


Shards a set of FASTQs into N output shards.

Shards a set of matched FASTQs (e.g. R1 and R2) into one or more set of FASTQs where each
input read ends up in exactly one output FASTQ. Reads are assigned to shards on a round-robin
basis, so e.g. if using `--shards 10` the first read in the input files will end up in the
first shard, the second read in the second shard ... and the tenth read in the tenth shard.

Each shard will contain one output FASTQ file per input FASTQ files.  Output files are named
as follows:

```
{output_prefix}.{shard_prefix}{shard_num}.{read_number_prefix}{read_num}.fq.gz
```

where `shard_num` is n for the nth shard (starting at 1), `read_num` corresponds to the nth
file in the `inputs` list (starting at 1), and all other values in `{}` are named command
line parameters.  The `output_prefix` may contain an absolute path, or a relative path, with
relative paths interpreted relative to the working directory where the command is run.

Inputs may be uncompressed, gzipped, or block-gzipped.  Output files are _always_ block gzipped.

Usage: fqtk shard [OPTIONS] --inputs <INPUTS>... --output-prefix <OUTPUT_PREFIX> --shards <SHARDS>

Options:
  -i, --inputs <INPUTS>...
          One or more input FASTQ files each corresponding to a sequencing read (e.g. R1, R2)

  -o, --output-prefix <OUTPUT_PREFIX>
          Output prefix for sharded FASTQ file(s)

  -S, --shard-prefix <SHARD_PREFIX>
          Prefix to place before the shard number in the generated output file names

          [default: s]

  -R, --read-number-prefix <READ_NUMBER_PREFIX>
          Prefix to place before the read number in the generated output file names

          [default: r]

  -s, --shards <SHARDS>
          Number of shards to generate

  -t, --threads <THREADS>
          The number of threads to use for compressing output files.  Minimum 2

          [default: 8]

  -c, --compression-level <COMPRESSION_LEVEL>
          The level of compression to use to compress outputs.  Defaults to 1 because sharded FASTQs are typically short-lived intermediates, where write throughput matters more than squeezing out the last few percent of file size

          [default: 1]

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

fqtk subsample

fqtk subsample reads one or more synchronized FASTQs (e.g. R1 and R2) and writes a randomly chosen subset of the reads, keeping or discarding each read across all files together so paired reads stay in sync.

Usage for fqtk subsample follows:


Subsamples reads from one or more synchronized FASTQ files.

Reads one or more FASTQ files (e.g. paired-end R1 and R2) and writes a
random subset of reads to output files. All input files must contain the
same number of reads in the same order; each read is either kept or
discarded across all files simultaneously.

Output files are named `{output}.R1.fq.gz`, `{output}.R2.fq.gz`, etc.
and are always BGZF compressed.

Each read is independently retained with probability equal to `--fraction`,
giving an approximate subsample without needing to know the total read count
upfront. When no explicit `--seed` is provided, a deterministic seed is
derived from all input parameters, so identical inputs and parameters always
produce identical output.

# Example

```bash
fqtk subsample \
    --input r1.fq.gz r2.fq.gz \
    --output subsampled \
    --fraction 0.1
```

Usage: fqtk subsample [OPTIONS] --inputs <INPUTS>... --output <OUTPUT> --fraction <FRACTION>

Options:
  -i, --inputs <INPUTS>...
          One or more input FASTQ files (may be gzipped). All files must have the same number of reads in the same order

  -o, --output <OUTPUT>
          Output path prefix. Files will be named {output}.R1.fq.gz, etc

  -f, --fraction <FRACTION>
          Fraction of reads to retain, in the range [0.0, 1.0]

  -t, --threads <THREADS>
          Number of threads for compression. Minimum 2

          [default: 8]

  -c, --compression-level <COMPRESSION_LEVEL>
          BGZF compression level for output files

          [default: 5]

  -s, --seed <SEED>
          Explicit RNG seed for reproducibility. When omitted, a deterministic seed is derived from all other parameters

      --disable-read-name-checking
          Disable checking that read names are in sync across input files

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

Installing

Installing with conda

To install with conda you must first install conda. Then, in your command line (and with the environment you wish to install fqtk into active) run:

conda install -c bioconda fqtk

Installing with cargo

To install with cargo you must first install rust. Which (On Mac OS and Linux) can be done with the command:

curl https://sh.rustup.rs -sSf | sh

Then, to install fqtk run:

cargo install fqtk

Building From Source

First, clone the git repo:

git clone https://github.com/fulcrumgenomics/fqtk.git

Secondly, if you do not already have rust development tools installed, install via rustup:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Then build the toolkit in release mode:

cd fqtk
cargo build --release
./target/release/fqtk --help

Developing

fqtk is developed in Rust and follows the conventions of using rustfmt and clippy to ensure both code quality and standardized formatting. When working on fqtk, before pushing any commits, please first run ./ci/check.sh and resolve any issues that are reported. Note that ./ci/check.sh only checks formatting; to auto-fix formatting issues, run cargo fmt --all.

Releasing a New Version

Pre-requisites

Install cargo-release

cargo install cargo-release

Prior to Any Release

Create a release that will not try to push to crates.io and verify the command:

cargo release [major,minor,patch,release,rc...] --no-publish

Note: "dry-run" is the default for cargo release.

See the cargo-release reference documentation for more information

Semantic Versioning

This tool follows Semantic Versioning. In brief:

  • MAJOR version when you make incompatible API changes,
  • MINOR version when you add functionality in a backwards compatible manner, and
  • PATCH version when you make backwards compatible bug fixes.

Major Release

To create a major release:

cargo release major --execute

This will remove any pre-release extension, create a new tag and push it to github, and push the release to creates.io.

Upon success, move the version to the next candidate release.

Finally, make sure to create a new release on GitHub.

Minor and Patch Release

To create a minor (patch) release, follow the Major Release instructions substituting major with minor (patch):

cargo release minor --execute

Release Candidate

To move to the next release candidate:

cargo release rc --no-tag --no-publish --execute

This will create or bump the pre-release version and push the changes to the main branch on github. This will not tag and publish the release candidate. If you would like to tag the release candidate on github, remove --no-tag to create a new tag and push it to github.