use std::sync::Arc;
use std::fs::File;
use std::path::PathBuf;
use std::io::BufRead;
use std::io::BufReader;
use std::collections::BTreeMap;
use hdf5::File as H5File;
use anyhow::Result;
use clap::Args;
pub const DEFAULT_PATH_TEMP: &str = "temp";
#[derive(Args)]
pub struct KrakenCMD {
#[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,
}
impl KrakenCMD {
pub fn try_execute(&mut self) -> Result<()> {
let params = Kraken {
path_tmp: self.path_tmp.clone(),
path_input: self.path_in.clone(),
path_output: self.path_out.clone(),
};
let _ = Kraken::run(
&Arc::new(params)
);
log::info!("Kraken has finished succesfully");
Ok(())
}
}
pub struct Kraken {
pub path_input: std::path::PathBuf,
pub path_tmp: std::path::PathBuf,
pub path_output: std::path::PathBuf,
}
impl Kraken {
pub fn run(
params: &Arc<Kraken>
) -> anyhow::Result<()> {
let mut mm = KrakenCountMatrix::new();
let file_in = File::open(¶ms.path_input).unwrap();
let bufreader = BufReader::new(&file_in);
let mut taxid_counter= BTreeMap::new();
let mut last_cellid = None;
for (_index, rline) in bufreader.lines().enumerate() { if let Ok(line) = rline {
let mut splitter = line.split("\t");
let is_categorized= splitter.next().unwrap();
if is_categorized=="C" {
let readname= splitter.next().unwrap();
let taxid: usize= splitter.next().unwrap().parse().expect("Failed to parse taxon id");
let mut splitter = readname.split(":");
let cellid = Some(splitter.next().unwrap().to_string());
if last_cellid != cellid {
if let Some(last_cellid_s) = last_cellid {
mm.add_taxids(&last_cellid_s, &mut taxid_counter);
taxid_counter.clear();
}
last_cellid = cellid;
}
let values = taxid_counter.entry(taxid).or_insert(0);
*values += 1;
}
} else {
anyhow::bail!("Failed to read one line of input");
}
}
if let Some(last_cellid_s) = last_cellid {
mm.add_taxids(&last_cellid_s, &mut taxid_counter);
}
println!("Storing count table to {}", params.path_output.display());
mm.save_to_anndata(¶ms.path_output).expect("Failed to save to HDF5 file");
println!("Cleaning up temp files");
Ok(())
}
}
pub struct KrakenCountMatrix {
pub cells: Vec<String>,
pub entries: Vec<(u32,u32,u32)>, pub max_taxid: usize
}
impl KrakenCountMatrix {
pub fn new() -> Self {
Self {
cells: Vec::new(),
entries: Vec::new(),
max_taxid: 0
}
}
pub fn add_cell(&mut self, cell: &String) -> usize {
let id = self.cells.len();
self.cells.push(cell.clone());
id as usize
}
pub fn add_value(
&mut self,
cell: usize,
feature: usize,
value: u32
) {
self.entries.push((cell as u32, feature as u32, value));
if feature > self.max_taxid {
self.max_taxid = feature;
}
}
pub fn add_taxids(
&mut self,
cell: &String,
taxid_counter: &mut BTreeMap<usize, u32>
) {
let cell_index = self.add_cell(&cell);
for (taxid, cnt) in taxid_counter {
self.add_value(cell_index, *taxid, *cnt);
}
}
pub fn save_to_anndata(&self, p: &PathBuf) -> anyhow::Result<()> {
if p.exists() {
std::fs::remove_file(&p).expect("Failed to delete previous output file");
}
let file = H5File::create(p)?;
let csr_data: Vec<u32> = self.entries.iter().map(|(_row,_col,data)| *data).collect();
let csr_cols: Vec<u32> = self.entries.iter().map(|(_row,col,_data)| *col).collect();
let csr_rows: Vec<u32> = self.entries.iter().map(|(row,_col,_data)| *row).collect();
let mut ind_ptr:Vec<u32> = Vec::new();
ind_ptr.push(0);
for i in 1..(csr_rows.len()) {
if csr_rows[i] != csr_rows[i-1] {
ind_ptr.push(i as u32);
}
}
ind_ptr.push((csr_data.len()) as u32);
let group = file.create_group("X")?;
let builder = group.new_dataset_builder();
let _ = builder.with_data(&csr_data.as_slice()).create("data")?; let builder = group.new_dataset_builder();
let _ = builder.with_data(&csr_cols.as_slice()).create("indices")?; let builder = group.new_dataset_builder();
let _ = builder.with_data(&ind_ptr.as_slice()).create("indptr")?;
let n_rows = self.cells.len();
let n_cols = self.max_taxid+1; let builder = group.new_dataset_builder();
let _ = builder.with_data(&[n_rows,n_cols].as_slice()).create("shape")?;
let list_cell_names = vec_to_h5_string(self.cells.as_slice());
let group = file.create_group("obs")?;
let builder = group.new_dataset_builder();
let _ = builder.
with_data(list_cell_names.as_slice()).
create("_index")?;
Ok(())
}
}
fn vec_to_h5_string(list: &[String]) -> Vec<hdf5::types::VarLenUnicode> {
list.iter().map(|f| f.parse().unwrap()).collect()
}