use rayon::prelude::*;
use std::path::Path;
use std::time::Instant;
use thousands::Separable;
use crate::prelude::*;
use crate::single_cell::sc_data::data_io::CellGeneSparseWriter;
const MERGE_CELL_BATCH_SIZE: usize = 10_000;
#[derive(Debug, Clone)]
pub struct BinMergeTask {
pub exp_id: String,
pub bin_cells_path: String,
pub cells_to_keep: Vec<usize>,
pub gene_local_to_universe: Vec<Option<u32>>,
}
#[derive(Debug, Clone)]
pub struct PerFileMergeResult {
pub exp_id: String,
pub lib_size: Vec<usize>,
pub nnz: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct MultiSingleCellsResult {
pub total_cells: usize,
pub total_genes: usize,
pub per_file: Vec<PerFileMergeResult>,
}
fn remap_cell(
cell: &CsrCellChunk,
gene_local_to_universe: &[Option<u32>],
new_cell_idx: usize,
renormalise: bool,
target_size: f32,
) -> (CsrCellChunk, usize, usize) {
let mut entries: Vec<(u32, u32, F16)> = Vec::with_capacity(cell.indices.len());
for (i, &local_idx) in cell.indices.iter().enumerate() {
if let Some(universe_idx) = gene_local_to_universe[local_idx as usize] {
entries.push((universe_idx, cell.data_raw.get(i), cell.data_norm[i]));
}
}
entries.sort_unstable_by_key(|&(u, _, _)| u);
let nnz = entries.len();
if renormalise {
let raw: Vec<u32> = entries.iter().map(|&(_, r, _)| r).collect();
let indices: Vec<u32> = entries.iter().map(|&(u, _, _)| u).collect();
let new_chunk = CsrCellChunk::from_data(&raw, &indices, new_cell_idx, target_size, true);
let lib_size = new_chunk.library_size;
(new_chunk, lib_size, nnz)
} else {
let raw: Vec<u32> = entries.iter().map(|&(_, r, _)| r).collect();
let norm: Vec<F16> = entries.iter().map(|&(_, _, n)| n).collect();
let indices: Vec<u32> = entries.iter().map(|&(u, _, _)| u).collect();
let new_chunk = CsrCellChunk {
data_raw: RawCounts::from_u32_auto(&raw),
data_norm: norm,
library_size: cell.library_size,
indices,
original_index: new_cell_idx,
to_keep: true,
};
(new_chunk, cell.library_size, nnz)
}
}
pub fn merge_sc_bin_files<P: AsRef<Path>>(
tasks: &[BinMergeTask],
output_bin_path: P,
universe_size: usize,
renormalise: bool,
target_size: f32,
verbose: bool,
) -> Result<MultiSingleCellsResult, BixverseErrors> {
let total_cells: usize = tasks.iter().map(|t| t.cells_to_keep.len()).sum();
let start = Instant::now();
if verbose {
println!(
"Merging {} input files, {} total cells, {} universe genes (renormalise = {})",
tasks.len(),
total_cells.separate_with_underscores(),
universe_size.separate_with_underscores(),
renormalise
);
}
let mut writer = CellGeneSparseWriter::new(output_bin_path, true, total_cells, universe_size)?;
let mut per_file: Vec<PerFileMergeResult> = Vec::with_capacity(tasks.len());
let mut new_cell_idx: usize = 0;
for (task_idx, task) in tasks.iter().enumerate() {
if verbose {
println!(
" Input {}/{} ({}): {} cells",
task_idx + 1,
tasks.len(),
task.exp_id,
task.cells_to_keep.len().separate_with_underscores()
);
}
let reader = ParallelSparseReader::new(&task.bin_cells_path)?;
let mut lib_size_out: Vec<usize> = Vec::with_capacity(task.cells_to_keep.len());
let mut nnz_out: Vec<usize> = Vec::with_capacity(task.cells_to_keep.len());
for batch in task.cells_to_keep.chunks(MERGE_CELL_BATCH_SIZE) {
let cells = reader.read_cells_parallel(batch)?;
let remapped: Vec<(CsrCellChunk, usize, usize)> = cells
.par_iter()
.enumerate()
.map(|(offset, cell)| {
let idx = new_cell_idx + offset;
remap_cell(
cell,
&task.gene_local_to_universe,
idx,
renormalise,
target_size,
)
})
.collect();
for (chunk, lib_size_i, nnz_i) in remapped {
lib_size_out.push(lib_size_i);
nnz_out.push(nnz_i);
writer.write_cell_chunk(chunk)?;
}
new_cell_idx += batch.len();
}
per_file.push(PerFileMergeResult {
exp_id: task.exp_id.clone(),
lib_size: lib_size_out,
nnz: nnz_out,
});
if verbose {
println!(" Task done in {:.2?}", start.elapsed());
}
}
writer.finalise()?;
if verbose {
println!(
"Merge complete: {} cells written in {:.2?}",
total_cells.separate_with_underscores(),
start.elapsed()
);
}
Ok(MultiSingleCellsResult {
total_cells,
total_genes: universe_size,
per_file,
})
}