use std::sync::Arc;
use std::fs;
use std::fs::File;
use std::io::BufWriter;
use std::io::Write;
use std::io::BufRead;
use std::collections::HashMap;
use std::collections::HashSet;
use anyhow::Result;
use clap::Args;
use std::path::PathBuf;
use crate::fileformat::CellID;
use crate::fileformat::ShardRandomFileExtractor;
use crate::fileformat::ZipBascetShardReader;
use crate::fileformat::shard::ShardCellDictionary;
use crate::fileformat::read_cell_list_file;
pub const DEFAULT_PATH_TEMP: &str = "temp";
#[derive(Args)]
pub struct MinhashHistCMD {
#[arg(short = 'i', value_parser= clap::value_parser!(PathBuf), num_args = 1.., value_delimiter = ',')] pub path_in: Vec<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(long = "cells")]
pub include_cells: Option<PathBuf>,
}
impl MinhashHistCMD {
pub fn try_execute(&mut self) -> Result<()> {
let include_cells = if let Some(p) = &self.include_cells {
let name_of_cells = read_cell_list_file(&p);
Some(name_of_cells)
} else {
None
};
let params = MinhashHist {
path_tmp: self.path_tmp.clone(),
path_input: self.path_in.clone(),
path_output: self.path_out.clone(),
include_cells: include_cells.clone(),
};
let _ = MinhashHist::run(
&Arc::new(params)
);
log::info!("MinhashHist has finished succesfully");
Ok(())
}
}
pub struct MinhashHist {
pub path_input: Vec<std::path::PathBuf>,
pub path_tmp: std::path::PathBuf,
pub path_output: std::path::PathBuf,
pub include_cells: Option<Vec<CellID>>,
}
impl MinhashHist {
pub fn run(
params: &Arc<MinhashHist>
) -> anyhow::Result<()> {
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 list_cells = if let Some(p) = ¶ms.include_cells {
p.clone()
} else {
let mut list_cells: Vec<String> = Vec::new();
for path_input in ¶ms.path_input {
let mut file_input = ZipBascetShardReader::new(&path_input).expect("Failed to open input file");
let mut cells_for_file= file_input.get_cell_ids().expect("Failed to get content listing for input file");
list_cells.append(&mut cells_for_file);
}
list_cells
};
println!("Preparing to process {} cells", list_cells.len());
let mut all_kmer: Vec<String> = Vec::with_capacity(list_cells.len()*500);
let mut hash_list_cells:HashSet<String> = HashSet::new();
for cellid in list_cells {
hash_list_cells.insert(cellid.clone());
}
let mut cur_file_id = 0;
for path_input in ¶ms.path_input {
let mut file_input = ZipBascetShardReader::new(&path_input).
expect("Failed to open input file");
let cells_for_file= file_input.get_cell_ids().
expect("Failed to get content listing for input file");
let cells_for_file = cells_for_file.iter().
filter(|&s| hash_list_cells.contains(s)).
collect::<Vec<&String>>();
for cell_id in cells_for_file {
if cur_file_id%1000 == 0 {
println!("Processing file {}, cell {}", path_input.display(), cur_file_id);
}
if file_input.has_cell(&cell_id) {
let list_files = file_input.get_files_for_cell(&cell_id).expect("Could not get list of files for cell"); let f1="minhash.txt".to_string();
if list_files.contains(&f1) {
let path_f1 = params.path_tmp.join(format!("cell_{}.minhash.txt", cur_file_id).to_string());
file_input.extract_as(&cell_id, &f1, &path_f1).unwrap();
let file = File::open(&path_f1)?;
let lines = std::io::BufReader::new(file).lines();
for line in lines {
let line = line.unwrap();
let mut splitter = line.split("\t");
let kmer_string = splitter.next().expect("Could not parse KMER sequence from minhash.txt in Bascet");
all_kmer.push(kmer_string.to_string());
}
std::fs::remove_file(&path_f1)?;
}
cur_file_id = cur_file_id + 1;
}
}
}
println!("Obtained minhashes from {} cells", cur_file_id);
println!("Counting minhashes");
let hist = make_histogram(all_kmer);
println!("Storing histogram");
let f=File::create(¶ms.path_output).expect("Could not open KMER histogram file for writing");
let mut bw=BufWriter::new(f);
for (kmer_string,cnt) in hist {
writeln!(bw, "{}\t{}", &kmer_string, cnt).unwrap();
}
fs::remove_dir_all(¶ms.path_tmp).unwrap();
Ok(())
}
}
fn make_histogram<I>(it: I) -> HashMap<I::Item, usize>
where
I: IntoIterator,
I::Item: Eq + core::hash::Hash,
{
let mut result = HashMap::new();
for item in it {
*result.entry(item).or_insert(0) += 1;
}
result
}