# BamScale <a href="https://cparsania.github.io/BamScale"><img src="man/figures/logo.png" align="right" height="240" alt="BamScale website" /></a>

> Bioconductor-friendly multithreaded BAM processing

<!-- versions -->
[![GitHub version](https://img.shields.io/github/r-package/v/cparsania/BamScale?label=GitHub&logo=github&color=blue)](https://github.com/cparsania/BamScale)
[![Bioc devel version](https://bioc.r-universe.dev/badges/BamScale?label=Bioc%20devel)](https://bioconductor.org/packages/3.24/bioc/html/BamScale.html)
[![Bioc release](https://img.shields.io/badge/Bioc%20release-upcoming%20(3.24)-orange)](https://bioconductor.org/packages/3.24/bioc/html/BamScale.html)
<!-- checks -->
[![R-CMD-check](https://github.com/cparsania/BamScale/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/cparsania/BamScale/actions/workflows/R-CMD-check.yaml)
[![Bioc build](https://bioconductor.org/shields/build/devel/bioc/BamScale.svg)](https://bioconductor.org/checkResults/devel/bioc-LATEST/BamScale/)
[![pkgdown](https://github.com/cparsania/BamScale/actions/workflows/pkgdown.yaml/badge.svg)](https://github.com/cparsania/BamScale/actions/workflows/pkgdown.yaml)
<!-- status -->
[![Bioconductor availability](https://bioconductor.org/shields/availability/devel/BamScale.svg)](https://bioconductor.org/packages/3.24/bioc/html/BamScale.html)
[![Bioc downloads](https://bioconductor.org/shields/downloads/devel/BamScale.svg)](https://bioconductor.org/packages/stats/bioc/BamScale/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

**BamScale** is a multithreaded BAM reader for R, built on the [`ompBAM`](https://bioconductor.org/packages/ompBAM) OpenMP engine. It returns the *exact* Bioconductor objects you already use — verified **byte-identical** to `Rsamtools` and `GenomicAlignments` — but decodes each file across many cores, taking the read step off the critical path in alignment-centric workflows.

- **2.5–4× faster** single-file reads than `scanBam` / `readGAlignments`
- **Drop-in**: same objects, same `ScanBamParam` filtering, same `BiocParallel` model
- **In-reader aggregation** (0.99.14): `fragment_sizes()`, `mapq_dist()`, `bam_coverage()`, and single-pass `bam_coverage_bigwig()` fold the computation inside the reader — byte-identical results without materialising per-read objects
- **Two parallel axes**: OpenMP `threads` *within* a file, `BiocParallel` *across* files
- **Verified correct**: output is byte-identical to the standard tools at every thread count

## Benchmarks

Intel Xeon Gold 6252 (96 cores), warm page cache, median of 5 iterations. Full methodology, figures, and fair-comparison details are in the [benchmark article](https://cparsania.github.io/BamScale/articles/benchmark-results.html).

**Single-file read throughput** — best BamScale configuration vs the single-threaded standard reader:

| Read pattern | Comparator | Standard | BamScale | Threads | Speedup |
| --- | --- | ---: | ---: | :---: | :---: |
| Core alignment fields | `Rsamtools::scanBam` | 15.4 s | 6.3 s | 48 | **2.5×** |
| `GAlignments` object | `GenomicAlignments::readGAlignments` | 10.6 s | 2.6 s | 48 | **4.0×** |
| Sequence + base quality | `Rsamtools::scanBam` | 21.6 s | 7.0 s | 48 | **3.1×** |

**End-to-end workflows** — BamScale swapped in for the read step only, every other step identical on both sides. The gain tracks how read-bound the workflow is (Amdahl's law):

| Workflow | Read fraction | End-to-end speedup |
| --- | :---: | :---: |
| ATAC fragment-size QC | 92% | **4.1×** |
| Coverage → `RleList` | 76% | **3.3×** |
| Coverage → bigWig | 22% | **1.2×** |

Every result is measured against output verified equal to the standard tool: the ATAC fragment-size table is byte-identical to `ATACseqQC::fragSizeDist` across 49.8M reads, and the coverage `RleList` is `identical()` to `GenomicAlignments::coverage()` at every thread count.

> BamScale is not a raw-decode replacement for command-line `samtools`. Its job is fast, object-faithful decoding *inside* R — putting the cores a single-threaded reader leaves idle to work, and handing back the precise Bioconductor objects your analysis depends on.

## Installation

Requires R with a C++17 toolchain and an OpenMP-capable compiler; `ompBAM` and the other dependencies are pulled in automatically.

BamScale is on [Bioconductor](https://bioconductor.org/packages/3.24/bioc/html/BamScale.html) (currently in the `devel` branch):

```r
if (!requireNamespace("BiocManager", quietly = TRUE)) install.packages("BiocManager")
BiocManager::install(version = "devel")
BiocManager::install("BamScale")
```

Or install the development version from GitHub:

```r
remotes::install_github("cparsania/BamScale")
```

## Quick start

```r
library(BamScale)

bam <- ompBAM::example_BAM("Unsorted")

# Core alignment fields (scanBam-style extraction)
x <- bam_read(
  bam,
  what    = c("qname", "flag", "rname", "pos", "mapq", "cigar"),
  threads = 4
)

# A GAlignments object (drop-in for readGAlignments)
ga <- bam_read(
  bam,
  what    = c("rname", "pos", "cigar", "strand"),
  as      = "GAlignments",
  threads = 4
)

# Sequence + base quality, in standard-compatible objects
sq <- bam_read(
  bam,
  what         = c("qname", "seq", "qual"),
  seqqual_mode = "compatible",
  threads      = 4
)

# Fast chromosome-level counts
cnt <- bam_count(bam, threads = 4)

# In-reader aggregation (0.99.14): summaries computed inside the C++ reader,
# byte-identical to the scanBam/coverage equivalents
fs  <- fragment_sizes(bam, threads = 4)   # paired-end fragment-size distribution
mq  <- mapq_dist(bam, threads = 4)        # mapping-quality distribution
cov <- bam_coverage(bam, threads = 4)     # per-base coverage RleList
bam_coverage_bigwig(bam, "coverage.bw", threads = 4)  # single-pass BAM -> bigWig
```

## Output modes & compatibility

BamScale fits where `Rsamtools` and `GenomicAlignments` already fit, adding a within-file threading axis on top:

- **Field extraction** via `what = c("qname", "flag", "rname", "pos", "mapq", "cigar", ...)` — the metadata pattern behind most QC, filtering, and fragment-level summaries.
- **Alignment objects** via `as = "GAlignments"` or `as = "GAlignmentPairs"`.
- **`scanBam`-shaped lists** via `as = "scanBam"`, or a `data.frame` / `S4Vectors::DataFrame`.
- **`ScanBamParam`-style filtering**: `mapqFilter`, `flag`, `which`, `what`, `tag`.
- **File-level parallelism** via `BiocParallel` (`BPPARAM`), exactly as in a standard workflow.

## Parallelism model

BamScale parallelizes on two axes — across files via `BPPARAM` workers, and within each file via OpenMP `threads`. Approximate effective concurrency:

```
min(length(files), bpnworkers(BPPARAM)) * threads
```

With `auto_threads = TRUE`, BamScale keeps per-file thread counts high when possible — reducing the number of concurrently active file workers before it shrinks per-file threads. Within-file threading is the key difference from the baseline model, and the reason BamScale removes the read bottleneck when there are fewer files than cores.

## Sequence/quality: `compatible` vs `compact`

`seqqual_mode = "compatible"` (default for equivalence) returns ordinary `DNAStringSet` / `PhredQuality`-style output, byte-identical to `scanBam`.

`seqqual_mode = "compact"` is a lower-level, deferred-decoding representation for when extraction throughput matters more than immediate string materialization:

- `seq` — a list-column of `raw` vectors holding BAM-native packed bytes (two bases per byte)
- `qual` — a list-column of `raw` vectors holding per-base Phred bytes (`255` = missing)
- `qwidth` is required to decode compact `seq` back to base letters

Decode it explicitly when needed:

```r
sq_compact <- bam_read(
  bam,
  what         = c("qname", "qwidth", "seq", "qual"),
  seqqual_mode = "compact",
  threads      = 4
)
decoded <- decode_seqqual_compact(sq_compact)
```

## Current limitations

- `param$which` is implemented as sequential filtering, not indexed random-access jumps.
- `seqqual_mode = "compact"` is optimization-oriented and not intended for strict cross-package output equivalence.
- `GAlignments` / `GAlignmentPairs` outputs exclude unmapped records by design.

## Reproducing the benchmarks

Benchmark drivers and reporting assets ship under [inst/benchmarks/](inst/benchmarks/) — see its [README](inst/benchmarks/README.md). Reported results come from `run_server_benchmark.R` (read patterns) and `run_workflow_benchmark.R` (end-to-end coverage and ATAC QC).

## Citation

```r
citation("BamScale")
```

If BamScale contributes to a performance claim, please also cite `ompBAM`.

## Contributing

Pull requests are welcome — please include a short motivation, tests for behavior changes, and benchmark evidence for performance claims. Before opening a PR:

```bash
R CMD build .
R CMD check --as-cran BamScale_*.tar.gz
Rscript -e "BiocCheck::BiocCheck('.')"
```

## Community & support

- User questions: [Bioconductor support site](https://support.bioconductor.org)
- Bugs & feature requests: [GitHub issues](https://github.com/cparsania/BamScale/issues)

## License

MIT — see [`LICENSE`](LICENSE).
