Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 35 additions & 12 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,28 @@ src/
config.rs — YAML configuration loading (serde), nested tool configs
io.rs — Shared I/O utilities (gzip-transparent file reading)
gtf.rs — GTF annotation file parser (with configurable attribute extraction)
common/
mod.rs — Re-exports the shared modules
bam_flags.rs — BAM flag constants and aux-tag helpers
bam_stat.rs — bam_stat.py reimplementation, result types
bam_stat_accum.rs — Read-level counter accumulator feeding bam_stat and samtools
cpp_rng.rs — C++ RNG FFI shim for preseq bootstrap reproducibility
preseq.rs — preseq lc_extrap library complexity extrapolation
samtools/
mod.rs — Re-exports the samtools writers
stats.rs — samtools stats full output (SN + all histogram sections)
flagstat.rs — samtools flagstat-compatible output
idxstats.rs — samtools idxstats-compatible output
dna/
mod.rs — Re-exports the DNA submodules
depth.rs — Per-contig depth accumulator (delta array, CIGAR walk,
mate-overlap correction, prefix sum)
mosdepth/
mod.rs — Per-contig summarisation feeding the mosdepth outputs
output.rs — The six mosdepth-compatible writers (bgzf for the BED outputs)
rna/
mod.rs — Re-exports all submodules (dupradar, featurecounts, rseqc, bam_flags, cpp_rng, preseq, qualimap)
bam_flags.rs — BAM flag constants
cpp_rng.rs — C++ RNG FFI shim for preseq bootstrap reproducibility
mod.rs — Re-exports the RNA submodules (dupradar, featurecounts, rseqc, qualimap)
and re-exports the shared ones from `common` for compatibility
dupradar/
mod.rs — Re-exports counting, dupmatrix, fitting, plots
counting.rs — BAM read counting engine (largest module)
Expand All @@ -75,7 +93,6 @@ src/
featurecounts/
mod.rs — Re-exports output
output.rs — featureCounts-format output & biotype counting
preseq.rs — preseq lc_extrap library complexity extrapolation
qualimap/
mod.rs — Re-exports all Qualimap modules
accumulator.rs — Gene body coverage accumulation logic
Expand All @@ -88,17 +105,13 @@ src/
mod.rs — Re-exports all RSeQC modules + common helpers
accumulators.rs — Shared RSeQC accumulator infrastructure (read dispatch)
common.rs — Shared junction/intron extraction, from_genes builders
bam_stat.rs — bam_stat.py reimplementation
flagstat.rs — samtools flagstat-compatible output
idxstats.rs — samtools idxstats-compatible output
infer_experiment.rs — infer_experiment.py reimplementation
inner_distance.rs — inner_distance.py reimplementation
junction_annotation.rs — junction_annotation.py reimplementation
junction_saturation.rs — junction_saturation.py reimplementation
plots.rs — RSeQC plot generation (duplication, junctions, etc.)
read_distribution.rs — read_distribution.py reimplementation
read_duplication.rs — read_duplication.py reimplementation
stats.rs — samtools stats full output (SN + all histogram sections)
tin.rs — TIN (Transcript Integrity Number) analysis
tests/
integration_test.rs — 12 integration tests vs R dupRadar reference output
Expand All @@ -107,13 +120,23 @@ tests/
create_test_data.R — R script to regenerate test data + references
```

Nested module structure — top-level modules (`cli`, `config`, `io`, `gtf`, `rna`) declared
in `main.rs`, no `lib.rs`. The `rna` module contains sub-modules for each tool group.
Inter-module access uses `crate::` paths (e.g., `use crate::rna::dupradar::counting::GeneCounts;`).
Nested module structure. The library crate root is `src/lib.rs`, which declares
`common`, `config`, `cpu`, `gtf`, `io`, `rna` and `summary`; the binary
(`src/main.rs`) additionally declares `cli`, `citations` and `ui`.
Inter-module access uses `crate::` paths (e.g., `use crate::common::bam_stat_accum::BamStatAccum;`).
Assay-agnostic analyses belong in `common`; put new code under `rna` only if it
needs a gene annotation or a library strand protocol.

The CLI uses a single subcommand:
The CLI has two subcommands:

- `rustqc rna <BAM>... --gtf <GTF> [OPTIONS]`
- `rustqc dna <BAM>... [OPTIONS]`

The `dna` subcommand needs no annotation. It runs depth of coverage
(mosdepth-compatible), the samtools-compatible outputs and preseq in one pass,
with one worker per contig. Shared flags keep their `rna` names, short forms
and `RUSTQC_*` environment variables, with one deliberate exception:
`-Q/--mapq` defaults to 0 for `dna`, matching mosdepth, rather than 30.

A GTF gene annotation file (`--gtf`) is required. This runs all analyses:
dupRadar duplicate rate analysis, featureCounts-compatible gene counting,
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# RustQC Changelog

## Unreleased

### Features

- New `rustqc dna` subcommand for DNA (WGS) quality control: depth of coverage
with mosdepth-compatible outputs, samtools-compatible stats, flagstat and
idxstats, and preseq library complexity, all in a single pass over the
alignment with one worker per contig. Validated for exact parity against
mosdepth 0.3.14 and samtools 1.24.

### Changed

- Internal: assay-agnostic analyses (BAM flag helpers, read-level statistics,
the samtools stats/flagstat/idxstats writers, preseq) moved from `rna` to a
new `common` module. The old `rustqc::rna::...` paths still resolve through
re-exports, so this is not a breaking change for library users.

## [Version 0.2.1](https://github.com/seqeralabs/RustQC/releases/tag/v0.2.1) - 2026-04-09

### Bug fixes
Expand Down
90 changes: 74 additions & 16 deletions src/citations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! Writes a Markdown file alongside results documenting which upstream tools
//! RustQC replicated in this run, their validated versions, and citation info.

use crate::config::RnaConfig;
use crate::config::{DnaConfig, RnaConfig};
use anyhow::{Context, Result};
use std::io::Write;
use std::path::Path;
Expand Down Expand Up @@ -48,6 +48,14 @@ const PRESEQ: Citation = Citation {
doi: "10.1038/nmeth.2375",
};

const MOSDEPTH: Citation = Citation {
heading: "mosdepth (v0.3.14)",
description: "RustQC reimplements the depth of coverage analysis of mosdepth.",
reference: "Pedersen BS, Quinlan AR. Mosdepth: quick coverage calculation for genomes and exomes. *Bioinformatics*. 2018;34(5):867-868.",
url: "https://github.com/brentp/mosdepth",
doi: "10.1093/bioinformatics/btx699",
};

const SAMTOOLS: Citation = Citation {
heading: "Samtools (v1.22.1)",
description: "RustQC produces Samtools-compatible flagstat, idxstats, and stats output.",
Expand All @@ -56,6 +64,19 @@ const SAMTOOLS: Citation = Citation {
doi: "10.1093/gigascience/giab008",
};

/// Same tool as [`SAMTOOLS`], different validated version.
///
/// The `rna` pipeline's outputs were checked against samtools 1.22.1 and the
/// `dna` pipeline's against 1.24, so each cites the version it was actually
/// compared with rather than both claiming the newer one.
const SAMTOOLS_DNA: Citation = Citation {
heading: "Samtools (v1.24)",
description: "RustQC produces Samtools-compatible flagstat, idxstats, and stats output.",
reference: "Danecek P, Bonfield JK, Liddle J, et al. Twelve years of Samtools and BCFtools. *GigaScience*. 2021;10(2):giab008.",
url: "http://www.htslib.org/",
doi: "10.1093/gigascience/giab008",
};

const QUALIMAP: Citation = Citation {
heading: "Qualimap (v2.3)",
description: "RustQC produces gene body coverage output compatible with Qualimap rnaseq.",
Expand All @@ -79,21 +100,7 @@ pub fn write_citations(path: &Path, config: &RnaConfig, version: &str, commit: &
.with_context(|| format!("Failed to create citations file: {}", path.display()))?;
let mut w = std::io::BufWriter::new(file);

writeln!(w, "# RustQC Citations\n")?;
writeln!(
w,
"This file was generated by [RustQC](https://github.com/seqeralabs/RustQC) v{version} ({commit})."
)?;
writeln!(
w,
"It documents the upstream tools whose behaviour this run replicated."
)?;
writeln!(
w,
"Please cite both RustQC and the relevant upstream tools listed below.\n"
)?;
writeln!(w, "## RustQC (v{version})\n")?;
writeln!(w, "- Repository: <https://github.com/seqeralabs/RustQC>\n")?;
write_header(&mut w, version, commit)?;

if config.any_dupradar_output() {
write_citation(&mut w, &DUPRADAR)?;
Expand All @@ -118,6 +125,57 @@ pub fn write_citations(path: &Path, config: &RnaConfig, version: &str, commit: &
Ok(())
}

/// Write `CITATIONS.md` for a `dna` run.
///
/// Shares the header and the per-tool blocks with [`write_citations`]; only
/// the set of tools differs, because the two pipelines replicate different
/// upstream programs.
pub fn write_dna_citations(
path: &Path,
config: &DnaConfig,
version: &str,
commit: &str,
) -> Result<()> {
let file = std::fs::File::create(path)
.with_context(|| format!("Failed to create citations file: {}", path.display()))?;
let mut w = std::io::BufWriter::new(file);

write_header(&mut w, version, commit)?;

if config.mosdepth.enabled {
write_citation(&mut w, &MOSDEPTH)?;
}
if config.samtools.enabled {
write_citation(&mut w, &SAMTOOLS_DNA)?;
}
if config.preseq.enabled {
write_citation(&mut w, &PRESEQ)?;
}

w.flush()?;
Ok(())
}

/// Shared preamble of both citation files.
fn write_header<W: Write>(w: &mut W, version: &str, commit: &str) -> Result<()> {
writeln!(w, "# RustQC Citations\n")?;
writeln!(
w,
"This file was generated by [RustQC](https://github.com/seqeralabs/RustQC) v{version} ({commit})."
)?;
writeln!(
w,
"It documents the upstream tools whose behaviour this run replicated."
)?;
writeln!(
w,
"Please cite both RustQC and the relevant upstream tools listed below.\n"
)?;
writeln!(w, "## RustQC (v{version})\n")?;
writeln!(w, "- Repository: <https://github.com/seqeralabs/RustQC>\n")?;
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading