use std::sync::{Arc, Mutex};
use std::fs;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::collections::BTreeMap;
use crossbeam::channel::Receiver;
use anyhow::Result;
use clap::Args;
use std::path::PathBuf;
use crate::fileformat::SparseCountMatrix;
use crate::fileformat::{CellID, ReadPair};
type ListReadWithBarcode = Arc<(CellID,Arc<Vec<ReadPair>>)>;
pub const DEFAULT_PATH_TEMP: &str = "temp";
#[derive(Args)]
pub struct QueryFqCMD {
#[arg(short = 'i', value_parser= clap::value_parser!(PathBuf))]
pub path_in: PathBuf,
#[arg(short = 't', value_parser= clap::value_parser!(PathBuf), default_value = DEFAULT_PATH_TEMP)]
pub path_tmp: PathBuf,
#[arg(short = 'o', value_parser = clap::value_parser!(PathBuf))]
pub path_out: PathBuf,
#[arg(short = 'f', value_parser = clap::value_parser!(PathBuf))]
pub path_features: PathBuf,
#[arg(short = 'm', value_parser = clap::value_parser!(usize), default_value = "1000000")]
pub max_reads: usize,
}
impl QueryFqCMD {
pub fn try_execute(&mut self) -> Result<()> {
let params = QueryFq {
path_tmp: self.path_tmp.clone(),
path_input: self.path_in.clone(),
path_output: self.path_out.clone(),
max_reads: self.max_reads,
path_features: self.path_features.clone(),
};
let _ = QueryFq::run(
&Arc::new(params)
);
log::info!("Query has finished succesfully");
Ok(())
}
}
pub struct QueryFq {
pub path_input: std::path::PathBuf,
pub path_tmp: std::path::PathBuf,
pub path_output: std::path::PathBuf,
pub path_features: std::path::PathBuf,
pub max_reads: usize,
}
impl QueryFq {
pub fn run(
params: &Arc<QueryFq>
) -> anyhow::Result<()> {
let mut mm = SparseCountMatrix::new();
if params.path_tmp.exists() {
anyhow::bail!("Temporary directory '{}' exists already. For safety reasons, this is not allowed. Specify as a subdirectory of an existing directory", params.path_tmp.display());
} else {
println!("Using tempdir {}", params.path_tmp.display());
if fs::create_dir_all(¶ms.path_tmp).is_err() {
panic!("Failed to create temporary directory");
};
}
let mut features_reference: BTreeMap<Vec<u8>, usize> = BTreeMap::new();
let file_features_ref = File::open(¶ms.path_features).unwrap();
let bufreader_features_ref = BufReader::new(&file_features_ref);
let mut kmer_size = 0;
let mut all_features: Vec<Vec<u8>> = Vec::new();
for rline in bufreader_features_ref.lines() {
let feature = rline.unwrap();
all_features.push(feature.as_bytes().to_vec());
}
all_features.sort();
for feature in all_features {
kmer_size = feature.len();
let sfeature = String::from_utf8_lossy(feature.as_slice());
let feature_index = mm.add_feature(&sfeature.to_string());
features_reference.insert(feature, feature_index);
}
if kmer_size==0 {
anyhow::bail!("Feature file has no features");
} else {
println!("Read {} features. Detected kmer-length of {}", features_reference.len(), kmer_size);
}
let n_output=10;
let thread_pool_write = threadpool::ThreadPool::new(n_output);
let (tx_data, rx_data) = crossbeam::channel::bounded::<Option<ListReadWithBarcode>>(n_output*2);
let (tx_data, rx_data) = (Arc::new(tx_data), Arc::new(rx_data));
let mm: Arc<Mutex<SparseCountMatrix>> = Arc::new(Mutex::new(mm));
let features_reference = Arc::new(features_reference);
for _ in 0..n_output {
start_matrix_counter_threads(
&Arc::clone(&features_reference),
kmer_size,
params.max_reads,
&mm,
&thread_pool_write,
&rx_data
)?;
}
let mut list_input: Vec<std::path::PathBuf> = Vec::new();
list_input.push(params.path_input.clone());
super::transform::create_stream_readers(
&list_input,
&tx_data
).unwrap();
for _ in 0..n_output {
tx_data.send(None).unwrap();
}
thread_pool_write.join();
println!("Storing count table to {}", params.path_output.display());
let mm=mm.lock().unwrap();
mm.save_to_anndata(¶ms.path_output).expect("Failed to save to HDF5 file");
Ok(())
}
}
fn start_matrix_counter_threads(
features_reference: &Arc<BTreeMap<Vec<u8>, usize>>, kmer_size: usize,
max_reads: usize,
mm: &Arc<Mutex<SparseCountMatrix>>,
thread_pool: &threadpool::ThreadPool,
rx_data: &Receiver<Option<ListReadWithBarcode>>,
) -> anyhow::Result<()> {
let features_reference = Arc::clone(features_reference);
let mm = Arc::clone(mm);
let rx_data = rx_data.clone();
thread_pool.execute(move || {
println!("Starting KMER counter process");
while let Ok(Some(dat)) = rx_data.recv() {
let cell_id=&dat.0;
let list_reads = &dat.1;
let mut features_count: BTreeMap<Vec<u8>, usize> = BTreeMap::new();
let mut cur_line = 0;
for rp in list_reads.iter() {
count_from_seq(
&features_reference,
&mut features_count,
&rp.r1,
kmer_size
).unwrap();
count_from_seq(
&features_reference,
&mut features_count,
&rp.r2,
kmer_size
).unwrap();
cur_line += 1;
if cur_line==max_reads {
break
}
}
let mut mm=mm.lock().unwrap();
let cell_index = mm.add_cell(&cell_id);
for (feature, cnt) in features_count {
let feature_index = features_reference.get(&feature).unwrap();
mm.add_value(cell_index, *feature_index, cnt as u32);
}
}
println!("Shutting down KMER counter");
});
Ok(())
}
fn count_from_seq(
features_reference: &BTreeMap<Vec<u8>, usize>, features_count: &mut BTreeMap<Vec<u8>, usize>, seq: &Vec<u8>,
kmer_size: usize
) -> anyhow::Result<()> {
for kmer in seq.windows(kmer_size) {
if features_reference.contains_key(kmer) {
*features_count.entry(kmer.to_owned()).or_default() += 1;
}
}
let rc_seq = revcomp(seq);
for kmer in rc_seq.windows(kmer_size) {
if features_reference.contains_key(kmer) {
*features_count.entry(kmer.to_owned()).or_default() += 1;
}
}
Ok(())
}
fn revcomp(seq: &[u8]) -> Vec<u8> {
seq.iter()
.rev()
.map(|c| if c & 2 != 0 { c ^ 4 } else { c ^ 21 })
.collect()
}