use anyhow::{anyhow, Result};
use regex::Captures;
use std::{
collections::VecDeque,
fmt,
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
};
use crate::info::{MaxSeqErrors, Results, SequenceErrors, SequenceFormat};
use ahash::AHashSet;
pub struct SequenceParser {
shared_mut_clone: SharedMutData,
sequence_errors_clone: SequenceErrors,
sequence_format_clone: SequenceFormat,
max_errors_clone: MaxSeqErrors,
sample_seqs: AHashSet<String>,
counted_barcode_seqs: Vec<AHashSet<String>>,
raw_sequence: RawSequenceRead,
barcode_groups: Vec<String>,
min_quality_score: f32,
}
impl SequenceParser {
pub fn new(
shared_mut_clone: SharedMutData,
sequence_errors_clone: SequenceErrors,
sequence_format_clone: SequenceFormat,
max_errors_clone: MaxSeqErrors,
sample_seqs: AHashSet<String>,
counted_barcode_seqs: Vec<AHashSet<String>>,
min_quality_score: f32,
) -> Self {
let mut barcode_groups = Vec::new();
for x in 0..sequence_format_clone.barcode_num {
barcode_groups.push(format!("barcode{}", x + 1))
}
SequenceParser {
shared_mut_clone,
sequence_errors_clone,
sequence_format_clone,
max_errors_clone,
sample_seqs,
counted_barcode_seqs,
raw_sequence: RawSequenceRead::new(),
barcode_groups,
min_quality_score,
}
}
pub fn parse(&mut self) -> Result<()> {
loop {
if self.get_seqeunce()? {
if let Some(seq_match_result) = self.match_seq()? {
let barcode_string = seq_match_result.barcode_string();
let added = self.shared_mut_clone.results.lock().unwrap().add_count(
&seq_match_result.sample_barcode,
seq_match_result.random_barcode.as_ref(),
barcode_string,
);
if added {
self.sequence_errors_clone.correct_match()
} else {
self.sequence_errors_clone.duplicated();
}
}
} else if self.shared_mut_clone.finished.load(Ordering::Relaxed) {
break;
}
}
Ok(())
}
fn get_seqeunce(&mut self) -> Result<bool> {
if let Some(new_raw_sequence) = self.shared_mut_clone.seq.lock().unwrap().pop_back() {
self.raw_sequence = RawSequenceRead::unpack(new_raw_sequence)?;
Ok(true)
} else {
Ok(false)
}
}
fn match_seq(&mut self) -> Result<Option<SequenceMatchResult>> {
self.check_and_fix_consant_region();
if let Some(barcodes) = self
.sequence_format_clone
.format_regex
.captures(&self.raw_sequence.sequence)
{
if self.min_quality_score > 0.0 {
if let Some(format_match) = self
.sequence_format_clone
.format_regex
.find(&self.raw_sequence.sequence)
{
let start = format_match.start();
if self.raw_sequence.low_quality(
self.min_quality_score,
&self.sequence_format_clone.regions_string,
start,
) {
self.sequence_errors_clone.low_quality_barcode();
return Ok(None);
}
} else {
return Err(anyhow!(
"Regex find failed after regex captures was successful"
));
}
}
let match_results = SequenceMatchResult::new(
barcodes,
&self.barcode_groups,
&self.counted_barcode_seqs,
self.max_errors_clone.max_barcode_errors(),
&self.sample_seqs,
self.max_errors_clone.max_sample_errors(),
);
if match_results.sample_barcode_error {
self.sequence_errors_clone.sample_barcode_error();
return Ok(None);
}
if match_results.counted_barcode_error {
self.sequence_errors_clone.barcode_error();
return Ok(None);
}
Ok(Some(match_results))
} else {
self.sequence_errors_clone.constant_region_error();
Ok(None)
}
}
fn check_and_fix_consant_region(&mut self) {
if !self
.sequence_format_clone
.format_regex
.is_match(&self.raw_sequence.sequence)
{
self.raw_sequence.fix_constant_region(
&self.sequence_format_clone.format_string,
self.max_errors_clone.max_constant_errors(),
);
}
}
}
pub struct SharedMutData {
pub seq: Arc<Mutex<VecDeque<String>>>,
pub finished: Arc<AtomicBool>,
pub results: Arc<Mutex<Results>>,
}
impl SharedMutData {
pub fn new(
seq: Arc<Mutex<VecDeque<String>>>,
finished: Arc<AtomicBool>,
results: Arc<Mutex<Results>>,
) -> Self {
SharedMutData {
seq,
finished,
results,
}
}
pub fn arc_clone(&self) -> SharedMutData {
let seq = Arc::clone(&self.seq);
let finished = Arc::clone(&self.finished);
let results = Arc::clone(&self.results);
SharedMutData {
seq,
finished,
results,
}
}
}
#[derive(Clone)]
pub struct RawSequenceRead {
description: String, pub sequence: String, add_description: String, quality_values: String, }
impl Default for RawSequenceRead {
fn default() -> Self {
Self::new()
}
}
impl RawSequenceRead {
pub fn new() -> Self {
RawSequenceRead {
description: String::new(),
sequence: String::new(),
add_description: String::new(),
quality_values: String::new(),
}
}
pub fn new_fill(
line_1: String,
line_2: String,
line_3: String,
line_4: String,
) -> RawSequenceRead {
RawSequenceRead {
description: line_1,
sequence: line_2,
add_description: line_3,
quality_values: line_4,
}
}
pub fn add_line(&mut self, line_num: u16, line: String) -> Result<()> {
match line_num {
1 => self.description = line,
2 => self.sequence = line,
3 => self.add_description = line,
4 => self.quality_values = line,
_ => {
return Err(anyhow!(
"Too many new lines found within fastq read\nCurrent read\n{}\nCurrent line: {}",
self,
line
))
}
}
Ok(())
}
pub fn pack(&self) -> String {
format!(
"{}\n{}\n{}\n{}",
self.description, self.sequence, self.add_description, self.quality_values
)
}
pub fn unpack(raw_string: String) -> Result<Self> {
let mut raw_sequence_read = RawSequenceRead::new();
for (line_num, line) in raw_string.split('\n').enumerate() {
let true_line = line_num as u16 + 1;
raw_sequence_read.add_line(true_line, line.to_string())?
}
Ok(raw_sequence_read)
}
pub fn insert_barcodes_constant_region(&mut self, format_string: &str, best_sequence: String) {
let mut fixed_sequence = String::new();
for (old_char, new_char) in best_sequence.chars().zip(format_string.chars()) {
if new_char == 'N' {
fixed_sequence.push_str(&old_char.to_string());
} else {
fixed_sequence.push_str(&new_char.to_string());
}
}
self.sequence = fixed_sequence
}
pub fn fix_constant_region(&mut self, format_string: &str, max_constant_errors: u16) {
let length_diff = self.sequence.len() - format_string.len();
let mut possible_seqs = Vec::new();
for index in 0..length_diff {
let possible_seq = self
.sequence
.chars()
.skip(index) .take(format_string.len())
.collect::<String>();
possible_seqs.push(possible_seq);
}
let best_sequence_option = fix_error(format_string, &possible_seqs, max_constant_errors);
if let Some(best_sequence) = best_sequence_option {
self.insert_barcodes_constant_region(format_string, best_sequence);
} else {
self.sequence = "".to_string();
}
}
pub fn quality_scores(&self) -> Vec<u8> {
self.quality_values
.chars()
.map(|ch| ch as u8 - 33)
.collect::<Vec<u8>>()
}
pub fn low_quality(
&self,
min_average: f32,
barcode_indicator_string: &str,
start: usize,
) -> bool {
let mut scores = Vec::new(); let mut previous_type = '\0';
for (score, seq_type) in self
.quality_scores()
.iter()
.skip(start)
.zip(barcode_indicator_string.chars())
{
if seq_type != previous_type {
if !scores.is_empty() {
let sum: f32 = scores.iter().sum();
let average_score: f32 = sum / scores.len() as f32;
if average_score < min_average {
return true;
}
scores = Vec::new();
}
previous_type = seq_type;
if seq_type != 'C' {
scores = vec![*score as f32];
}
} else {
if seq_type != 'C' {
scores.push(*score as f32);
}
}
}
false
}
pub fn check_fastq_format(&self) -> Result<()> {
match test_sequence(&self.description) {
LineType::Sequence => {
println!("{}", self);
return Err(anyhow!("The first line within the FASTQ contains DNA sequences. Check the FASTQ format"));
}
LineType::Metadata => (),
}
match test_sequence(&self.sequence) {
LineType::Sequence => (),
LineType::Metadata => {
println!("{}", self);
return Err(anyhow!("The second line within the FASTQ file is not a sequence. Check the FASTQ format"));
}
}
Ok(())
}
}
impl fmt::Display for RawSequenceRead {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Line 1: {}\nLine 2: {}\nLine 3: {}\nLine 4: {}",
self.description, self.sequence, self.add_description, self.quality_values
)
}
}
enum LineType {
Sequence,
Metadata,
}
fn test_sequence(sequence: &str) -> LineType {
let sequence_length = sequence.len(); let adenines = sequence.matches('A').count(); let guanines = sequence.matches('G').count();
let cytosines = sequence.matches('C').count();
let thymines = sequence.matches('T').count();
let any = sequence.matches('N').count();
let total_dna = adenines + guanines + cytosines + thymines + any;
if total_dna < sequence_length / 2 {
return LineType::Metadata;
}
LineType::Sequence
}
pub struct SequenceMatchResult {
pub sample_barcode: String,
pub counted_barcodes: Vec<String>,
pub counted_barcode_error: bool,
pub sample_barcode_error: bool,
pub random_barcode: Option<String>,
}
impl SequenceMatchResult {
pub fn new(
barcodes: Captures, barcode_groups: &[String],
counted_barcode_seqs: &[AHashSet<String>], counted_barcode_max_errors: &[u16], sample_seqs: &AHashSet<String>, sample_seqs_max_errors: u16, ) -> SequenceMatchResult {
let mut sample_barcode_error = false;
let sample_barcode;
if let Some(sample_barcode_match) = barcodes.name("sample") {
let sample_barcode_str = sample_barcode_match.as_str();
if sample_seqs.is_empty() {
sample_barcode = sample_barcode_str.to_string();
} else {
if sample_seqs.contains(sample_barcode_str) {
sample_barcode = sample_barcode_str.to_string();
} else {
let sample_barcode_fix_option =
fix_error(sample_barcode_str, sample_seqs, sample_seqs_max_errors);
if let Some(fixed_barcode) = sample_barcode_fix_option {
sample_barcode = fixed_barcode;
} else {
sample_barcode = String::new();
sample_barcode_error = true;
}
}
}
} else {
sample_barcode = "barcode".to_string();
}
let mut counted_barcode_error = false;
let mut counted_barcodes = Vec::new();
if !sample_barcode_error {
for (index, barcode_group) in barcode_groups.iter().enumerate() {
let mut counted_barcode =
barcodes.name(barcode_group).unwrap().as_str().to_string();
if !counted_barcode_seqs.is_empty() {
if !counted_barcode_seqs[index].contains(&counted_barcode) {
let barcode_seq_fix_option = fix_error(
&counted_barcode,
&counted_barcode_seqs[index],
counted_barcode_max_errors[index],
);
if let Some(fixed_barcode) = barcode_seq_fix_option {
counted_barcode = fixed_barcode;
} else {
counted_barcode_error = true;
break;
}
}
}
counted_barcodes.push(counted_barcode);
}
}
let random_barcode;
if let Some(random_barcode_match) = barcodes.name("random") {
random_barcode = Some(random_barcode_match.as_str().to_string())
} else {
random_barcode = None
}
SequenceMatchResult {
sample_barcode,
counted_barcodes,
counted_barcode_error,
sample_barcode_error,
random_barcode,
}
}
pub fn barcode_string(&self) -> String {
self.counted_barcodes.join(",")
}
}
pub fn fix_error<'a, I>(mismatch_seq: &str, possible_seqs: I, mismatches: u16) -> Option<String>
where
I: IntoIterator<Item = &'a String>,
{
let mut best_match = None; let mut best_mismatch_count = mismatches + 1; let mut keep = true;
for true_seq in possible_seqs {
let mut mismatches = 0;
for (possible_char, current_char) in true_seq.chars().zip(mismatch_seq.chars()) {
if possible_char != current_char && current_char != 'N' && possible_char != 'N' {
mismatches += 1;
}
if mismatches > best_mismatch_count {
break;
}
}
if mismatches == best_mismatch_count {
keep = false
}
if mismatches < best_mismatch_count {
keep = true;
best_mismatch_count = mismatches;
best_match = Some(true_seq.to_string());
}
}
if keep && best_match.is_some() {
best_match
} else {
None
}
}