slow5lib 0.1.0

Rust re-implementation of slow5lib: read and write SLOW5/BLOW5 nanopore sequencing files
Documentation

slow5lib

Crates.io docs.rs CI License: MIT MSRV: 1.87

This is an independent Rust re-implementation of hasindu2008/slow5lib. It is not a port, not a wrapper, and shares no code with the original C library. The C implementation is the authoritative specification for wire format and behaviour; this crate is designed to be wire-compatible with it.

Read and write SLOW5 (text) and BLOW5 (binary) files containing Oxford Nanopore Technologies sequencing signal data. The library exposes sequential and indexed access patterns with optional parallel decompression via rayon.

API reference

What are SLOW5 and BLOW5?

SLOW5 and BLOW5 are file formats for storing raw Oxford Nanopore sequencing signal data, developed by Hasindu Gamaarachchi as a simpler, more performant alternative to FAST5/POD5.

  • SLOW5 is a tab-separated text format, human-readable and easy to process with standard tools.
  • BLOW5 is the binary equivalent: same information, significantly smaller files and faster I/O. Records can be block-compressed with zstd or zlib, and the raw signal is compressed with SVB-ZD (StreamVByte + delta + zigzag) or ex-zd (quantize-trailing-shift + zigzag-delta + patched exceptions).

Both formats store one record per sequencing read, with primary fields (read_id, raw_signal, calibration parameters) and optional auxiliary fields for per-read metadata.

Installation

[dependencies]
slow5lib = "0.1"

# optional: parallel decompression via rayon
slow5lib = { version = "0.1", features = ["rayon"] }

Quick start

Reading a SLOW5 file

use slow5lib::{Slow5Reader, Result};

fn main() -> Result<()> {
    let mut reader = Slow5Reader::open("reads.slow5")?;
    for record in reader.records() {
        let rec = record?;
        println!("{}: {} samples", rec.read_id, rec.raw_signal.len());
    }
    Ok(())
}

Reading a BLOW5 file

use slow5lib::{Slow5Reader, Result};

fn main() -> Result<()> {
    let mut reader = Slow5Reader::open("reads.blow5")?;
    for record in reader.records() {
        let rec = record?;
        // calibrated picoamperes: (raw + offset) * range / digitisation
        let pa: Vec<f32> = rec.raw_signal.iter()
            .map(|&s| (s as f64 + rec.offset) * rec.range / rec.digitisation)
            .map(|v| v as f32)
            .collect();
        println!("{}: {} samples, first pA = {:.2}", rec.read_id, pa.len(), pa[0]);
    }
    Ok(())
}

Parallel decompression

For high-throughput pipelines, disk I/O stays on one thread while decompression fans out across the rayon pool:

use slow5lib::{Slow5Reader, Result};
use rayon::prelude::*;

fn main() -> Result<()> {
    let mut reader = Slow5Reader::open("reads.blow5")?;
    reader.records_raw()
        .par_bridge()
        .map(|r| r?.decompress())
        .for_each(|rec| {
            let rec = rec.unwrap();
            println!("{}: {} samples", rec.read_id, rec.raw_signal.len());
        });
    Ok(())
}

Or use the convenience wrapper with the rayon feature:

# #[cfg(feature = "rayon")]
use rayon::prelude::*;

# #[cfg(feature = "rayon")]
reader.par_records().for_each(|rec| { /* ... */ });

Indexed random access

use slow5lib::{Slow5IndexedReader, Result};

fn main() -> Result<()> {
    // Opens the .blow5.idx index (builds it if absent)
    let reader = Slow5IndexedReader::open("reads.blow5")?;

    // Thread-safe: multiple threads can call get() concurrently
    let rec = reader.get("50abece6-9476-46be-a86f-d48cc3fddeb6")?;
    println!("{} samples", rec.raw_signal.len());
    Ok(())
}

Writing a BLOW5 file

use slow5lib::{Slow5Writer, Record, Result};
use slow5lib::header::{Header, RecordCompression, SignalCompression, ReadGroup};
use slow5lib::aux::AuxMeta;

