Skip to content

Commit feb733f

Browse files
authored
Merge pull request #156 from ArcInstitute/integrate-cbq
Integrate cbq
2 parents 07da3fd + a086425 commit feb733f

15 files changed

Lines changed: 507 additions & 578 deletions

File tree

Cargo.toml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bqtools"
3-
version = "0.4.15"
3+
version = "0.5.0"
44
edition = "2021"
55
license = "MIT"
66
authors = ["Noam Teyssier <noam.teyssier@arcinstitute.org>"]
@@ -12,20 +12,20 @@ keywords = ["bioinformatics", "binseq", "fastx", "file-format", "sequencing"]
1212
[dependencies]
1313
aho-corasick = "1.1.4"
1414
anyhow = "1.0.100"
15-
binseq = "0.8.3"
16-
clap = { version = "4.5.53", features = ["derive"] }
15+
binseq = "0.9.0"
16+
clap = { version = "4.5.54", features = ["derive"] }
1717
csv = "1.4.0"
1818
env_logger = "0.11.8"
1919
fixedbitset = "0.5.7"
2020
gzp = "2.0.2"
2121
is-terminal = "0.4.17"
22-
libc = "0.2.178"
22+
libc = "0.2.180"
2323
log = "0.4.29"
2424
memmap2 = "0.9.9"
2525
niffler = "3.0.0"
26-
nix = { version = "0.30.1", features = ["fs"] }
26+
nix = { version = "0.31.1", features = ["fs"] }
2727
num_cpus = "1.17.0"
28-
paraseq = "0.4.7"
28+
paraseq = "0.4.8"
2929
parking_lot = "0.12.5"
3030
rand = "0.9.2"
3131
regex = "1.12.2"
@@ -35,10 +35,10 @@ walkdir = "2.5.0"
3535
zstd = { version = "0.13.3", features = ["zstdmt"] }
3636

3737
[dev-dependencies]
38-
bon = "3.8.1"
38+
bon = "3.8.2"
3939
itertools = "0.14.0"
4040
nucgen = "0.2.0"
41-
tempfile = "3.23.0"
41+
tempfile = "3.24.0"
4242

4343
[features]
4444
default = ["htslib", "gcs"]
@@ -47,6 +47,7 @@ htslib = ["paraseq/htslib"]
4747
gcs = ["paraseq/gcs"]
4848

4949
[profile.release]
50+
# debug = true
5051
lto = true
5152
codegen-units = 1
5253

README.md

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,40 @@ A command-line utility for working with BINSEQ files.
88
## Overview
99

