use crate::error::{GlintError, Result};
use ndarray::{Array2, Array3};
pub trait GlintAlgorithm: Send + Sync {
fn detect_glint(&self, image: &Array3<f64>) -> Result<Array2<u8>>;
fn name(&self) -> &'static str;
fn description(&self) -> &'static str;
fn validate_parameters(&self) -> Result<()> {
Ok(())
}
fn required_bands(&self) -> Option<usize> {
None
}
fn supports_bands(&self, band_count: usize) -> bool {
match self.required_bands() {
Some(required) => band_count == required,
None => true,
}
}
}
#[derive(Debug, Clone)]
pub struct AlgorithmConfig {
pub parameters: std::collections::HashMap<String, AlgorithmParameter>,
}
#[derive(Debug, Clone)]
pub enum AlgorithmParameter {
Float(f64),
Int(i64),
Bool(bool),
String(String),
FloatArray(Vec<f64>),
}
impl AlgorithmParameter {
pub fn as_float(&self) -> Result<f64> {
match self {
AlgorithmParameter::Float(v) => Ok(*v),
_ => Err(GlintError::validation("Parameter is not a float")),
}
}
pub fn as_int(&self) -> Result<i64> {
match self {
AlgorithmParameter::Int(v) => Ok(*v),
_ => Err(GlintError::validation("Parameter is not an integer")),
}
}
pub fn as_bool(&self) -> Result<bool> {
match self {
AlgorithmParameter::Bool(v) => Ok(*v),
_ => Err(GlintError::validation("Parameter is not a boolean")),
}
}
pub fn as_string(&self) -> Result<&str> {
match self {
AlgorithmParameter::String(v) => Ok(v),
_ => Err(GlintError::validation("Parameter is not a string")),
}
}
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)
}
}
pub fn validate_threshold(threshold: f64) -> Result<()> {
if !(0.0..=1.0).contains(&threshold) {
Err(GlintError::InvalidThreshold { value: threshold })
} else {
Ok(())
}
}
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());
}
}