BamNado
High-performance tools and utilities for working with BAM and BigWig files in modern genomics workflows. BamNado is written in Rust for speed and low memory use and provides both a command-line interface and Python bindings.
Overview
BamNado is designed for efficient, streaming manipulation of BAM files and signal tracks. It focuses on fast coverage generation, flexible filtering, and lightweight post-processing of bedGraph and BigWig data.
Common use cases:
- Rapid generation of coverage tracks from large BAM files
- Filtering reads by tags or barcodes to produce targeted BigWigs
- Fragment-aware coverage for ATAC-seq and related assays
- BigWig comparison and aggregation across samples
- Post-processing of binned signal tracks for visualization
Useful in workflows including single-cell and Micro-Capture-C (MCC), and many others.
Documentation: API docs on docs.rs
Features
- High-performance, streaming implementations in Rust
- Cross-platform support (Linux, macOS, Windows)
- BAM → bedGraph / BigWig coverage generation
- Fragment-aware and strand-specific pileups
- Read filtering by mapping quality, length, strand, fragment size, tags, and barcodes
- BigWig comparison (subtraction, ratio, log-ratio)
- BigWig aggregation (sum, mean, median, min, max)
- bedGraph post-processing with
collapse-bedgraph - Python bindings for selected functionality
Installation
Pre-built binaries (recommended)
Download the appropriate binary from the releases page.
After downloading, move to your user bin directory (no root required):
chmod +x bamnado
mv bamnado ~/.local/bin/bamnado
Ensure ~/.local/bin is on your PATH (add to ~/.bashrc or ~/.zshrc if needed):
export PATH="$HOME/.local/bin:$PATH"
Alternatively, you can run the binary directly from the download location:
./bamnado --help
cargo binstall
If you have cargo-binstall installed, this downloads the pre-built binary automatically:
cargo binstall bamnado
Docker
docker pull ghcr.io/alsmith151/bamnado:latest
docker run --rm ghcr.io/alsmith151/bamnado:latest --help
Images are available for linux/amd64 and linux/arm64.
Cargo
If you have Rust installed:
cargo install bamnado
Build from source
git clone https://github.com/alsmith151/BamNado.git
cd BamNado
cargo build --release
Optional dependency: samtools
samtools is not required but is strongly recommended if your BAM files have non-standard or incomplete headers (e.g. files produced by CellRanger). BamNado automatically falls back to samtools view -H to parse the header when the built-in parser fails. Without samtools on your PATH, BamNado will error on such files.
Install via conda or your system package manager:
conda install -c bioconda samtools
# or
brew install samtools
Command-line interface
Getting help
bamnado --help # List all commands
bamnado <command> --help # Help for a specific command
Available commands
| Command | Aliases | Description |
|---|---|---|
bam-coverage |
coverage |
BAM → bedGraph / BigWig coverage |
multi-bam-coverage |
multi-coverage |
Merge coverage from multiple BAM files into one track |
split |
— | Split BAM by tag or barcode |
split-exogenous |
split-spikein |
Split BAM into endogenous / exogenous reads |
modify |
— | Filter and/or adjust reads in a BAM file |
bigwig-compare |
compare-bigwigs |
Compare two BigWig files bin by bin |
bigwig-aggregate |
aggregate-bigwigs |
Aggregate multiple BigWig files into one track |
bigwig-infer-scale |
infer-scale |
Infer scaling factor and library size from a normalised BigWig |
bam-normalize |
norm-factors |
Compute between-sample scaling factors from BAM files |
collapse-bedgraph |
collapse |
Collapse adjacent equal-score bins in a bedGraph |
Read filtering
All coverage commands share common read filter flags:
| Flag | Default | Description |
|---|---|---|
--strand |
both |
forward, reverse, or both |
--proper-pairs |
off | Keep only properly-paired reads |
--min-mapq |
20 | Minimum mapping quality |
--min-length |
20 | Minimum read length (bp) |
--max-length |
1000 | Maximum read length (bp) |
--min-fragment-len |
— | Minimum insert size (bp); paired-end only |
--max-fragment-len |
— | Maximum insert size (bp); paired-end only |
--blacklist |
— | BED file of regions to exclude |
--barcode-allowlist |
— | Text file of cell barcodes (one per line) |
--read-group |
— | Keep only this read group |
--tag / --tag-value |
— | Keep reads where TAG == VALUE |
--ignore-duplicates |
off | Exclude PCR/optical duplicate reads |
--ignore-secondary |
off | Exclude secondary alignments |
--ignore-supplementary |
off | Exclude supplementary alignments |
Coverage-specific flags
| Flag | Default | Description |
|---|---|---|
--normalize |
raw |
Signal normalization method: raw, rpkm, or cpm |
--fragment-counts |
off | Count fragments instead of individual read alignments |
--ignore-scaffolds |
off | Skip scaffold or unplaced chromosomes |
--threads |
6 |
Threads used when writing BigWig output |
Examples
Generate basic coverage
bamnado bam-coverage \
--bam sample.bam \
--output coverage.bedgraph \
--bin-size 100
Generate high-quality, normalized coverage
bamnado bam-coverage \
--bam sample.bam \
--output coverage_hq.bw \
--bin-size 100 \
--normalize rpkm \
--min-mapq 30 \
--proper-pairs
Extract nucleosome-free regions from ATAC-seq
bamnado bam-coverage \
--bam atac.bam \
--output nfr_forward.bw \
--bin-size 10 \
--strand forward \
--min-fragment-len 100 \
--max-fragment-len 200 \
--fragment-counts \
--min-mapq 20
Filter by cell barcode (single-cell)
bamnado bam-coverage \
--bam possorted_genome_bam.bam \
--output cell_coverage.bw \
--bin-size 100 \
--barcode-allowlist barcodes.txt \
--fragment-counts
Filter by SAM tag (MCC viewpoint)
bamnado bam-coverage \
--bam mcc.bam \
--output BCL2_viewpoint.bw \
--bin-size 50 \
--tag VP \
--tag-value BCL2 \
--fragment-counts \
--min-mapq 30
Compare two BigWig files
bamnado bigwig-compare \
--bw1 sample_treated.bw \
--bw2 sample_control.bw \
--comparison log-ratio \
--pseudocount 1e-3 \
--threads 6 \
-o treated_vs_control.bw
Aggregate multiple BigWig files
bamnado bigwig-aggregate \
--bigwigs sample1.bw sample2.bw sample3.bw \
--method mean \
--threads 6 \
-o mean_coverage.bw
Simplify bedGraph file
bamnado collapse-bedgraph \
--input signal.bedgraph \
--output signal.collapsed.bedgraph
Normalize between ChIP-seq samples
bamnado bam-normalize \
--bams chip1.bam chip2.bam chip3.bam \
--method csaw-background \
--bin-size 10000 \
--blacklist blacklist.bed
Apply the reported scale_factor for each sample to bam-coverage -f (or
bigwig-aggregate --scale-factors) when generating tracks for that sample.
Between-sample normalization
Comparing ChIP-seq / CUT&Tag / CUT&RUN samples side by side requires removing
composition and technical bias, not just sequencing depth. A sample with a stronger
pulldown has more of its library consumed by peaks, which deflates its background and
makes every other region look artificially low relative to a shallower or weaker-pulldown
sample. Simple total-count (CPM) scaling does not correct for this — it's the same
problem csaw::normFactors / edgeR::calcNormFactors (TMM) solve in R/Bioconductor.
bamnado bam-normalize bins the genome, counts filtered reads (or fragments) per bin per
sample, and reports a per-sample scaling factor plus the underlying bin count matrix for
downstream sanity checks (e.g. M-A plots). It reports factors only — it does not generate
bigwigs itself.
Method choice
| Method | When to use |
|---|---|
csaw-background (default) |
Typical composition bias: TMM restricted to large background windows with the most-enriched bins excluded |
tmm |
TMM over all bins, without excluding enriched regions |
cpm |
Naive depth-only baseline, useful for comparison against the other methods |
median-of-ratios |
A robust DESeq2-style alternative estimator |
spike-in |
When a spike-in genome was used and is trusted more than a background estimate |
Applying the result
scale_factor is centred on 1 (the geometric mean across samples), so it can be passed
directly to bam-coverage -f <factor> or bigwig-aggregate --scale-factors for each
corresponding sample.
Use --counts-out counts.tsv to write the underlying bin count matrix (chrom, start, end,
one column per sample) for QC, e.g. plotting an M-A plot to confirm the fitted factor sits
at the centre of the trimmed M-value cloud.
Spike-in caveat
The spike-in method reads mapped-read counts directly from each BAM's index (no BAM
record scan), so those counts are unfiltered by MAPQ, duplicates, or
secondary/supplementary status — the same basis as bam-coverage's internal mapped-read
count. --exogenous-prefix is required and --bin-size is ignored for this method.
Python API
Installing the Python package also installs the bamnado CLI, including all Rust CLI
commands and flags:
pip install bamnado
bamnado bam-coverage --help
BamNado provides Python bindings for high-performance BAM signal generation with flexible read filtering. Install from PyPI:
pip install bamnado
# or
uv pip install bamnado
Quick start
import bamnado
import numpy as np
# Generate coverage from a BAM file
signal = bamnado.get_signal_for_chromosome(
bam_path="input.bam",
chromosome_name="chr1",
bin_size=50,
)
print(f"Mean coverage: {np.mean(signal):.2f}")
ReadFilter class
Customize read filtering with the ReadFilter class. All parameters are optional:
import bamnado
# Create a filter for high-quality, properly-paired reads
strict_filter = bamnado.ReadFilter(
min_mapq=30,
proper_pair=True,
min_length=50,
)
# Apply the filter
signal = bamnado.get_signal_for_chromosome(
bam_path="input.bam",
chromosome_name="chr1",
bin_size=100,
read_filter=strict_filter,
)
Filter parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
min_mapq |
int |
0 |
Minimum mapping quality |
proper_pair |
bool |
False |
Require properly paired reads |
min_length |
int |
0 |
Minimum read length (bp) |
max_length |
int |
1000 |
Maximum read length (bp) |
strand |
str |
"both" |
"forward", "reverse", or "both" |
min_fragment_length |
int | None |
None |
Minimum insert size (bp); paired-end only |
max_fragment_length |
int | None |
None |
Maximum insert size (bp); paired-end only |
blacklist_bed |
str | None |
None |
BED file of excluded regions |
whitelisted_barcodes |
list[str] | None |
None |
Cell barcodes to include (CB tag) |
read_group |
str | None |
None |
Read group to keep (RG tag) |
filter_tag |
str | None |
None |
SAM tag to filter on (e.g. "VP") |
filter_tag_value |
str | None |
None |
Required value for filter_tag |
Usage examples
Basic coverage
import bamnado
import numpy as np
signal = bamnado.get_signal_for_chromosome(
bam_path="sample.bam",
chromosome_name="chr1",
bin_size=50,
scale_factor=1.0,
use_fragment=False,
)
print(f"Signal shape: {signal.shape}, type: {signal.dtype}")
print(f"Mean: {np.mean(signal):.2f}, Max: {np.max(signal):.2f}")
Nucleosome-free regions (ATAC-seq)
import bamnado
# Forward-strand reads, 100–200 bp fragments
nfr_filter = bamnado.ReadFilter(
strand="forward",
min_fragment_length=100,
max_fragment_length=200,
min_mapq=20,
)
nfr_signal = bamnado.get_signal_for_chromosome(
bam_path="atac.bam",
chromosome_name="chr1",
bin_size=10,
use_fragment=True,
read_filter=nfr_filter,
)
Barcode-filtered coverage (single-cell)
import bamnado
# Get coverage for specific cell barcodes
cell_barcodes = ["ACGTACGT-1", "TGCATGCA-1", "AAATAAAA-1"]
barcode_filter = bamnado.ReadFilter(
whitelisted_barcodes=cell_barcodes,
min_mapq=25,
)
signal = bamnado.get_signal_for_chromosome(
bam_path="possorted_genome_bam.bam",
chromosome_name="chr1",
bin_size=100,
use_fragment=True,
read_filter=barcode_filter,
)
Tag-filtered coverage (e.g. MCC viewpoint)
import bamnado
# Get coverage for specific viewpoint
vp_filter = bamnado.ReadFilter(
filter_tag="VP",
filter_tag_value="BCL2",
min_mapq=30,
)
vp_signal = bamnado.get_signal_for_chromosome(
bam_path="mcc.bam",
chromosome_name="chr1",
bin_size=50,
use_fragment=True,
read_filter=vp_filter,
)
High-confidence coverage
import bamnado
# High-quality, properly-paired reads
confident_filter = bamnado.ReadFilter(
min_mapq=30,
proper_pair=True,
min_length=50,
min_fragment_length=100,
max_fragment_length=500,
)
signal = bamnado.get_signal_for_chromosome(
bam_path="sample.bam",
chromosome_name="chr1",
bin_size=50,
scale_factor=1e6, # CPM normalization
use_fragment=True,
read_filter=confident_filter,
)
get_signal_for_chromosome parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
bam_path |
str |
— | Path to indexed BAM file |
chromosome_name |
str |
— | Chromosome to process (e.g. "chr1") |
bin_size |
int |
50 |
Bin width in base pairs |
scale_factor |
float |
1.0 |
Linear scaling factor |
use_fragment |
bool |
False |
Use fragment (pair) coordinates instead of read |
ignore_scaffold_chromosomes |
bool |
True |
Skip non-standard chromosomes |
read_filter |
ReadFilter | None |
None |
Optional read filter |
Returns: numpy.ndarray of dtype float32 with length = chromosome size / bin_size
Note: Fragment length filtering (min_fragment_length, max_fragment_length) requires paired-end BAM files and will raise ValueError on single-end data.
Between-sample scaling factors
compute_scale_factors mirrors the bam-normalize CLI command — see
Between-sample normalization for the underlying method.
import bamnado
result = bamnado.compute_scale_factors(
["chip1.bam", "chip2.bam", "chip3.bam"],
method="csaw-background",
bin_size=10000,
)
print(result.sample_names, result.scale_factors)
# Include the bin count matrix for QC (e.g. an M-A plot)
result = bamnado.compute_scale_factors(
["chip1.bam", "chip2.bam"],
method="tmm",
return_counts=True,
)
counts = result.counts() # (n_bins, n_samples) uint64 array, or None
compute_scale_factors parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
bam_paths |
list[str] |
— | Input BAM file paths, one per sample |
method |
str |
"csaw-background" |
"tmm", "csaw-background", "cpm", "median-of-ratios", or "spike-in" |
bin_size |
int |
10000 |
Genomic bin width in bp; ignored for "spike-in" |
sample_names |
list[str] | None |
None |
Defaults to each BAM's file stem, disambiguated if duplicated |
exclude_top_percent |
float |
5.0 |
"csaw-background" only: percent of highest-mean bins dropped before estimating |
reference_sample |
str | None |
None |
TMM reference sample; defaults to the one closest to the geometric mean |
logratio_trim |
float |
0.3 |
TMM trim fraction for M-values |
sum_trim |
float |
0.05 |
TMM trim fraction for A-values |
exogenous_prefix |
str | None |
None |
Required for method="spike-in" |
use_fragment |
bool |
False |
Count fragments (pairs) instead of individual reads |
ignore_scaffold_chromosomes |
bool |
True |
Skip scaffold / unplaced chromosomes |
read_filter |
ReadFilter | None |
None |
Applied per sample |
return_counts |
bool |
False |
Populate ScaleFactorResult.counts() |
Returns: ScaleFactorResult with method, sample_names, library_sizes,
norm_factors, scale_factors, reference_sample, n_bins_total, n_bins_used, and
(when return_counts=True) counts(), bin_chroms, bin_starts, bin_ends.
Development
cargo build --release
cargo test
License
Apache-2.0 OR MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bamnado-0.9.0.tar.gz.
File metadata
- Download URL: bamnado-0.9.0.tar.gz
- Upload date:
- Size: 138.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca48412759c5d7f186698575a4203ffdc07290653afcd0f461a328181313d467
|
|
| MD5 |
6bed71122b40587d9b9de8d5f4f50699
|
|
| BLAKE2b-256 |
556999802c32c66d7bb62ce708f82e230fc94496a7b16e33afa59c1fe928f61d
|
File details
Details for the file bamnado-0.9.0-cp310-abi3-win_amd64.whl.
File metadata
- Download URL: bamnado-0.9.0-cp310-abi3-win_amd64.whl
- Upload date:
- Size: 17.6 MB
- Tags: CPython 3.10+, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9317535eec97d20d192e3d8f68aa78d9fc824df609350d3746b23374581bb9cf
|
|
| MD5 |
ffdf1a1879ca1e4aa358c54ebf74ac77
|
|
| BLAKE2b-256 |
629c97860e71921071038d0f94be3afae670a351465a92a5690eadef25622889
|
File details
Details for the file bamnado-0.9.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: bamnado-0.9.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 19.0 MB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5422267afd817b906457cf23e0afd907832a8492215b8108680c1c3b90caa70c
|
|
| MD5 |
d6d8d742b971487c914a5918bcf21dca
|
|
| BLAKE2b-256 |
39e7d74be18265eac354a5429b2d8cf49c406c0b79f81258bc929fb7ade569ed
|
File details
Details for the file bamnado-0.9.0-cp310-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: bamnado-0.9.0-cp310-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 17.4 MB
- Tags: CPython 3.10+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via:
maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
56b90b1eff514e1892156fa88a6884cbaad8cbbe137f0e2552f8a7681f9d1952
|
|
| MD5 |
0939cd07d308b832a35d3ef3af200067
|
|
| BLAKE2b-256 |
14691e7ad41970a739ba33669fd0716be977cbaa490b6cbdb2f47f751559ffd5
|
File details
Details for the file bamnado-0.9.0-cp310-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: bamnado-0.9.0-cp310-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 18.0 MB
- Tags: CPython 3.10+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ae8f146e2b7311235dac6c16bc38049e3f49c96ec2a79bd23e74c6bb204e7c44
|
|
| MD5 |
466b07f40af687073af86b9e4f8cc455
|
|
| BLAKE2b-256 |
5e9537f2d78c28ae99385eaacdc141bfc9384ce7ca56a2f2f00dbb9e86cce99c
|