Skip to main content

glint_mask_tools/core/
sensor.rs

1/// Sensor configuration and management system.
2///
3/// This module provides the sensor configuration system that defines how
4/// different sensor types are handled, including their band configurations,
5/// bit depths, and associated loaders.
6use crate::error::{GlintError, Result};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10/// Represents a sensor band with its properties
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12pub struct Band {
13    /// Band name (e.g., "Red", "Green", "Blue", "NIR")
14    pub name: String,
15    /// Default threshold value for glint detection
16    pub default_threshold: f64,
17    /// Optional wavelength information in nanometers
18    pub wavelength: Option<f64>,
19    /// Optional band description
20    pub description: Option<String>,
21}
22
23impl Band {
24    /// Create a new band with default threshold
25    pub fn new(name: impl Into<String>, default_threshold: f64) -> Self {
26        Self {
27            name: name.into(),
28            default_threshold,
29            wavelength: None,
30            description: None,
31        }
32    }
33
34    /// Create a new band with wavelength information
35    pub fn with_wavelength(
36        name: impl Into<String>,
37        default_threshold: f64,
38        wavelength: f64,
39    ) -> Self {
40        Self {
41            name: name.into(),
42            default_threshold,
43            wavelength: Some(wavelength),
44            description: None,
45        }
46    }
47
48    /// Set the description for this band
49    pub fn with_description(mut self, description: impl Into<String>) -> Self {
50        self.description = Some(description.into());
51        self
52    }
53
54    /// Validate the band configuration
55    pub fn validate(&self) -> Result<()> {
56        if self.default_threshold < 0.0 || self.default_threshold > 1.0 {
57            return Err(GlintError::InvalidThreshold {
58                value: self.default_threshold,
59            });
60        }
61
62        if let Some(wavelength) = self.wavelength {
63            if wavelength <= 0.0 || wavelength > 3000.0 {
64                return Err(GlintError::validation(format!(
65                    "Invalid wavelength: {} nm",
66                    wavelength
67                )));
68            }
69        }
70
71        Ok(())
72    }
73}
74
75/// Sensor configuration that defines how to handle a specific sensor type
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct Sensor {
78    /// Unique identifier for this sensor
79    pub id: String,
80    /// Human-readable name
81    pub name: String,
82    /// List of bands in order
83    pub bands: Vec<Band>,
84    /// Bit depth of the sensor data
85    pub bit_depth: u8,
86    /// Loader type identifier
87    pub loader_type: String,
88    /// Optional description
89    pub description: Option<String>,
90    /// Loader-specific configuration
91    pub loader_config: HashMap<String, String>,
92}
93
94impl Sensor {
95    /// Create a new sensor configuration
96    pub fn new(
97        id: impl Into<String>,
98        name: impl Into<String>,
99        bands: Vec<Band>,
100        bit_depth: u8,
101        loader_type: impl Into<String>,
102    ) -> Self {
103        Self {
104            id: id.into(),
105            name: name.into(),
106            bands,
107            bit_depth,
108            loader_type: loader_type.into(),
109            description: None,
110            loader_config: HashMap::new(),
111        }
112    }
113
114    /// Set the description for this sensor
115    pub fn with_description(mut self, description: impl Into<String>) -> Self {
116        self.description = Some(description.into());
117        self
118    }
119
120    /// Add a loader configuration parameter
121    pub fn with_loader_config(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
122        self.loader_config.insert(key.into(), value.into());
123        self
124    }
125
126    /// Get the default thresholds for all bands
127    pub fn default_thresholds(&self) -> Vec<f64> {
128        self.bands.iter().map(|b| b.default_threshold).collect()
129    }
130
131    /// Get the number of bands
132    pub fn band_count(&self) -> usize {
133        self.bands.len()
134    }
135
136    /// Get band names
137    pub fn band_names(&self) -> Vec<&str> {
138        self.bands.iter().map(|b| b.name.as_str()).collect()
139    }
140
141    /// Validate the sensor configuration
142    pub fn validate(&self) -> Result<()> {
143        // Validate bit depth
144        if !matches!(self.bit_depth, 8 | 16 | 32) {
145            return Err(GlintError::InvalidBitDepth {
146                bit_depth: self.bit_depth,
147            });
148        }
149
150        // Validate bands
151        if self.bands.is_empty() {
152            return Err(GlintError::validation("Sensor must have at least one band"));
153        }
154
155        for band in &self.bands {
156            band.validate()?;
157        }
158
159        // Check for duplicate band names
160        let mut names = std::collections::HashSet::new();
161        for band in &self.bands {
162            if !names.insert(&band.name) {
163                return Err(GlintError::validation(format!(
164                    "Duplicate band name: {}",
165                    band.name
166                )));
167            }
168        }
169
170        Ok(())
171    }
172}
173
174/// Registry for managing sensor configurations
175#[derive(Debug, Clone)]
176pub struct SensorRegistry {
177    sensors: HashMap<String, Sensor>,
178}
179
180impl SensorRegistry {
181    /// Create a new empty sensor registry
182    pub fn new() -> Self {
183        Self {
184            sensors: HashMap::new(),
185        }
186    }
187
188    /// Create a registry with default sensors loaded from built-in TOML
189    pub fn with_defaults() -> Self {
190        let mut registry = Self::new();
191
192        // Load default sensors from built-in TOML configuration
193        if let Ok(sensors) = Self::load_builtin_sensors() {
194            for sensor in sensors {
195                registry.register_sensor(sensor).unwrap();
196            }
197        }
198
199        registry
200    }
201
202    /// Create a registry with sensors loaded from user config, falling back to defaults
203    pub fn from_user_config() -> Self {
204        let mut registry = Self::new();
205
206        // Try to load from user config first
207        if let Ok(sensors) = Self::load_user_sensors() {
208            for sensor in sensors {
209                if registry.register_sensor(sensor).is_err() {
210                    // If sensor registration fails, continue with others
211                    continue;
212                }
213            }
214        }
215
216        // If no user sensors were loaded, fall back to built-in defaults
217        if registry.is_empty() {
218            if let Ok(sensors) = Self::load_builtin_sensors() {
219                for sensor in sensors {
220                    registry.register_sensor(sensor).unwrap();
221                }
222            }
223        }
224
225        registry
226    }
227
228    /// Load sensors from user configuration directory
229    fn load_user_sensors() -> Result<Vec<Sensor>> {
230        use crate::config::ConfigManager;
231
232        let config_manager = ConfigManager::new();
233        let config_dir = config_manager.get_config_dir()?;
234        let config_path = config_dir.join("sensors.toml");
235
236        // Create sample config if it doesn't exist
237        if !config_path.exists() {
238            std::fs::create_dir_all(&config_dir)
239                .map_err(|_| GlintError::config("Failed to create config directory"))?;
240
241            // Copy default sensors to user config
242            config_manager.create_sample_config(&config_path)?;
243        }
244
245        config_manager.load_from_file(&config_path)
246    }
247
248    /// Load sensors from the built-in TOML configuration
249    fn load_builtin_sensors() -> Result<Vec<Sensor>> {
250        // Include the built-in sensor configuration at compile time
251        const BUILTIN_CONFIG: &str = include_str!("../../sensor_configs/default_sensors.toml");
252
253        let config_file: crate::config::sensor_config::SensorConfigFile =
254            toml::from_str(BUILTIN_CONFIG).map_err(GlintError::Deserialization)?;
255
256        // Validate version (for future compatibility)
257        if config_file.version != "1.0" {
258            return Err(GlintError::config(format!(
259                "Unsupported configuration file version: {}",
260                config_file.version
261            )));
262        }
263
264        // Convert to sensors and validate
265        let mut sensors = Vec::new();
266        for sensor_config in config_file.sensors {
267            let sensor = Sensor::from(sensor_config);
268            sensor.validate()?;
269            sensors.push(sensor);
270        }
271
272        Ok(sensors)
273    }
274
275    /// Register a new sensor
276    pub fn register_sensor(&mut self, sensor: Sensor) -> Result<()> {
277        sensor.validate()?;
278
279        if self.sensors.contains_key(&sensor.id) {
280            return Err(GlintError::validation(format!(
281                "Sensor with ID '{}' already registered",
282                sensor.id
283            )));
284        }
285
286        self.sensors.insert(sensor.id.clone(), sensor);
287        Ok(())
288    }
289
290    /// Get a sensor by ID
291    pub fn get_sensor(&self, id: &str) -> Option<&Sensor> {
292        self.sensors.get(id)
293    }
294
295    /// Get all registered sensor IDs
296    pub fn sensor_ids(&self) -> Vec<&str> {
297        self.sensors.keys().map(|s| s.as_str()).collect()
298    }
299
300    /// Get all registered sensors
301    pub fn sensors(&self) -> Vec<&Sensor> {
302        self.sensors.values().collect()
303    }
304
305    /// Remove a sensor
306    pub fn remove_sensor(&mut self, id: &str) -> Option<Sensor> {
307        self.sensors.remove(id)
308    }
309
310    /// Check if a sensor is registered
311    pub fn has_sensor(&self, id: &str) -> bool {
312        self.sensors.contains_key(id)
313    }
314
315    /// Get the number of registered sensors
316    pub fn len(&self) -> usize {
317        self.sensors.len()
318    }
319
320    /// Check if the registry is empty
321    pub fn is_empty(&self) -> bool {
322        self.sensors.is_empty()
323    }
324
325    /// Load sensors from a configuration file
326    pub fn load_from_file(&mut self, path: &std::path::Path) -> Result<()> {
327        // Use the config module for proper deserialization
328        use crate::config::ConfigManager;
329        let config_manager = ConfigManager::new();
330        let sensors = config_manager.load_from_file(path)?;
331
332        for sensor in sensors {
333            self.register_sensor(sensor)?;
334        }
335
336        Ok(())
337    }
338
339    /// Save sensors to a configuration file
340    pub fn save_to_file(&self, path: &std::path::Path) -> Result<()> {
341        // Use the config module for proper serialization
342        use crate::config::ConfigManager;
343        let config_manager = ConfigManager::new();
344        let sensors: Vec<Sensor> = self.sensors.values().cloned().collect();
345        config_manager.save_to_file(&sensors, path)
346    }
347}
348
349impl Default for SensorRegistry {
350    fn default() -> Self {
351        Self::new()
352    }
353}
354
355/// Common band definitions for reuse
356pub mod bands {
357    use super::Band;
358
359    /// Blue band (typically ~475nm)
360    pub fn blue() -> Band {
361        Band::with_wavelength("Blue", 0.875, 475.0)
362            .with_description("Blue band for visible light imaging")
363    }
364
365    /// Green band (typically ~560nm)
366    pub fn green() -> Band {
367        Band::with_wavelength("Green", 1.0, 560.0)
368            .with_description("Green band for visible light imaging")
369    }
370
371    /// Red band (typically ~650nm)
372    pub fn red() -> Band {
373        Band::with_wavelength("Red", 1.0, 650.0)
374            .with_description("Red band for visible light imaging")
375    }
376
377    /// Red Edge band (typically ~730nm)
378    pub fn red_edge() -> Band {
379        Band::with_wavelength("Red Edge", 1.0, 730.0)
380            .with_description("Red edge band for vegetation analysis")
381    }
382
383    /// Near-infrared band (typically ~840nm)
384    pub fn near_ir() -> Band {
385        Band::with_wavelength("Near-IR", 1.0, 840.0)
386            .with_description("Near-infrared band for vegetation analysis")
387    }
388}
389
390#[cfg(test)]
391mod tests {
392    use super::*;
393    use tempfile::tempdir;
394
395    #[test]
396    fn test_band_creation() {
397        let band = Band::new("Red", 0.9);
398        assert_eq!(band.name, "Red");
399        assert_eq!(band.default_threshold, 0.9);
400        assert!(band.wavelength.is_none());
401
402        let band = Band::with_wavelength("Blue", 0.8, 475.0);
403        assert_eq!(band.wavelength, Some(475.0));
404    }
405
406    #[test]
407    fn test_band_validation() {
408        let band = Band::new("Red", 0.5);
409        assert!(band.validate().is_ok());
410
411        let band = Band::new("Red", 1.5);
412        assert!(band.validate().is_err());
413
414        let band = Band::with_wavelength("Red", 0.5, -100.0);
415        assert!(band.validate().is_err());
416    }
417
418    #[test]
419    fn test_sensor_creation() {
420        let bands = vec![
421            Band::new("Red", 0.9),
422            Band::new("Green", 0.8),
423            Band::new("Blue", 0.7),
424        ];
425
426        let sensor = Sensor::new("rgb", "RGB Camera", bands, 8, "single_file");
427        assert_eq!(sensor.id, "rgb");
428        assert_eq!(sensor.band_count(), 3);
429        assert_eq!(sensor.default_thresholds(), vec![0.9, 0.8, 0.7]);
430    }
431
432    #[test]
433    fn test_sensor_validation() {
434        let bands = vec![Band::new("Red", 0.9)];
435        let sensor = Sensor::new("test", "Test", bands, 8, "single_file");
436        assert!(sensor.validate().is_ok());
437
438        let sensor = Sensor::new("test", "Test", vec![], 8, "single_file");
439        assert!(sensor.validate().is_err());
440
441        let bands = vec![Band::new("Red", 1.5)];
442        let sensor = Sensor::new("test", "Test", bands, 8, "single_file");
443        assert!(sensor.validate().is_err());
444    }
445
446    #[test]
447    fn test_sensor_registry() {
448        let mut registry = SensorRegistry::new();
449
450        let bands = vec![Band::new("Red", 0.9)];
451        let sensor = Sensor::new("test", "Test", bands, 8, "single_file");
452
453        assert!(registry.register_sensor(sensor).is_ok());
454        assert!(registry.has_sensor("test"));
455        assert_eq!(registry.len(), 1);
456
457        let retrieved = registry.get_sensor("test").unwrap();
458        assert_eq!(retrieved.id, "test");
459    }
460
461    #[test]
462    fn test_sensor_registry_file_operations() {
463        let temp_dir = tempdir().unwrap();
464        let config_path = temp_dir.path().join("sensors.toml");
465
466        let mut registry = SensorRegistry::new();
467        let bands = vec![Band::new("Red", 0.9)];
468        let sensor = Sensor::new("test", "Test", bands, 8, "single_file");
469        registry.register_sensor(sensor).unwrap();
470
471        // Save to file
472        registry.save_to_file(&config_path).unwrap();
473        assert!(config_path.exists());
474
475        // Load from file
476        let mut new_registry = SensorRegistry::new();
477        new_registry.load_from_file(&config_path).unwrap();
478        assert!(new_registry.has_sensor("test"));
479    }
480}