Consistent formatting pass: line wrapping, import sorting, trailing whitespace removal, let-chain indentation, merged derive attributes, and unsafe block reformatting. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
639 lines
18 KiB
Rust
639 lines
18 KiB
Rust
//! Shared IPC types for CellAtlas single-cell transcriptomics demo.
|
|
//!
|
|
//! This crate provides data structures for communication between
|
|
//! the Tauri frontend and Rust backend for single-cell analysis.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
// ============================================================================
|
|
// Cell Data Types
|
|
// ============================================================================
|
|
|
|
/// A single cell with gene expression data.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Cell {
|
|
/// Unique cell identifier
|
|
pub id: String,
|
|
/// Cell barcode (from sequencing)
|
|
pub barcode: Option<String>,
|
|
/// Gene expression values (sparse representation)
|
|
pub expression: SparseExpression,
|
|
/// Assigned cell type
|
|
pub cell_type: Option<CellType>,
|
|
/// Cell state annotations
|
|
pub state: Option<CellState>,
|
|
/// Quality metrics
|
|
pub qc_metrics: QualityMetrics,
|
|
/// Spatial coordinates (if spatial transcriptomics)
|
|
pub spatial_coords: Option<SpatialCoords>,
|
|
/// Cluster assignment
|
|
pub cluster_id: Option<usize>,
|
|
/// UMAP/t-SNE embedding coordinates
|
|
pub embedding: Option<Embedding2D>,
|
|
}
|
|
|
|
/// Sparse gene expression representation.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SparseExpression {
|
|
/// Gene indices (into a gene name list)
|
|
pub gene_indices: Vec<usize>,
|
|
/// Expression values (counts or normalized)
|
|
pub values: Vec<f32>,
|
|
/// Total number of genes in reference
|
|
pub num_genes: usize,
|
|
}
|
|
|
|
impl SparseExpression {
|
|
/// Get expression value for a gene index.
|
|
pub fn get(&self, gene_idx: usize) -> f32 {
|
|
self.gene_indices
|
|
.iter()
|
|
.position(|&idx| idx == gene_idx)
|
|
.map_or(0.0, |pos| self.values[pos])
|
|
}
|
|
|
|
/// Get the number of expressed genes.
|
|
pub fn num_expressed(&self) -> usize {
|
|
self.values.iter().filter(|&&v| v > 0.0).count()
|
|
}
|
|
|
|
/// Get total UMI counts.
|
|
pub fn total_counts(&self) -> f32 {
|
|
self.values.iter().sum()
|
|
}
|
|
}
|
|
|
|
/// 2D embedding coordinates (UMAP, t-SNE, etc.).
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
|
pub struct Embedding2D {
|
|
pub x: f32,
|
|
pub y: f32,
|
|
}
|
|
|
|
/// Spatial coordinates for spatial transcriptomics.
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
|
pub struct SpatialCoords {
|
|
/// X coordinate in tissue space
|
|
pub x: f32,
|
|
/// Y coordinate in tissue space
|
|
pub y: f32,
|
|
/// Z coordinate (for 3D spatial data)
|
|
pub z: Option<f32>,
|
|
/// Tissue section/slice ID
|
|
pub section_id: Option<u32>,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Cell Type Annotation
|
|
// ============================================================================
|
|
|
|
/// Cell type annotation.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CellType {
|
|
/// Primary cell type name
|
|
pub name: String,
|
|
/// Confidence score (0-1)
|
|
pub confidence: f32,
|
|
/// Hierarchical lineage (e.g., ["Immune", "T cell", "CD8+ T cell"])
|
|
pub lineage: Vec<String>,
|
|
/// Marker genes used for annotation
|
|
pub markers: Vec<String>,
|
|
}
|
|
|
|
/// Cell state annotation (activation, cycling, etc.).
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CellState {
|
|
/// State name (e.g., "Activated", "Resting", "Cycling")
|
|
pub name: String,
|
|
/// State score (0-1)
|
|
pub score: f32,
|
|
/// Cell cycle phase (if applicable)
|
|
pub cell_cycle_phase: Option<CellCyclePhase>,
|
|
}
|
|
|
|
/// Cell cycle phase.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "UPPERCASE")]
|
|
pub enum CellCyclePhase {
|
|
G1,
|
|
S,
|
|
G2,
|
|
M,
|
|
G0,
|
|
}
|
|
|
|
/// Quality control metrics for a cell.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct QualityMetrics {
|
|
/// Number of genes detected
|
|
pub n_genes: usize,
|
|
/// Total UMI counts
|
|
pub total_counts: f32,
|
|
/// Percentage of mitochondrial reads
|
|
pub pct_mito: f32,
|
|
/// Percentage of ribosomal reads
|
|
pub pct_ribo: f32,
|
|
/// Doublet score (0-1, higher = more likely doublet)
|
|
pub doublet_score: Option<f32>,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Dataset Types
|
|
// ============================================================================
|
|
|
|
/// Single-cell dataset.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SingleCellDataset {
|
|
/// Dataset name
|
|
pub name: String,
|
|
/// Dataset description
|
|
pub description: Option<String>,
|
|
/// List of gene names
|
|
pub gene_names: Vec<String>,
|
|
/// Number of cells
|
|
pub n_cells: usize,
|
|
/// Number of genes
|
|
pub n_genes: usize,
|
|
/// Data modality
|
|
pub modality: DataModality,
|
|
/// Organism
|
|
pub organism: Organism,
|
|
/// Tissue/sample source
|
|
pub tissue: Option<String>,
|
|
}
|
|
|
|
/// Data modality.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum DataModality {
|
|
/// Single-cell RNA sequencing
|
|
ScRnaSeq,
|
|
/// Single-nucleus RNA sequencing
|
|
SnRnaSeq,
|
|
/// Spatial transcriptomics (Visium, etc.)
|
|
SpatialTranscriptomics,
|
|
/// CITE-seq (RNA + protein)
|
|
CiteSeq,
|
|
/// Single-cell ATAC-seq
|
|
ScAtacSeq,
|
|
/// Multi-modal (multiple modalities)
|
|
MultiModal,
|
|
}
|
|
|
|
/// Organism.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum Organism {
|
|
Human,
|
|
Mouse,
|
|
Rat,
|
|
Zebrafish,
|
|
Drosophila,
|
|
Other,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Analysis Request/Response Types
|
|
// ============================================================================
|
|
|
|
/// Request to analyze a single-cell dataset.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AnalysisRequest {
|
|
/// Dataset to analyze
|
|
pub dataset_id: String,
|
|
/// Analysis configuration
|
|
pub config: AnalysisConfig,
|
|
}
|
|
|
|
/// Analysis configuration.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
pub struct AnalysisConfig {
|
|
/// Preprocessing options
|
|
pub preprocessing: PreprocessingConfig,
|
|
/// Dimensionality reduction
|
|
pub dim_reduction: DimReductionConfig,
|
|
/// Clustering options
|
|
pub clustering: ClusteringConfig,
|
|
/// Cell type annotation options
|
|
pub annotation: AnnotationConfig,
|
|
}
|
|
|
|
/// Preprocessing configuration.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PreprocessingConfig {
|
|
/// Minimum genes per cell
|
|
pub min_genes: usize,
|
|
/// Minimum cells per gene
|
|
pub min_cells: usize,
|
|
/// Maximum mitochondrial percentage
|
|
pub max_mito_pct: f32,
|
|
/// Normalization method
|
|
pub normalization: NormalizationMethod,
|
|
/// Number of highly variable genes
|
|
pub n_hvg: usize,
|
|
}
|
|
|
|
impl Default for PreprocessingConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
min_genes: 200,
|
|
min_cells: 3,
|
|
max_mito_pct: 20.0,
|
|
normalization: NormalizationMethod::LogNormalize,
|
|
n_hvg: 2000,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Normalization method.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum NormalizationMethod {
|
|
/// Log normalization (Seurat/Scanpy default)
|
|
LogNormalize,
|
|
/// SCTransform (variance stabilizing)
|
|
ScTransform,
|
|
/// Counts per million
|
|
Cpm,
|
|
/// No normalization (use raw counts)
|
|
None,
|
|
}
|
|
|
|
/// Dimensionality reduction configuration.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DimReductionConfig {
|
|
/// Number of PCA components
|
|
pub n_pcs: usize,
|
|
/// Method for 2D embedding
|
|
pub embedding_method: EmbeddingMethod,
|
|
/// Number of neighbors for UMAP/t-SNE
|
|
pub n_neighbors: usize,
|
|
/// Minimum distance for UMAP
|
|
pub min_dist: f32,
|
|
}
|
|
|
|
impl Default for DimReductionConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
n_pcs: 50,
|
|
embedding_method: EmbeddingMethod::Umap,
|
|
n_neighbors: 15,
|
|
min_dist: 0.1,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Embedding method for visualization.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum EmbeddingMethod {
|
|
Umap,
|
|
Tsne,
|
|
Pca,
|
|
ForceDirected,
|
|
}
|
|
|
|
/// Clustering configuration.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ClusteringConfig {
|
|
/// Clustering algorithm
|
|
pub algorithm: ClusteringAlgorithm,
|
|
/// Resolution parameter (for Leiden/Louvain)
|
|
pub resolution: f32,
|
|
/// Number of clusters (for k-means)
|
|
pub n_clusters: Option<usize>,
|
|
}
|
|
|
|
impl Default for ClusteringConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
algorithm: ClusteringAlgorithm::Leiden,
|
|
resolution: 1.0,
|
|
n_clusters: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Clustering algorithm.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum ClusteringAlgorithm {
|
|
Leiden,
|
|
Louvain,
|
|
Kmeans,
|
|
Hierarchical,
|
|
}
|
|
|
|
/// Cell type annotation configuration.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AnnotationConfig {
|
|
/// Annotation method
|
|
pub method: AnnotationMethod,
|
|
/// Reference dataset for transfer learning
|
|
pub reference: Option<String>,
|
|
/// Marker gene sets
|
|
pub marker_genes: Option<Vec<MarkerGeneSet>>,
|
|
}
|
|
|
|
impl Default for AnnotationConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
method: AnnotationMethod::MarkerBased,
|
|
reference: None,
|
|
marker_genes: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Annotation method.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum AnnotationMethod {
|
|
/// Manual marker-based annotation
|
|
MarkerBased,
|
|
/// Transfer learning from reference
|
|
TransferLearning,
|
|
/// Foundation model (CellAtlas transformer)
|
|
FoundationModel,
|
|
}
|
|
|
|
/// Marker gene set for a cell type.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MarkerGeneSet {
|
|
/// Cell type name
|
|
pub cell_type: String,
|
|
/// Positive markers (must be expressed)
|
|
pub positive_markers: Vec<String>,
|
|
/// Negative markers (must not be expressed)
|
|
pub negative_markers: Vec<String>,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Analysis Results
|
|
// ============================================================================
|
|
|
|
/// Analysis results.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AnalysisResult {
|
|
/// Cluster assignments
|
|
pub clusters: Vec<ClusterInfo>,
|
|
/// Cell type annotations
|
|
pub cell_types: Vec<CellTypeAnnotation>,
|
|
/// Differential expression results
|
|
pub de_results: Option<Vec<DifferentialExpression>>,
|
|
/// Spatial niche analysis (if applicable)
|
|
pub spatial_niches: Option<Vec<SpatialNiche>>,
|
|
/// Quality summary
|
|
pub qc_summary: QcSummary,
|
|
}
|
|
|
|
/// Cluster information.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ClusterInfo {
|
|
/// Cluster ID
|
|
pub id: usize,
|
|
/// Number of cells
|
|
pub n_cells: usize,
|
|
/// Top marker genes
|
|
pub markers: Vec<String>,
|
|
/// Centroid in embedding space
|
|
pub centroid: Option<Embedding2D>,
|
|
}
|
|
|
|
/// Cell type annotation result.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CellTypeAnnotation {
|
|
/// Cell type name
|
|
pub name: String,
|
|
/// Number of cells
|
|
pub n_cells: usize,
|
|
/// Percentage of total
|
|
pub percentage: f32,
|
|
/// Average confidence
|
|
pub avg_confidence: f32,
|
|
/// Clusters with this cell type
|
|
pub clusters: Vec<usize>,
|
|
}
|
|
|
|
/// Differential expression result.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DifferentialExpression {
|
|
/// Gene name
|
|
pub gene: String,
|
|
/// Log2 fold change
|
|
pub log2fc: f32,
|
|
/// P-value
|
|
pub pvalue: f32,
|
|
/// Adjusted p-value (FDR)
|
|
pub padj: f32,
|
|
/// Percentage expressed in group 1
|
|
pub pct1: f32,
|
|
/// Percentage expressed in group 2
|
|
pub pct2: f32,
|
|
}
|
|
|
|
/// Spatial niche information.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SpatialNiche {
|
|
/// Niche ID
|
|
pub id: usize,
|
|
/// Niche name/description
|
|
pub name: String,
|
|
/// Cell types in this niche
|
|
pub cell_types: Vec<String>,
|
|
/// Cell type proportions
|
|
pub proportions: Vec<f32>,
|
|
/// Spatial center
|
|
pub center: SpatialCoords,
|
|
}
|
|
|
|
/// QC summary statistics.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct QcSummary {
|
|
/// Total cells before filtering
|
|
pub n_cells_before: usize,
|
|
/// Total cells after filtering
|
|
pub n_cells_after: usize,
|
|
/// Total genes before filtering
|
|
pub n_genes_before: usize,
|
|
/// Total genes after filtering
|
|
pub n_genes_after: usize,
|
|
/// Median genes per cell
|
|
pub median_genes: f32,
|
|
/// Median counts per cell
|
|
pub median_counts: f32,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Sample Data
|
|
// ============================================================================
|
|
|
|
/// Sample dataset for demo.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SampleDataset {
|
|
/// Dataset name
|
|
pub name: String,
|
|
/// Description
|
|
pub description: String,
|
|
/// Number of cells
|
|
pub n_cells: usize,
|
|
/// Modality
|
|
pub modality: DataModality,
|
|
/// Tissue type
|
|
pub tissue: String,
|
|
/// Organism
|
|
pub organism: Organism,
|
|
}
|
|
|
|
/// Get available sample datasets.
|
|
pub fn get_sample_datasets() -> Vec<SampleDataset> {
|
|
vec![
|
|
SampleDataset {
|
|
name: "PBMC 3k".to_string(),
|
|
description: "3,000 peripheral blood mononuclear cells from 10x Genomics".to_string(),
|
|
n_cells: 2700,
|
|
modality: DataModality::ScRnaSeq,
|
|
tissue: "Blood".to_string(),
|
|
organism: Organism::Human,
|
|
},
|
|
SampleDataset {
|
|
name: "Mouse Brain Spatial".to_string(),
|
|
description: "Spatial transcriptomics of mouse brain cortex".to_string(),
|
|
n_cells: 3500,
|
|
modality: DataModality::SpatialTranscriptomics,
|
|
tissue: "Brain".to_string(),
|
|
organism: Organism::Mouse,
|
|
},
|
|
SampleDataset {
|
|
name: "Tumor Microenvironment".to_string(),
|
|
description: "Single-cell analysis of tumor-infiltrating immune cells".to_string(),
|
|
n_cells: 8000,
|
|
modality: DataModality::ScRnaSeq,
|
|
tissue: "Tumor".to_string(),
|
|
organism: Organism::Human,
|
|
},
|
|
SampleDataset {
|
|
name: "Developing Heart".to_string(),
|
|
description: "Multi-nuclei RNA-seq of developing human heart".to_string(),
|
|
n_cells: 5000,
|
|
modality: DataModality::SnRnaSeq,
|
|
tissue: "Heart".to_string(),
|
|
organism: Organism::Human,
|
|
},
|
|
]
|
|
}
|
|
|
|
/// Common marker genes for major cell types.
|
|
pub fn get_common_markers() -> Vec<MarkerGeneSet> {
|
|
vec![
|
|
MarkerGeneSet {
|
|
cell_type: "T cell".to_string(),
|
|
positive_markers: vec!["CD3D".to_string(), "CD3E".to_string(), "CD3G".to_string()],
|
|
negative_markers: vec!["CD19".to_string(), "CD14".to_string()],
|
|
},
|
|
MarkerGeneSet {
|
|
cell_type: "B cell".to_string(),
|
|
positive_markers: vec!["CD19".to_string(), "MS4A1".to_string(), "CD79A".to_string()],
|
|
negative_markers: vec!["CD3D".to_string(), "CD14".to_string()],
|
|
},
|
|
MarkerGeneSet {
|
|
cell_type: "Monocyte".to_string(),
|
|
positive_markers: vec!["CD14".to_string(), "LYZ".to_string(), "CST3".to_string()],
|
|
negative_markers: vec!["CD3D".to_string(), "CD19".to_string()],
|
|
},
|
|
MarkerGeneSet {
|
|
cell_type: "NK cell".to_string(),
|
|
positive_markers: vec!["GNLY".to_string(), "NKG7".to_string(), "KLRD1".to_string()],
|
|
negative_markers: vec!["CD3D".to_string(), "CD19".to_string()],
|
|
},
|
|
MarkerGeneSet {
|
|
cell_type: "Dendritic cell".to_string(),
|
|
positive_markers: vec!["FCER1A".to_string(), "CD1C".to_string()],
|
|
negative_markers: vec!["CD3D".to_string(), "CD14".to_string()],
|
|
},
|
|
]
|
|
}
|
|
|
|
// ============================================================================
|
|
// Tests
|
|
// ============================================================================
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_sparse_expression() {
|
|
let expr = SparseExpression {
|
|
gene_indices: vec![0, 5, 10],
|
|
values: vec![1.0, 2.5, 0.5],
|
|
num_genes: 100,
|
|
};
|
|
|
|
assert_eq!(expr.get(0), 1.0);
|
|
assert_eq!(expr.get(5), 2.5);
|
|
assert_eq!(expr.get(1), 0.0); // Not expressed
|
|
assert_eq!(expr.num_expressed(), 3);
|
|
assert!((expr.total_counts() - 4.0).abs() < 0.001);
|
|
}
|
|
|
|
#[test]
|
|
fn test_analysis_config_default() {
|
|
let config = AnalysisConfig::default();
|
|
assert_eq!(config.preprocessing.min_genes, 200);
|
|
assert_eq!(config.dim_reduction.n_pcs, 50);
|
|
assert_eq!(config.clustering.algorithm, ClusteringAlgorithm::Leiden);
|
|
}
|
|
|
|
#[test]
|
|
fn test_sample_datasets() {
|
|
let datasets = get_sample_datasets();
|
|
assert_eq!(datasets.len(), 4);
|
|
assert!(datasets.iter().any(|d| d.name == "PBMC 3k"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_common_markers() {
|
|
let markers = get_common_markers();
|
|
assert!(!markers.is_empty());
|
|
|
|
let t_cell = markers.iter().find(|m| m.cell_type == "T cell").unwrap();
|
|
assert!(t_cell.positive_markers.contains(&"CD3D".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn test_modality_serialization() {
|
|
let modality = DataModality::SpatialTranscriptomics;
|
|
let json = serde_json::to_string(&modality).unwrap();
|
|
assert_eq!(json, "\"spatial_transcriptomics\"");
|
|
}
|
|
|
|
#[test]
|
|
fn test_cell_cycle_phase() {
|
|
let phase = CellCyclePhase::S;
|
|
let json = serde_json::to_string(&phase).unwrap();
|
|
assert_eq!(json, "\"S\"");
|
|
}
|
|
|
|
#[test]
|
|
fn test_embedding_method() {
|
|
let method = EmbeddingMethod::Umap;
|
|
let json = serde_json::to_string(&method).unwrap();
|
|
assert_eq!(json, "\"umap\"");
|
|
}
|
|
|
|
#[test]
|
|
fn test_quality_metrics() {
|
|
let qc = QualityMetrics {
|
|
n_genes: 2000,
|
|
total_counts: 5000.0,
|
|
pct_mito: 5.0,
|
|
pct_ribo: 10.0,
|
|
doublet_score: Some(0.1),
|
|
};
|
|
|
|
assert_eq!(qc.n_genes, 2000);
|
|
assert!(qc.doublet_score.is_some());
|
|
}
|
|
}
|