glint_mask_tools/algorithms/
threshold.rs1use crate::core::algorithm::{validate_thresholds, GlintAlgorithm};
7use crate::error::{GlintError, Result};
8use ndarray::{Array2, Array3};
9use rayon::prelude::*;
10
11#[derive(Debug, Clone)]
20pub struct ThresholdAlgorithm {
21 thresholds: Vec<f64>,
23}
24
25impl ThresholdAlgorithm {
26 pub fn new(thresholds: Vec<f64>) -> Result<Self> {
43 validate_thresholds(&thresholds)?;
44
45 if thresholds.is_empty() {
46 return Err(GlintError::validation(
47 "At least one threshold must be provided",
48 ));
49 }
50
51 Ok(Self { thresholds })
52 }
53
54 pub fn uniform(threshold: f64, band_count: usize) -> Result<Self> {
61 if band_count == 0 {
62 return Err(GlintError::validation("Band count must be greater than 0"));
63 }
64
65 Self::new(vec![threshold; band_count])
66 }
67
68 pub fn thresholds(&self) -> &[f64] {
70 &self.thresholds
71 }
72
73 pub fn band_count(&self) -> usize {
75 self.thresholds.len()
76 }
77
78 pub fn set_thresholds(&mut self, thresholds: Vec<f64>) -> Result<()> {
80 validate_thresholds(&thresholds)?;
81
82 if thresholds.is_empty() {
83 return Err(GlintError::validation(
84 "At least one threshold must be provided",
85 ));
86 }
87
88 self.thresholds = thresholds;
89 Ok(())
90 }
91
92 pub fn set_band_threshold(&mut self, band_index: usize, threshold: f64) -> Result<()> {
94 if band_index >= self.thresholds.len() {
95 return Err(GlintError::validation(format!(
96 "Band index {} out of range (max: {})",
97 band_index,
98 self.thresholds.len() - 1
99 )));
100 }
101
102 crate::core::algorithm::validate_threshold(threshold)?;
103 self.thresholds[band_index] = threshold;
104 Ok(())
105 }
106}
107
108impl GlintAlgorithm for ThresholdAlgorithm {
109 fn detect_glint(&self, image: &Array3<f64>) -> Result<Array2<u8>> {
110 let (height, width, bands) = image.dim();
111
112 if bands != self.thresholds.len() {
114 return Err(GlintError::BandCountMismatch {
115 expected: self.thresholds.len(),
116 actual: bands,
117 });
118 }
119
120 let mask_data: Vec<u8> = (0..height * width)
122 .into_par_iter()
123 .map(|idx| {
124 let y = idx / width;
125 let x = idx % width;
126 let mut is_glint = false;
127
128 for b in 0..bands {
130 if image[[y, x, b]] > self.thresholds[b] {
131 is_glint = true;
132 break; }
134 }
135
136 if is_glint {
137 1
138 } else {
139 0
140 }
141 })
142 .collect();
143
144 let mask = Array2::from_shape_vec((height, width), mask_data)
146 .map_err(|_| GlintError::processing("Failed to create output mask"))?;
147
148 Ok(mask)
149 }
150
151 fn name(&self) -> &'static str {
152 "Threshold"
153 }
154
155 fn description(&self) -> &'static str {
156 "Detects glint using per-band threshold values in a disjunctive manner"
157 }
158
159 fn validate_parameters(&self) -> Result<()> {
160 validate_thresholds(&self.thresholds)?;
161
162 if self.thresholds.is_empty() {
163 return Err(GlintError::validation("No thresholds specified"));
164 }
165
166 Ok(())
167 }
168
169 fn required_bands(&self) -> Option<usize> {
170 Some(self.thresholds.len())
171 }
172
173 fn supports_bands(&self, band_count: usize) -> bool {
174 band_count == self.thresholds.len()
175 }
176}
177
178#[derive(Debug, Default)]
180pub struct ThresholdAlgorithmBuilder {
181 thresholds: Vec<f64>,
182}
183
184impl ThresholdAlgorithmBuilder {
185 pub fn new() -> Self {
187 Self::default()
188 }
189
190 pub fn add_threshold(mut self, threshold: f64) -> Result<Self> {
192 crate::core::algorithm::validate_threshold(threshold)?;
193 self.thresholds.push(threshold);
194 Ok(self)
195 }
196
197 pub fn add_thresholds(mut self, thresholds: &[f64]) -> Result<Self> {
199 validate_thresholds(thresholds)?;
200 self.thresholds.extend_from_slice(thresholds);
201 Ok(self)
202 }
203
204 pub fn with_thresholds(mut self, thresholds: &[f64]) -> Result<Self> {
206 validate_thresholds(thresholds)?;
207 self.thresholds = thresholds.to_vec();
208 Ok(self)
209 }
210
211 pub fn uniform(mut self, threshold: f64, band_count: usize) -> Result<Self> {
213 crate::core::algorithm::validate_threshold(threshold)?;
214
215 if band_count == 0 {
216 return Err(GlintError::validation("Band count must be greater than 0"));
217 }
218
219 self.thresholds = vec![threshold; band_count];
220 Ok(self)
221 }
222
223 pub fn build(self) -> Result<ThresholdAlgorithm> {
225 ThresholdAlgorithm::new(self.thresholds)
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 #[test]
234 fn test_threshold_algorithm_creation() {
235 let algorithm = ThresholdAlgorithm::new(vec![0.8, 0.9, 0.7]).unwrap();
236 assert_eq!(algorithm.thresholds(), &[0.8, 0.9, 0.7]);
237 assert_eq!(algorithm.band_count(), 3);
238 }
239
240 #[test]
241 fn test_threshold_algorithm_validation() {
242 assert!(ThresholdAlgorithm::new(vec![0.5, 0.8]).is_ok());
244
245 assert!(ThresholdAlgorithm::new(vec![1.5]).is_err());
247 assert!(ThresholdAlgorithm::new(vec![-0.1]).is_err());
248 assert!(ThresholdAlgorithm::new(vec![]).is_err());
249 }
250
251 #[test]
252 fn test_uniform_threshold() {
253 let algorithm = ThresholdAlgorithm::uniform(0.8, 3).unwrap();
254 assert_eq!(algorithm.thresholds(), &[0.8, 0.8, 0.8]);
255
256 assert!(ThresholdAlgorithm::uniform(1.5, 3).is_err());
257 assert!(ThresholdAlgorithm::uniform(0.8, 0).is_err());
258 }
259
260 #[test]
261 fn test_glint_detection() {
262 let algorithm = ThresholdAlgorithm::new(vec![0.5, 0.6, 0.7]).unwrap();
263
264 let mut image = ndarray::Array3::zeros((3, 3, 3));
266
267 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();
283
284 assert_eq!(mask[[1, 1]], 1); assert_eq!(mask[[0, 0]], 1); assert_eq!(mask[[2, 2]], 0); assert_eq!(mask[[0, 1]], 0); }
290
291 #[test]
292 fn test_band_count_mismatch() {
293 let algorithm = ThresholdAlgorithm::new(vec![0.5, 0.6]).unwrap();
294 let image = ndarray::Array3::zeros((2, 2, 3)); let result = algorithm.detect_glint(&image);
297 assert!(result.is_err());
298 assert!(matches!(
299 result.unwrap_err(),
300 GlintError::BandCountMismatch { .. }
301 ));
302 }
303
304 #[test]
305 fn test_threshold_updates() {
306 let mut algorithm = ThresholdAlgorithm::new(vec![0.5, 0.6, 0.7]).unwrap();
307
308 algorithm.set_band_threshold(1, 0.8).unwrap();
310 assert_eq!(algorithm.thresholds()[1], 0.8);
311
312 assert!(algorithm.set_band_threshold(5, 0.8).is_err());
314
315 assert!(algorithm.set_band_threshold(1, 1.5).is_err());
317
318 algorithm.set_thresholds(vec![0.9, 0.8, 0.7]).unwrap();
320 assert_eq!(algorithm.thresholds(), &[0.9, 0.8, 0.7]);
321 }
322
323 #[test]
324 fn test_builder_pattern() {
325 let algorithm = ThresholdAlgorithmBuilder::new()
326 .add_threshold(0.8)
327 .unwrap()
328 .add_threshold(0.9)
329 .unwrap()
330 .add_threshold(0.7)
331 .unwrap()
332 .build()
333 .unwrap();
334
335 assert_eq!(algorithm.thresholds(), &[0.8, 0.9, 0.7]);
336
337 let uniform_algorithm = ThresholdAlgorithmBuilder::new()
338 .uniform(0.85, 4)
339 .unwrap()
340 .build()
341 .unwrap();
342
343 assert_eq!(uniform_algorithm.thresholds(), &[0.85, 0.85, 0.85, 0.85]);
344 }
345
346 #[test]
347 fn test_algorithm_interface() {
348 let algorithm = ThresholdAlgorithm::new(vec![0.8]).unwrap();
349
350 assert_eq!(algorithm.name(), "Threshold");
351 assert!(algorithm.description().contains("threshold"));
352 assert_eq!(algorithm.required_bands(), Some(1));
353 assert!(algorithm.supports_bands(1));
354 assert!(!algorithm.supports_bands(2));
355 assert!(algorithm.validate_parameters().is_ok());
356 }
357}