diff --git a/AGENTS.md b/AGENTS.md index 108414ac..d1c56db5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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) @@ -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 @@ -88,9 +105,6 @@ 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 @@ -98,7 +112,6 @@ src/ 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 @@ -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 ... --gtf [OPTIONS]` +- `rustqc dna ... [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, diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b67cce4..5352f57b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/citations.rs b/src/citations.rs index 316ed4d1..6c968f2b 100644 --- a/src/citations.rs +++ b/src/citations.rs @@ -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; @@ -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.", @@ -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.", @@ -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: \n")?; + write_header(&mut w, version, commit)?; if config.any_dupradar_output() { write_citation(&mut w, &DUPRADAR)?; @@ -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: &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: \n")?; + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/cli.rs b/src/cli.rs index 6e6459e8..2f8965a5 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -32,6 +32,13 @@ pub enum Commands { /// analyses in one pass. Requires a GTF annotation and duplicate-marked /// (not removed) alignments. Rna(RnaArgs), + + /// DNA QC — single-pass analysis of BAM/SAM/CRAM files. + /// + /// Runs depth of coverage, samtools stats and library complexity + /// estimation in one pass. Needs no gene annotation. Pass `--targets` + /// to switch to targeted (exome or panel) mode. + Dna(DnaArgs), } /// Arguments for the `rna` subcommand. @@ -369,6 +376,278 @@ pub struct RnaArgs { pub preseq_seg_len: Option, } +/// Arguments for the `dna` subcommand. +/// +/// Shared options keep the same long name, short flag and `RUSTQC_*` +/// environment variable as their `rna` counterparts, so wrapper scripts and +/// muscle memory carry over between the two pipelines. The differences are +/// deliberate: there is no `--gtf` and no `--stranded`, and `--mapq` defaults +/// to 0 rather than 30 because that is mosdepth's default. +#[derive(Parser, Debug)] +#[command( + next_line_help = false, + term_width = 120, + help_template = "\ +{about-with-newline} +{usage-heading} {usage} + +{all-args}" +)] +pub struct DnaArgs { + // ── Input / Output ────────────────────────────────────────────────── + /// Duplicate-marked alignment file(s) + #[arg(value_name = "INPUT", num_args = 1.., required = true, help_heading = "Input / Output")] + pub input: Vec, + + /// Reference FASTA (required for CRAM and for GC bias) + #[arg( + short, + long, + value_name = "FASTA", + env = "RUSTQC_REFERENCE", + help_heading = "Input / Output" + )] + pub reference: Option, + + /// Target intervals BED; switches on targeted (exome or panel) mode + #[arg( + long, + value_name = "BED", + env = "RUSTQC_TARGETS", + help_heading = "Input / Output" + )] + pub targets: Option, + + /// Capture bait intervals BED [default: same as --targets] + #[arg( + long, + value_name = "BED", + env = "RUSTQC_BAITS", + requires = "targets", + help_heading = "Input / Output" + )] + pub baits: Option, + + /// Output directory [default: .] + #[arg( + short, + long, + default_value = ".", + hide_default_value = true, + env = "RUSTQC_OUTDIR", + help_heading = "Input / Output" + )] + pub outdir: String, + + /// Override sample name for output filenames (default: derived from BAM filename) + #[arg( + long, + value_name = "NAME", + env = "RUSTQC_SAMPLE_NAME", + help_heading = "Input / Output" + )] + pub sample_name: Option, + + /// Write outputs to a flat directory (no subdirs) + #[arg( + long, + default_value_t = false, + env = "RUSTQC_FLAT_OUTPUT", + help_heading = "Input / Output" + )] + pub flat_output: bool, + + /// YAML configuration file (see also: RUSTQC_CONFIG env var) + #[arg(short, long, value_name = "CONFIG", help_heading = "Input / Output")] + pub config: Option, + + /// JSON summary path (use "-" for stdout) + #[arg(short = 'j', long = "json-summary", value_name = "PATH", num_args = 0..=1, default_missing_value = "", env = "RUSTQC_JSON_SUMMARY", help_heading = "Input / Output")] + pub json_summary: Option, + + // ── Library ───────────────────────────────────────────────────────── + /// Paired-end reads + #[arg(short, long, env = "RUSTQC_PAIRED", help_heading = "Library")] + pub paired: bool, + + // ── General ───────────────────────────────────────────────────────── + /// Number of threads [default: 1] + #[arg( + short, + long, + default_value_t = 1, + hide_default_value = true, + env = "RUSTQC_THREADS", + help_heading = "General" + )] + pub threads: usize, + + /// MAPQ cutoff; reads below it are ignored [default: 0] + #[arg( + short = 'Q', + long = "mapq", + default_value_t = 0, + hide_default_value = true, + env = "RUSTQC_MAPQ", + help_heading = "General" + )] + pub mapq_cut: u8, + + /// Skip duplicate-marking check + #[arg( + long, + default_value_t = false, + env = "RUSTQC_SKIP_DUP_CHECK", + help_heading = "General" + )] + pub skip_dup_check: bool, + + /// Suppress output except warnings/errors + #[arg( + short = 'q', + long, + conflicts_with = "verbose", + env = "RUSTQC_QUIET", + help_heading = "General" + )] + pub quiet: bool, + + /// Show additional detail + #[arg( + short = 'v', + long, + conflicts_with = "quiet", + env = "RUSTQC_VERBOSE", + help_heading = "General" + )] + pub verbose: bool, + + // ── Tool parameters ───────────────────────────────────────────────── + /// Coverage thresholds to report [default: 1,5,10,15,20,30,50] + #[arg( + long = "depth-thresholds", + value_name = "N,...", + value_delimiter = ',', + default_values_t = vec![1u32, 5, 10, 15, 20, 30, 50], + hide_default_value = true, + env = "RUSTQC_DEPTH_THRESHOLDS", + help_heading = "Tool parameters" + )] + pub depth_thresholds: Vec, + + /// Fixed-width window size for per-window depth + #[arg( + long = "window-size", + value_name = "N", + env = "RUSTQC_WINDOW_SIZE", + help_heading = "Tool parameters" + )] + pub window_size: Option, + + /// Picard COVERAGE_CAP [default: 250] + #[arg( + long = "coverage-cap", + value_name = "N", + default_value_t = 250, + hide_default_value = true, + env = "RUSTQC_COVERAGE_CAP", + help_heading = "Tool parameters" + )] + pub coverage_cap: u32, + + /// Picard MINIMUM_BASE_QUALITY [default: 20] + #[arg( + long = "min-base-quality", + value_name = "N", + default_value_t = 20, + hide_default_value = true, + env = "RUSTQC_MIN_BASE_QUALITY", + help_heading = "Tool parameters" + )] + pub min_base_quality: u8, + + /// Skip the per-base depth output, by far the largest file + #[arg( + long, + default_value_t = false, + env = "RUSTQC_SKIP_PER_BASE", + help_heading = "Tool parameters" + )] + pub skip_per_base: bool, + + /// Skip GC bias metrics + #[arg( + long, + default_value_t = false, + env = "RUSTQC_SKIP_GC_BIAS", + help_heading = "Tool parameters" + )] + pub skip_gc_bias: bool, + + /// Cap on concurrently live per-contig depth arrays [default: derived from RAM] + #[arg( + long = "max-depth-workers", + value_name = "N", + env = "RUSTQC_MAX_DEPTH_WORKERS", + help_heading = "Tool parameters" + )] + pub max_depth_workers: Option, + + /// Skip preseq library complexity analysis + #[arg( + long, + default_value_t = false, + env = "RUSTQC_SKIP_PRESEQ", + help_heading = "Tool parameters" + )] + pub skip_preseq: bool, + + /// preseq: random seed for bootstrap CIs + #[arg( + long = "preseq-seed", + value_name = "N", + env = "RUSTQC_PRESEQ_SEED", + help_heading = "Tool parameters" + )] + pub preseq_seed: Option, + + /// preseq: max extrapolation depth + #[arg( + long = "preseq-max-extrap", + value_name = "N", + env = "RUSTQC_PRESEQ_MAX_EXTRAP", + help_heading = "Tool parameters" + )] + pub preseq_max_extrap: Option, + + /// preseq: step size between points + #[arg( + long = "preseq-step-size", + value_name = "N", + env = "RUSTQC_PRESEQ_STEP_SIZE", + help_heading = "Tool parameters" + )] + pub preseq_step_size: Option, + + /// preseq: bootstrap replicates for CIs + #[arg( + long = "preseq-n-bootstraps", + value_name = "N", + env = "RUSTQC_PRESEQ_N_BOOTSTRAPS", + help_heading = "Tool parameters" + )] + pub preseq_n_bootstraps: Option, + + /// preseq: max segment length for PE merging + #[arg( + long = "preseq-seg-len", + value_name = "N", + env = "RUSTQC_PRESEQ_SEG_LEN", + help_heading = "Tool parameters" + )] + pub preseq_seg_len: Option, +} + /// Parse command-line arguments and return the Cli struct. /// /// Sets a `long_version` that includes the git commit, build timestamp, @@ -413,7 +692,6 @@ mod tests { assert_eq!(args.min_intron, None); assert_eq!(args.inner_distance_step, None); } - #[allow(unreachable_patterns)] _ => panic!("Expected Rna subcommand"), } } @@ -435,7 +713,6 @@ mod tests { assert_eq!(args.input, vec!["a.bam", "b.bam", "c.bam"]); assert_eq!(args.gtf, "genes.gtf"); } - #[allow(unreachable_patterns)] _ => panic!("Expected Rna subcommand"), } } @@ -470,7 +747,6 @@ mod tests { assert_eq!(args.reference, Some("genome.fa".to_string())); assert_eq!(args.mapq_cut, 20); } - #[allow(unreachable_patterns)] _ => panic!("Expected Rna subcommand"), } } @@ -512,7 +788,6 @@ mod tests { assert_eq!(args.inner_distance_upper_bound, Some(500)); assert_eq!(args.inner_distance_step, Some(10)); } - #[allow(unreachable_patterns)] _ => panic!("Expected Rna subcommand"), } } @@ -542,7 +817,6 @@ mod tests { assert_eq!(args.preseq_n_bootstraps, Some(200)); assert_eq!(args.preseq_seg_len, Some(100_000_000)); } - #[allow(unreachable_patterns)] _ => panic!("Expected Rna subcommand"), } } @@ -568,7 +842,6 @@ mod tests { assert_eq!(args.tin_seed, Some(2)); assert_eq!(args.junction_saturation_seed, Some(3)); } - #[allow(unreachable_patterns)] _ => panic!("Expected Rna subcommand"), } } @@ -587,8 +860,80 @@ mod tests { Commands::Rna(args) => { assert!(args.skip_preseq); } - #[allow(unreachable_patterns)] _ => panic!("Expected Rna subcommand"), } } + + #[test] + fn test_dna_default_args() { + let cli = Cli::parse_from(["rustqc", "dna", "test.bam"]); + match cli.command { + Commands::Dna(args) => { + assert_eq!(args.input, vec!["test.bam"]); + assert_eq!(args.outdir, "."); + assert_eq!(args.threads, 1); + assert_eq!(args.mapq_cut, 0); + assert_eq!(args.coverage_cap, 250); + assert_eq!(args.min_base_quality, 20); + assert_eq!(args.depth_thresholds, vec![1, 5, 10, 15, 20, 30, 50]); + assert_eq!(args.window_size, None); + assert!(args.targets.is_none()); + assert!(args.baits.is_none()); + assert!(!args.skip_per_base); + assert!(!args.skip_gc_bias); + } + _ => panic!("Expected Dna subcommand"), + } + } + + #[test] + fn test_dna_no_gtf_required() { + assert!(Cli::try_parse_from(["rustqc", "dna", "test.bam"]).is_ok()); + } + + #[test] + fn test_dna_targeted_args() { + let cli = Cli::parse_from([ + "rustqc", + "dna", + "a.bam", + "b.bam", + "--targets", + "t.bed", + "--baits", + "b.bed", + "--depth-thresholds", + "1,10,100", + "--window-size", + "500", + "--reference", + "genome.fa", + "-Q", + "20", + "--threads", + "4", + ]); + match cli.command { + Commands::Dna(args) => { + assert_eq!(args.input, vec!["a.bam", "b.bam"]); + assert_eq!(args.targets, Some("t.bed".to_string())); + assert_eq!(args.baits, Some("b.bed".to_string())); + assert_eq!(args.depth_thresholds, vec![1, 10, 100]); + assert_eq!(args.window_size, Some(500)); + assert_eq!(args.reference, Some("genome.fa".to_string())); + assert_eq!(args.mapq_cut, 20); + assert_eq!(args.threads, 4); + } + _ => panic!("Expected Dna subcommand"), + } + } + + #[test] + fn test_dna_baits_without_targets_is_rejected() { + let result = Cli::try_parse_from(["rustqc", "dna", "test.bam", "--baits", "b.bed"]); + assert!( + result.is_err(), + "--baits without --targets must be rejected" + ); + } } diff --git a/src/rna/bam_flags.rs b/src/common/bam_flags.rs similarity index 100% rename from src/rna/bam_flags.rs rename to src/common/bam_flags.rs diff --git a/src/rna/rseqc/bam_stat.rs b/src/common/bam_stat.rs similarity index 100% rename from src/rna/rseqc/bam_stat.rs rename to src/common/bam_stat.rs diff --git a/src/common/bam_stat_accum.rs b/src/common/bam_stat_accum.rs new file mode 100644 index 00000000..f4f08408 --- /dev/null +++ b/src/common/bam_stat_accum.rs @@ -0,0 +1,1313 @@ +//! Read-level alignment statistics accumulator. +//! +//! [`BamStatAccum`] gathers, in a single pass over the records, every counter +//! consumed by RSeQC `bam_stat` and by the samtools-compatible `stats`, +//! `flagstat` and `idxstats` writers. It needs no annotation and no library +//! protocol, so both the `rna` and `dna` pipelines drive the same struct: each +//! parallel worker owns one, and they are merged before conversion. + +use std::collections::HashMap; + +use rust_htslib::bam; + +use crate::common::bam_flags::*; +use crate::common::bam_stat::{BamStatResult, GcDepthBin}; + +/// Default GC-depth bin size in base pairs (matches upstream samtools default). +const GCD_BIN_SIZE: u64 = 20_000; + +// =================================================================== +// Merge helpers for Vec<[u64; N]> per-cycle arrays +// =================================================================== + +/// Merge two `Vec<[u64; N]>` arrays element-wise, extending target if shorter. +fn merge_vec_arrays(target: &mut Vec<[u64; N]>, source: Vec<[u64; N]>) { + if source.len() > target.len() { + target.resize(source.len(), [0u64; N]); + } + for (i, arr) in source.into_iter().enumerate() { + for j in 0..N { + target[i][j] += arr[j]; + } + } +} + +/// bam_stat accumulator — simple flag/MAPQ counting. +/// +/// Also collects the additional counters needed for samtools-compatible +/// flagstat, idxstats, and stats output. +#[derive(Debug)] +pub struct BamStatAccum { + // --- RSeQC bam_stat fields (original) --- + /// Total BAM records seen (primary + secondary + supplementary + unmapped). + pub total_records: u64, + /// Records with QC-fail flag (0x200). + pub qc_failed: u64, + /// Records with duplicate flag (0x400). + pub duplicates: u64, + /// Secondary alignment records (0x100). RSeQC calls these "non-primary". + pub non_primary: u64, + /// Unmapped reads (0x4). + pub unmapped: u64, + /// Mapped reads with MAPQ < cutoff. + pub non_unique: u64, + /// Mapped reads with MAPQ >= cutoff (uniquely mapped). + pub unique: u64, + /// Among unique reads: read1 in a pair. + pub read_1: u64, + /// Among unique reads: read2 in a pair. + pub read_2: u64, + /// Among unique reads: forward strand. + pub forward: u64, + /// Among unique reads: reverse strand. + pub reverse: u64, + /// Among unique reads: has splice junction (CIGAR N). + pub splice: u64, + /// Among unique reads: no splice junctions. + pub non_splice: u64, + /// Among unique reads: in proper pairs (0x2). + pub proper_pairs: u64, + /// Among proper-paired unique reads: mates on different chromosomes. + pub proper_pair_diff_chrom: u64, + + // --- samtools flagstat additional fields --- + /// Secondary alignments (0x100) — counted independently of QC/dup. + pub secondary: u64, + /// Supplementary alignments (0x800) — counted independently of QC/dup. + pub supplementary: u64, + /// All mapped records (not 0x4), regardless of QC/dup. + pub mapped: u64, + /// Paired reads (0x1), regardless of QC/dup. + pub paired_flagstat: u64, + /// Read1 in pair (0x40), regardless of QC/dup — for flagstat. + pub read1_flagstat: u64, + /// Read2 in pair (0x80), regardless of QC/dup — for flagstat. + pub read2_flagstat: u64, + /// First fragments for samtools stats: primary reads that are not "last fragments". + pub first_fragments: u64, + /// Last fragments for samtools stats: primary reads with 0x80 flag. + pub last_fragments: u64, + /// Properly paired reads (0x1 + 0x2), regardless of QC/dup. + pub properly_paired: u64, + /// Both mates mapped (paired + both !unmapped). + pub both_mapped: u64, + /// Singletons (paired, this mapped, mate unmapped). + pub singletons: u64, + /// Paired, both mapped, different reference. + pub mate_diff_chr: u64, + /// Paired, both mapped, different reference, MAPQ >= 5. + pub mate_diff_chr_mapq5: u64, + + // --- samtools idxstats additional fields --- + /// Per-reference (tid) mapped and unmapped counts. + pub chrom_counts: HashMap, + /// Unmapped reads with no reference (tid < 0). + pub unplaced_unmapped: u64, + + // --- samtools stats SN additional fields --- + /// Sum of query sequence lengths for all primary reads (non-secondary, non-supplementary). + pub total_len: u64, + /// Sum of first fragment (read1 or unpaired) sequence lengths. + pub total_first_fragment_len: u64, + /// Sum of last fragment (read2) sequence lengths. + pub total_last_fragment_len: u64, + /// Sum of query lengths for mapped primary reads. + pub bases_mapped: u64, + /// Sum of M/=/X CIGAR operations for mapped primary reads. + pub bases_mapped_cigar: u64, + /// Sum of query lengths for duplicate-flagged primary reads. + pub bases_duplicated: u64, + /// Maximum query sequence length (among primary reads). + pub max_len: u64, + /// Maximum first-fragment sequence length. + pub max_first_fragment_len: u64, + /// Maximum last-fragment sequence length. + pub max_last_fragment_len: u64, + /// Sum of average per-read base qualities (for average-of-averages). + pub quality_sum: f64, + /// Number of reads contributing to quality_sum (primary, non-QC-fail). + pub quality_count: u64, + /// Sum of NM tag values across mapped primary reads. + pub mismatches: u64, + /// Insert size with orientation: abs_tlen → [total, inward, outward, other]. + /// Only one mate per pair contributes (upstream mate), capped at 8000. + pub is_hist: HashMap, + /// Inward-oriented pairs (FR). + pub inward_pairs: u64, + /// Outward-oriented pairs (RF). + pub outward_pairs: u64, + /// Other orientation pairs (FF, RR). + pub other_orientation: u64, + /// Total primary reads (non-secondary, non-supplementary). + pub primary_count: u64, + /// Primary mapped reads count (non-secondary, non-supplementary, !unmapped). + pub primary_mapped: u64, + /// Primary duplicate reads. + pub primary_duplicates: u64, + /// Primary mapped reads with MAPQ = 0 (matching upstream samtools stats). + pub reads_mq0: u64, + /// Primary non-QC-fail mapped paired reads where mate is also mapped. + pub reads_mapped_and_paired: u64, + + // --- samtools stats histogram/distribution fields --- + /// Read length histogram (all primary reads): length → count. + pub rl_hist: HashMap, + /// First fragment read length histogram: length → count. + pub frl_hist: HashMap, + /// Last fragment read length histogram: length → count. + pub lrl_hist: HashMap, + /// MAPQ histogram: primary, mapped, !qcfail, !dup (quality 0-255). + pub mapq_hist: [u64; 256], + /// Per-cycle quality for first fragments (primary, mapped, !qcfail, !dup). + /// Outer: cycle index. Inner: quality value → count (64 buckets covers Q0-Q63). + pub ffq: Vec<[u64; 64]>, + /// Per-cycle quality for last fragments. + pub lfq: Vec<[u64; 64]>, + /// GC content step-function for first fragments, 200 bins (matching samtools ngc=200). + /// Each bin i stores the number of reads with gc_count * 199 / seq_len <= i. + pub gcf: [u64; 200], + /// GC content step-function for last fragments, 200 bins. + pub gcl: [u64; 200], + /// Per-cycle base composition for first fragments (primary, mapped, !qcfail, !dup). + /// [A, C, G, T, N, Other] per cycle. + pub fbc: Vec<[u64; 6]>, + /// Per-cycle base composition for last fragments. + pub lbc: Vec<[u64; 6]>, + /// Per-cycle base composition (read-oriented) for first fragments. + /// Reverse strand reads contribute in reversed cycle order. + pub fbc_ro: Vec<[u64; 6]>, + /// Per-cycle base composition (read-oriented) for last fragments. + pub lbc_ro: Vec<[u64; 6]>, + /// Per-cycle base composition (reverse-complemented for reverse-strand reads, + /// combined first+last fragments). Used for GCT output. [A, C, G, T] only. + pub gcc_rc: Vec<[u64; 4]>, + /// Total base counters for first fragments: [A, C, G, T, N]. + pub ftc: [u64; 5], + /// Total base counters for last fragments: [A, C, G, T, N]. + pub ltc: [u64; 5], + /// Indel distribution by size: length → [insertions, deletions]. + pub id_hist: HashMap, + /// Indels per cycle: cycle → [ins_fwd, ins_rev, del_fwd, del_rev]. + pub ic: Vec<[u64; 4]>, + /// CRC32 checksum sums: [names, sequences, qualities]. + /// Each is the wrapping u32 sum of per-read CRC32 values. + pub chk: [u32; 3], + /// Coverage distribution: depth → number of reference positions at that depth. + /// Populated from a round buffer pileup during sorted BAM processing. + pub cov_hist: HashMap, + /// Circular buffer for coverage pileup, matching upstream samtools design. + /// `cov_buf[cov_buf_idx]` corresponds to reference position `cov_buf_pos`. + /// The buffer grows dynamically to accommodate `max_read_length * 5`. + cov_buf: Vec, + /// Index into `cov_buf` corresponding to `cov_buf_pos`. + cov_buf_idx: usize, + /// Reference position of the element at `cov_buf[cov_buf_idx]`. + cov_buf_pos: i64, + /// Current chromosome tid for round buffer tracking. + cov_buf_tid: i32, + + // --- GC-depth (GCD section) fields --- + /// Accumulated GC-depth bins (one per `GCD_BIN_SIZE`-bp genomic window). + gcd_bins: Vec, + /// Start position of the current GCD bin. + gcd_pos: i64, + /// Chromosome tid of the current GCD bin. + gcd_tid: i32, +} + +impl Default for BamStatAccum { + fn default() -> Self { + Self { + total_records: 0, + qc_failed: 0, + duplicates: 0, + non_primary: 0, + unmapped: 0, + non_unique: 0, + unique: 0, + read_1: 0, + read_2: 0, + forward: 0, + reverse: 0, + splice: 0, + non_splice: 0, + proper_pairs: 0, + proper_pair_diff_chrom: 0, + secondary: 0, + supplementary: 0, + mapped: 0, + paired_flagstat: 0, + read1_flagstat: 0, + read2_flagstat: 0, + first_fragments: 0, + last_fragments: 0, + properly_paired: 0, + both_mapped: 0, + singletons: 0, + mate_diff_chr: 0, + mate_diff_chr_mapq5: 0, + chrom_counts: HashMap::new(), + unplaced_unmapped: 0, + total_len: 0, + total_first_fragment_len: 0, + total_last_fragment_len: 0, + bases_mapped: 0, + bases_mapped_cigar: 0, + bases_duplicated: 0, + max_len: 0, + max_first_fragment_len: 0, + max_last_fragment_len: 0, + quality_sum: 0.0, + quality_count: 0, + mismatches: 0, + is_hist: HashMap::new(), + inward_pairs: 0, + outward_pairs: 0, + other_orientation: 0, + primary_count: 0, + primary_mapped: 0, + primary_duplicates: 0, + reads_mq0: 0, + reads_mapped_and_paired: 0, + rl_hist: HashMap::new(), + frl_hist: HashMap::new(), + lrl_hist: HashMap::new(), + mapq_hist: [0u64; 256], + ffq: Vec::new(), + lfq: Vec::new(), + gcf: [0u64; 200], + gcl: [0u64; 200], + fbc: Vec::new(), + lbc: Vec::new(), + fbc_ro: Vec::new(), + lbc_ro: Vec::new(), + gcc_rc: Vec::new(), + ftc: [0u64; 5], + ltc: [0u64; 5], + id_hist: HashMap::new(), + ic: Vec::new(), + chk: [0u32; 3], + cov_hist: HashMap::new(), + cov_buf: vec![0u32; 1500], // matches upstream samtools: nbases * 5 = 300 * 5 + cov_buf_idx: 0, + cov_buf_pos: 0, + cov_buf_tid: -1, + gcd_bins: Vec::new(), + gcd_pos: -1, + gcd_tid: -1, + } + } +} + +impl BamStatAccum { + /// Process a single BAM record. Called for EVERY record (before counting filters). + /// + /// Collects counters for: + /// - RSeQC bam_stat (original cascade with early returns) + /// - samtools flagstat (counts all records independently) + /// - samtools idxstats (per-reference mapped/unmapped counts) + /// - samtools stats SN section (sequence lengths, quality, insert size, etc.) + pub fn process_read(&mut self, record: &bam::Record, mapq_cut: u8) { + let flags = record.flags(); + self.total_records += 1; + + let is_secondary = flags & BAM_FSECONDARY != 0; + let is_supplementary = flags & BAM_FSUPPLEMENTARY != 0; + let is_unmapped = flags & BAM_FUNMAP != 0; + let is_paired = flags & BAM_FPAIRED != 0; + let is_dup = flags & BAM_FDUP != 0; + let is_qcfail = flags & BAM_FQCFAIL != 0; + let is_primary = !is_secondary && !is_supplementary; + let is_mapped = !is_unmapped; + let tid = record.tid(); + let mapq = record.mapq(); + + // ================================================================= + // samtools flagstat counters (count ALL records, no early returns) + // ================================================================= + if is_secondary { + self.secondary += 1; + } + if is_supplementary { + self.supplementary += 1; + } + if is_mapped { + self.mapped += 1; + } + // samtools stats: "1st fragments" / "last fragments" count primary reads only + // For paired reads: read2 flag -> last, everything else -> 1st + // For SE reads (no PAIRED flag): all counted as 1st fragments + if is_primary { + if flags & BAM_FREAD2 != 0 { + self.last_fragments += 1; + } else { + self.first_fragments += 1; + } + } + // samtools flagstat: paired-read metrics count PRIMARY reads only + // (secondary/supplementary are excluded from paired/read1/read2/properly-paired counts) + if is_paired && is_primary { + self.paired_flagstat += 1; + if flags & BAM_FREAD1 != 0 { + self.read1_flagstat += 1; + } + if flags & BAM_FREAD2 != 0 { + self.read2_flagstat += 1; + } + if flags & BAM_FPROPER_PAIR != 0 { + self.properly_paired += 1; + } + let mate_unmapped = flags & BAM_FMUNMAP != 0; + if is_mapped && !mate_unmapped { + self.both_mapped += 1; + if tid != record.mtid() { + self.mate_diff_chr += 1; + if mapq >= 5 { + self.mate_diff_chr_mapq5 += 1; + } + } + } + if is_mapped && mate_unmapped { + self.singletons += 1; + } + } + + // ================================================================= + // samtools idxstats counters (per-reference) + // ================================================================= + if is_unmapped { + if tid >= 0 { + // Unmapped read placed on a reference (has tid) + self.chrom_counts.entry(tid).or_insert((0, 0)).1 += 1; + } else { + self.unplaced_unmapped += 1; + } + } else if tid >= 0 { + // Mapped read + self.chrom_counts.entry(tid).or_insert((0, 0)).0 += 1; + } + + // ================================================================= + // CHK checksums: computed on ALL reads (including secondary and + // supplementary). Matches samtools stats.c update_checksum() which + // is called before the secondary-read early return. + // ================================================================= + { + let qname = record.qname(); + let name_crc = crc32fast::hash(qname); + self.chk[0] = self.chk[0].wrapping_add(name_crc); + + let seq_len = record.seq_len(); + if seq_len > 0 { + // SAFETY: We access the raw BAM record data to compute CRC32 + // checksums matching samtools' approach. The pointer arithmetic + // replicates htslib's bam_get_seq() macro: + // data + l_qname + (n_cigar << 2) + // The seq_len > 0 guard above ensures sequence data exists. + // The slice length seq_len.div_ceil(2) matches the BAM spec's + // 4-bit encoded sequence format: (seq_len+1)/2 bytes. + let seq_bytes = unsafe { + let inner = record.inner(); + let data = inner.data; + let seq_offset = + inner.core.l_qname as isize + ((inner.core.n_cigar as isize) << 2); + let seq_nbytes = seq_len.div_ceil(2); + std::slice::from_raw_parts(data.offset(seq_offset), seq_nbytes) + }; + let seq_crc = crc32fast::hash(seq_bytes); + self.chk[1] = self.chk[1].wrapping_add(seq_crc); + + let qual = record.qual(); + let qual_crc = crc32fast::hash(qual); + self.chk[2] = self.chk[2].wrapping_add(qual_crc); + } + } + + // Track gc_count from the primary-read per-cycle loop so the GCD + // section below can reuse it without re-scanning the sequence. + let mut primary_gc_count: u64 = 0; + + // ================================================================= + // samtools stats SN counters (primary reads only) + // ================================================================= + if is_primary { + self.primary_count += 1; + let seq_len = record.seq_len() as u64; + let mate_unmapped = flags & BAM_FMUNMAP != 0; + + self.total_len += seq_len; + let is_last_fragment = is_paired && flags & BAM_FREAD2 != 0; + if is_last_fragment { + self.total_last_fragment_len += seq_len; + if seq_len > self.max_last_fragment_len { + self.max_last_fragment_len = seq_len; + } + } else { + self.total_first_fragment_len += seq_len; + if seq_len > self.max_first_fragment_len { + self.max_first_fragment_len = seq_len; + } + } + if seq_len > self.max_len { + self.max_len = seq_len; + } + + // RL/FRL/LRL: read length histograms (all primary reads) + *self.rl_hist.entry(seq_len).or_insert(0) += 1; + if is_last_fragment { + *self.lrl_hist.entry(seq_len).or_insert(0) += 1; + } else { + *self.frl_hist.entry(seq_len).or_insert(0) += 1; + } + + if is_dup { + self.primary_duplicates += 1; + self.bases_duplicated += seq_len; + } + // "reads mapped and paired" for samtools stats: primary, non-QC-fail, + // mapped, paired, mate also mapped + if is_mapped && is_paired && !is_qcfail && !mate_unmapped { + self.reads_mapped_and_paired += 1; + } + if is_mapped { + self.primary_mapped += 1; + self.bases_mapped += seq_len; + + // samtools stats: reads MQ0 counts primary mapped reads with MAPQ=0 + // (upstream stats.c: MQ0 is counted inside collect_orig_read_stats, + // which is only called for IS_ORIGINAL reads = non-secondary, non-supplementary) + if record.mapq() == 0 { + self.reads_mq0 += 1; + } + + // NOTE: bases_mapped_cigar is now computed in the IC/ID CIGAR + // loop below (for all mapped non-secondary reads) to avoid a + // separate full CIGAR traversal here. + + // NM tag (edit distance) + if let Ok(rust_htslib::bam::record::Aux::U8(nm)) = record.aux(b"NM") { + self.mismatches += u64::from(nm); + } else if let Ok(rust_htslib::bam::record::Aux::U16(nm)) = record.aux(b"NM") { + self.mismatches += u64::from(nm); + } else if let Ok(rust_htslib::bam::record::Aux::U32(nm)) = record.aux(b"NM") { + self.mismatches += u64::from(nm); + } else if let Ok(rust_htslib::bam::record::Aux::I8(nm)) = record.aux(b"NM") { + if nm > 0 { + self.mismatches += nm as u64; + } + } else if let Ok(rust_htslib::bam::record::Aux::I16(nm)) = record.aux(b"NM") { + if nm > 0 { + self.mismatches += nm as u64; + } + } else if let Ok(rust_htslib::bam::record::Aux::I32(nm)) = record.aux(b"NM") { + if nm > 0 { + self.mismatches += nm as u64; + } + } + + // Insert size + orientation for paired primary reads where both + // mates are mapped. Matches samtools stats gate: + // IS_PAIRED_AND_MAPPED && IS_ORIGINAL + // if (isize > 0 || tid == mtid) + // Both mates contribute; samtools divides by 2 at output. + // We do the same in write_insert_size() and the SN section. + if is_paired && !mate_unmapped { + let tid = record.tid(); + let mtid = record.mtid(); + let tlen = record.insert_size(); + let abs_tlen = tlen.unsigned_abs(); + + if abs_tlen > 0 || tid == mtid { + let pos = record.pos(); + let mpos = record.mpos(); + + // Compute orientation (only meaningful for same-chromosome) + let pos_fst = mpos - pos; + let is_fst: i64 = if flags & BAM_FREAD1 != 0 { 1 } else { -1 }; + let is_fwd: i64 = if flags & BAM_FREVERSE != 0 { -1 } else { 1 }; + let is_mfwd: i64 = if flags & BAM_FMREVERSE != 0 { -1 } else { 1 }; + + // orientation_idx: 1=inward, 2=outward, 3=other + let orientation_idx = if is_fwd * is_mfwd > 0 { + self.other_orientation += 1; + 3usize + } else if is_fst * pos_fst > 0 { + if is_fst * is_fwd > 0 { + self.inward_pairs += 1; + 1usize + } else { + self.outward_pairs += 1; + 2usize + } + } else if is_fst * pos_fst < 0 { + if is_fst * is_fwd > 0 { + self.outward_pairs += 1; + 2usize + } else { + self.inward_pairs += 1; + 1usize + } + } else { + self.inward_pairs += 1; + 1usize + }; + + if abs_tlen > 0 { + // Cap at MAX_INSERT_SIZE (8000), matching + // samtools stats which accumulates overflow + // into the cap bucket. + let capped = abs_tlen.min(8000); + let entry = self.is_hist.entry(capped).or_insert([0; 4]); + entry[0] += 1; // total + entry[orientation_idx] += 1; + } + } + } + } + + // Average quality for primary non-QC-fail reads. + // Upstream samtools stats computes per-BASE quality average: + // sum of all individual base qualities / total bases. + // (Not a per-read average of averages.) + if !is_qcfail { + let quals = record.qual(); + if !quals.is_empty() { + let base_qual_sum: f64 = quals.iter().map(|&q| f64::from(q)).sum::(); + self.quality_sum += base_qual_sum; + self.quality_count += quals.len() as u64; + } + } + + // ============================================================= + // MAPQ histogram: primary + mapped + !qcfail + !dup + // (matches samtools stats.c:1239 five-flag exclusion) + // ============================================================= + if is_mapped && !is_qcfail && !is_dup { + self.mapq_hist[mapq as usize] += 1; + } + + // ============================================================= + // Per-cycle quality & base composition histograms: + // FFQ/LFQ, FBC/LBC, GCF/GCL, FTC/LTC, FBC_RO/LBC_RO + // + // Upstream samtools stats includes duplicates, unmapped, and + // qcfail reads in these histograms (collect_orig_read_stats + // has no such checks). Only secondary+supplementary are + // excluded (via IS_ORIGINAL), which is already handled by + // the outer is_primary guard. + // ============================================================= + { + let is_reverse = flags & BAM_FREVERSE != 0; + + let seq = record.seq(); + let quals = record.qual(); + let read_len = seq.len(); + + // Determine which arrays to use (first vs last fragment) + // If paired: read2 = last, read1 = first. If SE: all = first. + let (qual_arr, base_arr, base_ro_arr, gc_arr, tc_arr) = if is_last_fragment { + ( + &mut self.lfq, + &mut self.lbc, + &mut self.lbc_ro, + &mut self.gcl, + &mut self.ltc, + ) + } else { + ( + &mut self.ffq, + &mut self.fbc, + &mut self.fbc_ro, + &mut self.gcf, + &mut self.ftc, + ) + }; + + // Ensure per-cycle arrays are large enough + if read_len > qual_arr.len() { + qual_arr.resize(read_len, [0u64; 64]); + } + if read_len > base_arr.len() { + base_arr.resize(read_len, [0u64; 6]); + } + if read_len > base_ro_arr.len() { + base_ro_arr.resize(read_len, [0u64; 6]); + } + if read_len > self.gcc_rc.len() { + self.gcc_rc.resize(read_len, [0u64; 4]); + } + + let mut gc_count: u64 = 0; + + // Pre-built lookup tables for the per-cycle inner loop, + // avoiding branches and match overhead on every base. + // + // BAM 4-bit encoding: A=1, C=2, G=4, T=8, N=15, others=0,3,5..14 + // BASE_IDX[nibble] → 0=A, 1=C, 2=G, 3=T, 4=N, 5=Other + const BASE_IDX: [u8; 16] = [5, 0, 1, 5, 2, 5, 5, 5, 3, 5, 5, 5, 5, 5, 5, 4]; + // RC_IDX[base_idx] → reverse-complement base_idx (A↔T, C↔G) + // Only meaningful for base_idx 0-3 (ACGT). Index 4/5 not used. + const RC_IDX: [u8; 6] = [3, 2, 1, 0, 4, 5]; // A→T, C→G, G→C, T→A + + // Hoist the is_reverse branch outside the inner loop so the + // compiler can version the loop and potentially auto-vectorize + // each variant independently. + if !is_reverse { + for i in 0..read_len { + let q = quals[i] as usize; + qual_arr[i][q.min(63)] += 1; + + let base_idx = BASE_IDX[seq.encoded_base(i) as usize] as usize; + base_arr[i][base_idx] += 1; + base_ro_arr[i][base_idx] += 1; + if base_idx < 4 { + self.gcc_rc[i][base_idx] += 1; + } + if base_idx == 1 || base_idx == 2 { + gc_count += 1; + } + if base_idx < 5 { + tc_arr[base_idx] += 1; + } + } + } else { + for i in 0..read_len { + let ro_cycle = read_len - 1 - i; + let q = quals[i] as usize; + qual_arr[ro_cycle][q.min(63)] += 1; + + let base_idx = BASE_IDX[seq.encoded_base(i) as usize] as usize; + base_arr[i][base_idx] += 1; + base_ro_arr[ro_cycle][base_idx] += 1; + if base_idx < 4 { + self.gcc_rc[ro_cycle][RC_IDX[base_idx] as usize] += 1; + } + if base_idx == 1 || base_idx == 2 { + gc_count += 1; + } + if base_idx < 5 { + tc_arr[base_idx] += 1; + } + } + } + + // Save gc_count for GCD section below (avoids re-scanning the sequence). + primary_gc_count = gc_count; + + // GC content: cumulative step function with ngc=200 bins. + // Matches samtools stats.c:925-941. For a read with gc_count G/C + // bases out of read_len total, increment bins gc_idx_min..gc_idx_max. + let ngc: usize = 200; + if let (Some(gc_idx_min), Some(gc_idx_max)) = ( + (gc_count as usize * (ngc - 1)).checked_div(read_len), + ((gc_count as usize + 1) * (ngc - 1)).checked_div(read_len), + ) { + let gc_idx_max = gc_idx_max.min(ngc - 1); + for item in gc_arr.iter_mut().take(gc_idx_max).skip(gc_idx_min) { + *item += 1; + } + } + } + } // if is_primary + + // ============================================================= + // Indel distribution (ID) and indels per cycle (IC) from CIGAR. + // + // Upstream samtools stats calls count_indels() AFTER the + // secondary-read early return (line 1206-1210) and the + // IS_UNMAPPED return (line 1255), but OUTSIDE IS_ORIGINAL(). + // This means: all mapped, non-secondary reads are included + // (supplementary, duplicate, qcfail all contribute). + // + // IC uses first-fragment/last-fragment read order (not + // forward/reverse strand) and read-oriented cycle indices, + // matching upstream count_indels(). + // ============================================================= + // ============================================================= + // Combined single-CIGAR-pass block for IC/ID (indel distribution), + // bases_mapped_cigar, and COV (coverage ring-buffer pileup). + // + // Both IC/ID and COV apply to the same read set (mapped, + // non-secondary). Merging them into one CIGAR traversal + // eliminates two redundant record.cigar() calls per read. + // + // IC/ID: Upstream samtools stats calls count_indels() outside + // IS_ORIGINAL() — supplementary/dup/qcfail all contribute. + // IC uses first/last-fragment order and read-oriented cycles. + // + // COV: Circular-buffer pileup; buffer flushed up to read start + // before CIGAR walk; M/=/X blocks inserted as ranges. + // Buffer grown to max_read_len * 5 as needed. + // ============================================================= + if is_mapped && !is_secondary { + use rust_htslib::bam::record::Cigar as C; + let is_reverse = flags & BAM_FREVERSE != 0; + let read_len = record.seq_len(); + let tid = record.tid(); + let pos = record.pos(); // 0-based + + // Upstream order: paired ? (read1?FIRST:0)+(read2?LAST:0) : FIRST + let order: u32 = if is_paired { + (if flags & BAM_FREAD1 != 0 { 1 } else { 0 }) + + (if flags & BAM_FREAD2 != 0 { 2 } else { 0 }) + } else { + 1 // unpaired → FIRST + }; + + // COV buffer setup (must happen before CIGAR walk). + // Skip reads with no sequence (upstream samtools early-return). + let do_cov = read_len > 0; + let buf_size = if do_cov { + // Grow buffer to max_read_len * 5 if needed. + // When growing, linearise the circular data just like + // upstream samtools: copy [idx..old_size] then [0..idx] + // into a fresh buffer, and reset idx to 0. + let need = read_len * 5; + if need > self.cov_buf.len() { + let old_size = self.cov_buf.len(); + let mut new_buf = vec![0u32; need]; + let head = old_size - self.cov_buf_idx; + new_buf[..head].copy_from_slice(&self.cov_buf[self.cov_buf_idx..]); + new_buf[head..head + self.cov_buf_idx] + .copy_from_slice(&self.cov_buf[..self.cov_buf_idx]); + self.cov_buf = new_buf; + self.cov_buf_idx = 0; + } + let bs = self.cov_buf.len(); + // Flush entire buffer on chromosome change + if tid != self.cov_buf_tid { + self.flush_cov_buf_all(); + self.cov_buf_tid = tid; + self.cov_buf_pos = pos; + self.cov_buf_idx = 0; + } + // Flush positions from cov_buf_pos up to read start + self.cov_buf_flush_to(pos, bs); + bs + } else { + 0 + }; + + // Single CIGAR traversal serving IC/ID + bases_mapped_cigar + COV + let cigar = record.cigar(); + let mut icycle: usize = 0; + let mut cigar_mapped: u64 = 0; + let mut ref_pos = pos; + + for op in cigar.iter() { + match op { + C::Ins(n) => { + let ncig = *n as usize; + let len = *n as u64; + cigar_mapped += len; // I counts toward bases_mapped_cigar + + // ID: indel size distribution + let id_entry = self.id_hist.entry(len).or_insert([0; 2]); + id_entry[0] += 1; // insertions + + // IC: indels per cycle (read-oriented index) + let idx = if is_reverse { + read_len.saturating_sub(icycle + ncig) + } else { + icycle + }; + if idx >= self.ic.len() { + self.ic.resize(idx + 1, [0u64; 4]); + } + if order == 1 { + self.ic[idx][0] += 1; // ins_1st + } + if order == 2 { + self.ic[idx][1] += 1; // ins_2nd + } + + icycle += ncig; // I advances query cycle; ref unchanged + // COV: I consumes no reference positions + } + C::Del(n) => { + let len = *n as u64; + // ID: indel size distribution + let id_entry = self.id_hist.entry(len).or_insert([0; 2]); + id_entry[1] += 1; // deletions + + // IC: indels per cycle (read-oriented index) + let idx = if is_reverse { + if icycle == 0 { + // Discard meaningless deletions at cycle 0 + // (upstream: "if (idx<0) continue;") + ref_pos += *n as i64; // still advance ref for COV + continue; + } + read_len.saturating_sub(icycle + 1) + } else { + if icycle == 0 { + ref_pos += *n as i64; + continue; + } + icycle - 1 + }; + if idx >= self.ic.len() { + self.ic.resize(idx + 1, [0u64; 4]); + } + if order == 1 { + self.ic[idx][2] += 1; // del_1st + } + if order == 2 { + self.ic[idx][3] += 1; // del_2nd + } + // D does NOT advance query cycle; does advance ref + ref_pos += *n as i64; + } + C::Match(n) | C::Equal(n) | C::Diff(n) => { + let len = *n as u64; + cigar_mapped += len; // M/=/X count toward bases_mapped_cigar + icycle += *n as usize; + // COV: M/=/X consumes reference positions + if do_cov { + let end = ref_pos + *n as i64; + self.cov_buf_insert(ref_pos, end, buf_size); + ref_pos = end; + } else { + ref_pos += *n as i64; + } + } + C::RefSkip(n) => { + ref_pos += *n as i64; // N advances ref (COV skips it) + } + C::SoftClip(n) => { + icycle += *n as usize; // S advances query cycle + // COV: S consumes no reference positions + } + C::HardClip(_) | C::Pad(_) => {} + } + } + self.bases_mapped_cigar += cigar_mapped; + } // if is_mapped && !is_secondary (IC/ID + COV combined) + + // ============================================================= + // GCD: GC-depth accumulation (no-reference path). + // + // Matches upstream samtools stats without --ref-seq: bins of + // GCD_BIN_SIZE bp, depth incremented for each read, GC fraction + // accumulated from the read's sequence. + // + // Included reads: mapped, non-secondary (same as COV). + // + // NOTE: gc_count_for_gcd is set from the primary-read per-cycle + // loop above (when is_primary is true), or computed here only for + // non-primary mapped reads, avoiding a redundant full sequence scan. + // ============================================================= + if is_mapped && !is_secondary { + let tid = record.tid(); + let pos = record.pos(); + let seq_len = record.seq_len(); + + if seq_len > 0 { + // Start a new bin on: first read, chromosome change, or + // read beyond current bin boundary. + let new_bin = self.gcd_pos < 0 + || tid != self.gcd_tid + || pos - self.gcd_pos > GCD_BIN_SIZE as i64; + + if new_bin { + self.gcd_bins.push(GcDepthBin { gc: 0.0, depth: 0 }); + self.gcd_pos = pos; + self.gcd_tid = tid; + } + + // Increment depth and accumulate GC fraction from read seq. + if let Some(bin) = self.gcd_bins.last_mut() { + bin.depth += 1; + // For primary reads, gc_count was already computed in the + // per-cycle base loop above. For non-primary mapped reads + // (supplementary, etc.) compute it here from the sequence. + let gc_count: u32 = if is_primary { + primary_gc_count as u32 + } else { + let seq = record.seq(); + let mut count: u32 = 0; + for i in 0..seq_len { + let base = seq.encoded_base(i); + if base == 2 || base == 4 { + count += 1; + } + } + count + }; + bin.gc += gc_count as f32 / seq_len as f32; + } + } + } // if is_mapped && !is_secondary (GCD) + + // ================================================================= + // RSeQC bam_stat cascade (original logic, with early returns) + // ================================================================= + + // 1. QC-failed + if is_qcfail { + self.qc_failed += 1; + return; + } + + // 2. Duplicate + if is_dup { + self.duplicates += 1; + return; + } + + // 3. Secondary (non-primary) — NOT supplementary + if is_secondary { + self.non_primary += 1; + return; + } + + // 4. Unmapped + if is_unmapped { + self.unmapped += 1; + return; + } + + // 5. MAPQ classification + if mapq < mapq_cut { + self.non_unique += 1; + return; + } + + // Uniquely mapped + self.unique += 1; + + if flags & BAM_FREAD1 != 0 { + self.read_1 += 1; + } + if flags & BAM_FREAD2 != 0 { + self.read_2 += 1; + } + if flags & BAM_FREVERSE != 0 { + self.reverse += 1; + } else { + self.forward += 1; + } + + // Splice detection: CIGAR N operation + let has_splice = record + .cigar() + .iter() + .any(|op| matches!(op, rust_htslib::bam::record::Cigar::RefSkip(_))); + if has_splice { + self.splice += 1; + } else { + self.non_splice += 1; + } + + // Proper pair analysis + if is_paired && flags & BAM_FPROPER_PAIR != 0 { + self.proper_pairs += 1; + if tid != record.mtid() { + self.proper_pair_diff_chrom += 1; + } + } + } + + /// Flush all remaining positions in the coverage round buffer into cov_hist. + /// Must be called after processing all reads (or when switching chromosomes). + /// Flush the circular buffer from `cov_buf_pos` up to (but not including) `pos`. + /// Each slot's depth is recorded in `cov_hist` and the slot is zeroed. + /// Matches upstream `round_buffer_flush` logic from samtools stats.c. + fn cov_buf_flush_to(&mut self, pos: i64, buf_size: usize) { + if pos - self.cov_buf_pos >= buf_size as i64 { + // Gap exceeds buffer size. Match upstream samtools exactly: + // flush `size - 1` positions (from cov_buf_pos to + // cov_buf_pos + size - 2), leaving the LAST slot untouched. + // Then advance idx by `size - 1` and jump pos. + // + // Upstream (stats.c round_buffer_flush lines 334-366): + // pos = rbuf.pos + size - 1; // cap at last slot + // ito = lidx2ridx(start, size, rbuf.pos, pos-1); + // // flush from start to ito (size-1 slots) + // rbuf.start = lidx2ridx(start, size, rbuf.pos, pos); + // rbuf.pos = new_pos; + let flush_count = buf_size - 1; // flush all but the last slot + for _ in 0..flush_count { + let depth = self.cov_buf[self.cov_buf_idx]; + if depth > 0 { + *self.cov_hist.entry(depth).or_insert(0) += 1; + self.cov_buf[self.cov_buf_idx] = 0; + } + self.cov_buf_idx += 1; + if self.cov_buf_idx >= buf_size { + self.cov_buf_idx = 0; + } + } + // idx now points to the ONE unflushed slot (the last position + // in the old window). Jump pos to the new read position. + self.cov_buf_pos = pos; + } else { + // Normal case: flush slot by slot. + while self.cov_buf_pos < pos { + let depth = self.cov_buf[self.cov_buf_idx]; + if depth > 0 { + *self.cov_hist.entry(depth).or_insert(0) += 1; + self.cov_buf[self.cov_buf_idx] = 0; + } + self.cov_buf_idx += 1; + if self.cov_buf_idx >= buf_size { + self.cov_buf_idx = 0; + } + self.cov_buf_pos += 1; + } + } + } + + /// Insert a contiguous reference range `[from, to)` into the circular buffer, + /// incrementing depth for each position. The range must fit within `buf_size`. + fn cov_buf_insert(&mut self, from: i64, to: i64, buf_size: usize) { + for ref_pos in from..to { + // Map ref_pos to buffer index: offset from cov_buf_idx by (ref_pos - cov_buf_pos) + let offset = (ref_pos - self.cov_buf_pos) as usize; + let idx = (self.cov_buf_idx + offset) % buf_size; + self.cov_buf[idx] += 1; + } + } + + /// Flush the entire circular buffer and reset tracking state. + pub fn flush_cov_buf_all(&mut self) { + for slot in self.cov_buf.iter_mut() { + if *slot > 0 { + *self.cov_hist.entry(*slot).or_insert(0) += 1; + *slot = 0; + } + } + self.cov_buf_idx = 0; + self.cov_buf_pos = 0; + self.cov_buf_tid = -1; + } + + /// Merge another accumulator into this one. + pub fn merge(&mut self, mut other: BamStatAccum) { + // Flush any remaining positions in the other's round buffer into its + // cov_hist before merging. Without this, positions still in the + // round buffer would be silently lost during parallel merges. + other.flush_cov_buf_all(); + + // RSeQC bam_stat fields + self.total_records += other.total_records; + self.qc_failed += other.qc_failed; + self.duplicates += other.duplicates; + self.non_primary += other.non_primary; + self.unmapped += other.unmapped; + self.non_unique += other.non_unique; + self.unique += other.unique; + self.read_1 += other.read_1; + self.read_2 += other.read_2; + self.forward += other.forward; + self.reverse += other.reverse; + self.splice += other.splice; + self.non_splice += other.non_splice; + self.proper_pairs += other.proper_pairs; + self.proper_pair_diff_chrom += other.proper_pair_diff_chrom; + + // samtools flagstat fields + self.secondary += other.secondary; + self.supplementary += other.supplementary; + self.mapped += other.mapped; + self.paired_flagstat += other.paired_flagstat; + self.read1_flagstat += other.read1_flagstat; + self.read2_flagstat += other.read2_flagstat; + self.first_fragments += other.first_fragments; + self.last_fragments += other.last_fragments; + self.properly_paired += other.properly_paired; + self.both_mapped += other.both_mapped; + self.singletons += other.singletons; + self.mate_diff_chr += other.mate_diff_chr; + self.mate_diff_chr_mapq5 += other.mate_diff_chr_mapq5; + + // samtools idxstats fields + for (tid, (m, u)) in other.chrom_counts { + let entry = self.chrom_counts.entry(tid).or_insert((0, 0)); + entry.0 += m; + entry.1 += u; + } + self.unplaced_unmapped += other.unplaced_unmapped; + + // samtools stats SN fields + self.total_len += other.total_len; + self.total_first_fragment_len += other.total_first_fragment_len; + self.total_last_fragment_len += other.total_last_fragment_len; + self.bases_mapped += other.bases_mapped; + self.bases_mapped_cigar += other.bases_mapped_cigar; + self.bases_duplicated += other.bases_duplicated; + if other.max_len > self.max_len { + self.max_len = other.max_len; + } + if other.max_first_fragment_len > self.max_first_fragment_len { + self.max_first_fragment_len = other.max_first_fragment_len; + } + if other.max_last_fragment_len > self.max_last_fragment_len { + self.max_last_fragment_len = other.max_last_fragment_len; + } + self.quality_sum += other.quality_sum; + self.quality_count += other.quality_count; + self.mismatches += other.mismatches; + for (isize_val, counts) in other.is_hist { + let entry = self.is_hist.entry(isize_val).or_insert([0; 4]); + for i in 0..4 { + entry[i] += counts[i]; + } + } + self.inward_pairs += other.inward_pairs; + self.outward_pairs += other.outward_pairs; + self.other_orientation += other.other_orientation; + self.primary_count += other.primary_count; + self.primary_mapped += other.primary_mapped; + self.primary_duplicates += other.primary_duplicates; + self.reads_mq0 += other.reads_mq0; + self.reads_mapped_and_paired += other.reads_mapped_and_paired; + + // Histogram/distribution fields + for (len, count) in other.rl_hist { + *self.rl_hist.entry(len).or_insert(0) += count; + } + for (len, count) in other.frl_hist { + *self.frl_hist.entry(len).or_insert(0) += count; + } + for (len, count) in other.lrl_hist { + *self.lrl_hist.entry(len).or_insert(0) += count; + } + for i in 0..256 { + self.mapq_hist[i] += other.mapq_hist[i]; + } + + // Per-cycle quality arrays (FFQ/LFQ) + merge_vec_arrays(&mut self.ffq, other.ffq); + merge_vec_arrays(&mut self.lfq, other.lfq); + + // GC content distributions (200 bins) + for i in 0..200 { + self.gcf[i] += other.gcf[i]; + self.gcl[i] += other.gcl[i]; + } + + // Per-cycle base composition (FBC/LBC and read-oriented) + merge_vec_arrays(&mut self.fbc, other.fbc); + merge_vec_arrays(&mut self.lbc, other.lbc); + merge_vec_arrays(&mut self.fbc_ro, other.fbc_ro); + merge_vec_arrays(&mut self.lbc_ro, other.lbc_ro); + merge_vec_arrays(&mut self.gcc_rc, other.gcc_rc); + + // Total base counters + for i in 0..5 { + self.ftc[i] += other.ftc[i]; + self.ltc[i] += other.ltc[i]; + } + + // Indel distribution + for (len, counts) in other.id_hist { + let entry = self.id_hist.entry(len).or_insert([0; 2]); + entry[0] += counts[0]; + entry[1] += counts[1]; + } + + // Indels per cycle + merge_vec_arrays(&mut self.ic, other.ic); + + // CHK checksums (wrapping u32 addition) + for i in 0..3 { + self.chk[i] = self.chk[i].wrapping_add(other.chk[i]); + } + + // COV histogram (additive merge) + for (depth, count) in other.cov_hist { + *self.cov_hist.entry(depth).or_insert(0) += count; + } + + // GCD bins (concatenate — bins from different chromosome workers + // are independent and will be sorted during output). + self.gcd_bins.append(&mut other.gcd_bins); + } +} + +impl BamStatAccum { + /// Convert accumulated counters into a `BamStatResult` for output. + pub fn into_result(mut self) -> BamStatResult { + // Flush remaining positions in the coverage round buffer + self.flush_cov_buf_all(); + BamStatResult { + // RSeQC bam_stat fields + total_records: self.total_records, + qc_failed: self.qc_failed, + duplicates: self.duplicates, + non_primary: self.non_primary, + unmapped: self.unmapped, + non_unique: self.non_unique, + unique: self.unique, + read_1: self.read_1, + read_2: self.read_2, + forward: self.forward, + reverse: self.reverse, + splice: self.splice, + non_splice: self.non_splice, + proper_pairs: self.proper_pairs, + proper_pair_diff_chrom: self.proper_pair_diff_chrom, + // samtools flagstat fields + secondary: self.secondary, + supplementary: self.supplementary, + mapped: self.mapped, + paired_flagstat: self.paired_flagstat, + read1_flagstat: self.read1_flagstat, + read2_flagstat: self.read2_flagstat, + first_fragments: self.first_fragments, + last_fragments: self.last_fragments, + properly_paired: self.properly_paired, + both_mapped: self.both_mapped, + singletons: self.singletons, + mate_diff_chr: self.mate_diff_chr, + mate_diff_chr_mapq5: self.mate_diff_chr_mapq5, + // samtools idxstats fields + chrom_counts: self.chrom_counts, + unplaced_unmapped: self.unplaced_unmapped, + // samtools stats SN fields + total_len: self.total_len, + total_first_fragment_len: self.total_first_fragment_len, + total_last_fragment_len: self.total_last_fragment_len, + bases_mapped: self.bases_mapped, + bases_mapped_cigar: self.bases_mapped_cigar, + bases_duplicated: self.bases_duplicated, + max_len: self.max_len, + max_first_fragment_len: self.max_first_fragment_len, + max_last_fragment_len: self.max_last_fragment_len, + quality_sum: self.quality_sum, + quality_count: self.quality_count, + mismatches: self.mismatches, + is_hist: self.is_hist, + inward_pairs: self.inward_pairs, + outward_pairs: self.outward_pairs, + other_orientation: self.other_orientation, + primary_count: self.primary_count, + primary_mapped: self.primary_mapped, + primary_duplicates: self.primary_duplicates, + reads_mq0: self.reads_mq0, + reads_mapped_and_paired: self.reads_mapped_and_paired, + // Histogram/distribution fields + rl_hist: self.rl_hist, + frl_hist: self.frl_hist, + lrl_hist: self.lrl_hist, + mapq_hist: self.mapq_hist, + ffq: self.ffq, + lfq: self.lfq, + gcf: self.gcf, + gcl: self.gcl, + fbc: self.fbc, + lbc: self.lbc, + fbc_ro: self.fbc_ro, + lbc_ro: self.lbc_ro, + gcc_rc: self.gcc_rc, + ftc: self.ftc, + ltc: self.ltc, + id_hist: self.id_hist, + ic: self.ic, + chk: self.chk, + cov_hist: self.cov_hist, + gcd_bins: self.gcd_bins, + } + } +} diff --git a/src/rna/cpp_rng.rs b/src/common/cpp_rng.rs similarity index 100% rename from src/rna/cpp_rng.rs rename to src/common/cpp_rng.rs diff --git a/src/common/mod.rs b/src/common/mod.rs new file mode 100644 index 00000000..31c3a7d3 --- /dev/null +++ b/src/common/mod.rs @@ -0,0 +1,13 @@ +//! Analysis modules shared between the `rna` and `dna` pipelines. +//! +//! Nothing in this module is specific to a library preparation or an assay: +//! BAM flag helpers, the C++ RNG shim used for preseq bootstrap +//! reproducibility, the preseq `lc_extrap` implementation, read-level +//! alignment statistics, and the samtools-compatible output writers. + +pub mod bam_flags; +pub mod bam_stat; +pub mod bam_stat_accum; +pub mod cpp_rng; +pub mod preseq; +pub mod samtools; diff --git a/src/rna/preseq.rs b/src/common/preseq.rs similarity index 100% rename from src/rna/preseq.rs rename to src/common/preseq.rs diff --git a/src/rna/rseqc/flagstat.rs b/src/common/samtools/flagstat.rs similarity index 99% rename from src/rna/rseqc/flagstat.rs rename to src/common/samtools/flagstat.rs index cb1e370d..b611ae03 100644 --- a/src/rna/rseqc/flagstat.rs +++ b/src/common/samtools/flagstat.rs @@ -8,7 +8,7 @@ use std::path::Path; use anyhow::{Context, Result}; use log::debug; -use super::bam_stat::BamStatResult; +use crate::common::bam_stat::BamStatResult; // ============================================================================ // Output formatting diff --git a/src/rna/rseqc/idxstats.rs b/src/common/samtools/idxstats.rs similarity index 98% rename from src/rna/rseqc/idxstats.rs rename to src/common/samtools/idxstats.rs index 93c018fe..61f05d4f 100644 --- a/src/rna/rseqc/idxstats.rs +++ b/src/common/samtools/idxstats.rs @@ -8,7 +8,7 @@ use std::path::Path; use anyhow::{Context, Result}; use log::debug; -use super::bam_stat::BamStatResult; +use crate::common::bam_stat::BamStatResult; // ============================================================================ // Output formatting diff --git a/src/common/samtools/mod.rs b/src/common/samtools/mod.rs new file mode 100644 index 00000000..f9b1d860 --- /dev/null +++ b/src/common/samtools/mod.rs @@ -0,0 +1,10 @@ +//! samtools-compatible output writers. +//! +//! Reproduce the exact output formats of `samtools stats`, `samtools flagstat` +//! and `samtools idxstats` from the counters gathered in +//! [`crate::common::bam_stat::BamStatResult`], so that MultiQC and +//! `plot-bamstats` parse RustQC output as if samtools had produced it. + +pub mod flagstat; +pub mod idxstats; +pub mod stats; diff --git a/src/rna/rseqc/stats.rs b/src/common/samtools/stats.rs similarity index 99% rename from src/rna/rseqc/stats.rs rename to src/common/samtools/stats.rs index 20abf8c0..32f42067 100644 --- a/src/rna/rseqc/stats.rs +++ b/src/common/samtools/stats.rs @@ -10,7 +10,7 @@ use std::path::Path; use anyhow::{Context, Result}; use log::debug; -use super::bam_stat::{BamStatResult, GcDepthBin}; +use crate::common::bam_stat::{BamStatResult, GcDepthBin}; // ============================================================================ // Output formatting @@ -62,7 +62,7 @@ pub fn write_stats(result: &BamStatResult, output_path: &Path) -> Result<()> { writeln!(out, "# This file was produced by samtools stats and RustQC")?; writeln!( out, - "# The command line was: rustqc rna (samtools stats compatible output)" + "# The command line was: rustqc (samtools stats compatible output)" )?; // Derived values diff --git a/src/config.rs b/src/config.rs index 4952a1fd..28ae5517 100644 --- a/src/config.rs +++ b/src/config.rs @@ -30,6 +30,10 @@ pub struct Config { /// RNA-Seq QC configuration (matches the `rna` subcommand). #[serde(default)] pub rna: RnaConfig, + + /// DNA QC configuration (matches the `dna` subcommand). + #[serde(default)] + pub dna: DnaConfig, } /// RNA-Seq QC configuration. @@ -908,6 +912,138 @@ impl RnaConfig { } } +// =================================================================== +// DNA QC configuration +// =================================================================== + +/// DNA QC configuration. +/// +/// Contains all settings for the `rustqc dna` subcommand. Tool-specific +/// settings are nested under their tool name (e.g. `mosdepth:`, `samtools:`, +/// `preseq:`). +/// +/// The shared settings are declared here rather than inherited from the root +/// [`Config`], mirroring [`RnaConfig`], so the two pipelines can be configured +/// independently in one file. +/// +/// Example: +/// ```yaml +/// dna: +/// flat_output: true +/// mosdepth: +/// window_size: 500 +/// thresholds: [1, 10, 30] +/// ``` +#[derive(Debug, Deserialize, Default)] +#[serde(default)] +pub struct DnaConfig { + /// Prefix to prepend to alignment file chromosome names before matching + /// interval-file names (for example a targets BED using `chr1` against an + /// alignment using `1`). + #[serde(default)] + pub chromosome_prefix: Option, + + /// Chromosome name mapping from interval-file names to alignment file names. + /// + /// Applied after `chromosome_prefix`, so explicit mappings override it. + #[serde(default)] + pub chromosome_mapping: HashMap, + + /// Override the sample name used in output filenames. + /// + /// The CLI `--sample-name` flag takes precedence over this setting. + #[serde(default)] + pub sample_name: Option, + + /// Write all output files to a flat directory (no subdirectories). + /// + /// By default (`false`), outputs are organised by tool: `mosdepth/`, + /// `samtools/`, `preseq/`. The CLI `--flat-output` flag enables flat + /// output regardless of this setting (either source being `true` produces + /// flat output). + #[serde(default)] + pub flat_output: bool, + + /// mosdepth-compatible depth of coverage configuration. + #[serde(default)] + pub mosdepth: MosdepthConfig, + + /// samtools-compatible output configuration (stats, flagstat, idxstats). + #[serde(default)] + pub samtools: SamtoolsConfig, + + /// preseq lc_extrap library complexity extrapolation configuration. + /// + /// Reuses the same type as the `rna` pipeline; the implementation is shared. + #[serde(default)] + pub preseq: PreseqConfig, +} + +/// Configuration for the mosdepth-compatible depth of coverage analysis. +/// +/// Example: +/// ```yaml +/// mosdepth: +/// enabled: true +/// window_size: 500 +/// thresholds: [1, 10, 30] +/// skip_per_base: false +/// ``` +#[derive(Debug, Deserialize)] +#[serde(default)] +pub struct MosdepthConfig { + /// Whether to compute depth of coverage. Defaults to true. + pub enabled: bool, + + /// Fixed-width window size for the per-window depth output. + /// + /// `None` (the default) means no `regions` output is written, matching + /// mosdepth run without `--by`. + pub window_size: Option, + + /// Coverage thresholds reported in the thresholds output and used for the + /// percent-of-bases-at-least-NX summary figures. + pub thresholds: Vec, + + /// Skip the per-base depth output, by far the largest file produced. + pub skip_per_base: bool, +} + +impl Default for MosdepthConfig { + fn default() -> Self { + Self { + enabled: true, + window_size: None, + thresholds: vec![1, 5, 10, 15, 20, 30, 50], + skip_per_base: false, + } + } +} + +/// Configuration for the samtools-compatible outputs of the DNA pipeline. +/// +/// A single toggle covers `stats`, `flagstat` and `idxstats` because all three +/// are produced from one accumulator in the same pass; disabling them +/// individually would save no work. +/// +/// Example: +/// ```yaml +/// samtools: +/// enabled: true +/// ``` +#[derive(Debug, Deserialize)] +#[serde(default)] +pub struct SamtoolsConfig { + /// Whether to write the samtools-compatible outputs. Defaults to true. + pub enabled: bool, +} + +impl Default for SamtoolsConfig { + fn default() -> Self { + Self { enabled: true } + } +} + #[cfg(test)] mod tests { use super::*; @@ -1294,4 +1430,44 @@ preseq: std::env::set_var("RUSTQC_CONFIG", val); } } + + #[test] + fn test_dna_config_defaults() { + let config = Config::default(); + assert!(config.dna.mosdepth.enabled); + assert!(config.dna.samtools.enabled); + assert!(config.dna.preseq.enabled); + assert!(!config.dna.flat_output); + assert_eq!( + config.dna.mosdepth.thresholds, + vec![1, 5, 10, 15, 20, 30, 50] + ); + assert_eq!(config.dna.mosdepth.window_size, None); + } + + #[test] + fn test_dna_config_from_yaml() { + let yaml = "dna:\n flat_output: true\n mosdepth:\n window_size: 500\n thresholds: [1, 30]\n preseq:\n enabled: false\n"; + let config: Config = serde_yaml_ng::from_str(yaml).unwrap(); + assert!(config.dna.flat_output); + assert_eq!(config.dna.mosdepth.window_size, Some(500)); + assert_eq!(config.dna.mosdepth.thresholds, vec![1, 30]); + assert!(!config.dna.preseq.enabled); + // A dna-only config leaves the rna side untouched. + assert!(config.rna.preseq.enabled); + } + + #[test] + fn test_dna_config_deep_merge() { + let mut merged: Value = serde_yaml_ng::from_str( + "dna:\n mosdepth:\n window_size: 100\n thresholds: [1]\n", + ) + .unwrap(); + let overlay: Value = + serde_yaml_ng::from_str("dna:\n mosdepth:\n window_size: 500\n").unwrap(); + deep_merge(&mut merged, overlay); + let config: Config = serde_yaml_ng::from_value(merged).unwrap(); + assert_eq!(config.dna.mosdepth.window_size, Some(500)); + assert_eq!(config.dna.mosdepth.thresholds, vec![1]); + } } diff --git a/src/dna/depth.rs b/src/dna/depth.rs new file mode 100644 index 00000000..ce39f2c2 --- /dev/null +++ b/src/dna/depth.rs @@ -0,0 +1,444 @@ +//! Per-contig depth of coverage accumulation. +//! +//! One [`DepthAccum`] covers one contig. Aligned blocks are recorded as +//! increments in a delta array the length of the contig, and a prefix sum at +//! the end turns that into per-base depth in a single linear pass. +//! +//! # Upstream semantics +//! +//! The filters and the CIGAR walk reproduce mosdepth 0.3.14 run without +//! `--fast-mode`, whose help text describes that flag as "dont look at +//! internal cigar operations or correct mate overlaps". Default mode +//! therefore does both, and so does this module: +//! +//! - records carrying any bit of [`MOSDEPTH_DEFAULT_EXCLUDE`] are skipped +//! (mosdepth's `-F` default of 1796); +//! - records with `MAPQ` below the cutoff are skipped (mosdepth's `-Q`, +//! default 0); +//! - `M`, `=` and `X` cover the reference, `D` and `N` advance without +//! covering, and `I`, `S`, `H` and `P` do not advance at all; +//! - a base covered by both mates of one pair counts once. +//! +//! That last rule is not a detail. On the project's test dataset, correcting +//! mate overlaps takes total covered bases from 469875 down to 247878, which +//! is exactly the gap between mosdepth's `--fast-mode` and its default. + +use std::collections::{BTreeMap, HashMap}; + +use rust_htslib::bam; +use rust_htslib::bam::record::Cigar; + +use crate::common::bam_flags::*; + +/// Bit mask matching mosdepth's `-F` default: `UNMAP | SECONDARY | QCFAIL | DUP`. +pub const MOSDEPTH_DEFAULT_EXCLUDE: u16 = BAM_FUNMAP | BAM_FSECONDARY | BAM_FQCFAIL | BAM_FDUP; + +/// Accumulates per-base depth for a single contig. +#[derive(Debug)] +pub struct DepthAccum { + /// Delta array of length `contig_len + 1`; a `+1` at a block start and a + /// `-1` one past its end, summed into depth by [`DepthAccum::into_depths`]. + deltas: Vec, + /// Contig length in bases. + len: usize, + /// Records with `MAPQ` strictly below this value are ignored. + mapq_cut: u8, + /// Records carrying any of these flag bits are ignored. + exclude_flags: u16, + /// Aligned blocks already counted for a pair whose second mate is still + /// ahead, keyed by read name. + pending: HashMap, Vec<(usize, usize)>>, + /// Read names indexed by the position their outstanding mate is expected + /// at, so stale entries can be evicted without scanning `pending`. + pending_by_pos: BTreeMap>>, +} + +impl DepthAccum { + /// Allocate for one contig of `length` bases. + pub fn new(length: u64, mapq_cut: u8, exclude_flags: u16) -> Self { + let len = length as usize; + Self { + deltas: vec![0i32; len + 1], + len, + mapq_cut, + exclude_flags, + pending: HashMap::new(), + pending_by_pos: BTreeMap::new(), + } + } + + /// Add one record's aligned blocks. Records failing the filters are ignored. + /// + /// Records are expected in coordinate order, which is what the per-contig + /// worker feeds. That ordering is what makes the pending-mate bookkeeping + /// bounded: once the read position passes the position an outstanding mate + /// was announced at, that entry can never be claimed and is dropped. + pub fn process_read(&mut self, record: &bam::Record) { + if !self.passes_filters(record) { + return; + } + let pos = record.pos(); + self.evict_unclaimable(pos); + + let blocks = Self::aligned_blocks(record, self.len); + if blocks.is_empty() { + return; + } + + // A record can only overlap its own mate, and only on the same contig. + let paired_here = record.flags() & BAM_FPAIRED != 0 + && record.flags() & BAM_FMUNMAP == 0 + && record.mtid() == record.tid(); + + if paired_here { + if let Some(mate_blocks) = self.pending.remove(record.qname()) { + // Second mate of the pair: shared bases are already counted. + self.add_blocks_excluding(&blocks, &mate_blocks); + return; + } + if record.mpos() >= pos { + let qname = record.qname().to_vec(); + self.pending.insert(qname.clone(), blocks.clone()); + self.pending_by_pos + .entry(record.mpos()) + .or_default() + .push(qname); + } + } + + for &(start, end) in &blocks { + self.add_block_usize(start, end); + } + } + + /// Number of pairs still waiting for their second mate. Test-only: the + /// bookkeeping is an implementation detail, but an unbounded map would be + /// a memory leak on a real chromosome, so it is worth asserting on. + #[cfg(test)] + pub fn pending_mates_len(&self) -> usize { + self.pending.len() + } + + /// Drop pending entries whose outstanding mate lies behind `pos` and can + /// therefore never arrive (it was filtered out, or the file is truncated). + fn evict_unclaimable(&mut self, pos: i64) { + while let Some((&mate_pos, _)) = self.pending_by_pos.iter().next() { + if mate_pos >= pos { + break; + } + // Safe: the key came from `iter().next()` on this same map. + let qnames = self.pending_by_pos.remove(&mate_pos).unwrap_or_default(); + for qname in qnames { + self.pending.remove(&qname); + } + } + } + + /// The record's reference-covering blocks as half-open `[start, end)` + /// intervals, clamped to `len`. + fn aligned_blocks(record: &bam::Record, len: usize) -> Vec<(usize, usize)> { + let mut blocks = Vec::new(); + let mut pos = record.pos(); + for op in record.cigar().iter() { + match op { + // Reference-consuming and query-consuming: covers the reference. + Cigar::Match(n) | Cigar::Equal(n) | Cigar::Diff(n) => { + let n = i64::from(*n); + let start = pos.max(0) as usize; + let end = ((pos + n).max(0) as usize).min(len); + if start < end { + blocks.push((start, end)); + } + pos += n; + } + // Reference-consuming only: advances without covering. + Cigar::Del(n) | Cigar::RefSkip(n) => pos += i64::from(*n), + // Neither reference-consuming nor covering. + Cigar::Ins(_) | Cigar::SoftClip(_) | Cigar::HardClip(_) | Cigar::Pad(_) => {} + } + } + blocks + } + + /// Add `blocks`, skipping any part already covered by `exclude`. + /// + /// Both sides are in ascending order and non-overlapping within themselves, + /// because each comes from one record's CIGAR walk. + fn add_blocks_excluding(&mut self, blocks: &[(usize, usize)], exclude: &[(usize, usize)]) { + for &(start, end) in blocks { + let mut cursor = start; + for &(ex_start, ex_end) in exclude { + if ex_end <= cursor { + continue; + } + if ex_start >= end { + break; + } + if ex_start > cursor { + self.add_block_usize(cursor, ex_start.min(end)); + } + cursor = cursor.max(ex_end); + if cursor >= end { + break; + } + } + if cursor < end { + self.add_block_usize(cursor, end); + } + } + } + + /// Consume the delta array and return per-base depth for the contig. + pub fn into_depths(self) -> Vec { + let mut depths = Vec::with_capacity(self.len); + let mut running = 0i32; + for delta in self.deltas.iter().take(self.len) { + running += delta; + // `running` cannot go negative: every `-1` is emitted only after + // its matching `+1`, and both are clamped to the same range. + depths.push(running.max(0) as u32); + } + depths + } + + /// Whether a record contributes to depth at all. + fn passes_filters(&self, record: &bam::Record) -> bool { + record.flags() & self.exclude_flags == 0 && record.mapq() >= self.mapq_cut + } + + /// Record a half-open aligned block `[start, end)`, already clamped. + fn add_block_usize(&mut self, start: usize, end: usize) { + if start >= end { + return; + } + self.deltas[start] += 1; + self.deltas[end] -= 1; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rust_htslib::bam::record::{Cigar, CigarString, Record}; + + /// Build a minimal mapped record at `pos` with the given CIGAR, MAPQ and flags. + /// + /// `seq` and `qual` must both be as long as the query-consuming part of the + /// CIGAR, otherwise the record is malformed and every assertion made against + /// it is meaningless, so the helper asserts that itself. + fn rec(pos: i64, cigar: Vec, mapq: u8, flags: u16) -> Record { + let query_len: usize = cigar + .iter() + .map(|op| match op { + Cigar::Match(n) | Cigar::Ins(n) | Cigar::SoftClip(n) => *n as usize, + Cigar::Equal(n) | Cigar::Diff(n) => *n as usize, + _ => 0, + }) + .sum(); + let seq = vec![b'A'; query_len]; + let qual = vec![30u8; query_len]; + assert_eq!(seq.len(), qual.len(), "malformed test record"); + + let mut r = Record::new(); + r.set(b"q", Some(&CigarString(cigar)), &seq, &qual); + r.set_tid(0); + r.set_pos(pos); + r.set_mapq(mapq); + r.set_flags(flags); + r + } + + /// Build a paired record whose mate sits at `mate_pos` on the same contig. + fn pair_rec(qname: &[u8], pos: i64, mate_pos: i64, cigar: Vec, read2: bool) -> Record { + let mut r = rec( + pos, + cigar, + 60, + BAM_FPAIRED | BAM_FPROPER_PAIR | if read2 { BAM_FREAD2 } else { BAM_FREAD1 }, + ); + r.set_qname(qname); + r.set_mtid(0); + r.set_mpos(mate_pos); + r + } + + #[test] + fn match_block_covers_exactly_its_span() { + let mut d = DepthAccum::new(20, 0, MOSDEPTH_DEFAULT_EXCLUDE); + d.process_read(&rec(5, vec![Cigar::Match(4)], 60, 0)); + assert_eq!(&d.into_depths()[4..10], &[0, 1, 1, 1, 1, 0]); + } + + #[test] + fn deletion_and_skip_advance_without_covering() { + let mut d = DepthAccum::new(20, 0, MOSDEPTH_DEFAULT_EXCLUDE); + d.process_read(&rec( + 0, + vec![Cigar::Match(2), Cigar::Del(3), Cigar::Match(2)], + 60, + 0, + )); + assert_eq!(&d.into_depths()[0..8], &[1, 1, 0, 0, 0, 1, 1, 0]); + } + + #[test] + fn ref_skip_advances_without_covering() { + let mut d = DepthAccum::new(20, 0, MOSDEPTH_DEFAULT_EXCLUDE); + d.process_read(&rec( + 0, + vec![Cigar::Match(2), Cigar::RefSkip(3), Cigar::Match(2)], + 60, + 0, + )); + assert_eq!(&d.into_depths()[0..8], &[1, 1, 0, 0, 0, 1, 1, 0]); + } + + #[test] + fn insertion_and_soft_clip_do_not_advance_the_reference() { + let mut d = DepthAccum::new(20, 0, MOSDEPTH_DEFAULT_EXCLUDE); + d.process_read(&rec( + 0, + vec![ + Cigar::SoftClip(3), + Cigar::Match(2), + Cigar::Ins(4), + Cigar::Match(2), + ], + 60, + 0, + )); + assert_eq!(&d.into_depths()[0..6], &[1, 1, 1, 1, 0, 0]); + } + + #[test] + fn duplicate_flagged_reads_are_excluded_by_default() { + let mut d = DepthAccum::new(20, 0, MOSDEPTH_DEFAULT_EXCLUDE); + d.process_read(&rec(0, vec![Cigar::Match(4)], 60, BAM_FDUP)); + assert_eq!(d.into_depths().iter().sum::(), 0); + } + + #[test] + fn secondary_qcfail_and_unmapped_reads_are_excluded_by_default() { + for flag in [BAM_FSECONDARY, BAM_FQCFAIL, BAM_FUNMAP] { + let mut d = DepthAccum::new(20, 0, MOSDEPTH_DEFAULT_EXCLUDE); + d.process_read(&rec(0, vec![Cigar::Match(4)], 60, flag)); + assert_eq!( + d.into_depths().iter().sum::(), + 0, + "flag {flag:#x} should be excluded" + ); + } + } + + #[test] + fn reads_below_the_mapq_cutoff_are_excluded() { + let mut d = DepthAccum::new(20, 30, MOSDEPTH_DEFAULT_EXCLUDE); + d.process_read(&rec(0, vec![Cigar::Match(4)], 29, 0)); + assert_eq!(d.into_depths().iter().sum::(), 0); + + let mut d = DepthAccum::new(20, 30, MOSDEPTH_DEFAULT_EXCLUDE); + d.process_read(&rec(0, vec![Cigar::Match(4)], 30, 0)); + assert_eq!(d.into_depths().iter().sum::(), 4); + } + + #[test] + fn a_read_running_past_the_contig_end_is_clipped_not_panicking() { + let mut d = DepthAccum::new(6, 0, MOSDEPTH_DEFAULT_EXCLUDE); + d.process_read(&rec(4, vec![Cigar::Match(10)], 60, 0)); + assert_eq!(d.into_depths(), vec![0, 0, 0, 0, 1, 1]); + } + + #[test] + fn overlapping_mates_cover_a_base_once() { + let mut d = DepthAccum::new(20, 0, MOSDEPTH_DEFAULT_EXCLUDE); + d.process_read(&pair_rec(b"pair1", 0, 0, vec![Cigar::Match(4)], false)); + d.process_read(&pair_rec(b"pair1", 0, 0, vec![Cigar::Match(4)], true)); + assert_eq!( + &d.into_depths()[0..5], + &[1, 1, 1, 1, 0], + "a base covered by both mates counts once" + ); + } + + #[test] + fn partially_overlapping_mates_count_the_shared_bases_once() { + let mut d = DepthAccum::new(20, 0, MOSDEPTH_DEFAULT_EXCLUDE); + d.process_read(&pair_rec(b"pair1", 0, 2, vec![Cigar::Match(4)], false)); + d.process_read(&pair_rec(b"pair1", 2, 0, vec![Cigar::Match(4)], true)); + // Mate 1 covers 0..4, mate 2 covers 2..6; bases 2 and 3 are shared. + assert_eq!(&d.into_depths()[0..7], &[1, 1, 1, 1, 1, 1, 0]); + } + + #[test] + fn non_overlapping_mates_each_contribute() { + let mut d = DepthAccum::new(20, 0, MOSDEPTH_DEFAULT_EXCLUDE); + d.process_read(&pair_rec(b"pair2", 0, 8, vec![Cigar::Match(4)], false)); + d.process_read(&pair_rec(b"pair2", 8, 0, vec![Cigar::Match(4)], true)); + assert_eq!( + &d.into_depths()[0..13], + &[1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0] + ); + } + + #[test] + fn reads_from_different_pairs_at_the_same_locus_both_count() { + let mut d = DepthAccum::new(20, 0, MOSDEPTH_DEFAULT_EXCLUDE); + d.process_read(&pair_rec(b"pairA", 0, 0, vec![Cigar::Match(4)], false)); + d.process_read(&pair_rec(b"pairB", 0, 0, vec![Cigar::Match(4)], false)); + assert_eq!(d.into_depths()[0], 2); + } + + #[test] + fn the_pending_mate_map_is_emptied_once_both_mates_are_seen() { + let mut d = DepthAccum::new(20, 0, MOSDEPTH_DEFAULT_EXCLUDE); + d.process_read(&pair_rec(b"pair1", 0, 0, vec![Cigar::Match(4)], false)); + d.process_read(&pair_rec(b"pair1", 0, 0, vec![Cigar::Match(4)], true)); + assert_eq!(d.pending_mates_len(), 0, "the entry must be dropped"); + } + + #[test] + fn a_pending_mate_that_never_arrives_is_evicted() { + let mut d = DepthAccum::new(200, 0, MOSDEPTH_DEFAULT_EXCLUDE); + // Its mate is announced at 10 but never turns up (filtered, say). + d.process_read(&pair_rec(b"orphan", 0, 10, vec![Cigar::Match(4)], false)); + assert_eq!(d.pending_mates_len(), 1); + // Walking past position 10 makes the entry unclaimable. + d.process_read(&pair_rec(b"later", 50, 50, vec![Cigar::Match(4)], false)); + assert_eq!( + d.pending_mates_len(), + 1, + "only the unclaimable one is dropped" + ); + } + + /// Engine-level parity check against mosdepth 0.3.14 on the committed + /// fixture. `tests/expected/dna/test.mosdepth.summary.txt` records + /// `total 40001 247878 6.20 0 867` for this BAM, so the total covered + /// bases and the maximum depth are both pinned here. Getting this right + /// requires the flag filter, the CIGAR walk and the mate-overlap + /// correction to all be right at once. + #[test] + fn total_covered_bases_match_mosdepth_on_the_fixture() { + use rust_htslib::bam::Read; + + let bam_path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/data/dna/test.dna.bam"); + let mut bam = bam::Reader::from_path(bam_path).unwrap(); + let header = bam.header().to_owned(); + let contig_len = header.target_len(0).unwrap(); + + let mut accum = DepthAccum::new(contig_len, 0, MOSDEPTH_DEFAULT_EXCLUDE); + let mut record = Record::new(); + while let Some(result) = bam.read(&mut record) { + result.unwrap(); + accum.process_read(&record); + } + + let depths = accum.into_depths(); + let total: u64 = depths.iter().map(|d| u64::from(*d)).sum(); + let max = depths.iter().copied().max().unwrap(); + + assert_eq!(depths.len(), 40001, "contig length"); + assert_eq!(total, 247878, "total covered bases must match mosdepth"); + assert_eq!(max, 867, "maximum depth must match mosdepth"); + } +} diff --git a/src/dna/mod.rs b/src/dna/mod.rs new file mode 100644 index 00000000..eafde9f9 --- /dev/null +++ b/src/dna/mod.rs @@ -0,0 +1,8 @@ +//! DNA quality control and analysis modules. +//! +//! Contains the depth of coverage engine and the mosdepth-compatible outputs +//! built on top of it. Read-level statistics, the samtools-compatible writers +//! and preseq are shared with the RNA pipeline and live in [`crate::common`]. + +pub mod depth; +pub mod mosdepth; diff --git a/src/dna/mosdepth/mod.rs b/src/dna/mosdepth/mod.rs new file mode 100644 index 00000000..b8fe1f8f --- /dev/null +++ b/src/dna/mosdepth/mod.rs @@ -0,0 +1,427 @@ +//! mosdepth-compatible depth of coverage results. +//! +//! [`ContigDepth::from_depths`] turns one contig's per-base depth vector into +//! everything the six mosdepth outputs need, in a single pass over the vector, +//! so the depth vector can be dropped as soon as the contig is done. +//! +//! # Output formats +//! +//! These were derived from mosdepth 0.3.14 output committed under +//! `tests/expected/dna/`, not from documentation, and every rule below was +//! checked against every row of those fixtures. +//! +//! `{prefix}.mosdepth.summary.txt` carries the header +//! `chrom length bases mean min max`, one row per contig, then one +//! `{contig}_region` row per contig when windows were requested, then `total` +//! and `total_region`. `mean` is `bases / length` to two decimals. +//! +//! `{prefix}.mosdepth.global.dist.txt` and `.region.dist.txt` carry +//! `chrom depth proportion` rows in descending depth order, where `proportion` +//! is the fraction at depth **at or above** `depth`, formatted to two +//! decimals, ending at depth 0 with `1.00`. Which depths get a row is the +//! non-obvious part: +//! +//! - depths 0 through [`DIST_DENSE_MAX`] always get a row, even when no base +//! sits at that exact depth; +//! - above that, only depths that actually occur; +//! - the maximum observed depth never gets a row. +//! +//! The global distribution is over bases and their exact depth; the region +//! distribution is over windows and their **rounded** mean depth. + +use std::collections::BTreeMap; + +pub mod output; + +/// Highest depth that always gets a distribution row, matching the size of +/// mosdepth's internal fixed depth array. +pub const DIST_DENSE_MAX: u32 = 300; + +/// A run of consecutive bases sharing one depth, as written to `per-base.bed.gz`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DepthRun { + /// Zero-based, inclusive start. + pub start: u64, + /// Zero-based, exclusive end. + pub end: u64, + /// Depth shared by every base in the run. + pub depth: u32, +} + +/// A fixed-width window and its mean depth, as written to `regions.bed.gz`. +#[derive(Debug, Clone, PartialEq)] +pub struct WindowDepth { + /// Zero-based, inclusive start. + pub start: u64, + /// Zero-based, exclusive end. + pub end: u64, + /// Mean depth over the window. + pub mean: f64, +} + +/// One window's per-threshold counts, as written to `thresholds.bed.gz`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ThresholdRow { + /// Zero-based, inclusive start. + pub start: u64, + /// Zero-based, exclusive end. + pub end: u64, + /// Bases at or above each requested threshold, in the requested order. + pub counts: Vec, +} + +/// Everything the mosdepth outputs need about one contig. +#[derive(Debug, Clone)] +pub struct ContigDepth { + /// Contig name as it appears in the alignment header. + pub name: String, + /// Contig length in bases. + pub length: u64, + /// Sum of per-base depth over the contig. + pub total_bases: u64, + /// Lowest per-base depth seen. + pub min: u32, + /// Highest per-base depth seen. + pub max: u32, + /// Base count per exact depth. + pub histogram: BTreeMap, + /// Collapsed runs of equal depth. + pub runs: Vec, + /// Per-window mean depth; empty when no window size was requested. + pub windows: Vec, + /// Per-window threshold counts; empty when no thresholds were requested. + pub thresholds: Vec, +} + +impl ContigDepth { + /// Summarise one contig's per-base depths in a single pass. + pub fn from_depths( + name: &str, + depths: &[u32], + window_size: Option, + thresholds: &[u32], + ) -> Self { + let length = depths.len() as u64; + let mut histogram: BTreeMap = BTreeMap::new(); + let mut runs: Vec = Vec::new(); + let mut total_bases = 0u64; + + for (i, &depth) in depths.iter().enumerate() { + total_bases += u64::from(depth); + *histogram.entry(depth).or_insert(0) += 1; + match runs.last_mut() { + Some(run) if run.depth == depth => run.end = i as u64 + 1, + _ => runs.push(DepthRun { + start: i as u64, + end: i as u64 + 1, + depth, + }), + } + } + + let min = depths.iter().copied().min().unwrap_or(0); + let max = depths.iter().copied().max().unwrap_or(0); + + let (windows, threshold_rows) = match window_size { + Some(size) if size > 0 => Self::windowed(depths, u64::from(size), thresholds), + _ => (Vec::new(), Vec::new()), + }; + + Self { + name: name.to_string(), + length, + total_bases, + min, + max, + histogram, + runs, + windows, + thresholds: threshold_rows, + } + } + + /// Split the contig into fixed-width windows, computing each window's mean + /// depth and its per-threshold base counts. + fn windowed( + depths: &[u32], + size: u64, + thresholds: &[u32], + ) -> (Vec, Vec) { + let mut windows = Vec::new(); + let mut rows = Vec::new(); + for (index, chunk) in depths.chunks(size as usize).enumerate() { + let start = index as u64 * size; + let end = start + chunk.len() as u64; + let sum: u64 = chunk.iter().map(|d| u64::from(*d)).sum(); + windows.push(WindowDepth { + start, + end, + mean: sum as f64 / chunk.len() as f64, + }); + if !thresholds.is_empty() { + let counts = thresholds + .iter() + .map(|t| chunk.iter().filter(|d| *d >= t).count() as u64) + .collect(); + rows.push(ThresholdRow { start, end, counts }); + } + } + (windows, rows) + } + + /// Mean depth over the contig. + pub fn mean(&self) -> f64 { + if self.length == 0 { + 0.0 + } else { + self.total_bases as f64 / self.length as f64 + } + } + + /// Histogram of window mean depths, rounded to the nearest integer, which + /// is what the region distribution is built from. + pub fn region_histogram(&self) -> BTreeMap { + let mut hist = BTreeMap::new(); + for window in &self.windows { + let key = window.mean.round().max(0.0) as u32; + *hist.entry(key).or_insert(0) += 1; + } + hist + } +} + +/// The mosdepth result for one alignment file. +#[derive(Debug, Clone)] +pub struct MosdepthResult { + /// Per-contig results, in alignment-header order. + pub contigs: Vec, + /// Window size, when per-window output was requested. + pub window_size: Option, + /// Requested coverage thresholds, in the order they are reported. + pub thresholds: Vec, +} + +impl MosdepthResult { + /// Total length across all contigs. + pub fn total_length(&self) -> u64 { + self.contigs.iter().map(|c| c.length).sum() + } + + /// Total covered bases across all contigs. + pub fn total_bases(&self) -> u64 { + self.contigs.iter().map(|c| c.total_bases).sum() + } + + /// Mean depth across all contigs. + pub fn mean(&self) -> f64 { + let length = self.total_length(); + if length == 0 { + 0.0 + } else { + self.total_bases() as f64 / length as f64 + } + } + + /// Lowest depth across all contigs. + pub fn min(&self) -> u32 { + self.contigs.iter().map(|c| c.min).min().unwrap_or(0) + } + + /// Highest depth across all contigs. + pub fn max(&self) -> u32 { + self.contigs.iter().map(|c| c.max).max().unwrap_or(0) + } +} + +/// Merge histograms element-wise. +pub fn merge_histograms<'a>( + parts: impl IntoIterator>, +) -> BTreeMap { + let mut merged = BTreeMap::new(); + for part in parts { + for (depth, count) in part { + *merged.entry(*depth).or_insert(0) += count; + } + } + merged +} + +/// The depths that get a distribution row, in descending order. +/// +/// The rule was derived from the committed fixtures and holds for both +/// distribution files: every depth from 0 up to `min(DIST_DENSE_MAX, max)` +/// gets a row whether or not anything sits at it, and above +/// [`DIST_DENSE_MAX`] only depths that actually occur and lie strictly below +/// the maximum do. +/// +/// The consequence worth stating plainly: the maximum observed depth gets a +/// row when it falls inside the dense range and no row when it does not. On +/// the project fixture the global distribution tops out at 866 with a maximum +/// of 867, while the region distribution does emit its maximum of 204. +pub fn dist_rows(histogram: &BTreeMap) -> Vec { + let observed_max = histogram + .iter() + .filter(|(_, count)| **count > 0) + .map(|(depth, _)| *depth) + .max() + .unwrap_or(0); + + let mut depths: Vec = histogram + .iter() + .filter(|(depth, count)| **count > 0 && **depth > DIST_DENSE_MAX && **depth < observed_max) + .map(|(depth, _)| *depth) + .collect(); + depths.extend(0..=DIST_DENSE_MAX.min(observed_max)); + depths.sort_unstable_by(|a, b| b.cmp(a)); + depths.dedup(); + depths +} + +/// Cumulative proportion at or above each depth in `rows`, given `histogram` +/// and a total to divide by. +pub fn dist_proportions(histogram: &BTreeMap, rows: &[u32], total: u64) -> Vec { + if total == 0 { + return vec![0.0; rows.len()]; + } + rows.iter() + .map(|threshold| { + let at_or_above: u64 = histogram + .iter() + .filter(|(depth, _)| *depth >= threshold) + .map(|(_, count)| count) + .sum(); + at_or_above as f64 / total as f64 + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn runs_collapse_equal_neighbours() { + let c = ContigDepth::from_depths("chr1", &[0, 0, 0, 2, 2, 1], None, &[]); + assert_eq!( + c.runs, + vec![ + DepthRun { + start: 0, + end: 3, + depth: 0 + }, + DepthRun { + start: 3, + end: 5, + depth: 2 + }, + DepthRun { + start: 5, + end: 6, + depth: 1 + }, + ] + ); + } + + #[test] + fn summary_figures_are_computed_over_the_whole_contig() { + let c = ContigDepth::from_depths("chr1", &[0, 0, 3, 5], None, &[]); + assert_eq!(c.length, 4); + assert_eq!(c.total_bases, 8); + assert_eq!(c.min, 0); + assert_eq!(c.max, 5); + assert!((c.mean() - 2.0).abs() < 1e-12); + } + + #[test] + fn windows_cover_the_tail_even_when_shorter_than_the_window() { + let c = ContigDepth::from_depths("chr1", &[4, 4, 4, 4, 10], Some(4), &[]); + assert_eq!(c.windows.len(), 2); + assert_eq!( + c.windows[0], + WindowDepth { + start: 0, + end: 4, + mean: 4.0 + } + ); + assert_eq!( + c.windows[1], + WindowDepth { + start: 4, + end: 5, + mean: 10.0 + } + ); + } + + #[test] + fn threshold_counts_are_at_or_above_each_threshold() { + let c = ContigDepth::from_depths("chr1", &[0, 1, 5, 10], Some(4), &[1, 5, 20]); + assert_eq!(c.thresholds.len(), 1); + assert_eq!(c.thresholds[0].counts, vec![3, 2, 0]); + } + + #[test] + fn dist_rows_emit_a_maximum_that_falls_inside_the_dense_range() { + let mut hist = BTreeMap::new(); + hist.insert(0u32, 10u64); + hist.insert(204, 1); // the maximum, but below DIST_DENSE_MAX + let rows = dist_rows(&hist); + assert_eq!( + rows.first(), + Some(&204), + "a maximum inside the dense range is emitted" + ); + assert_eq!(rows.len(), 205, "0 through 204 inclusive"); + } + + #[test] + fn dist_rows_skip_a_maximum_above_the_dense_range() { + let mut hist = BTreeMap::new(); + hist.insert(0u32, 10u64); + hist.insert(5, 2); + hist.insert(400, 1); + hist.insert(500, 1); // the maximum, never emitted + let rows = dist_rows(&hist); + assert!(!rows.contains(&500), "the maximum depth gets no row"); + assert!( + rows.contains(&400), + "an observed depth above the dense range does" + ); + assert!( + rows.contains(&7), + "an unobserved depth inside the dense range does" + ); + assert!( + !rows.contains(&350), + "an unobserved depth above the dense range does not" + ); + assert_eq!(rows.first(), Some(&400), "descending order"); + assert_eq!(rows.last(), Some(&0), "down to zero"); + } + + #[test] + fn dist_proportions_are_cumulative_from_the_top() { + let mut hist = BTreeMap::new(); + hist.insert(0u32, 2u64); + hist.insert(1, 1); + hist.insert(3, 1); + let rows = vec![3u32, 2, 1, 0]; + let props = dist_proportions(&hist, &rows, 4); + assert!((props[0] - 0.25).abs() < 1e-12); + assert!((props[1] - 0.25).abs() < 1e-12); + assert!((props[2] - 0.50).abs() < 1e-12); + assert!((props[3] - 1.00).abs() < 1e-12); + } + + #[test] + fn region_histogram_rounds_window_means() { + let c = ContigDepth::from_depths("chr1", &[1, 2, 2, 3], Some(2), &[]); + // Windows: mean 1.5 rounds to 2, mean 2.5 rounds to 3 (away from zero). + let hist = c.region_histogram(); + assert_eq!(hist.get(&2), Some(&1)); + assert_eq!(hist.get(&3), Some(&1)); + } +} diff --git a/src/dna/mosdepth/output.rs b/src/dna/mosdepth/output.rs new file mode 100644 index 00000000..78e4ccb0 --- /dev/null +++ b/src/dna/mosdepth/output.rs @@ -0,0 +1,337 @@ +//! Writers for the six mosdepth-compatible output files. +//! +//! Formats are documented in the parent module. Compressed outputs are written +//! as bgzf, which is what mosdepth writes and what both `tabix` and `gunzip` +//! read. Parity against the fixtures is therefore asserted on the decompressed +//! bytes: two bgzf writers at the same level need not emit identical +//! compressed bytes, so comparing the `.gz` byte for byte would be testing the +//! compressor rather than this code. + +use std::io::Write; +use std::path::Path; + +use anyhow::{bail, Context, Result}; +use rust_htslib::bgzf; + +use super::{dist_proportions, dist_rows, merge_histograms, MosdepthResult}; + +/// Write `{prefix}.mosdepth.summary.txt`. +pub fn write_summary(result: &MosdepthResult, path: &Path) -> Result<()> { + let mut out = std::fs::File::create(path) + .map(std::io::BufWriter::new) + .with_context(|| format!("Failed to create summary file: {}", path.display()))?; + + writeln!(out, "chrom\tlength\tbases\tmean\tmin\tmax")?; + for contig in &result.contigs { + writeln!( + out, + "{}\t{}\t{}\t{:.2}\t{}\t{}", + contig.name, + contig.length, + contig.total_bases, + contig.mean(), + contig.min, + contig.max + )?; + if result.window_size.is_some() { + writeln!( + out, + "{}_region\t{}\t{}\t{:.2}\t{}\t{}", + contig.name, + contig.length, + contig.total_bases, + contig.mean(), + contig.min, + contig.max + )?; + } + } + writeln!( + out, + "total\t{}\t{}\t{:.2}\t{}\t{}", + result.total_length(), + result.total_bases(), + result.mean(), + result.min(), + result.max() + )?; + if result.window_size.is_some() { + writeln!( + out, + "total_region\t{}\t{}\t{:.2}\t{}\t{}", + result.total_length(), + result.total_bases(), + result.mean(), + result.min(), + result.max() + )?; + } + out.flush()?; + Ok(()) +} + +/// Write `{prefix}.mosdepth.global.dist.txt`, the distribution over bases. +pub fn write_global_dist(result: &MosdepthResult, path: &Path) -> Result<()> { + let per_contig: Vec<_> = result + .contigs + .iter() + .map(|c| (c.name.as_str(), c.histogram.clone(), c.length)) + .collect(); + write_dist(&per_contig, path) +} + +/// Write `{prefix}.mosdepth.region.dist.txt`, the distribution over windows +/// and their rounded mean depth. +pub fn write_region_dist(result: &MosdepthResult, path: &Path) -> Result<()> { + let per_contig: Vec<_> = result + .contigs + .iter() + .map(|c| { + let hist = c.region_histogram(); + let total = hist.values().sum::(); + (c.name.as_str(), hist, total) + }) + .collect(); + write_dist(&per_contig, path) +} + +/// Shared body of both distribution writers. +fn write_dist( + per_contig: &[(&str, std::collections::BTreeMap, u64)], + path: &Path, +) -> Result<()> { + let mut out = std::fs::File::create(path) + .map(std::io::BufWriter::new) + .with_context(|| format!("Failed to create distribution file: {}", path.display()))?; + + for (name, histogram, total) in per_contig { + let rows = dist_rows(histogram); + for (depth, proportion) in rows.iter().zip(dist_proportions(histogram, &rows, *total)) { + writeln!(out, "{name}\t{depth}\t{proportion:.2}")?; + } + } + + let merged = merge_histograms(per_contig.iter().map(|(_, h, _)| h)); + let total: u64 = per_contig.iter().map(|(_, _, t)| t).sum(); + let rows = dist_rows(&merged); + for (depth, proportion) in rows.iter().zip(dist_proportions(&merged, &rows, total)) { + writeln!(out, "total\t{depth}\t{proportion:.2}")?; + } + + out.flush()?; + Ok(()) +} + +/// Write `{prefix}.per-base.bed.gz`, one line per run of equal depth. +pub fn write_per_base(result: &MosdepthResult, path: &Path) -> Result<()> { + let mut lines = Vec::new(); + for contig in &result.contigs { + for run in &contig.runs { + lines.push(format!( + "{}\t{}\t{}\t{}\n", + contig.name, run.start, run.end, run.depth + )); + } + } + write_bgzf(path, &lines.concat()) +} + +/// Write `{prefix}.regions.bed.gz`, one line per window with its mean depth. +pub fn write_regions(result: &MosdepthResult, path: &Path) -> Result<()> { + let mut lines = Vec::new(); + for contig in &result.contigs { + for window in &contig.windows { + lines.push(format!( + "{}\t{}\t{}\t{:.2}\n", + contig.name, window.start, window.end, window.mean + )); + } + } + write_bgzf(path, &lines.concat()) +} + +/// Write `{prefix}.thresholds.bed.gz`, one line per window with the number of +/// bases at or above each requested threshold. +pub fn write_thresholds(result: &MosdepthResult, path: &Path) -> Result<()> { + let mut body = String::from("#chrom\tstart\tend\tregion"); + for threshold in &result.thresholds { + body.push_str(&format!("\t{threshold}X")); + } + body.push('\n'); + + for contig in &result.contigs { + for row in &contig.thresholds { + body.push_str(&format!( + "{}\t{}\t{}\tunknown", + contig.name, row.start, row.end + )); + for count in &row.counts { + body.push_str(&format!("\t{count}")); + } + body.push('\n'); + } + } + write_bgzf(path, &body) +} + +/// Write `contents` to `path` as bgzf, then build its `.csi` index. +fn write_bgzf(path: &Path, contents: &str) -> Result<()> { + { + let mut writer = bgzf::Writer::from_path(path) + .with_context(|| format!("Failed to create bgzf file: {}", path.display()))?; + writer + .write_all(contents.as_bytes()) + .with_context(|| format!("Failed to write bgzf file: {}", path.display()))?; + // The writer must be dropped, and the bgzf stream closed, before the + // indexer reads the file back. + } + build_csi_index(path) +} + +/// Build the `.csi` companion index for a bgzf-compressed BED file. +/// +/// mosdepth writes one alongside each of its BED outputs, and `tabix` needs it +/// to seek into them. CSI rather than TBI because CSI carries no 512 Mb +/// coordinate ceiling, which matters on large contigs. +fn build_csi_index(path: &Path) -> Result<()> { + use std::ffi::CString; + + let path_c = CString::new(path.as_os_str().as_encoded_bytes()).with_context(|| { + format!( + "Path is not representable as a C string: {}", + path.display() + ) + })?; + + // SAFETY: `path_c` is a valid NUL-terminated string that outlives the + // call, `tbx_conf_bed` is a static provided by htslib, and the file was + // closed above. A min_shift of 14 selects CSI, matching what mosdepth and + // `tabix --csi` produce. + let ret = unsafe { + rust_htslib::htslib::tbx_index_build( + path_c.as_ptr(), + 14, + &raw const rust_htslib::htslib::tbx_conf_bed, + ) + }; + if ret < 0 { + bail!("Failed to build the CSI index for {}", path.display()); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::dna::mosdepth::ContigDepth; + use std::io::Read; + + fn scratch(name: &str) -> std::path::PathBuf { + let dir = std::env::temp_dir().join("rustqc-mosdepth-tests"); + std::fs::create_dir_all(&dir).unwrap(); + dir.join(name) + } + + fn result_with_windows() -> MosdepthResult { + let depths = vec![0u32, 0, 2, 2, 4, 4]; + MosdepthResult { + contigs: vec![ContigDepth::from_depths("chr1", &depths, Some(3), &[1, 4])], + window_size: Some(3), + thresholds: vec![1, 4], + } + } + + fn read_bgzf(path: &std::path::Path) -> String { + let mut reader = bgzf::Reader::from_path(path).unwrap(); + let mut buf = Vec::new(); + reader.read_to_end(&mut buf).unwrap(); + String::from_utf8(buf).unwrap() + } + + #[test] + fn summary_has_region_rows_only_when_windows_were_requested() { + let path = scratch("summary_windows.txt"); + write_summary(&result_with_windows(), &path).unwrap(); + let text = std::fs::read_to_string(&path).unwrap(); + assert_eq!( + text, + "chrom\tlength\tbases\tmean\tmin\tmax\n\ + chr1\t6\t12\t2.00\t0\t4\n\ + chr1_region\t6\t12\t2.00\t0\t4\n\ + total\t6\t12\t2.00\t0\t4\n\ + total_region\t6\t12\t2.00\t0\t4\n" + ); + + let depths = vec![0u32, 0, 2, 2, 4, 4]; + let no_windows = MosdepthResult { + contigs: vec![ContigDepth::from_depths("chr1", &depths, None, &[])], + window_size: None, + thresholds: vec![], + }; + let path = scratch("summary_nowindows.txt"); + write_summary(&no_windows, &path).unwrap(); + let text = std::fs::read_to_string(&path).unwrap(); + assert!(!text.contains("_region"), "no windows means no region rows"); + } + + #[test] + fn per_base_writes_one_line_per_run() { + let path = scratch("per-base.bed.gz"); + write_per_base(&result_with_windows(), &path).unwrap(); + assert_eq!( + read_bgzf(&path), + "chr1\t0\t2\t0\nchr1\t2\t4\t2\nchr1\t4\t6\t4\n" + ); + } + + #[test] + fn regions_carry_two_decimal_means() { + let path = scratch("regions.bed.gz"); + write_regions(&result_with_windows(), &path).unwrap(); + assert_eq!(read_bgzf(&path), "chr1\t0\t3\t0.67\nchr1\t3\t6\t3.33\n"); + } + + #[test] + fn thresholds_carry_a_header_and_one_column_per_threshold() { + let path = scratch("thresholds.bed.gz"); + write_thresholds(&result_with_windows(), &path).unwrap(); + assert_eq!( + read_bgzf(&path), + "#chrom\tstart\tend\tregion\t1X\t4X\n\ + chr1\t0\t3\tunknown\t1\t0\n\ + chr1\t3\t6\tunknown\t3\t2\n" + ); + } + + #[test] + fn global_dist_is_descending_and_ends_at_one() { + let path = scratch("global.dist.txt"); + write_global_dist(&result_with_windows(), &path).unwrap(); + let text = std::fs::read_to_string(&path).unwrap(); + let chr1: Vec<&str> = text.lines().filter(|l| l.starts_with("chr1\t")).collect(); + assert_eq!( + *chr1.first().unwrap(), + "chr1\t4\t0.33", + "descending from the maximum" + ); + assert_eq!(*chr1.last().unwrap(), "chr1\t0\t1.00", "down to zero"); + assert_eq!( + chr1.len(), + 5, + "depths 4 down to 0, all inside the dense range" + ); + assert!(text.contains("total\t0\t1.00")); + } + + #[test] + fn compressed_outputs_get_a_loadable_csi_index() { + let path = scratch("indexed.per-base.bed.gz"); + let index = scratch("indexed.per-base.bed.gz.csi"); + let _ = std::fs::remove_file(&index); + write_per_base(&result_with_windows(), &path).unwrap(); + assert!(index.exists(), "the .csi companion index must be written"); + // htslib refuses to open a malformed index, so opening it is the check. + let tbx = rust_htslib::tbx::Reader::from_path(&path); + assert!(tbx.is_ok(), "htslib could not open the indexed file"); + } +} diff --git a/src/lib.rs b/src/lib.rs index 9a228cae..2ce25ddf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,9 +23,12 @@ //! - [`config`] — configuration types that mirror the CLI's YAML config file. //! - [`summary`] — serializable types for the JSON run summary. //! - [`cpu`] — CPU feature detection and binary-target identification. +//! - [`common`] — analyses shared by every pipeline: BAM flag helpers, +//! read-level statistics ([`common::bam_stat`], [`common::bam_stat_accum`]), +//! the samtools-compatible writers ([`common::samtools`]), and preseq +//! library complexity extrapolation ([`common::preseq`]). //! - [`rna`] — the RNA-Seq analysis modules: -//! - [`rna::dupradar`], [`rna::featurecounts`], [`rna::qualimap`], -//! [`rna::preseq`], [`rna::rseqc`]. +//! - [`rna::dupradar`], [`rna::featurecounts`], [`rna::qualimap`], [`rna::rseqc`]. //! //! [`Strandedness`] lives at the crate root because it is used across most //! analysis modules. @@ -65,8 +68,10 @@ use clap::ValueEnum; use serde::Deserialize; +pub mod common; pub mod config; pub mod cpu; +pub mod dna; pub mod gtf; pub mod io; pub mod rna; diff --git a/src/main.rs b/src/main.rs index 4c66c173..9ba81a33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,14 +18,15 @@ use indexmap::IndexMap; use log::debug; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use std::collections::{HashMap, HashSet}; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::time::{Instant, SystemTime, UNIX_EPOCH}; use rustqc::io::{format_count, format_duration, format_pct}; -use rustqc::{config, cpu, gtf, rna, summary}; +use rustqc::{common, config, cpu, gtf, rna, summary}; use ui::{Ui, Verbosity}; +use rust_htslib::bam; use rust_htslib::bam::Read as BamRead; use rna::rseqc::accumulators::{RseqcAccumulators, RseqcAnnotations, RseqcConfig}; @@ -73,9 +74,13 @@ fn main() -> Result<()> { let cli = cli::parse_args(); // Determine verbosity from CLI flags - let verbosity = match &cli.command { - cli::Commands::Rna(args) if args.quiet => Verbosity::Quiet, - cli::Commands::Rna(args) if args.verbose => Verbosity::Verbose, + let (quiet, verbose) = match &cli.command { + cli::Commands::Rna(args) => (args.quiet, args.verbose), + cli::Commands::Dna(args) => (args.quiet, args.verbose), + }; + let verbosity = match (quiet, verbose) { + (true, _) => Verbosity::Quiet, + (_, true) => Verbosity::Verbose, _ => Verbosity::Normal, }; @@ -94,7 +99,492 @@ fn main() -> Result<()> { match cli.command { cli::Commands::Rna(args) => run_rna(args, &ui), + cli::Commands::Dna(args) => run_dna(args, &ui), + } +} + +/// Run the DNA QC pipeline: depth of coverage, samtools-compatible outputs +/// and library complexity estimation in a single pass over each input. +/// +/// Contigs are processed in parallel, one worker per contig, each holding its +/// own depth array. Input files are processed one after another so that the +/// per-contig parallelism gets the whole thread budget. +fn run_dna(args: cli::DnaArgs, ui: &Ui) -> Result<()> { + let run_start = Instant::now(); + let timestamp_start = format_utc_now(); + + let (merged, config_paths) = config::load_merged_config(args.config.as_deref())?; + let mut config = merged.dna; + + // CLI flags override the configuration file. + if !args.depth_thresholds.is_empty() { + config.mosdepth.thresholds = args.depth_thresholds.clone(); + } + if let Some(window) = args.window_size { + config.mosdepth.window_size = Some(window); + } + if args.skip_per_base { + config.mosdepth.skip_per_base = true; + } + if args.skip_preseq { + config.preseq.enabled = false; + } + if let Some(seed) = args.preseq_seed { + config.preseq.seed = seed; + } + if let Some(val) = args.preseq_max_extrap { + config.preseq.max_extrap = val; + } + if let Some(val) = args.preseq_step_size { + config.preseq.step_size = val; + } + if let Some(val) = args.preseq_n_bootstraps { + config.preseq.n_bootstraps = val; + } + if let Some(val) = args.preseq_seg_len { + config.preseq.max_segment_length = val; + } + + let flat_output = args.flat_output || config.flat_output; + let outdir = Path::new(&args.outdir); + std::fs::create_dir_all(outdir) + .with_context(|| format!("Failed to create output directory: {}", outdir.display()))?; + + ui.header( + env!("CARGO_PKG_VERSION"), + env!("GIT_SHORT_HASH"), + env!("BUILD_TIMESTAMP"), + Some(&rustqc::cpu::cpu_info_line()), + ); + for (path, source) in &config_paths { + ui.config("Config", &format!("{} ({source})", path.display())); + } + ui.config("Output dir", &args.outdir); + ui.config("Threads", &args.threads.to_string()); + if let Some(ref targets) = args.targets { + ui.config("Targets", targets); + ui.warn("--targets is accepted but targeted metrics are not implemented yet"); + } + + let mut inputs = Vec::new(); + for bam_path in &args.input { + let bam_start = Instant::now(); + let name = Path::new(bam_path) + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or(bam_path.as_str()) + .to_string(); + + match process_single_dna_bam(bam_path, &args, &config, outdir, flat_output, ui) { + Ok(mut summary) => { + summary.runtime_seconds = bam_start.elapsed().as_secs_f64(); + ui.bam_result_ok(&name, bam_start.elapsed()); + inputs.push(summary); + } + Err(e) => { + ui.bam_result_err(&name, &format!("{e:#}")); + inputs.push(summary::InputSummary { + bam_file: bam_path.clone(), + status: "failed".to_string(), + error: Some(format!("{e:#}")), + runtime_seconds: bam_start.elapsed().as_secs_f64(), + counting: None, + dupradar: None, + dna: None, + outputs: Vec::new(), + }); + } + } + } + + if let Some(ref json_path) = args.json_summary { + let summary = summary::RunSummary { + version: env!("CARGO_PKG_VERSION").to_string(), + commit: env!("GIT_SHORT_HASH").to_string(), + binary_target: cpu::binary_target().to_string(), + cpu_features: cpu::detected_features() + .iter() + .map(|s| s.to_string()) + .collect(), + timestamp_start, + timestamp_end: format_utc_now(), + runtime_seconds: run_start.elapsed().as_secs_f64(), + inputs, + }; + let json = serde_json::to_string_pretty(&summary)?; + if json_path == "-" { + println!("{json}"); + } else { + let path = if json_path.is_empty() { + outdir.join("rustqc_summary.json") + } else { + PathBuf::from(json_path) + }; + std::fs::write(&path, json) + .with_context(|| format!("Failed to write JSON summary: {}", path.display()))?; + } + } + + let citations_path = outdir.join("CITATIONS.md"); + citations::write_dna_citations( + &citations_path, + &config, + env!("CARGO_PKG_VERSION"), + env!("GIT_SHORT_HASH"), + )?; + ui.output_item("citations", &citations_path.display().to_string()); + + ui.finish("DNA QC", run_start.elapsed()); + Ok(()) +} + +/// Process one alignment file through the DNA pipeline. +fn process_single_dna_bam( + bam_path: &str, + args: &cli::DnaArgs, + config: &config::DnaConfig, + outdir: &Path, + flat_output: bool, + ui: &Ui, +) -> Result { + use rustqc::common::bam_stat_accum::BamStatAccum; + use rustqc::common::preseq::PreseqAccum; + use rustqc::dna::depth::{DepthAccum, MOSDEPTH_DEFAULT_EXCLUDE}; + use rustqc::dna::mosdepth::{output as mos_out, ContigDepth, MosdepthResult}; + + let sample_name = args + .sample_name + .clone() + .or_else(|| config.sample_name.clone()) + .unwrap_or_else(|| { + Path::new(bam_path) + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("sample") + .to_string() + }); + + let is_cram = bam_path.ends_with(".cram"); + ensure!( + !is_cram || args.reference.is_some(), + "CRAM input requires --reference" + ); + + // Read the header once to learn the contigs. + let header = { + let reader = bam::IndexedReader::from_path(bam_path) + .with_context(|| format!("Failed to open alignment file: {bam_path}"))?; + reader.header().to_owned() + }; + let mut contigs: Vec<(u32, String, u64)> = (0..header.target_count()) + .map(|tid| { + let name = String::from_utf8_lossy(header.tid2name(tid)).to_string(); + let len = header.target_len(tid).unwrap_or(0); + (tid, name, len) + }) + .collect(); + // Longest first, so the biggest depth arrays are allocated while the pool + // is emptiest. + contigs.sort_by_key(|contig| std::cmp::Reverse(contig.2)); + + let largest = contigs.first().map(|c| c.2).unwrap_or(0); + let workers = depth_worker_budget(args.threads, args.max_depth_workers, largest); + ui.config("Depth workers", &workers.to_string()); + + let pool = rayon::ThreadPoolBuilder::new() + .num_threads(workers) + .build() + .context("Failed to build rayon thread pool")?; + + let thresholds = config.mosdepth.thresholds.clone(); + let window_size = config.mosdepth.window_size; + let preseq_enabled = config.preseq.enabled; + let seg_len = config.preseq.max_segment_length; + let mapq_cut = args.mapq_cut; + + type ContigOutput = (ContigDepth, BamStatAccum, Option); + + let results: Vec> = pool.install(|| { + contigs + .par_iter() + .map(|(tid, name, len)| -> Result { + let mut reader = bam::IndexedReader::from_path(bam_path) + .with_context(|| format!("Failed to open alignment file: {bam_path}"))?; + if let Some(reference) = args.reference.as_deref() { + reader + .set_reference(reference) + .with_context(|| format!("Failed to set reference: {reference}"))?; + } + reader + .fetch(*tid) + .with_context(|| format!("Failed to fetch contig {name}"))?; + + let mut depth = DepthAccum::new(*len, mapq_cut, MOSDEPTH_DEFAULT_EXCLUDE); + let mut bam_stat = BamStatAccum::default(); + let mut preseq = preseq_enabled.then(|| PreseqAccum::new(seg_len)); + + let mut record = bam::Record::new(); + while let Some(result) = reader.read(&mut record) { + result.context("Failed to read record")?; + depth.process_read(&record); + bam_stat.process_read(&record, mapq_cut); + if let Some(accum) = preseq.as_mut() { + accum.process_read(&record); + } + } + + let depths = depth.into_depths(); + let contig = ContigDepth::from_depths(name, &depths, window_size, &thresholds); + Ok((contig, bam_stat, preseq)) + }) + .collect() + }); + + let mut per_contig = Vec::new(); + let mut bam_stat_total = BamStatAccum::default(); + let mut preseq_total: Option = None; + for result in results { + let (contig, bam_stat, preseq) = result?; + per_contig.push(contig); + bam_stat_total.merge(bam_stat); + match (preseq_total.as_mut(), preseq) { + (Some(total), Some(part)) => total.merge(part), + (None, part) => preseq_total = part, + _ => {} + } + } + + // Unmapped records carry no contig, so they need their own pass; flagstat + // and idxstats both report them. + { + let mut reader = bam::IndexedReader::from_path(bam_path) + .with_context(|| format!("Failed to open alignment file: {bam_path}"))?; + if let Some(reference) = args.reference.as_deref() { + reader.set_reference(reference).ok(); + } + if reader.fetch(bam::FetchDefinition::Unmapped).is_ok() { + let mut record = bam::Record::new(); + while let Some(result) = reader.read(&mut record) { + result.context("Failed to read unmapped record")?; + bam_stat_total.process_read(&record, mapq_cut); + } + } + } + + // Workers ran longest-contig-first; outputs go out in header order. + let order: Vec = (0..header.target_count()) + .map(|tid| String::from_utf8_lossy(header.tid2name(tid)).to_string()) + .collect(); + per_contig.sort_by_key(|contig| { + order + .iter() + .position(|name| name == &contig.name) + .unwrap_or(usize::MAX) + }); + + let result = MosdepthResult { + contigs: per_contig, + window_size, + thresholds: thresholds.clone(), + }; + + let bam_stat_result = bam_stat_total.into_result(); + ensure!( + args.skip_dup_check || bam_stat_result.duplicates > 0, + "No duplicate-flagged reads found in {bam_path}. RustQC expects \ + duplicate-marked (not removed) input. Pass --skip-dup-check to override." + ); + + let dir = |name: &str| -> PathBuf { + if flat_output { + outdir.to_path_buf() + } else { + outdir.join(name) + } + }; + let mut written: Vec = Vec::new(); + let mut record_output = |tool: &str, path: PathBuf| { + ui.output_item(tool, &path.display().to_string()); + written.push(summary::OutputFile { + tool: tool.to_string(), + path: path.display().to_string(), + }); + }; + + if config.mosdepth.enabled { + let mos_dir = dir("mosdepth"); + std::fs::create_dir_all(&mos_dir)?; + // Built with format! rather than with_extension: a sample name that + // contains a dot (test.dna, say) would otherwise lose its last segment. + let prefix = |suffix: &str| mos_dir.join(format!("{sample_name}.{suffix}")); + + let path = prefix("mosdepth.summary.txt"); + mos_out::write_summary(&result, &path)?; + record_output("mosdepth", path); + + let path = prefix("mosdepth.global.dist.txt"); + mos_out::write_global_dist(&result, &path)?; + record_output("mosdepth", path); + + if !config.mosdepth.skip_per_base { + let path = prefix("per-base.bed.gz"); + mos_out::write_per_base(&result, &path)?; + record_output("mosdepth", path); + } + + if window_size.is_some() { + let path = prefix("mosdepth.region.dist.txt"); + mos_out::write_region_dist(&result, &path)?; + record_output("mosdepth", path); + + let path = prefix("regions.bed.gz"); + mos_out::write_regions(&result, &path)?; + record_output("mosdepth", path); + + if !thresholds.is_empty() { + let path = prefix("thresholds.bed.gz"); + mos_out::write_thresholds(&result, &path)?; + record_output("mosdepth", path); + } + } + } + + if config.samtools.enabled { + let sam_dir = dir("samtools"); + std::fs::create_dir_all(&sam_dir)?; + + let path = sam_dir.join(format!("{sample_name}.stats.txt")); + common::samtools::stats::write_stats(&bam_stat_result, &path)?; + record_output("samtools stats", path); + + let path = sam_dir.join(format!("{sample_name}.flagstat.txt")); + common::samtools::flagstat::write_flagstat(&bam_stat_result, &path)?; + record_output("samtools flagstat", path); + + let refs: Vec<(String, u64)> = (0..header.target_count()) + .map(|tid| { + ( + String::from_utf8_lossy(header.tid2name(tid)).to_string(), + header.target_len(tid).unwrap_or(0), + ) + }) + .collect(); + let path = sam_dir.join(format!("{sample_name}.idxstats.txt")); + common::samtools::idxstats::write_idxstats(&bam_stat_result, &refs, &path)?; + record_output("samtools idxstats", path); + } + + if let Some(mut accum) = preseq_total { + let preseq_dir = dir("preseq"); + std::fs::create_dir_all(&preseq_dir)?; + accum.finalize(); + let total_reads = accum.total_fragments; + let n_distinct = accum.n_distinct(); + let histogram = accum.into_histogram(); + match common::preseq::estimate_complexity( + &histogram, + total_reads, + n_distinct, + &config.preseq, + ) { + Ok(preseq_result) => { + let path = preseq_dir.join(format!("{sample_name}.lc_extrap.txt")); + common::preseq::write_output( + &preseq_result, + &path, + config.preseq.confidence_level, + )?; + record_output("preseq", path); + } + Err(e) => ui.warn(&format!("preseq: {e:#}")), + } + } + + Ok(summary::InputSummary { + bam_file: bam_path.to_string(), + status: "success".to_string(), + error: None, + runtime_seconds: 0.0, + counting: None, + dupradar: None, + dna: Some(dna_summary(&result, &bam_stat_result, &thresholds)), + outputs: written, + }) +} + +/// Build the JSON summary block for a `dna` run. +fn dna_summary( + result: &rustqc::dna::mosdepth::MosdepthResult, + bam_stat: &rustqc::common::bam_stat::BamStatResult, + thresholds: &[u32], +) -> summary::DnaSummary { + let genome_length = result.total_length(); + let histogram = + rustqc::dna::mosdepth::merge_histograms(result.contigs.iter().map(|c| &c.histogram)); + + let coverage_thresholds = thresholds + .iter() + .map(|threshold| { + let at_or_above: u64 = histogram + .iter() + .filter(|(depth, _)| *depth >= threshold) + .map(|(_, count)| count) + .sum(); + summary::CoverageThreshold { + threshold: *threshold, + pct_bases: if genome_length == 0 { + 0.0 + } else { + at_or_above as f64 * 100.0 / genome_length as f64 + }, + } + }) + .collect(); + + // Median: walk the depth histogram until half the reference is behind us. + let mut seen = 0u64; + let mut median = 0u32; + for (depth, count) in &histogram { + seen += count; + if seen * 2 >= genome_length { + median = *depth; + break; + } + } + + let duplicate_pct = if bam_stat.total_records == 0 { + 0.0 + } else { + bam_stat.duplicates as f64 * 100.0 / bam_stat.total_records as f64 + }; + + summary::DnaSummary { + genome_length, + covered_bases: result.total_bases(), + mean_coverage: result.mean(), + median_coverage: median, + max_coverage: result.max(), + coverage_thresholds, + total_reads: bam_stat.total_records, + duplicates: bam_stat.duplicates, + duplicate_pct, + } +} + +/// How many contig depth arrays may be live at once. +/// +/// Each worker holds four bytes per base of its contig, so the largest contig +/// sets the per-worker cost: about 1 GB for GRCh38 chr1. There is no portable +/// way to ask the operating system how much memory is free, so the budget is a +/// fixed 4 GB unless the user overrides it with `--max-depth-workers`. +fn depth_worker_budget(threads: usize, override_value: Option, largest: u64) -> usize { + const BUDGET_BYTES: u64 = 4 * 1024 * 1024 * 1024; + if let Some(value) = override_value { + return value.max(1); } + let per_worker = largest.saturating_mul(4).max(1); + let affordable = (BUDGET_BYTES / per_worker).max(1) as usize; + threads.min(affordable).max(1) } /// Reconstruct the command line for the featureCounts-compatible header comment. @@ -618,6 +1108,7 @@ fn run_rna(args: cli::RnaArgs, ui: &Ui) -> Result<()> { runtime_seconds: 0.0, counting: None, dupradar: None, + dna: None, outputs: vec![], }); } @@ -902,6 +1393,7 @@ impl BamResult { runtime_seconds: self.duration.as_secs_f64(), counting, dupradar, + dna: None, outputs: self .outputs .iter() @@ -1562,7 +2054,7 @@ fn write_rseqc_outputs( if params.config.flagstat.enabled { std::fs::create_dir_all(&samtools_dir)?; let flagstat_path = samtools_dir.join(format!("{}.flagstat", sample_name)); - rna::rseqc::flagstat::write_flagstat(result, &flagstat_path)?; + common::samtools::flagstat::write_flagstat(result, &flagstat_path)?; let p = flagstat_path.display().to_string(); ui.output_item("flagstat", &p); written.push(("flagstat".into(), p)); @@ -1572,7 +2064,7 @@ fn write_rseqc_outputs( if params.config.idxstats.enabled { std::fs::create_dir_all(&samtools_dir)?; let idxstats_path = samtools_dir.join(format!("{}.idxstats", sample_name)); - rna::rseqc::idxstats::write_idxstats(result, bam_header_refs, &idxstats_path)?; + common::samtools::idxstats::write_idxstats(result, bam_header_refs, &idxstats_path)?; let p = idxstats_path.display().to_string(); ui.output_item("idxstats", &p); written.push(("idxstats".into(), p)); @@ -1582,7 +2074,7 @@ fn write_rseqc_outputs( if params.config.samtools_stats.enabled { std::fs::create_dir_all(&samtools_dir)?; let stats_path = samtools_dir.join(format!("{}.stats", sample_name)); - rna::rseqc::stats::write_stats(result, &stats_path)?; + common::samtools::stats::write_stats(result, &stats_path)?; let p = stats_path.display().to_string(); ui.output_item("stats", &p); written.push(("samtools stats".into(), p)); diff --git a/src/rna/mod.rs b/src/rna/mod.rs index 7dbcc094..29b50b64 100644 --- a/src/rna/mod.rs +++ b/src/rna/mod.rs @@ -3,10 +3,12 @@ //! Contains dupRadar duplication rate analysis, featureCounts-compatible output, //! and RSeQC tool reimplementations. -pub mod bam_flags; -pub mod cpp_rng; pub mod dupradar; pub mod featurecounts; -pub mod preseq; pub mod qualimap; pub mod rseqc; + +// These analyses are not RNA-specific and now live in `crate::common`. +// Re-exported here so existing `crate::rna::...` paths and the published +// 0.2.x library surface keep resolving. Drop the shims at 1.0. +pub use crate::common::{bam_flags, cpp_rng, preseq}; diff --git a/src/rna/rseqc/accumulators.rs b/src/rna/rseqc/accumulators.rs index b91a409e..20f2b964 100644 --- a/src/rna/rseqc/accumulators.rs +++ b/src/rna/rseqc/accumulators.rs @@ -11,10 +11,8 @@ use anyhow::Result; use indexmap::IndexMap; use rust_htslib::bam; -use super::bam_stat::{BamStatResult, GcDepthBin}; - -/// Default GC-depth bin size in base pairs (matches upstream samtools default). -const GCD_BIN_SIZE: u64 = 20_000; +// BamStatAccum is read-level and assay-agnostic; it lives in `crate::common` +// and is shared with the dna pipeline. Re-exported so existing paths resolve. use super::common::{self, KnownJunctionSet, ReferenceJunctions}; use super::infer_experiment::{GeneModel, InferExperimentResult}; use super::inner_distance::{ @@ -25,6 +23,7 @@ use super::junction_saturation::SaturationResult; use super::read_distribution::{ChromIntervals, ReadDistributionResult, RegionSets}; use super::read_duplication::ReadDuplicationResult; use super::tin::TinAccum; +pub use crate::common::bam_stat_accum::BamStatAccum; use crate::rna::preseq::PreseqAccum; use crate::rna::bam_flags::*; @@ -116,1200 +115,6 @@ pub struct RseqcConfig { // Per-tool accumulators // =================================================================== -/// bam_stat accumulator — simple flag/MAPQ counting. -/// -/// Also collects the additional counters needed for samtools-compatible -/// flagstat, idxstats, and stats output. -#[derive(Debug)] -pub struct BamStatAccum { - // --- RSeQC bam_stat fields (original) --- - /// Total BAM records seen (primary + secondary + supplementary + unmapped). - pub total_records: u64, - /// Records with QC-fail flag (0x200). - pub qc_failed: u64, - /// Records with duplicate flag (0x400). - pub duplicates: u64, - /// Secondary alignment records (0x100). RSeQC calls these "non-primary". - pub non_primary: u64, - /// Unmapped reads (0x4). - pub unmapped: u64, - /// Mapped reads with MAPQ < cutoff. - pub non_unique: u64, - /// Mapped reads with MAPQ >= cutoff (uniquely mapped). - pub unique: u64, - /// Among unique reads: read1 in a pair. - pub read_1: u64, - /// Among unique reads: read2 in a pair. - pub read_2: u64, - /// Among unique reads: forward strand. - pub forward: u64, - /// Among unique reads: reverse strand. - pub reverse: u64, - /// Among unique reads: has splice junction (CIGAR N). - pub splice: u64, - /// Among unique reads: no splice junctions. - pub non_splice: u64, - /// Among unique reads: in proper pairs (0x2). - pub proper_pairs: u64, - /// Among proper-paired unique reads: mates on different chromosomes. - pub proper_pair_diff_chrom: u64, - - // --- samtools flagstat additional fields --- - /// Secondary alignments (0x100) — counted independently of QC/dup. - pub secondary: u64, - /// Supplementary alignments (0x800) — counted independently of QC/dup. - pub supplementary: u64, - /// All mapped records (not 0x4), regardless of QC/dup. - pub mapped: u64, - /// Paired reads (0x1), regardless of QC/dup. - pub paired_flagstat: u64, - /// Read1 in pair (0x40), regardless of QC/dup — for flagstat. - pub read1_flagstat: u64, - /// Read2 in pair (0x80), regardless of QC/dup — for flagstat. - pub read2_flagstat: u64, - /// First fragments for samtools stats: primary reads that are not "last fragments". - pub first_fragments: u64, - /// Last fragments for samtools stats: primary reads with 0x80 flag. - pub last_fragments: u64, - /// Properly paired reads (0x1 + 0x2), regardless of QC/dup. - pub properly_paired: u64, - /// Both mates mapped (paired + both !unmapped). - pub both_mapped: u64, - /// Singletons (paired, this mapped, mate unmapped). - pub singletons: u64, - /// Paired, both mapped, different reference. - pub mate_diff_chr: u64, - /// Paired, both mapped, different reference, MAPQ >= 5. - pub mate_diff_chr_mapq5: u64, - - // --- samtools idxstats additional fields --- - /// Per-reference (tid) mapped and unmapped counts. - pub chrom_counts: HashMap, - /// Unmapped reads with no reference (tid < 0). - pub unplaced_unmapped: u64, - - // --- samtools stats SN additional fields --- - /// Sum of query sequence lengths for all primary reads (non-secondary, non-supplementary). - pub total_len: u64, - /// Sum of first fragment (read1 or unpaired) sequence lengths. - pub total_first_fragment_len: u64, - /// Sum of last fragment (read2) sequence lengths. - pub total_last_fragment_len: u64, - /// Sum of query lengths for mapped primary reads. - pub bases_mapped: u64, - /// Sum of M/=/X CIGAR operations for mapped primary reads. - pub bases_mapped_cigar: u64, - /// Sum of query lengths for duplicate-flagged primary reads. - pub bases_duplicated: u64, - /// Maximum query sequence length (among primary reads). - pub max_len: u64, - /// Maximum first-fragment sequence length. - pub max_first_fragment_len: u64, - /// Maximum last-fragment sequence length. - pub max_last_fragment_len: u64, - /// Sum of average per-read base qualities (for average-of-averages). - pub quality_sum: f64, - /// Number of reads contributing to quality_sum (primary, non-QC-fail). - pub quality_count: u64, - /// Sum of NM tag values across mapped primary reads. - pub mismatches: u64, - /// Insert size with orientation: abs_tlen → [total, inward, outward, other]. - /// Only one mate per pair contributes (upstream mate), capped at 8000. - pub is_hist: HashMap, - /// Inward-oriented pairs (FR). - pub inward_pairs: u64, - /// Outward-oriented pairs (RF). - pub outward_pairs: u64, - /// Other orientation pairs (FF, RR). - pub other_orientation: u64, - /// Total primary reads (non-secondary, non-supplementary). - pub primary_count: u64, - /// Primary mapped reads count (non-secondary, non-supplementary, !unmapped). - pub primary_mapped: u64, - /// Primary duplicate reads. - pub primary_duplicates: u64, - /// Primary mapped reads with MAPQ = 0 (matching upstream samtools stats). - pub reads_mq0: u64, - /// Primary non-QC-fail mapped paired reads where mate is also mapped. - pub reads_mapped_and_paired: u64, - - // --- samtools stats histogram/distribution fields --- - /// Read length histogram (all primary reads): length → count. - pub rl_hist: HashMap, - /// First fragment read length histogram: length → count. - pub frl_hist: HashMap, - /// Last fragment read length histogram: length → count. - pub lrl_hist: HashMap, - /// MAPQ histogram: primary, mapped, !qcfail, !dup (quality 0-255). - pub mapq_hist: [u64; 256], - /// Per-cycle quality for first fragments (primary, mapped, !qcfail, !dup). - /// Outer: cycle index. Inner: quality value → count (64 buckets covers Q0-Q63). - pub ffq: Vec<[u64; 64]>, - /// Per-cycle quality for last fragments. - pub lfq: Vec<[u64; 64]>, - /// GC content step-function for first fragments, 200 bins (matching samtools ngc=200). - /// Each bin i stores the number of reads with gc_count * 199 / seq_len <= i. - pub gcf: [u64; 200], - /// GC content step-function for last fragments, 200 bins. - pub gcl: [u64; 200], - /// Per-cycle base composition for first fragments (primary, mapped, !qcfail, !dup). - /// [A, C, G, T, N, Other] per cycle. - pub fbc: Vec<[u64; 6]>, - /// Per-cycle base composition for last fragments. - pub lbc: Vec<[u64; 6]>, - /// Per-cycle base composition (read-oriented) for first fragments. - /// Reverse strand reads contribute in reversed cycle order. - pub fbc_ro: Vec<[u64; 6]>, - /// Per-cycle base composition (read-oriented) for last fragments. - pub lbc_ro: Vec<[u64; 6]>, - /// Per-cycle base composition (reverse-complemented for reverse-strand reads, - /// combined first+last fragments). Used for GCT output. [A, C, G, T] only. - pub gcc_rc: Vec<[u64; 4]>, - /// Total base counters for first fragments: [A, C, G, T, N]. - pub ftc: [u64; 5], - /// Total base counters for last fragments: [A, C, G, T, N]. - pub ltc: [u64; 5], - /// Indel distribution by size: length → [insertions, deletions]. - pub id_hist: HashMap, - /// Indels per cycle: cycle → [ins_fwd, ins_rev, del_fwd, del_rev]. - pub ic: Vec<[u64; 4]>, - /// CRC32 checksum sums: [names, sequences, qualities]. - /// Each is the wrapping u32 sum of per-read CRC32 values. - pub chk: [u32; 3], - /// Coverage distribution: depth → number of reference positions at that depth. - /// Populated from a round buffer pileup during sorted BAM processing. - pub cov_hist: HashMap, - /// Circular buffer for coverage pileup, matching upstream samtools design. - /// `cov_buf[cov_buf_idx]` corresponds to reference position `cov_buf_pos`. - /// The buffer grows dynamically to accommodate `max_read_length * 5`. - cov_buf: Vec, - /// Index into `cov_buf` corresponding to `cov_buf_pos`. - cov_buf_idx: usize, - /// Reference position of the element at `cov_buf[cov_buf_idx]`. - cov_buf_pos: i64, - /// Current chromosome tid for round buffer tracking. - cov_buf_tid: i32, - - // --- GC-depth (GCD section) fields --- - /// Accumulated GC-depth bins (one per `GCD_BIN_SIZE`-bp genomic window). - gcd_bins: Vec, - /// Start position of the current GCD bin. - gcd_pos: i64, - /// Chromosome tid of the current GCD bin. - gcd_tid: i32, -} - -impl Default for BamStatAccum { - fn default() -> Self { - Self { - total_records: 0, - qc_failed: 0, - duplicates: 0, - non_primary: 0, - unmapped: 0, - non_unique: 0, - unique: 0, - read_1: 0, - read_2: 0, - forward: 0, - reverse: 0, - splice: 0, - non_splice: 0, - proper_pairs: 0, - proper_pair_diff_chrom: 0, - secondary: 0, - supplementary: 0, - mapped: 0, - paired_flagstat: 0, - read1_flagstat: 0, - read2_flagstat: 0, - first_fragments: 0, - last_fragments: 0, - properly_paired: 0, - both_mapped: 0, - singletons: 0, - mate_diff_chr: 0, - mate_diff_chr_mapq5: 0, - chrom_counts: HashMap::new(), - unplaced_unmapped: 0, - total_len: 0, - total_first_fragment_len: 0, - total_last_fragment_len: 0, - bases_mapped: 0, - bases_mapped_cigar: 0, - bases_duplicated: 0, - max_len: 0, - max_first_fragment_len: 0, - max_last_fragment_len: 0, - quality_sum: 0.0, - quality_count: 0, - mismatches: 0, - is_hist: HashMap::new(), - inward_pairs: 0, - outward_pairs: 0, - other_orientation: 0, - primary_count: 0, - primary_mapped: 0, - primary_duplicates: 0, - reads_mq0: 0, - reads_mapped_and_paired: 0, - rl_hist: HashMap::new(), - frl_hist: HashMap::new(), - lrl_hist: HashMap::new(), - mapq_hist: [0u64; 256], - ffq: Vec::new(), - lfq: Vec::new(), - gcf: [0u64; 200], - gcl: [0u64; 200], - fbc: Vec::new(), - lbc: Vec::new(), - fbc_ro: Vec::new(), - lbc_ro: Vec::new(), - gcc_rc: Vec::new(), - ftc: [0u64; 5], - ltc: [0u64; 5], - id_hist: HashMap::new(), - ic: Vec::new(), - chk: [0u32; 3], - cov_hist: HashMap::new(), - cov_buf: vec![0u32; 1500], // matches upstream samtools: nbases * 5 = 300 * 5 - cov_buf_idx: 0, - cov_buf_pos: 0, - cov_buf_tid: -1, - gcd_bins: Vec::new(), - gcd_pos: -1, - gcd_tid: -1, - } - } -} - -impl BamStatAccum { - /// Process a single BAM record. Called for EVERY record (before counting filters). - /// - /// Collects counters for: - /// - RSeQC bam_stat (original cascade with early returns) - /// - samtools flagstat (counts all records independently) - /// - samtools idxstats (per-reference mapped/unmapped counts) - /// - samtools stats SN section (sequence lengths, quality, insert size, etc.) - pub fn process_read(&mut self, record: &bam::Record, mapq_cut: u8) { - let flags = record.flags(); - self.total_records += 1; - - let is_secondary = flags & BAM_FSECONDARY != 0; - let is_supplementary = flags & BAM_FSUPPLEMENTARY != 0; - let is_unmapped = flags & BAM_FUNMAP != 0; - let is_paired = flags & BAM_FPAIRED != 0; - let is_dup = flags & BAM_FDUP != 0; - let is_qcfail = flags & BAM_FQCFAIL != 0; - let is_primary = !is_secondary && !is_supplementary; - let is_mapped = !is_unmapped; - let tid = record.tid(); - let mapq = record.mapq(); - - // ================================================================= - // samtools flagstat counters (count ALL records, no early returns) - // ================================================================= - if is_secondary { - self.secondary += 1; - } - if is_supplementary { - self.supplementary += 1; - } - if is_mapped { - self.mapped += 1; - } - // samtools stats: "1st fragments" / "last fragments" count primary reads only - // For paired reads: read2 flag -> last, everything else -> 1st - // For SE reads (no PAIRED flag): all counted as 1st fragments - if is_primary { - if flags & BAM_FREAD2 != 0 { - self.last_fragments += 1; - } else { - self.first_fragments += 1; - } - } - // samtools flagstat: paired-read metrics count PRIMARY reads only - // (secondary/supplementary are excluded from paired/read1/read2/properly-paired counts) - if is_paired && is_primary { - self.paired_flagstat += 1; - if flags & BAM_FREAD1 != 0 { - self.read1_flagstat += 1; - } - if flags & BAM_FREAD2 != 0 { - self.read2_flagstat += 1; - } - if flags & BAM_FPROPER_PAIR != 0 { - self.properly_paired += 1; - } - let mate_unmapped = flags & BAM_FMUNMAP != 0; - if is_mapped && !mate_unmapped { - self.both_mapped += 1; - if tid != record.mtid() { - self.mate_diff_chr += 1; - if mapq >= 5 { - self.mate_diff_chr_mapq5 += 1; - } - } - } - if is_mapped && mate_unmapped { - self.singletons += 1; - } - } - - // ================================================================= - // samtools idxstats counters (per-reference) - // ================================================================= - if is_unmapped { - if tid >= 0 { - // Unmapped read placed on a reference (has tid) - self.chrom_counts.entry(tid).or_insert((0, 0)).1 += 1; - } else { - self.unplaced_unmapped += 1; - } - } else if tid >= 0 { - // Mapped read - self.chrom_counts.entry(tid).or_insert((0, 0)).0 += 1; - } - - // ================================================================= - // CHK checksums: computed on ALL reads (including secondary and - // supplementary). Matches samtools stats.c update_checksum() which - // is called before the secondary-read early return. - // ================================================================= - { - let qname = record.qname(); - let name_crc = crc32fast::hash(qname); - self.chk[0] = self.chk[0].wrapping_add(name_crc); - - let seq_len = record.seq_len(); - if seq_len > 0 { - // SAFETY: We access the raw BAM record data to compute CRC32 - // checksums matching samtools' approach. The pointer arithmetic - // replicates htslib's bam_get_seq() macro: - // data + l_qname + (n_cigar << 2) - // The seq_len > 0 guard above ensures sequence data exists. - // The slice length seq_len.div_ceil(2) matches the BAM spec's - // 4-bit encoded sequence format: (seq_len+1)/2 bytes. - let seq_bytes = unsafe { - let inner = record.inner(); - let data = inner.data; - let seq_offset = - inner.core.l_qname as isize + ((inner.core.n_cigar as isize) << 2); - let seq_nbytes = seq_len.div_ceil(2); - std::slice::from_raw_parts(data.offset(seq_offset), seq_nbytes) - }; - let seq_crc = crc32fast::hash(seq_bytes); - self.chk[1] = self.chk[1].wrapping_add(seq_crc); - - let qual = record.qual(); - let qual_crc = crc32fast::hash(qual); - self.chk[2] = self.chk[2].wrapping_add(qual_crc); - } - } - - // Track gc_count from the primary-read per-cycle loop so the GCD - // section below can reuse it without re-scanning the sequence. - let mut primary_gc_count: u64 = 0; - - // ================================================================= - // samtools stats SN counters (primary reads only) - // ================================================================= - if is_primary { - self.primary_count += 1; - let seq_len = record.seq_len() as u64; - let mate_unmapped = flags & BAM_FMUNMAP != 0; - - self.total_len += seq_len; - let is_last_fragment = is_paired && flags & BAM_FREAD2 != 0; - if is_last_fragment { - self.total_last_fragment_len += seq_len; - if seq_len > self.max_last_fragment_len { - self.max_last_fragment_len = seq_len; - } - } else { - self.total_first_fragment_len += seq_len; - if seq_len > self.max_first_fragment_len { - self.max_first_fragment_len = seq_len; - } - } - if seq_len > self.max_len { - self.max_len = seq_len; - } - - // RL/FRL/LRL: read length histograms (all primary reads) - *self.rl_hist.entry(seq_len).or_insert(0) += 1; - if is_last_fragment { - *self.lrl_hist.entry(seq_len).or_insert(0) += 1; - } else { - *self.frl_hist.entry(seq_len).or_insert(0) += 1; - } - - if is_dup { - self.primary_duplicates += 1; - self.bases_duplicated += seq_len; - } - // "reads mapped and paired" for samtools stats: primary, non-QC-fail, - // mapped, paired, mate also mapped - if is_mapped && is_paired && !is_qcfail && !mate_unmapped { - self.reads_mapped_and_paired += 1; - } - if is_mapped { - self.primary_mapped += 1; - self.bases_mapped += seq_len; - - // samtools stats: reads MQ0 counts primary mapped reads with MAPQ=0 - // (upstream stats.c: MQ0 is counted inside collect_orig_read_stats, - // which is only called for IS_ORIGINAL reads = non-secondary, non-supplementary) - if record.mapq() == 0 { - self.reads_mq0 += 1; - } - - // NOTE: bases_mapped_cigar is now computed in the IC/ID CIGAR - // loop below (for all mapped non-secondary reads) to avoid a - // separate full CIGAR traversal here. - - // NM tag (edit distance) - if let Ok(rust_htslib::bam::record::Aux::U8(nm)) = record.aux(b"NM") { - self.mismatches += u64::from(nm); - } else if let Ok(rust_htslib::bam::record::Aux::U16(nm)) = record.aux(b"NM") { - self.mismatches += u64::from(nm); - } else if let Ok(rust_htslib::bam::record::Aux::U32(nm)) = record.aux(b"NM") { - self.mismatches += u64::from(nm); - } else if let Ok(rust_htslib::bam::record::Aux::I8(nm)) = record.aux(b"NM") { - if nm > 0 { - self.mismatches += nm as u64; - } - } else if let Ok(rust_htslib::bam::record::Aux::I16(nm)) = record.aux(b"NM") { - if nm > 0 { - self.mismatches += nm as u64; - } - } else if let Ok(rust_htslib::bam::record::Aux::I32(nm)) = record.aux(b"NM") { - if nm > 0 { - self.mismatches += nm as u64; - } - } - - // Insert size + orientation for paired primary reads where both - // mates are mapped. Matches samtools stats gate: - // IS_PAIRED_AND_MAPPED && IS_ORIGINAL - // if (isize > 0 || tid == mtid) - // Both mates contribute; samtools divides by 2 at output. - // We do the same in write_insert_size() and the SN section. - if is_paired && !mate_unmapped { - let tid = record.tid(); - let mtid = record.mtid(); - let tlen = record.insert_size(); - let abs_tlen = tlen.unsigned_abs(); - - if abs_tlen > 0 || tid == mtid { - let pos = record.pos(); - let mpos = record.mpos(); - - // Compute orientation (only meaningful for same-chromosome) - let pos_fst = mpos - pos; - let is_fst: i64 = if flags & BAM_FREAD1 != 0 { 1 } else { -1 }; - let is_fwd: i64 = if flags & BAM_FREVERSE != 0 { -1 } else { 1 }; - let is_mfwd: i64 = if flags & BAM_FMREVERSE != 0 { -1 } else { 1 }; - - // orientation_idx: 1=inward, 2=outward, 3=other - let orientation_idx = if is_fwd * is_mfwd > 0 { - self.other_orientation += 1; - 3usize - } else if is_fst * pos_fst > 0 { - if is_fst * is_fwd > 0 { - self.inward_pairs += 1; - 1usize - } else { - self.outward_pairs += 1; - 2usize - } - } else if is_fst * pos_fst < 0 { - if is_fst * is_fwd > 0 { - self.outward_pairs += 1; - 2usize - } else { - self.inward_pairs += 1; - 1usize - } - } else { - self.inward_pairs += 1; - 1usize - }; - - if abs_tlen > 0 { - // Cap at MAX_INSERT_SIZE (8000), matching - // samtools stats which accumulates overflow - // into the cap bucket. - let capped = abs_tlen.min(8000); - let entry = self.is_hist.entry(capped).or_insert([0; 4]); - entry[0] += 1; // total - entry[orientation_idx] += 1; - } - } - } - } - - // Average quality for primary non-QC-fail reads. - // Upstream samtools stats computes per-BASE quality average: - // sum of all individual base qualities / total bases. - // (Not a per-read average of averages.) - if !is_qcfail { - let quals = record.qual(); - if !quals.is_empty() { - let base_qual_sum: f64 = quals.iter().map(|&q| f64::from(q)).sum::(); - self.quality_sum += base_qual_sum; - self.quality_count += quals.len() as u64; - } - } - - // ============================================================= - // MAPQ histogram: primary + mapped + !qcfail + !dup - // (matches samtools stats.c:1239 five-flag exclusion) - // ============================================================= - if is_mapped && !is_qcfail && !is_dup { - self.mapq_hist[mapq as usize] += 1; - } - - // ============================================================= - // Per-cycle quality & base composition histograms: - // FFQ/LFQ, FBC/LBC, GCF/GCL, FTC/LTC, FBC_RO/LBC_RO - // - // Upstream samtools stats includes duplicates, unmapped, and - // qcfail reads in these histograms (collect_orig_read_stats - // has no such checks). Only secondary+supplementary are - // excluded (via IS_ORIGINAL), which is already handled by - // the outer is_primary guard. - // ============================================================= - { - let is_reverse = flags & BAM_FREVERSE != 0; - - let seq = record.seq(); - let quals = record.qual(); - let read_len = seq.len(); - - // Determine which arrays to use (first vs last fragment) - // If paired: read2 = last, read1 = first. If SE: all = first. - let (qual_arr, base_arr, base_ro_arr, gc_arr, tc_arr) = if is_last_fragment { - ( - &mut self.lfq, - &mut self.lbc, - &mut self.lbc_ro, - &mut self.gcl, - &mut self.ltc, - ) - } else { - ( - &mut self.ffq, - &mut self.fbc, - &mut self.fbc_ro, - &mut self.gcf, - &mut self.ftc, - ) - }; - - // Ensure per-cycle arrays are large enough - if read_len > qual_arr.len() { - qual_arr.resize(read_len, [0u64; 64]); - } - if read_len > base_arr.len() { - base_arr.resize(read_len, [0u64; 6]); - } - if read_len > base_ro_arr.len() { - base_ro_arr.resize(read_len, [0u64; 6]); - } - if read_len > self.gcc_rc.len() { - self.gcc_rc.resize(read_len, [0u64; 4]); - } - - let mut gc_count: u64 = 0; - - // Pre-built lookup tables for the per-cycle inner loop, - // avoiding branches and match overhead on every base. - // - // BAM 4-bit encoding: A=1, C=2, G=4, T=8, N=15, others=0,3,5..14 - // BASE_IDX[nibble] → 0=A, 1=C, 2=G, 3=T, 4=N, 5=Other - const BASE_IDX: [u8; 16] = [5, 0, 1, 5, 2, 5, 5, 5, 3, 5, 5, 5, 5, 5, 5, 4]; - // RC_IDX[base_idx] → reverse-complement base_idx (A↔T, C↔G) - // Only meaningful for base_idx 0-3 (ACGT). Index 4/5 not used. - const RC_IDX: [u8; 6] = [3, 2, 1, 0, 4, 5]; // A→T, C→G, G→C, T→A - - // Hoist the is_reverse branch outside the inner loop so the - // compiler can version the loop and potentially auto-vectorize - // each variant independently. - if !is_reverse { - for i in 0..read_len { - let q = quals[i] as usize; - qual_arr[i][q.min(63)] += 1; - - let base_idx = BASE_IDX[seq.encoded_base(i) as usize] as usize; - base_arr[i][base_idx] += 1; - base_ro_arr[i][base_idx] += 1; - if base_idx < 4 { - self.gcc_rc[i][base_idx] += 1; - } - if base_idx == 1 || base_idx == 2 { - gc_count += 1; - } - if base_idx < 5 { - tc_arr[base_idx] += 1; - } - } - } else { - for i in 0..read_len { - let ro_cycle = read_len - 1 - i; - let q = quals[i] as usize; - qual_arr[ro_cycle][q.min(63)] += 1; - - let base_idx = BASE_IDX[seq.encoded_base(i) as usize] as usize; - base_arr[i][base_idx] += 1; - base_ro_arr[ro_cycle][base_idx] += 1; - if base_idx < 4 { - self.gcc_rc[ro_cycle][RC_IDX[base_idx] as usize] += 1; - } - if base_idx == 1 || base_idx == 2 { - gc_count += 1; - } - if base_idx < 5 { - tc_arr[base_idx] += 1; - } - } - } - - // Save gc_count for GCD section below (avoids re-scanning the sequence). - primary_gc_count = gc_count; - - // GC content: cumulative step function with ngc=200 bins. - // Matches samtools stats.c:925-941. For a read with gc_count G/C - // bases out of read_len total, increment bins gc_idx_min..gc_idx_max. - let ngc: usize = 200; - if let (Some(gc_idx_min), Some(gc_idx_max)) = ( - (gc_count as usize * (ngc - 1)).checked_div(read_len), - ((gc_count as usize + 1) * (ngc - 1)).checked_div(read_len), - ) { - let gc_idx_max = gc_idx_max.min(ngc - 1); - for item in gc_arr.iter_mut().take(gc_idx_max).skip(gc_idx_min) { - *item += 1; - } - } - } - } // if is_primary - - // ============================================================= - // Indel distribution (ID) and indels per cycle (IC) from CIGAR. - // - // Upstream samtools stats calls count_indels() AFTER the - // secondary-read early return (line 1206-1210) and the - // IS_UNMAPPED return (line 1255), but OUTSIDE IS_ORIGINAL(). - // This means: all mapped, non-secondary reads are included - // (supplementary, duplicate, qcfail all contribute). - // - // IC uses first-fragment/last-fragment read order (not - // forward/reverse strand) and read-oriented cycle indices, - // matching upstream count_indels(). - // ============================================================= - // ============================================================= - // Combined single-CIGAR-pass block for IC/ID (indel distribution), - // bases_mapped_cigar, and COV (coverage ring-buffer pileup). - // - // Both IC/ID and COV apply to the same read set (mapped, - // non-secondary). Merging them into one CIGAR traversal - // eliminates two redundant record.cigar() calls per read. - // - // IC/ID: Upstream samtools stats calls count_indels() outside - // IS_ORIGINAL() — supplementary/dup/qcfail all contribute. - // IC uses first/last-fragment order and read-oriented cycles. - // - // COV: Circular-buffer pileup; buffer flushed up to read start - // before CIGAR walk; M/=/X blocks inserted as ranges. - // Buffer grown to max_read_len * 5 as needed. - // ============================================================= - if is_mapped && !is_secondary { - use rust_htslib::bam::record::Cigar as C; - let is_reverse = flags & BAM_FREVERSE != 0; - let read_len = record.seq_len(); - let tid = record.tid(); - let pos = record.pos(); // 0-based - - // Upstream order: paired ? (read1?FIRST:0)+(read2?LAST:0) : FIRST - let order: u32 = if is_paired { - (if flags & BAM_FREAD1 != 0 { 1 } else { 0 }) - + (if flags & BAM_FREAD2 != 0 { 2 } else { 0 }) - } else { - 1 // unpaired → FIRST - }; - - // COV buffer setup (must happen before CIGAR walk). - // Skip reads with no sequence (upstream samtools early-return). - let do_cov = read_len > 0; - let buf_size = if do_cov { - // Grow buffer to max_read_len * 5 if needed. - // When growing, linearise the circular data just like - // upstream samtools: copy [idx..old_size] then [0..idx] - // into a fresh buffer, and reset idx to 0. - let need = read_len * 5; - if need > self.cov_buf.len() { - let old_size = self.cov_buf.len(); - let mut new_buf = vec![0u32; need]; - let head = old_size - self.cov_buf_idx; - new_buf[..head].copy_from_slice(&self.cov_buf[self.cov_buf_idx..]); - new_buf[head..head + self.cov_buf_idx] - .copy_from_slice(&self.cov_buf[..self.cov_buf_idx]); - self.cov_buf = new_buf; - self.cov_buf_idx = 0; - } - let bs = self.cov_buf.len(); - // Flush entire buffer on chromosome change - if tid != self.cov_buf_tid { - self.flush_cov_buf_all(); - self.cov_buf_tid = tid; - self.cov_buf_pos = pos; - self.cov_buf_idx = 0; - } - // Flush positions from cov_buf_pos up to read start - self.cov_buf_flush_to(pos, bs); - bs - } else { - 0 - }; - - // Single CIGAR traversal serving IC/ID + bases_mapped_cigar + COV - let cigar = record.cigar(); - let mut icycle: usize = 0; - let mut cigar_mapped: u64 = 0; - let mut ref_pos = pos; - - for op in cigar.iter() { - match op { - C::Ins(n) => { - let ncig = *n as usize; - let len = *n as u64; - cigar_mapped += len; // I counts toward bases_mapped_cigar - - // ID: indel size distribution - let id_entry = self.id_hist.entry(len).or_insert([0; 2]); - id_entry[0] += 1; // insertions - - // IC: indels per cycle (read-oriented index) - let idx = if is_reverse { - read_len.saturating_sub(icycle + ncig) - } else { - icycle - }; - if idx >= self.ic.len() { - self.ic.resize(idx + 1, [0u64; 4]); - } - if order == 1 { - self.ic[idx][0] += 1; // ins_1st - } - if order == 2 { - self.ic[idx][1] += 1; // ins_2nd - } - - icycle += ncig; // I advances query cycle; ref unchanged - // COV: I consumes no reference positions - } - C::Del(n) => { - let len = *n as u64; - // ID: indel size distribution - let id_entry = self.id_hist.entry(len).or_insert([0; 2]); - id_entry[1] += 1; // deletions - - // IC: indels per cycle (read-oriented index) - let idx = if is_reverse { - if icycle == 0 { - // Discard meaningless deletions at cycle 0 - // (upstream: "if (idx<0) continue;") - ref_pos += *n as i64; // still advance ref for COV - continue; - } - read_len.saturating_sub(icycle + 1) - } else { - if icycle == 0 { - ref_pos += *n as i64; - continue; - } - icycle - 1 - }; - if idx >= self.ic.len() { - self.ic.resize(idx + 1, [0u64; 4]); - } - if order == 1 { - self.ic[idx][2] += 1; // del_1st - } - if order == 2 { - self.ic[idx][3] += 1; // del_2nd - } - // D does NOT advance query cycle; does advance ref - ref_pos += *n as i64; - } - C::Match(n) | C::Equal(n) | C::Diff(n) => { - let len = *n as u64; - cigar_mapped += len; // M/=/X count toward bases_mapped_cigar - icycle += *n as usize; - // COV: M/=/X consumes reference positions - if do_cov { - let end = ref_pos + *n as i64; - self.cov_buf_insert(ref_pos, end, buf_size); - ref_pos = end; - } else { - ref_pos += *n as i64; - } - } - C::RefSkip(n) => { - ref_pos += *n as i64; // N advances ref (COV skips it) - } - C::SoftClip(n) => { - icycle += *n as usize; // S advances query cycle - // COV: S consumes no reference positions - } - C::HardClip(_) | C::Pad(_) => {} - } - } - self.bases_mapped_cigar += cigar_mapped; - } // if is_mapped && !is_secondary (IC/ID + COV combined) - - // ============================================================= - // GCD: GC-depth accumulation (no-reference path). - // - // Matches upstream samtools stats without --ref-seq: bins of - // GCD_BIN_SIZE bp, depth incremented for each read, GC fraction - // accumulated from the read's sequence. - // - // Included reads: mapped, non-secondary (same as COV). - // - // NOTE: gc_count_for_gcd is set from the primary-read per-cycle - // loop above (when is_primary is true), or computed here only for - // non-primary mapped reads, avoiding a redundant full sequence scan. - // ============================================================= - if is_mapped && !is_secondary { - let tid = record.tid(); - let pos = record.pos(); - let seq_len = record.seq_len(); - - if seq_len > 0 { - // Start a new bin on: first read, chromosome change, or - // read beyond current bin boundary. - let new_bin = self.gcd_pos < 0 - || tid != self.gcd_tid - || pos - self.gcd_pos > GCD_BIN_SIZE as i64; - - if new_bin { - self.gcd_bins.push(GcDepthBin { gc: 0.0, depth: 0 }); - self.gcd_pos = pos; - self.gcd_tid = tid; - } - - // Increment depth and accumulate GC fraction from read seq. - if let Some(bin) = self.gcd_bins.last_mut() { - bin.depth += 1; - // For primary reads, gc_count was already computed in the - // per-cycle base loop above. For non-primary mapped reads - // (supplementary, etc.) compute it here from the sequence. - let gc_count: u32 = if is_primary { - primary_gc_count as u32 - } else { - let seq = record.seq(); - let mut count: u32 = 0; - for i in 0..seq_len { - let base = seq.encoded_base(i); - if base == 2 || base == 4 { - count += 1; - } - } - count - }; - bin.gc += gc_count as f32 / seq_len as f32; - } - } - } // if is_mapped && !is_secondary (GCD) - - // ================================================================= - // RSeQC bam_stat cascade (original logic, with early returns) - // ================================================================= - - // 1. QC-failed - if is_qcfail { - self.qc_failed += 1; - return; - } - - // 2. Duplicate - if is_dup { - self.duplicates += 1; - return; - } - - // 3. Secondary (non-primary) — NOT supplementary - if is_secondary { - self.non_primary += 1; - return; - } - - // 4. Unmapped - if is_unmapped { - self.unmapped += 1; - return; - } - - // 5. MAPQ classification - if mapq < mapq_cut { - self.non_unique += 1; - return; - } - - // Uniquely mapped - self.unique += 1; - - if flags & BAM_FREAD1 != 0 { - self.read_1 += 1; - } - if flags & BAM_FREAD2 != 0 { - self.read_2 += 1; - } - if flags & BAM_FREVERSE != 0 { - self.reverse += 1; - } else { - self.forward += 1; - } - - // Splice detection: CIGAR N operation - let has_splice = record - .cigar() - .iter() - .any(|op| matches!(op, rust_htslib::bam::record::Cigar::RefSkip(_))); - if has_splice { - self.splice += 1; - } else { - self.non_splice += 1; - } - - // Proper pair analysis - if is_paired && flags & BAM_FPROPER_PAIR != 0 { - self.proper_pairs += 1; - if tid != record.mtid() { - self.proper_pair_diff_chrom += 1; - } - } - } - - /// Flush all remaining positions in the coverage round buffer into cov_hist. - /// Must be called after processing all reads (or when switching chromosomes). - /// Flush the circular buffer from `cov_buf_pos` up to (but not including) `pos`. - /// Each slot's depth is recorded in `cov_hist` and the slot is zeroed. - /// Matches upstream `round_buffer_flush` logic from samtools stats.c. - fn cov_buf_flush_to(&mut self, pos: i64, buf_size: usize) { - if pos - self.cov_buf_pos >= buf_size as i64 { - // Gap exceeds buffer size. Match upstream samtools exactly: - // flush `size - 1` positions (from cov_buf_pos to - // cov_buf_pos + size - 2), leaving the LAST slot untouched. - // Then advance idx by `size - 1` and jump pos. - // - // Upstream (stats.c round_buffer_flush lines 334-366): - // pos = rbuf.pos + size - 1; // cap at last slot - // ito = lidx2ridx(start, size, rbuf.pos, pos-1); - // // flush from start to ito (size-1 slots) - // rbuf.start = lidx2ridx(start, size, rbuf.pos, pos); - // rbuf.pos = new_pos; - let flush_count = buf_size - 1; // flush all but the last slot - for _ in 0..flush_count { - let depth = self.cov_buf[self.cov_buf_idx]; - if depth > 0 { - *self.cov_hist.entry(depth).or_insert(0) += 1; - self.cov_buf[self.cov_buf_idx] = 0; - } - self.cov_buf_idx += 1; - if self.cov_buf_idx >= buf_size { - self.cov_buf_idx = 0; - } - } - // idx now points to the ONE unflushed slot (the last position - // in the old window). Jump pos to the new read position. - self.cov_buf_pos = pos; - } else { - // Normal case: flush slot by slot. - while self.cov_buf_pos < pos { - let depth = self.cov_buf[self.cov_buf_idx]; - if depth > 0 { - *self.cov_hist.entry(depth).or_insert(0) += 1; - self.cov_buf[self.cov_buf_idx] = 0; - } - self.cov_buf_idx += 1; - if self.cov_buf_idx >= buf_size { - self.cov_buf_idx = 0; - } - self.cov_buf_pos += 1; - } - } - } - - /// Insert a contiguous reference range `[from, to)` into the circular buffer, - /// incrementing depth for each position. The range must fit within `buf_size`. - fn cov_buf_insert(&mut self, from: i64, to: i64, buf_size: usize) { - for ref_pos in from..to { - // Map ref_pos to buffer index: offset from cov_buf_idx by (ref_pos - cov_buf_pos) - let offset = (ref_pos - self.cov_buf_pos) as usize; - let idx = (self.cov_buf_idx + offset) % buf_size; - self.cov_buf[idx] += 1; - } - } - - /// Flush the entire circular buffer and reset tracking state. - pub fn flush_cov_buf_all(&mut self) { - for slot in self.cov_buf.iter_mut() { - if *slot > 0 { - *self.cov_hist.entry(*slot).or_insert(0) += 1; - *slot = 0; - } - } - self.cov_buf_idx = 0; - self.cov_buf_pos = 0; - self.cov_buf_tid = -1; - } - - /// Merge another accumulator into this one. - pub fn merge(&mut self, mut other: BamStatAccum) { - // Flush any remaining positions in the other's round buffer into its - // cov_hist before merging. Without this, positions still in the - // round buffer would be silently lost during parallel merges. - other.flush_cov_buf_all(); - - // RSeQC bam_stat fields - self.total_records += other.total_records; - self.qc_failed += other.qc_failed; - self.duplicates += other.duplicates; - self.non_primary += other.non_primary; - self.unmapped += other.unmapped; - self.non_unique += other.non_unique; - self.unique += other.unique; - self.read_1 += other.read_1; - self.read_2 += other.read_2; - self.forward += other.forward; - self.reverse += other.reverse; - self.splice += other.splice; - self.non_splice += other.non_splice; - self.proper_pairs += other.proper_pairs; - self.proper_pair_diff_chrom += other.proper_pair_diff_chrom; - - // samtools flagstat fields - self.secondary += other.secondary; - self.supplementary += other.supplementary; - self.mapped += other.mapped; - self.paired_flagstat += other.paired_flagstat; - self.read1_flagstat += other.read1_flagstat; - self.read2_flagstat += other.read2_flagstat; - self.first_fragments += other.first_fragments; - self.last_fragments += other.last_fragments; - self.properly_paired += other.properly_paired; - self.both_mapped += other.both_mapped; - self.singletons += other.singletons; - self.mate_diff_chr += other.mate_diff_chr; - self.mate_diff_chr_mapq5 += other.mate_diff_chr_mapq5; - - // samtools idxstats fields - for (tid, (m, u)) in other.chrom_counts { - let entry = self.chrom_counts.entry(tid).or_insert((0, 0)); - entry.0 += m; - entry.1 += u; - } - self.unplaced_unmapped += other.unplaced_unmapped; - - // samtools stats SN fields - self.total_len += other.total_len; - self.total_first_fragment_len += other.total_first_fragment_len; - self.total_last_fragment_len += other.total_last_fragment_len; - self.bases_mapped += other.bases_mapped; - self.bases_mapped_cigar += other.bases_mapped_cigar; - self.bases_duplicated += other.bases_duplicated; - if other.max_len > self.max_len { - self.max_len = other.max_len; - } - if other.max_first_fragment_len > self.max_first_fragment_len { - self.max_first_fragment_len = other.max_first_fragment_len; - } - if other.max_last_fragment_len > self.max_last_fragment_len { - self.max_last_fragment_len = other.max_last_fragment_len; - } - self.quality_sum += other.quality_sum; - self.quality_count += other.quality_count; - self.mismatches += other.mismatches; - for (isize_val, counts) in other.is_hist { - let entry = self.is_hist.entry(isize_val).or_insert([0; 4]); - for i in 0..4 { - entry[i] += counts[i]; - } - } - self.inward_pairs += other.inward_pairs; - self.outward_pairs += other.outward_pairs; - self.other_orientation += other.other_orientation; - self.primary_count += other.primary_count; - self.primary_mapped += other.primary_mapped; - self.primary_duplicates += other.primary_duplicates; - self.reads_mq0 += other.reads_mq0; - self.reads_mapped_and_paired += other.reads_mapped_and_paired; - - // Histogram/distribution fields - for (len, count) in other.rl_hist { - *self.rl_hist.entry(len).or_insert(0) += count; - } - for (len, count) in other.frl_hist { - *self.frl_hist.entry(len).or_insert(0) += count; - } - for (len, count) in other.lrl_hist { - *self.lrl_hist.entry(len).or_insert(0) += count; - } - for i in 0..256 { - self.mapq_hist[i] += other.mapq_hist[i]; - } - - // Per-cycle quality arrays (FFQ/LFQ) - merge_vec_arrays(&mut self.ffq, other.ffq); - merge_vec_arrays(&mut self.lfq, other.lfq); - - // GC content distributions (200 bins) - for i in 0..200 { - self.gcf[i] += other.gcf[i]; - self.gcl[i] += other.gcl[i]; - } - - // Per-cycle base composition (FBC/LBC and read-oriented) - merge_vec_arrays(&mut self.fbc, other.fbc); - merge_vec_arrays(&mut self.lbc, other.lbc); - merge_vec_arrays(&mut self.fbc_ro, other.fbc_ro); - merge_vec_arrays(&mut self.lbc_ro, other.lbc_ro); - merge_vec_arrays(&mut self.gcc_rc, other.gcc_rc); - - // Total base counters - for i in 0..5 { - self.ftc[i] += other.ftc[i]; - self.ltc[i] += other.ltc[i]; - } - - // Indel distribution - for (len, counts) in other.id_hist { - let entry = self.id_hist.entry(len).or_insert([0; 2]); - entry[0] += counts[0]; - entry[1] += counts[1]; - } - - // Indels per cycle - merge_vec_arrays(&mut self.ic, other.ic); - - // CHK checksums (wrapping u32 addition) - for i in 0..3 { - self.chk[i] = self.chk[i].wrapping_add(other.chk[i]); - } - - // COV histogram (additive merge) - for (depth, count) in other.cov_hist { - *self.cov_hist.entry(depth).or_insert(0) += count; - } - - // GCD bins (concatenate — bins from different chromosome workers - // are independent and will be sorted during output). - self.gcd_bins.append(&mut other.gcd_bins); - } -} - // ------------------------------------------------------------------- // infer_experiment accumulator // ------------------------------------------------------------------- @@ -2327,112 +1132,10 @@ fn point_in(region_map: &HashMap, chrom: &str, point: u6 region_map.get(chrom).is_some_and(|ci| ci.contains(point)) } -// =================================================================== -// Merge helpers for Vec<[u64; N]> per-cycle arrays -// =================================================================== - -/// Merge two `Vec<[u64; N]>` arrays element-wise, extending target if shorter. -fn merge_vec_arrays(target: &mut Vec<[u64; N]>, source: Vec<[u64; N]>) { - if source.len() > target.len() { - target.resize(source.len(), [0u64; N]); - } - for (i, arr) in source.into_iter().enumerate() { - for j in 0..N { - target[i][j] += arr[j]; - } - } -} - // =================================================================== // Converter methods: accumulator → result types for output functions // =================================================================== -impl BamStatAccum { - /// Convert accumulated counters into a `BamStatResult` for output. - pub fn into_result(mut self) -> BamStatResult { - // Flush remaining positions in the coverage round buffer - self.flush_cov_buf_all(); - BamStatResult { - // RSeQC bam_stat fields - total_records: self.total_records, - qc_failed: self.qc_failed, - duplicates: self.duplicates, - non_primary: self.non_primary, - unmapped: self.unmapped, - non_unique: self.non_unique, - unique: self.unique, - read_1: self.read_1, - read_2: self.read_2, - forward: self.forward, - reverse: self.reverse, - splice: self.splice, - non_splice: self.non_splice, - proper_pairs: self.proper_pairs, - proper_pair_diff_chrom: self.proper_pair_diff_chrom, - // samtools flagstat fields - secondary: self.secondary, - supplementary: self.supplementary, - mapped: self.mapped, - paired_flagstat: self.paired_flagstat, - read1_flagstat: self.read1_flagstat, - read2_flagstat: self.read2_flagstat, - first_fragments: self.first_fragments, - last_fragments: self.last_fragments, - properly_paired: self.properly_paired, - both_mapped: self.both_mapped, - singletons: self.singletons, - mate_diff_chr: self.mate_diff_chr, - mate_diff_chr_mapq5: self.mate_diff_chr_mapq5, - // samtools idxstats fields - chrom_counts: self.chrom_counts, - unplaced_unmapped: self.unplaced_unmapped, - // samtools stats SN fields - total_len: self.total_len, - total_first_fragment_len: self.total_first_fragment_len, - total_last_fragment_len: self.total_last_fragment_len, - bases_mapped: self.bases_mapped, - bases_mapped_cigar: self.bases_mapped_cigar, - bases_duplicated: self.bases_duplicated, - max_len: self.max_len, - max_first_fragment_len: self.max_first_fragment_len, - max_last_fragment_len: self.max_last_fragment_len, - quality_sum: self.quality_sum, - quality_count: self.quality_count, - mismatches: self.mismatches, - is_hist: self.is_hist, - inward_pairs: self.inward_pairs, - outward_pairs: self.outward_pairs, - other_orientation: self.other_orientation, - primary_count: self.primary_count, - primary_mapped: self.primary_mapped, - primary_duplicates: self.primary_duplicates, - reads_mq0: self.reads_mq0, - reads_mapped_and_paired: self.reads_mapped_and_paired, - // Histogram/distribution fields - rl_hist: self.rl_hist, - frl_hist: self.frl_hist, - lrl_hist: self.lrl_hist, - mapq_hist: self.mapq_hist, - ffq: self.ffq, - lfq: self.lfq, - gcf: self.gcf, - gcl: self.gcl, - fbc: self.fbc, - lbc: self.lbc, - fbc_ro: self.fbc_ro, - lbc_ro: self.lbc_ro, - gcc_rc: self.gcc_rc, - ftc: self.ftc, - ltc: self.ltc, - id_hist: self.id_hist, - ic: self.ic, - chk: self.chk, - cov_hist: self.cov_hist, - gcd_bins: self.gcd_bins, - } - } -} - impl InferExpAccum { /// Convert accumulated strand counts into an `InferExperimentResult`. pub fn into_result(self) -> InferExperimentResult { diff --git a/src/rna/rseqc/mod.rs b/src/rna/rseqc/mod.rs index a4730ccb..6f38ecca 100644 --- a/src/rna/rseqc/mod.rs +++ b/src/rna/rseqc/mod.rs @@ -7,14 +7,34 @@ pub mod accumulators; pub mod common; pub mod plots; -pub mod bam_stat; -pub mod flagstat; -pub mod idxstats; pub mod infer_experiment; pub mod inner_distance; pub mod junction_annotation; pub mod junction_saturation; pub mod read_distribution; pub mod read_duplication; -pub mod stats; pub mod tin; + +// bam_stat and the samtools writers are read-level and assay-agnostic; they +// now live in `crate::common`. Re-exported so existing +// `crate::rna::rseqc::...` paths and the published 0.2.x library surface +// keep resolving. Drop the shims at 1.0. +pub use crate::common::bam_stat; +pub use crate::common::samtools::{flagstat, idxstats, stats}; + +#[cfg(test)] +mod compat_tests { + //! Guards the re-export shims that keep the published 0.2.x paths alive. + //! These are compile-time assertions; there is nothing to observe at runtime. + + #[test] + fn moved_modules_are_still_reachable_from_their_old_paths() { + let _: fn( + &crate::rna::rseqc::bam_stat::BamStatResult, + &std::path::Path, + ) -> anyhow::Result<()> = crate::rna::rseqc::flagstat::write_flagstat; + let _ = crate::rna::rseqc::accumulators::BamStatAccum::default(); + let _: u16 = crate::rna::bam_flags::BAM_FDUP; + let _: Option<&crate::rna::preseq::PreseqAccum> = None; + } +} diff --git a/src/summary.rs b/src/summary.rs index 2f1320a6..05f18254 100644 --- a/src/summary.rs +++ b/src/summary.rs @@ -44,6 +44,11 @@ pub struct InputSummary { /// dupRadar summary (if successful and enabled). #[serde(skip_serializing_if = "Option::is_none")] pub dupradar: Option, + /// DNA depth-of-coverage summary (if this was a `dna` run). + /// + /// An input carries either the RNA fields above or this one, never both. + #[serde(skip_serializing_if = "Option::is_none")] + pub dna: Option, /// List of output files written. pub outputs: Vec, } @@ -90,6 +95,39 @@ pub struct DupradarSummary { pub slope: Option, } +/// Depth of coverage summary for a single alignment file. +#[derive(Debug, Serialize)] +pub struct DnaSummary { + /// Total reference bases across all contigs. + pub genome_length: u64, + /// Sum of per-base depth, that is total bases covered. + pub covered_bases: u64, + /// Mean depth across the reference. + pub mean_coverage: f64, + /// Median per-base depth. + pub median_coverage: u32, + /// Highest per-base depth seen. + pub max_coverage: u32, + /// Percentage of reference bases at or above each requested threshold, + /// in the order the thresholds were requested. + pub coverage_thresholds: Vec, + /// Total records seen. + pub total_reads: u64, + /// Duplicate-flagged records. + pub duplicates: u64, + /// Duplicate rate as a percentage of total records. + pub duplicate_pct: f64, +} + +/// Percentage of the reference covered at or above one depth threshold. +#[derive(Debug, Serialize)] +pub struct CoverageThreshold { + /// The threshold itself, in reads (for example 10 for 10X). + pub threshold: u32, + /// Percentage of reference bases at or above it. + pub pct_bases: f64, +} + /// A single output file written during processing. #[derive(Debug, Serialize)] pub struct OutputFile { diff --git a/tests/create_dna_test_data.sh b/tests/create_dna_test_data.sh new file mode 100755 index 00000000..22289b74 --- /dev/null +++ b/tests/create_dna_test_data.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Regenerate the DNA test inputs and the reference outputs they are compared against. +# +# Inputs come from nf-core/test-datasets (a real human chr22 slice, 40 kb). +# The upstream BAM is not duplicate-marked, so this script marks duplicates +# with samtools; RustQC requires duplicate-marked input. +# +# The reference outputs are produced by the pinned tool versions recorded in +# tests/expected/dna/VERSIONS.txt. Regenerating with a different version will +# make the parity tests fail, which is the intended behaviour: fixtures and +# tool versions travel together. +set -euo pipefail + +MOSDEPTH_VERSION="0.3.14" +SAMTOOLS_VERSION="1.24" + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +data="$here/data/dna" +expected="$here/expected/dna" +base="https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/homo_sapiens" + +have() { command -v "$1" >/dev/null || { echo "missing tool: $1" >&2; exit 1; }; } +have samtools; have mosdepth; have curl + +check_version() { + local tool="$1" want="$2" got + got="$($tool --version 2>&1 | head -1 | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -1)" + if [[ "$got" != "$want" ]]; then + echo "$tool version $got does not match the pinned $want" >&2 + echo "Install the pinned version, or update VERSIONS.txt and the fixtures together." >&2 + exit 1 + fi +} +check_version samtools "$SAMTOOLS_VERSION" +check_version mosdepth "$MOSDEPTH_VERSION" + +mkdir -p "$data" "$expected" +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +curl -sSfL -o "$tmp/upstream.bam" "$base/illumina/bam/test.paired_end.sorted.bam" +curl -sSfL -o "$data/genome.fasta" "$base/genome/genome.fasta" +curl -sSfL -o "$data/genome.fasta.fai" "$base/genome/genome.fasta.fai" + +# Mark duplicates: name-sort, add mate tags, coordinate-sort, then markdup. +samtools sort -n -o "$tmp/ns.bam" "$tmp/upstream.bam" +samtools fixmate -m "$tmp/ns.bam" "$tmp/fm.bam" +samtools sort -o "$tmp/cs.bam" "$tmp/fm.bam" +samtools markdup -S "$tmp/cs.bam" "$data/test.dna.bam" +samtools index "$data/test.dna.bam" + +mosdepth --by 500 --thresholds 1,5,10,15,20,30,50 "$expected/test" "$data/test.dna.bam" +samtools stats "$data/test.dna.bam" > "$expected/test.stats.txt" +samtools flagstat "$data/test.dna.bam" > "$expected/test.flagstat.txt" +samtools idxstats "$data/test.dna.bam" > "$expected/test.idxstats.txt" + +printf 'mosdepth\t%s\nsamtools\t%s\n' "$MOSDEPTH_VERSION" "$SAMTOOLS_VERSION" > "$expected/VERSIONS.txt" + +echo "Regenerated $(find "$data" "$expected" -type f | wc -l | tr -d ' ') files." diff --git a/tests/data/dna/genome.fasta b/tests/data/dna/genome.fasta new file mode 100644 index 00000000..b0ea69be --- /dev/null +++ b/tests/data/dna/genome.fasta @@ -0,0 +1,668 @@ +>chr22 +ACTCAAGATAATGATGAGTAAAGAATATATTTCTAACAACAAAAAGGAAATTTGATAGTA +TTTCTAAAGACAAAAAGGAAATTTGTATTCACATTCAGTTAGTCATTCCACCAGAATGAC +TTCATCACACAATATTTTGTGACAAGAACCTGAACAGCCTCATGTTTTACAATATTCTTT +TCATCTTTTATTATATGCACCAAAATTTTCTTTTTTAAATTTTCTTGAACCTCTAAATCT +ACTTTAAAAATTTACCTGATACACTTTTTAAATGGACAAATGCTGAAGGTAGCTGTGTAT +ACAAATGTGACTAGAAGGAAAAAGATGATGTAGAAATACAATAACTCCTTGAGTTGATCA +TTCTGATTGGCATTTATAGAGTAGAAATGTTTTGTAATTACAGAGGAAAAAAGATGGCCT +TTCCTTCAACAGTTATGAGCCGTCAGAATTTTCAAAAATATTGCATTTTGACAATGTAGT +TTCTAGTTTGACAATGATATATTTATCTTCAAAACCAGGAAAATGTAGATAAGGATTTGG +TTTTATAATATTTAAATTCTTATTAAAATGTATAATAAAATTGTTTTCCCCATCACTTTA +TTCTTCTGTAAGTTATTTTACGTTTAAAATGTAAACAAATAAAAATAAGTAAATAAACAG +TAGCAGCTTCTTTTCCTGGTGAATCGAGGATTGAGTATGTATTATATCTTTCCTGGACTA +TTGGAATAACCTCTCCCTCCTTCCACAGAGAAGCCATAATAATCTTTATGAAATACAAAT +CAAATCATGGTATTCATTCTTTAAATAGTTATCAATAAAAATAAAATCCCAACTTTATAC +CCTGTTCTGCAAATTTTAACGTGGTCTGAATTCAGCTTACATTTCTTCTTTCCCTTGTCT +ATTGCCCATCAGGCTCACTGGCCTTATTCCTTCACACCAAACTAGTTATTTCCGGGGTGG +GAGGAAGGCTTGCAGTGTTTTCTCCATCTGCAATAGTCTTTCCCAAATCTTAGTGTGGAT +AAAGTTTCCTTCTTGTTACTTGAATCACAAATACTATGTTCTTAGTCATTCTCTGTTACA +TCATCCAGAGTACATTATATCAATTTTCCAATATTTTTATTTATTTGATTTCCCACTATA +ACAGAGGCTCTGTTAGTGCAGGGTCTTTTACTCTTTTGTAATCCCAACAGCAAGAACAAA +ACAAGGTACATAGTACATATTTAATAAATACCTGTTGAACAAATATGTGCCAGTAATATT +TCTTCATGCTGCTGAATAAGTTAACAGCATATAAACACATACAAACCAAGTGGCATGGAT +GTCTGCTTTGATTTTTAGCCATTTAAAAATATACGTAACCCATCCTAAGGGGTTTATATT +TGTTTTGCATAATACATTAATATGTACTCATTATTCATTACACAGTTAATATATCTATAT +TTGCAGGGAATATACATTGCTTGGAATTATACAAAAAAATATTATTTTTCGTTTTCTAAT +ATTCAGGATACAGTGTTTTAATGGGGGTGTTTCTTCATTCTTTTTTTCTTACTGGTTTTT +ACTTTTTAAATTTGAAAGCCTTGCAGTGATCATAAGGATCTGTTCAGGCAAAGAACATGA +AAGAGTTTAAATTTTTATCATTTTAGTGTTTCTTATTCTCTATATCAAAAACATTCACAG +GTAAGTTAACAAGATCCTCATCAGGAGGAAAAGTAAATTGTTCACTACCATCCTCTAGTA +TCCTAATCTGGTCTTGTTGTTGGCTAACTTCAGCAGTTACTATTCTGTGATTGGTGTAAT +ATTAACCAAATAAATTACTGGATTTGTTCCACAAATATTATATCTTAGATTGGTTCTTTC +CTGTCTCTGAAAATAAAGTCTTGCAATGAGAATAAATTATTTTACAACAGTTAATTAGCA +ATGTAAAGTTTATTGAAAATGTATTTGCTTTTTTTGTAAATCATCTGTGAATCCAGAGGG +GAAAAATATGACAAAGAAAGCTATATAAGATATTATTTTATTTTACAGAGTAACAGACTA +GCTAGAGACAATGAATTAAGGGAAAATGACAAAGAACAGCTCAAAGCAATTTCTACACGA +GATCCTCTCTCTGAAATCACTGCGCAGGAGAAAGATTTTCTATGGACCACAGGTAAGTGC +TAAAATGGAGATTCTCTGTTTCTTTTTCTTTATTACAGAAAAAATAACTGACTTTGGCTG +ATCTCAGCATGTTTTTACCATACCTATTAGAATAAATGAAGCAGAATTTACATGATTTTT +AAACTATAAACATTGCCTTTTTAAAAACAATGGCTGTAAATTGATATTTGTAGAAAATCA +TACTACATTTGTAGTTGGCACATTAAATGCTTTTTCTTACTCTGAATTCCTGATATGACT +TTCTTTAGGATTGTTTAAAATATTCTAGTAGTTTTAGGTCAATTTAGATGTGATTTAGTT +GCTCTAGATATTATAATTTTTAGGGGTTCCCTTTCATTTTTTTCTTACGTTTCTTCAAAT +AGTATAATGCCTTATTTTCATTTATGAAGAAATTACCCTGCTGTTGGTGATACGGGTATA +TTTAAATAAACCAGTTGCAGTGCATTTTTGCAGAAAGTCCATTAAGACATAAATTTTGTC +CAGTAACCACAGTAGAAGTGGTGACTCTATGATTCATTCATGTTGCATAAGTAGGTGAAA +AATATGAGCTATATTCTGTCTGTTAAATGGAATTCTAGAGATGAAGTAGCCCAGGTAAAT +GTATGTTTGAGATTACTAGATAACTGTTGTACAAATTGGTATGTCACTTAAATTGTTTTC +TCTCAGAAAGTCCACATAAATAAATGAAATAGACTAATAATAGTAATATGGTGTAGAAAA +AACTCCCTTAACATTATTTCCATAGATAAAACTAATTAGAACTGTAAATTCTAAGGAGAT +TATTTATCTAAACTAATTTTAAAATCAGAAGTTAAGGCAGTGTTTTAGATGGCTCATTCA +CAACTATCTTTCCCCTTTAAATATGATTTATTGTCTTTCTCATACACAGATGTATTGCTT +GGTAAAAGATTGGCCTCCAATCAAACCTGAACAGGCTATGGAACTTCTGGACTGTAATTA +CCCAGATCCTATGGTTCGAAGTTTTGCTGTTCAGTGCTTGGAAAAATATTTAACAGATAA +CAAACTTTCTCAGTATTTAATTCAGCTAGTACAGGTAAAATAATGTAAAATAGTGAATAA +TGTTTAATTACAATAATAATTTATTTTAGATCCATACAACTTCCTTTTAAAAAACCTACT +GCACTAACTAGTTTTATGCTTAAAAAAAATTATTACCAGTAATATCCACTTTCTTTCTGA +AAAAATTTTCTTTAGATCGGCCATGCAGAAACTGAACCTGATTTGTTTTTTTTGAATCAC +CTAGGTCCTAAAATATGAACAATATTTGGATAACTTGCTTGTGAGATTTTTACTGAAGAA +AGCATTGACTAATCAAAGGATTGGGCACTTTTTCTTTTGGCATTTAAAGTAAGTCTAATT +ATTTTCCCATTAAATTCTTAAGGTACATATTACTTGCTTTCTTAATAGATTTATAAATAT +GTATTACTTATATACTTTTGTTTATGTTTGGCTGGAAGAGTTTTCCATACTAAAACTATT +TTGTACCAGTGATGAGCTTCTCAACTTTTGCTCTTTGAAATTTAAAAAGTAATAAATTCA +AAACTAAATTTCAGTCATGAATGAGAGCTTAAATATTTTTAAAGATTTTTGTTCTACTTA +AGTAAAATTTTCTAGGTCCAGATGAATATTGCTGTAGGTTTCACTGTGTGTATGGATTAA +AATATCCCCAAAAAAAGAAAAAAAATGTTTTACCTTGAGATTCAGAACAATAATGTCAAA +CTCCCGTGGTTCTTACTGAAAAACAAGCTAATTAAGAATAAAAAATGTTTTGTAGAATGT +GATATATGCAGTACTCAAAAGTTACAGGTCATAAACCATATAACTTTTCATAAATTTAGA +AACAGATTTATATCTAATATGATATTTTAAGTGTTAAAATTTAAAAATGGAACCCAGAAG +TTAAGTTGAAAACAAGAAGCGTAGACGTGTGTCAGAAGAGTCAAACAGCATTCACTGAGC +GCTTTGTTCCCTCCCTCTTCATTTGATTATTTTTGTGCTCAATTTCCTTTTTTCATGCTT +TTATATCTTGTACTGAGATTAGTCAATGAAAACTAGTTGAAATAAACCTAAAAACTAGAT +GTTTATTTAATCACATATTCAGGAACTACCTGAAACTCATGGTGGTTTTGCTTCTAAATT +ACAGGTTTTGAATAATGTTATTATTAGTATGATTGTAACATTTATTGGATTTCAAAAATG +AGTGTTTAAATTGTTTAGCAAAGATTATTTGTATACTGATTTAAGACTATATATATATTT +TTCTAATTTTGCATGATTCTTTTAGATCTGAGATGCACAATAAAACACTTAGCCAGAGGT +TTGGCCTGCTTTTGGAGTCCTATTGTCGTGCATGTGGGATGTATTTGAAGCACCTGAATA +GGCAAGTCGAGGCAATGGAAAAGCTCATTAACTTAACTGACATTGTCAAACAGGAGAAGA +AGGATGAAACACAAAAGTTGTGTGACTCTAGTCTGTGTTTGAGACTCTTTTCACTGCAGT +GGGGCAGAGTTGTTTAGAAGCCCAGTGTATATACAGATCATGGTCCTTGGAATCAAGCAG +ATTAGGATTTGGAACCAAGTTCCACTGCCTCTCATCTGTGTAGTGTTAGACACGTTATGC +AGGCTCTCAAGACTCATTTTCTTTGTCTGTAAAATGGGAATAATACCTGCTTCGTAAGGC +CATTGTGAGAATTAAATTACATGAGATATGCAAAGAACCTATCACAATCCTTGGAACACA +GAAGGTGCCCAATAAATGTTAGATCCCTTTACTTTCCCTTCCTTTCTCTTATTCAGGTCC +CTAAGTATTTACAGTGATTATTTCCTTATTCTGTCATTTATTATCTCTCAGTAATGACCC +TGAAAATGAGTGGAAAGAAGTTAGTTTTTACATTTCCAAGTTTAAAATGGATTTCGAGTC +ACTCAGTAAATATATCACACCCTCTAGTCATCTGCTGTCTAGCTTAGTGTAACTAAGAGT +AGGAAATACAATGTAAACTTTTTTTTTTGAGACAGGGTCTGGCTCTTTTGCCCGGCCTGG +AATGCAGTGGTGCAATTTCGGCTCACTGCAGCCTTGACCTCCTGGGTTCAAGCCATCCTC +CCACCTCAGCCTCCTGAGTAGCTAGGACTATAGGAGCATGCCACCACTCCCAGCTAATTT +TTGTATTTTTAGTAGAGACAGTGTTCTATTCTGCTTTATATTAAAAGCCCCTTAGAAAAT +GGGAACCTGGTGAATATATAATGAATTGTAAAATATTTTAATGTGTAACTTTTTCAACTG +TGAAACTGACTACTGATTTTTTGATGAAAACAGCTGCTGATAAAGTATTTTGTGTAAAGT +GTAGTTCTTATTAATCAGGAAAATGATGACTTGATTAGACTGTATATGCCCTCTTGGATT +TTATTTTAAATGGATTGGTGACTTTCACATAGGTAAAACACAGTCCATCTGTATTCTTTT +TTCCATCAAAAAGCGAGTGATTTAGAATTATAAAAAAATTTGTGAGCAGCCTATTTGAAA +GGCATCATGGAAATTTCACAGCACAATAACATGGATTTGTTTTTTTCTTAATGATGTAAA +TCCGTTTAATTCATATTTTGATCAATAGCCCATGCTTGCCAACTCTGAAGAAATTTAATT +TCCAGCAGTATTTTAAAGCTAGCCTGTTAACTTTTTCTGAATATTTAAAGTTCCTCTTTT +TTCTATGTCTGCACAAACTGCAGACCTGGGCTGGACCCACATACTCAAGAGTCCACCTTA +AGAAATTATTTTGATGTCCAAGACATCACTAAAATATTTCAGTTTAAAGATAACATGTGG +TGTTAATAGATTGTGGTGCTTTTACTATTTAAAGACAACTTTCATACTTCAGATGTTTTT +GAGAAGAGGGGAATGTGAGGGGAGGGGGCAGAACAGGGAGGAGTTTGAATGAATTACATT +CTTTATATCCATCCTGCTCATTTGGGGCATGTCTTTAAGAGAAGGCTGAAAGTTGTGAGA +GTATATTGTATACCGTAAGAGAATCAACTCTTCATCATGGATGGGATTGTGAAGGCTGAA +CTGTAAAAGTCAGCATTGACAGCATCCTCAATTAATAATTCTTGGTGACAGAATAATACA +GCTGGGCTGTTTTATAAATATAAACAATACCATTTTTAATTATTACATTAAAAATTTTAA +ATATATCTATGTGCCATGGCCTGGGAAGCCTGTTTTCTATTTTCATAAAAATTATTTTTA +CTGTATGAAAAGATTATGGGGTTTAGCTCAAAATATCTGTGGTCCTGATAAAATTGGATT +GGTAACTCTACCTCAGAAGGAAAATGGGAAAAAAAAATAGATGAGTCACAATTCAATACT +TCAAGCTCAGAAACTGTGCAGATCACTGAATTTTAGATTTATAAAGTCAGAGTTGGCATG +CGTTGTTTTTAATGATATGGAAGACCTTAAGAAAAAAACTTGGCTGAAGTTTAATCGTTG +GTCCAGCCATTTGAAAAAGGCAATAGTTCGAGGAGGTTTCCGAATTCGGCATTTGAAATT +CATTTTGTTCTCTCTTCTTCATTATTAGTGCATTTGGTGTGTGTATACTTGCACACAATT +CTGTTTGTGTACACACTGCTTGCTAAGCCCTAGTCAAGAGGCATCTTTTATAAAAGGTGT +AAAGAAATATCAAGGTTCTAAAATTCGGAAGAGTTTAGAATTTATTAGGAGTTTCCCAAG +TTGGGATGTTAGTCTTTAAATAAACTTCATGCACCTATTCCACTTAAGGTTTTGCACCTC +CTTTTTATTAGTGCAGTGCCATTTCTTCTGCTTGATTTTAGGTATGTTAATATTCCAGCC +TTGCTAGTTAGCATAAAGTGACAGGTGTGAGCCATGAGGAAATTTTCTGACTTAATTTTT +ATACAACTACATATGAGTTTTAGTGGAGAAAAAAAATTAGTCCCTTGTGCATATATAGTA +GTTAGGTAAATGATTTTTCTACCAACAGTGTACTCCATTCCTCATGTAGGTAAGTACAGA +AAAGGTTTTTAAATGTATTTTGTTAGCCAGTTAAAGTCTATGAATCTATCTGCAACCTTA +TTTAATCTGTCACTACAATAATTTTGTGGTTATGCTAAGAACCATGTATACTTTTAGGTA +TTCTTATTTTTGTCAATTTTTCTAGGTTAGCAAGGAGGCAGAAAAGCTTCACTGTTTCAT +ATTAAAATATAATTAGACTAAACTTAATTCTAGTATGAATTTCCAAAATCATTATCTATT +TATTTCATTTTTATTTAATTTTGTTTTTAGTTCATTTTTAAAAGTCCCTTGTTCAATTTA +ATTTATGTTCCTAAGAGTGGTTGGAGAACTTGGCCTTCATCTGATTTCAAAAACATTTTG +AGTTTCAAATGAAGTTAATGGTTTCAGTGTGATTCAGTCCTCAGACCTAATTGGGTTGAA +TAAAATCTAAAAGAATATACCCTTTTGGAGCATAACATTTTAATACCTTGAGGAATGTGG +CACTACCAAAAGAAGACTACTAACACGTCAGATGTTCACCTGGAAGCTTTAACAAGAAAT +TCGAACCACCCTTTTGGCCCCATTAATTGTAGCAAGTTTATTTCTCTATATTTTGTCATT +CAGTGAATTGAAGTCCTGTGGTATACTGCATTCATTAGAAGAAAAACGTTTTTAATGTCC +TTTTAATGATGGCCCAGAAAGCATTTGACACAGCAAGATGCATGTATTATTATATTGAGA +ATACAGAATAATAACAGTATCACTAAATTTAAGACCTCTTCCCAGTCTTGCTGTTCCTAG +CAAGAAGTTTGGCCCGTGACTGCACTTACTGTTTATGCTCATCAGAAACTGTCAATGTCT +GCTTTTCTTTAACTCTGCAGTCTGTAACATCATGCTGTTTATTAAAAAAAAAGAAAAATT +ACTTTGACTTGTGTCCAAACAATCCTTAGTGTACTACATAAGCAAAAAACTGTGATAATT +CTCTTTTGCCATTCCTTTTGAAAAGCAAGCCAGTGTTGCTAAAATCAAAATTTAGCTGAA +TTTGAGTTCTTTTCAGTAATGACTAAGAATACTTGATTGAAAATCTGAAACTATTATACC +TTAAAAGCCAATTTTTCTGCCCCAGTAAAGTGATGAATATTAAAGAAATGTATGTTTAAA +TATTTACTTCCTTTAAGCATAAAGAATTATATGCTTGTATTTTAAGAAATATATGTATGT +ATACATACATATGAATGTATGTATATGCAATAGGTAAGTGGACTTTTTTCCAAGTCATTT +GAAGATCAGAACCTAGAAATGAAGTTAGGCTACAAGCAAACTGGTTTTGCTTTCAGTTCT +CATAAACATTGCAAAAGGTAAGTGTGGGCTTTTCTTTGACCATTAATGCACATAGGCATT +AACAACTTAGTATTTCTGAGCAATTAAGCAAATAATTACTTACATTTTATTTATTTGCCA +AATGGTTTAAATAATTTTGAATTGACTTTGCTCTCCAGGGATAATATCTCTCTTTGCTGG +AATGATTCAGGTAGCTCCTATCTAAATGGAAAACTGTGGTAATTGAAACACACACTTTAC +ATTTTAAATTAGCAGTTTTGAATTTGTTAGGGAAAAAAATCCCAGCAATTGCATATTGTT +AGGTAGAAGTCAAATTTACAAAGAAACGGAATAGAGATGTGCCCTTGAGAAAAGTGTAGA +ATCTCAATGTGCAGATGATTTAAAATGTGCGTGCATATAAAATGTTCATGTGTACTTACA +TACTTTATTACAGAGAAGTCTTTGGTATACAAAATAGTTTACCACAACCTTTTAAACAGC +AGGTTCTGGGCCTTAAATGCGTATCACATTTAGCCAAGAGAACTCGGGTAGGGGCATGGA +AAATGAACTGCAGCTCCCTATCCCTAGCCTCTATACCAGCTGTTCAATGAAAAGTACCAA +GGCTCACTGAATGTTATAACCTAGCAGATTGTTACATAAATGATCTAACATTTTTGAGCA +CCGCTACTGGATGCTAGAAGCTAAGCTAAAGTGTTTCACATGCCCTACTTTGCTTATTCT +ATAAAATAACTGCGTGAAAGAACAGGTTATCCCCATTTTATAGATGAGAAAAGAAAGGTT +TACACAGGTTAGCTTATTTGCCCAAAGTTGTGATTATGGCCTACAAAGTCAAATAAATCC +TACTCTGAGACACATGTTCTTTCCACCATTGCACACTAGAAAGGAAAACACCAAGATTAT +TCATTACTGATCAAGTCAATATTGCTGTATTCAGCTAATTTAGTAATATGTGTCTTGAAA +TTAATTGCTAAAAGGGATTAAACTGACTTAGAATCAGTTTTTTGTTTGATTACATCTACA +TACAAAAGTAGCTTCAAATGTCTCATTCTACTGTCCATAATTTAAGATTTTTGAGTATAA +TACAATTTTAAAGATACTTTGAGGCACTTTGGAAAATCAGACCAAAATCTCTTTTCCACT +CACAGATTCGGCTTAATCAATCTGGAAAGCATTTGTTGAGAGCCTTATGACATCATTTAA +TAACCACGGTTGATTCATTAATTAAAGTACAGACAATTGTTGACTATCCATGTGGGACTT +TTCTATTAGGTTGACGCAAAAATAATTGCGGTTTTTCGCCATTAAAGGTTAACAGCGAAA +ACTGGAATTACTTTTGCACCAGCCTAATACGATGTGGATCATCTGAGATGAATGTTGAAA +TCCAGTATAGCTTCTTCATATTTCTGGCCCATTTTTCCCACCAGAAAGTGCACAAAGTGA +AATGAGCTTATGAAAAGCTTAATTAACTAGAAAAATGTTACTGAAAGAAAAATTACATGG +TACATGACAAGGCTAAATACTAGTAACTCTAAACTTAGTGAATTTTCTAGGCAGCAGCTT +TCCTCTGCTGTCTAGACTGGTAAAGAACAAACTAAGGCCAGGCGCAGTGGCTCATGCCTG +TAATCCCAGCACTTTGGGAGGCTGAGGCGGCCAAATCACCTGAGGTCAGGAGTTCAAGAC +CAGCCTGATCAACATGGTGAAACCCTGTCTACACTAAAAATATAAAAATTAGCTGGGCGT +GGTGGTGCACACCTGTAATCCCAGCTACTTAGGAAGCTGAAGCAGGAGAATTGCTTGAAC +CCAGGAGGCAGAGGTTGCAGTGAGCCAAGATCACGCCACTGTGCTCCAGCCTGGGCTACA +AGAGCAAAACTCCATCTCAAAAAGGAAAAAAAAGAAAAAAACTATAATAAATATGTTAGG +TCCATGTTTTCTTAAGTTTTCTACCGGATTTTTATCTTCGTATAGTGAACGAACTGTTAA +GAACTTTTTTATGAGAAATATTTTAGTATGACTATATTGCATAGAGTTAGGCTGATGGTT +CAGTGTTCAGTAGGTTAGATACCCTCATTGTTTATTTCCATATTGACTGGTTCTAGCTAG +AGCTGAAATTAGGCAAAGAATATCTTGAACTCATTTTGCTATACAGGAAAAAAGTGCTTC +CTTAGCTCATTTGGAAAGAGATTGAGATTAGAAAAGATGGTTAATTTGTATGTATTTATA +GAAATAAATAGAATACAAAATGAGGCTTTTAAATTTTTTCCCACATGAAAATATGATACT +TTAATCATTACGTTTTACATTGTTAGTTTGCAGACAGGCATAATTAGGTCCTCAGTTGCA +GAAATCACAGACATCTGAAGGCCAGCCCTTTAATTTGGCCACCGTCTTAAGATTTCTCTG +CTCCTTCCTTTGCTCCTCCTCCTACTGCACAGTTTGAACTGATGCTGTTCTATATAAGGT +ACTTTTCCACCTACCTCATCTCTGACTACAGTGCTATATTTTTCACACAGTAAGGACAGG +TGTTGTGTTAATCTCACCATGCCAACAATCAGGGCACCACCTAGCAGAGTCAGTGAAGGC +CAAAATAAACAGTGGAAGATAGCCATTTGGTCATACTTTTTTATAAGAATGACATCTTCA +GATTGGCTGGCTGGACTGTAGAAGCATGAAAAGGGGGTTCCATTTTTGTGATCGAAGAAT +TCTTTTATGTCCAGAGCACTGTTGAGCAAATCATTTCTATCTTGGTGGCACTTAGGTGTG +TAAAAGCACTAGGAATATGGAAGAGGGAAAAAGATAAAGGCACTGTCACCAATACCAAAT +ACTTAACAGTTTCTAATTATGAAATAGCTTCAGGCTGAAGTTATTAGTGGGCAGTTTCAA +TCTTAGAAGGTGGTAAAATATTACATAGCTCATGGGAAAGGGTTGATTGGAGGGCCACAG +TGAAATGGCCATTTCCAGTCATTAAGCAAGGATGTGGAAGAGAATTCTTAGTTTATATGA +CATTGCAGGAGAGTCAGTGACCAATTTCATAAGGAATATGACTCCTCCCTACATGCAGGT +TCTTGGACTCTTGGACAGTATGAATCCGTTTGTCCATTGAACAAAAATGTATTGAGCCTT +ACTATGAGCTTTCAACACCTAGTAATGCCTCTGTGGTCTCTGTCTTGATCTCCTGTAGCA +AAATATTACCCTGAAGAAAAGCACGTTGAGGCTTTTGCTCTAGACTCACAGACAGGGAGC +CCCACCTGGACTTTGGTTCCTGGGAGACAGAACCAGTGGAGAAGGGAGCTCTGTCAGCTG +GTGACTTTTTTCAAAAAAGCTTGAGGTTTATTACCATATCCATTAGGTACTTGAGGTACT +GTGCTAAAGGCCTACAAACTGTTTGAAATCTTAAAAATCATTGCATCCAAAATAGAAAAC +AAAAGTCATCAGATTGAAATTGATGCTTAAAGACAATAAAGTGTAACATGTCAACTAATC +TAACACAACTCAACTTTTATAGTTAGGTATAAATATAAATTTTAAATCATATGAAAGACT +ATACTTTCAGGGATCATTTCTATAATTCGTTAAATCATATGAACCCATTGTGTAACTTAT +TAAAATAAAAATAATCTTTACATTTATTTGATAAGAAAAAATTACTCGCTTGATTCAAGG +GAGACTGTGGTACACTGTAGCATATGTTATATGGCGCGGAGTGGAATCTCCAAAAGAAAG +ACTCCCCACAAATGACTACTCATTGGCTCAGCCTATAAATTCCAGACACCAAGTTGTGAA +ATTGGAATAATTTCTCTCCTTTCTATATACCCCATTTCTCCACCAAGAAGAAAGCTTCAT +TTATCCTGATTTGATCACTATAAAAATGTTCACTCCAAAAAAATAGATTTATCCCTAAAG +ACAGCCCTGGGTTATTTATGTACCCTGCTAGGGACAGTCTGGCAGGGAAAGGTTGCTGTC +ATAAGAACTCTTTAAACTTTACAATACCTTGGGATTTATCTGGACAGCCTCTTCATTATA +ATGTAGGAGAGCTTTCTGAGCTGAATGGGTGAGGTTCACAAACACCCGAAGACACGAGTA +CTTCCCGTGACCACGGCAGTGCACACCACAGGTGAAGGCACAGTCCAGCCAGTCGTCCAT +GATATCTGTGTGGATGGCAGTGCAGGTTGATTCTTCTCTCCGAATGCTTCAATTTGAAAA +AAAAAAAAATGTTCTTCACTTACTAGAAAATTTCGTTCTACATTTTGGTGCGGTTATGAG +CTTATGTACACAATTAGCTGGGATTACAGGCGCTCAGCTGCCATGTCCAGCTAATTTTTG +TATTTTTAGTAGAGACAGGGTGTTGGCCAGGCTCGTCTCCAACTCCTGACCTCAAGTGAT +CCACCCACCTTGGCCTCCCAAAGTGCTGGGATTACAGGCATGAGCCACTGCACCTGGCCC +AAATACTATGTTTTATCAATTCTAAAGTGCACTTTAGTATTTACATTTTAATATAACTAA +AATCAATATGTATTTTGCAATCAATGGCATCTTGCTATTATTTGAAAACATTTCTTTAAT +AGTCTGTAAAATAATGGAACATGCCCAGATGCAGTGGCTTATGCCTGTAATCCCAGCACT +TTGAAGGGTCAAGATAGGAGGATCGCTTGAGCCCAGGAGCTGGAGACCAGCCTGGCCAAT +ATAGTGACAGAATAAATAAATAAGTAAATAAAATAATGGAAAATCTCACAAATGGTGATG +TTTTAGGTTCGACAAAATACATTAACTAGCCCATTTAGTTTTCTGAAATTATTTTGATGT +TATTGCTTACAATATTTGTTCTGTGGTACACAACCATAGGATTAATAATATTGATGAAAA +TAATAAAAGAATAATAAGCATGTATTGAGCTCTTCCTGTGTGAAGTTCTGGACAAATCCT +CATAAAGCCTTAAAAGGCAGATACTAGGCTGGGCACGGTGGCTCATGCCTGTAATCCCAG +CACTTTGGGAGGCCGAGGCAGGCAGATCACGCGGTCAGGAGATTGAGACCATCCTGGCTA +ACATGATGAAACACGGTCTCTACTAAAAATACAAAAAATTAGCCAGGCATGGTGGCACGT +GCCTGTAGTCCCAGCTACTCGGGAGGCTGAGGCAGGAAAATCGCTTGAACCTGGGAGGCT +GAGGTTGCAGTGAGCCAAGATCGCACCACTGCTCTCCAGCCTGGGCGACAGAGCAAGACT +CTGTCTTAAAAAAAAAAAAAAAAAAAAGAAAGAAACAGGCAGATACTAGCCCAGGCACGG +TGGCTCATGCCTGTAATCCCACACCTTCGAAGGCCCAGGCGGGTGGATTATCTGAGGTCA +GGAGTTTGAGACCAGCCTGACCAACATTGTGAAACCCTGTCTCTACTAAAAATACAAAAA +TATTAGCCAGGTGTGGTGACAGGTGCCTGTAATTCCAGCTACTCAGGAGGCTAAGGCAGG +AGAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCTGAGATTGTGCCACTTTACTC +CAGCCTAGGTGACAGAGGAAGACTCTGTCTCAAAAAAAACAAACAAACAACAACAACAAC +ATCAAAAAGAAACCTATAGTAATAAAATTGAAATAGAAGGAGGTTTGCAATCAAAATGAC +TGACTAGGAATGAAATAGGAAACATAATATTTTGCATCTGCATAGGGAAGTCTGAGATTG +GCTGATCTTGTTCTCTTCTGTAGGGGAAATACTAGTCCAGAACTTGGGGTGCCTGCCAAG +AGGGGAGCAGCCACAGTAGGAAAGGGGGACTCTGGAATGCTAGGGTTCTGGGGTCTGTGG +ACACAGGAGGCAGAGGACATGTGTTAAGATGTTTTAAGAAATGAATGTTGAACTGGATAT +GAAAATATTTTTCAGCCGGGCGCAGTGGCTCACGTCTGTAATCCCAGTACTTTGGGAGGC +TGAGGCGGGTGGATCATGAGGTCAGGAGATCGAGACCATCCTGGCTAACACGGTGAAACC +CCGTCCGTCTCTACTGAAAATACAAAAAGTTAGCCAGGCGTGGTGGCGGAGGCCTGTAAT +CCCAGTTACTCTGGCGGCTGAAGCAGGAGAATGGCGTGAACCTGGGAGACGGAGCTTGCA +GTGAGCCGAGATTGCACCAGTGCACTCTAGCCTGGGCGACAGAGGGAGACTCCATCTAAA +AAAAAAAAAAAAAGAAAGAAAATATTTTTCACTATAGAGAGGCATATGTCCCCTGAACTT +GCCGGGATCCACCTTTCCTGCTGGTGCATTCTGTGAGTTAGAAGAAAACTTCCAAAGAGC +CATTTTTTCCACCCTGTCTACTGTATAAAATTGCTTCTCAAACATGTGCTGCATTGCAGA +GGATTACCATTGTTTTGCTAACCAGCGTCTGGTCTTTCTTATGTGGCGCTGCAATTACTA +GTGTCAAACCCTGTTGGTAATACCCAGAGGACGGTGTCTGAAGTCTTTACTCAATATTCA +CATTTGGCCGGGTGTGGTGGCTCACACCTGTAATCCCAGCACTTTCGGAAGCAGAGGCAG +GCGGATCACTTGAGGTCAGGAGTTCAAGACCAGCCTGGCCAACATGGTGAAACTCCATCT +CTACTAAAAATACAAAAATTAGCCGGGTATGGTGGCGGGTGCCTGTAATCTCAGCTACTA +GGGAGGCTGAGACAGGAGAATCACTTGAACCCAGGAGGTGGAGGTTACAGTGAGCCAAGA +TTGTGCCACTGTACTCCAGCCTGGGGGAAAATTCACATTTGTAGAGAGTTTAAATTCTTT +TTTGATACGGAGTCTCGCTCTGTTGCCCGGGCTGGAGTGCAGTGGCAGGGTCTTGACTCA +CTACAACCTCTGCCTCCCAGGCTCAAGGGATTCTCCTGCTTTAGCCTCCTGAGTAGTTGG +GATTACAGGCACCCACCAAAACACCTGGGCAATTTTTGTATTTTTATTAGAGACAGGGTT +TCACCATGTTGTCCAGGCTGATCTGAAACTCCTGACCTCAGGTGATCTGCCTGCCCTGGC +CTCCCAAAGTGCTGGGATTACAGGCATGAGCCACCACGCCCGGCCGAGAGTTTAAATTCT +TAAGTCCTACACTCCAATGTGTGGGAAGTATTCGTGCTATGCTTTTATAACTAAATCATC +TCAGTATTTCTATTTCTAGCCCCCTTTTTCTGCCTGATGGTAAGATACTTAATCTAGTCA +ATTCCAGGTAAACTTTGGCCTTTTATGATTTTTCCTGATCAGGCCAAACCTCAACCAAGT +CCCTTCTTGATCTTCTCCTTCACCTCCTTCTCTCATTCACCCGACAATTAGCCTCCAGTC +CACGGGCTGATGCAGCATCTTGGTGTCCTGTGGTCTGAGGTCATTTTCTGTCTTTCTCAA +GCCTCAGCTAAAGTTTACAATCCTACCTTTTCTCATGACCTTGAAATGCCCTAAGGTTCA +GGGGCTTCATGGTTGCTGCTTCATGGGGGAACCTGGCTGTTCTCTGAGGCTGCTCGGCCG +CGAACACCCCATCAACTACCCGGGGCCCATCTACGCCCGAGGCCTCAGCCATTCCTGCTC +TACAGCTCTGCTGTCCCATTGGCACAGGGAACTTCTTGGGGCCCCAGGGTTCCAGATTGG +AAGCAGAGAATCTCCTCTGTTCTCAGACCCCCAAACTTTGTTGTGGATTCTAATTGTCCT +TTCCCCCATCTCACTCCTTGGAACCCACTGGGAGGTGAGTAGAATCCCTGTCAGAGATTC +TACCACCATCTCCCTCATTCTTACCCTAACTTTCTTCCTCTTCCTCCCTAGTTAGGAAAG +AGGATCTTTAGCCTGCGGCGGGGGGGTGGGGGTGGGGATGCTTGATGTTTCAGGGGAAAA +GGTGACTCAGCTACTTTTGGAATATCTGTCATACCTGTCTACTGGTGCAATGAGCTGGGA +TCACACCACTACACTCCAGCCTGGGTGACAGAGCAAGATTCCATCTCAAAAATAAATAAA +TAAATAAATAAAGACTCTGGAGAAACAACTCAATACACATGAGAAGAGGCTGGCCCATGT +AGGGAAAGGACTGGCAAACTATGACAACTCTTTTCTGTTGTTTTGTTTTCAATAGTCTCT +TCACAGTTCTTTTCACAGTTTGGAATTGATACCTTTTTCTCTTCATCAGAACTCCAATGT +TTTTGTAGATTGAAGTCTTTTTTTTTTTTTTTCTTGAGAAAGGGTCTCACTTTGTCACCC +AGGCTGGAGTGCAGTGGACCAATCACTGCTCACTGCAGCCTCGACTTCCTGGGCTCAAGA +AATCCTTCCACCTCAGCCCCCCAGTAGCTAGGACTACAGGTGTTCACCACCATGCCCAGT +TAATTTTTATTTTTTAATGTATTATTATTATTATTATTATTATTATTATTATTATTATTA +TTATTTTGAGATGGAGTCTTGCTCTGTTGCCCAGGCTGGAGTGCAGCGGCACCATCTCGG +CTCACTGCAACCTCTGCCTCCTGGGTTCAAGAGATTCTCTTGCCTCAGCCTTCCAAGTAA +GTGGGACTACAGGTGCATGCCCCCACACCTGGGTAATTTATTTTTTTGTAGAAAAGGGGT +ATCAGTGTGCTGTCCAGGCTGGTCTCAAACTCCTAACCTCGAGTGATCTGCCTGCCTTGG +CCTTCCAAACTACTGGGATTAGAGGTAATGAGTCACCATGACTGGCCTACGTATAGCCCA +AATGGATGAGCAGTTCCCAAGGCTCATTCCCAGCCTCCACTATCCAAGTCAGCCTCTCAT +CTCCTTCATTTCCCAGGACTTAGTTCTCATTTTCCTCCCCTGTTTTCTCCGGATTGTGGC +TATTGTTCCCTGGTTGCTAGATCAACCTGGAGCACAGTAAAGCAGTGTCACAAAGCTGGA +AGGGGTCTGGGATGAGTCCACCAGCTACAAGTTCTTATAGAAAACGTACTCCGGGGATGG +CCGGGCCCAGTGGCTCATGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATC +CCCTGAGGTTGGGAGTTCGAGACCAGCCTGACCAACATGGAGAAACCCCGTCTCTACTAA +AAATACAAAATTAGCTGGGTGTGGTGGCACATGCCTGTAATCCCAGCTACTAGGGAGGCT +GAGGCAGGGGAATCGCTTGAACCTGGGAGGCGGAGGTTGCGGTGAGCCAAGATTATGCCA +TTGCACTCCAGCCTGGGCAACAAGAGTGAAACTCCATCTCAAAAAAAAAAAAAAAAAAGA +AAATGTACTCCAGGAATTGTCATTTCTGAAATTCAACAGCTTCTGGAATTGAAGCAAACA +GCTCATCTTGGAAGAGAAATATGTAGCCAACTCCAAAGCCAAAGCCTTTGAGTATTGAGA +CCTAGCATGCTAGGAGACCTTGATCCTGTAACCTCAGAAGAAGAATCTGGATCTGGCCAA +ATTGAGGTCAAATTCTGCTCAACTTCTCCATAGTCAGTAGGAGAAAAAAACCAACTTGAT +GTTTGAGTCATATGTTTTGACAACTAAAGAGGACACTTATGCTGGGGTCGGTGGTTCATG +CCTGTAATCCCAGCACTTTGGGAGGTCGAGGCGGGTGAATCATTTGAGGTCAGGGGTTCG +AGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACAAAAAATTCAAAAAAATTGGCT +GGGGGCAGTGGCTCATGCCTGTAATCCCAGCACTTTGGGAGGCTGAGATGGGTGGATCAC +GAGGTCAGGAGTTCAAGACCAGCCTGGCCATTATGGTGAGACCCTGTCTCTACTAAAAAT +ACAAAAATGATCCGGGCATGGTGGCGCACGCCTGTGGTCCCAGCTACTCAGGAGGCTGAG +ACAGAAGAATCTCTTGAACCTGGGAGGTGGAGGTTGCAGTGAGCCGAGATCACGCCACTG +CACTCCAGGCTGGGTGACAGAGTGAGATGTCATCTCAAAAAATAAATAAATAAATAAATA +AAATTAGTCTGACTTAGTGGCGGGCCCCTGTAATCCCAGCTACTGGGAGGCTGAGGCAGG +AGAATCACTTGAACCCGGGAGGTGGATGCAGTGAGCCAAGATCATGCCACTGCACTCTAG +CCTGGGCGAGTGAGACTCCATCTCAAAAAAAAAAAAAAAAAAAAAAGACACTTAAAGATG +ACATTAAAGAGGATACTTAGATTCTAGACAAAATCAAGATATAGCAAATTGGGGTGGGAC +ACACCTGTAATCTCAGCATTTGGGGAGGCCGAGGCAGGTGGATCACCTGAGGTCCAAAGT +TTGAGACCACCCTGACCAACATGGCGAAACCCCGTCTCTACTAAAAATACAAAAATTAGC +CAGGCATGGTGGTGGACACCTGTAGTCCCAGCTACTCAGGAGGCTGAGGCAGGAGAATCA +TTTGAGCCCAGGAGGCAGAGGTTGCAGTGAGCTGAGACTGCACTGCTGCACTGGTGCCTG +GGCCACACCAGTCACTATGCCTGGGTGACAGAGCAAGACTCTGTCTCAAAATAAATAAAT +AAATAAATAAAATTTTGTTTTGCTGTGTTGCGGCTAATATGCGTGCTATAAGACAATGGT +TTCTTGAGTCTCATTCTCTCTGCATATGCCTAAAGCTTTTTTATTTTTATGATTCTAAAA +GATTGTACCTTCTCATCTCCTAGATTCTGTCCCATAGGTTCTGATTTTTCCTAGAGTAAC +TTGGAAGTTAAAAAAGTGGAAAAAGCTTTGCGTATTAGGTGCCAAACCCACTCAGCTCTG +CTCAAACCCCTTCTTTAATGCCCAAGGTTGTCCAATCCTAGCCCTTCCCCCTACCCTCAG +CTTTCTCCTCACCTACACAGCAACCTTAGTATAGTCCTAAAGTATGTGTTCTTATCTTCT +GTTATCTATGCCAAGGATGTTTGCTGGTTTTGTTTTGTTTTGTTGAGACAGGGTCTTGCT +CTGTCTCTTAGGCTGGAGTGCAGTGGCACAATCACAGCTCACTGCAACCTCGATCTCCTG +GGCTTAAGTGATCCCCCCACTCAGCCTCCTGAGTAGCTGGGACTACAGGTATGCATCACC +ACGCCTGGCTAATTTTTTTTTTTTTTTTTTTTTTTGAGGCAGAGTTTTGCTCTTGTTGCC +CAGGCTGGGGTACAATAGTGTCATCTCAGCTCACCACAACCTCTGCCTCCCAGGTTCAAG +CAATTCTCCTGCCTCAGCCTCTCAAATAGCTGGGATTACAGGCATGTGCCATCACATCCG +GCTACTGTTTTGTATTTTTAGTAGAGATGGGGTTTCTCCACGTTGGCCAGGCTGGTCTTG +AACTCCTGACCTCAGCTGATCCACCCACCTTGGGCTCCCAAAGTGCTGGGATTAAAGGCT +TGAGCCACCATGCCCGGCCCATGCCTGGCTAATTTTTTTTAATTTTTATTTTTGTAGAGA +TAGGGTCTCACTATGTTGTCCAGGCTAGTCTTGAACTCCTGGACTCAAGCGATCTTCCTG +TCTCAGCCTCCCAAAGTGCAGGAATTATAGGCATGAGCCACTTTGCCAGGCAAGGATTTT +TTTCTTTTTAAGTTACATTTCTGCCTGCCACCACAGCAGCTCTTTCTCCTGCTCTCTCTC +TCTCTCTGTGCTTTAAGATGATAGTCCCTTCTTTTTTTTCAAATAACCACAACAGGAAGG +ACTGACCACTCTTGTAAGCTGCAACTGATGTTTTCAGACTCCTAAAGTGACATCTAGACA +TAAGTCCATATATGTCAGAATATCATGCAGGGAATGCTCAAATAGTTGGGAAGAGATTGC +TGCACTGTGTTTTGCACGCCCAAAGCCCACATAGGTACTCAGTTTAAAAATCTTAATAGA +ATTGAATCCTGCTCTTATCATAGGAAAGGAAGAGCATCTGATAGAAACACAAAATGAAAA +GGTCAAGAACTGGCTGGGCACAGTGGCTCTCGCCTGTAATCCCAGCACTTTGGGAGGCTG +AGGCGGGAGGATCATGAGGTCAGGAGTTCGAGACCAGCCTGGTCAATATGGTGAAACCCC +GTCTCTACTAAAAATACAAAAAATAGCTGGGCGTGGTGGCGCGCACCTGTAGTCCCAGCT +ATTCAGGAGGCTGAGGCAGGAAAATCGCTTGAACCTGGGAGGCGGAGGTTGCAGTGAGCC +AAGATCACGCCACTGCACACCAGCCTGGGCAACAGAGCAAGACTCCGTCTCTCAAAAAAA +AAAAACAAAAAAAGTCGAGAACTGGAAAGGAACTAAGCGCATGAAAAGAAATTTTATGTT +CCTTCATGTTTTTATTTAAAGAAAGTGAATCAAGTACCAAACACGGAATAAAGGCAAACA +TTCATTTTTGGGGTGATTGTTCCCTTCTTGGCAATCCCTGTTTTATTGAGGGTATCACTA +GTTATTCAATCCAAGGATTTTTTTTGTTTCCACAGGAGGTGGGTGTTTCTTTGTCTTCTT +AGAGTCAGGATTCCAGATCTCCTGATGTGTGGGACTTTTCTTGGCCACTACGATTTCATC +TACAGTCACGAGCTGTAGCACCACCTCAGCCACTGCTCGAAATCCTTGGGCTTTGACTAT +TAGGGTGTCCCACACCCCTTCCTGGGCCACATTTATTATCCCTTCAGTTCCCACACCCAT +TAGGAGGTTCCCACCTTGGTGCACTCCACTCATTTCTGCCATCACGTCTGAGACAGCTAA +GCCTGCATTCTCTGCCAAAGTTTTAGGAAGATACTTCAGGGCCCAGGCAAATGCTAGGAA +TGCAGGCCCACTGGGCCCTTCCAATCTGCTTCCTTTATCAGAAAGCATTTTTGCCAAAGC +CATTTCTGTGGCCCCAGCTCCTGGAATCAGTCTGGGATCTTGACATAGCTGGAAATAGGC +ATCAATGCCGTGGTAGACGGCCTGCTCTGCACTCCGCAGCCCCTGGGTGGTGGCTCCCCT +GAGAACCACAGTGAGGGCAGGTGTGCCTGTACATTCCCATTCAAATACCACAGCCAAACC +ATCTCCCAGCTCCTGCCTGTAAACCCTCTGGCACTTGCCTGGCCTCTGGGGAGGGAGCAG +ACGAGGCAGCAGAGGTGTGTCCAACACCTCACTCAGGTAAATGATCTCCATCCAAGACCT +AGCTTGAATCACCACGATGCCATACTTGTCCGCCAGTGTGAGGGTCTCCTCGTCGACCTC +CCCCAACACCACTGCCACATTAATTCCTGCAGCTGCTAGCTGGCCTACTTGCTTTTCTAG +TAATTGATCGCTTCCTTTACTAAATTGAGCTAGATCAGCAGGACTAGAAAGACGGGCCGT +TGCTGGTGCATTTGGATGGGCAGGACCAAAGGGGCAAGCAAAGAGAGCCACCCTGGCACC +ACTTAACACTGTGGCCATTTGCCCACAGAGCTTCCCAGATATTGCTAACCCCGGGAGGAG +GCAGGAATCCTCCAGTGTCCCCCCGGGCAGCGCGCACACCCCAACACGCTCAGGCTTGAA +GCTGCCGTCTAGTTCCTTGATAGCCCAGCAGGCGTGGGCCACCAGCTTGGTCAAGTGGTC +CATGGGGGACAGGGTGTGGGTATTCATCACAGAATGGAGGGCCCAGGATGGATCTTCCAA +AGGCCCCAGAGATTGGATGGCCAGGGAGGGCAGTGTGGCCAGGACCTCTGCAGTGGCCGT +GGCGTAGGCCTCCCGGAGCTGCGGGCGAGGCAGGCCAGCCTTCAGCAGCTGCTCTGCCTG +TTCCAGCAAGGCTTCCGTCAGCAGAACCACGAAGGCTGTGCCGTCCCCACTATTCTCTGC +CTGGGTTTGTCCTGCTTCCCGGAGGAGCCATGCTGCTGGGTGCTCCAGCTCCAGGGCCCT +GAGGATGGCAGTGGCACACCCCGTGCACACTGTTTCTCCTTTCATGGTCACCAGGAACTT +CTGCCGGCCGTGGGGGCCATAGCAAGGCCGGATGACACTGGCCAGGGTCTGGACTGCAGC +CAAGCTGCTCAGCAGGTGGGGCTCCTCCTCTTCTGGACTCCTCGGGCTCTCCCTTGGGTT +CAGTGCCAGCCGCTGGGGCAGCTCCAGGGCTGAAGGGACTGTGCTGTCCATGGCCCGCAG +AGAGAGGAGAGGCCACCGTGGGTTGCAGAGATGCTCTAGAAACAGCAGCTGGGGCACTCC +TGACACCGATCGTTGAAAGTACTCAAGAGGTCAGTGGAAGCAAGGAGCCAAATGCCCATT +GATTGGTATCTGAAGACATCAGCACGGACCAGCACTCCACTGTGGGTCCAAGGATGAGCT +CCAAAGAGCCCAGTCCTAAAGCCACCCCAGGGTTGATTCTGTAAAGGAACTGGGTCTTGG +GGCCTCTCAACCTTGGTGGCTGAAATGGGATCTTTAACTGATGAAGTCACAAAGTGGAAA +ATGGAACCAGGATAGAGAATGAGGTCACAGAAGGCTGGTTAGAACTGAGGAGGCCCTACC +AGCAGGCAAAAGTCAGGCCTTGTCCAGCAATGGAGGTACATGCACCTCTGCACCAGGTTT +GAGACTTGTTTAAACGTAAGAGACAATGAGGAGGAGATCAAGTGAAAAACTACCCATTTC +ACCCTATCTGGAGTGCAGGGGCATAACCATGGTTCACTGCAGGCCCAGCTCCCTGGTCTC +AAGCAGTCCTCCTGCTCAGGTTCCCAAGTACCTGGGACTACAGGCACACACCACCACACC +TAGCTAGTTTTTTTATTTTTTGTAGAGACAGTGTTTCTGTCTGTTGTCCAGGCAGGTCTC +GAATTCCTAGCCTCAAGAGAGCCTTCCACCTTGGCCTCCCAAAGTGCTAGGACTACAGGT +GTGAGCCACCACCTCACCCACCCTTTTTTTTTTTTTTTTTTTGAGACAGAGTCACACTCT +GTTGCCCAGGCTGGAGTGCAGTGGTACAATCTTAGCTCACTGCAACCTCCACCTCCCAGG +TTCAAGCAGTTCTCCTGCCTCAGCCTCTCAGTAGCTGGGATTACAGGTGCCAGCCACCAC +GCCCGGCTAATTTTTTATATTTTTAGTAGAGATAGGGGGATTTCACCATGTTGGCCATGG +TTGGCCAGGTTAGTCTCAAACTCCTGGCCTCAAGTGATCCGCCCACCTCGGCCTCCAAAA +GTGCTGGGATTACAGGTGTGAGCCACTGCACCTGGCCTTTTTTTTTTATTTGAGAAGGAA +CTGAGAGATGATGTCTGTGTTTTGTTTTGTTTTGGTGTTACTTTCTCTTGCAGTACTGTG +TAATATTAGCCATGTTTTGCTGTCTGCCTTTGACTTTTTGGGTATCTTATCAGTTTGTGC +TTGTGTATCAGGTTTCTTAGGGTGTCTGTTGGTCTTTCAGGGTGCAGGTGTGGGAGGCTG +CACAGCGTGCATGCCTGTGCCACGACTCCCAACTCTGCCTCCCTGGCAGAGGCAGGGCAA +GACAAGTGGGGAAGGATGCTGACAGCTCACAGACAAATAGAAGTGAACCCAGAGGGGTGA +AAAGCAACCAGCCTCCCAGCGGTCAGGGAGGTAGAAGCCTAAATGGGGTCCTGAGATTTA +AATGCGAATCGCCTTCCCATCCTAACCTTCAATGCTTACAATTTAAGTCTCTTTTTTTCA +TTCTCTCTCCTTTCCTCACTTGTCTCCTCTTTCCTCCTATAGAGCCTACTCGGGTAATGA +TGCTTCTGCTTTAGTTTAACACATATTTAGTCTGGGCGTGGTGGCTCATGCATGTAATCC +CTGCACGTTGGGAGGCTGAGGCGGGAGGATTGCTTAAGCTCAGGAGGTTGAGGCTTCAGT +GAGCCATGATTGCACCACTGCATTCCAGCTAGGGCAACAGAGTGAGACTTGTCTCAAAAA +AAATAGGGGAAAGGTCATTTGGAATCCTAGTCCAGAGATAACCATTGTTTACAACTTGAT +GAACATTACTACTTTGCACATATTATATGCATACATAATTATAGATTTACACCATTTTAC +ATAAGATTATGATACATATATGCTATTCTGTGATCATTTCCCCCTCAACATTATCTTGGC +TCAGAGAAATGTTTCTTTTTTTGTTTGGACATGGAGTTTCGGAGTTTCGCTCTTGTCGCC +CAGGCTGGAGTACAATGGCGCAATCTCGGCTCACCCTCGGCTCACCACAGCCTCTGCCTC +CCGGGTTCAAGCAATTCTCTTGCCTCAGCCTCCTGAGTAGCTGGGACTGAGTAGCCATGT +GCCACCATGCCCGGCTAATTTTGTGTTTTTAGTAGAGACAGGGTTTCTCCATGTTAGTCA +GGCTGGTCTCAAACTCCTGACCTCAGGGGATCCACCCGCCTCGGCCTCCCAAAAGTGCTG +GGATTACAGGCGTGTGCCACTGTGCCTGGTCTGTGAGCCACTGTGCCCGGCCTGAGAAAT +GTTTCTTTTTTTCTTTCTTTTTTTTTTTTTAAGCAGAAACACATTCATTTATTAACCAAA +GGGATGATCCTAATGAATCCAACACACTTTGAAATAGCTGCATGTAAAATGTTTGTGATA +AAGATAATTGAACACAGTAATGAAAAAAAAAAAAGAAAGAAAGAAACGGTATGGAGATTT +GCTCATTGAACTGAGCTTGGTCATTCTCTTAGTTAACTCCTGTCCAAAGTGATGATGGAA +TCTTTATTGTACTTTTTCATAGATCCGAGTACAGGCGACATGGTTCATGACACAGTCCAC +CACTAATTTCCCATCTTTCAATGTTCTTGTTATTGTGCTTTCCTTCCCATCCCACTCCTG +ATGCTGAACCAATGCACCATCTGTAAAGTTGCACACAGTCTGAGTTTTTCTGCCATCAGC +TGTGGTTTCTTCAAACTTCTCTCCCAGGGTACAAGAAAACTGTGTTGTTTTCAAAGTGCT +CTCAGTTTTTATGGTGAGGTTTTTGCCATCACAAGTGATGATACAATCTGGCTTGGCCAT +TGCGCCCATTTTTTGCAAAGCTATTTCCTCCTAGCTCCTTCATGTATTCATCAAAGCCTT +CGCTGTCCACCAGGCGCCATCTTCCTTCCAGCTGCTGAACTGTGGCCATGGTGGGTGCAG +GGGGGCTGGTGTGCAGAGCAGGGTCTGCGTCGGCGTGGCAGCGTGCTGTCGAGAAATGTT +TCTAAGGAGATCTTATTTGGTCTGAGAACCATGAATGATTATTTTGAGCACTTTTGATTC +TGGAGACTCCATTTGGATCAGGCATGGTCCTCCAAATTCAGGCTTCTGAAAGCCTGTACC +TCAGAGTAGGCTTGATGTTCCATAAAAGATGTGGTTATGAGTGCAAAGATGACTTGCCTG +TATTGTTATACAAATGTAAAATGTAACAATCAACAAAAATGTAGCAAAGTATGCATGTAT +ACATTTTCTCTAAAGATACAGTTTCTTTTTTGAAAAAATAAACACATTAGGCAGGTGTGA +TGGCGGGTGCCTGTTATCCCAGCTACTCCGGAGGCTAAGGCACGAGAATCTCTTGAACCT +GGGAGGTGGACAAATTGCAGTGAGCCAAGATTGCGCCACTATACTCCAGCCTGGGCAATA +GAGCGAGACTCAGTCTCAAAAAATAAATAAATAAATAAATAAATAAATAAATAAATAAAA +TAAACACTACCGGCCAGTGGCCATGGCTCGAGCCTATAATCCCAGCACTTTGGGAGGCCT +GAGCCAGGTGGAGTTCAGGCATTCAAGACCAGCTTGGGCAATATGACAAGACCCCTGTCT +CTACTAAAAATACAAAACAATAGCCGGCCGTGGTGGTGTGTGCCTGTAGTCAGCTGCTTG +GGAGGCTGAGGTGGGAGGATTGCTTGAGCCCTGAAGGTGGAAGTTGCAGTGAGCTGAGAT +AGTGCCATTGCACTCCAGCCTGGGTGACAGAGTGAGACCCTGTCTCAAAAAATAAAATAA +AATAAACACTCCTATAAAGGATCCTCTTAGCTCTTTTTCTAACACCTAATCTACATTTTC +ATATTCATTTCAGTTACCCTACAACTGTTCACTGAGCTGCTGTTGAATAGGGGAAATAAG +GCAGATAACTACTGCCATCTCCGCTGGAGGGACGATACAGACATTAATCTGGGCACTTTG +ATTACAGGCAATGAGAGCTGTGAGTGGGGAAAGCACAAGGTTGGCAGAAGCATTTAGGGG +GACACAGCCATTCTCACGGAGGGCAGAGGTCTAAAGCAAGAGCTGAATAAAAAGTAGGAA +CTGGCCTCGTGGAAAGGGGAAGGGTGATGGGACAGCCTGGTGGTTTGTAGCCCACTGGAA +GGAGTTCTGAAAACTGGTGGTCAGGTGAGAAGGAAAGCTGGGGAAGAGATGAGCACGTTC +GCCAGAGGGTAGCAGGGGCTCTCCGGACCTAGTGAGTCAAGCCAAGGAATTAAGGCTTCA +GCCTGCAGGGTGATGAATAGGGCTGTCTATTCCATTTCTTCCTTCTTTCTTTCTTTTCTT +TCTTTTTTTGAGACAGCGTCTCACTCTGTCACCCAGGCTGGAGTGCAGTGGCACGATCCT +GGCTCACTGCAACCTCTGCCTCCCTGATTCAAGCAATTCTCCTGCTTCAGCCTCCAGAAT +AGCCGGGATTACGGGTGCCTGCTACCACGCCTGGCTAATTTTGTATTTTTAGTAGAGGCG +AGGTTTCACCATGTTGGTCAGGCTGGTCTCGAACTCCTGACCTCAAGTGATCTGCCTACC +TCGGCCTCCCAAAGTGCTGGGATTACAGGTGTAAACCACCGTGCCTGGCCTGAAAATTTC +TAGTTTATGATACTTGCCAGCAGAATGTGTTCTGTCACCCTCTTCTGAATAGATATGGTT +GTCTGCTATGACTTCTCCCACTGCTGCCCTTCCCCCTGAATCCACAGATGCATTTCTTTT +AAAACTATGATCTTGTACACAATGGATGTAAATATTTAATCTTTCTATTTGTATGTTTTT +CCATGTTTCTTTTCTTTCTTTCTCTTTTTTTTTTTTTTTTTTTTTTTTTTGGAGGTGGTG +TCTGCCTCTATTGCCCACAGGCTGGAGTGCACTGGTACAATCTCGGCTCACTGCACCCTC +CGCCTCCTAGGTTCAAGGGATTCTGCTGCCTGAGCCTCCTGAGTAGCTGGGACTACAGGT +GTGCACCACCACGCCCGGCTAGTTTTTATATTTTTAACAGAGACAGGGTTTCACCATATT +GGCCAGGCTGGTCTCGAACTCCTGACCTCGTGATCCTCTCACCTCGTCCTCCCAAAGTGC +TGGGATTACAGGCATGAGCCACCGTGCCCGGCCTCCATGTTTATTTTCTAGTTGCTTACT +TGTCCTTTTGTGTTTATCCTTGTTAACTACTACTGCCAGGCTTAAAGTATAGACCCCTAG +AGGGCAAGATTTGTATCTATATAAAATGTACTGCAAAACATCTACTTAAGCCTCACATTC +TTAAACACAAATTACTTTTGAAGATGACTGTTCTGTTTGTTTCCTTCCTGGTTTCTTCCT +TTAACTTTTCCACCAAACAGGTACATGATATACTTTACTGAAATAACTTATATAGCAATA +TGAATTTTTTTTTTGAGGCGGAGTTTCGCTCTTGTTGCCCAGGCTAGAGTGCAATGGCGT +GATCTTGGCTCACTGCAACCTCCGCCTCCTGGGTTCAAACAATTCTCCTGTCTCAGCCTC +CAGAATAGCGGGGATTACAGGCGCACACCACCATGCCAGGCTAATTTTTGTATTTTTAGT +AGAGACGGGGGTTCACCATGTTGGCCACGCTGGTCTCGAACTCCTGACCTCAGGTGATCC +GCCTGCCTTGGCCTCCCAAAGTGCTGGGACTACAGGCATGAGCCACCGTGCCCGGCAAAT +TTGAGGTGGAGGTTGCAGTGAGCTGAGATCGCATCACTGCACTCTAGCCTAGGTGACAGA +GCAAGACTGTCTCCCACTTCAGCCTCCCAAGTAGCTGGGACTACAAGCATGTGCCACCAG +ACCTGGTTAATTTTTTTTTTTTTTTTTTTTGAGACGGAGTCTCGCTCCATCACCCAGGCT +GGAGTGCAGTGGCGCGATCTCAGCTCACTGCAAGCTCCCCCTCCCGGGTACACGCCACTC +TCCTGCCTCAGCCTCCCGAGTAGCTGGGACTACAGGCACCTGCCAGCACGCCCGGCTAAC +TTTTTGCATTTTTAGTAGAGACAGGGTTTCACCGTGTTAGCCAGGATGGTCTCGATCTCC +TGACCTCATGATCCACCTGCCTTGGCCTCTCAAAGTGCTGGGATTATAGGCGTGAGCCAC +CGCGCCCAGCCAGGCCTGGTTAATTTTCTTTGGTATTTTTTTGTAGAGACGGAGGTCTCA +CTATGTTGCCCAGGCTGGTCTCGAACTCCTGAGCTCAAGTGATCCACCTGCCTTGGCCTT +CCAAAGTGCTAGGATTACAGGCATGAGCCACGGTGCCCAGCCTACAGTGCAACTTTAATA +ATAACAATATGAACACAAAAATTCTAAGATCTAAAATTTAAGCTTTCAGTAGTCCTTCTA +TAACTGTGAAAGTTTGGTTCCTAAAAAGCCCTGAGGAATTTATGGGAAAACAAGAGAGAC +AACATTTAGTAGTGAACCTGTGCATTCTAAATAAAGACAATATCAATGACGTGTTATAGG +TCTTCAATTAGTAAGAATGAATATTGGACTATGAATTTTTATTCACTGTCACTTGTTTGC +TAGATGCTTTGAGAATCTTCCTTGCCTATATTTTCCTGAGATGTTGGTTTTTCTTTGTCA +CAGATAACAATGCTCATTCCCTCCCCATTAAAAACTAAATATATATATATATATATATAT +GATTAAACGATTACTACATGTGCTTTGAAATATTCAAATATTTTAGACAGTAAAAGTCCC +TTGTAATTCAACCCTTTGCAGATGATTGGTTAACAGGTTAGTACACATCTACCTAAATTT +AAAATCCCATATTTAACATGTATACTTATTAGAAAGTACACATTCTAATATTTTTCTATT +GTATTTGGTACTATTTTCAGATGCTCCTGCCTTTTTCTTTCGTAATTTTGAAGGACCTCA +GCTCCCTGCCTCCTAGATTTTTGCTACTATGGTCTCAGAGCTGTGTAATTTGGATGACTG +AGATGGAAAAACCTCTGGAAAACCTTTATTTATGTTGAATAAGTATTCCTTGAATCCTTC +CTCAGCATCCTGGGTTATATTTGATTTGCTCTGCTCATGATAACTTCATGCCAAGGAGAC +TGCTATCAGTTCTCTTAAAACAGATCCCAACTCCCTGCTCATAGTGGCCAAAGGAATGGA +GATTTCAGGCTGAGTTTACTTACGTGCATCATCTTCATCTATCCAGAAGCATCCCTGCAC +AAAACCTCTGTTTCTACCCTTCCATTCACTCGGCTCACTTTTCTGCTCTTAGTACCCTTT +GTTTCTTGTGAACTCTCCAGCAGGAGTGACTTGCAATTTGTATCCACTGACACTTAAGTT +CTCGGAAGTGCTGGAGAAGTGTATGGAAGTAAATTATCCTGATGTATAATTTTGTGCATG +TGAAACTCACCGTGGAAGTGCCTATCTAATTTCAGTATGGAACACAGCTAAACATTTGGA +TCAATAATCCAGTTTTGAAACCACACTTCATTTAAAGTACAATGTGCTGAAAAAAATGAA +AAAAGGGTGCTTTCAAATTTGTACTTAGTAAACTTTCACTAGATCACATCATATGTTTAT +CACTAGTCATGTTGTATTTCTATGTGTAATCGCCAGGCACTTTTAATTTCTAGTTTGCAT +TTACCATGCCAGCCTCCTCCTCAATCCCAAATTTCCTTTGGTTATAAATTTAGTAAATTT +GAAAGAGCCAGCAGGGATTAAACCCTGAAGGTATTCAAATGACTATCTGACGTTATTCCT +CATTTCAGCCATTTCGAAAAATTATGCTTTCATTTAGAATAGGCTCTGGGAATCAAAGTG +TGTGTATTTTGCCCAAGTAGAAGACACAGTTTAAAGTTAACATCCTAGCTACTAGAAGGG +AAAGCAAACAACATCGCTGCAAAAGGAGCCTATTTTTTTTTTACCTTACACTAAAACTAC +ATTGTGAAGATCAAACGAAATCAAGATGAGAGTGTGCCTCTTAACGCCAGGTCCAAAGTA +GATGCTTATTAAATGATAGTTTACCCCAATCCTTCACAAATGGTTGATAGGTCTTACTAT +TTCCCCCCTATTCAAATCTAGAATTTTTTCACTCCCATATACTAATCGATAGTTAATGGA +AAGCACAGAATAGATCATCGTCCAAGTGTTAGGTATTAGCCTGAGGAATCCGGAATCCCA +TATTTGTAACTGTCCTTCTTGAGAAAGTGCATTTTTCAGGCGGATTCTAGCCCCATTTTT +CCTTTTACCATTTTTACATGTTATGAGAGGTGGCTTAGAAATACTTCGATTTTTGCCTCT +TCATCACAACACACTGAACGTTAAAATCAAGTGGTTGGGTTTTTATTGGCTTATTTTGTC +TCTAACCGTTTTATTTCTCGAGCTGTCATCGTTCTTTTCGTCTTACATCCTTATGAACCT +TTTCTGGATTAAAAAAATGACGTTATAATAAGGAAACTGTAACTGGCGTTGGATTAGAAC +GAAGTTGACTCCATTCCTTTTCCTCCCCGTAGTGTGGGCGATACGAGGAAAGACCTCGGC +AAGAACCAGCGAAGCCCCGGCTGCCCTCGCCCTGCGGGCGCACACTTGCTCCTCGCGCCG +GGCTGCGCCGGGCGCCCGCGCCGCCTCGGCGTGTGTCCGCGGCTCCCTCCCGCCCTCGCC +CGCAGTCCCCCGATCCCGATCCCGGATCTCTGGGTCCACAGCTTGGCTCCCTCCCGAGCC +GGAGCCGGAGCCGGAGCCGAAGTCGCGGCTGGGCCCGGCCGCCCCGTCACAGGGGGAGGG +AACCCATGGGGAGGGGGAGGGGCGGTGAGGTCAGCGGCGGCGGCGCGTCCGCGGGCGGCG +GGAGCTTCGCATGCGCGGAGCGAGGCCCGTGAGTGGCAGCGGCGGCGCGCGGGGGGCGGG +CGAGGGGCCGAGAGTGGGGGAGCGGGCGGGGGCCGTCGAGGAGGCGTTGTGTGGGCGCGA +CGGCTGCGAGTTGGGGAGGTCTGTGGTGCGGGTCGCCCCGGGGGATCCCCGGCGCGGGCC +TCGCGCGACGGCCACGGTCGCGCGGCGTGTGTGGGGGGTCCACGCACACCCGCAAAACTT +CCTCCTCCCCTGCTCCGGGAGAGCGAGCGAGCGTGTGTGAGAGCGAGTGTGAGGAGCGAG +CCGCGGCCCGACGCCCAGCGCCGCCGCTGGAGCAGCTGTCAAAACTTCGCCGCCGCCCGG +GCCCCGCGGCCCGCCCTCCCCGCGCCGGGCCCCTTTCTCTTCCTGCTGCGGGCGGCCCGG +GGGAGGGGCCGCGGGCGGAGACCCCGGAGGCCGGCGCCCCTCACGCCGCCCGCCCGCCCG +CTCCCCGCCCGGCCCCTGCGCGCGTGCGTGTCCTGCTCGCTCCATGTTGCCGCCTCTCCC +GGTACCTGCTGCTGCTCCCGGGGCTTCGGGAAATGCGAGAGTCTGAGCCGGGGAGGAGGA +ACCCGAGCAGCGGCGGCGGCGGCCGCGGCGGCGGGAGCCCCCCAAGAGGAGGACCGGGAT +CCATGTGTCTTTCCTGGTGACTAGGATGTCGTCGGAGGAGAACAAGTGCGTGGAGCAGCC +GCAGCCACCACCCCCCGAGGAGCCTGGAGCCCCGGCCCCGAGCCCCCCAGCCGCAGACAA +AAGACCTCGGGGCCGGCCTCGCAAGGCGCTTCCCCTTTCCAGAGAGCCAGAAAGAAGTAA +GTTGAGTGCGAGGGAGCCAGGCCGGGAGCCAGCGGCGGCGCCGGGCCGGAGCTGCCACCG +GGCGCCCGCCCCGCGGCCTCCACGCCTTGGCGCCCCCCGGCGGGATGGGGGCGGGGCGGG +CCCGCGGGCGGCGGCAGCTCCCGGCCCCGGCCCCACGCCCCTCGGTAGCCGCCCGCGCCC +GGCCTCCCCCGCTCCGCGCCGCCCGCCCGGGCTCCCGTCGGCGCCCGGCTTCGCACACTT +TACTTTTCAGTCGGGCCTTTTCAGTGGGTCTTCTCCGCGACTCTTCTTTTGGAGAAATTT +CTCGTAGCCGCGTCTTGGCCTAGCTGGATCATTGAGAAAACAAGCCCGGAGCGCGCGCAG +GTAGTCCCCGGACGGACTCCGAGCGAACCGCCGAGCCGTGGGCGCTCGGGAAACTCGGAG +CTGTCAAAACGCCCGGGCCAGGTGGTCTCGGGGCGCGGGCTGGGGGCGAGAAGAAAGCGG +CCGGGCGAGTGCAGCTTTTGTTTGTCAGCGACTCGTTCGTGGAACTTTTCCTGGTCCCAA +ACCTGTGTTTTCTTCTTTTGATGATATATTAGGAAGCCATTTGGCTTCTTCCTTCCCCCT +CCCCCAACACCCAGCACCGCACTCCCGGGCTCCGAAAGCACAAGTCCTGTGGGAACCCCC +AGCTTCGGGGAACGGCCTGCCTAAGTTTTGGAGACGTAGCCAGCGTCCCCTCGTAAGGCA +GAATACCAAGAGCACTTATTCAGAGAGAGTGCAGATGTAAATGTCGTTTCCCTCGTAAGT +CTTAGCTGTAAGGGGCTTGGGAATAGGGTCGCCTGCCTTTGACCGACCGTACTGTAGGGC +TGGACACCGGCTTATTAGAGGACCAGAAATGTCTTCTTACAGAACGGTTATTTGACGGCT +TTGCTTGTAAATTAAGACACCGTTTTAGTGCCAGCGAGCTGCTCGGCTTCTGTGGCTCTC +GCGTGTGCCGTGGAAGAACTGTGAATGTCTTTCGAAGTTGTAGAATGGCGTGTGTGCTTA +CTCATTTCATGAGATGATATTCTCATTGAACTGTCGGGAGTGGAAGGGTGCGCTGGGACG +TGAAGGAAGCCAGCACGTTTATGGATAGGCTGTTTCTTTGGTTCGGGTGCATTCACTTAG +TAATAGTGTTGTTTGGTGATTTGTAGTAAAAATAGTAGCGTGAACTGAGGCATAGCAGAG +CTGGGTTGTGGGAACCCATTAAGCTCTTGACTTGAATGTGCTCTTTTCTTGCCCCGCTGT +CCTTTTACTATGAAAATGATTCAGGGCCTTCAACTTGCCTCCATATTTTATTGCCAGCTC +TTACCTAGCTATGATAATCGTGAGGGAGGCAAGTACAGGATGTGTGTACGTTATTACATT +AGCTTCTTCGTGATACAAAGTTAGGACTTACTTATGCCACTTGCGTTGTAATACAATGGC +AAATATAAAATGCCCTTATTCTATATTAACTGAAATTTGGAGAAGGAAGTGGAGGTTTAA +GTAATTTTTAGACGTCTAAGCCACTTTTTTGCATCCTTTAAAGCAACTCAGGACAAGCCA +TATTGGGGGTTTTACCTTGATTGCCTCCCATTTCACTATTTGCAAAGCATTTCTTCATCT +CTTACTGAACATTAATTTGCAATTTTTTTTTTTAATTTGCATTTGAATTCTTACTCCAGA +AAGATTAGATCTGTGTTGTCACACCCCACACCCCATACTCCTGTAAGGGCGTGCTTGTGC +ACGCGCACACGCTCACACGCACGCGCACACTCGCACACACCCTACTTTTGAAATGAGCTC +ATTTGTATTAGTGCAGCTCCTGAGTGCACTGGACGATTAGGGTATTGCCACTTTATTATT +TTAATTCTTAATCTCATATTATGAAGAAATAGGTAGCCTTTGGAGAAGATAAAAAATTTC +TGCTGAATAACAGTATAATCTAACTATGAAACATCAAAACTTTTGGAAATATTTAGAACA +AATGTAAGTCTGTAGAGAGCTTTTTCTTTTAGATTTGAAAACTAGTACTGCTTTCTTTAT +AGGAAAGTAAAGTCTACTGGTAAATTTCACGGGTCTAAACTTTTTAGAGCTTTTTTTTGA +AATTGTGTCTTTTGAAGGGAGTGGAATCTCCAGTTGTTTTTAGAAACATGTAAATGGAAA +CTAACATATGAATTGGAAAGCAAAGAGAAAGTTTTTCAATTGTGTATCTCTATACTGTAT +AAGAATCCATGCAGAAAAGACCCTGTAGTTGGATAGTAAAGACCCTGAAGGTGAAACTTA +TGTGTAACCAGTGTAAATTAGGTTTGTAACCAGTGAAATTATGTGAAATTGCAAATAATT +CACCTGAGAAATGAAAATTAATCTTCTTTGCTAAATGCCATAGAGATATTTTAAGTTGCT +AATGTTACTTAGATGTTCATTAACTTAGTGAGTTACATTAAGTAGAGAAGATGCCTTTTT +TTTTTTTCTGTACGAAGTCTTGCTCTGTAGCCCAGTGTAGTGGTATGATCTCGGCTCACC +ACAACCTCCGCCTCCTGTATTCAAGCGACTCTCCTGCCTCAGCCTCCAGAGTAGCTGGGA +TTACAGGTGTGCACCATCGCACCTAGCTAATTTTTTGTATTTTTAGCAGAGACAGCATTT +CACCATGTTGGCCAGGCTGTTCTTGAACCCCCGACCTCAGGTAATCCACCCTCCTTAGCC +TCCCAAAGTGCCAGGATTACAGGCGTGAGCCACTGCACCCTGCTGAGAAGATGCCTTTTG +ACAATGAAGTGGATTTGTATATTTATCTTTGGCTTAAAAAAACATGCACCACCAATTACA +CTTTCCTCAAGTTTAAATTTTTAATAATTAGGAAAATAAAGCATTTTCTTGTCTTATAGT +GTTAGCTAGATTGTTTTTGTGTATTTTGTCATGAATAAAAAGCATAGCTATATAGTTACT +GCTTTTACATTAACTATAAATATCTTAAAATTTTACTACCTAAAATCAGGAAACTTGAAC +TGAAGCTACTAATCTTAGAGTTGGAAAAGTAAATACATAGAGGTTTCCTGTTGTACAAAT +GTCAAGTGGCACAGTGAAATTTACATTCATTTGAAAGTTTTCCTTAACTGTAAAAAGTAT +CAAATTACTTGATACTTTGGAGTAGTTCATCATCTTTATCAGAGGCACAGGTCTTAACCA +TTGGCAAGCCTCTGTCAGAATATGCACATATTAAAGATCTGATTATTTTTGTGTTAATGT +TAAAAAATTTTTCTGAAGCTTTTATCTTATTTTTTCCATCCTTACACCGTAAATTCACAT +TACCAAGTTGGGAAGCCAAAGAAACATTCTACTCTACTATGTTTCTTACCAGTTCATGAA +AGTTGATGTTAGAAATGGGTGTGGGTGTGGGGGATGGGGGTGGTTGTACAGAAGCAGCAG +GTGGTAGGGATAGGATTTCTGAAGCACTATCCTTGGCCTTTTTTGAGTAAACTCTTTATA +CCCTGAGCCACTTTCTTTTCAGAGGGCAATTGCTATTATTAGAGAGCCACCTTAAGCATT +ATTGTTGTAGAAAAATTAGGCACAACCAGTGATTGTCATTACAAGGACCAGCAAAAATGG +CTAGGTTGCTACTCTGTATTTGTAACGCCCTTCCCCCAACAAAATTTCTCCTTTTCATAT +CTGTGAATTAGAAATAAGTGATAGAAAACTGTACTGCATTACAATATATACCATTTAATA +AAACAAGTTTATAGTTGAGAGCACTATTCATGCTTTTTGAGATAATGCAAATTTGTAATT +TTTATGATAGCAATTCTTAATAATTTATTGTCCAAGAGATTTGATAAAATTTTTGATAGT +TATTGGTCTCTGGGACTCAATAGGCACTGAAATGTTTTAATTCAGTTGAAAAGTTGGTTC +AGGATTGCTACCCTCTCTTACCTGTTAGGAGGTTGTTGTTTAACCTGACCTGAAATTCCC +ATGAATAAGAACCTGTTTTTTTTTTTTTTTTCTTTGACAGAGTCTTGCTCTGTCGCCCAG +GCTGCAGTGCAGTGGTGCGATCTTGGCTCGCTGCAAGTTCCGCCTCCCAGGTTCAAGCGA +TTCTCCTGTCTCAGCCTCCCAAGTAGCTGGAGTAGCTGGGACTGCAGGCACGTACCACCA +TGCCTGACTAATTTTTGTATTTTTAGTAGAGACGGGGTTTCACCGTGTTAGCCAGGATGG +TCGCAATCTCTTGACCTCATGATCTGCCTGCCTTGGCCTCCCAAAGTGCTGGGATTACAG +GTGTGAGCCACCGCACCTGGCCCAGGGAATTTCTAATATTTGAGAAGATGTTATTTTTAG +TCTATTATACAAATTTATATATTGTTTACTAATATATAAATTTACATATTGGTTACTAAT +ATGTAAACACCAATTTACATATTGGTTACTAATATGTAAACTTGATAAACATGGATTTCC +ATGGAAATTTAAAAGTATCACAACAATTTGTTTTCCCATTCTGAAACTTGTGATTTATTA +CATTTTCCTACTATTTCAGTTAATTCCATAATGCCAGATTTGTTGTCAATTTGCCGAGTG +ACAAGCCACACTGCTTCCTCTCATTCCTCTATTCCGCAAAACTGCAAAGTTTCCCAGACC +ACAGTCAGGTTTCTCTGGGTTGTCCAACTCTGTAAACTTACAGAGTGGTTGTCCAACTCT +GTAAACTTACAGAGTGGTTGTCCAACTCTGTAAACTTACAGAGTGGTTGTCCAACTCTGT +AAACTTAAGTCACTTTAAGTTTATGACGGAGGGGCTTCGTGAAACTTCATTGACCTTCCA +AGGTGAAAATTGGTCAGTTTTCAGTTATAAAGGACATTAAGGATGGGTGTGGTGGCTGAT +ACATGTAATCCCAGCACTTTCGGGAGACTGAGTCAGGAGGATCACTTAATCCTCATTTAA +AAGGAGTTTGAGACCAGCCTGGGCAACAAAGTGAGGCCTTGTCTCTACAAAAAAATTAGC +TGGGTGTGGTGGTAGGCACTTGTAATCCCAACTACTCTGGAGACTGAGCTGAGAGAAGAT +TGTGTGAGGCTTGGAGGTTGAGGCTGCAGTGAACGGACATCACACCACTACACTCTAGTC +AGGTGACAGAGCAAGACTCTAAATAAATAGGAACATTAGATGGTCTCTCTGCACTCTTGC +CTGGTGGGGACGTGTTAGATACCCTCGTTAGGTTGTGATTTAGTTTTTAATCTGTGAGAT +GTTTGGGTCAAACAATTTTTAGCTGCCATGGAATAAACTTTCCAGTCAGCGTGTGAGTTT +GTGTTTGCCTTTACTTTTTTTTTTCTATATTGTTTTGGTCTATTTTTATCTTTTAATTTC +AGAAAGCTGATTAATCTCTTCCTTTTCTCTTTAAAAATTTTCTTTATCATGTTTGTGCTA +CAGTGGTTATTTTGAGAACTTGTTGGCAGGATAAGTTGCAAAAGTTATGAAGTAGAATAG +GGATGATTTCTGTTTTTGTTTTTTTTTTTTTCAGACAGAGTCTCACTCTCTTGCCTAGGC +TGGAGTGCAGTGGCGTGATCCTGGCTCACTGCAGCCGCCGCCCTCCGGATTCAAGTGATT +TGCCTGGCTCAGCCTCCCAAAAAGCTGGGATTACAGGTGCATGCCACCACACCCAGCTAA +TTTTTGTGTTTTTAGTAGAGATGGGTGTTCACCATGTTGGCCAGGCTGGTCTCAAACTCC +TGACCTCAGGTGATCTGCCTGCCTCCGCACTCCCAAAGTGCTGGGATTACAGACGTGAGC +CACCATGCCTGGCTGAGATTATTTCTTTTTTTATTATAGCCATTGCTTGTAGATATATGC +TGGTGGTTATCTGTAAAAATGTAATAGAAAGGCCGGGCACGGTGGCTCACACCGGTAATC +CCAGCACTTTGGGAGGCTGAGGTGGGCGGATCACAAGGTCAGGAGTGGGAGACCAGCCTG +GCCAATATGGTGAAACCCCGTCTCTACCAAAAATACAAAAATTAGCTGGGCATAGTGGCG +GGCACCTATAGTCCCAGTGACTCGGGAAGCTGAGGCAGGACAATCGCTTGAACCCAGGAG +GCAGAGGTTGCAGTGAGCTGAGATCGTGCTATTATTGCACACCAGCCTGGGCGACAGAGT +GAGACTCCGTCTCAAAAAGAAAAAAGTAATAGACCAATCTTGAATTTATAATTGGAAGTG +TTGATCCCTTTATTTGCAGAATTTATTTATTTGTGACGCAGCTGTTGCTACCTCGCCTTT +TCTTTTGTTGAGCTTAATCTCATGTCAAGTCATTCAACCAACTCAAAAGCGATGAAGACA +TTATTGAATCAACCTGAACTAAATCAGACCTAGGCTTCTTAAAATATACAGCTTAATGCT +TCCAAATGATTTAGAAAACTAAAAAACCTAGCTACGCTGTAGGACACACAGTGGCCAATA +ATACAGGACCCCCAAACTGGCCAGTGGACCACTGCAACCACTATTTACTTCCTCCGTGTT +TAGGAATGTTCAACGCTCCAAGCCCCATAGGCTGATTCAAGAAGATAAAGTGAGACTCAA +GGAATTTCGAAGTGGAACAATACACCAAAGCCTTAAACCTGAAATGACTCTCCTTTTCTG +GGGGGTGAGGGGGAAAGAAAAAGAAAAAGTTTCTAGGGCTCTCGGGGTGGCCTGGATGCC +AGGGTCCCAGAAGTGGCCTTTTCTAGCTCCTGTAACTAAACCTGGCGGAAAACTCCCCGC +CTGCTCACTCCACCCCCACCCGCCCAAGAATGCGTCTTCCCGTCTTCGGTGGCCCTACCC +AGAATCCCAAAATGTGGGTTCCAACCCGGGCCCTGAATGTCTTCTCAAATCCCCGGGACC +CAGGTTCCGGTGCGTGCCTTGCGTGCCGGGTCTTGCCCCTCGGGCGGTACCACCCAGGCA +GCCCTAAATCCAGCCTCCCGGGCCCCCAGCAGCGCCCTCCGCCCCTCCACTATCCGGTCC +GGCTCGAAGTCGGGGCCAAATCCAGAGACAAGAGGGCTGTGCCTGAAACTGAGCAGTTTC +ACCACTCGGCACTCCTGGCGGAAACTTCCCTTTAAAAAAAAGAAAAGAAAAGAAAAGCAA +CAGCACTTTTGGGCTAGCATTTCAATCCTTCCTGCCCTTTAGAGTTCCCAGTTCTGCTTC +CAGCTGGCTTTGGGTGTTCCACTAGAATTGAGTTGTAAAGATATTCTTTAAGTGTTTATA +GAACATTAAGACTTAAAAAAAATCTTTAAAATTAGAGGAGGGAAAAAGCCACCTTATCGC +ACACATCCAGGAAATGCAGCCCCGTGCATCCCTGCTCAGGGATGAGCAGGCGCCCCAGGA +CTCCCGGAGACAGATTTTTGGGCACCCGAGGGAGTCACCGGGCGCGTGTCGGGGTCCGCG +GTGAGGCCCAGCCCCTCCGGCGGTCCCTTAGACGCGCCCTCTGCCCGGCCGGTGTGGACC +GTCCCGGCCATTGTTTACGGGGGATGCCCGTCCAGACGCATTGTTTTGGCCGTTTCCAAC +TTGCCCCGGCCCTTTCCGGGGCATCGCGGGGGACCCTACACCGACGTCCCCCCTCCGCCC +GCGCCCCAAGGGCTGACTGGGCAAATTGGCAGATCCGCCCCGCGGGGCGACCCAACTTTT +CGGAACAGCCCCCCACCGCCCACCCCTGCAGATCCCCGGACCCCCGCTCCCGGCGGAGAT +TCAGGGAACCCCGCATCCCAAGCCCTTCTAAATCGTGCGGCCTGAGTGTGACGGCCAAGA +GCGGATGCAGCCCGGGATCGCCCGCACCTTCCCGTGGGCGG diff --git a/tests/data/dna/genome.fasta.fai b/tests/data/dna/genome.fasta.fai new file mode 100644 index 00000000..b542e338 --- /dev/null +++ b/tests/data/dna/genome.fasta.fai @@ -0,0 +1 @@ +chr22 40001 7 60 61 diff --git a/tests/data/dna/test.dna.bam b/tests/data/dna/test.dna.bam new file mode 100644 index 00000000..b1f4af3a Binary files /dev/null and b/tests/data/dna/test.dna.bam differ diff --git a/tests/data/dna/test.dna.bam.bai b/tests/data/dna/test.dna.bam.bai new file mode 100644 index 00000000..a21f9c86 Binary files /dev/null and b/tests/data/dna/test.dna.bam.bai differ diff --git a/tests/dna_integration_test.rs b/tests/dna_integration_test.rs new file mode 100644 index 00000000..07f267ca --- /dev/null +++ b/tests/dna_integration_test.rs @@ -0,0 +1,413 @@ +//! Parity tests for the DNA pipeline against the committed reference outputs. +//! +//! The fixtures under `tests/expected/dna/` are the output of the upstream +//! tools themselves, at the versions pinned in `VERSIONS.txt`. A failure here +//! is a defect in RustQC, not a reason to regenerate the fixture. +//! +//! Compressed outputs are compared on their decompressed bytes. Two bgzf +//! writers at the same compression level need not emit identical compressed +//! bytes, so comparing the `.gz` files directly would test the compressor +//! rather than this code. + +use std::collections::BTreeMap; +use std::io::Read; +use std::path::{Path, PathBuf}; + +use rust_htslib::bam::Read as BamRead; +use rust_htslib::{bam, bgzf}; + +use rustqc::dna::depth::{DepthAccum, MOSDEPTH_DEFAULT_EXCLUDE}; +use rustqc::dna::mosdepth::{output, ContigDepth, MosdepthResult}; + +/// Window size and thresholds the fixtures were generated with. +const WINDOW_SIZE: u32 = 500; +const THRESHOLDS: [u32; 7] = [1, 5, 10, 15, 20, 30, 50]; + +fn fixture(name: &str) -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/expected/dna") + .join(name) +} + +fn scratch(name: &str) -> PathBuf { + let dir = std::env::temp_dir().join("rustqc-dna-parity"); + std::fs::create_dir_all(&dir).unwrap(); + dir.join(name) +} + +/// Run the depth engine over the committed test BAM and summarise it exactly +/// as the fixtures were generated. +fn compute() -> MosdepthResult { + let bam_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data/dna/test.dna.bam"); + let reader = bam::Reader::from_path(&bam_path).unwrap(); + let header = reader.header().to_owned(); + + let mut contigs = Vec::new(); + for tid in 0..header.target_count() { + let name = String::from_utf8(header.tid2name(tid).to_vec()).unwrap(); + let length = header.target_len(tid).unwrap(); + let mut accum = DepthAccum::new(length, 0, MOSDEPTH_DEFAULT_EXCLUDE); + + let mut record = bam::Record::new(); + let mut per_contig = bam::Reader::from_path(&bam_path).unwrap(); + while let Some(result) = per_contig.read(&mut record) { + result.unwrap(); + if record.tid() == tid as i32 { + accum.process_read(&record); + } + } + let depths = accum.into_depths(); + contigs.push(ContigDepth::from_depths( + &name, + &depths, + Some(WINDOW_SIZE), + &THRESHOLDS, + )); + } + + MosdepthResult { + contigs, + window_size: Some(WINDOW_SIZE), + thresholds: THRESHOLDS.to_vec(), + } +} + +fn read_bgzf(path: &Path) -> String { + let mut reader = bgzf::Reader::from_path(path).unwrap(); + let mut buf = Vec::new(); + reader.read_to_end(&mut buf).unwrap(); + String::from_utf8(buf).unwrap() +} + +/// Compare line by line so a failure names the offending row. +fn assert_same_lines(actual: &str, expected: &str, what: &str) { + let a: Vec<&str> = actual.lines().collect(); + let e: Vec<&str> = expected.lines().collect(); + for (i, (got, want)) in a.iter().zip(e.iter()).enumerate() { + assert_eq!(got, want, "{what}: line {} differs", i + 1); + } + assert_eq!(a.len(), e.len(), "{what}: line count differs"); +} + +#[test] +fn fixture_tool_versions_are_the_pinned_ones() { + let versions = std::fs::read_to_string(fixture("VERSIONS.txt")).unwrap(); + assert!( + versions.contains("mosdepth\t0.3.14"), + "unexpected mosdepth fixture version: {versions}" + ); + assert!( + versions.contains("samtools\t1.24"), + "unexpected samtools fixture version: {versions}" + ); +} + +#[test] +fn summary_matches_mosdepth() { + let path = scratch("test.mosdepth.summary.txt"); + output::write_summary(&compute(), &path).unwrap(); + assert_same_lines( + &std::fs::read_to_string(&path).unwrap(), + &std::fs::read_to_string(fixture("test.mosdepth.summary.txt")).unwrap(), + "summary", + ); +} + +#[test] +fn global_dist_matches_mosdepth() { + let path = scratch("test.mosdepth.global.dist.txt"); + output::write_global_dist(&compute(), &path).unwrap(); + assert_same_lines( + &std::fs::read_to_string(&path).unwrap(), + &std::fs::read_to_string(fixture("test.mosdepth.global.dist.txt")).unwrap(), + "global dist", + ); +} + +#[test] +fn region_dist_matches_mosdepth() { + let path = scratch("test.mosdepth.region.dist.txt"); + output::write_region_dist(&compute(), &path).unwrap(); + assert_same_lines( + &std::fs::read_to_string(&path).unwrap(), + &std::fs::read_to_string(fixture("test.mosdepth.region.dist.txt")).unwrap(), + "region dist", + ); +} + +#[test] +fn per_base_matches_mosdepth() { + let path = scratch("test.per-base.bed.gz"); + output::write_per_base(&compute(), &path).unwrap(); + assert_same_lines( + &read_bgzf(&path), + &read_bgzf(&fixture("test.per-base.bed.gz")), + "per-base", + ); +} + +#[test] +fn regions_match_mosdepth() { + let path = scratch("test.regions.bed.gz"); + output::write_regions(&compute(), &path).unwrap(); + assert_same_lines( + &read_bgzf(&path), + &read_bgzf(&fixture("test.regions.bed.gz")), + "regions", + ); +} + +#[test] +fn thresholds_match_mosdepth() { + let path = scratch("test.thresholds.bed.gz"); + output::write_thresholds(&compute(), &path).unwrap(); + assert_same_lines( + &read_bgzf(&path), + &read_bgzf(&fixture("test.thresholds.bed.gz")), + "thresholds", + ); +} + +/// The depth histogram is the input to both distribution files, so pinning it +/// separately makes a distribution failure easy to attribute. +#[test] +fn depth_histogram_matches_the_per_base_fixture() { + let result = compute(); + let mut expected: BTreeMap = BTreeMap::new(); + for line in read_bgzf(&fixture("test.per-base.bed.gz")).lines() { + let fields: Vec<&str> = line.split('\t').collect(); + let start: u64 = fields[1].parse().unwrap(); + let end: u64 = fields[2].parse().unwrap(); + let depth: u32 = fields[3].parse().unwrap(); + *expected.entry(depth).or_insert(0) += end - start; + } + assert_eq!(result.contigs[0].histogram, expected); +} + +// =================================================================== +// End-to-end parity: the binary, not just the library +// =================================================================== + +/// Run `rustqc dna` once into a scratch directory shared by every end-to-end +/// test, with the same window size and thresholds the fixtures were made with. +fn run_binary() -> &'static Path { + static OUTDIR: std::sync::OnceLock = std::sync::OnceLock::new(); + OUTDIR.get_or_init(|| { + let root = Path::new(env!("CARGO_MANIFEST_DIR")); + let outdir = std::env::temp_dir().join("rustqc-dna-e2e"); + let _ = std::fs::remove_dir_all(&outdir); + std::fs::create_dir_all(&outdir).unwrap(); + + let status = std::process::Command::new(env!("CARGO_BIN_EXE_rustqc")) + .arg("dna") + .arg(root.join("tests/data/dna/test.dna.bam")) + .arg("--outdir") + .arg(&outdir) + .arg("--window-size") + .arg(WINDOW_SIZE.to_string()) + .arg("--quiet") + .status() + .expect("failed to run the rustqc binary"); + assert!(status.success(), "rustqc dna exited with {status}"); + outdir + }) +} + +/// The sample name is the BAM file stem, dots included. +const SAMPLE: &str = "test.dna"; + +fn produced(subdir: &str, name: &str) -> PathBuf { + run_binary().join(subdir).join(name) +} + +#[test] +fn binary_writes_every_mosdepth_output_byte_for_byte() { + for suffix in [ + "mosdepth.summary.txt", + "mosdepth.global.dist.txt", + "mosdepth.region.dist.txt", + ] { + let got = std::fs::read_to_string(produced("mosdepth", &format!("{SAMPLE}.{suffix}"))) + .unwrap_or_else(|e| panic!("reading {suffix}: {e}")); + let want = std::fs::read_to_string(fixture(&format!("test.{suffix}"))).unwrap(); + assert_same_lines(&got, &want, suffix); + } + for suffix in ["per-base.bed.gz", "regions.bed.gz", "thresholds.bed.gz"] { + let got = read_bgzf(&produced("mosdepth", &format!("{SAMPLE}.{suffix}"))); + let want = read_bgzf(&fixture(&format!("test.{suffix}"))); + assert_same_lines(&got, &want, suffix); + } +} + +#[test] +fn binary_writes_flagstat_and_idxstats_byte_for_byte() { + for suffix in ["flagstat", "idxstats"] { + let got = std::fs::read_to_string(produced("samtools", &format!("{SAMPLE}.{suffix}.txt"))) + .unwrap(); + let want = std::fs::read_to_string(fixture(&format!("test.{suffix}.txt"))).unwrap(); + assert_eq!(got, want, "{suffix} must match samtools exactly"); + } +} + +/// `samtools stats` output is compared on its data lines only. RustQC writes +/// its own `#` header, naming itself rather than reproducing samtools' command +/// line and version banner, which is deliberate and shared with the `rna` +/// pipeline. Everything below the header must match exactly. +#[test] +fn binary_writes_samtools_stats_data_lines_byte_for_byte() { + let got = + std::fs::read_to_string(produced("samtools", &format!("{SAMPLE}.stats.txt"))).unwrap(); + let want = std::fs::read_to_string(fixture("test.stats.txt")).unwrap(); + let strip = |s: &str| { + s.lines() + .filter(|l| !l.starts_with('#')) + .collect::>() + .join("\n") + }; + assert_same_lines(&strip(&got), &strip(&want), "samtools stats data lines"); +} + +#[test] +fn the_stats_header_does_not_claim_the_wrong_subcommand() { + let got = + std::fs::read_to_string(produced("samtools", &format!("{SAMPLE}.stats.txt"))).unwrap(); + assert!( + !got.contains("rustqc rna"), + "the dna pipeline must not label its output as rna output" + ); +} + +#[test] +fn binary_refuses_input_without_duplicate_marks() { + let root = Path::new(env!("CARGO_MANIFEST_DIR")); + let outdir = std::env::temp_dir().join("rustqc-dna-nodup"); + let _ = std::fs::remove_dir_all(&outdir); + let output = std::process::Command::new(env!("CARGO_BIN_EXE_rustqc")) + .arg("dna") + .arg(root.join("tests/data/test_nodup.bam")) + .arg("--outdir") + .arg(&outdir) + .arg("--json-summary") + .arg("-") + .output() + .expect("failed to run the rustqc binary"); + let combined = format!( + "{}{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + assert!( + combined.contains("duplicate-flagged") || combined.contains("failed"), + "expected a duplicate-marking complaint, got: {combined}" + ); +} + +/// The JSON summary is the machine-readable face of a run, so its DNA block is +/// pinned against the same figures the mosdepth fixtures carry. +#[test] +fn json_summary_carries_the_dna_block() { + let root = Path::new(env!("CARGO_MANIFEST_DIR")); + let outdir = std::env::temp_dir().join("rustqc-dna-json"); + let _ = std::fs::remove_dir_all(&outdir); + std::fs::create_dir_all(&outdir).unwrap(); + let json_path = outdir.join("summary.json"); + + let status = std::process::Command::new(env!("CARGO_BIN_EXE_rustqc")) + .arg("dna") + .arg(root.join("tests/data/dna/test.dna.bam")) + .arg("--outdir") + .arg(&outdir) + .arg("--window-size") + .arg(WINDOW_SIZE.to_string()) + .arg("--json-summary") + .arg(&json_path) + .arg("--quiet") + .status() + .expect("failed to run the rustqc binary"); + assert!(status.success()); + + let text = std::fs::read_to_string(&json_path).unwrap(); + // Checked as text rather than parsed: the point is that these exact + // figures reach the summary, and pulling in a JSON parser for one test + // would not make the assertion any stronger. + for needle in [ + "\"genome_length\": 40001", + "\"covered_bases\": 247878", + "\"max_coverage\": 867", + "\"total_reads\": 5644", + "\"duplicates\": 1656", + ] { + assert!( + text.contains(needle), + "summary is missing {needle}:\n{text}" + ); + } + assert!( + !text.contains("\"dupradar\""), + "a dna run must not emit the rna summary blocks" + ); +} + +/// The citations file names the tools this pipeline actually replicated. +#[test] +fn citations_name_the_dna_tools_only() { + let citations = std::fs::read_to_string(run_binary().join("CITATIONS.md")).unwrap(); + assert!(citations.contains("mosdepth"), "mosdepth must be cited"); + assert!(citations.contains("Samtools"), "samtools must be cited"); + assert!( + !citations.contains("dupRadar") && !citations.contains("RSeQC"), + "a dna run must not cite the rna-only tools" + ); +} + +/// The `.csi` companion indexes are not compared byte for byte: an index is +/// binary metadata over the compressed blocks, and two writers answering the +/// same queries need not produce the same bytes. What matters is that a region +/// query returns the same rows through our index as through mosdepth's. +/// +/// The query goes through the `tabix` binary rather than rust-htslib's tabix +/// reader, which ends a fetched region by yielding a `TabixTruncatedRecord` +/// instead of stopping, and does so at different points for the two files. The +/// test is skipped where `tabix` is not installed, the same way the fixtures +/// themselves depend on the upstream tools being present. +#[test] +fn csi_indexes_answer_region_queries_like_mosdepths() { + if std::process::Command::new("tabix") + .arg("--version") + .output() + .is_err() + { + eprintln!("skipping: tabix is not installed"); + return; + } + + let query = |path: &Path| -> String { + let out = std::process::Command::new("tabix") + .arg(path) + .arg("chr22:2000-2500") + .output() + .unwrap_or_else(|e| panic!("querying {}: {e}", path.display())); + assert!( + out.status.success(), + "tabix failed on {}: {}", + path.display(), + String::from_utf8_lossy(&out.stderr) + ); + String::from_utf8(out.stdout).unwrap() + }; + + for suffix in ["per-base.bed.gz", "regions.bed.gz", "thresholds.bed.gz"] { + let ours = produced("mosdepth", &format!("{SAMPLE}.{suffix}")); + let index = ours.with_file_name(format!("{SAMPLE}.{suffix}.csi")); + assert!( + index.exists(), + "{suffix} must have a .csi companion at {}", + index.display() + ); + + let mine = query(&ours); + let theirs = query(&fixture(&format!("test.{suffix}"))); + assert!(!mine.is_empty(), "{suffix}: the query returned nothing"); + assert_eq!(mine, theirs, "{suffix}: region query results differ"); + } +} diff --git a/tests/expected/dna/VERSIONS.txt b/tests/expected/dna/VERSIONS.txt new file mode 100644 index 00000000..640b6982 --- /dev/null +++ b/tests/expected/dna/VERSIONS.txt @@ -0,0 +1,2 @@ +mosdepth 0.3.14 +samtools 1.24 diff --git a/tests/expected/dna/test.flagstat.txt b/tests/expected/dna/test.flagstat.txt new file mode 100644 index 00000000..1d2e97e5 --- /dev/null +++ b/tests/expected/dna/test.flagstat.txt @@ -0,0 +1,16 @@ +5644 + 0 in total (QC-passed reads + QC-failed reads) +5642 + 0 primary +2 + 0 secondary +0 + 0 supplementary +1656 + 0 duplicates +1656 + 0 primary duplicates +5642 + 0 mapped (99.96% : N/A) +5640 + 0 primary mapped (99.96% : N/A) +5642 + 0 paired in sequencing +2821 + 0 read1 +2821 + 0 read2 +5638 + 0 properly paired (99.93% : N/A) +5640 + 0 with itself and mate mapped +0 + 0 singletons (0.00% : N/A) +0 + 0 with mate mapped to a different chr +0 + 0 with mate mapped to a different chr (mapQ>=5) diff --git a/tests/expected/dna/test.idxstats.txt b/tests/expected/dna/test.idxstats.txt new file mode 100644 index 00000000..f56aa9fe --- /dev/null +++ b/tests/expected/dna/test.idxstats.txt @@ -0,0 +1,2 @@ +chr22 40001 5642 0 +* 0 0 2 diff --git a/tests/expected/dna/test.mosdepth.global.dist.txt b/tests/expected/dna/test.mosdepth.global.dist.txt new file mode 100644 index 00000000..2299da21 --- /dev/null +++ b/tests/expected/dna/test.mosdepth.global.dist.txt @@ -0,0 +1,1094 @@ +chr22 866 0.00 +chr22 865 0.00 +chr22 863 0.00 +chr22 862 0.00 +chr22 860 0.00 +chr22 859 0.00 +chr22 858 0.00 +chr22 857 0.00 +chr22 854 0.00 +chr22 851 0.00 +chr22 848 0.00 +chr22 846 0.00 +chr22 842 0.00 +chr22 840 0.00 +chr22 837 0.00 +chr22 833 0.00 +chr22 832 0.00 +chr22 831 0.00 +chr22 830 0.00 +chr22 827 0.00 +chr22 825 0.00 +chr22 822 0.00 +chr22 817 0.00 +chr22 816 0.00 +chr22 814 0.00 +chr22 812 0.00 +chr22 811 0.00 +chr22 808 0.00 +chr22 802 0.00 +chr22 801 0.00 +chr22 799 0.00 +chr22 798 0.00 +chr22 795 0.00 +chr22 792 0.00 +chr22 790 0.00 +chr22 788 0.00 +chr22 786 0.00 +chr22 784 0.00 +chr22 779 0.00 +chr22 777 0.00 +chr22 775 0.00 +chr22 773 0.00 +chr22 768 0.00 +chr22 764 0.00 +chr22 761 0.00 +chr22 757 0.00 +chr22 754 0.00 +chr22 750 0.00 +chr22 747 0.00 +chr22 745 0.00 +chr22 743 0.00 +chr22 738 0.00 +chr22 734 0.00 +chr22 733 0.00 +chr22 726 0.00 +chr22 724 0.00 +chr22 720 0.00 +chr22 716 0.00 +chr22 711 0.00 +chr22 706 0.00 +chr22 702 0.00 +chr22 695 0.00 +chr22 693 0.00 +chr22 690 0.00 +chr22 689 0.00 +chr22 684 0.00 +chr22 683 0.00 +chr22 677 0.00 +chr22 675 0.00 +chr22 668 0.00 +chr22 667 0.00 +chr22 666 0.00 +chr22 662 0.00 +chr22 661 0.00 +chr22 659 0.00 +chr22 658 0.00 +chr22 657 0.00 +chr22 656 0.00 +chr22 654 0.00 +chr22 652 0.00 +chr22 651 0.00 +chr22 649 0.00 +chr22 647 0.00 +chr22 645 0.00 +chr22 644 0.00 +chr22 641 0.00 +chr22 640 0.00 +chr22 639 0.00 +chr22 638 0.00 +chr22 637 0.00 +chr22 636 0.00 +chr22 635 0.00 +chr22 634 0.00 +chr22 633 0.00 +chr22 631 0.00 +chr22 628 0.00 +chr22 627 0.00 +chr22 626 0.00 +chr22 624 0.00 +chr22 623 0.00 +chr22 622 0.00 +chr22 618 0.00 +chr22 617 0.00 +chr22 616 0.00 +chr22 614 0.00 +chr22 613 0.00 +chr22 612 0.00 +chr22 611 0.00 +chr22 609 0.00 +chr22 608 0.00 +chr22 605 0.00 +chr22 604 0.00 +chr22 603 0.00 +chr22 602 0.00 +chr22 601 0.00 +chr22 600 0.00 +chr22 598 0.00 +chr22 596 0.00 +chr22 595 0.00 +chr22 594 0.00 +chr22 592 0.00 +chr22 590 0.00 +chr22 589 0.00 +chr22 588 0.00 +chr22 587 0.00 +chr22 583 0.00 +chr22 582 0.00 +chr22 579 0.00 +chr22 577 0.00 +chr22 576 0.00 +chr22 575 0.00 +chr22 574 0.00 +chr22 571 0.00 +chr22 565 0.00 +chr22 562 0.00 +chr22 561 0.00 +chr22 557 0.00 +chr22 555 0.00 +chr22 554 0.00 +chr22 552 0.00 +chr22 550 0.00 +chr22 549 0.00 +chr22 547 0.00 +chr22 545 0.00 +chr22 540 0.00 +chr22 539 0.00 +chr22 536 0.00 +chr22 532 0.00 +chr22 531 0.00 +chr22 527 0.00 +chr22 526 0.01 +chr22 520 0.01 +chr22 518 0.01 +chr22 517 0.01 +chr22 516 0.01 +chr22 514 0.01 +chr22 512 0.01 +chr22 506 0.01 +chr22 505 0.01 +chr22 503 0.01 +chr22 500 0.01 +chr22 499 0.01 +chr22 496 0.01 +chr22 494 0.01 +chr22 491 0.01 +chr22 490 0.01 +chr22 489 0.01 +chr22 488 0.01 +chr22 485 0.01 +chr22 483 0.01 +chr22 482 0.01 +chr22 481 0.01 +chr22 477 0.01 +chr22 474 0.01 +chr22 472 0.01 +chr22 469 0.01 +chr22 468 0.01 +chr22 466 0.01 +chr22 461 0.01 +chr22 460 0.01 +chr22 457 0.01 +chr22 455 0.01 +chr22 453 0.01 +chr22 451 0.01 +chr22 448 0.01 +chr22 445 0.01 +chr22 444 0.01 +chr22 441 0.01 +chr22 439 0.01 +chr22 437 0.01 +chr22 435 0.01 +chr22 434 0.01 +chr22 431 0.01 +chr22 426 0.01 +chr22 425 0.01 +chr22 422 0.01 +chr22 419 0.01 +chr22 418 0.01 +chr22 414 0.01 +chr22 413 0.01 +chr22 410 0.01 +chr22 408 0.01 +chr22 406 0.01 +chr22 405 0.01 +chr22 401 0.01 +chr22 397 0.01 +chr22 395 0.01 +chr22 394 0.01 +chr22 392 0.01 +chr22 391 0.01 +chr22 387 0.01 +chr22 385 0.01 +chr22 384 0.01 +chr22 383 0.01 +chr22 381 0.01 +chr22 379 0.01 +chr22 376 0.01 +chr22 374 0.01 +chr22 373 0.01 +chr22 368 0.01 +chr22 366 0.01 +chr22 365 0.01 +chr22 361 0.01 +chr22 360 0.01 +chr22 359 0.01 +chr22 353 0.01 +chr22 352 0.01 +chr22 350 0.01 +chr22 345 0.01 +chr22 344 0.01 +chr22 343 0.01 +chr22 338 0.01 +chr22 337 0.01 +chr22 335 0.01 +chr22 334 0.01 +chr22 330 0.01 +chr22 328 0.01 +chr22 327 0.01 +chr22 324 0.01 +chr22 320 0.01 +chr22 318 0.01 +chr22 315 0.01 +chr22 311 0.01 +chr22 310 0.01 +chr22 307 0.01 +chr22 305 0.01 +chr22 300 0.01 +chr22 299 0.01 +chr22 298 0.01 +chr22 297 0.01 +chr22 296 0.01 +chr22 295 0.01 +chr22 294 0.01 +chr22 293 0.01 +chr22 292 0.01 +chr22 291 0.01 +chr22 290 0.01 +chr22 289 0.01 +chr22 288 0.01 +chr22 287 0.01 +chr22 286 0.01 +chr22 285 0.01 +chr22 284 0.01 +chr22 283 0.01 +chr22 282 0.01 +chr22 281 0.01 +chr22 280 0.01 +chr22 279 0.01 +chr22 278 0.01 +chr22 277 0.01 +chr22 276 0.01 +chr22 275 0.01 +chr22 274 0.01 +chr22 273 0.01 +chr22 272 0.01 +chr22 271 0.01 +chr22 270 0.01 +chr22 269 0.01 +chr22 268 0.01 +chr22 267 0.01 +chr22 266 0.01 +chr22 265 0.01 +chr22 264 0.01 +chr22 263 0.01 +chr22 262 0.01 +chr22 261 0.01 +chr22 260 0.01 +chr22 259 0.01 +chr22 258 0.01 +chr22 257 0.01 +chr22 256 0.01 +chr22 255 0.01 +chr22 254 0.01 +chr22 253 0.01 +chr22 252 0.01 +chr22 251 0.01 +chr22 250 0.01 +chr22 249 0.01 +chr22 248 0.01 +chr22 247 0.01 +chr22 246 0.01 +chr22 245 0.01 +chr22 244 0.01 +chr22 243 0.01 +chr22 242 0.01 +chr22 241 0.01 +chr22 240 0.01 +chr22 239 0.01 +chr22 238 0.01 +chr22 237 0.01 +chr22 236 0.01 +chr22 235 0.01 +chr22 234 0.01 +chr22 233 0.01 +chr22 232 0.01 +chr22 231 0.01 +chr22 230 0.01 +chr22 229 0.01 +chr22 228 0.01 +chr22 227 0.01 +chr22 226 0.01 +chr22 225 0.01 +chr22 224 0.01 +chr22 223 0.01 +chr22 222 0.01 +chr22 221 0.01 +chr22 220 0.01 +chr22 219 0.01 +chr22 218 0.01 +chr22 217 0.01 +chr22 216 0.01 +chr22 215 0.01 +chr22 214 0.01 +chr22 213 0.01 +chr22 212 0.01 +chr22 211 0.01 +chr22 210 0.01 +chr22 209 0.01 +chr22 208 0.01 +chr22 207 0.01 +chr22 206 0.01 +chr22 205 0.01 +chr22 204 0.01 +chr22 203 0.01 +chr22 202 0.01 +chr22 201 0.01 +chr22 200 0.01 +chr22 199 0.01 +chr22 198 0.01 +chr22 197 0.01 +chr22 196 0.01 +chr22 195 0.01 +chr22 194 0.01 +chr22 193 0.01 +chr22 192 0.01 +chr22 191 0.01 +chr22 190 0.01 +chr22 189 0.01 +chr22 188 0.01 +chr22 187 0.01 +chr22 186 0.01 +chr22 185 0.01 +chr22 184 0.01 +chr22 183 0.01 +chr22 182 0.01 +chr22 181 0.01 +chr22 180 0.01 +chr22 179 0.01 +chr22 178 0.01 +chr22 177 0.01 +chr22 176 0.01 +chr22 175 0.01 +chr22 174 0.01 +chr22 173 0.01 +chr22 172 0.01 +chr22 171 0.01 +chr22 170 0.01 +chr22 169 0.01 +chr22 168 0.01 +chr22 167 0.01 +chr22 166 0.01 +chr22 165 0.01 +chr22 164 0.01 +chr22 163 0.01 +chr22 162 0.01 +chr22 161 0.01 +chr22 160 0.01 +chr22 159 0.01 +chr22 158 0.01 +chr22 157 0.01 +chr22 156 0.01 +chr22 155 0.01 +chr22 154 0.01 +chr22 153 0.01 +chr22 152 0.01 +chr22 151 0.01 +chr22 150 0.01 +chr22 149 0.01 +chr22 148 0.01 +chr22 147 0.01 +chr22 146 0.01 +chr22 145 0.01 +chr22 144 0.01 +chr22 143 0.01 +chr22 142 0.01 +chr22 141 0.01 +chr22 140 0.01 +chr22 139 0.01 +chr22 138 0.01 +chr22 137 0.01 +chr22 136 0.01 +chr22 135 0.01 +chr22 134 0.01 +chr22 133 0.01 +chr22 132 0.01 +chr22 131 0.01 +chr22 130 0.01 +chr22 129 0.01 +chr22 128 0.01 +chr22 127 0.01 +chr22 126 0.01 +chr22 125 0.01 +chr22 124 0.01 +chr22 123 0.01 +chr22 122 0.01 +chr22 121 0.01 +chr22 120 0.01 +chr22 119 0.01 +chr22 118 0.01 +chr22 117 0.01 +chr22 116 0.01 +chr22 115 0.01 +chr22 114 0.01 +chr22 113 0.01 +chr22 112 0.01 +chr22 111 0.01 +chr22 110 0.01 +chr22 109 0.01 +chr22 108 0.01 +chr22 107 0.01 +chr22 106 0.01 +chr22 105 0.01 +chr22 104 0.01 +chr22 103 0.02 +chr22 102 0.02 +chr22 101 0.02 +chr22 100 0.02 +chr22 99 0.02 +chr22 98 0.02 +chr22 97 0.02 +chr22 96 0.02 +chr22 95 0.02 +chr22 94 0.02 +chr22 93 0.02 +chr22 92 0.02 +chr22 91 0.02 +chr22 90 0.02 +chr22 89 0.02 +chr22 88 0.02 +chr22 87 0.02 +chr22 86 0.02 +chr22 85 0.02 +chr22 84 0.02 +chr22 83 0.02 +chr22 82 0.02 +chr22 81 0.02 +chr22 80 0.02 +chr22 79 0.02 +chr22 78 0.02 +chr22 77 0.02 +chr22 76 0.02 +chr22 75 0.02 +chr22 74 0.02 +chr22 73 0.02 +chr22 72 0.02 +chr22 71 0.02 +chr22 70 0.02 +chr22 69 0.02 +chr22 68 0.02 +chr22 67 0.02 +chr22 66 0.02 +chr22 65 0.02 +chr22 64 0.02 +chr22 63 0.02 +chr22 62 0.02 +chr22 61 0.02 +chr22 60 0.02 +chr22 59 0.02 +chr22 58 0.02 +chr22 57 0.02 +chr22 56 0.02 +chr22 55 0.02 +chr22 54 0.02 +chr22 53 0.02 +chr22 52 0.02 +chr22 51 0.02 +chr22 50 0.02 +chr22 49 0.02 +chr22 48 0.02 +chr22 47 0.02 +chr22 46 0.02 +chr22 45 0.02 +chr22 44 0.02 +chr22 43 0.02 +chr22 42 0.02 +chr22 41 0.02 +chr22 40 0.02 +chr22 39 0.02 +chr22 38 0.02 +chr22 37 0.02 +chr22 36 0.02 +chr22 35 0.02 +chr22 34 0.02 +chr22 33 0.02 +chr22 32 0.02 +chr22 31 0.02 +chr22 30 0.02 +chr22 29 0.02 +chr22 28 0.02 +chr22 27 0.02 +chr22 26 0.02 +chr22 25 0.02 +chr22 24 0.02 +chr22 23 0.02 +chr22 22 0.02 +chr22 21 0.02 +chr22 20 0.02 +chr22 19 0.02 +chr22 18 0.02 +chr22 17 0.02 +chr22 16 0.02 +chr22 15 0.02 +chr22 14 0.02 +chr22 13 0.02 +chr22 12 0.02 +chr22 11 0.02 +chr22 10 0.02 +chr22 9 0.02 +chr22 8 0.02 +chr22 7 0.02 +chr22 6 0.02 +chr22 5 0.02 +chr22 4 0.03 +chr22 3 0.03 +chr22 2 0.03 +chr22 1 0.03 +chr22 0 1.00 +total 866 0.00 +total 865 0.00 +total 863 0.00 +total 862 0.00 +total 860 0.00 +total 859 0.00 +total 858 0.00 +total 857 0.00 +total 854 0.00 +total 851 0.00 +total 848 0.00 +total 846 0.00 +total 842 0.00 +total 840 0.00 +total 837 0.00 +total 833 0.00 +total 832 0.00 +total 831 0.00 +total 830 0.00 +total 827 0.00 +total 825 0.00 +total 822 0.00 +total 817 0.00 +total 816 0.00 +total 814 0.00 +total 812 0.00 +total 811 0.00 +total 808 0.00 +total 802 0.00 +total 801 0.00 +total 799 0.00 +total 798 0.00 +total 795 0.00 +total 792 0.00 +total 790 0.00 +total 788 0.00 +total 786 0.00 +total 784 0.00 +total 779 0.00 +total 777 0.00 +total 775 0.00 +total 773 0.00 +total 768 0.00 +total 764 0.00 +total 761 0.00 +total 757 0.00 +total 754 0.00 +total 750 0.00 +total 747 0.00 +total 745 0.00 +total 743 0.00 +total 738 0.00 +total 734 0.00 +total 733 0.00 +total 726 0.00 +total 724 0.00 +total 720 0.00 +total 716 0.00 +total 711 0.00 +total 706 0.00 +total 702 0.00 +total 695 0.00 +total 693 0.00 +total 690 0.00 +total 689 0.00 +total 684 0.00 +total 683 0.00 +total 677 0.00 +total 675 0.00 +total 668 0.00 +total 667 0.00 +total 666 0.00 +total 662 0.00 +total 661 0.00 +total 659 0.00 +total 658 0.00 +total 657 0.00 +total 656 0.00 +total 654 0.00 +total 652 0.00 +total 651 0.00 +total 649 0.00 +total 647 0.00 +total 645 0.00 +total 644 0.00 +total 641 0.00 +total 640 0.00 +total 639 0.00 +total 638 0.00 +total 637 0.00 +total 636 0.00 +total 635 0.00 +total 634 0.00 +total 633 0.00 +total 631 0.00 +total 628 0.00 +total 627 0.00 +total 626 0.00 +total 624 0.00 +total 623 0.00 +total 622 0.00 +total 618 0.00 +total 617 0.00 +total 616 0.00 +total 614 0.00 +total 613 0.00 +total 612 0.00 +total 611 0.00 +total 609 0.00 +total 608 0.00 +total 605 0.00 +total 604 0.00 +total 603 0.00 +total 602 0.00 +total 601 0.00 +total 600 0.00 +total 598 0.00 +total 596 0.00 +total 595 0.00 +total 594 0.00 +total 592 0.00 +total 590 0.00 +total 589 0.00 +total 588 0.00 +total 587 0.00 +total 583 0.00 +total 582 0.00 +total 579 0.00 +total 577 0.00 +total 576 0.00 +total 575 0.00 +total 574 0.00 +total 571 0.00 +total 565 0.00 +total 562 0.00 +total 561 0.00 +total 557 0.00 +total 555 0.00 +total 554 0.00 +total 552 0.00 +total 550 0.00 +total 549 0.00 +total 547 0.00 +total 545 0.00 +total 540 0.00 +total 539 0.00 +total 536 0.00 +total 532 0.00 +total 531 0.00 +total 527 0.00 +total 526 0.01 +total 520 0.01 +total 518 0.01 +total 517 0.01 +total 516 0.01 +total 514 0.01 +total 512 0.01 +total 506 0.01 +total 505 0.01 +total 503 0.01 +total 500 0.01 +total 499 0.01 +total 496 0.01 +total 494 0.01 +total 491 0.01 +total 490 0.01 +total 489 0.01 +total 488 0.01 +total 485 0.01 +total 483 0.01 +total 482 0.01 +total 481 0.01 +total 477 0.01 +total 474 0.01 +total 472 0.01 +total 469 0.01 +total 468 0.01 +total 466 0.01 +total 461 0.01 +total 460 0.01 +total 457 0.01 +total 455 0.01 +total 453 0.01 +total 451 0.01 +total 448 0.01 +total 445 0.01 +total 444 0.01 +total 441 0.01 +total 439 0.01 +total 437 0.01 +total 435 0.01 +total 434 0.01 +total 431 0.01 +total 426 0.01 +total 425 0.01 +total 422 0.01 +total 419 0.01 +total 418 0.01 +total 414 0.01 +total 413 0.01 +total 410 0.01 +total 408 0.01 +total 406 0.01 +total 405 0.01 +total 401 0.01 +total 397 0.01 +total 395 0.01 +total 394 0.01 +total 392 0.01 +total 391 0.01 +total 387 0.01 +total 385 0.01 +total 384 0.01 +total 383 0.01 +total 381 0.01 +total 379 0.01 +total 376 0.01 +total 374 0.01 +total 373 0.01 +total 368 0.01 +total 366 0.01 +total 365 0.01 +total 361 0.01 +total 360 0.01 +total 359 0.01 +total 353 0.01 +total 352 0.01 +total 350 0.01 +total 345 0.01 +total 344 0.01 +total 343 0.01 +total 338 0.01 +total 337 0.01 +total 335 0.01 +total 334 0.01 +total 330 0.01 +total 328 0.01 +total 327 0.01 +total 324 0.01 +total 320 0.01 +total 318 0.01 +total 315 0.01 +total 311 0.01 +total 310 0.01 +total 307 0.01 +total 305 0.01 +total 300 0.01 +total 299 0.01 +total 298 0.01 +total 297 0.01 +total 296 0.01 +total 295 0.01 +total 294 0.01 +total 293 0.01 +total 292 0.01 +total 291 0.01 +total 290 0.01 +total 289 0.01 +total 288 0.01 +total 287 0.01 +total 286 0.01 +total 285 0.01 +total 284 0.01 +total 283 0.01 +total 282 0.01 +total 281 0.01 +total 280 0.01 +total 279 0.01 +total 278 0.01 +total 277 0.01 +total 276 0.01 +total 275 0.01 +total 274 0.01 +total 273 0.01 +total 272 0.01 +total 271 0.01 +total 270 0.01 +total 269 0.01 +total 268 0.01 +total 267 0.01 +total 266 0.01 +total 265 0.01 +total 264 0.01 +total 263 0.01 +total 262 0.01 +total 261 0.01 +total 260 0.01 +total 259 0.01 +total 258 0.01 +total 257 0.01 +total 256 0.01 +total 255 0.01 +total 254 0.01 +total 253 0.01 +total 252 0.01 +total 251 0.01 +total 250 0.01 +total 249 0.01 +total 248 0.01 +total 247 0.01 +total 246 0.01 +total 245 0.01 +total 244 0.01 +total 243 0.01 +total 242 0.01 +total 241 0.01 +total 240 0.01 +total 239 0.01 +total 238 0.01 +total 237 0.01 +total 236 0.01 +total 235 0.01 +total 234 0.01 +total 233 0.01 +total 232 0.01 +total 231 0.01 +total 230 0.01 +total 229 0.01 +total 228 0.01 +total 227 0.01 +total 226 0.01 +total 225 0.01 +total 224 0.01 +total 223 0.01 +total 222 0.01 +total 221 0.01 +total 220 0.01 +total 219 0.01 +total 218 0.01 +total 217 0.01 +total 216 0.01 +total 215 0.01 +total 214 0.01 +total 213 0.01 +total 212 0.01 +total 211 0.01 +total 210 0.01 +total 209 0.01 +total 208 0.01 +total 207 0.01 +total 206 0.01 +total 205 0.01 +total 204 0.01 +total 203 0.01 +total 202 0.01 +total 201 0.01 +total 200 0.01 +total 199 0.01 +total 198 0.01 +total 197 0.01 +total 196 0.01 +total 195 0.01 +total 194 0.01 +total 193 0.01 +total 192 0.01 +total 191 0.01 +total 190 0.01 +total 189 0.01 +total 188 0.01 +total 187 0.01 +total 186 0.01 +total 185 0.01 +total 184 0.01 +total 183 0.01 +total 182 0.01 +total 181 0.01 +total 180 0.01 +total 179 0.01 +total 178 0.01 +total 177 0.01 +total 176 0.01 +total 175 0.01 +total 174 0.01 +total 173 0.01 +total 172 0.01 +total 171 0.01 +total 170 0.01 +total 169 0.01 +total 168 0.01 +total 167 0.01 +total 166 0.01 +total 165 0.01 +total 164 0.01 +total 163 0.01 +total 162 0.01 +total 161 0.01 +total 160 0.01 +total 159 0.01 +total 158 0.01 +total 157 0.01 +total 156 0.01 +total 155 0.01 +total 154 0.01 +total 153 0.01 +total 152 0.01 +total 151 0.01 +total 150 0.01 +total 149 0.01 +total 148 0.01 +total 147 0.01 +total 146 0.01 +total 145 0.01 +total 144 0.01 +total 143 0.01 +total 142 0.01 +total 141 0.01 +total 140 0.01 +total 139 0.01 +total 138 0.01 +total 137 0.01 +total 136 0.01 +total 135 0.01 +total 134 0.01 +total 133 0.01 +total 132 0.01 +total 131 0.01 +total 130 0.01 +total 129 0.01 +total 128 0.01 +total 127 0.01 +total 126 0.01 +total 125 0.01 +total 124 0.01 +total 123 0.01 +total 122 0.01 +total 121 0.01 +total 120 0.01 +total 119 0.01 +total 118 0.01 +total 117 0.01 +total 116 0.01 +total 115 0.01 +total 114 0.01 +total 113 0.01 +total 112 0.01 +total 111 0.01 +total 110 0.01 +total 109 0.01 +total 108 0.01 +total 107 0.01 +total 106 0.01 +total 105 0.01 +total 104 0.01 +total 103 0.02 +total 102 0.02 +total 101 0.02 +total 100 0.02 +total 99 0.02 +total 98 0.02 +total 97 0.02 +total 96 0.02 +total 95 0.02 +total 94 0.02 +total 93 0.02 +total 92 0.02 +total 91 0.02 +total 90 0.02 +total 89 0.02 +total 88 0.02 +total 87 0.02 +total 86 0.02 +total 85 0.02 +total 84 0.02 +total 83 0.02 +total 82 0.02 +total 81 0.02 +total 80 0.02 +total 79 0.02 +total 78 0.02 +total 77 0.02 +total 76 0.02 +total 75 0.02 +total 74 0.02 +total 73 0.02 +total 72 0.02 +total 71 0.02 +total 70 0.02 +total 69 0.02 +total 68 0.02 +total 67 0.02 +total 66 0.02 +total 65 0.02 +total 64 0.02 +total 63 0.02 +total 62 0.02 +total 61 0.02 +total 60 0.02 +total 59 0.02 +total 58 0.02 +total 57 0.02 +total 56 0.02 +total 55 0.02 +total 54 0.02 +total 53 0.02 +total 52 0.02 +total 51 0.02 +total 50 0.02 +total 49 0.02 +total 48 0.02 +total 47 0.02 +total 46 0.02 +total 45 0.02 +total 44 0.02 +total 43 0.02 +total 42 0.02 +total 41 0.02 +total 40 0.02 +total 39 0.02 +total 38 0.02 +total 37 0.02 +total 36 0.02 +total 35 0.02 +total 34 0.02 +total 33 0.02 +total 32 0.02 +total 31 0.02 +total 30 0.02 +total 29 0.02 +total 28 0.02 +total 27 0.02 +total 26 0.02 +total 25 0.02 +total 24 0.02 +total 23 0.02 +total 22 0.02 +total 21 0.02 +total 20 0.02 +total 19 0.02 +total 18 0.02 +total 17 0.02 +total 16 0.02 +total 15 0.02 +total 14 0.02 +total 13 0.02 +total 12 0.02 +total 11 0.02 +total 10 0.02 +total 9 0.02 +total 8 0.02 +total 7 0.02 +total 6 0.02 +total 5 0.02 +total 4 0.03 +total 3 0.03 +total 2 0.03 +total 1 0.03 +total 0 1.00 diff --git a/tests/expected/dna/test.mosdepth.region.dist.txt b/tests/expected/dna/test.mosdepth.region.dist.txt new file mode 100644 index 00000000..6e1d6dc7 --- /dev/null +++ b/tests/expected/dna/test.mosdepth.region.dist.txt @@ -0,0 +1,410 @@ +chr22 204 0.01 +chr22 203 0.01 +chr22 202 0.01 +chr22 201 0.01 +chr22 200 0.01 +chr22 199 0.01 +chr22 198 0.01 +chr22 197 0.01 +chr22 196 0.01 +chr22 195 0.01 +chr22 194 0.01 +chr22 193 0.01 +chr22 192 0.01 +chr22 191 0.01 +chr22 190 0.01 +chr22 189 0.01 +chr22 188 0.01 +chr22 187 0.01 +chr22 186 0.01 +chr22 185 0.01 +chr22 184 0.01 +chr22 183 0.01 +chr22 182 0.01 +chr22 181 0.01 +chr22 180 0.01 +chr22 179 0.01 +chr22 178 0.01 +chr22 177 0.01 +chr22 176 0.01 +chr22 175 0.01 +chr22 174 0.01 +chr22 173 0.01 +chr22 172 0.01 +chr22 171 0.01 +chr22 170 0.01 +chr22 169 0.01 +chr22 168 0.01 +chr22 167 0.01 +chr22 166 0.01 +chr22 165 0.01 +chr22 164 0.01 +chr22 163 0.01 +chr22 162 0.01 +chr22 161 0.01 +chr22 160 0.01 +chr22 159 0.01 +chr22 158 0.01 +chr22 157 0.01 +chr22 156 0.01 +chr22 155 0.01 +chr22 154 0.01 +chr22 153 0.01 +chr22 152 0.01 +chr22 151 0.02 +chr22 150 0.02 +chr22 149 0.02 +chr22 148 0.02 +chr22 147 0.02 +chr22 146 0.02 +chr22 145 0.02 +chr22 144 0.02 +chr22 143 0.02 +chr22 142 0.02 +chr22 141 0.02 +chr22 140 0.02 +chr22 139 0.02 +chr22 138 0.02 +chr22 137 0.02 +chr22 136 0.02 +chr22 135 0.02 +chr22 134 0.02 +chr22 133 0.02 +chr22 132 0.02 +chr22 131 0.02 +chr22 130 0.02 +chr22 129 0.02 +chr22 128 0.02 +chr22 127 0.02 +chr22 126 0.02 +chr22 125 0.02 +chr22 124 0.02 +chr22 123 0.02 +chr22 122 0.02 +chr22 121 0.02 +chr22 120 0.02 +chr22 119 0.02 +chr22 118 0.02 +chr22 117 0.02 +chr22 116 0.02 +chr22 115 0.02 +chr22 114 0.02 +chr22 113 0.02 +chr22 112 0.02 +chr22 111 0.02 +chr22 110 0.02 +chr22 109 0.02 +chr22 108 0.02 +chr22 107 0.02 +chr22 106 0.02 +chr22 105 0.02 +chr22 104 0.02 +chr22 103 0.02 +chr22 102 0.02 +chr22 101 0.02 +chr22 100 0.02 +chr22 99 0.02 +chr22 98 0.02 +chr22 97 0.02 +chr22 96 0.02 +chr22 95 0.02 +chr22 94 0.02 +chr22 93 0.02 +chr22 92 0.02 +chr22 91 0.02 +chr22 90 0.02 +chr22 89 0.02 +chr22 88 0.02 +chr22 87 0.02 +chr22 86 0.02 +chr22 85 0.02 +chr22 84 0.02 +chr22 83 0.02 +chr22 82 0.02 +chr22 81 0.02 +chr22 80 0.04 +chr22 79 0.04 +chr22 78 0.04 +chr22 77 0.04 +chr22 76 0.04 +chr22 75 0.04 +chr22 74 0.04 +chr22 73 0.04 +chr22 72 0.04 +chr22 71 0.04 +chr22 70 0.04 +chr22 69 0.04 +chr22 68 0.04 +chr22 67 0.04 +chr22 66 0.04 +chr22 65 0.04 +chr22 64 0.04 +chr22 63 0.04 +chr22 62 0.04 +chr22 61 0.04 +chr22 60 0.04 +chr22 59 0.04 +chr22 58 0.04 +chr22 57 0.04 +chr22 56 0.04 +chr22 55 0.04 +chr22 54 0.04 +chr22 53 0.04 +chr22 52 0.04 +chr22 51 0.04 +chr22 50 0.04 +chr22 49 0.05 +chr22 48 0.05 +chr22 47 0.05 +chr22 46 0.05 +chr22 45 0.05 +chr22 44 0.05 +chr22 43 0.05 +chr22 42 0.05 +chr22 41 0.05 +chr22 40 0.05 +chr22 39 0.05 +chr22 38 0.05 +chr22 37 0.05 +chr22 36 0.05 +chr22 35 0.05 +chr22 34 0.05 +chr22 33 0.05 +chr22 32 0.05 +chr22 31 0.05 +chr22 30 0.05 +chr22 29 0.05 +chr22 28 0.05 +chr22 27 0.05 +chr22 26 0.05 +chr22 25 0.05 +chr22 24 0.05 +chr22 23 0.05 +chr22 22 0.05 +chr22 21 0.05 +chr22 20 0.05 +chr22 19 0.05 +chr22 18 0.05 +chr22 17 0.05 +chr22 16 0.05 +chr22 15 0.05 +chr22 14 0.05 +chr22 13 0.05 +chr22 12 0.05 +chr22 11 0.05 +chr22 10 0.05 +chr22 9 0.05 +chr22 8 0.05 +chr22 7 0.06 +chr22 6 0.06 +chr22 5 0.07 +chr22 4 0.07 +chr22 3 0.07 +chr22 2 0.07 +chr22 1 0.07 +chr22 0 1.00 +total 204 0.01 +total 203 0.01 +total 202 0.01 +total 201 0.01 +total 200 0.01 +total 199 0.01 +total 198 0.01 +total 197 0.01 +total 196 0.01 +total 195 0.01 +total 194 0.01 +total 193 0.01 +total 192 0.01 +total 191 0.01 +total 190 0.01 +total 189 0.01 +total 188 0.01 +total 187 0.01 +total 186 0.01 +total 185 0.01 +total 184 0.01 +total 183 0.01 +total 182 0.01 +total 181 0.01 +total 180 0.01 +total 179 0.01 +total 178 0.01 +total 177 0.01 +total 176 0.01 +total 175 0.01 +total 174 0.01 +total 173 0.01 +total 172 0.01 +total 171 0.01 +total 170 0.01 +total 169 0.01 +total 168 0.01 +total 167 0.01 +total 166 0.01 +total 165 0.01 +total 164 0.01 +total 163 0.01 +total 162 0.01 +total 161 0.01 +total 160 0.01 +total 159 0.01 +total 158 0.01 +total 157 0.01 +total 156 0.01 +total 155 0.01 +total 154 0.01 +total 153 0.01 +total 152 0.01 +total 151 0.02 +total 150 0.02 +total 149 0.02 +total 148 0.02 +total 147 0.02 +total 146 0.02 +total 145 0.02 +total 144 0.02 +total 143 0.02 +total 142 0.02 +total 141 0.02 +total 140 0.02 +total 139 0.02 +total 138 0.02 +total 137 0.02 +total 136 0.02 +total 135 0.02 +total 134 0.02 +total 133 0.02 +total 132 0.02 +total 131 0.02 +total 130 0.02 +total 129 0.02 +total 128 0.02 +total 127 0.02 +total 126 0.02 +total 125 0.02 +total 124 0.02 +total 123 0.02 +total 122 0.02 +total 121 0.02 +total 120 0.02 +total 119 0.02 +total 118 0.02 +total 117 0.02 +total 116 0.02 +total 115 0.02 +total 114 0.02 +total 113 0.02 +total 112 0.02 +total 111 0.02 +total 110 0.02 +total 109 0.02 +total 108 0.02 +total 107 0.02 +total 106 0.02 +total 105 0.02 +total 104 0.02 +total 103 0.02 +total 102 0.02 +total 101 0.02 +total 100 0.02 +total 99 0.02 +total 98 0.02 +total 97 0.02 +total 96 0.02 +total 95 0.02 +total 94 0.02 +total 93 0.02 +total 92 0.02 +total 91 0.02 +total 90 0.02 +total 89 0.02 +total 88 0.02 +total 87 0.02 +total 86 0.02 +total 85 0.02 +total 84 0.02 +total 83 0.02 +total 82 0.02 +total 81 0.02 +total 80 0.04 +total 79 0.04 +total 78 0.04 +total 77 0.04 +total 76 0.04 +total 75 0.04 +total 74 0.04 +total 73 0.04 +total 72 0.04 +total 71 0.04 +total 70 0.04 +total 69 0.04 +total 68 0.04 +total 67 0.04 +total 66 0.04 +total 65 0.04 +total 64 0.04 +total 63 0.04 +total 62 0.04 +total 61 0.04 +total 60 0.04 +total 59 0.04 +total 58 0.04 +total 57 0.04 +total 56 0.04 +total 55 0.04 +total 54 0.04 +total 53 0.04 +total 52 0.04 +total 51 0.04 +total 50 0.04 +total 49 0.05 +total 48 0.05 +total 47 0.05 +total 46 0.05 +total 45 0.05 +total 44 0.05 +total 43 0.05 +total 42 0.05 +total 41 0.05 +total 40 0.05 +total 39 0.05 +total 38 0.05 +total 37 0.05 +total 36 0.05 +total 35 0.05 +total 34 0.05 +total 33 0.05 +total 32 0.05 +total 31 0.05 +total 30 0.05 +total 29 0.05 +total 28 0.05 +total 27 0.05 +total 26 0.05 +total 25 0.05 +total 24 0.05 +total 23 0.05 +total 22 0.05 +total 21 0.05 +total 20 0.05 +total 19 0.05 +total 18 0.05 +total 17 0.05 +total 16 0.05 +total 15 0.05 +total 14 0.05 +total 13 0.05 +total 12 0.05 +total 11 0.05 +total 10 0.05 +total 9 0.05 +total 8 0.05 +total 7 0.06 +total 6 0.06 +total 5 0.07 +total 4 0.07 +total 3 0.07 +total 2 0.07 +total 1 0.07 +total 0 1.00 diff --git a/tests/expected/dna/test.mosdepth.summary.txt b/tests/expected/dna/test.mosdepth.summary.txt new file mode 100644 index 00000000..ec15caf6 --- /dev/null +++ b/tests/expected/dna/test.mosdepth.summary.txt @@ -0,0 +1,5 @@ +chrom length bases mean min max +chr22 40001 247878 6.20 0 867 +chr22_region 40001 247878 6.20 0 867 +total 40001 247878 6.20 0 867 +total_region 40001 247878 6.20 0 867 diff --git a/tests/expected/dna/test.per-base.bed.gz b/tests/expected/dna/test.per-base.bed.gz new file mode 100644 index 00000000..2bcaa495 Binary files /dev/null and b/tests/expected/dna/test.per-base.bed.gz differ diff --git a/tests/expected/dna/test.per-base.bed.gz.csi b/tests/expected/dna/test.per-base.bed.gz.csi new file mode 100644 index 00000000..360bf772 Binary files /dev/null and b/tests/expected/dna/test.per-base.bed.gz.csi differ diff --git a/tests/expected/dna/test.regions.bed.gz b/tests/expected/dna/test.regions.bed.gz new file mode 100644 index 00000000..06a1c5e1 Binary files /dev/null and b/tests/expected/dna/test.regions.bed.gz differ diff --git a/tests/expected/dna/test.regions.bed.gz.csi b/tests/expected/dna/test.regions.bed.gz.csi new file mode 100644 index 00000000..7fe77157 Binary files /dev/null and b/tests/expected/dna/test.regions.bed.gz.csi differ diff --git a/tests/expected/dna/test.stats.txt b/tests/expected/dna/test.stats.txt new file mode 100644 index 00000000..9779c96d --- /dev/null +++ b/tests/expected/dna/test.stats.txt @@ -0,0 +1,1916 @@ +# This file was produced by samtools stats (1.24+htslib-1.24) and can be plotted using plot-bamstats +# This file contains statistics for all reads. +# The command line was: stats /Users/benjamin/RustQC-dna/tests/data/dna/test.dna.bam +# CHK, Checksum [2]Read Names [3]Sequences [4]Qualities +# CHK, CRC32 of reads which passed filtering followed by addition (32bit overflow) +CHK 82cbdacd 541c12e0 25a61aa9 +# Summary Numbers. Use `grep ^SN | cut -f 2-` to extract this part. +SN raw total sequences: 5642 # excluding supplementary and secondary reads +SN filtered sequences: 0 +SN sequences: 5642 +SN is sorted: 1 # sorted by coordinate +SN 1st fragments: 2821 +SN last fragments: 2821 +SN reads mapped: 5640 +SN reads mapped and paired: 5640 # paired-end technology bit set + both mates mapped +SN reads unmapped: 2 +SN reads properly paired: 5638 # proper-pair bit set +SN reads paired: 5642 # paired-end technology bit set +SN reads duplicated: 1656 # PCR or optical duplicate bit set +SN reads MQ0: 0 # mapped and MQ=0 +SN reads QC failed: 0 +SN non-primary alignments: 2 +SN supplementary alignments: 0 +SN total length: 672131 # ignores clipping +SN total first fragment length: 335944 # ignores clipping +SN total last fragment length: 336187 # ignores clipping +SN bases mapped: 671854 # ignores clipping +SN bases mapped (cigar): 670991 # more accurate +SN bases trimmed: 0 +SN bases duplicated: 201314 +SN mismatches: 1352 # from NM fields +SN error rate: 2.014930e-03 # mismatches / bases mapped (cigar) +SN average length: 119 +SN average first fragment length: 119 +SN average last fragment length: 119 +SN maximum length: 143 +SN maximum first fragment length: 143 +SN maximum last fragment length: 143 +SN average quality: 40.9 +SN insert size average: 124.8 +SN insert size standard deviation: 31.2 +SN inward oriented pairs: 2814 +SN outward oriented pairs: 6 +SN pairs with other orientation: 0 +SN pairs on different chromosomes: 0 +SN percentage of properly paired reads (%): 99.9 +# First Fragment Qualities. Use `grep ^FFQ | cut -f 2-` to extract this part. +# Columns correspond to qualities and rows to cycles. First column is the cycle number. +FFQ 1 0 0 2 0 0 0 0 0 0 0 0 0 0 17 0 0 0 0 0 0 15 0 0 0 0 0 0 0 0 0 0 34 0 0 881 0 0 0 0 0 1 0 0 1 54 1816 0 +FFQ 2 0 0 1 0 0 0 0 0 0 0 0 0 0 25 0 0 0 0 0 0 15 0 0 0 0 0 0 0 0 0 0 24 0 0 882 0 0 0 3 0 0 0 1 1 53 1816 0 +FFQ 3 0 0 0 0 1 0 0 0 0 0 0 0 0 24 0 0 0 0 0 0 15 0 0 0 0 0 0 0 0 0 0 35 0 0 872 0 0 0 2 0 0 0 0 0 62 1810 0 +FFQ 4 0 0 1 0 0 0 0 0 0 0 0 0 0 13 0 0 0 0 0 0 15 0 0 0 0 0 0 0 0 0 0 36 0 0 883 1 0 0 4 0 0 0 0 0 50 1818 0 +FFQ 5 0 0 0 0 0 0 0 0 0 0 0 0 0 11 0 0 0 0 0 0 18 0 0 0 0 0 0 0 0 0 0 34 0 0 885 2 0 0 0 0 0 0 0 0 42 1829 0 +FFQ 6 0 0 1 0 0 0 0 0 0 0 0 0 0 25 0 0 0 0 0 0 14 0 0 0 0 0 0 0 0 0 0 32 0 0 877 0 0 0 1 0 0 0 0 0 66 1805 0 +FFQ 7 0 0 0 0 0 0 0 0 0 0 0 0 0 25 0 0 0 0 0 0 14 0 0 0 0 0 1 0 0 0 0 30 0 0 876 1 0 0 1 0 0 0 0 0 46 1827 0 +FFQ 8 0 0 2 0 0 0 0 0 0 0 0 0 0 23 0 0 0 0 0 0 16 0 0 0 0 0 1 0 0 0 0 33 0 0 873 0 0 0 2 0 0 0 0 0 61 1810 0 +FFQ 9 0 0 1 0 0 0 0 0 0 0 0 0 0 24 0 0 0 0 1 0 8 0 0 0 0 0 1 0 0 0 0 37 0 0 876 0 0 0 4 0 0 0 0 1 53 1815 0 +FFQ 10 0 0 1 0 0 0 0 0 0 0 0 0 0 20 0 0 0 0 0 0 9 0 0 0 0 0 0 0 0 0 0 23 0 0 895 1 0 0 3 0 2 0 0 0 58 1809 0 +FFQ 11 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 22 0 0 0 0 0 0 0 0 0 0 23 0 0 895 0 0 0 0 0 0 0 0 1 63 1809 0 +FFQ 12 0 0 4 0 0 0 0 0 0 0 0 0 0 13 0 0 0 0 0 0 17 0 0 0 0 0 0 0 0 0 0 40 0 0 879 0 0 0 6 0 0 0 0 0 53 1809 0 +FFQ 13 0 0 2 0 0 0 0 0 0 0 0 0 0 21 0 0 0 1 0 0 13 0 0 0 0 0 3 0 0 0 0 38 0 0 872 0 0 0 3 0 0 0 0 1 49 1818 0 +FFQ 14 0 0 1 0 0 0 0 0 0 0 0 0 0 18 0 0 0 0 0 0 15 0 0 0 0 0 3 0 0 0 0 41 2 0 870 0 0 0 2 0 1 0 0 1 64 1803 0 +FFQ 15 0 0 4 0 1 0 0 0 0 0 0 0 0 21 0 1 0 0 0 0 14 0 0 0 0 0 0 0 0 0 0 34 0 0 878 0 0 0 1 0 0 0 0 0 63 1804 0 +FFQ 16 0 0 4 0 0 0 0 0 0 0 0 0 0 20 0 0 0 0 0 0 12 0 0 0 0 0 1 0 0 0 0 30 1 0 886 0 0 0 0 0 0 0 0 1 44 1822 0 +FFQ 17 0 0 3 0 0 0 0 0 0 0 0 0 0 18 0 0 0 0 0 0 11 0 0 0 0 0 1 0 0 0 0 35 0 0 883 0 0 0 2 0 1 0 0 0 61 1806 0 +FFQ 18 0 0 3 0 0 0 0 0 0 0 0 0 0 22 0 0 0 0 0 0 11 0 0 0 0 0 1 0 0 0 0 39 0 0 876 0 0 0 0 0 0 0 0 1 55 1813 0 +FFQ 19 0 0 2 0 1 0 0 0 0 0 0 0 0 27 0 0 0 0 0 0 14 0 0 0 0 0 0 0 0 0 0 35 0 0 873 0 0 0 2 0 0 0 0 0 69 1798 0 +FFQ 20 0 0 2 0 1 0 0 0 1 0 0 0 0 26 0 0 0 0 0 0 9 0 0 0 0 0 3 0 0 0 0 36 3 0 873 0 0 0 1 0 3 0 1 2 70 1790 0 +FFQ 21 0 0 0 0 0 0 0 0 0 0 0 0 0 28 0 0 0 0 0 0 5 0 0 0 0 0 2 0 0 0 0 46 0 0 866 0 0 0 1 0 0 0 0 0 64 1809 0 +FFQ 22 0 0 3 0 0 0 0 0 0 0 0 0 0 26 0 0 0 0 1 0 13 0 0 0 0 0 5 0 0 0 0 31 0 0 873 0 0 0 2 0 0 0 0 0 55 1812 0 +FFQ 23 0 0 3 0 0 0 0 0 0 0 0 0 0 30 0 0 0 0 0 0 10 0 0 0 0 0 4 0 0 0 0 41 0 0 863 0 0 0 4 0 2 0 1 1 52 1810 0 +FFQ 24 0 0 2 0 0 0 0 0 0 0 0 0 0 34 0 0 0 0 1 0 12 0 0 0 0 0 4 0 0 0 0 29 0 0 870 1 0 0 6 0 0 0 0 1 73 1788 0 +FFQ 25 0 0 5 0 0 0 0 0 0 0 0 0 0 28 0 0 0 0 0 0 11 0 0 0 0 0 0 0 0 0 0 34 0 0 877 0 0 0 1 0 0 0 2 0 53 1810 0 +FFQ 26 0 0 2 0 2 0 0 0 0 0 0 0 0 32 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0 0 0 37 0 0 873 0 0 0 3 0 0 0 0 0 57 1809 0 +FFQ 27 0 0 3 0 0 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 7 0 0 0 0 0 4 0 0 0 0 29 0 0 870 0 0 0 6 0 0 0 0 0 40 1822 0 +FFQ 28 0 0 5 0 1 0 0 0 0 0 0 0 0 36 0 0 0 0 0 0 9 0 0 0 0 0 1 0 0 0 0 24 2 0 880 0 0 0 2 0 0 0 0 0 50 1811 0 +FFQ 29 0 0 1 0 1 0 0 0 0 0 0 0 0 32 0 0 0 0 0 0 3 0 0 0 0 1 4 0 0 0 0 52 0 0 857 3 0 0 7 0 0 0 0 1 68 1791 0 +FFQ 30 0 0 1 0 0 0 0 0 0 0 0 0 0 35 0 0 0 0 0 0 4 0 0 0 0 0 3 0 0 0 0 32 0 0 874 0 0 0 6 0 0 0 0 4 58 1804 0 +FFQ 31 0 0 5 0 2 0 0 0 0 0 0 0 0 24 0 0 0 0 0 0 11 0 0 0 0 0 1 0 0 0 0 38 0 0 875 0 0 0 3 0 0 0 0 0 54 1807 0 +FFQ 32 0 0 3 0 1 0 0 0 0 0 0 0 0 35 0 0 0 0 0 0 8 0 0 0 0 0 1 0 0 0 0 32 1 0 873 0 0 0 2 0 0 0 1 0 59 1804 0 +FFQ 33 0 0 7 0 2 0 0 0 0 0 0 0 0 35 0 0 0 0 1 0 6 0 0 0 0 0 5 0 0 0 0 26 0 0 877 0 0 0 3 0 0 0 0 0 53 1805 0 +FFQ 34 0 0 5 0 0 0 0 0 0 0 0 0 0 32 0 0 0 0 0 0 6 0 0 0 0 0 5 0 0 0 0 29 1 0 877 0 0 0 2 0 0 0 0 0 62 1801 0 +FFQ 35 0 0 4 0 0 0 0 0 0 0 0 0 0 31 0 0 0 0 0 0 8 0 0 0 0 0 5 0 0 0 0 29 0 0 877 0 0 0 1 0 0 0 0 0 55 1810 0 +FFQ 36 0 0 2 0 0 0 0 0 0 0 0 0 0 32 0 0 0 0 0 0 7 0 0 0 0 0 7 0 0 0 0 31 0 0 872 0 0 0 2 0 0 0 0 1 64 1802 0 +FFQ 37 0 0 3 0 0 0 0 0 0 0 0 0 0 36 0 0 0 0 0 0 8 0 0 0 0 0 4 0 0 0 0 34 0 0 867 1 0 0 0 0 0 0 2 0 65 1800 0 +FFQ 38 0 0 2 0 0 0 0 0 0 0 0 0 0 36 0 0 0 0 0 0 6 0 0 0 0 0 7 0 0 0 0 33 1 0 867 1 0 0 5 0 1 0 0 0 69 1792 0 +FFQ 39 0 0 3 0 2 0 0 0 0 0 0 0 0 32 0 0 0 0 0 0 2 0 0 0 0 0 5 0 0 0 0 40 1 0 870 0 0 0 2 0 0 0 0 0 63 1800 0 +FFQ 40 1 0 2 0 0 0 0 0 0 0 0 0 0 33 0 0 0 0 1 0 9 0 0 0 0 0 7 0 0 0 0 35 2 0 865 0 0 0 3 0 0 0 0 0 62 1800 0 +FFQ 41 0 0 3 0 0 0 0 0 0 0 0 0 0 24 0 0 0 0 0 0 8 0 0 0 0 0 9 0 0 0 0 33 0 0 875 0 0 0 2 0 0 1 1 1 58 1805 0 +FFQ 42 0 0 3 0 0 0 0 0 0 0 0 0 0 32 0 0 0 0 0 0 14 0 0 0 0 0 9 0 0 0 0 26 0 0 870 0 0 0 4 0 0 0 0 0 51 1810 0 +FFQ 43 0 0 2 0 0 0 0 0 0 0 0 0 0 42 0 0 0 0 1 0 6 0 0 0 0 0 4 0 0 0 0 30 0 0 864 0 0 0 2 0 0 0 2 0 60 1806 0 +FFQ 44 1 0 5 0 0 0 0 0 0 0 0 0 0 39 0 0 0 0 0 0 7 0 0 0 0 0 5 0 0 0 0 33 0 0 863 0 0 0 5 0 0 0 0 1 72 1788 0 +FFQ 45 1 0 3 0 0 0 0 0 0 0 0 0 0 30 0 0 0 0 0 0 7 0 0 0 0 0 5 0 0 0 0 39 0 0 867 1 0 0 3 0 0 0 1 1 70 1791 0 +FFQ 46 1 0 1 0 0 0 0 0 0 0 0 0 0 37 0 0 0 0 0 0 7 0 0 0 0 0 5 0 0 0 0 32 1 0 863 0 0 0 2 0 0 0 0 1 71 1797 0 +FFQ 47 2 0 4 0 0 0 0 0 0 0 0 0 0 39 0 0 0 0 0 0 4 0 0 0 0 0 1 0 0 0 0 34 0 0 866 0 0 0 7 0 1 0 0 2 76 1782 0 +FFQ 48 1 0 4 0 1 0 0 0 0 0 1 0 0 37 0 0 0 1 0 0 6 0 0 0 0 0 11 0 0 0 0 40 0 0 851 0 0 0 4 0 0 0 0 2 99 1760 0 +FFQ 49 1 0 1 0 1 0 0 0 0 0 0 0 0 43 0 0 0 0 0 0 6 0 0 0 0 0 7 0 0 0 0 45 0 0 844 0 0 0 2 0 0 0 1 2 94 1771 0 +FFQ 50 0 0 4 0 0 0 0 0 0 0 0 0 0 30 0 0 0 0 0 0 9 0 0 0 0 0 11 0 0 0 0 59 0 0 837 0 0 0 2 0 0 0 0 1 91 1771 0 +FFQ 51 1 0 3 0 1 0 0 0 0 0 0 0 0 32 0 0 0 0 0 0 12 0 0 0 0 0 8 0 0 0 0 59 0 0 835 0 0 0 4 0 0 0 1 2 99 1758 0 +FFQ 52 2 0 0 0 1 0 0 0 0 0 0 0 0 35 0 0 0 0 0 0 8 0 0 0 0 1 14 0 0 0 0 59 0 0 829 0 0 0 0 0 2 0 1 3 117 1742 0 +FFQ 53 0 0 1 0 0 0 0 0 0 0 0 0 0 33 0 0 0 0 0 0 7 0 0 0 0 0 10 0 0 0 0 57 0 0 838 0 0 0 1 0 1 0 0 1 100 1763 0 +FFQ 54 1 0 3 0 2 0 0 0 0 0 0 0 0 33 0 0 0 0 0 0 5 0 0 0 0 0 9 0 0 0 0 49 0 0 847 0 0 0 1 0 0 0 1 0 95 1766 0 +FFQ 55 0 0 6 0 0 0 0 0 0 0 0 0 0 34 0 0 0 0 0 0 13 0 0 0 0 0 16 0 0 0 0 51 1 0 830 1 0 0 4 0 1 0 1 1 107 1745 0 +FFQ 56 1 0 3 0 0 0 0 0 0 0 0 0 0 38 0 0 0 0 0 0 12 0 0 0 0 0 12 0 0 0 0 48 0 0 834 0 0 0 4 0 2 0 0 0 94 1763 0 +FFQ 57 0 0 4 0 0 0 0 0 0 0 0 0 0 37 0 0 0 0 1 0 9 0 0 0 0 0 9 0 0 0 0 57 0 0 833 1 0 0 2 0 3 0 1 0 108 1746 0 +FFQ 58 2 0 5 0 0 0 0 0 0 0 0 0 0 38 0 0 0 0 0 0 5 0 0 0 0 0 14 0 0 0 0 44 0 0 842 0 0 0 2 0 0 0 2 2 76 1779 0 +FFQ 59 1 0 3 0 0 1 0 0 0 0 0 0 0 42 0 0 0 0 0 0 9 0 0 0 0 0 8 0 0 0 0 54 1 0 831 0 0 0 6 0 0 0 2 1 105 1746 0 +FFQ 60 2 0 2 0 1 0 0 0 0 0 0 0 0 35 0 0 0 1 0 0 8 0 0 0 0 0 12 0 0 0 0 52 0 0 832 1 0 0 2 0 2 0 1 2 109 1746 0 +FFQ 61 1 0 3 0 0 0 0 0 0 0 0 0 0 36 0 0 0 0 0 0 11 0 0 0 0 0 18 0 0 0 0 69 1 0 811 0 1 0 3 0 0 0 0 6 99 1748 0 +FFQ 62 1 0 2 0 2 0 0 0 0 0 0 0 0 47 0 0 0 0 0 0 5 0 0 1 0 1 16 0 0 0 0 60 0 0 812 1 0 0 2 0 0 0 2 3 89 1759 0 +FFQ 63 1 0 6 0 0 0 0 0 0 0 0 0 0 34 0 0 0 0 0 0 8 0 0 0 0 0 19 0 0 0 0 60 1 0 821 0 0 0 3 0 0 0 2 3 93 1751 0 +FFQ 64 2 0 2 0 0 0 0 0 0 0 0 1 0 34 0 0 0 0 1 0 7 0 0 0 0 0 20 0 0 0 0 56 1 0 820 0 0 0 1 0 1 0 0 2 96 1753 0 +FFQ 65 1 0 5 0 0 0 0 0 0 0 0 0 0 42 0 0 0 1 0 0 8 0 0 0 0 0 14 0 0 0 0 51 1 0 821 0 0 0 4 0 0 0 0 4 93 1752 0 +FFQ 66 1 0 1 0 0 0 0 0 1 0 0 0 0 38 0 0 0 0 0 0 6 0 0 0 0 0 12 0 0 0 0 47 0 0 829 0 0 0 0 0 0 1 1 1 118 1736 0 +FFQ 67 0 0 3 0 0 0 0 0 0 0 0 0 0 28 0 0 0 0 0 0 5 0 0 0 0 0 16 0 0 0 0 51 0 0 831 0 0 0 1 0 0 0 0 0 109 1746 0 +FFQ 68 0 0 1 0 3 1 0 0 0 0 0 0 0 36 0 0 0 1 0 0 4 0 0 0 0 0 17 0 0 0 0 59 0 0 811 0 0 0 4 0 1 0 0 2 110 1734 0 +FFQ 69 0 0 2 0 1 0 0 0 0 0 0 0 0 38 0 0 0 0 1 0 7 0 0 0 0 0 16 0 0 0 0 62 0 0 805 0 0 0 1 0 4 0 0 3 111 1730 0 +FFQ 70 0 0 1 0 0 0 0 0 2 0 0 0 0 42 0 0 0 0 0 0 5 0 0 0 0 0 16 0 0 0 0 56 0 0 805 0 0 0 1 0 0 0 4 0 105 1739 0 +FFQ 71 0 0 1 0 0 0 0 0 2 0 0 0 0 38 0 0 0 0 1 0 8 0 0 0 0 0 18 0 0 0 0 53 0 0 805 0 0 0 5 0 0 0 1 6 99 1729 0 +FFQ 72 0 0 1 0 1 0 0 0 2 0 0 0 0 31 0 0 0 0 0 0 8 0 0 0 0 0 23 0 0 0 0 79 1 0 778 1 0 0 4 0 0 0 1 2 102 1721 0 +FFQ 73 0 0 1 0 1 0 0 0 1 0 0 0 0 43 0 0 0 0 0 0 5 0 0 0 0 0 10 0 0 0 0 58 0 0 797 0 0 0 2 0 1 0 0 5 111 1713 0 +FFQ 74 0 0 0 0 1 0 0 0 0 0 0 0 0 33 0 0 0 0 0 0 10 0 0 0 0 0 19 0 0 0 0 53 1 0 793 0 0 0 4 0 0 0 0 1 108 1717 0 +FFQ 75 0 0 3 0 0 0 0 0 0 0 0 0 0 41 0 0 0 1 0 0 8 0 0 0 0 0 22 0 0 0 0 63 1 0 776 0 0 0 1 0 1 0 0 1 115 1703 0 +FFQ 76 0 0 3 0 0 0 0 0 0 0 0 0 0 39 0 0 0 0 0 0 8 0 0 0 0 0 13 0 0 0 0 51 1 0 793 1 0 0 0 0 0 0 1 2 114 1698 0 +FFQ 77 0 0 0 0 0 0 0 0 0 0 0 0 0 39 0 0 0 0 0 0 2 0 0 0 0 0 16 0 0 0 0 55 1 0 786 1 0 0 0 0 0 0 0 1 120 1692 0 +FFQ 78 0 0 4 0 0 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 2 0 0 0 0 0 18 0 0 0 0 49 0 0 777 0 0 0 2 0 1 0 1 0 112 1688 0 +FFQ 79 0 0 2 0 0 0 0 0 0 0 0 0 0 48 0 0 0 1 0 0 7 0 0 0 0 0 12 0 0 0 0 57 0 0 753 0 0 0 0 0 0 0 1 1 117 1680 0 +FFQ 80 0 0 3 0 0 0 0 0 1 0 0 0 0 43 0 0 0 0 0 0 10 0 0 0 0 0 12 0 0 0 0 69 0 0 738 0 0 0 2 0 0 0 1 0 118 1669 0 +FFQ 81 0 0 2 0 0 0 0 0 1 0 0 0 0 37 0 0 0 0 1 0 6 0 0 0 0 0 19 0 0 0 0 55 0 0 745 0 0 0 0 0 1 0 3 3 127 1649 0 +FFQ 82 0 0 0 0 0 0 0 0 0 0 0 0 0 41 0 0 0 0 0 0 4 0 0 0 0 0 13 0 0 0 0 76 3 0 715 1 0 0 1 0 0 0 0 0 109 1662 0 +FFQ 83 0 0 2 0 0 0 0 0 0 0 0 0 0 46 0 0 0 0 0 0 5 0 0 0 0 0 15 0 0 0 0 57 0 0 717 0 0 0 0 0 0 0 0 1 92 1672 0 +FFQ 84 0 0 1 0 0 0 0 0 1 0 0 0 0 31 0 0 0 0 0 0 6 0 0 0 0 0 17 0 0 0 0 60 2 0 718 0 0 0 0 0 0 0 0 0 122 1630 0 +FFQ 85 0 0 4 0 0 0 0 0 0 0 0 0 0 27 0 0 0 0 0 0 9 0 0 0 0 0 16 0 0 0 0 58 2 0 710 0 0 0 0 0 0 0 0 4 130 1603 0 +FFQ 86 0 0 1 0 0 0 0 0 0 0 0 0 0 43 0 0 0 0 0 0 10 0 0 0 0 0 25 0 0 0 0 46 1 0 685 0 0 0 1 0 0 0 1 1 109 1625 0 +FFQ 87 0 0 1 0 1 0 0 0 0 0 0 0 0 35 0 0 0 0 0 0 3 0 0 0 0 0 19 0 0 0 0 53 2 0 692 0 0 0 0 0 0 0 0 2 117 1599 0 +FFQ 88 0 0 0 0 1 0 0 0 1 0 0 0 0 37 0 0 0 0 0 0 2 0 0 0 0 0 23 0 0 0 0 63 1 0 664 0 0 0 0 0 0 0 2 3 118 1579 0 +FFQ 89 0 0 1 0 0 0 0 0 0 0 0 0 0 27 0 0 0 0 0 0 7 0 0 0 0 0 17 0 0 0 0 59 1 0 666 0 0 0 2 0 0 0 0 3 131 1551 0 +FFQ 90 0 0 0 0 1 0 0 0 0 0 0 0 0 29 0 0 0 0 0 0 2 0 0 0 0 0 15 0 0 0 0 47 0 0 678 1 0 0 0 0 2 1 1 4 115 1548 0 +FFQ 91 0 0 0 0 0 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 2 0 0 0 0 0 17 0 0 0 0 66 1 0 635 1 0 0 1 0 0 0 2 4 119 1540 0 +FFQ 92 0 0 1 0 0 0 0 0 0 0 0 0 0 34 0 0 0 1 0 0 7 0 0 0 0 0 18 0 0 0 0 47 0 0 643 0 0 0 0 0 1 0 1 10 120 1522 0 +FFQ 93 0 0 0 0 0 0 0 0 0 0 0 0 0 35 0 0 0 0 0 0 5 1 0 0 0 0 26 0 0 0 0 63 1 0 610 1 0 0 2 0 0 0 1 8 103 1519 0 +FFQ 94 0 0 0 0 0 0 0 0 0 0 0 0 0 34 0 0 0 0 0 0 5 0 0 0 0 0 25 0 0 0 0 68 0 0 599 0 0 0 1 0 0 0 2 3 121 1494 0 +FFQ 95 0 0 1 0 0 0 0 0 0 0 0 0 0 29 0 0 0 1 0 0 3 0 0 0 0 0 17 0 0 0 0 78 0 0 593 0 0 0 0 0 0 0 1 1 124 1484 0 +FFQ 96 0 0 0 0 0 0 0 0 0 0 0 0 0 36 0 0 0 0 0 0 2 0 0 0 0 0 14 0 0 0 0 74 1 0 575 0 0 0 0 0 0 0 0 3 126 1459 0 +FFQ 97 0 0 0 0 0 0 0 0 1 0 0 0 0 30 0 0 0 1 0 0 6 0 0 0 0 0 21 0 0 0 0 76 0 0 545 0 0 0 0 0 0 0 1 5 137 1413 0 +FFQ 98 0 0 0 0 0 0 0 0 0 0 0 0 0 30 0 0 0 0 1 0 6 0 0 0 0 0 27 0 0 0 0 71 1 0 532 0 0 0 0 0 0 0 0 5 132 1397 0 +FFQ 99 0 0 0 0 0 0 0 0 0 0 0 0 0 25 0 0 0 0 0 0 5 0 0 0 0 0 32 0 0 0 0 85 1 0 506 1 0 0 1 0 0 0 0 4 133 1381 0 +FFQ 100 0 0 0 0 0 0 0 0 0 0 0 0 0 31 0 0 0 0 0 0 4 0 0 0 0 0 20 0 0 0 0 66 0 0 521 0 0 0 0 0 0 0 3 3 135 1367 0 +FFQ 101 0 0 0 0 0 0 0 0 0 0 0 0 0 26 0 0 0 0 0 0 5 0 0 0 0 0 21 0 0 0 0 83 0 0 489 0 0 0 0 0 0 0 0 2 135 1345 0 +FFQ 102 0 0 0 0 0 0 0 0 0 0 0 0 0 28 0 0 0 0 0 0 3 0 0 0 0 0 25 0 0 0 0 78 0 0 483 0 0 0 0 0 0 0 3 1 136 1325 0 +FFQ 103 0 0 0 0 0 0 0 0 0 0 0 0 0 32 0 0 0 0 0 0 3 0 0 0 0 0 34 0 0 0 0 80 1 0 460 0 0 0 0 0 0 0 1 4 138 1302 0 +FFQ 104 0 0 0 0 0 0 0 0 0 0 0 0 0 20 0 0 0 0 0 0 5 0 0 0 0 0 28 0 0 0 0 75 0 0 473 0 0 0 0 0 0 0 2 4 141 1285 0 +FFQ 105 0 0 0 0 0 0 0 0 0 0 0 0 0 35 0 0 0 0 0 0 8 0 0 0 0 0 22 0 0 0 0 80 0 0 441 0 0 0 0 0 0 0 3 6 125 1280 0 +FFQ 106 0 0 0 0 0 0 0 0 0 0 0 0 0 18 0 0 0 0 0 0 2 0 0 0 0 0 27 0 0 0 0 89 4 0 440 0 0 0 1 0 0 0 2 3 132 1256 0 +FFQ 107 0 0 0 0 0 0 0 0 0 0 0 0 0 30 0 0 0 0 0 0 7 0 0 0 0 0 24 0 0 0 0 81 0 0 423 0 0 0 1 0 0 0 4 4 147 1225 0 +FFQ 108 0 0 0 0 0 0 0 0 0 0 0 0 0 23 0 0 0 0 0 0 6 0 0 0 0 0 28 0 0 0 0 84 1 0 412 0 0 0 1 0 0 0 0 4 158 1193 0 +FFQ 109 0 0 0 0 0 0 0 0 0 0 0 0 0 33 0 0 0 1 0 0 6 0 0 0 0 0 22 0 0 0 0 99 1 0 381 0 0 0 1 0 0 0 3 7 134 1196 0 +FFQ 110 0 0 0 0 0 0 0 0 0 0 0 0 0 31 0 0 0 1 0 0 4 0 0 0 0 0 30 0 0 0 0 94 2 0 371 0 0 0 0 0 0 1 3 3 149 1171 0 +FFQ 111 0 0 0 0 0 0 0 0 0 0 0 0 0 33 0 0 0 0 0 0 3 0 0 0 0 0 44 0 0 0 0 86 2 0 353 0 0 0 1 0 0 0 2 2 132 1168 0 +FFQ 112 0 0 0 0 1 0 0 0 0 0 0 0 0 27 0 0 0 0 0 0 5 0 0 0 0 0 30 0 0 0 0 107 1 0 343 0 0 0 0 0 0 0 4 3 167 1109 0 +FFQ 113 0 0 0 0 0 0 0 0 0 0 0 0 0 27 0 0 0 0 0 0 4 0 0 0 0 0 34 0 1 0 0 103 1 0 332 0 0 0 0 0 0 0 3 3 164 1103 0 +FFQ 114 0 0 0 0 0 0 0 0 0 0 0 0 0 30 0 0 0 0 0 0 6 0 0 0 0 0 33 0 0 0 0 105 1 0 317 1 0 0 0 0 0 0 5 6 163 1072 0 +FFQ 115 0 0 0 0 1 0 0 0 0 0 0 0 0 26 0 0 0 0 0 0 5 0 0 0 0 0 41 0 0 0 0 117 0 0 293 0 0 0 1 0 0 0 3 5 151 1066 0 +FFQ 116 0 0 0 0 0 0 0 0 0 0 0 0 0 17 0 0 0 0 0 0 8 0 0 0 0 0 39 0 0 0 0 113 2 0 291 0 0 0 1 0 0 0 1 0 162 1026 0 +FFQ 117 0 0 0 0 0 0 0 0 0 0 0 0 0 25 0 0 0 0 0 0 11 0 0 0 0 0 25 0 0 0 0 116 0 0 279 0 0 0 0 0 0 0 0 4 156 1008 0 +FFQ 118 0 0 0 0 0 0 0 0 0 0 0 0 0 29 0 0 0 0 0 0 8 0 0 0 0 0 29 0 0 0 0 96 0 0 285 0 0 0 2 0 0 0 2 3 151 986 0 +FFQ 119 0 0 1 0 0 0 0 0 0 0 0 0 0 18 0 0 0 0 0 0 8 0 0 0 0 0 36 0 0 0 0 105 0 0 269 0 0 0 1 0 0 0 2 2 154 961 0 +FFQ 120 0 0 0 0 0 0 1 0 0 0 0 0 0 23 0 0 0 0 0 0 10 0 0 0 0 0 33 0 0 0 0 110 2 0 251 0 0 0 0 0 0 0 3 5 157 924 0 +FFQ 121 0 0 0 0 0 0 0 0 0 0 0 0 0 23 0 0 0 0 0 0 5 0 0 0 0 0 32 0 0 0 0 92 0 0 264 0 0 0 0 0 0 0 3 7 142 937 0 +FFQ 122 0 0 0 0 0 0 0 0 0 0 0 0 0 19 0 0 0 1 0 0 8 0 0 0 0 0 47 0 0 0 0 91 0 0 237 0 0 0 0 0 0 0 1 8 143 911 0 +FFQ 123 0 0 0 0 0 0 0 0 0 0 0 0 0 25 0 0 0 0 0 0 10 0 0 0 0 0 24 0 0 0 0 116 0 0 221 0 0 0 0 0 0 0 2 7 147 884 0 +FFQ 124 0 0 0 0 0 0 0 0 0 0 0 0 0 20 0 0 0 1 0 0 8 0 0 0 0 0 45 0 0 0 0 97 0 0 216 0 0 0 0 0 0 0 2 4 140 875 0 +FFQ 125 0 0 0 0 0 0 0 0 0 0 0 0 0 20 0 0 0 0 0 0 8 0 0 0 0 0 35 0 0 0 0 102 1 0 204 0 0 0 0 0 0 0 0 5 119 878 0 +FFQ 126 0 0 0 0 0 0 0 0 0 0 0 0 0 15 0 0 0 1 0 0 5 0 0 0 0 0 23 0 0 0 0 100 0 0 214 0 0 0 0 0 0 0 1 9 139 829 0 +FFQ 127 0 0 0 0 0 0 0 0 0 0 0 0 0 20 0 0 0 0 0 0 7 0 0 0 0 0 30 0 0 0 0 100 1 0 193 0 0 0 1 0 0 0 1 2 131 824 0 +FFQ 128 0 0 0 0 0 0 0 0 0 0 0 0 0 20 0 0 0 0 0 0 3 0 0 0 0 0 21 0 0 0 0 85 0 0 208 0 0 0 0 0 0 0 2 6 136 798 0 +FFQ 129 0 0 0 0 0 0 0 0 0 0 0 0 0 23 0 0 0 0 0 0 4 0 0 0 0 0 16 0 0 0 0 72 2 0 212 0 0 0 1 0 0 0 1 1 127 789 0 +FFQ 130 0 0 0 0 0 0 0 0 0 0 0 0 0 19 0 0 0 1 0 0 6 0 0 0 0 0 24 0 0 0 0 69 0 0 198 0 0 0 0 0 1 0 2 4 115 781 0 +FFQ 131 0 0 0 0 0 0 0 0 0 0 0 0 0 21 0 0 0 0 0 0 4 0 0 0 0 0 27 0 0 0 0 71 0 0 183 0 0 0 1 0 0 0 1 7 114 751 0 +FFQ 132 0 0 0 0 0 0 0 0 0 0 0 0 0 16 0 0 0 0 0 0 4 0 0 0 0 0 27 0 0 0 0 73 0 0 174 0 0 0 2 0 0 0 1 6 107 725 0 +FFQ 133 0 0 0 0 0 0 0 0 0 0 0 0 0 19 0 0 0 0 0 0 2 0 0 0 0 0 23 0 0 0 0 86 0 0 158 0 0 0 0 0 0 0 0 2 121 699 0 +FFQ 134 0 0 0 0 0 0 0 0 0 0 0 0 0 20 0 0 0 0 0 0 4 0 0 0 0 0 15 0 0 0 0 79 1 0 163 0 0 0 0 0 0 0 1 5 108 696 0 +FFQ 135 0 0 0 0 0 0 0 0 0 0 0 0 0 15 0 0 0 0 0 0 4 0 0 0 0 0 25 0 0 0 0 75 0 0 149 0 0 0 0 0 0 0 2 8 100 688 0 +FFQ 136 0 0 0 0 1 0 0 0 0 0 0 0 0 16 0 0 0 0 0 0 2 0 0 0 0 0 21 0 0 0 0 81 1 0 136 0 0 0 1 0 0 0 5 4 94 673 0 +FFQ 137 0 0 0 0 0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 0 2 0 0 0 0 0 20 0 0 0 0 69 0 0 144 0 0 0 0 0 0 0 0 1 113 646 0 +FFQ 138 0 0 0 0 0 0 0 0 0 0 0 0 0 14 0 0 0 0 0 0 5 0 0 0 0 0 16 0 0 0 0 73 1 0 134 0 0 0 1 0 0 0 2 3 98 629 0 +FFQ 139 0 0 0 0 0 0 0 0 0 0 0 0 0 9 0 0 0 0 0 0 5 0 0 0 0 0 19 0 0 0 0 77 0 0 124 0 0 0 0 0 0 0 0 6 87 615 0 +FFQ 140 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 3 0 0 0 0 0 18 0 0 0 0 70 0 0 122 0 0 0 0 0 0 0 2 5 87 595 0 +FFQ 141 0 0 0 0 0 0 0 0 0 0 0 0 0 15 0 0 0 0 0 0 4 0 0 0 0 0 12 0 0 0 0 67 0 0 116 0 0 0 0 0 0 0 2 5 71 590 0 +FFQ 142 0 0 0 0 0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 0 3 0 0 0 0 0 19 0 0 0 0 52 0 0 116 0 0 0 0 0 0 0 0 4 78 559 0 +FFQ 143 0 0 1 1 0 0 0 0 0 0 0 0 0 61 0 0 0 1 3 0 10 0 0 0 0 0 29 0 0 0 0 64 21 0 33 1 0 0 1 0 0 1 13 15 117 442 0 +# Last Fragment Qualities. Use `grep ^LFQ | cut -f 2-` to extract this part. +# Columns correspond to qualities and rows to cycles. First column is the cycle number. +LFQ 1 1 0 0 0 0 0 0 0 0 0 0 0 0 34 0 0 0 0 0 0 15 0 0 0 0 0 1 0 0 0 0 48 0 0 849 0 0 0 0 0 0 0 0 1 80 1792 0 +LFQ 2 2 0 1 0 0 0 0 0 0 0 0 0 0 28 0 0 0 0 0 0 10 0 0 0 0 0 0 0 0 0 0 38 0 0 869 0 0 0 0 0 0 0 0 3 86 1784 0 +LFQ 3 2 0 0 0 0 0 0 0 0 0 0 0 0 31 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0 0 0 50 0 0 855 0 0 0 3 0 1 0 0 0 69 1802 0 +LFQ 4 3 0 0 0 0 0 0 0 0 0 0 0 0 32 0 0 0 0 0 0 12 0 0 0 0 0 2 0 0 0 0 38 0 0 861 0 0 0 0 0 0 0 0 2 60 1811 0 +LFQ 5 2 0 0 0 0 0 0 0 0 0 0 0 0 33 0 0 0 0 0 0 8 0 0 0 0 0 1 0 0 0 0 39 0 0 866 0 0 0 0 0 0 0 0 1 72 1799 0 +LFQ 6 3 0 0 0 0 0 0 0 0 0 0 0 0 29 0 0 0 0 0 0 15 0 0 0 0 0 1 0 0 0 0 42 0 0 858 0 0 0 1 0 0 0 0 0 68 1804 0 +LFQ 7 2 0 0 0 0 0 0 0 0 0 0 0 0 39 0 0 0 0 0 0 12 0 0 0 0 0 0 0 0 0 0 44 0 0 852 0 0 0 1 0 0 0 0 1 71 1799 0 +LFQ 8 3 0 1 0 0 0 0 0 0 0 0 0 0 31 0 0 0 0 0 0 16 0 0 0 0 0 2 0 0 0 0 39 0 0 857 0 0 0 0 0 0 0 0 1 72 1799 0 +LFQ 9 3 0 0 0 0 0 0 0 0 0 1 0 0 32 0 0 0 0 0 0 7 0 0 0 0 0 0 0 0 0 0 41 0 0 863 0 0 0 1 0 0 0 0 1 69 1803 0 +LFQ 10 3 0 0 0 0 0 0 0 0 0 0 0 0 34 0 0 0 0 0 0 13 0 0 0 0 0 0 0 0 0 0 57 0 0 844 0 0 0 0 0 0 0 0 3 70 1797 0 +LFQ 11 3 0 0 0 0 0 0 0 0 0 0 0 0 33 0 0 0 0 0 0 12 0 0 0 0 0 1 0 0 0 0 33 1 0 866 0 0 0 1 0 0 0 1 0 66 1804 0 +LFQ 12 3 0 0 0 0 0 0 0 0 0 0 0 0 34 0 0 0 0 0 0 12 0 0 0 0 0 3 0 0 0 0 31 0 0 865 0 0 0 0 0 0 0 0 0 62 1811 0 +LFQ 13 2 0 0 0 0 0 0 0 0 0 0 0 0 42 0 0 0 0 0 0 10 0 0 0 0 0 4 0 0 0 0 44 1 0 848 0 0 0 1 0 0 0 0 1 75 1793 0 +LFQ 14 3 0 0 0 0 0 0 0 0 0 0 0 0 38 0 0 0 0 0 0 5 0 0 0 0 0 4 0 0 0 0 47 0 0 850 0 0 0 0 0 0 0 0 3 75 1796 0 +LFQ 15 3 0 0 0 0 0 0 0 0 0 0 0 0 41 0 0 0 0 0 0 7 0 0 0 0 0 0 0 0 0 0 41 1 0 858 0 0 0 1 0 0 0 0 1 84 1784 0 +LFQ 16 3 0 1 0 0 0 0 0 0 0 0 0 0 39 0 0 0 0 0 0 4 0 0 0 0 0 13 0 0 0 0 39 1 0 850 0 0 0 0 0 0 0 0 0 70 1801 0 +LFQ 17 3 0 0 0 0 0 0 0 0 0 0 0 0 42 0 0 0 0 0 0 8 0 0 0 0 0 2 0 0 0 0 31 0 0 863 0 0 0 0 0 0 0 0 1 77 1794 0 +LFQ 18 2 0 0 0 0 0 0 0 0 0 0 0 0 46 0 0 0 0 0 0 9 0 0 0 0 0 3 0 0 0 0 35 0 0 854 0 0 0 0 0 0 0 1 2 67 1802 0 +LFQ 19 3 0 0 0 0 0 0 0 0 0 0 0 0 44 0 0 0 0 0 0 8 0 0 0 0 0 3 0 0 0 0 37 0 0 854 0 0 0 1 0 0 0 0 1 70 1800 0 +LFQ 20 3 0 0 0 0 0 0 0 0 0 0 0 0 50 0 0 0 0 0 0 6 0 0 0 0 0 3 0 0 0 0 36 0 0 851 0 0 0 2 0 0 0 0 3 69 1798 0 +LFQ 21 3 0 1 0 0 0 0 0 0 0 0 0 0 34 0 0 0 0 0 0 6 0 0 0 0 0 2 0 0 0 0 36 1 0 867 0 0 0 1 0 0 0 0 1 73 1796 0 +LFQ 22 3 0 0 0 0 0 0 0 0 0 0 0 0 30 0 0 0 0 0 0 6 0 0 0 0 0 1 0 0 0 0 43 2 0 866 0 0 0 0 0 0 0 0 3 70 1797 0 +LFQ 23 3 0 0 0 0 0 0 0 0 0 0 0 0 46 0 0 0 0 0 0 2 0 0 0 0 0 2 0 0 0 0 38 0 0 859 0 0 0 0 0 0 0 0 0 71 1800 0 +LFQ 24 3 0 0 0 0 0 0 0 0 0 0 0 0 45 0 0 0 0 0 0 4 0 0 0 0 0 2 0 0 0 0 35 0 0 859 0 0 0 0 0 0 0 0 1 72 1800 0 +LFQ 25 3 0 0 0 0 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 6 0 0 0 0 0 2 0 0 0 0 36 0 0 861 0 0 0 0 0 0 0 0 3 63 1807 0 +LFQ 26 3 0 0 0 0 0 0 0 0 0 0 0 0 43 0 0 0 0 0 0 4 0 0 0 0 0 4 0 0 0 0 41 1 0 853 0 0 0 0 0 0 0 0 2 73 1797 0 +LFQ 27 3 0 0 0 0 0 0 0 0 0 0 0 0 46 0 0 0 0 0 0 10 0 0 0 0 0 9 0 0 0 0 32 0 0 850 0 0 0 0 0 0 0 0 2 73 1796 0 +LFQ 28 3 0 0 0 0 0 0 0 0 0 0 0 0 45 0 0 0 0 1 0 6 0 0 0 0 0 7 0 0 0 0 39 2 0 848 0 0 0 0 0 0 0 0 0 73 1797 0 +LFQ 29 3 0 0 0 0 0 0 0 0 0 0 0 0 45 0 0 0 0 0 0 1 0 0 0 0 0 4 0 0 0 0 35 1 0 860 0 0 0 1 0 0 0 0 3 73 1795 0 +LFQ 30 3 0 0 0 0 0 0 0 0 0 0 0 0 39 0 0 0 0 0 0 2 0 0 0 0 0 8 0 0 0 0 34 0 0 863 0 0 0 0 0 0 0 0 2 69 1801 0 +LFQ 31 3 0 0 0 0 0 0 0 0 0 0 0 0 45 0 0 0 1 0 0 5 0 0 0 0 0 2 0 0 0 0 38 1 0 857 0 0 0 0 0 0 0 0 3 77 1789 0 +LFQ 32 3 0 0 0 0 0 0 0 0 0 0 0 0 46 0 0 0 0 0 0 5 0 0 0 0 0 3 0 0 0 0 37 2 0 855 0 0 0 0 0 0 0 0 2 82 1786 0 +LFQ 33 3 0 0 0 0 0 0 0 0 0 0 0 0 45 0 0 0 0 0 0 7 0 0 0 0 0 6 0 0 0 0 35 0 0 853 0 0 0 0 0 0 0 0 1 76 1795 0 +LFQ 34 3 0 0 0 0 0 0 0 0 0 0 0 0 43 0 0 0 0 0 0 11 0 0 0 0 0 10 0 0 0 0 38 2 0 844 0 0 0 1 0 0 0 0 0 85 1783 0 +LFQ 35 3 0 0 0 0 0 0 0 0 0 0 0 0 39 0 0 0 1 0 0 9 0 0 0 0 0 5 0 0 0 0 37 0 0 856 0 0 0 1 0 0 0 1 1 80 1787 0 +LFQ 36 3 0 1 0 0 0 0 0 0 0 0 0 0 41 0 0 0 0 0 0 8 0 0 0 0 0 6 0 0 0 0 33 2 0 856 0 0 0 1 0 0 0 0 1 72 1796 0 +LFQ 37 3 0 0 0 0 0 0 0 0 0 0 0 0 47 0 0 0 0 0 0 4 0 0 0 0 0 3 0 0 0 0 25 0 0 867 0 0 0 0 0 0 0 0 3 84 1784 0 +LFQ 38 3 0 0 0 0 0 0 0 0 0 0 0 0 54 0 0 0 0 0 0 8 0 0 0 0 0 8 0 0 0 0 27 0 0 849 0 0 0 0 0 0 0 0 1 85 1785 0 +LFQ 39 3 0 2 0 0 0 0 0 0 0 0 0 0 41 0 0 0 0 0 0 11 0 0 0 0 0 8 0 0 0 0 44 1 0 840 0 0 0 1 0 0 0 0 3 67 1799 0 +LFQ 40 3 0 0 0 0 0 0 0 0 0 0 0 0 51 0 0 0 0 0 0 5 0 0 0 0 0 10 0 0 0 0 41 2 0 838 0 0 0 1 0 0 0 1 1 93 1774 0 +LFQ 41 3 0 0 0 0 0 0 0 0 0 0 0 0 52 0 0 0 1 0 0 7 0 0 0 0 0 11 0 0 0 0 56 0 0 818 0 0 0 0 0 0 0 0 2 96 1774 0 +LFQ 42 3 0 0 0 0 0 0 0 0 0 0 0 0 46 0 0 0 0 0 0 5 0 0 0 0 0 10 0 0 0 0 35 1 0 848 0 0 0 1 0 0 0 2 3 102 1764 0 +LFQ 43 3 0 0 0 0 0 0 0 0 0 0 0 0 46 0 0 0 0 0 0 6 0 0 0 0 0 5 0 0 0 0 42 0 0 845 2 0 0 0 0 0 0 0 2 103 1766 0 +LFQ 44 3 0 0 0 1 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 10 0 0 0 0 0 11 0 0 0 0 51 0 0 834 0 0 0 0 0 0 0 0 3 97 1770 0 +LFQ 45 3 0 1 0 0 0 0 0 0 0 0 0 0 50 0 0 0 1 0 0 4 0 0 0 0 0 14 0 0 0 0 46 1 0 831 0 0 0 1 0 0 0 0 3 94 1771 0 +LFQ 46 3 0 0 0 0 0 0 0 0 0 0 0 0 59 0 0 0 0 0 0 10 0 0 0 0 0 9 0 0 0 0 33 0 0 835 0 0 0 1 0 0 0 0 4 104 1762 0 +LFQ 47 3 0 0 0 0 0 0 0 0 0 0 0 0 48 0 0 0 0 0 0 8 0 0 0 0 0 15 0 0 0 0 64 1 0 810 0 0 0 1 0 0 0 1 3 117 1749 0 +LFQ 48 3 0 0 0 0 0 0 0 0 0 0 0 0 43 0 0 0 0 0 0 7 0 0 0 0 0 14 0 0 0 0 62 3 0 820 0 0 0 0 0 0 0 0 3 103 1762 0 +LFQ 49 3 0 0 0 0 0 0 0 0 0 0 0 0 51 0 0 0 0 0 0 7 0 0 0 0 0 17 0 0 0 0 61 2 0 809 0 0 0 0 0 0 0 1 3 106 1760 0 +LFQ 50 3 0 0 0 1 0 0 0 0 0 0 0 0 48 0 0 0 0 0 0 4 0 0 0 0 0 18 0 0 0 0 56 3 0 818 0 0 0 1 0 0 0 2 3 132 1728 0 +LFQ 51 3 0 1 0 0 0 0 0 0 0 0 0 0 55 0 0 0 0 0 0 6 0 0 0 0 0 20 0 0 0 0 66 3 0 796 0 0 0 1 0 0 0 1 4 111 1750 0 +LFQ 52 3 0 0 0 0 0 0 0 0 0 0 0 0 47 0 0 0 0 0 0 5 0 0 0 0 0 36 0 0 0 0 56 0 0 799 0 0 0 0 0 0 0 0 3 139 1728 0 +LFQ 53 3 0 0 0 0 0 0 0 0 0 0 0 0 47 0 0 0 0 0 0 7 0 0 0 0 0 22 0 0 0 0 60 1 0 808 0 0 0 1 0 0 0 0 2 114 1749 0 +LFQ 54 3 0 0 0 0 0 0 0 0 0 0 0 0 51 0 0 0 0 0 0 5 0 0 0 0 0 21 0 0 0 0 62 0 0 803 0 0 0 1 0 0 0 2 1 109 1756 0 +LFQ 55 3 0 0 0 0 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 12 0 0 0 0 0 17 0 0 0 0 74 1 0 799 0 0 0 1 0 0 0 0 2 121 1743 0 +LFQ 56 3 0 0 0 0 0 0 0 0 0 0 0 0 49 0 0 0 0 0 0 9 0 0 0 0 0 16 0 0 0 0 60 1 0 808 0 0 0 0 0 0 0 0 2 134 1731 0 +LFQ 57 3 0 1 0 0 0 0 0 0 0 0 0 0 45 0 0 0 0 0 0 3 0 0 0 0 0 18 0 0 0 0 57 0 0 818 0 0 0 1 0 0 0 1 5 132 1729 0 +LFQ 58 3 0 0 0 0 0 0 0 0 0 0 0 0 51 0 0 0 0 0 0 2 0 0 0 0 0 11 0 0 0 0 52 1 0 826 0 0 0 2 0 0 0 0 6 107 1752 0 +LFQ 59 3 0 0 0 0 0 0 0 0 0 0 0 0 30 0 0 0 0 0 0 9 0 0 0 0 0 14 0 0 0 0 55 1 0 832 1 0 0 0 0 0 0 1 0 129 1737 0 +LFQ 60 3 0 0 0 0 0 0 0 0 0 0 0 0 35 0 0 0 0 0 0 8 0 0 0 0 0 14 0 0 0 0 70 0 0 813 0 0 0 0 0 0 0 0 6 118 1743 0 +LFQ 61 3 0 0 0 0 0 0 0 0 0 0 0 0 50 0 0 0 0 0 0 3 0 0 0 0 0 15 0 0 0 0 49 0 0 820 0 0 0 1 0 0 0 0 2 107 1759 0 +LFQ 62 3 0 0 0 0 0 0 0 0 0 0 0 0 35 0 0 0 1 0 0 9 0 0 0 0 0 21 0 0 0 0 67 1 0 808 0 0 0 0 0 0 0 1 2 121 1736 0 +LFQ 63 3 0 0 0 0 0 0 0 0 0 0 0 0 46 0 0 0 0 0 0 4 0 0 0 0 0 23 0 0 0 0 72 1 0 792 0 0 0 1 0 0 0 0 2 124 1736 0 +LFQ 64 3 0 0 0 0 0 0 0 0 0 0 0 0 58 0 0 0 0 0 0 6 0 0 0 0 0 19 0 0 0 0 78 2 0 772 0 0 0 1 0 0 0 0 2 111 1747 0 +LFQ 65 3 0 0 0 0 0 0 0 0 0 0 0 0 47 0 0 0 0 0 0 11 0 0 0 0 0 22 0 0 0 0 61 1 0 794 0 0 0 0 0 0 0 0 6 134 1720 0 +LFQ 66 2 0 0 0 0 0 0 0 0 0 0 0 0 46 0 0 0 0 0 0 8 0 0 0 0 0 25 0 0 0 0 53 1 0 801 0 0 0 1 0 0 0 1 1 135 1720 0 +LFQ 67 2 0 0 0 0 0 0 0 0 0 0 0 0 39 0 0 0 0 0 0 3 0 0 0 0 0 22 0 0 0 0 69 1 0 796 0 0 0 1 0 0 0 2 0 114 1743 0 +LFQ 68 2 0 0 0 0 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 6 0 0 0 0 0 28 0 0 0 0 60 0 0 792 0 0 0 1 0 0 0 0 3 128 1726 0 +LFQ 69 2 0 0 0 0 0 0 0 0 0 0 0 0 42 0 0 0 1 0 0 11 0 0 0 0 0 22 0 0 0 0 68 0 0 781 0 0 0 1 0 0 0 1 5 143 1705 0 +LFQ 70 3 0 0 0 0 0 0 0 0 0 0 0 0 47 0 0 0 0 0 0 7 0 0 0 0 0 31 0 0 0 0 57 2 0 778 0 0 0 0 0 0 0 1 4 127 1720 0 +LFQ 71 2 0 0 0 0 0 0 0 0 0 0 0 0 54 0 0 0 0 0 0 3 0 0 0 0 0 16 0 0 0 0 67 3 0 778 0 0 0 0 0 0 0 1 4 138 1701 0 +LFQ 72 2 0 1 0 0 0 0 0 0 0 0 0 0 49 0 0 0 0 0 0 6 0 0 0 0 0 18 0 0 0 0 64 2 0 777 0 0 0 1 0 0 0 0 5 139 1692 0 +LFQ 73 1 0 0 0 0 0 0 0 0 0 0 0 0 38 0 0 0 0 0 0 8 0 0 0 0 0 29 0 0 0 0 75 0 0 761 0 0 0 0 0 0 0 1 5 146 1685 0 +LFQ 74 2 0 1 0 1 0 0 0 0 0 0 0 0 54 0 0 0 0 0 0 9 0 0 0 0 0 13 0 0 0 0 84 0 0 745 0 0 0 0 0 0 0 2 0 145 1685 0 +LFQ 75 2 0 0 0 0 0 0 0 0 0 0 0 0 56 0 0 0 0 0 0 8 0 0 0 0 0 24 0 0 0 0 55 0 0 762 0 0 0 0 0 1 0 0 3 153 1673 0 +LFQ 76 2 0 0 0 0 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 15 0 0 0 0 0 18 0 0 0 0 80 2 0 749 0 0 0 0 0 0 0 1 6 146 1666 0 +LFQ 77 1 0 1 0 0 0 0 0 0 0 0 0 0 61 0 0 0 0 0 0 8 0 0 0 0 0 20 0 0 0 0 73 1 0 736 0 0 0 1 0 0 0 1 4 136 1671 0 +LFQ 78 1 0 0 0 0 0 0 0 0 0 0 0 0 56 0 0 0 0 0 0 16 0 0 0 0 0 21 0 0 0 0 68 1 0 724 0 0 0 1 0 0 0 0 4 148 1655 0 +LFQ 79 1 0 0 0 0 0 0 0 0 0 0 0 0 45 0 0 0 0 0 0 14 0 0 0 0 0 24 0 0 0 0 78 2 0 717 0 0 0 1 0 0 0 2 4 151 1641 0 +LFQ 80 2 0 0 0 0 0 0 0 0 0 0 0 0 51 0 0 0 0 0 0 7 0 0 0 0 0 37 0 0 0 0 76 1 0 700 0 0 0 0 0 0 0 1 6 146 1640 0 +LFQ 81 1 0 0 0 0 0 0 0 0 0 0 0 0 45 0 0 0 0 0 0 10 0 0 0 0 0 13 0 0 0 0 90 2 0 703 0 0 0 0 0 0 0 1 5 142 1638 0 +LFQ 82 2 0 0 0 1 0 0 0 0 0 0 0 0 54 0 0 0 0 0 0 6 0 0 0 0 1 25 0 0 0 0 74 0 0 688 0 0 0 4 0 0 0 0 5 154 1612 0 +LFQ 83 1 0 0 0 0 0 0 0 0 0 0 0 0 39 0 0 0 0 0 0 4 0 0 0 0 0 25 0 0 0 0 71 0 0 700 0 0 0 1 0 0 0 1 5 160 1601 0 +LFQ 84 1 0 0 0 0 0 0 0 0 0 0 0 0 54 0 0 0 0 0 0 10 0 0 0 0 0 20 0 0 0 0 70 1 0 675 0 0 0 0 0 0 0 2 9 135 1612 0 +LFQ 85 1 0 0 0 0 0 0 0 0 0 0 0 0 45 0 0 0 0 0 0 5 0 0 0 0 0 24 0 0 0 0 76 2 0 668 0 0 0 0 0 0 0 0 6 145 1592 0 +LFQ 86 0 0 0 0 0 0 0 0 0 0 0 0 0 45 0 0 0 0 0 0 9 0 0 0 0 0 29 0 0 0 0 84 3 0 642 0 0 0 0 0 0 0 3 6 139 1589 0 +LFQ 87 1 0 0 0 0 0 0 0 0 0 0 0 0 53 0 0 0 0 0 0 7 0 0 0 0 0 27 0 0 0 0 88 1 0 629 0 0 0 0 0 0 0 0 10 147 1562 0 +LFQ 88 1 0 0 0 0 0 0 0 0 0 0 0 0 38 0 0 0 1 0 0 7 0 0 0 0 0 25 0 0 0 0 83 2 0 637 0 0 0 0 0 0 0 3 7 122 1569 0 +LFQ 89 1 0 1 0 0 0 0 0 0 0 0 0 0 44 0 0 0 0 0 0 5 0 0 0 0 0 32 0 0 0 0 75 3 0 621 0 0 0 0 0 0 0 1 7 124 1552 0 +LFQ 90 1 0 0 0 0 0 0 0 0 0 0 0 0 55 0 0 0 0 0 0 2 0 0 0 0 0 20 0 0 0 0 57 0 0 637 0 0 0 0 0 0 0 2 3 137 1531 0 +LFQ 91 1 0 0 0 0 0 0 0 0 0 0 0 0 42 0 0 0 0 0 0 5 0 0 0 0 0 31 0 0 0 0 67 1 0 614 0 0 0 1 0 0 0 1 3 135 1528 0 +LFQ 92 1 0 1 0 0 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 3 0 0 0 0 0 24 0 0 0 0 67 3 0 616 0 0 0 1 0 0 0 2 2 149 1498 0 +LFQ 93 1 0 0 0 0 0 0 0 0 0 0 0 0 46 0 0 0 0 0 0 5 0 0 0 0 0 30 0 0 0 0 49 2 0 610 0 0 0 0 0 0 0 1 5 142 1486 0 +LFQ 94 0 0 1 0 0 0 0 0 0 0 0 0 0 48 0 0 0 0 0 0 4 0 0 0 0 0 20 0 0 0 0 73 2 0 588 0 0 0 1 0 0 0 1 8 149 1459 0 +LFQ 95 0 0 0 0 0 0 0 0 0 0 0 0 0 52 0 0 0 0 0 0 2 0 0 0 0 0 30 0 0 0 0 68 2 0 569 0 0 0 0 0 0 0 2 6 143 1460 0 +LFQ 96 1 0 0 0 0 0 0 0 0 0 0 0 0 45 0 0 0 0 0 0 9 0 0 0 0 0 32 0 0 0 0 74 0 0 544 0 0 0 0 0 0 0 1 5 149 1431 0 +LFQ 97 1 0 1 0 0 0 0 0 0 0 0 0 0 52 0 0 0 0 0 0 6 0 0 0 0 0 29 0 0 0 0 62 0 0 530 0 0 0 0 0 0 0 2 5 147 1402 0 +LFQ 98 1 0 0 0 0 0 0 0 0 0 0 0 0 50 0 0 0 0 0 0 10 0 0 0 0 0 27 0 0 0 0 61 2 0 520 0 0 0 2 0 0 0 4 4 148 1374 0 +LFQ 99 1 0 0 0 0 0 0 0 0 0 0 0 0 46 0 0 0 0 0 0 5 0 0 0 0 0 28 0 0 0 0 58 0 0 518 0 0 0 0 0 0 0 1 6 152 1360 0 +LFQ 100 0 0 0 0 0 0 0 0 0 0 0 0 0 39 0 0 0 0 0 0 5 0 0 0 0 0 32 0 0 0 0 66 1 0 502 0 0 0 0 0 0 0 4 7 138 1357 0 +LFQ 101 1 0 1 0 0 0 0 0 0 0 0 0 0 35 0 0 0 0 0 0 6 0 0 0 0 1 38 0 0 0 0 80 0 0 466 0 0 0 0 0 0 0 0 11 143 1325 0 +LFQ 102 1 0 0 0 0 0 0 0 0 0 0 0 0 39 0 0 0 0 0 0 3 0 0 0 0 0 36 0 0 0 0 73 0 0 466 0 0 0 0 0 0 0 1 6 150 1308 0 +LFQ 103 1 0 0 0 0 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 6 0 0 0 0 0 42 0 0 0 0 77 2 0 445 0 0 0 1 0 0 0 2 4 144 1292 0 +LFQ 104 0 0 0 0 0 0 0 0 0 0 0 0 0 38 0 0 0 1 0 0 6 0 0 0 0 0 46 0 0 0 0 70 1 0 444 0 0 0 0 0 0 0 1 9 149 1269 0 +LFQ 105 0 0 0 0 0 0 0 0 0 0 0 0 0 38 0 0 0 0 0 0 4 0 0 0 0 0 30 0 0 0 0 83 3 0 432 0 0 0 0 0 0 0 6 5 175 1225 0 +LFQ 106 0 0 0 0 1 0 0 0 0 0 0 0 0 39 0 0 0 0 0 0 4 0 0 0 0 0 37 0 0 0 0 81 3 0 417 0 0 0 0 0 0 0 2 4 186 1201 0 +LFQ 107 0 0 1 0 0 0 0 0 0 0 0 0 0 38 0 0 0 0 0 0 6 0 0 0 0 0 43 0 0 0 0 81 0 0 400 0 0 0 1 0 0 0 1 8 161 1207 0 +LFQ 108 0 0 2 0 0 0 0 0 0 0 0 0 0 33 0 0 0 1 0 0 3 0 0 0 0 0 47 0 0 0 0 108 2 0 365 0 0 0 0 0 0 0 2 3 161 1185 0 +LFQ 109 1 0 0 0 0 0 0 0 0 0 0 0 0 35 0 0 0 1 0 0 2 0 0 0 0 0 45 0 0 0 0 107 0 0 353 0 0 0 0 0 0 0 2 7 178 1155 0 +LFQ 110 1 0 0 0 1 0 0 0 0 0 0 0 0 36 0 0 0 0 0 0 10 0 0 0 0 0 44 0 0 0 0 90 2 0 352 0 0 0 0 0 0 0 4 10 166 1146 0 +LFQ 111 0 0 0 0 0 0 0 0 1 0 0 0 0 30 0 0 0 0 0 0 9 0 0 0 0 0 36 0 0 0 0 124 0 0 322 0 0 0 0 0 0 0 3 5 182 1116 0 +LFQ 112 1 0 0 0 0 0 0 0 0 0 0 0 0 31 0 0 0 0 0 0 10 0 0 0 0 0 33 0 0 0 0 118 2 0 322 0 0 0 0 0 0 0 4 7 182 1089 0 +LFQ 113 0 0 0 0 0 0 0 0 0 0 0 0 0 35 0 0 0 0 0 0 11 0 0 0 0 0 42 0 0 0 0 110 2 0 305 0 0 0 1 0 0 0 2 11 194 1064 0 +LFQ 114 0 0 0 0 0 0 0 0 0 0 0 0 0 38 0 0 0 1 0 0 7 0 0 0 0 0 44 0 0 0 0 120 1 0 285 0 0 0 1 0 0 0 6 9 190 1039 0 +LFQ 115 1 0 0 0 0 0 0 0 0 0 0 0 0 31 0 0 0 2 0 0 12 0 0 0 0 0 39 0 0 0 0 114 2 0 287 0 0 0 1 0 0 0 1 10 187 1024 0 +LFQ 116 1 0 0 0 0 0 0 0 0 0 0 0 0 38 0 0 0 0 0 0 5 0 0 0 0 0 44 0 0 0 0 117 1 0 266 0 0 0 0 0 0 0 4 7 183 997 0 +LFQ 117 0 0 0 0 0 0 0 1 0 0 0 0 0 33 0 0 0 2 0 0 9 0 0 0 0 0 43 0 0 0 0 102 1 0 273 0 0 0 0 0 0 0 3 4 164 992 0 +LFQ 118 0 0 0 0 0 0 0 0 0 0 0 0 0 34 0 0 0 1 0 0 6 0 0 0 0 0 46 0 0 0 0 98 0 0 266 0 0 0 0 0 0 0 2 5 180 956 0 +LFQ 119 0 0 1 0 0 0 0 0 0 0 0 0 0 35 0 0 0 0 0 0 8 0 0 0 0 0 37 0 0 0 0 110 1 0 249 0 0 0 1 0 0 0 2 9 161 946 0 +LFQ 120 0 0 0 0 0 0 0 0 0 0 0 0 0 30 0 0 0 1 0 0 11 0 0 0 0 0 36 0 0 0 0 97 2 0 256 0 0 0 1 0 0 0 4 8 163 914 0 +LFQ 121 0 0 0 0 0 0 0 0 0 0 0 0 0 33 0 0 0 1 0 0 13 0 0 0 0 0 33 0 0 0 0 103 0 0 237 0 0 0 0 0 0 0 3 6 162 917 0 +LFQ 122 0 0 0 0 0 0 0 0 0 0 0 0 0 38 0 0 0 0 0 0 6 0 0 0 0 0 31 0 0 0 0 105 0 0 225 0 0 0 0 0 0 0 0 6 147 911 0 +LFQ 123 0 0 0 0 0 0 0 0 0 0 0 0 0 30 0 0 0 0 0 0 13 0 0 0 0 0 31 0 0 0 0 107 0 0 218 0 0 0 0 0 0 0 2 6 153 879 0 +LFQ 124 0 0 0 0 0 0 0 0 0 0 0 0 0 28 0 0 0 0 0 0 9 0 0 0 0 0 33 0 0 0 0 91 1 0 228 0 0 0 2 0 0 0 2 8 158 851 0 +LFQ 125 0 0 0 0 0 0 0 0 0 0 0 0 0 29 0 0 0 1 0 0 8 0 0 0 0 0 29 0 0 0 0 113 0 0 193 0 0 0 0 0 0 0 3 8 155 836 0 +LFQ 126 0 0 0 0 0 0 0 0 0 0 0 0 0 36 0 0 0 0 0 0 11 0 0 0 0 0 27 0 0 0 0 84 0 0 202 0 0 0 1 0 0 1 3 8 146 820 0 +LFQ 127 1 0 0 0 1 0 0 0 0 0 0 0 0 27 0 0 0 0 0 0 12 0 0 0 0 0 35 0 0 0 0 96 1 0 186 0 0 0 0 0 0 0 2 4 138 812 0 +LFQ 128 1 0 0 0 0 0 0 0 0 0 0 0 0 28 0 0 0 1 0 0 7 0 0 0 0 0 26 0 0 0 0 80 0 0 200 0 0 0 0 0 0 0 2 13 141 785 0 +LFQ 129 0 0 0 0 0 0 0 1 0 0 1 0 0 34 0 0 0 1 0 0 6 0 0 0 0 0 27 0 0 0 0 75 3 0 192 0 0 0 0 0 0 0 1 7 138 767 0 +LFQ 130 0 0 1 0 0 0 0 0 0 0 0 0 0 27 0 0 0 0 0 0 4 0 0 0 0 0 28 0 0 0 0 79 2 0 184 0 0 0 0 0 0 0 4 11 133 752 0 +LFQ 131 0 0 0 0 0 0 0 0 0 0 0 0 0 23 0 0 0 1 0 0 6 0 0 0 0 0 21 0 0 0 0 82 1 0 182 0 0 0 1 0 0 0 3 7 113 746 0 +LFQ 132 0 0 0 0 0 0 0 0 0 0 0 0 0 19 0 0 0 0 0 0 9 0 0 0 0 0 18 0 0 0 0 71 0 0 186 0 0 0 0 0 0 0 0 5 134 699 0 +LFQ 133 0 0 0 0 0 0 0 0 0 0 0 0 0 25 0 0 0 0 0 0 3 0 0 0 0 0 25 0 0 0 0 83 0 0 160 0 0 0 1 0 0 0 3 5 112 699 0 +LFQ 134 0 0 0 0 0 0 0 0 0 0 0 0 0 26 0 0 0 1 0 0 5 0 0 0 0 0 26 0 0 0 0 70 1 0 160 0 0 0 0 0 0 0 0 9 115 684 0 +LFQ 135 0 0 0 0 0 0 0 0 0 0 0 0 0 21 0 0 0 0 0 0 6 0 0 0 0 0 26 0 0 0 0 70 0 0 152 2 0 0 0 0 0 0 0 5 115 675 0 +LFQ 136 0 0 0 0 0 0 0 0 0 0 0 0 0 26 0 0 0 1 0 0 7 0 0 0 0 0 15 0 0 0 0 71 0 0 144 0 0 0 1 0 0 0 0 3 111 662 0 +LFQ 137 0 0 0 0 0 0 0 0 0 0 0 0 0 25 0 0 0 1 0 0 6 0 0 0 0 0 21 0 0 0 0 62 0 0 138 0 0 0 0 0 0 0 4 5 103 646 0 +LFQ 138 0 0 0 0 0 0 0 0 0 0 0 0 0 17 0 0 0 0 0 0 4 0 0 0 0 0 21 0 0 0 0 65 1 0 142 1 0 0 0 0 0 0 2 5 111 613 0 +LFQ 139 0 0 0 0 0 0 0 0 0 0 0 0 0 15 0 0 0 0 0 0 10 0 0 0 0 0 15 0 0 0 0 66 0 0 134 0 0 0 1 0 0 0 3 4 109 591 0 +LFQ 140 0 0 0 0 0 0 0 0 0 0 0 0 0 25 0 0 0 0 0 0 5 0 0 0 0 0 12 0 0 0 0 59 0 0 127 0 0 0 1 0 0 0 2 5 97 583 0 +LFQ 141 0 0 0 0 0 0 0 0 0 0 0 0 0 18 0 0 0 0 0 0 5 0 0 0 0 0 16 0 0 0 0 64 0 0 119 0 0 0 0 0 0 0 2 6 84 574 0 +LFQ 142 0 0 0 0 0 0 0 0 0 0 0 0 0 20 0 0 0 1 0 0 3 0 0 0 0 0 20 0 0 0 0 47 0 0 117 0 0 0 1 0 0 0 1 2 88 547 0 +LFQ 143 0 0 5 1 0 0 0 0 0 0 0 1 0 71 0 1 0 5 4 0 12 0 0 0 0 3 34 0 0 0 0 52 18 1 36 2 1 0 5 0 0 1 13 14 124 416 0 +# GC Content of first fragments. Use `grep ^GCF | cut -f 2-` to extract this part. +GCF 7.29 0 +GCF 15.08 1 +GCF 17.09 0 +GCF 18.84 1 +GCF 19.35 2 +GCF 19.85 1 +GCF 20.35 0 +GCF 20.85 5 +GCF 21.36 4 +GCF 21.86 6 +GCF 22.36 20 +GCF 22.86 14 +GCF 23.37 23 +GCF 23.87 57 +GCF 24.37 86 +GCF 24.87 83 +GCF 25.38 107 +GCF 25.88 104 +GCF 26.38 78 +GCF 26.88 77 +GCF 27.39 125 +GCF 27.89 201 +GCF 28.39 258 +GCF 28.89 336 +GCF 29.40 353 +GCF 29.90 254 +GCF 30.40 213 +GCF 30.90 217 +GCF 31.41 195 +GCF 31.91 203 +GCF 32.41 177 +GCF 32.91 153 +GCF 33.42 160 +GCF 33.92 130 +GCF 34.42 104 +GCF 34.92 82 +GCF 35.43 96 +GCF 35.93 100 +GCF 36.43 106 +GCF 36.93 136 +GCF 37.44 138 +GCF 37.94 113 +GCF 38.44 73 +GCF 38.94 35 +GCF 39.45 16 +GCF 39.95 12 +GCF 40.45 8 +GCF 40.95 11 +GCF 41.46 13 +GCF 42.21 11 +GCF 42.96 13 +GCF 43.47 15 +GCF 43.97 12 +GCF 44.47 13 +GCF 44.97 12 +GCF 45.48 10 +GCF 45.98 12 +GCF 46.48 10 +GCF 46.98 5 +GCF 47.49 13 +GCF 47.99 14 +GCF 48.49 13 +GCF 49.25 4 +GCF 50.00 3 +GCF 50.50 2 +GCF 51.01 1 +# GC Content of last fragments. Use `grep ^GCL | cut -f 2-` to extract this part. +GCL 5.53 0 +GCL 11.31 1 +GCL 13.07 0 +GCL 14.82 1 +GCL 15.83 0 +GCL 17.09 2 +GCL 18.09 1 +GCL 18.84 0 +GCL 19.35 1 +GCL 19.85 0 +GCL 20.35 2 +GCL 20.85 6 +GCL 21.36 5 +GCL 21.86 6 +GCL 22.61 16 +GCL 23.37 24 +GCL 23.87 58 +GCL 24.37 94 +GCL 24.87 90 +GCL 25.38 110 +GCL 25.88 106 +GCL 26.38 85 +GCL 26.88 79 +GCL 27.39 120 +GCL 27.89 210 +GCL 28.39 262 +GCL 29.15 342 +GCL 29.90 254 +GCL 30.40 211 +GCL 30.90 227 +GCL 31.41 196 +GCL 31.91 203 +GCL 32.41 176 +GCL 32.91 155 +GCL 33.42 153 +GCL 33.92 122 +GCL 34.42 94 +GCL 34.92 75 +GCL 35.43 96 +GCL 35.93 100 +GCL 36.43 103 +GCL 37.19 141 +GCL 37.94 111 +GCL 38.44 65 +GCL 38.94 34 +GCL 39.45 19 +GCL 39.95 10 +GCL 40.45 5 +GCL 40.95 10 +GCL 41.46 16 +GCL 41.96 12 +GCL 42.46 10 +GCL 42.96 13 +GCL 43.47 15 +GCL 44.22 12 +GCL 45.23 10 +GCL 45.98 13 +GCL 46.48 10 +GCL 46.98 5 +GCL 47.49 12 +GCL 47.99 13 +GCL 48.49 12 +GCL 49.50 3 +GCL 50.50 2 +GCL 51.01 1 +# ACGT content per cycle. Use `grep ^GCC | cut -f 2-` to extract this part. The columns are: cycle; A,C,G,T base counts as a percentage of all A/C/G/T bases [%]; and N and O counts as a percentage of all A/C/G/T bases [%] +GCC 1 33.73 15.23 16.08 34.95 0.05 0.00 +GCC 2 33.26 15.50 17.56 33.68 0.07 0.00 +GCC 3 34.59 15.25 15.74 34.41 0.04 0.00 +GCC 4 34.25 15.59 14.81 35.35 0.07 0.00 +GCC 5 34.04 15.51 14.65 35.80 0.04 0.00 +GCC 6 34.55 15.18 14.90 35.37 0.07 0.00 +GCC 7 33.79 15.99 15.21 35.00 0.04 0.00 +GCC 8 33.09 15.24 16.00 35.66 0.11 0.00 +GCC 9 34.44 14.22 15.75 35.58 0.07 0.00 +GCC 10 34.60 13.75 16.53 35.12 0.07 0.00 +GCC 11 34.33 14.12 15.87 35.68 0.05 0.00 +GCC 12 34.09 13.63 16.18 36.10 0.12 0.00 +GCC 13 33.74 13.78 18.14 34.34 0.07 0.00 +GCC 14 33.91 13.41 17.33 35.35 0.07 0.00 +GCC 15 33.43 15.10 16.95 34.52 0.12 0.00 +GCC 16 35.00 14.64 16.58 33.78 0.14 0.00 +GCC 17 33.22 13.89 16.64 36.25 0.11 0.00 +GCC 18 33.53 14.44 17.40 34.63 0.09 0.00 +GCC 19 32.82 14.78 16.44 35.96 0.09 0.00 +GCC 20 32.91 14.01 15.13 37.95 0.09 0.00 +GCC 21 32.48 14.01 15.89 37.62 0.07 0.00 +GCC 22 33.20 13.15 17.02 36.64 0.11 0.00 +GCC 23 33.37 13.15 16.96 36.52 0.11 0.00 +GCC 24 33.42 14.32 16.55 35.71 0.09 0.00 +GCC 25 34.75 14.16 15.39 35.69 0.14 0.00 +GCC 26 34.77 14.74 15.86 34.63 0.09 0.00 +GCC 27 34.44 14.62 16.41 34.53 0.11 0.00 +GCC 28 33.81 14.80 16.60 34.79 0.14 0.00 +GCC 29 32.78 15.11 17.35 34.76 0.07 0.00 +GCC 30 33.59 14.01 16.28 36.11 0.07 0.00 +GCC 31 31.51 15.00 16.46 37.03 0.14 0.00 +GCC 32 32.85 15.33 15.53 36.29 0.11 0.00 +GCC 33 33.03 14.83 14.74 37.40 0.18 0.00 +GCC 34 31.55 14.77 15.43 38.25 0.14 0.00 +GCC 35 31.62 13.70 17.11 37.56 0.12 0.00 +GCC 36 33.35 13.99 16.95 35.71 0.11 0.00 +GCC 37 34.93 13.37 15.30 36.40 0.11 0.00 +GCC 38 32.95 15.35 16.29 35.40 0.09 0.00 +GCC 39 32.83 14.42 16.44 36.31 0.14 0.00 +GCC 40 32.53 14.47 17.47 35.53 0.11 0.00 +GCC 41 32.82 14.00 16.47 36.71 0.11 0.00 +GCC 42 32.08 15.16 14.97 37.80 0.11 0.00 +GCC 43 32.53 14.39 15.71 37.36 0.09 0.00 +GCC 44 33.32 13.78 16.18 36.71 0.16 0.00 +GCC 45 34.93 14.79 15.20 35.07 0.14 0.00 +GCC 46 33.30 14.49 15.91 36.30 0.09 0.00 +GCC 47 33.27 14.41 15.58 36.74 0.16 0.00 +GCC 48 33.61 13.94 16.09 36.36 0.14 0.00 +GCC 49 33.68 14.10 16.10 36.13 0.09 0.00 +GCC 50 34.40 14.52 16.02 35.06 0.12 0.00 +GCC 51 32.24 16.31 15.61 35.85 0.14 0.00 +GCC 52 30.68 14.79 17.46 37.07 0.09 0.00 +GCC 53 32.12 15.12 17.20 35.56 0.07 0.00 +GCC 54 32.50 15.04 16.66 35.81 0.12 0.00 +GCC 55 31.01 16.58 17.31 35.10 0.16 0.00 +GCC 56 31.83 15.29 16.65 36.23 0.12 0.00 +GCC 57 31.48 15.42 15.83 37.27 0.14 0.00 +GCC 58 33.02 14.52 15.02 37.44 0.18 0.00 +GCC 59 31.54 14.75 16.56 37.15 0.12 0.00 +GCC 60 30.03 14.95 16.88 38.14 0.12 0.00 +GCC 61 30.74 15.64 16.51 37.12 0.12 0.00 +GCC 62 31.90 16.15 15.32 36.63 0.11 0.00 +GCC 63 32.40 14.53 15.56 37.51 0.18 0.00 +GCC 64 30.36 15.07 16.43 38.15 0.13 0.00 +GCC 65 30.70 14.89 16.59 37.82 0.16 0.00 +GCC 66 30.54 16.30 15.44 37.71 0.07 0.00 +GCC 67 30.73 16.55 14.74 37.98 0.09 0.00 +GCC 68 30.99 14.80 16.96 37.26 0.05 0.00 +GCC 69 29.97 15.79 16.10 38.14 0.07 0.00 +GCC 70 29.88 15.26 16.44 38.42 0.07 0.00 +GCC 71 29.89 15.82 16.20 38.08 0.05 0.00 +GCC 72 31.27 16.51 15.56 36.66 0.07 0.00 +GCC 73 30.37 15.27 15.12 39.24 0.04 0.00 +GCC 74 30.08 14.71 15.41 39.80 0.05 0.00 +GCC 75 30.67 14.94 14.61 39.78 0.09 0.00 +GCC 76 29.79 14.84 15.43 39.93 0.09 0.00 +GCC 77 29.44 14.93 16.17 39.47 0.04 0.00 +GCC 78 29.81 15.62 16.34 38.22 0.09 0.00 +GCC 79 31.83 14.51 14.84 38.82 0.06 0.00 +GCC 80 29.94 15.88 14.58 39.60 0.09 0.00 +GCC 81 31.19 15.92 15.18 37.71 0.06 0.00 +GCC 82 31.66 16.19 15.34 36.81 0.04 0.00 +GCC 83 31.18 16.06 13.76 39.01 0.06 0.00 +GCC 84 30.96 16.06 12.89 40.10 0.04 0.00 +GCC 85 30.01 15.23 13.24 41.53 0.10 0.00 +GCC 86 30.81 15.86 14.66 38.68 0.02 0.00 +GCC 87 30.47 16.15 12.72 40.66 0.04 0.00 +GCC 88 31.11 16.50 12.35 40.04 0.02 0.00 +GCC 89 30.24 15.85 11.73 42.19 0.06 0.00 +GCC 90 31.22 14.87 12.36 41.55 0.02 0.00 +GCC 91 30.23 17.01 12.89 39.87 0.02 0.00 +GCC 92 31.36 14.43 13.43 40.78 0.06 0.00 +GCC 93 32.06 15.47 13.66 38.81 0.02 0.00 +GCC 94 32.56 14.86 14.96 37.62 0.02 0.00 +GCC 95 31.75 15.88 14.36 38.01 0.02 0.00 +GCC 96 32.25 16.62 12.71 38.43 0.02 0.00 +GCC 97 31.78 16.01 15.10 37.11 0.04 0.00 +GCC 98 31.47 15.10 15.78 37.65 0.02 0.00 +GCC 99 32.77 16.08 14.79 36.36 0.02 0.00 +GCC 100 31.18 16.81 13.53 38.48 0.00 0.00 +GCC 101 32.53 15.79 12.82 38.85 0.05 0.00 +GCC 102 34.85 14.89 13.38 36.89 0.02 0.00 +GCC 103 33.09 15.40 14.57 36.93 0.02 0.00 +GCC 104 34.25 13.60 14.78 37.37 0.00 0.00 +GCC 105 34.82 15.70 13.12 36.37 0.00 0.00 +GCC 106 36.36 14.74 13.60 35.30 0.00 0.00 +GCC 107 34.51 15.11 13.95 36.43 0.03 0.00 +GCC 108 32.09 16.88 14.37 36.65 0.05 0.00 +GCC 109 33.64 14.78 14.09 37.49 0.03 0.00 +GCC 110 35.15 14.14 15.88 34.83 0.03 0.00 +GCC 111 32.46 15.41 15.41 36.73 0.00 0.00 +GCC 112 30.38 16.47 14.49 38.66 0.03 0.00 +GCC 113 30.38 16.75 13.57 39.30 0.00 0.00 +GCC 114 31.95 16.24 15.52 36.29 0.00 0.00 +GCC 115 31.65 16.61 16.00 35.74 0.03 0.00 +GCC 116 32.63 15.92 13.73 37.72 0.03 0.00 +GCC 117 30.30 15.50 13.72 40.48 0.00 0.00 +GCC 118 31.59 15.86 13.78 38.78 0.00 0.00 +GCC 119 29.82 15.70 15.86 38.62 0.06 0.00 +GCC 120 32.51 15.42 14.86 37.21 0.00 0.00 +GCC 121 30.50 16.06 15.80 37.64 0.00 0.00 +GCC 122 31.72 16.32 15.67 36.29 0.00 0.00 +GCC 123 31.69 15.51 15.51 37.29 0.00 0.00 +GCC 124 31.50 15.71 16.74 36.04 0.00 0.00 +GCC 125 31.78 15.14 16.82 36.26 0.00 0.00 +GCC 126 33.98 16.26 14.92 34.84 0.00 0.00 +GCC 127 36.59 13.61 14.98 34.83 0.04 0.00 +GCC 128 31.58 16.32 16.04 36.07 0.04 0.00 +GCC 129 32.15 14.51 15.23 38.10 0.00 0.00 +GCC 130 32.32 14.77 14.65 38.26 0.04 0.00 +GCC 131 33.14 14.71 15.77 36.39 0.00 0.00 +GCC 132 31.94 14.32 16.61 37.13 0.00 0.00 +GCC 133 34.86 13.79 15.59 35.76 0.00 0.00 +GCC 134 32.53 15.71 18.55 33.21 0.00 0.00 +GCC 135 30.59 14.64 17.77 37.00 0.00 0.00 +GCC 136 31.60 13.15 18.79 36.46 0.00 0.00 +GCC 137 31.35 13.59 18.50 36.56 0.00 0.00 +GCC 138 33.20 12.92 20.58 33.30 0.00 0.00 +GCC 139 33.17 13.39 18.15 35.29 0.00 0.00 +GCC 140 33.84 12.65 17.74 35.76 0.00 0.00 +GCC 141 32.37 12.77 17.18 37.68 0.00 0.00 +GCC 142 33.29 12.32 17.83 36.55 0.00 0.00 +GCC 143 32.56 11.24 16.09 40.11 0.37 0.00 +# ACGT content per cycle, read oriented. Use `grep ^GCT | cut -f 2-` to extract this part. The columns are: cycle; A,C,G,T base counts as a percentage of all A/C/G/T bases [%] +GCT 1 36.58 12.84 18.48 32.10 +GCT 2 36.22 13.80 19.26 30.72 +GCT 3 37.11 13.23 17.77 31.90 +GCT 4 36.98 13.14 17.26 32.62 +GCT 5 38.07 12.38 17.78 31.77 +GCT 6 37.81 12.27 17.81 32.10 +GCT 7 36.21 12.91 18.30 32.59 +GCT 8 35.72 13.64 17.60 33.04 +GCT 9 36.34 12.08 17.90 33.68 +GCT 10 37.67 12.10 18.18 32.05 +GCT 11 36.48 12.75 17.24 33.53 +GCT 12 36.45 12.69 17.13 33.74 +GCT 13 35.19 14.35 17.58 32.88 +GCT 14 35.86 12.95 17.79 33.40 +GCT 15 36.82 14.00 18.05 31.13 +GCT 16 37.26 13.01 18.21 31.52 +GCT 17 37.51 13.08 17.46 31.96 +GCT 18 35.16 14.12 17.72 33.00 +GCT 19 35.16 14.78 16.44 33.62 +GCT 20 34.03 13.30 15.84 36.83 +GCT 21 35.12 12.75 17.15 34.98 +GCT 22 34.87 13.08 17.09 34.97 +GCT 23 34.23 13.27 16.84 35.66 +GCT 24 34.10 13.18 17.69 35.04 +GCT 25 34.81 13.03 16.52 35.64 +GCT 26 34.86 13.54 17.07 34.54 +GCT 27 34.40 13.54 17.49 34.56 +GCT 28 33.32 13.45 17.94 35.29 +GCT 29 34.23 14.47 17.99 33.31 +GCT 30 34.50 13.53 16.76 35.21 +GCT 31 35.20 13.78 17.68 33.34 +GCT 32 32.78 14.18 16.68 36.36 +GCT 33 34.98 13.09 16.48 35.45 +GCT 34 33.88 13.58 16.62 35.92 +GCT 35 33.39 14.11 16.71 35.79 +GCT 36 32.46 13.61 17.32 36.60 +GCT 37 34.27 12.07 16.60 37.06 +GCT 38 34.06 13.54 18.10 34.30 +GCT 39 33.98 13.46 17.40 35.16 +GCT 40 33.55 15.02 16.92 34.52 +GCT 41 35.20 14.57 15.90 34.33 +GCT 42 34.00 13.72 16.40 35.88 +GCT 43 34.03 14.00 16.10 35.87 +GCT 44 34.12 14.23 15.74 35.91 +GCT 45 35.14 14.83 15.17 34.86 +GCT 46 34.12 15.13 15.27 35.49 +GCT 47 34.39 14.60 15.38 35.62 +GCT 48 34.99 14.97 15.06 34.97 +GCT 49 35.63 14.56 15.64 34.17 +GCT 50 36.43 14.61 15.93 33.03 +GCT 51 34.32 15.65 16.27 33.77 +GCT 52 35.36 16.62 15.63 32.39 +GCT 53 35.01 15.21 17.11 32.68 +GCT 54 35.63 15.93 15.77 32.67 +GCT 55 33.93 16.85 17.04 32.18 +GCT 56 36.03 15.86 16.08 32.03 +GCT 57 35.72 15.46 15.79 33.03 +GCT 58 36.44 14.57 14.96 34.02 +GCT 59 34.96 16.46 14.85 33.73 +GCT 60 34.75 16.61 15.22 33.42 +GCT 61 34.73 16.81 15.33 33.13 +GCT 62 36.49 15.41 16.07 32.04 +GCT 63 35.69 14.65 15.44 34.22 +GCT 64 33.57 15.32 16.17 34.94 +GCT 65 35.42 15.12 16.36 33.09 +GCT 66 34.43 15.55 16.19 33.82 +GCT 67 36.56 16.42 14.86 32.15 +GCT 68 35.24 16.51 15.25 33.00 +GCT 69 35.89 16.19 15.70 32.22 +GCT 70 35.74 15.39 16.31 32.56 +GCT 71 35.12 15.48 16.55 32.86 +GCT 72 36.28 16.36 15.71 31.65 +GCT 73 36.49 15.27 15.12 33.12 +GCT 74 36.00 15.06 15.06 33.88 +GCT 75 35.02 14.25 15.31 35.42 +GCT 76 33.67 15.61 14.66 36.06 +GCT 77 33.99 15.78 15.32 34.91 +GCT 78 34.47 16.31 15.66 33.56 +GCT 79 35.53 15.03 14.32 35.12 +GCT 80 34.22 16.55 13.91 35.32 +GCT 81 34.06 16.94 14.16 34.84 +GCT 82 33.49 16.12 15.41 34.98 +GCT 83 33.37 16.39 13.43 36.82 +GCT 84 34.51 16.41 12.54 36.54 +GCT 85 35.36 15.54 12.92 36.18 +GCT 86 35.87 16.56 13.95 33.61 +GCT 87 36.14 16.15 12.72 34.99 +GCT 88 34.86 15.78 13.07 36.29 +GCT 89 36.49 15.48 12.09 35.94 +GCT 90 35.66 14.38 12.85 37.11 +GCT 91 34.51 16.17 13.74 35.58 +GCT 92 36.35 15.74 12.12 35.79 +GCT 93 37.04 15.83 13.30 33.82 +GCT 94 36.03 16.49 13.33 34.16 +GCT 95 35.13 15.86 14.38 34.62 +GCT 96 36.29 15.90 13.43 34.39 +GCT 97 34.38 16.04 15.07 34.51 +GCT 98 34.97 16.60 14.28 34.15 +GCT 99 35.63 17.13 13.73 33.51 +GCT 100 35.04 17.97 12.37 34.62 +GCT 101 36.07 16.41 12.21 35.31 +GCT 102 35.95 15.95 12.32 35.78 +GCT 103 35.69 16.42 13.55 34.33 +GCT 104 36.71 15.83 12.54 34.92 +GCT 105 35.89 16.97 11.85 35.29 +GCT 106 37.78 16.00 12.33 33.88 +GCT 107 34.71 16.75 12.31 36.23 +GCT 108 33.61 16.91 14.35 35.13 +GCT 109 33.72 15.23 13.64 37.41 +GCT 110 33.73 16.96 13.06 36.25 +GCT 111 33.42 18.23 12.59 35.77 +GCT 112 33.35 17.52 13.44 35.69 +GCT 113 33.19 16.78 13.54 36.49 +GCT 114 31.64 17.33 14.43 36.61 +GCT 115 31.76 18.46 14.16 35.62 +GCT 116 33.38 16.35 13.31 36.97 +GCT 117 31.41 16.15 13.07 39.37 +GCT 118 34.41 16.99 12.65 35.95 +GCT 119 32.94 19.20 12.36 35.51 +GCT 120 33.20 17.23 13.05 36.52 +GCT 121 32.36 18.62 13.24 35.78 +GCT 122 31.69 18.88 13.12 36.32 +GCT 123 30.82 18.61 12.42 38.16 +GCT 124 30.51 19.76 12.70 37.03 +GCT 125 32.33 18.89 13.07 35.71 +GCT 126 31.78 18.09 13.08 37.05 +GCT 127 35.79 16.65 11.93 35.63 +GCT 128 31.73 17.56 14.79 35.91 +GCT 129 32.59 17.07 12.67 37.66 +GCT 130 33.80 17.72 11.70 36.78 +GCT 131 34.32 17.75 12.72 35.21 +GCT 132 33.96 18.59 12.35 35.11 +GCT 133 34.23 15.86 13.52 36.39 +GCT 134 30.65 19.64 14.62 35.08 +GCT 135 32.13 18.62 13.80 35.45 +GCT 136 30.97 18.93 13.01 37.09 +GCT 137 30.41 17.96 14.14 37.50 +GCT 138 30.75 20.17 13.33 35.75 +GCT 139 32.65 18.89 12.65 35.82 +GCT 140 33.73 18.57 11.83 35.87 +GCT 141 32.09 16.33 13.62 37.97 +GCT 142 32.41 16.47 13.68 37.44 +GCT 143 32.43 16.71 10.63 40.23 +# ACGT content per cycle for first fragments. Use `grep ^FBC | cut -f 2-` to extract this part. The columns are: cycle; A,C,G,T base counts as a percentage of all A/C/G/T bases [%]; and N and O counts as a percentage of all A/C/G/T bases [%] +FBC 1 34.09 15.25 16.11 34.55 0.07 0.00 +FBC 2 33.51 15.96 16.74 33.79 0.04 0.00 +FBC 3 34.70 14.46 15.92 34.92 0.00 0.00 +FBC 4 34.08 15.92 14.79 35.21 0.04 0.00 +FBC 5 34.42 15.38 15.03 35.16 0.00 0.00 +FBC 6 34.72 14.72 15.14 35.43 0.04 0.00 +FBC 7 34.46 15.88 15.31 34.35 0.00 0.00 +FBC 8 33.88 15.04 15.75 35.33 0.07 0.00 +FBC 9 34.04 14.61 15.78 35.57 0.04 0.00 +FBC 10 34.82 13.16 16.95 35.07 0.04 0.00 +FBC 11 34.10 14.14 15.60 36.16 0.00 0.00 +FBC 12 34.68 13.35 16.19 35.78 0.14 0.00 +FBC 13 33.81 14.40 17.81 33.98 0.07 0.00 +FBC 14 34.11 13.40 17.48 35.00 0.04 0.00 +FBC 15 33.33 15.55 16.68 34.43 0.14 0.00 +FBC 16 34.22 14.52 17.32 33.94 0.14 0.00 +FBC 17 32.82 13.27 16.64 37.26 0.11 0.00 +FBC 18 33.71 15.08 17.28 33.92 0.11 0.00 +FBC 19 32.67 14.83 16.42 36.08 0.07 0.00 +FBC 20 32.74 14.44 15.04 37.78 0.07 0.00 +FBC 21 32.29 13.97 16.06 37.68 0.00 0.00 +FBC 22 32.82 13.38 17.28 36.52 0.11 0.00 +FBC 23 33.43 12.53 16.93 37.12 0.11 0.00 +FBC 24 33.63 14.51 16.28 35.58 0.07 0.00 +FBC 25 34.69 13.99 15.06 36.26 0.18 0.00 +FBC 26 35.15 15.40 15.29 34.16 0.07 0.00 +FBC 27 34.00 14.58 16.11 35.31 0.11 0.00 +FBC 28 33.45 14.60 17.37 34.59 0.18 0.00 +FBC 29 32.27 15.21 18.12 34.40 0.04 0.00 +FBC 30 34.18 14.08 15.89 35.85 0.04 0.00 +FBC 31 31.26 15.10 16.91 36.73 0.18 0.00 +FBC 32 33.51 15.26 15.16 36.07 0.11 0.00 +FBC 33 33.03 15.32 14.54 37.11 0.25 0.00 +FBC 34 30.80 14.42 15.63 39.15 0.18 0.00 +FBC 35 31.00 14.10 17.86 37.04 0.14 0.00 +FBC 36 33.46 13.80 16.75 35.98 0.07 0.00 +FBC 37 35.46 13.38 15.19 35.96 0.11 0.00 +FBC 38 33.43 14.98 16.11 35.49 0.07 0.00 +FBC 39 32.84 14.48 16.90 35.78 0.11 0.00 +FBC 40 33.19 14.16 17.96 34.68 0.11 0.00 +FBC 41 32.13 13.99 16.79 37.10 0.11 0.00 +FBC 42 32.03 15.48 14.42 38.07 0.11 0.00 +FBC 43 33.26 14.41 15.65 36.67 0.07 0.00 +FBC 44 33.84 13.58 15.85 36.72 0.21 0.00 +FBC 45 34.60 14.71 15.52 35.17 0.14 0.00 +FBC 46 32.88 14.74 15.98 36.40 0.07 0.00 +FBC 47 32.79 14.83 15.40 36.98 0.21 0.00 +FBC 48 33.99 14.54 15.71 35.76 0.18 0.00 +FBC 49 33.45 13.96 16.26 36.33 0.07 0.00 +FBC 50 33.69 14.62 16.08 35.61 0.14 0.00 +FBC 51 32.16 16.15 15.44 36.25 0.14 0.00 +FBC 52 30.94 14.69 16.75 37.62 0.07 0.00 +FBC 53 32.23 15.12 17.22 35.43 0.04 0.00 +FBC 54 32.55 15.38 16.52 35.54 0.14 0.00 +FBC 55 30.94 17.04 17.65 34.37 0.21 0.00 +FBC 56 31.92 15.18 17.10 35.80 0.14 0.00 +FBC 57 31.60 14.82 15.96 37.62 0.14 0.00 +FBC 58 33.31 14.59 14.87 37.23 0.25 0.00 +FBC 59 31.90 15.29 16.25 36.56 0.14 0.00 +FBC 60 29.60 15.34 17.19 37.87 0.14 0.00 +FBC 61 30.86 15.13 16.77 37.25 0.14 0.00 +FBC 62 31.18 16.18 15.79 36.86 0.11 0.00 +FBC 63 33.06 14.31 15.67 36.96 0.25 0.00 +FBC 64 30.43 15.11 16.65 37.81 0.14 0.00 +FBC 65 30.88 14.73 16.34 38.05 0.21 0.00 +FBC 66 30.22 16.45 15.38 37.96 0.07 0.00 +FBC 67 31.04 16.65 14.68 37.64 0.11 0.00 +FBC 68 31.15 14.73 17.21 36.90 0.04 0.00 +FBC 69 29.94 16.05 16.23 37.78 0.07 0.00 +FBC 70 29.91 15.10 16.43 38.56 0.04 0.00 +FBC 71 30.05 15.37 16.24 38.34 0.04 0.00 +FBC 72 30.68 16.45 16.19 36.67 0.04 0.00 +FBC 73 29.85 15.33 15.36 39.46 0.04 0.00 +FBC 74 30.33 15.04 15.36 39.27 0.00 0.00 +FBC 75 31.03 15.04 14.16 39.77 0.11 0.00 +FBC 76 30.10 14.81 15.21 39.88 0.11 0.00 +FBC 77 28.60 14.89 15.85 40.66 0.00 0.00 +FBC 78 29.78 15.54 16.28 38.40 0.15 0.00 +FBC 79 31.34 14.87 15.09 38.70 0.07 0.00 +FBC 80 29.67 15.62 14.91 39.80 0.11 0.00 +FBC 81 30.87 16.55 15.45 37.14 0.08 0.00 +FBC 82 31.50 16.42 15.39 36.69 0.00 0.00 +FBC 83 30.02 16.62 14.13 39.23 0.08 0.00 +FBC 84 30.85 15.31 13.18 40.66 0.04 0.00 +FBC 85 30.17 15.47 12.93 41.42 0.16 0.00 +FBC 86 31.37 15.78 14.76 38.08 0.04 0.00 +FBC 87 30.56 16.41 12.64 40.39 0.04 0.00 +FBC 88 30.67 16.76 13.03 39.53 0.00 0.00 +FBC 89 29.71 16.27 11.77 42.25 0.04 0.00 +FBC 90 31.34 14.16 12.48 42.02 0.00 0.00 +FBC 91 30.60 16.14 12.93 40.32 0.00 0.00 +FBC 92 30.78 14.52 13.14 41.56 0.04 0.00 +FBC 93 32.80 15.03 13.81 38.36 0.00 0.00 +FBC 94 32.91 14.97 14.75 37.37 0.00 0.00 +FBC 95 32.13 15.79 14.71 37.37 0.04 0.00 +FBC 96 31.62 16.77 13.01 38.60 0.00 0.00 +FBC 97 32.07 15.65 15.47 36.81 0.00 0.00 +FBC 98 32.11 14.85 15.80 37.24 0.00 0.00 +FBC 99 33.12 15.64 15.32 35.92 0.00 0.00 +FBC 100 30.84 16.23 13.95 38.98 0.00 0.00 +FBC 101 33.24 15.62 12.49 38.65 0.00 0.00 +FBC 102 34.92 14.41 13.59 37.08 0.00 0.00 +FBC 103 32.65 15.33 14.94 37.08 0.00 0.00 +FBC 104 34.58 13.08 14.46 37.88 0.00 0.00 +FBC 105 34.40 15.80 13.45 36.35 0.00 0.00 +FBC 106 35.66 15.10 13.22 36.02 0.00 0.00 +FBC 107 34.69 15.42 13.87 36.02 0.00 0.00 +FBC 108 31.83 17.43 14.35 36.39 0.00 0.00 +FBC 109 32.91 15.23 13.91 37.95 0.00 0.00 +FBC 110 35.22 13.71 15.48 35.59 0.00 0.00 +FBC 111 32.48 15.28 14.95 37.29 0.00 0.00 +FBC 112 30.55 16.69 14.75 38.01 0.00 0.00 +FBC 113 30.42 17.35 14.25 37.97 0.00 0.00 +FBC 114 32.26 16.85 15.53 35.37 0.00 0.00 +FBC 115 31.07 17.14 15.68 36.10 0.00 0.00 +FBC 116 32.41 16.39 13.31 37.89 0.00 0.00 +FBC 117 30.23 15.83 13.61 40.33 0.00 0.00 +FBC 118 30.92 15.96 13.64 39.47 0.00 0.00 +FBC 119 29.50 16.07 15.55 38.88 0.06 0.00 +FBC 120 33.18 14.88 15.08 36.87 0.00 0.00 +FBC 121 30.76 16.15 16.15 36.94 0.00 0.00 +FBC 122 31.86 16.71 15.69 35.74 0.00 0.00 +FBC 123 32.17 15.53 14.97 37.33 0.00 0.00 +FBC 124 31.68 15.84 16.48 36.01 0.00 0.00 +FBC 125 32.43 14.87 16.69 36.01 0.00 0.00 +FBC 126 35.10 15.72 14.45 34.73 0.00 0.00 +FBC 127 35.80 13.82 15.11 35.27 0.00 0.00 +FBC 128 32.06 16.34 15.79 35.81 0.00 0.00 +FBC 129 32.05 14.34 15.54 38.06 0.00 0.00 +FBC 130 31.15 14.75 14.75 39.34 0.00 0.00 +FBC 131 32.63 14.15 16.36 36.86 0.00 0.00 +FBC 132 31.81 14.54 16.48 37.18 0.00 0.00 +FBC 133 35.14 13.69 15.23 35.95 0.00 0.00 +FBC 134 31.78 16.30 18.50 33.42 0.00 0.00 +FBC 135 30.21 14.35 17.54 37.90 0.00 0.00 +FBC 136 31.59 12.75 18.94 36.71 0.00 0.00 +FBC 137 30.85 14.23 19.60 35.32 0.00 0.00 +FBC 138 33.71 13.11 20.80 32.38 0.00 0.00 +FBC 139 33.12 13.06 18.37 35.46 0.00 0.00 +FBC 140 33.74 12.75 17.47 36.04 0.00 0.00 +FBC 141 32.65 13.61 17.35 36.39 0.00 0.00 +FBC 142 31.63 12.84 19.02 36.50 0.00 0.00 +FBC 143 31.49 11.32 16.11 41.08 0.12 0.00 +# ACGT raw counters for first fragments. Use `grep ^FTC | cut -f 2-` to extract this part. The columns are: A,C,G,T,N base counters +FTC 108689 50470 52527 124018 240 +# ACGT content per cycle for last fragments. Use `grep ^LBC | cut -f 2-` to extract this part. The columns are: cycle; A,C,G,T base counts as a percentage of all A/C/G/T bases [%]; and N and O counts as a percentage of all A/C/G/T bases [%] +LBC 1 33.37 15.21 16.06 35.35 0.04 0.00 +LBC 2 33.00 15.05 18.38 33.57 0.11 0.00 +LBC 3 34.48 16.03 15.57 33.91 0.07 0.00 +LBC 4 34.42 15.26 14.83 35.49 0.11 0.00 +LBC 5 33.66 15.64 14.26 36.43 0.07 0.00 +LBC 6 34.39 15.65 14.66 35.31 0.11 0.00 +LBC 7 33.13 16.11 15.11 35.65 0.07 0.00 +LBC 8 32.30 15.44 16.26 36.00 0.14 0.00 +LBC 9 34.85 13.84 15.72 35.59 0.11 0.00 +LBC 10 34.39 14.34 16.11 35.17 0.11 0.00 +LBC 11 34.56 14.09 16.15 35.20 0.11 0.00 +LBC 12 33.50 13.91 16.18 36.41 0.11 0.00 +LBC 13 33.66 13.16 18.48 34.69 0.07 0.00 +LBC 14 33.71 13.41 17.18 35.70 0.11 0.00 +LBC 15 33.53 14.66 17.21 34.60 0.11 0.00 +LBC 16 35.78 14.77 15.83 33.62 0.14 0.00 +LBC 17 33.61 14.51 16.64 35.24 0.11 0.00 +LBC 18 33.35 13.80 17.52 35.33 0.07 0.00 +LBC 19 32.97 14.73 16.47 35.84 0.11 0.00 +LBC 20 33.07 13.59 15.22 38.11 0.11 0.00 +LBC 21 32.66 14.06 15.73 37.56 0.14 0.00 +LBC 22 33.57 12.92 16.75 36.76 0.11 0.00 +LBC 23 33.32 13.77 17.00 35.91 0.11 0.00 +LBC 24 33.22 14.12 16.82 35.84 0.11 0.00 +LBC 25 34.81 14.34 15.72 35.13 0.11 0.00 +LBC 26 34.39 14.09 16.43 35.10 0.11 0.00 +LBC 27 34.88 14.66 16.71 33.75 0.11 0.00 +LBC 28 34.17 15.01 15.83 34.99 0.11 0.00 +LBC 29 33.29 15.01 16.57 35.13 0.11 0.00 +LBC 30 33.00 13.95 16.68 36.37 0.11 0.00 +LBC 31 31.76 14.90 16.00 37.33 0.11 0.00 +LBC 32 32.19 15.40 15.90 36.52 0.11 0.00 +LBC 33 33.04 14.34 14.94 37.69 0.11 0.00 +LBC 34 32.30 15.12 15.23 37.34 0.11 0.00 +LBC 35 32.23 13.31 16.36 38.09 0.11 0.00 +LBC 36 33.24 14.17 17.15 35.44 0.14 0.00 +LBC 37 34.40 13.35 15.41 36.85 0.11 0.00 +LBC 38 32.48 15.73 16.47 35.32 0.11 0.00 +LBC 39 32.82 14.35 15.99 36.84 0.18 0.00 +LBC 40 31.88 14.77 16.97 36.39 0.11 0.00 +LBC 41 33.51 14.02 16.15 36.32 0.11 0.00 +LBC 42 32.13 14.84 15.51 37.52 0.11 0.00 +LBC 43 31.81 14.38 15.76 38.05 0.11 0.00 +LBC 44 32.80 13.99 16.51 36.71 0.11 0.00 +LBC 45 35.26 14.88 14.88 34.98 0.14 0.00 +LBC 46 33.72 14.24 15.83 36.21 0.11 0.00 +LBC 47 33.76 13.99 15.76 36.49 0.11 0.00 +LBC 48 33.23 13.35 16.47 36.95 0.11 0.00 +LBC 49 33.90 14.24 15.94 35.92 0.11 0.00 +LBC 50 35.11 14.43 15.96 34.51 0.11 0.00 +LBC 51 32.31 16.46 15.78 35.44 0.14 0.00 +LBC 52 30.43 14.90 18.17 36.51 0.11 0.00 +LBC 53 32.02 15.12 17.18 35.68 0.11 0.00 +LBC 54 32.44 14.69 16.79 36.07 0.11 0.00 +LBC 55 31.07 16.12 16.98 35.84 0.11 0.00 +LBC 56 31.74 15.41 16.19 36.65 0.11 0.00 +LBC 57 31.36 16.02 15.70 36.92 0.14 0.00 +LBC 58 32.74 14.45 15.16 37.65 0.11 0.00 +LBC 59 31.19 14.20 16.87 37.74 0.11 0.00 +LBC 60 30.46 14.57 16.57 38.40 0.11 0.00 +LBC 61 30.61 16.14 16.25 36.99 0.11 0.00 +LBC 62 32.62 16.13 14.85 36.40 0.11 0.00 +LBC 63 31.74 14.74 15.46 38.06 0.11 0.00 +LBC 64 30.29 15.02 16.20 38.48 0.11 0.00 +LBC 65 30.51 15.06 16.85 37.59 0.11 0.00 +LBC 66 30.87 16.15 15.51 37.46 0.07 0.00 +LBC 67 30.43 16.45 14.80 38.32 0.07 0.00 +LBC 68 30.82 14.87 16.70 37.61 0.07 0.00 +LBC 69 30.00 15.54 15.97 38.49 0.07 0.00 +LBC 70 29.85 15.43 16.44 38.28 0.11 0.00 +LBC 71 29.73 16.27 16.17 37.83 0.07 0.00 +LBC 72 31.86 16.56 14.93 36.65 0.11 0.00 +LBC 73 30.90 15.21 14.88 39.01 0.04 0.00 +LBC 74 29.84 14.39 15.45 40.32 0.11 0.00 +LBC 75 30.31 14.84 15.06 39.78 0.07 0.00 +LBC 76 29.49 14.87 15.64 39.99 0.07 0.00 +LBC 77 30.27 14.97 16.48 38.27 0.07 0.00 +LBC 78 29.84 15.70 16.41 38.05 0.04 0.00 +LBC 79 32.33 14.15 14.59 38.93 0.04 0.00 +LBC 80 30.21 16.14 14.26 39.40 0.08 0.00 +LBC 81 31.52 15.29 14.91 38.28 0.04 0.00 +LBC 82 31.82 15.97 15.28 36.93 0.08 0.00 +LBC 83 32.34 15.50 13.39 38.78 0.04 0.00 +LBC 84 31.07 16.81 12.60 39.53 0.04 0.00 +LBC 85 29.85 14.98 13.54 41.63 0.04 0.00 +LBC 86 30.25 15.93 14.55 39.27 0.00 0.00 +LBC 87 30.39 15.89 12.80 40.93 0.04 0.00 +LBC 88 31.56 16.24 11.67 40.54 0.04 0.00 +LBC 89 30.76 15.42 11.69 42.13 0.08 0.00 +LBC 90 31.10 15.59 12.23 41.08 0.04 0.00 +LBC 91 29.86 17.87 12.85 39.42 0.04 0.00 +LBC 92 31.93 14.35 13.72 40.00 0.08 0.00 +LBC 93 31.31 15.91 13.51 39.27 0.04 0.00 +LBC 94 32.21 14.75 15.17 37.87 0.04 0.00 +LBC 95 31.36 15.98 14.01 38.65 0.00 0.00 +LBC 96 32.88 16.46 12.40 38.25 0.04 0.00 +LBC 97 31.50 16.38 14.72 37.40 0.09 0.00 +LBC 98 30.84 15.35 15.76 38.06 0.05 0.00 +LBC 99 32.43 16.51 14.26 36.80 0.05 0.00 +LBC 100 31.52 17.39 13.11 37.98 0.00 0.00 +LBC 101 31.83 15.96 13.16 39.05 0.10 0.00 +LBC 102 34.77 15.37 13.16 36.70 0.05 0.00 +LBC 103 33.53 15.47 14.21 36.79 0.05 0.00 +LBC 104 33.92 14.11 15.09 36.87 0.00 0.00 +LBC 105 35.23 15.59 12.79 36.38 0.00 0.00 +LBC 106 37.06 14.38 13.97 34.58 0.00 0.00 +LBC 107 34.33 14.80 14.03 36.84 0.05 0.00 +LBC 108 32.36 16.34 14.40 36.91 0.10 0.00 +LBC 109 34.38 14.32 14.27 37.03 0.05 0.00 +LBC 110 35.09 14.56 16.28 34.07 0.05 0.00 +LBC 111 32.44 15.54 15.86 36.16 0.00 0.00 +LBC 112 30.20 16.24 14.24 39.32 0.06 0.00 +LBC 113 30.33 16.15 12.89 40.63 0.00 0.00 +LBC 114 31.65 15.62 15.51 37.22 0.00 0.00 +LBC 115 32.22 16.08 16.32 35.38 0.06 0.00 +LBC 116 32.85 15.46 14.14 37.55 0.06 0.00 +LBC 117 30.36 15.18 13.83 40.63 0.00 0.00 +LBC 118 32.25 15.75 13.93 38.08 0.00 0.00 +LBC 119 30.15 15.33 16.16 38.36 0.06 0.00 +LBC 120 31.85 15.96 14.64 37.56 0.00 0.00 +LBC 121 30.24 15.98 15.45 38.33 0.00 0.00 +LBC 122 31.59 15.93 15.66 36.83 0.00 0.00 +LBC 123 31.20 15.50 16.05 37.25 0.00 0.00 +LBC 124 31.33 15.59 17.01 36.07 0.00 0.00 +LBC 125 31.13 15.42 16.95 36.51 0.00 0.00 +LBC 126 32.86 16.80 15.38 34.95 0.00 0.00 +LBC 127 37.37 13.39 14.84 34.40 0.08 0.00 +LBC 128 31.10 16.29 16.29 36.32 0.08 0.00 +LBC 129 32.24 14.68 14.92 38.15 0.00 0.00 +LBC 130 33.50 14.79 14.54 37.17 0.08 0.00 +LBC 131 33.64 15.26 15.18 35.92 0.00 0.00 +LBC 132 32.08 14.11 16.74 37.07 0.00 0.00 +LBC 133 34.59 13.89 15.95 35.57 0.00 0.00 +LBC 134 33.27 15.13 18.60 33.00 0.00 0.00 +LBC 135 30.97 14.93 18.00 36.10 0.00 0.00 +LBC 136 31.60 13.54 18.64 36.22 0.00 0.00 +LBC 137 31.85 12.96 17.41 37.78 0.00 0.00 +LBC 138 32.69 12.73 20.37 34.22 0.00 0.00 +LBC 139 33.23 13.71 17.93 35.13 0.00 0.00 +LBC 140 33.95 12.55 18.01 35.48 0.00 0.00 +LBC 141 32.09 11.94 17.00 38.96 0.00 0.00 +LBC 142 34.95 11.81 16.65 36.60 0.00 0.00 +LBC 143 33.62 11.17 16.07 39.14 0.61 0.00 +# ACGT raw counters for last fragments. Use `grep ^LTC | cut -f 2-` to extract this part. The columns are: A,C,G,T,N base counters +LTC 108882 50371 52310 124355 269 +# Insert sizes. Use `grep ^IS | cut -f 2-` to extract this part. The columns are: insert size, pairs total, inward oriented pairs, outward oriented pairs, other pairs +IS 0 0 0 0 0 +IS 1 0 0 0 0 +IS 2 0 0 0 0 +IS 3 0 0 0 0 +IS 4 0 0 0 0 +IS 5 0 0 0 0 +IS 6 0 0 0 0 +IS 7 0 0 0 0 +IS 8 0 0 0 0 +IS 9 0 0 0 0 +IS 10 0 0 0 0 +IS 11 0 0 0 0 +IS 12 0 0 0 0 +IS 13 0 0 0 0 +IS 14 0 0 0 0 +IS 15 0 0 0 0 +IS 16 0 0 0 0 +IS 17 0 0 0 0 +IS 18 0 0 0 0 +IS 19 0 0 0 0 +IS 20 0 0 0 0 +IS 21 0 0 0 0 +IS 22 0 0 0 0 +IS 23 0 0 0 0 +IS 24 0 0 0 0 +IS 25 0 0 0 0 +IS 26 0 0 0 0 +IS 27 0 0 0 0 +IS 28 0 0 0 0 +IS 29 0 0 0 0 +IS 30 0 0 0 0 +IS 31 0 0 0 0 +IS 32 1 0 1 0 +IS 33 0 0 0 0 +IS 34 0 0 0 0 +IS 35 0 0 0 0 +IS 36 0 0 0 0 +IS 37 0 0 0 0 +IS 38 0 0 0 0 +IS 39 0 0 0 0 +IS 40 0 0 0 0 +IS 41 1 1 0 0 +IS 42 0 0 0 0 +IS 43 0 0 0 0 +IS 44 0 0 0 0 +IS 45 0 0 0 0 +IS 46 0 0 0 0 +IS 47 0 0 0 0 +IS 48 0 0 0 0 +IS 49 3 3 0 0 +IS 50 0 0 0 0 +IS 51 1 1 0 0 +IS 52 2 2 0 0 +IS 53 0 0 0 0 +IS 54 1 1 0 0 +IS 55 0 0 0 0 +IS 56 0 0 0 0 +IS 57 0 0 0 0 +IS 58 1 1 0 0 +IS 59 2 2 0 0 +IS 60 1 1 0 0 +IS 61 4 4 0 0 +IS 62 1 1 0 0 +IS 63 5 5 0 0 +IS 64 0 0 0 0 +IS 65 5 5 0 0 +IS 66 2 2 0 0 +IS 67 6 6 0 0 +IS 68 3 3 0 0 +IS 69 5 5 0 0 +IS 70 10 10 0 0 +IS 71 11 11 0 0 +IS 72 7 7 0 0 +IS 73 8 8 0 0 +IS 74 4 4 0 0 +IS 75 12 12 0 0 +IS 76 11 11 0 0 +IS 77 19 19 0 0 +IS 78 15 15 0 0 +IS 79 13 13 0 0 +IS 80 17 17 0 0 +IS 81 24 24 0 0 +IS 82 18 18 0 0 +IS 83 19 19 0 0 +IS 84 25 25 0 0 +IS 85 15 15 0 0 +IS 86 24 24 0 0 +IS 87 30 30 0 0 +IS 88 29 29 0 0 +IS 89 21 21 0 0 +IS 90 16 16 0 0 +IS 91 24 24 0 0 +IS 92 30 30 0 0 +IS 93 23 23 0 0 +IS 94 21 20 1 0 +IS 95 43 43 0 0 +IS 96 54 54 0 0 +IS 97 34 34 0 0 +IS 98 28 28 0 0 +IS 99 24 24 0 0 +IS 100 44 44 0 0 +IS 101 24 24 0 0 +IS 102 27 27 0 0 +IS 103 22 22 0 0 +IS 104 33 33 0 0 +IS 105 26 26 0 0 +IS 106 28 28 0 0 +IS 107 35 35 0 0 +IS 108 26 26 0 0 +IS 109 24 24 0 0 +IS 110 34 34 0 0 +IS 111 29 29 0 0 +IS 112 22 22 0 0 +IS 113 36 36 0 0 +IS 114 30 30 0 0 +IS 115 49 49 0 0 +IS 116 36 35 1 0 +IS 117 33 33 0 0 +IS 118 34 34 0 0 +IS 119 38 38 0 0 +IS 120 14 14 0 0 +IS 121 39 39 0 0 +IS 122 30 30 0 0 +IS 123 28 28 0 0 +IS 124 36 35 1 0 +IS 125 36 36 0 0 +IS 126 25 25 0 0 +IS 127 32 32 0 0 +IS 128 31 31 0 0 +IS 129 28 28 0 0 +IS 130 39 39 0 0 +IS 131 45 44 1 0 +IS 132 25 25 0 0 +IS 133 18 18 0 0 +IS 134 25 25 0 0 +IS 135 31 31 0 0 +IS 136 30 29 1 0 +IS 137 29 29 0 0 +IS 138 34 34 0 0 +IS 139 32 32 0 0 +IS 140 28 28 0 0 +IS 141 41 41 0 0 +IS 142 27 27 0 0 +IS 143 23 23 0 0 +IS 144 26 26 0 0 +IS 145 31 31 0 0 +IS 146 21 21 0 0 +IS 147 29 29 0 0 +IS 148 18 18 0 0 +IS 149 17 17 0 0 +IS 150 19 19 0 0 +IS 151 20 20 0 0 +IS 152 28 28 0 0 +IS 153 28 28 0 0 +IS 154 18 18 0 0 +IS 155 23 23 0 0 +IS 156 20 20 0 0 +IS 157 29 29 0 0 +IS 158 16 16 0 0 +IS 159 15 15 0 0 +IS 160 14 14 0 0 +IS 161 18 18 0 0 +IS 162 19 19 0 0 +IS 163 15 15 0 0 +IS 164 9 9 0 0 +IS 165 11 11 0 0 +IS 166 21 21 0 0 +IS 167 9 9 0 0 +IS 168 17 17 0 0 +IS 169 16 16 0 0 +IS 170 17 17 0 0 +IS 171 13 13 0 0 +IS 172 14 14 0 0 +IS 173 21 21 0 0 +IS 174 9 9 0 0 +IS 175 9 9 0 0 +IS 176 7 7 0 0 +IS 177 9 9 0 0 +IS 178 9 9 0 0 +IS 179 9 9 0 0 +IS 180 2 2 0 0 +IS 181 8 8 0 0 +IS 182 8 8 0 0 +IS 183 3 3 0 0 +IS 184 12 12 0 0 +IS 185 10 10 0 0 +IS 186 5 5 0 0 +IS 187 7 7 0 0 +IS 188 1 1 0 0 +IS 189 5 5 0 0 +IS 190 8 8 0 0 +IS 191 10 10 0 0 +IS 192 8 8 0 0 +IS 193 2 2 0 0 +IS 194 6 6 0 0 +IS 195 1 1 0 0 +IS 196 2 2 0 0 +IS 197 3 3 0 0 +IS 198 2 2 0 0 +IS 199 4 4 0 0 +IS 200 7 7 0 0 +IS 201 2 2 0 0 +IS 202 6 6 0 0 +IS 203 4 4 0 0 +IS 204 4 4 0 0 +IS 205 2 2 0 0 +IS 206 4 4 0 0 +IS 207 4 4 0 0 +# Read lengths. Use `grep ^RL | cut -f 2-` to extract this part. The columns are: read length, count +RL 30 1 +RL 33 1 +RL 41 1 +RL 45 1 +RL 49 6 +RL 51 2 +RL 52 4 +RL 54 2 +RL 58 2 +RL 59 4 +RL 60 2 +RL 61 8 +RL 62 2 +RL 63 10 +RL 65 10 +RL 66 4 +RL 67 12 +RL 68 7 +RL 69 10 +RL 70 20 +RL 71 22 +RL 72 14 +RL 73 16 +RL 74 8 +RL 75 24 +RL 76 22 +RL 77 38 +RL 78 30 +RL 79 26 +RL 80 34 +RL 81 48 +RL 82 36 +RL 83 38 +RL 84 50 +RL 85 30 +RL 86 48 +RL 87 60 +RL 88 58 +RL 89 42 +RL 90 32 +RL 91 45 +RL 92 60 +RL 93 46 +RL 94 40 +RL 95 85 +RL 96 108 +RL 97 68 +RL 98 56 +RL 99 48 +RL 100 88 +RL 101 48 +RL 102 54 +RL 103 44 +RL 104 66 +RL 105 52 +RL 106 56 +RL 107 71 +RL 108 52 +RL 109 48 +RL 110 68 +RL 111 58 +RL 112 44 +RL 113 72 +RL 114 60 +RL 115 97 +RL 116 72 +RL 117 66 +RL 118 68 +RL 119 75 +RL 120 29 +RL 121 78 +RL 122 60 +RL 123 56 +RL 124 72 +RL 125 72 +RL 126 50 +RL 127 62 +RL 128 62 +RL 129 56 +RL 130 79 +RL 131 90 +RL 132 50 +RL 133 37 +RL 134 51 +RL 135 62 +RL 136 60 +RL 137 58 +RL 138 68 +RL 139 64 +RL 140 56 +RL 141 82 +RL 142 54 +RL 143 1634 +# Read lengths - first fragments. Use `grep ^FRL | cut -f 2-` to extract this part. The columns are: read length, count +FRL 30 1 +FRL 41 1 +FRL 45 1 +FRL 49 3 +FRL 51 1 +FRL 52 2 +FRL 54 1 +FRL 58 1 +FRL 59 2 +FRL 60 1 +FRL 61 4 +FRL 62 1 +FRL 63 5 +FRL 65 5 +FRL 66 2 +FRL 67 6 +FRL 68 3 +FRL 69 5 +FRL 70 10 +FRL 71 11 +FRL 72 7 +FRL 73 8 +FRL 74 4 +FRL 75 12 +FRL 76 11 +FRL 77 19 +FRL 78 15 +FRL 79 13 +FRL 80 17 +FRL 81 24 +FRL 82 18 +FRL 83 19 +FRL 84 25 +FRL 85 15 +FRL 86 24 +FRL 87 30 +FRL 88 29 +FRL 89 21 +FRL 90 16 +FRL 91 23 +FRL 92 30 +FRL 93 23 +FRL 94 20 +FRL 95 42 +FRL 96 54 +FRL 97 34 +FRL 98 28 +FRL 99 24 +FRL 100 44 +FRL 101 24 +FRL 102 27 +FRL 103 22 +FRL 104 33 +FRL 105 26 +FRL 106 28 +FRL 107 36 +FRL 108 26 +FRL 109 24 +FRL 110 34 +FRL 111 29 +FRL 112 22 +FRL 113 36 +FRL 114 30 +FRL 115 49 +FRL 116 36 +FRL 117 33 +FRL 118 34 +FRL 119 38 +FRL 120 14 +FRL 121 39 +FRL 122 30 +FRL 123 28 +FRL 124 36 +FRL 125 36 +FRL 126 26 +FRL 127 31 +FRL 128 31 +FRL 129 28 +FRL 130 40 +FRL 131 45 +FRL 132 25 +FRL 133 18 +FRL 134 26 +FRL 135 31 +FRL 136 30 +FRL 137 29 +FRL 138 34 +FRL 139 32 +FRL 140 28 +FRL 141 41 +FRL 142 27 +FRL 143 814 +# Read lengths - last fragments. Use `grep ^LRL | cut -f 2-` to extract this part. The columns are: read length, count +LRL 33 1 +LRL 49 3 +LRL 51 1 +LRL 52 2 +LRL 54 1 +LRL 58 1 +LRL 59 2 +LRL 60 1 +LRL 61 4 +LRL 62 1 +LRL 63 5 +LRL 65 5 +LRL 66 2 +LRL 67 6 +LRL 68 4 +LRL 69 5 +LRL 70 10 +LRL 71 11 +LRL 72 7 +LRL 73 8 +LRL 74 4 +LRL 75 12 +LRL 76 11 +LRL 77 19 +LRL 78 15 +LRL 79 13 +LRL 80 17 +LRL 81 24 +LRL 82 18 +LRL 83 19 +LRL 84 25 +LRL 85 15 +LRL 86 24 +LRL 87 30 +LRL 88 29 +LRL 89 21 +LRL 90 16 +LRL 91 22 +LRL 92 30 +LRL 93 23 +LRL 94 20 +LRL 95 43 +LRL 96 54 +LRL 97 34 +LRL 98 28 +LRL 99 24 +LRL 100 44 +LRL 101 24 +LRL 102 27 +LRL 103 22 +LRL 104 33 +LRL 105 26 +LRL 106 28 +LRL 107 35 +LRL 108 26 +LRL 109 24 +LRL 110 34 +LRL 111 29 +LRL 112 22 +LRL 113 36 +LRL 114 30 +LRL 115 48 +LRL 116 36 +LRL 117 33 +LRL 118 34 +LRL 119 37 +LRL 120 15 +LRL 121 39 +LRL 122 30 +LRL 123 28 +LRL 124 36 +LRL 125 36 +LRL 126 24 +LRL 127 31 +LRL 128 31 +LRL 129 28 +LRL 130 39 +LRL 131 45 +LRL 132 25 +LRL 133 19 +LRL 134 25 +LRL 135 31 +LRL 136 30 +LRL 137 29 +LRL 138 34 +LRL 139 32 +LRL 140 28 +LRL 141 41 +LRL 142 27 +LRL 143 820 +# Mapping qualities for reads !(UNMAP|SECOND|SUPPL|QCFAIL|DUP). Use `grep ^MAPQ | cut -f 2-` to extract this part. The columns are: mapq, count +MAPQ 40 1 +MAPQ 42 1 +MAPQ 44 1 +MAPQ 54 1 +MAPQ 60 3980 +# Indel distribution. Use `grep ^ID | cut -f 2-` to extract this part. The columns are: length, number of insertions, number of deletions +ID 1 2 10 +# Indels per cycle. Use `grep ^IC | cut -f 2-` to extract this part. The columns are: cycle, number of insertions (fwd), .. (rev) , number of deletions (fwd), .. (rev) +IC 3 0 0 1 0 +IC 10 0 1 0 0 +IC 35 0 0 1 0 +IC 39 0 0 1 0 +IC 53 0 0 0 1 +IC 54 0 0 0 1 +IC 61 0 0 1 0 +IC 62 0 0 0 1 +IC 77 0 0 1 0 +IC 80 1 0 0 1 +IC 132 0 0 0 1 +# Coverage distribution. Use `grep ^COV | cut -f 2-` to extract this part. +COV [1-1] 1 40 +COV [2-2] 2 83 +COV [3-3] 3 32 +COV [4-4] 4 14 +COV [5-5] 5 10 +COV [6-6] 6 1 +COV [7-7] 7 12 +COV [8-8] 8 8 +COV [9-9] 9 9 +COV [10-10] 10 10 +COV [11-11] 11 1 +COV [12-12] 12 5 +COV [13-13] 13 1 +COV [14-14] 14 4 +COV [15-15] 15 1 +COV [16-16] 16 9 +COV [17-17] 17 1 +COV [18-18] 18 1 +COV [19-19] 19 2 +COV [20-20] 20 5 +COV [21-21] 21 13 +COV [22-22] 22 9 +COV [23-23] 23 2 +COV [24-24] 24 6 +COV [25-25] 25 1 +COV [26-26] 26 98 +COV [30-30] 30 2 +COV [32-32] 32 1 +COV [36-36] 36 1 +COV [37-37] 37 1 +COV [40-40] 40 2 +COV [41-41] 41 1 +COV [43-43] 43 2 +COV [45-45] 45 2 +COV [46-46] 46 1 +COV [48-48] 48 1 +COV [50-50] 50 5 +COV [52-52] 52 5 +COV [54-54] 54 3 +COV [55-55] 55 1 +COV [56-56] 56 2 +COV [57-57] 57 1 +COV [58-58] 58 2 +COV [59-59] 59 1 +COV [60-60] 60 1 +COV [63-63] 63 1 +COV [64-64] 64 1 +COV [66-66] 66 5 +COV [68-68] 68 1 +COV [70-70] 70 1 +COV [71-71] 71 1 +COV [72-72] 72 3 +COV [73-73] 73 1 +COV [74-74] 74 7 +COV [78-78] 78 6 +COV [80-80] 80 6 +COV [81-81] 81 1 +COV [82-82] 82 7 +COV [83-83] 83 1 +COV [84-84] 84 2 +COV [85-85] 85 1 +COV [86-86] 86 4 +COV [87-87] 87 1 +COV [88-88] 88 23 +COV [90-90] 90 7 +COV [92-92] 92 7 +COV [93-93] 93 1 +COV [94-94] 94 1 +COV [95-95] 95 1 +COV [98-98] 98 1 +COV [100-100] 100 1 +COV [101-101] 101 2 +COV [103-103] 103 1 +COV [104-104] 104 1 +COV [111-111] 111 1 +COV [114-114] 114 1 +COV [115-115] 115 2 +COV [116-116] 116 1 +COV [120-120] 120 1 +COV [121-121] 121 1 +COV [125-125] 125 1 +COV [126-126] 126 1 +COV [129-129] 129 2 +COV [130-130] 130 1 +COV [135-135] 135 1 +COV [136-136] 136 1 +COV [140-140] 140 1 +COV [143-143] 143 1 +COV [144-144] 144 1 +COV [145-145] 145 1 +COV [147-147] 147 1 +COV [148-148] 148 1 +COV [151-151] 151 1 +COV [154-154] 154 1 +COV [158-158] 158 2 +COV [159-159] 159 1 +COV [160-160] 160 1 +COV [164-164] 164 1 +COV [166-166] 166 1 +COV [170-170] 170 2 +COV [172-172] 172 1 +COV [173-173] 173 1 +COV [176-176] 176 1 +COV [178-178] 178 1 +COV [180-180] 180 2 +COV [184-184] 184 1 +COV [188-188] 188 1 +COV [190-190] 190 2 +COV [191-191] 191 1 +COV [192-192] 192 2 +COV [197-197] 197 1 +COV [200-200] 200 2 +COV [205-205] 205 1 +COV [208-208] 208 2 +COV [210-210] 210 1 +COV [212-212] 212 1 +COV [214-214] 214 1 +COV [215-215] 215 1 +COV [218-218] 218 1 +COV [224-224] 224 1 +COV [226-226] 226 1 +COV [229-229] 229 1 +COV [231-231] 231 2 +COV [232-232] 232 1 +COV [236-236] 236 1 +COV [240-240] 240 2 +COV [241-241] 241 1 +COV [242-242] 242 1 +COV [244-244] 244 3 +COV [245-245] 245 1 +COV [247-247] 247 1 +COV [250-250] 250 1 +COV [252-252] 252 1 +COV [254-254] 254 1 +COV [258-258] 258 2 +COV [259-259] 259 1 +COV [262-262] 262 1 +COV [263-263] 263 1 +COV [264-264] 264 1 +COV [265-265] 265 1 +COV [271-271] 271 1 +COV [274-274] 274 1 +COV [275-275] 275 1 +COV [278-278] 278 1 +COV [280-280] 280 1 +COV [281-281] 281 2 +COV [284-284] 284 1 +COV [286-286] 286 2 +COV [288-288] 288 2 +COV [289-289] 289 1 +COV [292-292] 292 1 +COV [293-293] 293 1 +COV [294-294] 294 1 +COV [296-296] 296 1 +COV [300-300] 300 1 +COV [302-302] 302 1 +COV [304-304] 304 2 +COV [306-306] 306 1 +COV [308-308] 308 1 +COV [310-310] 310 1 +COV [311-311] 311 1 +COV [314-314] 314 1 +COV [315-315] 315 1 +COV [317-317] 317 1 +COV [318-318] 318 2 +COV [320-320] 320 2 +COV [324-324] 324 1 +COV [325-325] 325 1 +COV [326-326] 326 3 +COV [329-329] 329 1 +COV [330-330] 330 1 +COV [331-331] 331 1 +COV [332-332] 332 1 +COV [333-333] 333 1 +COV [334-334] 334 2 +COV [338-338] 338 1 +COV [339-339] 339 1 +COV [340-340] 340 1 +COV [342-342] 342 1 +COV [343-343] 343 1 +COV [344-344] 344 3 +COV [345-345] 345 1 +COV [348-348] 348 3 +COV [349-349] 349 1 +COV [350-350] 350 1 +COV [352-352] 352 1 +COV [356-356] 356 3 +COV [357-357] 357 1 +COV [358-358] 358 4 +COV [360-360] 360 1 +COV [362-362] 362 9 +COV [364-364] 364 7 +COV [366-366] 366 3 +COV [367-367] 367 2 +COV [368-368] 368 28 +COV [374-374] 374 2 +COV [375-375] 375 2 +COV [387-387] 387 2 +COV [388-388] 388 1 +COV [389-389] 389 1 +COV [399-399] 399 1 +COV [401-401] 401 1 +COV [403-403] 403 1 +COV [406-406] 406 1 +COV [415-415] 415 1 +COV [419-419] 419 1 +COV [425-425] 425 1 +COV [426-426] 426 1 +COV [430-430] 430 1 +COV [432-432] 432 1 +COV [436-436] 436 1 +COV [445-445] 445 1 +COV [447-447] 447 1 +COV [454-454] 454 1 +COV [458-458] 458 2 +COV [459-459] 459 1 +COV [460-460] 460 1 +COV [463-463] 463 1 +COV [476-476] 476 1 +COV [477-477] 477 1 +COV [480-480] 480 1 +COV [481-481] 481 1 +COV [483-483] 483 2 +COV [489-489] 489 1 +COV [492-492] 492 1 +COV [500-500] 500 1 +COV [501-501] 501 1 +COV [504-504] 504 1 +COV [508-508] 508 1 +COV [511-511] 511 1 +COV [512-512] 512 1 +COV [515-515] 515 1 +COV [525-525] 525 3 +COV [529-529] 529 1 +COV [533-533] 533 1 +COV [539-539] 539 1 +COV [540-540] 540 1 +COV [541-541] 541 1 +COV [546-546] 546 1 +COV [549-549] 549 1 +COV [550-550] 550 1 +COV [553-553] 553 1 +COV [559-559] 559 1 +COV [563-563] 563 2 +COV [565-565] 565 1 +COV [569-569] 569 1 +COV [575-575] 575 1 +COV [577-577] 577 1 +COV [578-578] 578 1 +COV [579-579] 579 1 +COV [591-591] 591 2 +COV [592-592] 592 2 +COV [593-593] 593 2 +COV [601-601] 601 1 +COV [603-603] 603 1 +COV [605-605] 605 1 +COV [610-610] 610 1 +COV [611-611] 611 1 +COV [613-613] 613 2 +COV [617-617] 617 1 +COV [622-622] 622 1 +COV [625-625] 625 1 +COV [628-628] 628 1 +COV [637-637] 637 2 +COV [639-639] 639 1 +COV [640-640] 640 1 +COV [643-643] 643 1 +COV [652-652] 652 2 +COV [657-657] 657 1 +COV [661-661] 661 1 +COV [663-663] 663 2 +COV [665-665] 665 1 +COV [669-669] 669 1 +COV [671-671] 671 1 +COV [674-674] 674 1 +COV [675-675] 675 1 +COV [679-679] 679 1 +COV [685-685] 685 1 +COV [687-687] 687 1 +COV [689-689] 689 1 +COV [692-692] 692 1 +COV [694-694] 694 1 +COV [697-697] 697 2 +COV [698-698] 698 1 +COV [699-699] 699 1 +COV [705-705] 705 1 +COV [711-711] 711 1 +COV [714-714] 714 1 +COV [719-719] 719 2 +COV [724-724] 724 1 +COV [727-727] 727 1 +COV [728-728] 728 1 +COV [732-732] 732 1 +COV [733-733] 733 1 +COV [735-735] 735 1 +COV [738-738] 738 1 +COV [741-741] 741 1 +COV [746-746] 746 1 +COV [752-752] 752 1 +COV [755-755] 755 3 +COV [756-756] 756 1 +COV [757-757] 757 1 +COV [763-763] 763 1 +COV [765-765] 765 1 +COV [767-767] 767 1 +COV [769-769] 769 1 +COV [770-770] 770 1 +COV [771-771] 771 2 +COV [773-773] 773 2 +COV [774-774] 774 1 +COV [775-775] 775 1 +COV [779-779] 779 3 +COV [781-781] 781 1 +COV [782-782] 782 1 +COV [785-785] 785 2 +COV [788-788] 788 1 +COV [789-789] 789 2 +COV [792-792] 792 1 +COV [793-793] 793 5 +COV [794-794] 794 4 +COV [795-795] 795 7 +COV [796-796] 796 9 +COV [797-797] 797 8 +COV [799-799] 799 1 +COV [801-801] 801 1 +COV [806-806] 806 1 +COV [807-807] 807 1 +COV [817-817] 817 1 +COV [820-820] 820 1 +COV [824-824] 824 1 +COV [825-825] 825 1 +COV [847-847] 847 1 +COV [850-850] 850 1 +COV [851-851] 851 1 +COV [853-853] 853 1 +COV [868-868] 868 1 +COV [873-873] 873 1 +COV [874-874] 874 1 +COV [875-875] 875 1 +COV [892-892] 892 1 +COV [893-893] 893 1 +COV [902-902] 902 1 +COV [906-906] 906 1 +COV [908-908] 908 1 +COV [916-916] 916 1 +COV [925-925] 925 1 +COV [927-927] 927 1 +COV [935-935] 935 1 +COV [937-937] 937 1 +COV [944-944] 944 1 +COV [955-955] 955 1 +COV [965-965] 965 2 +COV [967-967] 967 1 +COV [986-986] 986 1 +COV [988-988] 988 1 +COV [999-999] 999 2 +COV [1000<] 1000 259 +# GC-depth. Use `grep ^GCD | cut -f 2-` to extract this part. The columns are: GC%, unique sequence percentiles, 10th, 25th, 50th, 75th and 90th depth percentile +GCD 0.0 100.000 0.000 0.000 0.000 0.000 0.000 diff --git a/tests/expected/dna/test.thresholds.bed.gz b/tests/expected/dna/test.thresholds.bed.gz new file mode 100644 index 00000000..10d328e2 Binary files /dev/null and b/tests/expected/dna/test.thresholds.bed.gz differ diff --git a/tests/expected/dna/test.thresholds.bed.gz.csi b/tests/expected/dna/test.thresholds.bed.gz.csi new file mode 100644 index 00000000..efa85327 Binary files /dev/null and b/tests/expected/dna/test.thresholds.bed.gz.csi differ