use derive_builder::Builder;
use polars::prelude::*;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use crate::TransformType;
pub type EventDataFrame = Arc<DataFrame>;
pub type EventDatum = f32;
pub type ChannelName = Arc<str>;
pub type LabelName = Arc<str>;
pub type ParameterMap = FxHashMap<ChannelName, Parameter>;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[cfg_attr(feature = "typescript", ts(export))]
pub enum ParameterProcessing {
Raw,
Compensated,
Unmixed,
UnmixedCompensated,
}
impl Default for ParameterProcessing {
fn default() -> Self {
ParameterProcessing::Raw
}
}
#[derive(Serialize, Debug, Clone, PartialEq, Eq)]
pub enum ParameterCategory {
Raw,
Fluorescence,
Compensated,
Transformed,
CompensatedTransformed,
Unmixed,
}
#[derive(Serialize, Debug, Clone)]
pub struct ParameterOption {
pub id: String,
pub display_label: String,
pub parameter: Parameter,
pub category: ParameterCategory,
}
#[derive(Serialize, Debug, Clone, Builder, Hash)]
#[builder(setter(into))]
pub struct Parameter {
pub parameter_number: usize,
pub channel_name: ChannelName,
pub label_name: LabelName,
pub transform: TransformType,
#[builder(default)]
#[serde(default)]
pub state: ParameterProcessing,
#[builder(default)]
pub excitation_wavelength: Option<usize>,
}
impl Parameter {
#[must_use]
pub fn new(
parameter_number: &usize,
channel_name: &str,
label_name: &str,
transform: &TransformType,
) -> Self {
Self {
parameter_number: *parameter_number,
channel_name: channel_name.into(),
label_name: label_name.into(),
transform: transform.clone(),
state: ParameterProcessing::default(),
excitation_wavelength: None,
}
}
#[must_use]
pub fn is_fluorescence(&self) -> bool {
let upper = self.channel_name.to_uppercase();
!upper.contains("FSC") && !upper.contains("SSC") && !upper.contains("TIME")
}
#[must_use]
pub fn get_display_label(&self) -> String {
let prefix = match self.state {
ParameterProcessing::Raw => "",
ParameterProcessing::Compensated => "Comp::",
ParameterProcessing::Unmixed => "Unmix::",
ParameterProcessing::UnmixedCompensated => "Comp+Unmix::",
};
if self.label_name.is_empty() || self.label_name.as_ref() == self.channel_name.as_ref() {
format!("{}{}", prefix, self.channel_name)
} else {
format!("{}{}::{}", prefix, self.channel_name, self.label_name)
}
}
#[must_use]
pub fn get_short_label(&self) -> String {
if self.label_name.is_empty() || self.label_name.as_ref() == self.channel_name.as_ref() {
self.channel_name.to_string()
} else {
format!("{}::{}", self.channel_name, self.label_name)
}
}
#[must_use]
pub fn with_state(&self, state: ParameterProcessing) -> Self {
Self {
state,
..self.clone()
}
}
#[must_use]
pub fn with_transform(&self, transform: TransformType) -> Self {
Self {
transform,
..self.clone()
}
}
pub fn generate_plot_options(&self, include_compensated: bool) -> Vec<ParameterOption> {
let mut options = Vec::new();
if self.is_fluorescence() {
let transformed = self.with_transform(TransformType::default());
let transformed_label = self.get_short_label();
options.push(ParameterOption {
id: format!("transformed::{}", self.channel_name),
display_label: transformed_label,
parameter: transformed,
category: ParameterCategory::Fluorescence,
});
if include_compensated {
let comp_trans = self
.with_state(ParameterProcessing::Compensated)
.with_transform(TransformType::default());
let comp_trans_label = comp_trans.get_display_label();
options.push(ParameterOption {
id: format!("comp_trans::{}", self.channel_name),
display_label: comp_trans_label,
parameter: comp_trans,
category: ParameterCategory::CompensatedTransformed,
});
let unmix_trans = self
.with_state(ParameterProcessing::Unmixed)
.with_transform(TransformType::default());
let unmix_trans_label = unmix_trans.get_display_label();
options.push(ParameterOption {
id: format!("unmix_trans::{}", self.channel_name),
display_label: unmix_trans_label,
parameter: unmix_trans,
category: ParameterCategory::Unmixed,
});
let comp_unmix_trans = self
.with_state(ParameterProcessing::UnmixedCompensated)
.with_transform(TransformType::default());
let comp_unmix_trans_label = comp_unmix_trans.get_display_label();
options.push(ParameterOption {
id: format!("comp_unmix_trans::{}", self.channel_name),
display_label: comp_unmix_trans_label,
parameter: comp_unmix_trans,
category: ParameterCategory::Unmixed,
});
}
} else {
options.push(ParameterOption {
id: format!("raw::{}", self.channel_name),
display_label: self.get_short_label(),
parameter: self.clone(),
category: ParameterCategory::Raw,
});
}
options
}
}