fn main() -> Result<()> {
    let header = Header {
        version: (0, 2, 0),
        num_read_groups: 1,
        record_compression: RecordCompression::Zstd,
        signal_compression: SignalCompression::SvbZd,
        read_groups: vec![ReadGroup::default()],
        aux_meta: AuxMeta::default(),
    };

    let mut writer = Slow5Writer::create("out.blow5", header)?;

    let record = Record {
        read_id: "my-read-001".into(),
        read_group: 0,
        digitisation: 2048.0,
        offset: -224.0,
        range: 1463.0,
        sampling_rate: 4000.0,
        raw_signal: vec![512i16, 515, 510, 518, 511],
        aux: Default::default(),
    };

    writer.write(&record)?;
    writer.finish()?;
    Ok(())
}

Compression

Record compression Signal compression Notes
None None Uncompressed; byte-identical to C library
None SVB-ZD StreamVByte + delta + zigzag; byte-identical to C library
None ex-zd Quantize-shift + zigzag-delta + patched exceptions; byte-identical to C library
Zstd SVB-ZD Recommended default; smallest files, fastest random access
Zstd ex-zd Alternative signal codec; compression relative to SVB-ZD depends on the data
Zstd None
Zlib SVB-ZD Compatible with tools that do not support Zstd
Zlib ex-zd
Zlib None

SVB-ZD and ex-zd signal compression use the svb crate with SIMD-accelerated encode/decode (AVX2, SSSE3, NEON).

Design notes

No mmap. The library uses BufReader<File> for sequential reads and FileExt::read_at (pread) for indexed random access. Memory-mapped I/O is deliberately avoided -- it is the main source of poor memory behaviour in C and Python implementations of similar formats.

Two reader types reflect two access patterns. Slow5Reader is a sequential cursor (not Sync). Slow5IndexedReader holds a File for read_at and is Send + Sync -- multiple threads can call get() concurrently with no locking.

Decompression is separated from I/O. records_raw() yields RawRecord values (compressed bytes, no CPU work). RawRecord::decompress() pays the CPU cost. Splitting the two lets rayon's par_bridge() decompress in parallel while disk reads stay sequential.

Known limitations

  • Windows is not in CI. The indexed reader uses Unix read_at (FileExt::read_at); a Windows equivalent (seek_read) has not been wired in. WSL and Git Bash both expose Unix read_at and are unaffected.
  • Parity testing covers one dataset. Byte-identical and value-level parity is validated against a single author-supplied BLOW5 file and against slow5tools committed fixtures; it has not yet been validated across multiple organisms, sequencing platforms, or library preps.

Validation

Wire-compatibility is validated two ways:

  • tests/parity.rs compares Rust writer output against reference files committed to the repo, generated with slow5tools v1.3.0 (v1.4.0 for the ex-zd fixtures, since ex-zd support was added upstream after v1.3.0).
  • tests/c_parity.rs runs live against whatever slow5tools build is on PATH at test time (skips automatically if absent).

If you re-validate against a different upstream version and find a wire-format or behavioural difference, please open an issue -- see CONTRIBUTING.md for the policy on re-validating after upstream releases.

Acknowledgements

SLOW5 and BLOW5 were designed by Hasindu Gamaarachchi and contributors. The original C library (hasindu2008/slow5lib) is the reference implementation and specification for the format. This crate is an independent re-implementation; the C library's behaviour and wire format are authoritative wherever they differ from any written specification.

The original slow5lib is released under the MIT licence. A copy is included in LICENSE-slow5lib.

If you use SLOW5/BLOW5 in your research, please cite:

Gamaarachchi, H., Samarakoon, H., Jenner, S.P. et al. Fast nanopore sequencing data analysis with SLOW5. Nat Biotechnol 40, 1026-1029 (2022). https://doi.org/10.1038/s41587-021-01147-4

Samarakoon, H., Ferguson, J.M., Jenner, S.P. et al. Flexible and efficient handling of nanopore sequencing signal data with slow5tools. Genome Biol 24, 69 (2023). https://doi.org/10.1186/s13059-023-02910-3

AI assistance

This library was developed with AI assistance (Claude). Architecture decisions, wire-compatibility validation, and algorithm choices are the author's own; AI tooling served as an accelerator over existing skill. See CONTRIBUTING.md for details.

MSRV

1.87 (edition 2024).

License

MIT. See LICENSE. Copyright 2026 James Ferguson.

The SLOW5/BLOW5 format and original C implementation are copyright Hasindu Gamaarachchi and contributors, MIT licence. See LICENSE-slow5lib.