Skip to main content

glint_mask_tools/core/
algorithm.rs

1/// Glint detection algorithm abstraction.
2///
3/// This module defines the [`GlintAlgorithm`] trait which provides a uniform
4/// interface for different glint detection algorithms. The primary algorithm
5/// is threshold-based detection, but the trait allows for future extensions.
6use crate::error::{GlintError, Result};
7use ndarray::{Array2, Array3};
8
9/// Trait for glint detection algorithms.
10///
11/// This trait defines the interface for algorithms that can detect glint
12/// (specular reflections) in imagery. Algorithms take normalized image data
13/// and return a binary mask indicating glint pixels.
14pub trait GlintAlgorithm: Send + Sync {
15    /// Apply the glint detection algorithm to an image
16    ///
17    /// # Arguments
18    ///
19    /// * `image` - Normalized image data (values in [0, 1]) with shape (height, width, channels)
20    ///
21    /// # Returns
22    ///
23    /// A binary mask where:
24    /// - `1` indicates glint pixels (to be masked)
25    /// - `0` indicates non-glint pixels (to keep)
26    ///
27    /// The output mask has shape (height, width)
28    fn detect_glint(&self, image: &Array3<f64>) -> Result<Array2<u8>>;
29
30    /// Get the name of this algorithm
31    fn name(&self) -> &'static str;
32
33    /// Get a description of this algorithm
34    fn description(&self) -> &'static str;
35
36    /// Validate that the algorithm parameters are valid
37    fn validate_parameters(&self) -> Result<()> {
38        // Default implementation - algorithms can override if needed
39        Ok(())
40    }
41
42    /// Get the required number of bands for this algorithm
43    ///
44    /// Returns None if the algorithm can work with any number of bands
45    fn required_bands(&self) -> Option<usize> {
46        None
47    }
48
49    /// Check if this algorithm supports the given band configuration
50    fn supports_bands(&self, band_count: usize) -> bool {
51        match self.required_bands() {
52            Some(required) => band_count == required,
53            None => true,
54        }
55    }
56}
57
58/// Configuration for algorithm parameters
59#[derive(Debug, Clone)]
60pub struct AlgorithmConfig {
61    /// Algorithm-specific parameters
62    pub parameters: std::collections::HashMap<String, AlgorithmParameter>,
63}
64
65/// Represents a configurable algorithm parameter
66#[derive(Debug, Clone)]
67pub enum AlgorithmParameter {
68    Float(f64),
69    Int(i64),
70    Bool(bool),
71    String(String),
72    FloatArray(Vec<f64>),
73}
74
75impl AlgorithmParameter {
76    /// Extract a float value from the parameter
77    pub fn as_float(&self) -> Result<f64> {
78        match self {
79            AlgorithmParameter::Float(v) => Ok(*v),
80            _ => Err(GlintError::validation("Parameter is not a float")),
81        }
82    }
83
84    /// Extract an integer value from the parameter
85    pub fn as_int(&self) -> Result<i64> {
86        match self {
87            AlgorithmParameter::Int(v) => Ok(*v),
88            _ => Err(GlintError::validation("Parameter is not an integer")),
89        }
90    }
91
92    /// Extract a boolean value from the parameter
93    pub fn as_bool(&self) -> Result<bool> {
94        match self {
95            AlgorithmParameter::Bool(v) => Ok(*v),
96            _ => Err(GlintError::validation("Parameter is not a boolean")),
97        }
98    }
99
100    /// Extract a string value from the parameter
101    pub fn as_string(&self) -> Result<&str> {
102        match self {
103            AlgorithmParameter::String(v) => Ok(v),
104            _ => Err(GlintError::validation("Parameter is not a string")),
105        }
106    }
107
108    /// Extract a float array value from the parameter
109    pub fn as_float_array(&self) -> Result<&[f64]> {
110        match self {
111            AlgorithmParameter::FloatArray(v) => Ok(v),
112            _ => Err(GlintError::validation("Parameter is not a float array")),
113        }
114    }
115}
116
117impl From<f64> for AlgorithmParameter {
118    fn from(value: f64) -> Self {
119        AlgorithmParameter::Float(value)
120    }
121}
122
123impl From<i64> for AlgorithmParameter {
124    fn from(value: i64) -> Self {
125        AlgorithmParameter::Int(value)
126    }
127}
128
129impl From<bool> for AlgorithmParameter {
130    fn from(value: bool) -> Self {
131        AlgorithmParameter::Bool(value)
132    }
133}
134
135impl From<String> for AlgorithmParameter {
136    fn from(value: String) -> Self {
137        AlgorithmParameter::String(value)
138    }
139}
140
141impl From<Vec<f64>> for AlgorithmParameter {
142    fn from(value: Vec<f64>) -> Self {
143        AlgorithmParameter::FloatArray(value)
144    }
145}
146
147/// Utility function to validate threshold values
148pub fn validate_threshold(threshold: f64) -> Result<()> {
149    if !(0.0..=1.0).contains(&threshold) {
150        Err(GlintError::InvalidThreshold { value: threshold })
151    } else {
152        Ok(())
153    }
154}
155
156/// Utility function to validate threshold arrays
157pub fn validate_thresholds(thresholds: &[f64]) -> Result<()> {
158    for &threshold in thresholds {
159        validate_threshold(threshold)?;
160    }
161    Ok(())
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_algorithm_parameter_conversion() {
170        let param = AlgorithmParameter::from(0.5);
171        assert_eq!(param.as_float().unwrap(), 0.5);
172
173        let param = AlgorithmParameter::from(42i64);
174        assert_eq!(param.as_int().unwrap(), 42);
175
176        let param = AlgorithmParameter::from(true);
177        assert!(param.as_bool().unwrap());
178
179        let param = AlgorithmParameter::from("test".to_string());
180        assert_eq!(param.as_string().unwrap(), "test");
181
182        let param = AlgorithmParameter::from(vec![0.1, 0.2, 0.3]);
183        assert_eq!(param.as_float_array().unwrap(), &[0.1, 0.2, 0.3]);
184    }
185
186    #[test]
187    fn test_validate_threshold() {
188        assert!(validate_threshold(0.5).is_ok());
189        assert!(validate_threshold(0.0).is_ok());
190        assert!(validate_threshold(1.0).is_ok());
191        assert!(validate_threshold(-0.1).is_err());
192        assert!(validate_threshold(1.1).is_err());
193    }
194
195    #[test]
196    fn test_validate_thresholds() {
197        assert!(validate_thresholds(&[0.1, 0.5, 0.9]).is_ok());
198        assert!(validate_thresholds(&[0.1, 1.5, 0.9]).is_err());
199    }
200}