use std::time::Instant;
use crate::core::math::vector_helpers::{median, quantile};
use crate::prelude::*;
use super::candidates::{compute_candidate_metacells, make_incoming_view};
use super::deviants::find_deviant_cells;
use super::dissolve::dissolve_metacells;
use super::downsample::downsample_pile;
use super::knn::build_knn_graph;
use super::params::MetacellsParams;
use super::pile::Pile;
use super::select::select_features;
use super::similarity::compute_similarity;
#[derive(Debug, Clone)]
pub struct DirectMetacellsResult {
pub metacell_of_cell: Vec<i32>,
pub deviant_of_cell: Vec<bool>,
pub dissolved_of_cell: Vec<bool>,
pub n_metacells: usize,
}
#[allow(clippy::too_many_arguments)]
fn compute_knn_k(
n_cells: usize,
umis_per_cell: &[f32],
target_metacell_size: usize,
target_metacell_umis: f64,
knn_k_size_factor: f32,
knn_k_umis_quantile: f32,
min_knn_k: Option<usize>,
knn_k_override: Option<usize>,
) -> usize {
if let Some(k) = knn_k_override {
return k;
}
let by_size = ((n_cells as f64) / (target_metacell_size as f64)).round() as i64;
let median = median(umis_per_cell).unwrap();
let by_median = if median > 0.0 {
(knn_k_size_factor as f64 * target_metacell_umis / median as f64).round() as i64
} else {
0
};
let _by_median_check = if median > 0.0 {
(target_metacell_umis / median as f64).round() as i64
} else {
0
};
let q_umis = quantile(umis_per_cell, knn_k_umis_quantile);
let by_quantile = if q_umis > 0.0 {
(target_metacell_umis / q_umis as f64).round() as i64
} else {
0
};
let floor = min_knn_k.unwrap_or(0) as i64;
by_size.max(by_median).max(by_quantile).max(floor).max(0) as usize
}
pub fn compute_direct_metacells(
pile: &mut Pile,
params: &MetacellsParams,
seed: usize,
verbose: bool,
) -> Result<DirectMetacellsResult, BixverseErrors> {
let start_pile = Instant::now();
let n_cells = pile.raw.shape.0;
if verbose {
println!("Starting UMI downsampling and feature selection for the pile...")
}
downsample_pile(pile, ¶ms.select, seed as u64);
select_features(pile, ¶ms.select);
let end_downsampling_features = start_pile.elapsed();
if verbose {
println!(
" Finished downsampling and feature selection in {:.2?}...",
end_downsampling_features
)
}
if verbose {
println!("Computing similarities for the pile...")
}
let similarity = compute_similarity(pile, ¶ms.similarity)?;
let end_similarity = start_pile.elapsed();
if verbose {
println!(
" Finished similarity calculations in {:.2?}...",
end_similarity
)
}
if verbose {
println!("Computing balanced kNN for the pile...")
}
let knn_k = compute_knn_k(
n_cells,
&pile.umis_per_cell,
params.target_metacell_size,
params.target_metacell_umis as f64,
params.knn.k_size_factor,
params.knn.k_umis_quantile,
params.knn.min_knn_k,
params.knn.knn_k_override,
);
if knn_k == 0 || knn_k >= n_cells {
return Ok(DirectMetacellsResult {
metacell_of_cell: vec![0; n_cells],
deviant_of_cell: vec![false; n_cells],
dissolved_of_cell: vec![false; n_cells],
n_metacells: 1,
});
}
let outgoing = build_knn_graph(&similarity, knn_k, ¶ms.knn);
drop(similarity);
let incoming = make_incoming_view(&outgoing);
let end_knn = start_pile.elapsed();
if verbose {
println!(" Finished kNN generations in {:.2?}...", end_knn)
}
if verbose {
println!("Identifying candidates for the pile...")
}
let candidate_of_cell = compute_candidate_metacells(
&outgoing,
&incoming,
&pile.umis_per_cell,
params,
seed as u64,
);
drop(outgoing);
drop(incoming);
let end_candidates = start_pile.elapsed();
if verbose {
println!(
" Finished candidates identification in {:.2?}...",
end_candidates
)
}
if verbose {
println!("Identifying deviant cells and dissolve too small metacells for the pile...")
}
let deviant_of_cell = find_deviant_cells(
&pile.raw,
&pile.umis_per_cell,
&candidate_of_cell,
¶ms.deviants,
);
let (metacell_of_cell, dissolved_of_cell) = dissolve_metacells(
&pile.raw,
&pile.umis_per_cell,
&candidate_of_cell,
&deviant_of_cell,
params.target_metacell_size,
params.target_metacell_umis as f64,
params.min_metacell_size,
¶ms.dissolve,
);
let end_deviant_dissolving = start_pile.elapsed();
if verbose {
println!(
" Finished deviant identification and dissolution in {:.2?}...",
end_deviant_dissolving
)
}
let n_metacells = (metacell_of_cell.iter().copied().max().unwrap_or(-1) + 1) as usize;
let end_pile = start_pile.elapsed();
if verbose {
println!(" Finished the pile processing in {:.2?}...", end_pile)
}
Ok(DirectMetacellsResult {
metacell_of_cell,
deviant_of_cell,
dissolved_of_cell,
n_metacells,
})
}