pub mod metrics;
use noodles::sam;
use rand::prelude::*;
use sam::alignment::Record;
use sam::record::sequence::Base;
use crate::qc::results;
use crate::qc::ComputationalLoad;
use crate::qc::RecordBasedQualityControlFacet;
use crate::utils::histogram::Histogram;
use self::metrics::GCContentMetrics;
use self::metrics::SummaryMetrics;
pub const TRUNCATION_LENGTH: usize = 100;
#[derive(Default)]
pub struct GCContentFacet {
pub metrics: GCContentMetrics,
}
impl RecordBasedQualityControlFacet for GCContentFacet {
fn name(&self) -> &'static str {
"GC Content"
}
fn computational_load(&self) -> ComputationalLoad {
ComputationalLoad::Light
}
fn process(&mut self, record: &Record) -> anyhow::Result<()> {
let flags = record.flags();
if flags.is_duplicate() || flags.is_secondary() {
self.metrics.records.ignored_flags += 1;
return Ok(());
};
let sequence = record.sequence();
let nucleobases = sequence.as_ref();
let sequence_length = nucleobases.len();
if sequence_length < TRUNCATION_LENGTH {
self.metrics.records.ignored_too_short += 1;
return Ok(());
}
let mut gc_this_read = 0usize;
let offset = if TRUNCATION_LENGTH < sequence_length {
let max_offset = sequence_length - TRUNCATION_LENGTH;
ThreadRng::default().gen_range(0..max_offset)
} else {
0
};
for i in 0..TRUNCATION_LENGTH {
let nucleobase = nucleobases[offset + i];
match nucleobase {
Base::C | Base::G => {
gc_this_read += 1;
self.metrics.nucleobases.total_gc_count += 1;
}
Base::A | Base::T => self.metrics.nucleobases.total_at_count += 1,
_ => self.metrics.nucleobases.total_other_count += 1,
}
}
let gc_content_this_read_pct =
((gc_this_read as f64 / TRUNCATION_LENGTH as f64) * 100.0).round() as usize;
self.metrics
.histogram
.increment(gc_content_this_read_pct)
.unwrap();
self.metrics.records.processed += 1;
Ok(())
}
fn summarize(&mut self) -> anyhow::Result<()> {
self.metrics.summary = Some(SummaryMetrics {
gc_content_pct: (self.metrics.nucleobases.total_gc_count as f64
/ (self.metrics.nucleobases.total_gc_count
+ self.metrics.nucleobases.total_at_count
+ self.metrics.nucleobases.total_other_count) as f64)
* 100.0,
ignored_flags_pct: (self.metrics.records.ignored_flags as f64
/ (self.metrics.records.ignored_flags
+ self.metrics.records.ignored_too_short
+ self.metrics.records.processed) as f64)
* 100.0,
ignored_too_short_pct: (self.metrics.records.ignored_too_short as f64
/ (self.metrics.records.ignored_flags
+ self.metrics.records.ignored_too_short
+ self.metrics.records.processed) as f64)
* 100.0,
});
Ok(())
}
fn aggregate(&self, results: &mut results::Results) {
results.gc_content = Some(self.metrics.clone());
}
}
impl Default for GCContentMetrics {
fn default() -> Self {
Self {
histogram: Histogram::zero_based_with_capacity(100),
nucleobases: Default::default(),
records: Default::default(),
summary: Default::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn it_defaults_with_zero_based_100_capacity_histogram() {
let default = GCContentFacet::default();
assert_eq!(default.metrics.histogram.range_start(), 0);
assert_eq!(default.metrics.histogram.range_stop(), 100);
assert_eq!(default.metrics.histogram.range_len(), 101);
}
}