flow_fcs/
transform.rs

1use serde::{Deserialize, Serialize};
2use std::hash::Hash;
3
4/// Transformation type to apply to flow cytometry parameter data
5///
6/// Transformations are used to convert raw instrument values into display-friendly scales.
7/// The most common transformation for fluorescence data is arcsinh (inverse hyperbolic sine),
8/// which provides a log-like scale that handles both positive and negative values.
9#[derive(Clone, Serialize, Deserialize, Debug)]
10pub enum TransformType {
11    /// Linear transformation (no scaling, identity function)
12    /// Used for scatter parameters (FSC, SSC) and time
13    Linear,
14    /// Arcsinh (inverse hyperbolic sine) transformation with configurable cofactor
15    /// Formula: `arcsinh(x / cofactor)`
16    /// Common cofactors: 150-200 for modern instruments
17    /// This is the default transformation for fluorescence parameters
18    Arcsinh { cofactor: f32 },
19}
20
21impl TransformType {
22    /// Create a TransformType from a string. If no string is provided or the string is not matched, the default `arcsinh` transform is used.
23    pub fn create_from_str(s: Option<&str>) -> Self {
24        match s {
25            Some("linear") => TransformType::Linear,
26            Some("arcsinh") => TransformType::default(),
27            _ => TransformType::default(),
28        }
29    }
30}
31
32/// Trait for types that can transform values from raw to display scale
33///
34/// Transformations are typically applied when displaying data, not when storing it.
35/// This allows the raw data to remain unchanged while providing flexible visualization options.
36pub trait Transformable {
37    fn transform(&self, value: &f32) -> f32;
38    fn inverse_transform(&self, value: &f32) -> f32;
39}
40/// Trait for types that can format transformed values for display
41///
42/// Formatting converts numeric values into human-readable strings,
43/// typically using scientific notation for large numbers.
44#[allow(unused)]
45pub trait Formattable {
46    fn format(&self, value: &f32) -> String;
47}
48
49impl Transformable for TransformType {
50    fn transform(&self, value: &f32) -> f32 {
51        match self {
52            TransformType::Linear => *value,
53            TransformType::Arcsinh { cofactor } => (value / cofactor).asinh(),
54        }
55    }
56    fn inverse_transform(&self, value: &f32) -> f32 {
57        match self {
58            TransformType::Linear => *value,
59            TransformType::Arcsinh { cofactor } => {
60                eprintln!(
61                    "🔧 [INVERSE_TRANSFORM] Arcsinh inverse: value={}, cofactor={}",
62                    value, cofactor
63                );
64                let sinh_result = value.sinh();
65                eprintln!("🔧 [INVERSE_TRANSFORM] sinh({}) = {}", value, sinh_result);
66                let final_result = sinh_result * cofactor;
67                eprintln!(
68                    "🔧 [INVERSE_TRANSFORM] final result: {} * {} = {}",
69                    sinh_result, cofactor, final_result
70                );
71                final_result
72            }
73        }
74    }
75}
76impl Formattable for TransformType {
77    fn format(&self, value: &f32) -> String {
78        match self {
79            TransformType::Linear => format!("{:.1e}", value),
80            TransformType::Arcsinh { cofactor: _ } => {
81                // Convert from transformed space back to original space
82                let original_value = self.inverse_transform(value);
83
84                // Make nice rounded labels in original space
85                format!("{:.1e}", original_value)
86            }
87        }
88    }
89}
90impl Default for TransformType {
91    fn default() -> Self {
92        TransformType::Arcsinh { cofactor: 200.0 }
93    }
94}
95impl Hash for TransformType {
96    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
97        match self {
98            TransformType::Linear => "linear".hash(state),
99            TransformType::Arcsinh { cofactor: _ } => "arcsinh".hash(state),
100        }
101    }
102}
103
104#[test]
105fn test_transform() {
106    let t = TransformType::Linear;
107    assert_eq!(t.transform(&1.0), 1.0);
108    assert_eq!(t.inverse_transform(&1.0), 1.0);
109
110    let t = TransformType::Arcsinh { cofactor: 200.0 };
111    assert_eq!(t.transform(&1.0), 0.005);
112    assert_eq!(t.inverse_transform(&0.005), 1.0);
113    // Assert that the transform results in a number
114    assert!(!t.transform(&-1.0).is_nan());
115    assert!(!t.transform(&0.0).is_nan());
116    assert!(!t.transform(&-200.0).is_nan());
117}