#[derive(Debug, Clone, Default)]
pub struct SpikeEventStore {
#[cfg_attr(not(test), allow(dead_code))]
pub times_sec: Vec<f64>,
by_channel: Vec<Vec<f64>>,
}
impl SpikeEventStore {
pub fn len(&self) -> usize {
self.times_sec.len()
}
pub fn is_empty(&self) -> bool {
self.times_sec.is_empty()
}
pub fn count(&self, channel: usize) -> usize {
self.by_channel.get(channel).map_or(0, Vec::len)
}
pub fn in_window(&self, channel: usize, t0: f64, t1: f64) -> &[f64] {
let Some(times) = self.by_channel.get(channel) else { return &[] };
let lo = times.partition_point(|&t| t < t0);
let hi = times.partition_point(|&t| t <= t1);
×[lo..hi.max(lo)]
}
#[cfg(test)]
pub fn detect(source: &dyn dspk_view::SignalBackend) -> Self {
use dspk_synapse::{detect_spikes_threshold, SpikePolarity, ThresholdSpikeDetector};
const THRESHOLD_FACTOR: f32 = 4.5;
const REFRACTORY_SEC: f64 = 0.002;
let info = source.info();
let sample_rate = info.sample_rate_hz();
let samples = info.samples as usize;
let mut by_channel = Vec::with_capacity(info.channel_count());
let mut all: Vec<f64> = Vec::new();
let mut trace = vec![0.0f32; samples];
for ch in 0..info.channel_count() {
if source.read(&[ch], 0..samples as u64, &mut trace).is_err() {
by_channel.push(Vec::new());
continue;
}
let settings = ThresholdSpikeDetector::new(THRESHOLD_FACTOR, REFRACTORY_SEC * 1e3, SpikePolarity::Negative);
let events = detect_spikes_threshold(&trace, 1, samples, sample_rate, &settings);
let times: Vec<f64> = events
.iter()
.map(|e| e.sample_index as f64 / sample_rate)
.collect();
all.extend_from_slice(×);
by_channel.push(times);
}
all.sort_by(f64::total_cmp);
Self { times_sec: all, by_channel }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data::recording::Dataset;
#[test]
fn test_spike_event_detection() {
let sample_rate = 10_000.0;
let samples = 2_000;
let mut data = vec![1.0f32; 2 * samples];
data[500] = -100.0; data[505] = -120.0;
data[600] = -100.0;
data[samples + 1000] = -110.0;
let ds = Dataset::from_samples("test", data, 2, sample_rate);
let store = SpikeEventStore::detect(ds.signal().as_ref());
assert_eq!(store.len(), 3);
assert!((store.times_sec[0] - 0.0505).abs() < 1e-6);
assert!((store.times_sec[1] - 0.060).abs() < 1e-6);
assert!((store.times_sec[2] - 0.100).abs() < 1e-6);
assert_eq!(store.in_window(0, 0.0, 1.0), &[0.0505, 0.060]);
assert_eq!(store.in_window(1, 0.0, 1.0), &[0.100]);
assert_eq!(store.in_window(0, 0.055, 1.0), &[0.060]);
assert_eq!(store.in_window(1, 0.0, 0.05), &[] as &[f64]);
assert_eq!(store.in_window(5, 0.0, 1.0), &[] as &[f64]);
}
}