Skip to content

fqxv compress

Compress one or more FASTQ files into a single .fqxv archive.

fqxv compress demo

Usage

fqxv compress <INPUTS>... [-o <OUTPUT>] [OPTIONS]

Give one input for single-end, two for paired-end, or three/four for single-cell (R1/R2/I1[/I2]). Multiple inputs are interleaved per spot into one archive and can be split back out with decompress --split. Input order is preserved for the split.

Arguments

Argument Description
<INPUTS>... Input FASTQ file(s), plain or gzipped (gzip auto-detected).

Options

Option Description
-o, --output <PATH> Output .fqxv path. Defaults to the first input's name with the FASTQ/gzip extension replaced by .fqxv (reads.fastq.gzreads.fqxv), alongside the input. Required when reading from stdin (-).
-f, --force Overwrite the output archive if it already exists. By default compress refuses to clobber an existing file and errors before doing any work.
--verify After writing the archive, re-read and fully decode it to confirm it round-trips before reporting success — recommended before deleting the source FASTQ. See Verifying on write.
--max Maximum-compression preset: deepest sequence context plus read reordering where it helps (applied to short reads, auto-skipped for long reads). Overrides --level/--order.
--estimate Predict the archive size and compression ratio from a sample of the input, then exit without writing anything. See Estimating compression.
--threads <N> Worker threads (0 = all cores).

Advanced options

Option Description
-l, --level <N> Effort 1–9; higher raises the sequence context order (up to order 11, reached at level 5) and the block size, and enables a hashed high-order tier at level 8+. Default: 5.
--block-reads <N> Reads per row group, overriding the size --level would pick. Decouples random-access granularity from effort: smaller groups give finer remote/parallel access and more parallelism at some ratio cost; larger groups the reverse. Sequence order still follows --level. Ignored by the reorder path (--order any/--max). See Row-group sizing.
--order <MODE> Read-order guarantee: preserve (default, restores original order), any (allows reordering for a better ratio; single-end order may change), or shuffle (like any, but discards order and regenerates purely positional names — reorder-lossy, single-end only).
--interleaved <N> Interleaving of a single input, in members per spot (1 = single-end, 2 = paired as from sracha get -Z). Auto-detected from read names by default. Ignored with multiple inputs.
--keep-order With --order any, force original read order to be restored (store a permutation, code names/quality in original order). Chosen automatically when it makes the archive smaller.
--no-rescue With --order any, disable the adaptive assembly codecs (block-local literal-rescue and the whole-file global reference) and use the faster single-contig sequence codec only.
--quality-bin <MODE> lossless (default), or a lossy table: bin8, bin4, bin2 (Illumina), ont, hifi (long read). See Lossy quality binning.
--platform <NAME> Sequencing platform to record: illumina, nanopore, pacbio, mgi. Auto-detected from read names by default; pass to override.

Examples

# single-end, gzipped input (-o defaults to reads.fqxv)
fqxv compress reads.fastq.gz

# paired-end at higher effort
fqxv compress sample_R1.fq.gz sample_R2.fq.gz -o sample.fqxv --level 7 --threads 16

# 10x single-cell (R1 + R2 + I1 + I2)
fqxv compress R1.fq R2.fq I1.fq I2.fq -o sample.fqxv

# lossy quality (Illumina 8-level binning)
fqxv compress reads.fastq -o reads.fqxv --quality-bin bin8

# lossy quality on long reads (match the table to the platform)
fqxv compress ont_reads.fastq -o ont_reads.fqxv --quality-bin ont
fqxv compress hifi_reads.fastq -o hifi_reads.fqxv --quality-bin hifi

# finer row groups for remote/parallel access (smaller range fetches)
fqxv compress reads.fastq.gz -o reads.fqxv --block-reads 65536

# maximum compression (deepest context + read reordering where it helps)
fqxv compress reads.fastq.gz --max

# verify the archive round-trips before trusting (or deleting) the source
fqxv compress reads.fastq.gz --verify

# estimate the ratio and archive size from a sample, writing nothing
fqxv compress reads.fastq.gz --estimate

Verifying on write

--verify closes the window between writing an archive and trusting it. After the archive is written, it is reopened and fully decoded — exercising every block CRC-32C and content digest — and the decoded read count is checked against what was written, all before compress reports success:

fqxv compress reads.fastq.gz --verify

This catches a codec or in-flight memory error that produced a CRC-valid but wrong archive, which the archive's own on-disk checksums cannot detect on their own. It is the check to run before deleting the source FASTQ.

  • On any failure — a decode error or a read-count mismatch — the archive is left in place for inspection and the command exits non-zero. A bad archive is never reported as done.
  • It adds a full decode pass, so expect roughly double the wall time (the --max / reorder path is the heaviest).
  • It validates the bytes the encoder emitted, not long-term on-disk durability; for the latter, re-run fqxv verify against the stored file later.