1010
bqtools provides tools to encode, decode, manipulate, and analyze [BINSEQ](https://github.com/arcinstitute/binseq) files.
11-
It supports both (`*.bq`) and (`*.vbq`) files and makes use of the [`binseq`](https://crates.io/crates/binseq) library.
11+
It supports all BINSEQ variants (`*.bq`, `*.cbq`, `*.vbq`) and makes use of the [`binseq`](https://crates.io/crates/binseq) library.
1212

1313
BINSEQ is a binary file format family designed for high-performance processing of DNA sequences.
1414
It currently has two variants: BQ and VBQ.
1515

16-
- **BQ (\*.bq)**: Optimized for _fixed-length_ DNA sequences **without** quality scores.
17-
- **VBQ (\*.vbq)**: Optimized for _variable-length_ DNA sequences **with optional** quality scores.
16+
- **BQ (\*.bq)**: Optimized for _fixed-length_ DNA sequences **without** quality scores (2bit/4bit).
17+
- **VBQ (\*.vbq)**: Optimized for _variable-length_ DNA sequences **with optional** quality scores, headers with 2bit/4bit.
18+
- **CBQ (\*.cbq)**: Optimized for _variable-length_ DNA sequences **with optional** quality scores, headers with 2bit + N.
1819

19-
Both support single and paired sequences and make use of two-bit or four-bit encoding for efficient nucleotide packing using [`bitnuc`](https://crates.io/crates/bitnuc) and efficient parallel FASTX processing using [`paraseq`](https://crates.io/crates/paraseq).
20+
All support single and paired sequences and make use of two-bit or four-bit encoding for efficient nucleotide packing using [`bitnuc`](https://crates.io/crates/bitnuc) and efficient parallel FASTX processing using [`paraseq`](https://crates.io/crates/paraseq).
2021

2122
For more information about BINSEQ, see our [preprint](https://www.biorxiv.org/content/10.1101/2025.04.08.647863v1) where we describe the format family and its applications.
2223

24+
### Description of variants
25+
26+
> TL;DR: `*.cbq` is the recommended format for most applications.
27+
28+
For most applications the BINSEQ variant of choice is `*.cbq`.
29+
This format is lossless by default and supports variable-length sequences.
30+
It achieves better compression than `*.vbq` and `*.bq` by using blocked-columnar compression of sequence attributes.
31+
It can optionally exclude quality scores and headers (but they are included by default).
32+
For an overview of the format check out the [BINSEQ docs](https://docs.rs/binseq/latest/binseq/cbq/index.html).
33+
34+
If your application _only requires sequences_ and has _fixed-length_ reads then `*.bq` is the best choice.
35+
It is the _fastest_ variant but _is lossy_ by design.
36+
37+
> Note: `*.vbq` was originally designed for variable-length sequences with quality scores and headers, but it is now deprecated in favor of `*.cbq` which is more compressable, lossless, and has faster decoding.
38+
2339
## Features
2440

2541
- **Encode**: Convert FASTA or FASTQ files to a BINSEQ format
2642
- **Decode**: Convert a BINSEQ file back to FASTA, FASTQ, or TSV format
2743
- **Cat**: Concatenate multiple BINSEQ files
28-
- **Count**: Count records in a BINSEQ file
44+
- **Info**: Show information and statistics about a BINSEQ file.
2945
- **Grep**: Search for fixed-string, regex, or fuzzy matches in BINSEQ files.
3046
- **Pipe**: Create named-pipes for efficient data processing with legacy tools that don't support BINSEQ.
3147

@@ -96,7 +112,9 @@ bqtools --help
96112
bqtools encode --help
97113
bqtools decode --help
98114
bqtools cat --help
99-
bqtools count --help
115+
bqtools info --help
116+
bqtools grep --help
117+
bqtools pipe --help
100118
```
101119

102120
### Encoding
@@ -236,12 +254,18 @@ Combine multiple BINSEQ files:
236254
bqtools cat file1.bq file2.bq file3.bq -o combined.bq
237255
```
238256

239-
### Counting
257+
### Information and Statistics
240258

241-
Count records in a BINSEQ file:
259+
Show information and statistics about a BINSEQ file.
242260

243261
```bash
244-
bqtools count input.bq
262+
bqtools info input.cbq
263+
264+
# print out the VBQ index
265+
bqtools info input.vbq --show-index
266+
267+
# print out the CBQ block headers
268+
bqtools info input.cbq --show-headers
245269
```
246270

247271
### Grep
@@ -382,4 +406,4 @@ ls fifo_*.fq | xargs -P 4 -I {} sh -c 'legacy-tool {} > {.}.out'
382406
- Auto-scales to CPU count with `-p0` (default)
383407
- Pipes can be read sequentially _or_ in parallel without blocking.
384408

385-
Note: This feature is not available on Windows.
409+
> Note: This feature is not available on Windows.

src/cli/cli.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use clap::{
88

99
use crate::cli::PipeCommand;
1010

11-
use super::{CatCommand, CountCommand, DecodeCommand, EncodeCommand, GrepCommand, SampleCommand};
11+
use super::{CatCommand, DecodeCommand, EncodeCommand, GrepCommand, InfoCommand, SampleCommand};
1212

1313
// Configures Clap v3-style help menu colors
1414
const STYLES: Styles = Styles::styled()
@@ -33,7 +33,7 @@ pub enum Commands {
3333

3434
Cat(CatCommand),
3535

36-
Count(CountCommand),
36+
Info(InfoCommand),
3737

3838
Grep(GrepCommand),
3939

src/cli/encode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ impl EncodeCommand {
1919
if let Some(mode) = self.output.mode {
2020
Ok(mode)
2121
} else if self.input.recursive {
22-
Ok(BinseqMode::VBinseq)
22+
Ok(BinseqMode::default())
2323
} else {
2424
self.output.mode()
2525
}

src/cli/count.rs renamed to src/cli/info.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,27 @@ use clap::Parser;
33
use super::InputBinseq;
44

55
#[derive(Parser, Debug)]
6-
/// Count the number of records in a BINSEQ file.
7-
pub struct CountCommand {
6+
/// Show information about a BINSEQ file.
7+
pub struct InfoCommand {
88
#[clap(flatten)]
99
pub input: InputBinseq,
1010

1111
#[clap(flatten)]
12-
pub opts: CountOpts,
12+
pub opts: InfoOpts,
1313
}
1414

1515
#[derive(Parser, Debug)]
16-
#[clap(next_help_heading = "COUNT OPTIONS")]
17-
pub struct CountOpts {
16+
#[clap(next_help_heading = "INFO OPTIONS")]
17+
pub struct InfoOpts {
1818
/// Only print the number of records in the file
1919
#[clap(short, long)]
2020
pub num: bool,
2121

2222
/// Print the index of the file
2323
#[clap(long)]
2424
pub show_index: bool,
25+
26+
/// Print the block headers of the file
27+
#[clap(long, conflicts_with = "show_index")]
28+
pub show_headers: bool,
2529
}

src/cli/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
mod cat;
22
mod cli;
3-
mod count;
43
mod decode;
54
mod encode;
65
mod formats;
76
mod grep;
7+
mod info;
88
mod input;
99
mod output;
1010
mod pipe;
1111
mod sample;
1212

1313
pub use cat::CatCommand;
1414
pub use cli::{Cli, Commands};
15-
pub use count::CountCommand;
1615
pub use decode::DecodeCommand;
1716
pub use encode::EncodeCommand;
1817
pub use formats::FileFormat;
1918
pub use grep::GrepCommand;
19+
pub use info::InfoCommand;
2020
pub use input::{InputBinseq, InputFile, MultiInputBinseq};
2121
pub use output::{BinseqMode, Mate, OutputBinseq, OutputFile};
2222
pub use pipe::PipeCommand;

src/cli/output.rs

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -150,16 +150,22 @@ pub struct OutputBinseq {
150150
pub mode: Option<BinseqMode>,
151151

152152
/// Policy for handling Ns in sequences
153+
///
154+
/// Used by bq+vbq
153155
#[clap(short = 'p', long, default_value = "r")]
154156
pub policy: PolicyWrapper,
155157

156158
/// Encoding bitsize (2 or 4 bits per nucleotide)
159+
///
160+
/// Used by bq+vbq
157161
#[clap(short = 'S', long, default_value = "2")]
158162
bitsize: u8,
159163

160-
/// Include sequence names (headers) in the vbq file
164+
/// Exclude sequence names (headers) in the binseq file
165+
///
166+
/// Used by vbq+cbq
161167
#[clap(short = 'H', long)]
162-
pub headers: bool,
168+
skip_headers: bool,
163169

164170
/// Skip ZSTD compression of VBQ blocks (default: compressed)
165171
///
@@ -169,13 +175,13 @@ pub struct OutputBinseq {
169175

170176
/// Skip inclusion of quality scores (default: included)
171177
///
172-
/// Only used by vbq.
178+
/// Used by vbq+cbq
173179
#[clap(short = 'Q', long)]
174180
pub skip_quality: bool,
175181

176-
/// VBQ virtual block size (in bytes)
182+
/// Virtual block size (in bytes)
177183
///
178-
/// Only used by vbq
184+
/// Used by vbq+cbq
179185
#[clap(short = 'B', long, value_parser = parse_memory_size, default_value = "128K")]
180186
block_size: usize,
181187

@@ -189,6 +195,8 @@ pub struct OutputBinseq {
189195
/// The compression level is between 1 and 22, with 3 being the default.
190196
/// Higher levels provide better compression at the cost of speed.
191197
/// Level 0 disables compression.
198+
///
199+
/// Used by vbq+cbq
192200
#[clap(short, long, default_value = "3")]
193201
pub level: i32,
194202

@@ -225,6 +233,14 @@ impl OutputBinseq {
225233
}
226234
}
227235

236+
pub fn headers(&self) -> bool {
237+
if self.archive {
238+
true
239+
} else {
240+
!self.skip_headers
241+
}
242+
}
243+
228244
pub fn block_size(&self) -> usize {
229245
if self.archive {
230246
200 * 1024 * 1024
@@ -319,18 +335,21 @@ impl From<PolicyWrapper> for Policy {
319335
#[derive(Debug, Clone, Copy, ValueEnum, Default, PartialEq)]
320336
pub enum BinseqMode {
321337
#[clap(name = "bq")]
322-
Binseq,
338+
Bq,
323339
#[clap(name = "vbq")]
340+
Vbq,
341+
#[clap(name = "cbq")]
324342
#[default]
325-
VBinseq,
343+
Cbq,
326344
}
327345
impl BinseqMode {
328346
pub fn determine(path: &str) -> Result<Self> {
329347
let pathbuf = Path::new(path);
330348
if let Some(ext) = pathbuf.extension() {
331349
match ext.to_str() {
332-
Some("bq") => Ok(Self::Binseq),
333-
Some("vbq") => Ok(Self::VBinseq),
350+
Some("bq") => Ok(Self::Bq),
351+
Some("vbq") => Ok(Self::Vbq),
352+
Some("cbq") => Ok(Self::Cbq),
334353
_ => bail!("Could not determine BINSEQ output mode from path: {path}"),
335354
}
336355
} else {
@@ -339,8 +358,18 @@ impl BinseqMode {
339358
}
340359
pub fn extension(&self) -> &str {
341360
match self {
342-
Self::Binseq => ".bq",
343-
Self::VBinseq => ".vbq",
361+
Self::Bq => ".bq",
362+
Self::Vbq => ".vbq",
363+
Self::Cbq => ".cbq",
364+
}
365+
}
366+
}
367+
impl Into<binseq::write::Format> for BinseqMode {
368+
fn into(self) -> binseq::write::Format {
369+
match self {
370+
Self::Bq => binseq::write::Format::Bq,
371+
Self::Vbq => binseq::write::Format::Vbq,
372+
Self::Cbq => binseq::write::Format::Cbq,
344373
}
345374
}
346375
}

0 commit comments

Comments
 (0)