glint-mask-tools 0.1.1

Rust implementation of glint mask generation tools for UAV and aerial imagery
Documentation
/// Glint detection algorithm abstraction.
///
/// This module defines the [`GlintAlgorithm`] trait which provides a uniform
/// interface for different glint detection algorithms. The primary algorithm
/// is threshold-based detection, but the trait allows for future extensions.
use crate::error::{GlintError, Result};
use ndarray::{Array2, Array3};

/// Trait for glint detection algorithms.
///
/// This trait defines the interface for algorithms that can detect glint
/// (specular reflections) in imagery. Algorithms take normalized image data
/// and return a binary mask indicating glint pixels.
pub trait GlintAlgorithm: Send + Sync {
    /// Apply the glint detection algorithm to an image
    ///
    /// # Arguments
    ///
    /// * `image` - Normalized image data (values in [0, 1]) with shape (height, width, channels)
    ///
    /// # Returns
    ///
    /// A binary mask where:
    /// - `1` indicates glint pixels (to be masked)
    /// - `0` indicates non-glint pixels (to keep)
    ///
    /// The output mask has shape (height, width)
    fn detect_glint(&self, image: &Array3<f64>) -> Result<Array2<u8>>;

    /// Get the name of this algorithm
    fn name(&self) -> &'static str;

    /// Get a description of this algorithm
    fn description(&self) -> &'static str;

    /// Validate that the algorithm parameters are valid
    fn validate_parameters(&self) -> Result<()> {
        // Default implementation - algorithms can override if needed
        Ok(())
    }

    /// Get the required number of bands for this algorithm
    ///
    /// Returns None if the algorithm can work with any number of bands
    fn required_bands(&self) -> Option<usize> {
        None
    }

    /// Check if this algorithm supports the given band configuration
    fn supports_bands(&self, band_count: usize) -> bool {
        match self.required_bands() {
            Some(required) => band_count == required,
            None => true,
        }
    }
}

/// Configuration for algorithm parameters
#[derive(Debug, Clone)]
pub struct AlgorithmConfig {
    /// Algorithm-specific parameters
    pub parameters: std::collections::HashMap<String, AlgorithmParameter>,
}

/// Represents a configurable algorithm parameter
#[derive(Debug, Clone)]
pub enum AlgorithmParameter {
    Float(f64),
    Int(i64),
    Bool(bool),
    String(String),
    FloatArray(Vec<f64>),
}

impl AlgorithmParameter {
    /// Extract a float value from the parameter
    pub fn as_float(&self) -> Result<f64> {
        match self {
            AlgorithmParameter::Float(v) => Ok(*v),
            _ => Err(GlintError::validation("Parameter is not a float")),
        }
    }

    /// Extract an integer value from the parameter
    pub fn as_int(&self) -> Result<i64> {
        match self {
            AlgorithmParameter::Int(v) => Ok(*v),
            _ => Err(GlintError::validation("Parameter is not an integer")),
        }
    }

    /// Extract a boolean value from the parameter
    pub fn as_bool(&self) -> Result<bool> {
        match self {
            AlgorithmParameter::Bool(v) => Ok(*v),
            _ => Err(GlintError::validation("Parameter is not a boolean")),
        }
    }

    /// Extract a string value from the parameter
    pub fn as_string(&self) -> Result<&str> {
        match self {
            AlgorithmParameter::String(v) => Ok(v),
            _ => Err(GlintError::validation("Parameter is not a string")),
        }
    }

    /// Extract a float array value from the parameter
    pub fn as_float_array(&self) -> Result<&[f64]> {
        match self {
            AlgorithmParameter::FloatArray(v) => Ok(v),
            _ => Err(GlintError::validation("Parameter is not a float array")),
        }
    }
}

impl From<f64> for AlgorithmParameter {
    fn from(value: f64) -> Self {
        AlgorithmParameter::Float(value)
    }
}

impl From<i64> for AlgorithmParameter {
    fn from(value: i64) -> Self {
        AlgorithmParameter::Int(value)
    }
}

impl From<bool> for AlgorithmParameter {
    fn from(value: bool) -> Self {
        AlgorithmParameter::Bool(value)
    }
}

impl From<String> for AlgorithmParameter {
    fn from(value: String) -> Self {
        AlgorithmParameter::String(value)
    }
}

impl From<Vec<f64>> for AlgorithmParameter {
    fn from(value: Vec<f64>) -> Self {
        AlgorithmParameter::FloatArray(value)
    }
}

/// Utility function to validate threshold values
pub fn validate_threshold(threshold: f64) -> Result<()> {
    if !(0.0..=1.0).contains(&threshold) {
        Err(GlintError::InvalidThreshold { value: threshold })
    } else {
        Ok(())
    }
}

/// Utility function to validate threshold arrays
pub fn validate_thresholds(thresholds: &[f64]) -> Result<()> {
    for &threshold in thresholds {
        validate_threshold(threshold)?;
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_algorithm_parameter_conversion() {
        let param = AlgorithmParameter::from(0.5);
        assert_eq!(param.as_float().unwrap(), 0.5);

        let param = AlgorithmParameter::from(42i64);
        assert_eq!(param.as_int().unwrap(), 42);

        let param = AlgorithmParameter::from(true);
        assert!(param.as_bool().unwrap());

        let param = AlgorithmParameter::from("test".to_string());
        assert_eq!(param.as_string().unwrap(), "test");

        let param = AlgorithmParameter::from(vec![0.1, 0.2, 0.3]);
        assert_eq!(param.as_float_array().unwrap(), &[0.1, 0.2, 0.3]);
    }

    #[test]
    fn test_validate_threshold() {
        assert!(validate_threshold(0.5).is_ok());
        assert!(validate_threshold(0.0).is_ok());
        assert!(validate_threshold(1.0).is_ok());
        assert!(validate_threshold(-0.1).is_err());
        assert!(validate_threshold(1.1).is_err());
    }

    #[test]
    fn test_validate_thresholds() {
        assert!(validate_thresholds(&[0.1, 0.5, 0.9]).is_ok());
        assert!(validate_thresholds(&[0.1, 1.5, 0.9]).is_err());
    }
}