Estimating compression

--estimate tells you how well an input will compress before committing to a full run. It codes a bounded sample of the leading reads (about one block, up to ~1M reads) with the real codecs at the chosen --level/--quality-bin, then projects the whole-file archive and prints the result — no archive is written:

reads.fastq.gz (436.93 MB)  →  estimated fqxv ~216.87 MB  (50% smaller, ~2.01x)

Estimated from a 1,048,576-read sample (1.31 GB uncompressed FASTQ):
  stream      compressed     share   rate
  names         6.42 KB     0.0%   0.05 bits/read
  sequence     17.11 MB    43.8%   1.355 bits/base
  quality      21.97 MB    56.2%   1.740 bits/base
  vs uncompressed FASTQ (~1.31 GB):  ~6.19x

The top line leads with the outcome: input size(s) → estimated archive size, as a percent reduction and ratio against the input on disk. The per-stream table below shows where the bytes go (names / sequence / quality), and the final line gives the ratio against the uncompressed FASTQ. Multiple inputs are listed individually under a combined headline; a streaming stdin input has no on-disk size, so it reports the sample's own reduction and omits the whole-file projection (pass a file to get one).

Accuracy. Because blocks are coded independently and the models are per-block stationary, the sample's ratio is a faithful proxy for the whole file — in practice within ~1–2% of a real run (every projected figure is marked ~). The projection scales the sample by the fraction of on-disk bytes it consumed, so it works for plain and gzipped inputs alike.

Reordering is a lower bound. --order any / --max reordering is not modeled — its cross-read redundancy grows with read count, so a small sample can't capture it. With those flags the estimate is a conservative lower bound (the real archive comes out that size or smaller), and the report says so.

Row-group sizing

The archive is a run of independently-coded row groups (blocks); the row group is the unit of parallelism, of coarse random access, and of remote column projection. By default its size comes from --level — higher effort uses larger groups, which train the sequence model on more reads for a better ratio. Pass --block-reads <N> to set it directly and decouple granularity from effort.

The trade-off is real. Smaller groups mean finer random access and more parallelism, but a worse ratio (the order-k sequence model has fewer reads to train on per group) and a slightly larger footer index. Larger groups compress better but make the smallest independently-fetchable unit coarser. When archiving to object storage where clients issue small Range reads — say, fetching just the read names, or just one row group — a smaller --block-reads makes those fetches cheaper; for a write-once/read-sequentially archive the --level default is the right call.

The per-group and per-stream byte offsets recorded in the footer are what make this projection possible without reading whole blocks — see Container format → Column projection. The reorder path (--order any/--max) clusters globally and does not use this sizing, so --block-reads is ignored there.

Lossy quality binning

Quality is lossless by default. --quality-bin maps each quality byte through a fixed table before coding — an explicit, opt-in lossy transform that shrinks the quality stream (usually the largest part of the archive). Sequence and read names are never touched, and a binned read still aligns exactly where the original did.

Mode Levels Calibrated for
lossless all default; nothing is discarded
bin8 8 Illumina standard binning
bin4 4 Illumina documented 4-level (NovaSeq X / RTA4)
bin2 2 custom, most aggressive
ont 4 Oxford Nanopore (CoLoRd ONT cutpoints)
hifi 5 PacBio HiFi (CoLoRd HiFi cutpoints; Q93 kept exact)

Match the table to the platform. The tables are not interchangeable: the Illumina bins are absolute-Phred cutpoints that collapse HiFi's narrow high-Q band into a single level, and ont applied to HiFi data folds the Q93 max-quality symbol into the 26+ bin, destroying its application meaning (measured mean |Δ| 42.84, 99.4% of bases changed). hifi keeps Q93 as its own level. On ONT data the ont and hifi tables are byte-identical, since ONT never reaches Q93.

On the ecoli_ont benchmark, --quality-bin ont cuts the quality stream from 163.7 MB to 47.0 MB (3.5×) — the whole archive from 3.01× to 7.17× — at mean |Δ| 3.35. Cutpoints should ultimately be judged by downstream fidelity, not raw ratio — see Long-read support.

Notes

  • Losslessness: read name + description, sequence, and quality are preserved exactly. The + separator line is normalized to a bare +.
  • Grouping: interleaving mates shrinks the archive — near-identical mate names collapse to matches, and a spot's related reads sit together for the sequence model.