use edge_rs::sc::nebula::NebulaParams;
use edge_rs::sc::test::ScTested;
use extendr_api::*;
use std::collections::HashMap;
use crate::ml::clustering::k_means::KMeansParamsWrappers;
use crate::ml::gp::LandmarkGpParams;
use crate::prelude::{BixverseFloat, LanczosParams, VecConvert, VecFloatConvert};
use crate::single_cell::mc_generation::{
hdwgcna_meta_cells::BootstrappedMetaCellParams, metacells2::params::*,
seacells::SEACellsParams, super_cells::SuperCellParams,
};
use crate::single_cell::sc_analysis::{
cellphonedb::CellPhoneDbParams,
dge_pathway_scores::{AucellParams, parse_auc_type},
dialogue::{DialogueParams, HlmParams, PmdParams, RefineParams, parse_averaging},
fast_clusters::FastLouvainParams,
hotspot::{HotSpotGraphParams, HotSpotParams},
meld::{MeldParams, parse_lap_type, parse_meld_filter},
milo_r::MiloRParams,
nebula::{NebulaScParams, parse_nebula_method},
nichenet::ligand_regulatory_potential::LigandTargetParams,
regulon_binarise::BinariseParams,
scenic::{
ExtraTreesConfig, GradientBoostingConfig, RandomForestConfig, RegressionLearner,
ScenicParams,
},
vision::SignatureGenes,
};
use crate::single_cell::sc_data::h5ad_io::parse_h5ad_format;
use crate::single_cell::sc_processing::magic::{MagicLayer, MagicParams};
use crate::single_cell::sc_processing::residuals::{
assert_ascending, validate_clip_range, validate_groups,
};
use crate::single_cell::sc_trajectory::gene_trends::{
BranchSelectionParams, BranchWeighting, GeneTrendsParams,
};
use crate::single_cell::sc_trajectory::palantir::PalantirParams;
use crate::utils::r_rust_interface::{
r_list_count, r_list_count_allow_zero, r_list_real, r_list_required_real, r_list_to_map,
};
use crate::single_cell::sc_annotation::{
sc_type::{CellTypeMarkers, ScTypeCellParams, SctypeRes, parse_score_calibration},
symphony::SymphonyMapParams,
};
use crate::single_cell::sc_batch_correction::{
bbknn::BbknnParams, fast_mnn::FastMnnParams, harmony::HarmonyParams,
harmony_core::HARMONY_KMEANS_ITERS, harmony_v2::HarmonyParamsV2, seurat_cca::SeuratCcaParams,
seurat_rpca::SeuratRpcaParams,
};
use crate::single_cell::sc_data::h5ad_io::RawDataSlot;
use crate::single_cell::sc_data::{
bin_merge_io::BinMergeTask, data_io::MinCellQuality, h5_10x_io::parse_tenx_version,
h5_10x_multifile_io::TenxFileTask, h5ad_io::parse_raw_slot, h5ad_multifile_io::H5adFileTask,
mtx_multifile_io::MtxFileTask, sc_synthetic_data::CellTypeConfig,
};
use crate::single_cell::sc_processing::analytic_pearson::model::{AprModel, AprParams};
use crate::single_cell::sc_processing::analytic_pearson::stream::AprGroupedFit;
use crate::single_cell::sc_processing::sctransform::model::{SctCovariates, SctModel, SctParams};
use crate::single_cell::sc_processing::sctransform::stream::SctGroupedFit;
use crate::single_cell::sc_processing::{
cellsweep::{
CellSweepFit, CellSweepParams, CellSweepSample, EmptyDropletCall, parse_empty_droplet_call,
},
doublet_detection::BoostParams,
hvg::ScranTrendParams,
knn::KnnParams,
pca::{PcaSolver, SingleCellPcaParams, parse_pca_solver},
scdblfinder::ScDblFinderParams,
scrublet::ScrubletParams,
utils_doublets::ScDblSimParams,
};
pub fn assignments_to_r_list(assignments: &[Option<usize>], n_cells: usize) -> List {
let r_assignments: Vec<i32> = assignments
.iter()
.map(|&x| match x {
Some(id) => (id + 1) as i32,
None => -1,
})
.collect();
let n_unassigned = assignments.iter().filter(|x| x.is_none()).count();
let actual_k = assignments
.iter()
.filter_map(|&x| x)
.max()
.map(|x| x + 1)
.unwrap_or(0);
let mut metacells = vec![Vec::new(); actual_k];
for (cell_id, &metacell_id) in assignments.iter().enumerate() {
if let Some(id) = metacell_id {
metacells[id].push((cell_id + 1) as i32);
}
}
let unassigned: Vec<i32> = assignments
.iter()
.enumerate()
.filter_map(|(cell_id, &x)| {
if x.is_none() {
Some((cell_id + 1) as i32)
} else {
None
}
})
.collect();
let metacells_list: List = metacells.into_iter().map(Robj::from).collect();
list!(
assignments = r_assignments,
metacells = metacells_list,
unassigned = unassigned,
n_metacells = actual_k,
n_cells = n_cells,
n_unassigned = n_unassigned
)
}
pub fn metacells_to_r_list(metacells: &[Vec<usize>], n_cells: usize) -> List {
let mut cell_assignments: Vec<Vec<i32>> = vec![Vec::new(); n_cells];
for (mc_id, cells) in metacells.iter().enumerate() {
for &cell_id in cells {
cell_assignments[cell_id].push((mc_id + 1) as i32);
}
}
let unassigned: Vec<i32> = cell_assignments
.iter()
.enumerate()
.filter_map(|(cell_id, mcs)| mcs.is_empty().then_some((cell_id + 1) as i32))
.collect();
let n_unassigned = unassigned.len();
let n_metacells = metacells.len();
let metacells_list: List = metacells
.iter()
.map(|cells| {
let r_cells: Vec<i32> = cells.iter().map(|&c| (c + 1) as i32).collect();
Robj::from(r_cells)
})
.collect();
let assignments_list: List = cell_assignments.into_iter().map(Robj::from).collect();
list!(
assignments = assignments_list,
metacells = metacells_list,
unassigned = unassigned,
n_metacells = n_metacells,
n_cells = n_cells,
n_unassigned = n_unassigned
)
}
impl CellTypeConfig {
pub fn from_r_list(r_list: List) -> Result<Self> {
let map: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let marker_genes = map
.get("marker_genes")
.and_then(|v| v.as_integer_vector())
.map(|v| v.iter().map(|x| *x as usize).collect())
.unwrap_or_default();
Ok(CellTypeConfig { marker_genes })
}
}
impl MinCellQuality {
pub fn from_r_list(r_list: List) -> Result<Self> {
let min_qc: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let min_unique_genes = min_qc
.get("min_unique_genes")
.and_then(|v| v.as_integer())
.unwrap_or(100) as usize;
let min_lib_size = min_qc
.get("min_lib_size")
.and_then(|v| v.as_integer())
.unwrap_or(250) as usize;
let min_cells = min_qc
.get("min_cells")
.and_then(|v| v.as_integer())
.unwrap_or(10) as usize;
let target_size = min_qc
.get("target_size")
.and_then(|v| v.as_real())
.unwrap_or(1e5) as f32;
Ok(MinCellQuality {
min_unique_genes,
min_lib_size,
min_cells,
target_size,
})
}
}
impl KnnParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let knn_method = std::string::String::from(
params_list
.get("knn_method")
.and_then(|v| v.as_str())
.unwrap_or("annoy"),
);
let ann_dist = std::string::String::from(
params_list
.get("ann_dist")
.and_then(|v| v.as_str())
.unwrap_or("cosine"),
);
let k = r_list_count_allow_zero(¶ms_list, "k")?.unwrap_or(15);
let n_tree = r_list_count(¶ms_list, "n_trees")?.unwrap_or(50);
let search_budget = r_list_count(¶ms_list, "search_budget")?;
let diversify_prob = params_list
.get("diversify_prob")
.and_then(|v| v.as_real())
.unwrap_or(0.0) as f32;
let delta = params_list
.get("delta")
.and_then(|v| v.as_real())
.unwrap_or(0.001) as f32;
let ef_budget = r_list_count(¶ms_list, "ef_budget")?;
let extract_knn = params_list
.get("extract_knn")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let m = r_list_count(¶ms_list, "m")?.unwrap_or(16);
let ef_construction = r_list_count(¶ms_list, "ef_construction")?.unwrap_or(200);
let ef_search = r_list_count(¶ms_list, "ef_search")?.unwrap_or(100);
let n_list = r_list_count(¶ms_list, "n_list")?;
let n_probe = r_list_count(¶ms_list, "n_probe")?;
Ok(Self {
knn_method,
ann_dist,
k,
n_tree,
search_budget,
ef_budget,
extract_knn,
diversify_prob,
delta,
m,
ef_construction,
ef_search,
n_list,
n_probe,
})
}
}
impl ScranTrendParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = Self::default();
let flag =
|key: &str, default: bool| params.get(key).and_then(|v| v.as_bool()).unwrap_or(default);
Ok(Self {
mean_filter: flag("mean_filter", defaults.mean_filter),
minimum_mean: r_list_real(¶ms, "min_mean")?.unwrap_or(defaults.minimum_mean),
transform: flag("transform", defaults.transform),
use_minimum_width: flag("use_min_width", defaults.use_minimum_width),
minimum_width: r_list_real(¶ms, "min_width")?.unwrap_or(defaults.minimum_width),
minimum_window_count: r_list_count(¶ms, "min_window_count")?
.unwrap_or(defaults.minimum_window_count),
span: r_list_real(¶ms, "span")?.unwrap_or(defaults.span),
})
}
}
impl ScrubletParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let knn_params = KnnParams::from_r_list(r_list.clone())?;
let scrublet_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let log_transform = scrublet_list
.get("log_transform")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let mean_center = scrublet_list
.get("mean_center")
.and_then(|v| v.as_bool())
.unwrap_or(true);
let normalise_variance = scrublet_list
.get("normalise_variance")
.and_then(|v| v.as_bool())
.unwrap_or(true);
let target_size = scrublet_list
.get("target_size")
.and_then(|v| v.as_real())
.map(|x| x as f32);
let min_gene_var_pctl = scrublet_list
.get("min_gene_var_pctl")
.and_then(|v| v.as_real())
.unwrap_or(0.85) as f32;
let hvg_method = std::string::String::from(
scrublet_list
.get("hvg_method")
.and_then(|v| v.as_str())
.unwrap_or("vst"),
);
let loess_span = scrublet_list
.get("loess_span")
.and_then(|v| v.as_real())
.unwrap_or(0.3);
let clip_max = scrublet_list
.get("clip_max")
.and_then(|v| v.as_real())
.map(|x| x as f32);
let n_bins = scrublet_list
.get("n_bins")
.and_then(|v| v.as_integer())
.unwrap_or(20) as usize;
let binning_strategy = std::string::String::from(
scrublet_list
.get("binning_strategy")
.and_then(|v| v.as_str())
.unwrap_or("equal_width"),
);
let no_pcs = scrublet_list
.get("no_pcs")
.and_then(|v| v.as_integer())
.unwrap_or(30) as usize;
let svd_solver = match scrublet_list.get("svd_solver").and_then(|v| v.as_str()) {
Some(x) => parse_pca_solver(x).ok_or_else(|| format!("Invalid PCA solver: {}", x))?,
None => PcaSolver::Covariance,
};
let sim_doublet_ratio = scrublet_list
.get("sim_doublet_ratio")
.and_then(|v| v.as_real())
.unwrap_or(2.0) as f32;
let expected_doublet_rate = scrublet_list
.get("expected_doublet_rate")
.and_then(|v| v.as_real())
.unwrap_or(0.1) as f32;
let stdev_doublet_rate = scrublet_list
.get("stdev_doublet_rate")
.and_then(|v| v.as_real())
.unwrap_or(0.02) as f32;
let n_bins_hist = scrublet_list
.get("n_bins_histogram")
.or_else(|| scrublet_list.get("n_bins_hist"))
.and_then(|v| v.as_integer())
.unwrap_or(50) as usize;
let manual_threshold = scrublet_list
.get("manual_threshold")
.and_then(|v| v.as_real())
.map(|x| x as f32);
Ok(Self {
log_transform,
normalise_variance,
mean_center,
target_size,
min_gene_var_pctl,
hvg_method,
loess_span,
clip_max,
n_bins,
binning_strategy,
no_pcs,
svd_solver,
sim_doublet_ratio,
expected_doublet_rate,
stdev_doublet_rate,
n_bins_hist,
manual_threshold,
knn_params,
})
}
}
impl BoostParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let knn_params = KnnParams::from_r_list(r_list.clone())?;
let fast_cluster_params = FastLouvainParams::from_r_list(r_list.clone())?;
let params_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let log_transform = params_list
.get("log_transform")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let mean_center = params_list
.get("mean_center")
.and_then(|v| v.as_bool())
.unwrap_or(true);
let normalise_variance = params_list
.get("normalise_variance")
.and_then(|v| v.as_bool())
.unwrap_or(true);
let target_size = params_list
.get("target_size")
.and_then(|v| v.as_real())
.map(|x| x as f32);
let min_gene_var_pctl = params_list
.get("min_gene_var_pctl")
.and_then(|v| v.as_real())
.unwrap_or(0.85) as f32;
let hvg_method = std::string::String::from(
params_list
.get("hvg_method")
.and_then(|v| v.as_str())
.unwrap_or("vst"),
);
let loess_span = params_list
.get("loess_span")
.and_then(|v| v.as_real())
.unwrap_or(0.3);
let clip_max = params_list
.get("clip_max")
.and_then(|v| v.as_real())
.map(|x| x as f32);
let n_bins = params_list
.get("n_bins")
.and_then(|v| v.as_integer())
.unwrap_or(20) as usize;
let binning_strategy = std::string::String::from(
params_list
.get("binning_strategy")
.and_then(|v| v.as_str())
.unwrap_or("equal_width"),
);
let no_pcs = params_list
.get("no_pcs")
.and_then(|v| v.as_integer())
.unwrap_or(30) as usize;
let svd_solver = match params_list.get("svd_solver").and_then(|v| v.as_str()) {
Some(x) => parse_pca_solver(x).ok_or_else(|| format!("Invalid PCA solver: {}", x))?,
None => PcaSolver::Covariance,
};
let boost_rate = params_list
.get("boost_rate")
.and_then(|v| v.as_real())
.unwrap_or(0.25) as f32;
let replace = params_list
.get("replace")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let resolution = params_list
.get("resolution")
.and_then(|v| v.as_real())
.unwrap_or(1.0) as f32;
let louvain_iters = params_list
.get("louvain_iters")
.and_then(|v| v.as_integer())
.unwrap_or(10) as usize;
let n_iters = params_list
.get("n_iters")
.and_then(|v| v.as_integer())
.unwrap_or(10) as usize;
let p_thresh = params_list
.get("p_thresh")
.and_then(|v| v.as_real())
.unwrap_or(1e-7) as f32;
let voter_thresh = params_list
.get("voter_thresh")
.and_then(|v| v.as_real())
.unwrap_or(0.9) as f32;
let fast_cluster = params_list
.get("fast_cluster")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let km_type = std::string::String::from(
params_list
.get("km_type")
.and_then(|v| v.as_str())
.unwrap_or("minibatch"),
);
Ok(Self {
log_transform,
mean_center,
normalise_variance,
target_size,
min_gene_var_pctl,
hvg_method,
loess_span,
clip_max,
n_bins,
binning_strategy,
no_pcs,
svd_solver,
boost_rate,
replace,
resolution,
louvain_iters,
n_iters,
p_thresh,
voter_thresh,
knn_params,
fast_cluster,
fast_cluster_params,
km_type,
})
}
}
impl ScDblFinderParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let knn_params = KnnParams::from_r_list(r_list.clone())?;
let fast_cluster_params = FastLouvainParams::from_r_list(r_list.clone())?;
let map: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = Self::default();
let km_type = std::string::String::from(
map.get("km_type")
.and_then(|v| v.as_str())
.unwrap_or("minibatch"),
);
Ok(Self {
log_transform: map
.get("log_transform")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.log_transform),
mean_center: map
.get("mean_center")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.mean_center),
normalise_variance: map
.get("normalise_variance")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.normalise_variance),
target_size: map
.get("target_size")
.and_then(|v| v.as_real())
.map(|x| x as f32),
n_genes: map
.get("n_genes")
.and_then(|v| v.as_integer())
.unwrap_or(defaults.n_genes as i32) as usize,
doublet_ratio: map
.get("doublet_ratio")
.and_then(|v| v.as_real())
.unwrap_or(defaults.doublet_ratio as f64) as f32,
heterotypic_bias: map
.get("heterotypic_bias")
.and_then(|v| v.as_real())
.unwrap_or(defaults.heterotypic_bias as f64) as f32,
sim_params: ScDblSimParams::default(),
no_pcs: map
.get("no_pcs")
.and_then(|v| v.as_integer())
.unwrap_or(defaults.no_pcs as i32) as usize,
svd_solver: match map.get("svd_solver").and_then(|v| v.as_str()) {
Some(x) => {
parse_pca_solver(x).ok_or_else(|| format!("Invalid PCA solver: {}", x))?
}
None => defaults.svd_solver,
},
cluster_resolution: map
.get("cluster_resolution")
.and_then(|v| v.as_real())
.unwrap_or(defaults.cluster_resolution as f64)
as f32,
cluster_iters: map
.get("cluster_iters")
.and_then(|v| v.as_integer())
.unwrap_or(defaults.cluster_iters as i32) as usize,
fast_cluster: map
.get("fast_cluster")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.fast_cluster),
km_type,
fast_cluster_params,
knn_params,
n_iterations: map
.get("n_iterations")
.and_then(|v| v.as_integer())
.unwrap_or(defaults.n_iterations as i32) as usize,
gbm_n_trees: map
.get("n_trees")
.and_then(|v| v.as_integer())
.unwrap_or(defaults.gbm_n_trees as i32) as usize,
max_depth: map
.get("max_depth")
.and_then(|v| v.as_integer())
.unwrap_or(defaults.max_depth as i32) as usize,
learning_rate: map
.get("learning_rate")
.and_then(|v| v.as_real())
.unwrap_or(defaults.learning_rate as f64) as f32,
min_samples_leaf: map
.get("min_samples_leaf")
.and_then(|v| v.as_integer())
.unwrap_or(defaults.min_samples_leaf as i32) as usize,
subsample_rate: map
.get("subsample_rate")
.and_then(|v| v.as_real())
.unwrap_or(defaults.subsample_rate as f64) as f32,
cv_folds: map
.get("cv_folds")
.and_then(|v| v.as_integer())
.unwrap_or(defaults.cv_folds as i32) as usize,
cv_early_stop: map
.get("cv_early_stop")
.and_then(|v| v.as_integer())
.unwrap_or(defaults.cv_early_stop as i32) as usize,
se_fraction: map
.get("se_fraction")
.and_then(|v| v.as_real())
.unwrap_or(defaults.se_fraction as f64) as f32,
include_pcs: map
.get("include_pcs")
.and_then(|v| v.as_integer())
.unwrap_or(defaults.include_pcs as i32) as usize,
return_features: map
.get("return_features")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.return_features),
cxds_genes: map
.get("cxds_genes")
.and_then(|v| v.as_integer())
.map(|x| x as usize),
expected_doublet_rate: map
.get("expected_doublet_rate")
.and_then(|v| v.as_real())
.map(|x| x as f32),
manual_threshold: map
.get("manual_threshold")
.and_then(|v| v.as_real())
.map(|x| x as f32),
})
}
}
impl FastLouvainParams<f32> {
pub fn from_r_list(r_list: List) -> Result<Self> {
let knn_params = KnnParams::from_r_list(r_list.clone())?;
let kmeans_params = KMeansParamsWrappers::from_r_list(r_list.clone())?;
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults: FastLouvainParams<f32> = Self::default();
let same_weight = params
.get("same_weight")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.same_weight);
let n_centroids = params
.get("n_centroids")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.n_centroids);
let batch_size = params
.get("batch_size")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.batch_size);
let drift_threshold: f32 = params
.get("drift_threshold")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.drift_threshold);
let lr_alpha: f32 = params
.get("lr_alpha")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.lr_alpha);
let louvain_iters = params
.get("louvain_iters")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.louvain_iters);
let multi_level_louvain = params
.get("multi_level_louvain")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.multi_level_louvain);
let full_snn = params
.get("full_snn")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.full_snn);
let pruning = params
.get("pruning")
.and_then(|v| v.as_real())
.map(|x| x as f32);
let snn_similarity = std::string::String::from(
params
.get("snn_similarity")
.and_then(|v| v.as_str())
.unwrap_or("jaccard"),
);
Ok(Self {
n_centroids,
kmeans_params,
batch_size,
drift_threshold,
lr_alpha,
louvain_iters,
knn_params,
full_snn,
pruning,
snn_similarity,
multi_level_louvain,
same_weight,
})
}
}
impl SingleCellPcaParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = SingleCellPcaParams::default();
let mean_center = params
.get("mean_center")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.mean_center);
let normalise_variance = params
.get("normalise_variance")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.normalise_variance);
let svd_solver = match params.get("svd_solver").and_then(|v| v.as_str()) {
Some(x) => parse_pca_solver(x).ok_or_else(|| format!("Invalid PCA solver: {}", x))?,
None => defaults.svd_solver,
};
let clr = params
.get("clr")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.clr);
let size_factor = params
.get("size_factor")
.and_then(|v| v.as_real())
.unwrap_or(1e4) as f32;
Ok(Self {
mean_center,
normalise_variance,
svd_solver,
clr,
size_factor,
})
}
}
impl BbknnParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let knn_params = KnnParams::from_r_list(r_list.clone())?;
let bbknn_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let neighbours_within_batch = bbknn_list
.get("neighbours_within_batch")
.and_then(|v| v.as_integer())
.unwrap_or(3) as usize;
let set_op_mix_ratio = bbknn_list
.get("set_op_mix_ratio")
.and_then(|v| v.as_real())
.unwrap_or(1.0) as f32;
let local_connectivity = bbknn_list
.get("local_connectivity")
.and_then(|v| v.as_real())
.unwrap_or(1.0) as f32;
let trim = bbknn_list
.get("trim")
.and_then(|v| v.as_integer())
.unwrap_or(10 * neighbours_within_batch as i32) as usize;
Ok(Self {
neighbours_within_batch,
set_op_mix_ratio,
local_connectivity,
trim: Some(trim),
knn_params,
})
}
}
impl FastMnnParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let knn_params = KnnParams::from_r_list(r_list.clone())?;
let pca_params = SingleCellPcaParams::from_r_list(r_list.clone())?;
let fastmnn_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let ndist = fastmnn_list
.get("ndist")
.and_then(|v| v.as_real())
.unwrap_or(3.0) as f32;
let cos_norm = fastmnn_list
.get("cos_norm")
.and_then(|v| v.as_logical())
.map(|rb| rb.is_true())
.unwrap_or(true);
let no_pcs = fastmnn_list
.get("no_pcs")
.and_then(|v| v.as_integer())
.unwrap_or(30) as usize;
let sparse_svd = fastmnn_list
.get("sparse_svd")
.and_then(|v| v.as_bool())
.unwrap_or(true);
Ok(Self {
ndist,
no_pcs,
sparse_svd,
cos_norm,
knn_params,
pca_params,
})
}
}
impl SeuratCcaParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let knn_params = KnnParams::from_r_list(r_list.clone())?;
let pca_params = SingleCellPcaParams::from_r_list(r_list.clone())?;
let cca_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let num_cc = cca_list
.get("num_cc")
.and_then(|v| v.as_integer())
.unwrap_or(30) as usize;
let dims = cca_list
.get("dims")
.and_then(|v| v.as_integer())
.unwrap_or(30) as usize;
let k_anchor = cca_list
.get("k_anchor")
.and_then(|v| v.as_integer())
.unwrap_or(5) as usize;
let k_filter = cca_list
.get("k_filter")
.and_then(|v| v.as_integer())
.unwrap_or(200) as usize;
let k_score = cca_list
.get("k_score")
.and_then(|v| v.as_integer())
.unwrap_or(30) as usize;
let k_weight = cca_list
.get("k_weight")
.and_then(|v| v.as_integer())
.unwrap_or(100) as usize;
let n_top_features = cca_list
.get("n_top_features")
.and_then(|v| v.as_integer())
.unwrap_or(200) as usize;
let l2_norm = cca_list
.get("l2_norm")
.and_then(|v| v.as_logical())
.map(|rb| rb.is_true())
.unwrap_or(true);
let sd = cca_list.get("sd").and_then(|v| v.as_real()).unwrap_or(1.0) as f32;
Ok(Self {
num_cc,
dims,
k_anchor,
k_filter,
k_score,
k_weight,
n_top_features,
l2_norm,
sd,
knn_params,
pca_params,
})
}
}
impl SeuratRpcaParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let knn_params = KnnParams::from_r_list(r_list.clone())?;
let pca_params = SingleCellPcaParams::from_r_list(r_list.clone())?;
let rpca_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let dims = rpca_list
.get("dims")
.and_then(|v| v.as_integer())
.unwrap_or(30) as usize;
let k_anchor = rpca_list
.get("k_anchor")
.and_then(|v| v.as_integer())
.unwrap_or(5) as usize;
let k_score = rpca_list
.get("k_score")
.and_then(|v| v.as_integer())
.unwrap_or(30) as usize;
let k_weight = rpca_list
.get("k_weight")
.and_then(|v| v.as_integer())
.unwrap_or(100) as usize;
let l2_norm = rpca_list
.get("l2_norm")
.and_then(|v| v.as_logical())
.map(|rb| rb.is_true())
.unwrap_or(true);
let sd = rpca_list.get("sd").and_then(|v| v.as_real()).unwrap_or(1.0) as f32;
Ok(Self {
dims,
k_anchor,
k_score,
k_weight,
l2_norm,
sd,
knn_params,
pca_params,
})
}
}
impl MiloRParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let knn_params = KnnParams::from_r_list(r_list.clone())?;
let params_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let prop = params_list
.get("prop")
.and_then(|v| v.as_real())
.unwrap_or(0.2);
let k_refine = params_list
.get("k_refine")
.and_then(|v| v.as_integer())
.unwrap_or(20) as usize;
let index_type = std::string::String::from(
params_list
.get("index_type")
.and_then(|v| v.as_str())
.unwrap_or("annoy"),
);
let refinement_strategy = std::string::String::from(
params_list
.get("refinement_strategy")
.and_then(|v| v.as_str())
.unwrap_or("approximate"),
);
Ok(Self {
prop,
k_refine,
refinement_strategy,
index_type,
knn_params,
})
}
}
impl SEACellsParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let defaults = Self::default();
let knn_params = KnnParams::from_r_list(r_list.clone())?;
let seacells_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let lanczos_params = LanczosParams::from_r_list(&seacells_list)?;
let n_sea_cells = seacells_list
.get("n_sea_cells")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.n_sea_cells);
let max_fw_iters = seacells_list
.get("max_fw_iters")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.max_fw_iters);
let convergence_epsilon = seacells_list
.get("convergence_epsilon")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.convergence_epsilon);
let max_iter = seacells_list
.get("max_iter")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.max_iter);
let min_iter = seacells_list
.get("min_iter")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.min_iter);
let greedy_threshold = seacells_list
.get("greedy_threshold")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.greedy_threshold);
let graph_building = seacells_list
.get("graph_building")
.and_then(|v| v.as_str())
.map(String::from)
.unwrap_or(defaults.graph_building);
let pruning = seacells_list
.get("pruning")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.pruning);
let pruning_threshold = seacells_list
.get("pruning_threshold")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.pruning_threshold);
let n_landmarks = seacells_list
.get("n_landmarks")
.and_then(|v| v.as_integer())
.map(|x| x as usize);
Ok(Self {
n_sea_cells,
max_fw_iters,
convergence_epsilon,
max_iter,
min_iter,
greedy_threshold,
graph_building,
pruning,
pruning_threshold,
n_landmarks,
knn_params,
lanczos_params,
})
}
}
impl BootstrappedMetaCellParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let knn_params = KnnParams::from_r_list(r_list.clone())?;
let meta_cell_params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let max_shared = meta_cell_params
.get("max_shared")
.and_then(|v| v.as_integer())
.unwrap_or(15) as usize;
let target_no_metacells = meta_cell_params
.get("target_no_metacells")
.and_then(|v| v.as_integer())
.unwrap_or(1000) as usize;
let max_iter = meta_cell_params
.get("max_iter")
.and_then(|v| v.as_integer())
.unwrap_or(5000) as usize;
Ok(Self {
max_shared,
target_no_metacells,
max_iter,
knn_params,
})
}
}
impl SuperCellParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let knn_params = KnnParams::from_r_list(r_list.clone())?;
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let walk_length = params
.get("walk_length")
.and_then(|v| v.as_integer())
.unwrap_or(3) as usize;
let graining_factor = params
.get("graining_factor")
.and_then(|v| v.as_real())
.unwrap_or(50.0);
let use_kernel = params
.get("use_kernel")
.and_then(|v| v.as_bool())
.unwrap_or(true);
let k_ith = params
.get("k_ith")
.and_then(|v| v.as_integer())
.map(|x| x as usize);
let max_support = params
.get("max_support")
.and_then(|v| v.as_integer())
.map(|x| x as usize);
Ok(Self {
walk_length,
graining_factor,
knn_params,
use_kernel,
k_ith,
max_support,
})
}
}
impl SignatureGenes {
pub fn from_r_list(r_list: List) -> Result<Self> {
let r_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let positive: Vec<usize> = r_list
.get("pos")
.and_then(|v| v.as_integer_vector())
.unwrap_or_default()
.iter()
.map(|x| *x as usize)
.collect();
let negative: Vec<usize> = r_list
.get("neg")
.and_then(|v| v.as_integer_vector())
.unwrap_or_default()
.iter()
.map(|x| *x as usize)
.collect();
Ok(Self { positive, negative })
}
}
impl HotSpotParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let knn_params = KnnParams::from_r_list(r_list.clone())?;
let params_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let model = std::string::String::from(
params_list
.get("model")
.and_then(|v| v.as_str())
.unwrap_or("normal"),
);
let normalise = params_list
.get("normalise")
.and_then(|v| v.as_bool())
.unwrap_or(true);
let defaults = HotSpotGraphParams::default();
let weighted_graph = params_list
.get("weighted_graph")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.weighted_graph);
let neighborhood_factor = params_list
.get("neighborhood_factor")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.filter(|v| *v > 0.0)
.unwrap_or(defaults.neighborhood_factor);
Ok(Self {
model,
normalise,
knn_params,
graph_params: HotSpotGraphParams::new(weighted_graph, neighborhood_factor),
})
}
}
impl HarmonyParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let defaults = Self::default();
let kmeans_params =
KMeansParamsWrappers::from_r_list_with_iters(r_list.clone(), HARMONY_KMEANS_ITERS)?;
let params_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let k = params_list
.get("k")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.k);
let sigma = params_list
.get("sigma")
.and_then(|v| v.as_real_vector())
.map(|v| v.iter().map(|&x| x as f32).collect())
.unwrap_or(defaults.sigma);
let theta = params_list
.get("theta")
.and_then(|v| v.as_real_vector())
.map(|v| v.iter().map(|&x| x as f32).collect())
.unwrap_or(defaults.theta);
let lambda = params_list
.get("lambda")
.and_then(|v| v.as_real_vector())
.map(|v| v.iter().map(|&x| x as f32).collect())
.unwrap_or(defaults.lambda);
let block_size = params_list
.get("block_size")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.block_size);
let max_iter_kmeans = params_list
.get("max_iter_kmeans")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.max_iter_kmeans);
let max_iter_harmony = params_list
.get("max_iter_harmony")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.max_iter_harmony);
let epsilon_kmeans = params_list
.get("epsilon_kmeans")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.epsilon_kmeans);
let epsilon_harmony = params_list
.get("epsilon_harmony")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.epsilon_harmony);
let window_size = params_list
.get("window_size")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.window_size);
Ok(Self {
k,
sigma,
theta,
lambda,
block_size,
max_iter_kmeans,
max_iter_harmony,
epsilon_kmeans,
epsilon_harmony,
window_size,
kmeans_params,
})
}
}
impl HarmonyParamsV2 {
pub fn from_r_list(r_list: List) -> Result<Self> {
let defaults = Self::default();
let kmeans_params =
KMeansParamsWrappers::from_r_list_with_iters(r_list.clone(), HARMONY_KMEANS_ITERS)?;
let params_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let k = params_list
.get("k")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.k);
let sigma = params_list
.get("sigma")
.and_then(|v| v.as_real_vector())
.map(|v| v.iter().map(|&x| x as f32).collect())
.unwrap_or(defaults.sigma);
let theta = params_list
.get("theta")
.and_then(|v| v.as_real_vector())
.map(|v| v.iter().map(|&x| x as f32).collect())
.unwrap_or(defaults.theta);
let lambda = params_list
.get("lambda")
.and_then(|v| v.as_real_vector())
.map(|v| v.iter().map(|&x| x as f32).collect())
.unwrap_or(defaults.lambda);
let block_size = params_list
.get("block_size")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.block_size);
let max_iter_kmeans = params_list
.get("max_iter_kmeans")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.max_iter_kmeans);
let max_iter_harmony = params_list
.get("max_iter_harmony")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.max_iter_harmony);
let epsilon_kmeans = params_list
.get("epsilon_kmeans")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.epsilon_kmeans);
let epsilon_harmony = params_list
.get("epsilon_harmony")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.epsilon_harmony);
let window_size = params_list
.get("window_size")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.window_size);
let alpha = params_list
.get("alpha")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.alpha);
let tau = params_list
.get("tau")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.tau);
let batch_proportion_cutoff = params_list
.get("batch_proportion_cutoff")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.batch_proportion_cutoff);
let use_dynamic_lambda = params_list
.get("use_dynamic_lambda")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.use_dynamic_lambda);
Ok(Self {
k,
sigma,
theta,
lambda,
block_size,
max_iter_kmeans,
max_iter_harmony,
epsilon_kmeans,
epsilon_harmony,
window_size,
alpha,
tau,
batch_proportion_cutoff,
use_dynamic_lambda,
kmeans_params,
})
}
}
impl SymphonyMapParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = SymphonyMapParams::default();
let sigma = params_list
.get("sigma")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.sigma);
let lambda = params_list
.get("lambda")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.lambda);
Ok(Self { sigma, lambda })
}
}
impl RandomForestConfig {
pub fn from_r_list(r_list: List) -> Result<Self> {
let defaults = Self::default();
let params_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let n_trees = params_list
.get("n_trees")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.n_trees);
let min_samples_leaf = params_list
.get("min_samples_leaf")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.min_samples_leaf);
let n_features_split = params_list
.get("n_features_split")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.n_features_split);
let subsample_rate = params_list
.get("subsample_rate")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.subsample_rate);
let bootstrap = params_list
.get("bootstrap")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.bootstrap);
let max_depth = params_list
.get("max_depth")
.and_then(|v| v.as_integer())
.map(|v| Some(v as usize))
.unwrap_or(defaults.max_depth);
let subsample_frac = params_list
.get("subsample_frac")
.and_then(|v| v.as_real())
.map(|v| Some(v as f32))
.unwrap_or(defaults.subsample_frac);
Ok(Self {
n_trees,
min_samples_leaf,
n_features_split,
subsample_rate,
bootstrap,
max_depth,
subsample_frac,
})
}
}
impl ExtraTreesConfig {
pub fn from_r_list(r_list: List) -> Result<Self> {
let defaults = Self::default();
let params_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let n_trees = params_list
.get("n_trees")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.n_trees);
let min_samples_leaf = params_list
.get("min_samples_leaf")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.min_samples_leaf);
let n_features_split = params_list
.get("n_features_split")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.n_features_split);
let n_thresholds = params_list
.get("n_thresholds")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.n_thresholds);
let max_depth = params_list
.get("max_depth")
.and_then(|v| v.as_integer())
.map(|v| Some(v as usize))
.unwrap_or(defaults.max_depth);
let subsample_frac = params_list
.get("subsample_frac")
.and_then(|v| v.as_real())
.map(|v| Some(v as f32))
.unwrap_or(defaults.subsample_frac);
Ok(Self {
n_trees,
min_samples_leaf,
n_features_split,
n_thresholds,
max_depth,
subsample_frac,
})
}
}
impl GradientBoostingConfig {
pub fn from_r_list(r_list: List) -> Result<Self> {
let defaults = Self::default();
let params_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let n_trees_max = params_list
.get("n_trees_max")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.n_trees_max);
let learning_rate = params_list
.get("learning_rate")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.learning_rate);
let max_depth = params_list
.get("max_depth")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.max_depth);
let min_samples_leaf = params_list
.get("min_samples_leaf")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.min_samples_leaf);
let early_stop_window = params_list
.get("early_stop_window")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.early_stop_window);
let subsample_rate = params_list
.get("subsample_rate")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.subsample_rate);
let n_features_split = params_list
.get("n_features_split")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.n_features_split);
Ok(Self {
n_trees_max,
learning_rate,
max_depth,
min_samples_leaf,
early_stop_window,
subsample_rate,
n_features_split,
})
}
}
impl ScenicParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let defaults = Self::default();
let params: HashMap<&str, Robj> = r_list_to_map(r_list.clone())?;
let min_counts = params
.get("min_counts")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.min_counts);
let min_cells = params
.get("min_cells")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.min_cells);
let gene_batch_strategy = params
.get("gene_batch_strategy")
.and_then(|v| v.as_str())
.map(String::from)
.unwrap_or(defaults.gene_batch_strategy);
let gene_batch_size = params
.get("gene_batch_size")
.and_then(|v| v.as_integer())
.map(|v| Some(v as usize))
.unwrap_or(defaults.gene_batch_size);
let n_pcs = params
.get("n_pcs")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.n_pcs);
let n_subsample = params
.get("n_subsample")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.n_subsample);
let learner_type = params
.get("learner_type")
.and_then(|v| v.as_str())
.unwrap_or("randomforest");
let regression_learner = match learner_type.to_lowercase().as_str() {
"extratrees" => {
RegressionLearner::ExtraTrees(ExtraTreesConfig::from_r_list(r_list.clone())?)
}
"grnboost2" => RegressionLearner::GradientBoosting(
GradientBoostingConfig::from_r_list(r_list.clone())?,
),
_ => RegressionLearner::RandomForest(RandomForestConfig::from_r_list(r_list.clone())?),
};
Ok(Self {
min_counts,
min_cells,
regression_learner,
gene_batch_strategy,
gene_batch_size,
n_pcs,
n_subsample,
})
}
}
impl H5adFileTask {
pub fn from_r_list(r_list: List) -> extendr_api::Result<Self> {
let map: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let raw_slot: RawDataSlot = match map.get("raw_slot").and_then(|v| v.as_str()) {
Some(s) => {
parse_raw_slot(s).ok_or_else(|| Error::Other(format!("Invalid raw slot: {}", s)))?
}
None => RawDataSlot::default(),
};
let exp_id = map
.get("exp_id")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::Other("exp_id missing or not a string".into()))?
.to_string();
let h5_path = map
.get("h5_path")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::Other("h5_path missing or not a string".into()))?
.to_string();
let cs_type_str = map
.get("cs_type")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::Other("cs_type missing or not a string".into()))?;
let cs_type = parse_h5ad_format(cs_type_str)
.ok_or_else(|| Error::Other("cs_type must be 'csr' or 'csc'".into()))?;
let no_cells = map
.get("no_cells")
.and_then(|v| v.as_integer())
.ok_or_else(|| Error::Other("no_cells missing or not an integer".into()))?
as usize;
let no_genes = map
.get("no_genes")
.and_then(|v| v.as_integer())
.ok_or_else(|| Error::Other("no_genes missing or not an integer".into()))?
as usize;
let mapping_raw: Vec<i32> = map
.get("gene_local_to_universe")
.ok_or_else(|| Error::Other("gene_local_to_universe missing".into()))?
.as_integer_slice()
.ok_or_else(|| Error::Other("gene_local_to_universe must be an integer vector".into()))?
.to_vec();
let gene_local_to_universe: Vec<Option<usize>> = mapping_raw
.into_iter()
.map(|v| {
if v == i32::MIN {
None
} else {
Some(v as usize)
}
})
.collect();
Ok(Self {
exp_id,
h5_path,
cs_type,
no_cells,
no_genes,
gene_local_to_universe,
raw_slot,
})
}
}
impl BinMergeTask {
pub fn from_r_list(r_list: List) -> extendr_api::Result<Self> {
let map: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let exp_id = map
.get("exp_id")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::Other("exp_id missing or not a string".into()))?
.to_string();
let bin_cells_path = map
.get("bin_cells_path")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::Other("bin_cells_path missing or not a string".into()))?
.to_string();
let cells_to_keep: Vec<usize> = map
.get("cells_to_keep")
.ok_or_else(|| Error::Other("cells_to_keep missing".into()))?
.as_integer_slice()
.ok_or_else(|| Error::Other("cells_to_keep must be an integer vector".into()))?
.iter()
.map(|&x| x as usize)
.collect();
let mapping_raw: Vec<i32> = map
.get("gene_local_to_universe")
.ok_or_else(|| Error::Other("gene_local_to_universe missing".into()))?
.as_integer_slice()
.ok_or_else(|| Error::Other("gene_local_to_universe must be an integer vector".into()))?
.to_vec();
let gene_local_to_universe: Vec<Option<u32>> = mapping_raw
.into_iter()
.map(|v| if v == i32::MIN { None } else { Some(v as u32) })
.collect();
Ok(Self {
exp_id,
bin_cells_path,
cells_to_keep,
gene_local_to_universe,
})
}
}
impl MtxFileTask {
pub fn from_r_list(r_list: List) -> extendr_api::Result<Self> {
let map: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let exp_id = map
.get("exp_id")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::Other("exp_id missing or not a string".into()))?
.to_string();
let mtx_path = map
.get("mtx_path")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::Other("mtx_path missing or not a string".into()))?
.to_string();
let cells_as_rows = map
.get("cells_as_rows")
.and_then(|v| v.as_bool())
.ok_or_else(|| Error::Other("cells_as_rows missing or not a boolean".into()))?;
let mapping_raw: Vec<i32> = map
.get("gene_local_to_universe")
.ok_or_else(|| Error::Other("gene_local_to_universe missing".into()))?
.as_integer_slice()
.ok_or_else(|| Error::Other("gene_local_to_universe must be an integer vector".into()))?
.to_vec();
let gene_local_to_universe: Vec<Option<usize>> = mapping_raw
.into_iter()
.map(|v| {
if v == i32::MIN {
None
} else {
Some(v as usize)
}
})
.collect();
Ok(Self {
exp_id,
mtx_path,
gene_local_to_universe,
cells_as_rows,
})
}
}
impl TenxFileTask {
pub fn from_r_list(r_list: List) -> extendr_api::Result<Self> {
let map: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let exp_id = map
.get("exp_id")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::Other("exp_id missing or not a string".into()))?
.to_string();
let h5_path = map
.get("h5_path")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::Other("h5_path missing or not a string".into()))?
.to_string();
let version_str = map
.get("version")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::Other("version missing or not a string".into()))?;
let version = parse_tenx_version(version_str)
.ok_or_else(|| Error::Other("version must be 'v2' or 'v3'".into()))?;
let no_cells = map
.get("no_cells")
.and_then(|v| v.as_integer())
.ok_or_else(|| Error::Other("no_cells missing or not an integer".into()))?
as usize;
let no_genes = map
.get("no_genes")
.and_then(|v| v.as_integer())
.ok_or_else(|| Error::Other("no_genes missing or not an integer".into()))?
as usize;
let mapping_raw: Vec<i32> = map
.get("gene_local_to_universe")
.ok_or_else(|| Error::Other("gene_local_to_universe missing".into()))?
.as_integer_slice()
.ok_or_else(|| Error::Other("gene_local_to_universe must be an integer vector".into()))?
.to_vec();
let gene_local_to_universe: Vec<Option<usize>> = mapping_raw
.into_iter()
.map(|v| {
if v == i32::MIN {
None
} else {
Some(v as usize)
}
})
.collect();
let feature_type = map
.get("feature_type")
.and_then(|v| v.as_str())
.map(|v| v.to_string());
Ok(Self {
exp_id,
h5_path,
version,
no_cells,
no_genes,
gene_local_to_universe,
feature_type,
})
}
}
impl<T> LigandTargetParams<T>
where
T: BixverseFloat,
{
pub fn from_r_list(r_list: List) -> extendr_api::Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = LigandTargetParams::default();
let lr_sig_hub = params
.get("lr_sig_hub")
.and_then(|x| x.as_real())
.and_then(|x| T::from_f64(x))
.unwrap_or(defaults.lr_sig_hub);
let gr_hub = params
.get("gr_hub")
.and_then(|x| x.as_real())
.and_then(|x| T::from_f64(x))
.unwrap_or(defaults.gr_hub);
let ltf_cutoff = params
.get("ltf_cutoff")
.and_then(|x| x.as_real())
.and_then(|x| T::from_f64(x))
.unwrap_or(defaults.ltf_cutoff);
let damping_factor = params
.get("damping_factor")
.and_then(|x| x.as_real())
.and_then(|x| T::from_f64(x))
.unwrap_or(defaults.damping_factor);
let tol = params
.get("tol")
.and_then(|x| x.as_real())
.and_then(|x| T::from_f64(x))
.unwrap_or(defaults.tol);
let max_iter = params
.get("max_iter")
.and_then(|x| x.as_integer())
.map(|x| x as usize)
.unwrap_or(defaults.max_iter);
let topology_correction = params
.get("topology_correction")
.and_then(|x| x.as_bool())
.unwrap_or(defaults.topology_correction);
let secondary_targets = params
.get("secondary_targets")
.and_then(|x| x.as_bool())
.unwrap_or(defaults.secondary_targets);
Ok(Self {
lr_sig_hub,
gr_hub,
ltf_cutoff,
damping_factor,
max_iter,
tol,
secondary_targets,
topology_correction,
})
}
}
impl<T> CellPhoneDbParams<T>
where
T: BixverseFloat,
{
pub fn from_r_list(r_list: List) -> extendr_api::Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = CellPhoneDbParams::default();
let n_perm = params
.get("n_perm")
.and_then(|x| x.as_integer())
.map(|x| x as usize)
.unwrap_or(defaults.n_perm);
let threshold = params
.get("threshold")
.and_then(|x| x.as_real())
.and_then(|x| T::from_f64(x))
.unwrap_or(defaults.threshold);
let seed = params
.get("seed")
.and_then(|x| x.as_integer())
.map(|x| x as usize)
.unwrap_or(defaults.seed);
let perm_batch = params
.get("perm_batch")
.and_then(|x| x.as_integer())
.map(|x| x as usize)
.or(defaults.perm_batch);
Ok(Self {
n_perm,
threshold,
seed,
perm_batch,
})
}
}
impl SelectParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = Self::default();
let downsample_min_samples = params
.get("downsample_min_samples")
.and_then(|v| v.as_integer())
.map(|v| v as u32)
.unwrap_or(defaults.downsample_min_samples);
let downsample_min_cell_quantile = params
.get("downsample_min_cell_quantile")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.downsample_min_cell_quantile);
let downsample_max_cell_quantile = params
.get("downsample_max_cell_quantile")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.downsample_max_cell_quantile);
let min_gene_total = params
.get("min_gene_total")
.and_then(|v| v.as_integer())
.map(|v| Some(v as u32))
.unwrap_or(defaults.min_gene_total);
let min_gene_top3 = params
.get("min_gene_top3")
.and_then(|v| v.as_integer())
.map(|v| Some(v as u32))
.unwrap_or(defaults.min_gene_top3);
let min_gene_relative_variance = params
.get("min_gene_relative_variance")
.and_then(|v| v.as_real())
.map(|v| Some(v as f32))
.unwrap_or(defaults.min_gene_relative_variance);
let min_genes = params
.get("min_genes")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.min_genes);
let relative_variance_window_size = params
.get("relative_variance_window_size")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.relative_variance_window_size);
Ok(Self {
downsample_min_samples,
downsample_min_cell_quantile,
downsample_max_cell_quantile,
min_gene_total,
min_gene_top3,
min_gene_relative_variance,
min_genes,
relative_variance_window_size,
lateral_gene_mask: defaults.lateral_gene_mask,
})
}
}
impl SimilarityParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = Self::default();
let method = match params.get("similarity_method").and_then(|v| v.as_str()) {
Some(s) => match s.to_lowercase().as_str() {
"log_pearson" | "logpearson" => SimilarityMethod::LogPearson,
"pearson" => SimilarityMethod::Pearson,
"spearman" => SimilarityMethod::Spearman,
other => {
return Err(Error::Other(format!(
"Invalid similarity method: {}",
other
)));
}
},
None => defaults.method,
};
let value_regularisation = params
.get("value_regularisation")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.value_regularisation);
Ok(Self {
method,
value_regularisation,
})
}
}
impl MC2KnnParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = Self::default();
let balanced_ranks_factor = params
.get("balanced_ranks_factor")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.balanced_ranks_factor);
let incoming_degree_factor = params
.get("incoming_degree_factor")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.incoming_degree_factor);
let outgoing_degree_factor = params
.get("outgoing_degree_factor")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.outgoing_degree_factor);
let min_outgoing_degree = params
.get("min_outgoing_degree")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.min_outgoing_degree);
let k_size_factor = params
.get("k_size_factor")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.k_size_factor);
let k_umis_quantile = params
.get("k_umis_quantile")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.k_umis_quantile);
let min_knn_k = params
.get("min_knn_k")
.and_then(|v| v.as_integer())
.map(|v| Some(v as usize))
.unwrap_or(defaults.min_knn_k);
let knn_k_override = params
.get("knn_k_override")
.and_then(|v| v.as_integer())
.map(|v| Some(v as usize))
.unwrap_or(defaults.knn_k_override);
Ok(Self {
balanced_ranks_factor,
incoming_degree_factor,
outgoing_degree_factor,
min_outgoing_degree,
k_size_factor,
k_umis_quantile,
min_knn_k,
knn_k_override,
})
}
}
impl PartitionParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = Self::default();
let cooldown_pass = params
.get("cooldown_pass")
.and_then(|v| v.as_real())
.unwrap_or(defaults.cooldown_pass);
let cooldown_node = params
.get("cooldown_node")
.and_then(|v| v.as_real())
.unwrap_or(defaults.cooldown_node);
let cooldown_phase = params
.get("cooldown_phase")
.and_then(|v| v.as_real())
.unwrap_or(defaults.cooldown_phase);
let min_split_size_factor = params
.get("min_split_size_factor")
.and_then(|v| v.as_real())
.unwrap_or(defaults.min_split_size_factor);
let max_merge_size_factor = params
.get("max_merge_size_factor")
.and_then(|v| v.as_real())
.unwrap_or(defaults.max_merge_size_factor);
let max_split_min_cut_strength = params
.get("max_split_min_cut_strength")
.and_then(|v| v.as_real())
.unwrap_or(defaults.max_split_min_cut_strength);
let min_cut_seed_cells = params
.get("min_cut_seed_cells")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.min_cut_seed_cells);
let min_seed_size_quantile = params
.get("min_seed_size_quantile")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.min_seed_size_quantile);
let max_seed_size_quantile = params
.get("max_seed_size_quantile")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.max_seed_size_quantile);
Ok(Self {
cooldown_pass,
cooldown_node,
cooldown_phase,
min_split_size_factor,
max_merge_size_factor,
max_split_min_cut_strength,
min_cut_seed_cells,
min_seed_size_quantile,
max_seed_size_quantile,
})
}
}
impl DeviantsParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = Self::default();
let min_gene_fold_factor = params
.get("min_gene_fold_factor")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.min_gene_fold_factor);
let max_gene_fraction = params
.get("max_gene_fraction")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.max_gene_fraction);
let max_cell_fraction = params
.get("max_cell_fraction")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.max_cell_fraction);
let gap_skip_cells = params
.get("gap_skip_cells")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.gap_skip_cells);
let max_gap_cells_count = params
.get("max_gap_cells_count")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.max_gap_cells_count);
let max_gap_cells_fraction = params
.get("max_gap_cells_fraction")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.max_gap_cells_fraction);
let min_compare_umis = params
.get("min_compare_umis")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.min_compare_umis);
let cells_regularisation_quantile = params
.get("cells_regularisation_quantile")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.cells_regularisation_quantile);
Ok(Self {
min_gene_fold_factor,
max_gene_fraction,
max_cell_fraction,
gap_skip_cells,
max_gap_cells_count,
max_gap_cells_fraction,
min_compare_umis,
cells_regularisation_quantile,
})
}
}
impl DissolveParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = Self::default();
let min_robust_size_factor = params
.get("min_robust_size_factor")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.min_robust_size_factor);
let min_convincing_gene_fold_factor = params
.get("min_convincing_gene_fold_factor")
.and_then(|v| v.as_real())
.map(|v| Some(v as f32))
.unwrap_or(defaults.min_convincing_gene_fold_factor);
Ok(Self {
min_robust_size_factor,
min_convincing_gene_fold_factor,
})
}
}
impl MetacellsParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let select = SelectParams::from_r_list(r_list.clone())?;
let similarity = SimilarityParams::from_r_list(r_list.clone())?;
let knn = MC2KnnParams::from_r_list(r_list.clone())?;
let partition = PartitionParams::from_r_list(r_list.clone())?;
let deviants = DeviantsParams::from_r_list(r_list.clone())?;
let dissolve = DissolveParams::from_r_list(r_list.clone())?;
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = Self::default();
let target_metacell_size = params
.get("target_metacell_size")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.target_metacell_size);
let min_metacell_size = params
.get("min_metacell_size")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.min_metacell_size);
let target_metacell_umis = params
.get("target_metacell_umis")
.and_then(|v| v.as_real())
.map(|v| v as u64)
.unwrap_or(defaults.target_metacell_umis);
let must_complete_cover = params
.get("must_complete_cover")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.must_complete_cover);
let random_seed = params
.get("random_seed")
.and_then(|v| v.as_real())
.map(|v| v as u64)
.unwrap_or(defaults.random_seed);
Ok(Self {
select,
similarity,
knn,
partition,
deviants,
dissolve,
target_metacell_size,
min_metacell_size,
target_metacell_umis,
must_complete_cover,
random_seed,
})
}
}
impl CellTypeMarkers {
pub fn from_r_list(r_list: List) -> Result<Self> {
let data: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let cell_type = data
.get("cell_type")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::Other("cell_type missing or not a string".into()))?
.to_string();
let positive_indices: Vec<usize> = data
.get("positive_indices")
.and_then(|v| v.as_integer_vector())
.map(|v| v.r_int_convert())
.unwrap_or_default();
let negative_indices: Vec<usize> = data
.get("negative_indices")
.and_then(|v| v.as_integer_vector())
.map(|v| v.r_int_convert())
.unwrap_or_default();
Ok(Self {
cell_type,
positive_indices,
negative_indices,
})
}
}
impl SctypeRes {
pub fn from_r_list(r_list: List) -> Result<Self> {
let data: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let cell_types = data
.get("cell_types")
.and_then(|v| v.as_string_vector())
.ok_or_else(|| Error::Other("cell_types missing or not a string vector".into()))?;
let scores = data
.get("scores")
.and_then(|v| v.as_real_vector())
.ok_or_else(|| Error::Other("scores missing or not a real vector".into()))?
.r_float_convert();
let n_cells: usize = data
.get("n_cells")
.and_then(|v| v.as_real())
.map(|v| v as usize)
.ok_or_else(|| Error::Other("n_cells missing or not an integer".into()))?;
let n_cell_types: usize = data
.get("n_cell_types")
.and_then(|v| v.as_real())
.map(|v| v as usize)
.ok_or_else(|| Error::Other("n_cell_types missing or not an integer".into()))?;
Ok(Self {
cell_types,
scores,
n_cells,
n_cell_types,
})
}
}
impl ScTypeCellParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let data: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let calibration =
match data.get("calibration").and_then(|v| v.as_str()) {
Some(s) => Some(parse_score_calibration(s).ok_or_else(|| {
Error::Other(format!("Invalid ScType score calibration: {}", s))
})?),
None => None,
};
Ok(Self::new(
data.get("alpha")
.and_then(|v| v.as_real())
.map(|x| x as f32),
data.get("iterations")
.and_then(|v| v.as_integer())
.map(|x| x as usize),
data.get("tolerance")
.and_then(|v| v.as_real())
.map(|x| x as f32),
calibration,
data.get("score_floor")
.and_then(|v| v.as_real())
.map(|x| x as f32),
data.get("purity_threshold")
.and_then(|v| v.as_real())
.map(|x| x as f32),
))
}
}
impl MeldParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let knn_params = KnnParams::from_r_list(r_list.clone())?;
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = Self::default();
let beta = params
.get("beta")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.beta);
let offset = params
.get("offset")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.offset);
let order = params
.get("order")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.order);
let filter = match params.get("filter").and_then(|v| v.as_str()) {
Some(s) => parse_meld_filter(s)
.ok_or_else(|| Error::Other(format!("Invalid MELD filter: {}", s)))?,
None => defaults.filter,
};
let chebyshev_order = params
.get("chebyshev_order")
.and_then(|v| v.as_integer())
.map(|v| v as usize)
.unwrap_or(defaults.chebyshev_order);
let lap_type = match params.get("lap_type").and_then(|v| v.as_str()) {
Some(s) => parse_lap_type(s)
.ok_or_else(|| Error::Other(format!("Invalid MELD Laplacian type: {}", s)))?,
None => defaults.lap_type,
};
let normalise_indicators = params
.get("normalise_indicators")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.normalise_indicators);
Ok(Self {
beta,
offset,
order,
filter,
chebyshev_order,
lap_type,
normalise_indicators,
knn_params,
})
}
}
impl AucellParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = Self::default();
let auc_type = match params.get("auc_type").and_then(|v| v.as_str()) {
Some(s) => {
parse_auc_type(s).ok_or_else(|| Error::Other(format!("Invalid AUC type: {}", s)))?
}
None => defaults.auc_type,
};
let max_rank = params
.get("max_rank")
.and_then(|v| v.as_real())
.map(|v| v as usize)
.or(defaults.max_rank);
let standardise = params
.get("standardise")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.standardise);
Ok(Self {
auc_type,
max_rank,
standardise,
})
}
}
impl BinariseParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = Self::default();
let bw_adjust = params
.get("bw_adjust")
.and_then(|v| v.as_real())
.unwrap_or(defaults.bw_adjust);
let n_grid = params
.get("n_grid")
.and_then(|v| v.as_real())
.map(|v| v as usize)
.unwrap_or(defaults.n_grid);
let n_bins = params
.get("n_bins")
.and_then(|v| v.as_real())
.map(|v| v as usize)
.unwrap_or(defaults.n_bins);
Ok(Self {
bw_adjust,
n_grid,
n_bins,
})
}
}
impl PalantirParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let defaults = Self::default();
let knn_params = KnnParams::from_r_list(r_list.clone())?;
let palantir_list: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let lanczos_params = LanczosParams::from_r_list(&palantir_list)?;
let n_dcs = r_list_count(&palantir_list, "n_dcs")?.unwrap_or(defaults.n_dcs);
let n_eigs = r_list_count(&palantir_list, "n_eigs")?;
let knn = r_list_count(&palantir_list, "knn")?.unwrap_or(defaults.knn);
let num_waypoints =
r_list_count(&palantir_list, "num_waypoints")?.unwrap_or(defaults.num_waypoints);
let scale_components = palantir_list
.get("scale_components")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.scale_components);
let use_early_cell_as_start = palantir_list
.get("use_early_cell_as_start")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.use_early_cell_as_start);
let max_iterations =
r_list_count(&palantir_list, "max_iterations")?.unwrap_or(defaults.max_iterations);
let branch_prob_threshold = palantir_list
.get("branch_prob_threshold")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.branch_prob_threshold);
Ok(Self {
n_dcs,
n_eigs,
knn,
num_waypoints,
scale_components,
use_early_cell_as_start,
max_iterations,
branch_prob_threshold,
knn_params,
lanczos_params,
})
}
}
impl MagicParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let defaults = Self::default();
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let n_steps = r_list_count_allow_zero(¶ms, "n_steps")?.unwrap_or(defaults.n_steps);
let clip_threshold = params
.get("clip_threshold")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.clip_threshold);
let gene_batch_size =
r_list_count(¶ms, "gene_batch_size")?.unwrap_or(defaults.gene_batch_size);
let layer = params
.get("layer")
.and_then(|v| v.as_str())
.and_then(parse_magic_layer)
.unwrap_or(defaults.layer);
let allow_large = params
.get("allow_large")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.allow_large);
Ok(Self {
n_steps,
clip_threshold,
gene_batch_size,
layer,
allow_large,
})
}
}
pub fn parse_magic_layer(s: &str) -> Option<MagicLayer> {
match s.to_lowercase().as_str() {
"norm" | "normalised" | "normalized" => Some(MagicLayer::Norm),
"raw" | "counts" => Some(MagicLayer::Raw),
_ => None,
}
}
impl BranchSelectionParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let defaults = Self::default();
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let q = params
.get("q")
.and_then(|v| v.as_real())
.unwrap_or(defaults.q);
let eps = params
.get("eps")
.and_then(|v| v.as_real())
.unwrap_or(defaults.eps);
let resolution = r_list_count(¶ms, "resolution")?.unwrap_or(defaults.resolution);
Ok(Self { q, eps, resolution })
}
}
impl LandmarkGpParams {
pub fn from_r_map(params: &HashMap<&str, Robj>) -> Result<Self> {
let defaults = Self::default();
let length_scale = params
.get("length_scale")
.and_then(|v| v.as_real())
.unwrap_or(defaults.length_scale);
let sigma = params
.get("sigma")
.and_then(|v| v.as_real())
.unwrap_or(defaults.sigma);
let jitter = params
.get("jitter")
.and_then(|v| v.as_real())
.unwrap_or(defaults.jitter);
let max_jitter_retries = r_list_count_allow_zero(params, "max_jitter_retries")?
.unwrap_or(defaults.max_jitter_retries);
let chunk_size =
r_list_count_allow_zero(params, "chunk_size")?.unwrap_or(defaults.chunk_size);
Ok(Self {
length_scale,
sigma,
jitter,
max_jitter_retries,
chunk_size,
})
}
}
impl GeneTrendsParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let defaults = Self::default();
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let gp = LandmarkGpParams::from_r_map(¶ms)?;
let resolution = r_list_count(¶ms, "resolution")?.unwrap_or(defaults.resolution);
let weighting = params
.get("weighting")
.and_then(|v| v.as_str())
.and_then(parse_branch_weighting)
.unwrap_or(defaults.weighting);
Ok(Self {
resolution,
weighting,
gp,
})
}
}
pub fn parse_branch_weighting(s: &str) -> Option<BranchWeighting> {
match s.to_lowercase().as_str() {
"hard_mask" | "hardmask" | "mask" => Some(BranchWeighting::HardMask),
"fate_probability" | "fateprobability" | "fate_prob" => {
Some(BranchWeighting::FateProbability)
}
_ => None,
}
}
impl PmdParams {
pub fn from_r_map(params: &HashMap<&str, Robj>) -> Result<Self> {
let defaults = Self::default();
let k = r_list_count(params, "k")?.unwrap_or(defaults.k);
let n_permutations =
r_list_count(params, "n_permutations")?.unwrap_or(defaults.n_permutations);
let extra_sparse = params
.get("extra_sparse")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.extra_sparse);
let abn_c = r_list_count_allow_zero(params, "abn_c")?.unwrap_or(defaults.abn_c);
let p_anova = params
.get("p_anova")
.and_then(|v| v.as_real())
.unwrap_or(defaults.p_anova);
let centre = params
.get("centre")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.centre);
let cap = params
.get("cap")
.and_then(|v| v.as_real())
.unwrap_or(defaults.cap);
let spatial = params
.get("spatial")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.spatial);
let n_genes = r_list_count(params, "n_genes")?.unwrap_or(defaults.n_genes);
let min_ci = params
.get("min_ci")
.and_then(|v| v.as_real())
.unwrap_or(defaults.min_ci);
let averaging = match params.get("averaging").and_then(|v| v.as_str()) {
Some(s) => parse_averaging(s)
.ok_or_else(|| Error::Other(format!("Invalid DIALOGUE averaging: {}", s)))?,
None => defaults.averaging,
};
let mcp_assignment_p = params
.get("mcp_assignment_p")
.and_then(|v| v.as_real())
.unwrap_or(defaults.mcp_assignment_p);
let seed = params
.get("seed")
.and_then(|v| v.as_real())
.map(|v| v as u64)
.unwrap_or(defaults.seed);
Ok(Self {
k,
n_permutations,
extra_sparse,
abn_c,
p_anova,
centre,
cap,
spatial,
n_genes,
min_ci,
averaging,
mcp_assignment_p,
seed,
})
}
}
impl HlmParams {
pub fn from_r_map(params: &HashMap<&str, Robj>) -> Result<Self> {
let defaults = Self::default();
let min_cells_per_sample = r_list_count_allow_zero(params, "min_cells_per_sample")?
.unwrap_or(defaults.min_cells_per_sample);
let use_tme_qc = params
.get("use_tme_qc")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.use_tme_qc);
let use_cell_quality = params
.get("use_cell_quality")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.use_cell_quality);
let satterthwaite = params
.get("satterthwaite")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.satterthwaite);
Ok(Self {
min_cells_per_sample,
use_tme_qc,
use_cell_quality,
satterthwaite,
})
}
}
impl RefineParams {
pub fn from_r_map(params: &HashMap<&str, Robj>) -> Result<Self> {
let defaults = Self::default();
let support_p = params
.get("support_p")
.and_then(|v| v.as_real())
.unwrap_or(defaults.support_p);
let min_support_fraction = params
.get("min_support_fraction")
.and_then(|v| v.as_real())
.unwrap_or(defaults.min_support_fraction);
let min_stratum =
r_list_count_allow_zero(params, "min_stratum")?.unwrap_or(defaults.min_stratum);
let early_stop_cor = params
.get("early_stop_cor")
.and_then(|v| v.as_real())
.unwrap_or(defaults.early_stop_cor);
let permissive_p = params
.get("permissive_p")
.and_then(|v| v.as_real())
.unwrap_or(defaults.permissive_p);
let strict_p = params
.get("strict_p")
.and_then(|v| v.as_real())
.unwrap_or(defaults.strict_p);
Ok(Self {
support_p,
min_support_fraction,
min_stratum,
early_stop_cor,
permissive_p,
strict_p,
})
}
}
impl DialogueParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
Ok(Self {
pmd: PmdParams::from_r_map(¶ms)?,
hlm: HlmParams::from_r_map(¶ms)?,
refine: RefineParams::from_r_map(¶ms)?,
})
}
}
pub trait NebulaParamsFromR: Sized {
fn from_r_map(params: &HashMap<&str, Robj>) -> Result<Self>;
}
impl NebulaParamsFromR for NebulaParams {
fn from_r_map(params: &HashMap<&str, Robj>) -> Result<Self> {
let defaults = Self::default();
let real = |key: &str, fallback: f64| -> f64 {
params
.get(key)
.and_then(|v| v.as_real())
.unwrap_or(fallback)
};
let method = match params.get("nebula_method").and_then(|v| v.as_str()) {
Some(s) => parse_nebula_method(s)
.ok_or_else(|| Error::Other(format!("Invalid NEBULA method: {}", s)))?,
None => defaults.method,
};
Ok(Self {
min: (
real("min_sigma", defaults.min.0),
real("min_phi", defaults.min.1),
),
max: (
real("max_sigma", defaults.max.0),
real("max_phi", defaults.max.1),
),
method,
cutoff_cell: real("cutoff_cell", defaults.cutoff_cell),
kappa: real("kappa", defaults.kappa),
cpc: real("cpc", defaults.cpc),
mincp: r_list_count(params, "mincp")?.unwrap_or(defaults.mincp),
min_subjects: r_list_count_allow_zero(params, "min_subjects")?
.unwrap_or(defaults.min_subjects),
reml: params
.get("reml")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.reml),
eps: real("eps", defaults.eps),
})
}
}
pub trait ScTestedFromR: Sized {
fn from_r_map(params: &HashMap<&str, Robj>) -> Result<Self>;
}
impl ScTestedFromR for ScTested {
fn from_r_map(params: &HashMap<&str, Robj>) -> Result<Self> {
if let Some(contrast) = params.get("contrast").and_then(|v| v.as_real_vector()) {
return Ok(ScTested::Contrast(contrast));
}
match r_list_count_allow_zero(params, "coef")? {
Some(coef) => Ok(ScTested::Coef(coef)),
None => Err(Error::Other(
"NEBULA needs either `coef` or `contrast` to know what to test".into(),
)),
}
}
}
impl NebulaScParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = Self::default();
Ok(Self {
nebula: NebulaParams::from_r_map(¶ms)?,
gene_batch_size: r_list_count(¶ms, "gene_batch_size")?
.unwrap_or(defaults.gene_batch_size),
shrink_dispersion: params
.get("shrink_dispersion")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.shrink_dispersion),
tested: ScTested::from_r_map(¶ms)?,
})
}
}
impl CellSweepParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = Self::default();
Ok(Self {
freeze_empties: params
.get("freeze_empties")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.freeze_empties),
freeze_ambient_profile: params
.get("freeze_ambient_profile")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.freeze_ambient_profile),
init_alpha: params
.get("init_alpha")
.and_then(|v| v.as_real())
.unwrap_or(defaults.init_alpha),
init_beta: params
.get("init_beta")
.and_then(|v| v.as_real())
.unwrap_or(defaults.init_beta),
alpha_cap: params
.get("alpha_cap")
.and_then(|v| v.as_real())
.unwrap_or(defaults.alpha_cap),
repulsion_strength: params
.get("repulsion_strength")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.repulsion_strength),
max_frac_gene_repulsion: params
.get("max_frac_gene_repulsion")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.max_frac_gene_repulsion),
celltype_lambda: params
.get("celltype_lambda")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.celltype_lambda),
ambient_lambda: params
.get("ambient_lambda")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.ambient_lambda),
bulk_lambda: params
.get("bulk_lambda")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.bulk_lambda),
eps: params
.get("eps")
.and_then(|v| v.as_real())
.unwrap_or(defaults.eps),
log_eps: params
.get("log_eps")
.and_then(|v| v.as_real())
.unwrap_or(defaults.log_eps),
max_iter: r_list_count(¶ms, "max_iter")?.unwrap_or(defaults.max_iter),
del0_ll_tol: params
.get("del0_ll_tol")
.and_then(|v| v.as_real())
.unwrap_or(defaults.del0_ll_tol),
min_ll_tol: params
.get("min_ll_tol")
.and_then(|v| v.as_real())
.unwrap_or(defaults.min_ll_tol),
tol_p: params
.get("tol_p")
.and_then(|v| v.as_real())
.map(|v| v as f32)
.unwrap_or(defaults.tol_p),
tol_f: params
.get("tol_f")
.and_then(|v| v.as_real())
.unwrap_or(defaults.tol_f),
norm_from_rounded: params
.get("norm_from_rounded")
.and_then(|v| v.as_bool())
.unwrap_or(defaults.norm_from_rounded),
seed: r_list_count(¶ms, "seed")?.unwrap_or(defaults.seed as usize) as u64,
})
}
}
pub fn empty_droplet_call_from_r_list(r_list: List) -> Result<EmptyDropletCall> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let method = params
.get("method")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::Other("Empty droplet params need a 'method' entry.".to_string()))?;
let call = parse_empty_droplet_call(method).ok_or_else(|| {
Error::Other(format!(
"'{method}' is not a known empty droplet method. Use one of 'supplied', 'umi_cutoff', 'expected_cells', 'knee'."
))
})?;
Ok(match call {
EmptyDropletCall::UmiCutoff(_) => {
let cutoff = r_list_count(¶ms, "umi_cutoff")?.ok_or_else(|| {
Error::Other("method = 'umi_cutoff' needs a 'umi_cutoff' entry.".to_string())
})?;
EmptyDropletCall::UmiCutoff(cutoff as u32)
}
EmptyDropletCall::ExpectedCells(_) => {
let expected = r_list_count(¶ms, "expected_cells")?.ok_or_else(|| {
Error::Other(
"method = 'expected_cells' needs an 'expected_cells' entry.".to_string(),
)
})?;
EmptyDropletCall::ExpectedCells(expected)
}
other => other,
})
}
pub fn cellsweep_sample_from_r_list(r_list: List) -> Result<CellSweepSample> {
let map: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let sample_id = map
.get("sample_id")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::Other("sample_id missing or not a string".into()))?
.to_string();
let indices = |key: &str| -> Result<Vec<usize>> {
Ok(map
.get(key)
.ok_or_else(|| Error::Other(format!("{key} missing")))?
.as_integer_slice()
.ok_or_else(|| Error::Other(format!("{key} must be an integer vector")))?
.iter()
.map(|&x| x as usize)
.collect())
};
Ok(CellSweepSample {
sample_id,
real_cells: indices("real_cells")?,
empty_cells: indices("empty_cells")?,
celltype_idx: indices("celltype_idx")?,
n_celltypes: r_list_count(&map, "n_celltypes")?
.ok_or_else(|| Error::Other("n_celltypes missing".into()))?,
})
}
impl CellSweepFit {
pub fn to_r_list(&self, sample_id: &str) -> List {
let n_genes = self.ambient.len().max(1);
list!(
sample_id = sample_id,
alpha = self.alpha.clone(),
z_hat = self
.z_hat
.iter()
.map(|&k| (k + 1) as i32)
.collect::<Vec<i32>>(),
beta = self.beta,
ambient = self.ambient.iter().map(|&v| v as f64).collect::<Vec<f64>>(),
celltype_profiles = self
.celltype_profiles
.iter()
.map(|&v| v as f64)
.collect::<Vec<f64>>(),
n_celltypes = (self.celltype_profiles.len() / n_genes) as i32,
log_likelihood = self.log_likelihood,
n_iter = self.n_iter as i32,
converged = self.converged
)
}
}
impl SctParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = SctParams::default();
let n_genes = r_list_count(¶ms, "n_genes")?.unwrap_or(defaults.n_genes);
let n_cells = r_list_count(¶ms, "n_cells")?.unwrap_or(defaults.n_cells);
let min_cells = r_list_count(¶ms, "min_cells")?.unwrap_or(defaults.min_cells);
let bw_adjust = r_list_real(¶ms, "bw_adjust")?.unwrap_or(defaults.bw_adjust);
let gmean_eps = r_list_real(¶ms, "gmean_eps")?.unwrap_or(defaults.gmean_eps);
let outlier_th = r_list_real(¶ms, "outlier_th")?.unwrap_or(defaults.outlier_th);
let poisson_diff_theta =
r_list_real(¶ms, "poisson_diff_theta")?.unwrap_or(defaults.poisson_diff_theta);
let clip_range = match (
r_list_real(¶ms, "clip_min")?,
r_list_real(¶ms, "clip_max")?,
) {
(Some(lo), Some(hi)) => {
validate_clip_range((lo, hi)).map_err(|e| Error::Other(e.to_string()))?;
Some((lo, hi))
}
_ => defaults.clip_range,
};
Ok(Self {
n_genes,
n_cells,
min_cells,
bw_adjust,
gmean_eps,
outlier_th,
poisson_diff_theta,
clip_range,
})
}
}
impl SctModel {
pub fn to_r_list(&self) -> List {
let genes: Vec<i32> = self.genes.iter().map(|&g| g as i32).collect();
list!(
genes = genes,
theta = self.theta.clone(),
coefficients = self.coefficients.clone(),
n_coef = self.n_coef as i32,
covariate_names = self.covariate_names.clone(),
log_umi_coef = self.log_umi_coef,
min_variance = self.min_variance,
clip_min = self.clip_range.0,
clip_max = self.clip_range.1,
)
}
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let required = |name: &'static str| -> Result<&Robj> {
params
.get(name)
.ok_or_else(|| Error::Other(format!("scTransform model is missing '{name}'")))
};
let genes: Vec<usize> = required("genes")?
.as_integer_slice()
.ok_or_else(|| Error::Other("scTransform model 'genes' is not integer".to_string()))?
.iter()
.map(|&g| g as usize)
.collect();
let theta = required("theta")?
.as_real_vector()
.ok_or_else(|| Error::Other("scTransform model 'theta' is not numeric".to_string()))?;
let coefficients = required("coefficients")?.as_real_vector().ok_or_else(|| {
Error::Other("scTransform model 'coefficients' is not numeric".to_string())
})?;
let n_coef = required("n_coef")?.as_integer().ok_or_else(|| {
Error::Other("scTransform model 'n_coef' is not an integer".to_string())
})? as usize;
let covariate_names: Vec<String> = required("covariate_names")?
.as_str_vector()
.map(|v| v.into_iter().map(String::from).collect())
.unwrap_or_default();
if n_coef == 0 {
return Err(Error::Other(
"scTransform model 'n_coef' must be at least 1 (the intercept)".to_string(),
));
}
let n = genes.len();
for (name, len, want) in [
("theta", theta.len(), n),
("coefficients", coefficients.len(), n * n_coef),
("covariate_names", covariate_names.len(), n_coef - 1),
] {
if len != want {
return Err(Error::Other(format!(
"scTransform model '{name}' has {len} entries, expected {want}"
)));
}
}
assert_ascending(&genes, "scTransform model genes")
.map_err(|e| Error::Other(e.to_string()))?;
let what = "scTransform model";
let clip_range = (
r_list_required_real(¶ms, "clip_min", what)?,
r_list_required_real(¶ms, "clip_max", what)?,
);
validate_clip_range(clip_range).map_err(|e| Error::Other(e.to_string()))?;
Ok(Self {
genes,
theta,
coefficients,
n_coef,
covariate_names,
log_umi_coef: r_list_required_real(¶ms, "log_umi_coef", what)?,
min_variance: r_list_required_real(¶ms, "min_variance", what)?,
clip_range,
})
}
}
impl SctCovariates {
pub fn to_r_list(&self) -> List {
let k = self.n_covariates();
if k == 0 {
return List::new(0);
}
let n_cells = self.values.len() / k;
let columns: Vec<Robj> = (0..k)
.map(|j| {
let column: Vec<f64> = (0..n_cells).map(|c| self.row(c)[j]).collect();
Robj::from(column)
})
.collect();
let mut out = List::from_values(columns);
out.set_names(self.names.iter().map(|s| s.as_str()))
.expect("one name per covariate column by construction");
out
}
pub fn from_r_list(r_list: List) -> Result<Self> {
let mut columns: Vec<(String, Vec<f64>)> = Vec::with_capacity(r_list.len());
for (name, value) in r_list.iter() {
let values = value
.as_real_vector()
.or_else(|| {
value
.as_integer_slice()
.map(|s| s.iter().map(|&x| x as f64).collect())
})
.ok_or_else(|| {
Error::Other(format!(
"scTransform covariate '{name}' must be a numeric or integer vector"
))
})?;
if let Some(pos) = values.iter().position(|v| !v.is_finite()) {
return Err(Error::Other(format!(
"scTransform covariate '{name}' has a missing or non-finite value at position {}",
pos + 1
)));
}
columns.push((name.to_string(), values));
}
if columns.is_empty() {
return Ok(Self::default());
}
Self::from_columns(&columns).map_err(|e| Error::Other(e.to_string()))
}
}
impl SctGroupedFit {
pub fn to_r_list(&self) -> List {
let models = List::from_values(self.models.iter().map(|m| m.to_r_list()));
let genes: Vec<i32> = self.genes.iter().map(|&g| g as i32).collect();
let group_of_cell: Vec<i32> = self.group_of_cell.iter().map(|&g| g as i32).collect();
list!(
models = models,
genes = genes,
group_of_cell = group_of_cell,
n_groups = self.models.len() as i32,
)
}
pub fn from_r_list(r_list: List) -> Result<Self> {
let (models, genes, group_of_cell) =
grouped_fit_parts(r_list, "scTransform grouped fit", SctModel::from_r_list)?;
assert_ascending(&genes, "scTransform grouped fit genes")
.map_err(|e| Error::Other(e.to_string()))?;
Ok(Self {
models,
genes,
passes: Vec::new(),
group_of_cell,
})
}
}
fn grouped_fit_parts<M>(
r_list: List,
what: &'static str,
parse_model: impl Fn(List) -> Result<M>,
) -> Result<(Vec<M>, Vec<usize>, Vec<u32>)> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let required = |name: &'static str| -> Result<&Robj> {
params
.get(name)
.ok_or_else(|| Error::Other(format!("{what} is missing '{name}'")))
};
let models_robj = required("models")?;
let models_list = models_robj
.as_list()
.ok_or_else(|| Error::Other(format!("{what} 'models' is not a list")))?;
let models: Vec<M> = models_list
.values()
.enumerate()
.map(|(group, model)| {
let model = model
.as_list()
.ok_or_else(|| Error::Other(format!("{what} model {group} is not a list")))?;
parse_model(model)
})
.collect::<Result<Vec<M>>>()?;
if models.is_empty() {
return Err(Error::Other(format!("{what} carries no models")));
}
let group_of_cell: Vec<u32> = required("group_of_cell")?
.as_integer_slice()
.ok_or_else(|| Error::Other(format!("{what} 'group_of_cell' is not integer")))?
.iter()
.map(|&g| {
u32::try_from(g)
.map_err(|_| Error::Other(format!("{what} 'group_of_cell' has a negative entry")))
})
.collect::<Result<Vec<u32>>>()?;
let n_groups = validate_groups(&group_of_cell, group_of_cell.len())
.map_err(|e| Error::Other(e.to_string()))?;
if n_groups != models.len() {
return Err(Error::Other(format!(
"{what} has {} models but the group map implies {n_groups}",
models.len()
)));
}
let genes: Vec<usize> = match params.get("genes") {
Some(robj) if !robj.is_null() => robj
.as_integer_slice()
.ok_or_else(|| Error::Other(format!("{what} 'genes' is not integer")))?
.iter()
.map(|&g| {
usize::try_from(g)
.map_err(|_| Error::Other(format!("{what} 'genes' has a negative entry")))
})
.collect::<Result<Vec<usize>>>()?,
_ => Vec::new(),
};
Ok((models, genes, group_of_cell))
}
impl AprParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let defaults = AprParams::default();
let theta = r_list_real(¶ms, "theta")?.unwrap_or(defaults.theta);
let min_cells =
r_list_count_allow_zero(¶ms, "min_cells")?.unwrap_or(defaults.min_cells);
let clip_range = match (
r_list_real(¶ms, "clip_min")?,
r_list_real(¶ms, "clip_max")?,
) {
(Some(lo), Some(hi)) => Some((lo, hi)),
_ => defaults.clip_range,
};
let parsed = Self {
theta,
min_cells,
clip_range,
};
parsed.validate().map_err(|e| Error::Other(e.to_string()))?;
Ok(parsed)
}
}
impl AprModel {
pub fn to_r_list(&self) -> List {
let genes: Vec<i32> = self.genes.iter().map(|&g| g as i32).collect();
list!(
genes = genes,
gene_sums = self.gene_sums.clone(),
total = self.total,
theta = self.theta,
clip_min = self.clip_range.0,
clip_max = self.clip_range.1,
)
}
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let required = |name: &'static str| -> Result<&Robj> {
params
.get(name)
.ok_or_else(|| Error::Other(format!("Analytic Pearson model is missing '{name}'")))
};
let genes: Vec<usize> = required("genes")?
.as_integer_slice()
.ok_or_else(|| {
Error::Other("Analytic Pearson model 'genes' is not integer".to_string())
})?
.iter()
.map(|&g| g as usize)
.collect();
let gene_sums = required("gene_sums")?.as_real_vector().ok_or_else(|| {
Error::Other("Analytic Pearson model 'gene_sums' is not numeric".to_string())
})?;
if gene_sums.len() != genes.len() {
return Err(Error::Other(format!(
"Analytic Pearson model 'gene_sums' has {} entries, expected {}",
gene_sums.len(),
genes.len()
)));
}
assert_ascending(&genes, "Analytic Pearson model genes")
.map_err(|e| Error::Other(e.to_string()))?;
let what = "Analytic Pearson model";
let model = Self {
genes,
gene_sums,
total: r_list_required_real(¶ms, "total", what)?,
theta: r_list_required_real(¶ms, "theta", what)?,
clip_range: (
r_list_required_real(¶ms, "clip_min", what)?,
r_list_required_real(¶ms, "clip_max", what)?,
),
};
validate_clip_range(model.clip_range).map_err(|e| Error::Other(e.to_string()))?;
if model.total <= 0.0 || model.total.is_nan() {
return Err(Error::Other(format!(
"{what} 'total' must be positive, got {}",
model.total
)));
}
if model.theta <= 0.0 || model.theta.is_nan() {
return Err(Error::Other(format!(
"{what} 'theta' must be positive, got {}",
model.theta
)));
}
Ok(model)
}
}
impl AprGroupedFit {
pub fn to_r_list(&self) -> List {
let models = List::from_values(self.models.iter().map(|m| m.to_r_list()));
let group_of_cell: Vec<i32> = self.group_of_cell.iter().map(|&g| g as i32).collect();
list!(
models = models,
cell_totals = self.cell_totals.clone(),
group_of_cell = group_of_cell,
n_groups = self.models.len() as i32,
)
}
pub fn from_r_list(r_list: List) -> Result<Self> {
let cell_totals = r_list_to_map(r_list.clone())?
.get("cell_totals")
.ok_or_else(|| {
Error::Other("Analytic Pearson grouped fit is missing 'cell_totals'".to_string())
})?
.as_real_vector()
.ok_or_else(|| {
Error::Other(
"Analytic Pearson grouped fit 'cell_totals' is not numeric".to_string(),
)
})?;
let (models, _genes, group_of_cell) = grouped_fit_parts(
r_list,
"Analytic Pearson grouped fit",
AprModel::from_r_list,
)?;
if cell_totals.len() != group_of_cell.len() {
return Err(Error::Other(format!(
"Analytic Pearson grouped fit 'cell_totals' has {} entries but \
'group_of_cell' has {}",
cell_totals.len(),
group_of_cell.len()
)));
}
Ok(Self {
models,
cell_totals,
group_of_cell,
})
}
}
#[cfg(feature = "bonsai")]
mod bonsai_wrappers {
use bonsai_rs::bonsai::StartTree;
use bonsai_rs::sanity_sc_rs::config::VarianceRule;
use bonsai_rs::search::nni::NniSearch;
use bonsai_rs::search::spr::SprSearch;
use bonsai_rs::tree::NO_NODE;
use extendr_api::*;
use std::collections::HashMap;
use crate::single_cell::sc_analysis::bonsai::{
BonsaiScParams, BonsaiScResult, parse_bonsai_layout,
};
use crate::utils::r_rust_interface::r_list_to_map;
impl BonsaiScParams {
pub fn from_r_list(r_list: List) -> Result<Self> {
let params: HashMap<&str, Robj> = r_list_to_map(r_list)?;
let mut out = Self::default();
let real = |k: &str| params.get(k).and_then(|v| v.as_real());
let text = |k: &str| params.get(k).and_then(|v| v.as_str());
let unknown = |k: &str, v: &str| Error::Other(format!("Invalid Bonsai {k}: {v}"));
if let Some(v) = text("variance_rule") {
out.sanity.variance_rule = match v.to_lowercase().as_str() {
"fixed" => VarianceRule::Fixed(real("fixed_variance").ok_or_else(|| {
Error::Other(
"Bonsai variance_rule 'fixed' needs fixed_variance".to_string(),
)
})?),
s => parse_variance_rule(s).ok_or_else(|| unknown("variance_rule", v))?,
};
}
if let Some(v) = params.get("variance_bins").and_then(|v| v.as_integer()) {
out.sanity.variance_bins = v as usize;
}
if let Some(v) = real("variance_min") {
out.sanity.variance_min = v;
}
if let Some(v) = real("variance_max") {
out.sanity.variance_max = v;
}
if let Some(v) = real("min_signal_to_noise") {
out.bonsai.ingest.min_signal_to_noise = v;
}
if let Some(v) = text("start") {
out.bonsai.start = parse_start_tree(v).ok_or_else(|| unknown("start", v))?;
}
if let Some(v) = text("spr_search") {
out.bonsai.spr.search =
parse_spr_search(v).ok_or_else(|| unknown("spr_search", v))?;
}
if let Some(v) = text("nni_search") {
out.bonsai.nni.search =
parse_nni_search(v).ok_or_else(|| unknown("nni_search", v))?;
}
if let Some(v) = params.get("seed").and_then(|v| v.as_integer()) {
out.bonsai.linkage.seed = v as usize;
out.bonsai.spr.seed = v as u64;
out.bonsai.nni.seed = v as u64;
}
if let Some(v) = params.get("reroot").and_then(|v| v.as_bool()) {
out.bonsai.reroot = v;
}
if let Some(v) = text("layout") {
out.layout = parse_bonsai_layout(v).ok_or_else(|| unknown("layout", v))?;
}
if let Some(v) = params.get("hyperbolic").and_then(|v| v.as_bool()) {
out.hyperbolic = v;
}
Ok(out)
}
}
pub fn parse_variance_rule(s: &str) -> Option<VarianceRule> {
match s {
"marginalise" => Some(VarianceRule::Marginalise),
"posterior_mean" => Some(VarianceRule::PosteriorMean),
"max_posterior" => Some(VarianceRule::MaxPosterior),
_ => None,
}
}
pub fn parse_start_tree(s: &str) -> Option<StartTree> {
match s.to_lowercase().as_str() {
"linkage" => Some(StartTree::Linkage),
"greedy_merge" => Some(StartTree::GreedyMerge),
_ => None,
}
}
pub fn parse_spr_search(s: &str) -> Option<SprSearch> {
match s.to_lowercase().as_str() {
"approximate" => Some(SprSearch::default()),
"exact" => Some(SprSearch::Exact),
_ => None,
}
}
pub fn parse_nni_search(s: &str) -> Option<NniSearch> {
match s.to_lowercase().as_str() {
"approximate" => Some(NniSearch::default()),
"exact" => Some(NniSearch::Exact),
_ => None,
}
}
pub fn parents_to_r(parent: &[u32]) -> Vec<i32> {
parent
.iter()
.map(|&p| if p == NO_NODE { -1 } else { p as i32 })
.collect()
}
pub fn parents_from_r(parent: &[i32]) -> Vec<u32> {
parent
.iter()
.map(|&p| if p < 0 { NO_NODE } else { p as u32 })
.collect()
}
pub fn bonsai_sc_to_r_list(res: BonsaiScResult) -> List {
let mut step = Vec::with_capacity(res.steps.len());
let mut step_loglik = Vec::with_capacity(res.steps.len());
let mut step_seconds = Vec::with_capacity(res.steps.len());
for (name, loglik, secs) in res.steps {
step.push(name);
step_loglik.push(loglik);
step_seconds.push(secs);
}
let (stage, stage_seconds): (Vec<String>, Vec<f64>) = res.timings.into_iter().unzip();
list!(
parent = parents_to_r(&res.parent),
branch = res.branch,
x = res.x,
y = res.y,
n_leaves = res.n_leaves as i32,
loglik = res.loglik,
steps = list!(step = step, loglik = step_loglik, seconds = step_seconds),
timings = list!(stage = stage, seconds = stage_seconds),
genes_used = res
.genes_used
.iter()
.map(|&g| g as i32)
.collect::<Vec<i32>>()
)
}
}
#[cfg(feature = "bonsai")]
pub use bonsai_wrappers::*;