use serde::{Deserialize, Serialize};
use crate::error::{Result, RuvNeuralError};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultiChannelTimeSeries {
pub data: Vec<Vec<f64>>,
pub sample_rate_hz: f64,
pub num_channels: usize,
pub num_samples: usize,
pub timestamp_start: f64,
}
impl MultiChannelTimeSeries {
pub fn new(data: Vec<Vec<f64>>, sample_rate_hz: f64, timestamp_start: f64) -> Result<Self> {
if !sample_rate_hz.is_finite() || sample_rate_hz <= 0.0 {
return Err(RuvNeuralError::Signal(
"sample_rate_hz must be finite and positive".into(),
));
}
let num_channels = data.len();
if num_channels == 0 {
return Err(RuvNeuralError::Signal(
"Time series must have at least one channel".into(),
));
}
let num_samples = data[0].len();
for (i, ch) in data.iter().enumerate() {
if ch.len() != num_samples {
return Err(RuvNeuralError::DimensionMismatch {
expected: num_samples,
got: ch.len(),
});
}
let _ = i; }
Ok(Self {
data,
sample_rate_hz,
num_channels,
num_samples,
timestamp_start,
})
}
pub fn duration_s(&self) -> f64 {
self.num_samples as f64 / self.sample_rate_hz
}
pub fn channel(&self, index: usize) -> Result<&[f64]> {
if index >= self.num_channels {
return Err(RuvNeuralError::ChannelOutOfRange {
channel: index,
max: self.num_channels.saturating_sub(1),
});
}
Ok(&self.data[index])
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum FrequencyBand {
Delta,
Theta,
Alpha,
Beta,
Gamma,
HighGamma,
Custom {
low_hz: f64,
high_hz: f64,
},
}
impl FrequencyBand {
pub fn range_hz(&self) -> (f64, f64) {
match self {
FrequencyBand::Delta => (1.0, 4.0),
FrequencyBand::Theta => (4.0, 8.0),
FrequencyBand::Alpha => (8.0, 13.0),
FrequencyBand::Beta => (13.0, 30.0),
FrequencyBand::Gamma => (30.0, 100.0),
FrequencyBand::HighGamma => (100.0, 200.0),
FrequencyBand::Custom { low_hz, high_hz } => (*low_hz, *high_hz),
}
}
pub fn center_hz(&self) -> f64 {
let (lo, hi) = self.range_hz();
(lo + hi) / 2.0
}
pub fn bandwidth_hz(&self) -> f64 {
let (lo, hi) = self.range_hz();
hi - lo
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpectralFeatures {
pub band_powers: Vec<(FrequencyBand, f64)>,
pub spectral_entropy: f64,
pub peak_frequency_hz: f64,
pub total_power: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeFrequencyMap {
pub data: Vec<Vec<f64>>,
pub time_points: Vec<f64>,
pub frequency_bins: Vec<f64>,
}
impl TimeFrequencyMap {
pub fn num_time_points(&self) -> usize {
self.time_points.len()
}
pub fn num_frequency_bins(&self) -> usize {
self.frequency_bins.len()
}
}