Skip to main content

ruv_neural_core/
sensor.rs

1//! Sensor types for brain signal acquisition.
2
3use serde::{Deserialize, Serialize};
4
5/// Sensor technology type.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub enum SensorType {
8    /// Nitrogen-vacancy diamond magnetometer.
9    NvDiamond,
10    /// Optically pumped magnetometer.
11    Opm,
12    /// Electroencephalography.
13    Eeg,
14    /// Superconducting quantum interference device MEG.
15    SquidMeg,
16    /// Atom interferometer for gravitational neural sensing.
17    AtomInterferometer,
18}
19
20impl SensorType {
21    /// Typical sensitivity in fT/sqrt(Hz) for this sensor technology.
22    pub fn typical_sensitivity_ft_sqrt_hz(&self) -> f64 {
23        match self {
24            SensorType::NvDiamond => 10.0,
25            SensorType::Opm => 7.0,
26            SensorType::Eeg => 1000.0,
27            SensorType::SquidMeg => 3.0,
28            SensorType::AtomInterferometer => 1.0,
29        }
30    }
31}
32
33/// Sensor channel metadata.
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct SensorChannel {
36    /// Channel index.
37    pub id: usize,
38    /// Type of sensor.
39    pub sensor_type: SensorType,
40    /// Position in head-frame coordinates (x, y, z in meters).
41    pub position: [f64; 3],
42    /// Orientation unit normal vector.
43    pub orientation: [f64; 3],
44    /// Sensitivity in fT/sqrt(Hz).
45    pub sensitivity_ft_sqrt_hz: f64,
46    /// Sampling rate in Hz.
47    pub sample_rate_hz: f64,
48    /// Human-readable label (e.g., "Fz", "OPM-L01").
49    pub label: String,
50}
51
52/// Sensor array configuration (a collection of channels of one type).
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct SensorArray {
55    /// All channels in the array.
56    pub channels: Vec<SensorChannel>,
57    /// Sensor technology used by this array.
58    pub sensor_type: SensorType,
59    /// Human-readable name for the array.
60    pub name: String,
61}
62
63impl SensorArray {
64    /// Number of channels in the array.
65    pub fn num_channels(&self) -> usize {
66        self.channels.len()
67    }
68
69    /// Returns true if the array has no channels.
70    pub fn is_empty(&self) -> bool {
71        self.channels.is_empty()
72    }
73
74    /// Get a channel by its index within this array.
75    pub fn get_channel(&self, index: usize) -> Option<&SensorChannel> {
76        self.channels.get(index)
77    }
78
79    /// Get the bounding box of channel positions as ([min_x, min_y, min_z], [max_x, max_y, max_z]).
80    pub fn bounding_box(&self) -> Option<([f64; 3], [f64; 3])> {
81        if self.channels.is_empty() {
82            return None;
83        }
84        let mut min = [f64::INFINITY; 3];
85        let mut max = [f64::NEG_INFINITY; 3];
86        for ch in &self.channels {
87            for i in 0..3 {
88                if ch.position[i] < min[i] {
89                    min[i] = ch.position[i];
90                }
91                if ch.position[i] > max[i] {
92                    max[i] = ch.position[i];
93                }
94            }
95        }
96        Some((min, max))
97    }
98}