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.
36#[allow(unused)]
37pub trait Transformable {
38    fn transform(&self, value: &f32) -> f32;
39    fn inverse_transform(&self, value: &f32) -> f32;
40}
41/// Trait for types that can format transformed values for display
42///
43/// Formatting converts numeric values into human-readable strings,
44/// typically using scientific notation for large numbers.
45#[allow(unused)]
46pub trait Formattable {
47    fn format(&self, value: &f32) -> String;
48}
49
50impl Transformable for TransformType {
51    fn transform(&self, value: &f32) -> f32 {
52        match self {
53            TransformType::Linear => *value,
54            TransformType::Arcsinh { cofactor } => (value / cofactor).asinh(),
55        }
56    }
57    fn inverse_transform(&self, value: &f32) -> f32 {
58        match self {
59            TransformType::Linear => *value,
60            TransformType::Arcsinh { cofactor } => {
61                eprintln!(
62                    "🔧 [INVERSE_TRANSFORM] Arcsinh inverse: value={}, cofactor={}",
63                    value, cofactor
64                );
65                let sinh_result = value.sinh();
66                eprintln!("🔧 [INVERSE_TRANSFORM] sinh({}) = {}", value, sinh_result);
67                let final_result = sinh_result * cofactor;
68                eprintln!(
69                    "🔧 [INVERSE_TRANSFORM] final result: {} * {} = {}",
70                    sinh_result, cofactor, final_result
71                );
72                final_result
73            }
74        }
75    }
76}
77impl Formattable for TransformType {
78    fn format(&self, value: &f32) -> String {
79        match self {
80            TransformType::Linear => format!("{:.1e}", value),
81            TransformType::Arcsinh { cofactor: _ } => {
82                // Convert from transformed space back to original space
83                let original_value = self.inverse_transform(value);
84
85                // Make nice rounded labels in original space
86                format!("{:.1e}", original_value)
87            }
88        }
89    }
90}
91impl Default for TransformType {
92    fn default() -> Self {
93        TransformType::Arcsinh { cofactor: 200.0 }
94    }
95}
96impl Hash for TransformType {
97    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
98        match self {
99            TransformType::Linear => "linear".hash(state),
100            TransformType::Arcsinh { cofactor: _ } => "arcsinh".hash(state),
101        }
102    }
103}
104
105#[test]
106fn test_transform() {
107    let t = TransformType::Linear;
108    assert_eq!(t.transform(&1.0), 1.0);
109    assert_eq!(t.inverse_transform(&1.0), 1.0);
110
111    let t = TransformType::Arcsinh { cofactor: 200.0 };
112    assert_eq!(t.transform(&1.0), 0.005);
113    assert_eq!(t.inverse_transform(&0.005), 1.0);
114    // Assert that the transform results in a number
115    assert!(!t.transform(&-1.0).is_nan());
116    assert!(!t.transform(&0.0).is_nan());
117    assert!(!t.transform(&-200.0).is_nan());
118}