use crate::core::algorithm::{validate_thresholds, GlintAlgorithm};
use crate::error::{GlintError, Result};
use ndarray::{Array2, Array3};
use rayon::prelude::*;
#[derive(Debug, Clone)]
pub struct ThresholdAlgorithm {
thresholds: Vec<f64>,
}
impl ThresholdAlgorithm {
pub fn new(thresholds: Vec<f64>) -> Result<Self> {
validate_thresholds(&thresholds)?;
if thresholds.is_empty() {
return Err(GlintError::validation(
"At least one threshold must be provided",
));
}
Ok(Self { thresholds })
}
pub fn uniform(threshold: f64, band_count: usize) -> Result<Self> {
if band_count == 0 {
return Err(GlintError::validation("Band count must be greater than 0"));
}
Self::new(vec![threshold; band_count])
}
pub fn thresholds(&self) -> &[f64] {
&self.thresholds
}
pub fn band_count(&self) -> usize {
self.thresholds.len()
}
pub fn set_thresholds(&mut self, thresholds: Vec<f64>) -> Result<()> {
validate_thresholds(&thresholds)?;
if thresholds.is_empty() {
return Err(GlintError::validation(
"At least one threshold must be provided",
));
}
self.thresholds = thresholds;
Ok(())
}
pub fn set_band_threshold(&mut self, band_index: usize, threshold: f64) -> Result<()> {
if band_index >= self.thresholds.len() {
return Err(GlintError::validation(format!(
"Band index {} out of range (max: {})",
band_index,
self.thresholds.len() - 1
)));
}
crate::core::algorithm::validate_threshold(threshold)?;
self.thresholds[band_index] = threshold;
Ok(())
}
}
impl GlintAlgorithm for ThresholdAlgorithm {
fn detect_glint(&self, image: &Array3<f64>) -> Result<Array2<u8>> {
let (height, width, bands) = image.dim();
if bands != self.thresholds.len() {
return Err(GlintError::BandCountMismatch {
expected: self.thresholds.len(),
actual: bands,
});
}
let mask_data: Vec<u8> = (0..height * width)
.into_par_iter()
.map(|idx| {
let y = idx / width;
let x = idx % width;
let mut is_glint = false;
for b in 0..bands {
if image[[y, x, b]] > self.thresholds[b] {
is_glint = true;
break; }
}
if is_glint {
1
} else {
0
}
})
.collect();
let mask = Array2::from_shape_vec((height, width), mask_data)
.map_err(|_| GlintError::processing("Failed to create output mask"))?;
Ok(mask)
}
fn name(&self) -> &'static str {
"Threshold"
}
fn description(&self) -> &'static str {
"Detects glint using per-band threshold values in a disjunctive manner"
}
fn validate_parameters(&self) -> Result<()> {
validate_thresholds(&self.thresholds)?;
if self.thresholds.is_empty() {
return Err(GlintError::validation("No thresholds specified"));
}
Ok(())
}
fn required_bands(&self) -> Option<usize> {
Some(self.thresholds.len())
}
fn supports_bands(&self, band_count: usize) -> bool {
band_count == self.thresholds.len()
}
}
#[derive(Debug, Default)]
pub struct ThresholdAlgorithmBuilder {
thresholds: Vec<f64>,
}
impl ThresholdAlgorithmBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn add_threshold(mut self, threshold: f64) -> Result<Self> {
crate::core::algorithm::validate_threshold(threshold)?;
self.thresholds.push(threshold);
Ok(self)
}
pub fn add_thresholds(mut self, thresholds: &[f64]) -> Result<Self> {
validate_thresholds(thresholds)?;
self.thresholds.extend_from_slice(thresholds);
Ok(self)
}
pub fn with_thresholds(mut self, thresholds: &[f64]) -> Result<Self> {
validate_thresholds(thresholds)?;
self.thresholds = thresholds.to_vec();
Ok(self)
}
pub fn uniform(mut self, threshold: f64, band_count: usize) -> Result<Self> {
crate::core::algorithm::validate_threshold(threshold)?;
if band_count == 0 {
return Err(GlintError::validation("Band count must be greater than 0"));
}
self.thresholds = vec![threshold; band_count];
Ok(self)
}
pub fn build(self) -> Result<ThresholdAlgorithm> {
ThresholdAlgorithm::new(self.thresholds)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_threshold_algorithm_creation() {
let algorithm = ThresholdAlgorithm::new(vec![0.8, 0.9, 0.7]).unwrap();
assert_eq!(algorithm.thresholds(), &[0.8, 0.9, 0.7]);
assert_eq!(algorithm.band_count(), 3);
}
#[test]
fn test_threshold_algorithm_validation() {
assert!(ThresholdAlgorithm::new(vec![0.5, 0.8]).is_ok());
assert!(ThresholdAlgorithm::new(vec![1.5]).is_err());
assert!(ThresholdAlgorithm::new(vec![-0.1]).is_err());
assert!(ThresholdAlgorithm::new(vec![]).is_err());
}
#[test]
fn test_uniform_threshold() {
let algorithm = ThresholdAlgorithm::uniform(0.8, 3).unwrap();
assert_eq!(algorithm.thresholds(), &[0.8, 0.8, 0.8]);
assert!(ThresholdAlgorithm::uniform(1.5, 3).is_err());
assert!(ThresholdAlgorithm::uniform(0.8, 0).is_err());
}
#[test]
fn test_glint_detection() {
let algorithm = ThresholdAlgorithm::new(vec![0.5, 0.6, 0.7]).unwrap();
let mut image = ndarray::Array3::zeros((3, 3, 3));
image[[1, 1, 0]] = 0.8; image[[1, 1, 1]] = 0.9; image[[1, 1, 2]] = 0.9;
image[[0, 0, 0]] = 0.6; image[[0, 0, 1]] = 0.3; image[[0, 0, 2]] = 0.3;
image[[2, 2, 0]] = 0.3; image[[2, 2, 1]] = 0.3; image[[2, 2, 2]] = 0.3;
let mask = algorithm.detect_glint(&image).unwrap();
assert_eq!(mask[[1, 1]], 1); assert_eq!(mask[[0, 0]], 1); assert_eq!(mask[[2, 2]], 0); assert_eq!(mask[[0, 1]], 0); }
#[test]
fn test_band_count_mismatch() {
let algorithm = ThresholdAlgorithm::new(vec![0.5, 0.6]).unwrap();
let image = ndarray::Array3::zeros((2, 2, 3));
let result = algorithm.detect_glint(&image);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
GlintError::BandCountMismatch { .. }
));
}
#[test]
fn test_threshold_updates() {
let mut algorithm = ThresholdAlgorithm::new(vec![0.5, 0.6, 0.7]).unwrap();
algorithm.set_band_threshold(1, 0.8).unwrap();
assert_eq!(algorithm.thresholds()[1], 0.8);
assert!(algorithm.set_band_threshold(5, 0.8).is_err());
assert!(algorithm.set_band_threshold(1, 1.5).is_err());
algorithm.set_thresholds(vec![0.9, 0.8, 0.7]).unwrap();
assert_eq!(algorithm.thresholds(), &[0.9, 0.8, 0.7]);
}
#[test]
fn test_builder_pattern() {
let algorithm = ThresholdAlgorithmBuilder::new()
.add_threshold(0.8)
.unwrap()
.add_threshold(0.9)
.unwrap()
.add_threshold(0.7)
.unwrap()
.build()
.unwrap();
assert_eq!(algorithm.thresholds(), &[0.8, 0.9, 0.7]);
let uniform_algorithm = ThresholdAlgorithmBuilder::new()
.uniform(0.85, 4)
.unwrap()
.build()
.unwrap();
assert_eq!(uniform_algorithm.thresholds(), &[0.85, 0.85, 0.85, 0.85]);
}
#[test]
fn test_algorithm_interface() {
let algorithm = ThresholdAlgorithm::new(vec![0.8]).unwrap();
assert_eq!(algorithm.name(), "Threshold");
assert!(algorithm.description().contains("threshold"));
assert_eq!(algorithm.required_bands(), Some(1));
assert!(algorithm.supports_bands(1));
assert!(!algorithm.supports_bands(2));
assert!(algorithm.validate_parameters().is_ok());
}
}