Initial commit

This commit is contained in:
redclawsystems
2026-03-04 00:08:42 +00:00
commit 4d88dc0584
4449 changed files with 1556714 additions and 0 deletions
+383
View File
@@ -0,0 +1,383 @@
//! Events and annotations for neuroimaging data.
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Event identifier - maps event codes to names
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventId {
/// Mapping from event name to numeric code
pub name_to_id: HashMap<String, i32>,
/// Mapping from numeric code to event name
pub id_to_name: HashMap<i32, String>,
}
impl EventId {
/// Create a new empty event ID mapping
#[must_use]
pub fn new() -> Self {
Self {
name_to_id: HashMap::new(),
id_to_name: HashMap::new(),
}
}
/// Add an event type
pub fn add(&mut self, name: impl Into<String>, id: i32) {
let name = name.into();
self.name_to_id.insert(name.clone(), id);
self.id_to_name.insert(id, name);
}
/// Get the numeric ID for an event name
#[must_use]
pub fn get_id(&self, name: &str) -> Option<i32> {
self.name_to_id.get(name).copied()
}
/// Get the name for a numeric ID
#[must_use]
pub fn get_name(&self, id: i32) -> Option<&str> {
self.id_to_name.get(&id).map(String::as_str)
}
/// Get all event names
#[must_use]
pub fn names(&self) -> Vec<&str> {
self.name_to_id.keys().map(String::as_str).collect()
}
}
impl Default for EventId {
fn default() -> Self {
Self::new()
}
}
/// A discrete event in the recording (stimulus, response, marker)
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Event {
/// Sample number when the event occurred (0-indexed)
pub sample: usize,
/// Duration in samples (0 for instantaneous events)
pub duration: usize,
/// Event code/value
pub value: i32,
}
impl Event {
/// Create a new instantaneous event
#[must_use]
pub fn new(sample: usize, value: i32) -> Self {
Self {
sample,
duration: 0,
value,
}
}
/// Create a new event with duration
#[must_use]
pub fn with_duration(sample: usize, duration: usize, value: i32) -> Self {
Self {
sample,
duration,
value,
}
}
/// Convert sample to time given sampling frequency
#[must_use]
pub fn time(&self, sfreq: f64) -> f64 {
self.sample as f64 / sfreq
}
/// Convert duration to time given sampling frequency
#[must_use]
pub fn duration_sec(&self, sfreq: f64) -> f64 {
self.duration as f64 / sfreq
}
}
/// Collection of events
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Events {
/// List of events sorted by sample number
events: Vec<Event>,
/// Event ID mapping
pub event_id: EventId,
}
impl Events {
/// Create a new empty events collection
#[must_use]
pub fn new() -> Self {
Self {
events: Vec::new(),
event_id: EventId::new(),
}
}
/// Create from a vector of events
#[must_use]
pub fn from_events(mut events: Vec<Event>, event_id: EventId) -> Self {
events.sort_by_key(|e| e.sample);
Self { events, event_id }
}
/// Number of events
#[must_use]
pub fn len(&self) -> usize {
self.events.len()
}
/// Returns true if there are no events
#[must_use]
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
/// Add an event
pub fn add(&mut self, event: Event) {
let pos = self
.events
.binary_search_by_key(&event.sample, |e| e.sample)
.unwrap_or_else(|e| e);
self.events.insert(pos, event);
}
/// Get events matching a specific value
#[must_use]
pub fn find_by_value(&self, value: i32) -> Vec<&Event> {
self.events.iter().filter(|e| e.value == value).collect()
}
/// Get events matching a specific name
#[must_use]
pub fn find_by_name(&self, name: &str) -> Vec<&Event> {
if let Some(id) = self.event_id.get_id(name) {
self.find_by_value(id)
} else {
Vec::new()
}
}
/// Get events in a time range (inclusive)
#[must_use]
pub fn in_range(&self, start_sample: usize, end_sample: usize) -> Vec<&Event> {
self.events
.iter()
.filter(|e| e.sample >= start_sample && e.sample <= end_sample)
.collect()
}
/// Get all events as a slice
#[must_use]
pub fn as_slice(&self) -> &[Event] {
&self.events
}
/// Iterate over events
pub fn iter(&self) -> impl Iterator<Item = &Event> {
self.events.iter()
}
/// Get unique event values
#[must_use]
pub fn unique_values(&self) -> Vec<i32> {
let mut values: Vec<i32> = self.events.iter().map(|e| e.value).collect();
values.sort_unstable();
values.dedup();
values
}
/// Count events by value
#[must_use]
pub fn counts(&self) -> HashMap<i32, usize> {
let mut counts = HashMap::new();
for event in &self.events {
*counts.entry(event.value).or_insert(0) += 1;
}
counts
}
}
impl IntoIterator for Events {
type Item = Event;
type IntoIter = std::vec::IntoIter<Event>;
fn into_iter(self) -> Self::IntoIter {
self.events.into_iter()
}
}
impl<'a> IntoIterator for &'a Events {
type Item = &'a Event;
type IntoIter = std::slice::Iter<'a, Event>;
fn into_iter(self) -> Self::IntoIter {
self.events.iter()
}
}
/// An annotation - text description with time range
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Annotation {
/// Start time in seconds
pub onset: f64,
/// Duration in seconds
pub duration: f64,
/// Description/label
pub description: String,
}
impl Annotation {
/// Create a new annotation
#[must_use]
pub fn new(onset: f64, duration: f64, description: impl Into<String>) -> Self {
Self {
onset,
duration,
description: description.into(),
}
}
/// Create an instantaneous annotation (duration = 0)
#[must_use]
pub fn instant(onset: f64, description: impl Into<String>) -> Self {
Self::new(onset, 0.0, description)
}
/// End time of the annotation
#[must_use]
pub fn end(&self) -> f64 {
self.onset + self.duration
}
/// Check if a time point falls within this annotation
#[must_use]
pub fn contains(&self, time: f64) -> bool {
time >= self.onset && time <= self.end()
}
}
/// Collection of annotations
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Annotations {
annotations: Vec<Annotation>,
}
impl Annotations {
/// Create a new empty annotations collection
#[must_use]
pub fn new() -> Self {
Self {
annotations: Vec::new(),
}
}
/// Number of annotations
#[must_use]
pub fn len(&self) -> usize {
self.annotations.len()
}
/// Returns true if there are no annotations
#[must_use]
pub fn is_empty(&self) -> bool {
self.annotations.is_empty()
}
/// Add an annotation
pub fn add(&mut self, annotation: Annotation) {
self.annotations.push(annotation);
self.annotations.sort_by(|a, b| {
a.onset
.partial_cmp(&b.onset)
.unwrap_or(std::cmp::Ordering::Equal)
});
}
/// Get annotations containing a time point
#[must_use]
pub fn at_time(&self, time: f64) -> Vec<&Annotation> {
self.annotations
.iter()
.filter(|a| a.contains(time))
.collect()
}
/// Get annotations with a specific description
#[must_use]
pub fn find_by_description(&self, desc: &str) -> Vec<&Annotation> {
self.annotations
.iter()
.filter(|a| a.description == desc)
.collect()
}
/// Get unique descriptions
#[must_use]
pub fn unique_descriptions(&self) -> Vec<&str> {
let mut descs: Vec<&str> = self
.annotations
.iter()
.map(|a| a.description.as_str())
.collect();
descs.sort_unstable();
descs.dedup();
descs
}
/// Iterate over annotations
pub fn iter(&self) -> impl Iterator<Item = &Annotation> {
self.annotations.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_creation() {
let event = Event::new(1000, 1);
assert_eq!(event.sample, 1000);
assert_eq!(event.value, 1);
assert_eq!(event.duration, 0);
// Test time conversion at 1000 Hz
assert!((event.time(1000.0) - 1.0).abs() < 1e-10);
}
#[test]
fn test_events_sorting() {
let mut events = Events::new();
events.add(Event::new(500, 2));
events.add(Event::new(100, 1));
events.add(Event::new(300, 3));
let samples: Vec<usize> = events.iter().map(|e| e.sample).collect();
assert_eq!(samples, vec![100, 300, 500]);
}
#[test]
fn test_event_id_mapping() {
let mut event_id = EventId::new();
event_id.add("stimulus", 1);
event_id.add("response", 2);
assert_eq!(event_id.get_id("stimulus"), Some(1));
assert_eq!(event_id.get_name(2), Some("response"));
assert_eq!(event_id.get_id("unknown"), None);
}
#[test]
fn test_annotation() {
let ann = Annotation::new(1.5, 0.5, "artifact");
assert!(ann.contains(1.5));
assert!(ann.contains(1.7));
assert!(ann.contains(2.0));
assert!(!ann.contains(2.1));
}
}