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
@@ -0,0 +1,314 @@
//! Recording metadata and information.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
/// Information about a recording session
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecordingInfo {
/// Subject ID
pub subject_id: String,
/// Recording date and time
pub recording_date: Option<DateTime<Utc>>,
/// Recording device/system name
pub device: String,
/// Device serial number
pub device_serial: Option<String>,
/// Experimenter name
pub experimenter: Option<String>,
/// Recording location/institution
pub institution: Option<String>,
/// Description/comments
pub description: String,
/// Line frequency (50 or 60 Hz)
pub line_freq: Option<f64>,
/// High-pass filter setting during acquisition
pub highpass: Option<f64>,
/// Low-pass filter setting during acquisition
pub lowpass: Option<f64>,
}
impl RecordingInfo {
/// Create a new recording info with minimal required fields
#[must_use]
pub fn new(subject_id: impl Into<String>) -> Self {
Self {
subject_id: subject_id.into(),
recording_date: None,
device: "Unknown".to_string(),
device_serial: None,
experimenter: None,
institution: None,
description: String::new(),
line_freq: None,
highpass: None,
lowpass: None,
}
}
/// Set recording date
#[must_use]
pub fn with_date(mut self, date: DateTime<Utc>) -> Self {
self.recording_date = Some(date);
self
}
/// Set device information
#[must_use]
pub fn with_device(mut self, device: impl Into<String>) -> Self {
self.device = device.into();
self
}
/// Set line frequency
#[must_use]
pub fn with_line_freq(mut self, freq: f64) -> Self {
self.line_freq = Some(freq);
self
}
}
impl Default for RecordingInfo {
fn default() -> Self {
Self::new("unknown")
}
}
/// Handle to a loaded recording
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Recording {
/// Unique identifier
pub id: uuid::Uuid,
/// File path
pub path: std::path::PathBuf,
/// Recording metadata
pub info: RecordingInfo,
/// Format type
pub format: RecordingFormat,
/// Whether data is currently loaded in memory
pub loaded: bool,
}
impl Recording {
/// Create a new recording handle
#[must_use]
pub fn new(
path: impl Into<std::path::PathBuf>,
info: RecordingInfo,
format: RecordingFormat,
) -> Self {
Self {
id: uuid::Uuid::new_v4(),
path: path.into(),
info,
format,
loaded: false,
}
}
}
/// Supported recording file formats
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum RecordingFormat {
/// European Data Format
Edf,
/// BioSemi Data Format (24-bit EDF variant)
Bdf,
/// BrainVision format (.vhdr/.vmrk/.eeg)
BrainVision,
/// EEGLAB format (.set)
EegLab,
/// Elekta/Neuromag FIF format
Fif,
/// CTF MEG format (.ds folder)
Ctf,
/// 4D Neuroimaging format
FourD,
/// Yokogawa/Ricoh MEG format
Yokogawa,
/// KIT MEG format
Kit,
/// Curry format
Curry,
/// Neuroscan format (.cnt, .eeg)
Neuroscan,
/// Nihon Kohden format
NihonKohden,
/// BIDS dataset
Bids,
/// Neurodata Without Borders
Nwb,
/// Generic binary format
Binary,
/// Unknown format
Unknown,
}
impl RecordingFormat {
/// Detect format from file extension
#[must_use]
pub fn from_extension(ext: &str) -> Self {
match ext.to_lowercase().as_str() {
"edf" => Self::Edf,
"bdf" => Self::Bdf,
"vhdr" | "vmrk" | "eeg" => Self::BrainVision,
"set" => Self::EegLab,
"fif" => Self::Fif,
"ds" => Self::Ctf,
"cnt" => Self::Neuroscan,
"nwb" => Self::Nwb,
_ => Self::Unknown,
}
}
/// Get typical file extensions for this format
#[must_use]
pub fn extensions(&self) -> &[&str] {
match self {
Self::Edf => &["edf"],
Self::Bdf => &["bdf"],
Self::BrainVision => &["vhdr", "vmrk", "eeg"],
Self::EegLab => &["set"],
Self::Fif => &["fif", "fif.gz"],
Self::Ctf => &["ds"],
Self::Neuroscan => &["cnt", "eeg", "avg"],
Self::NihonKohden => &["eeg"],
Self::Nwb => &["nwb"],
Self::Yokogawa | Self::Kit => &["sqd", "con", "raw", "ave"],
_ => &[],
}
}
/// Whether this format supports continuous raw data
#[must_use]
pub fn supports_raw(&self) -> bool {
matches!(
self,
Self::Edf
| Self::Bdf
| Self::BrainVision
| Self::Fif
| Self::Ctf
| Self::FourD
| Self::Neuroscan
)
}
/// Whether this format supports epochs
#[must_use]
pub fn supports_epochs(&self) -> bool {
matches!(self, Self::EegLab | Self::Fif | Self::Nwb)
}
/// Whether this is an MEG format
#[must_use]
pub fn is_meg(&self) -> bool {
matches!(
self,
Self::Fif | Self::Ctf | Self::FourD | Self::Yokogawa | Self::Kit
)
}
/// Whether this is an EEG format
#[must_use]
pub fn is_eeg(&self) -> bool {
matches!(
self,
Self::Edf
| Self::Bdf
| Self::BrainVision
| Self::EegLab
| Self::Neuroscan
| Self::NihonKohden
| Self::Curry
)
}
}
/// Processing history for a recording
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessingHistory {
/// List of processing steps applied
steps: Vec<ProcessingStep>,
}
impl ProcessingHistory {
/// Create a new empty history
#[must_use]
pub fn new() -> Self {
Self { steps: Vec::new() }
}
/// Add a processing step
pub fn add(&mut self, step: ProcessingStep) {
self.steps.push(step);
}
/// Get all steps
#[must_use]
pub fn steps(&self) -> &[ProcessingStep] {
&self.steps
}
}
impl Default for ProcessingHistory {
fn default() -> Self {
Self::new()
}
}
/// A single processing step
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessingStep {
/// Name of the processing function
pub function: String,
/// Parameters used
pub parameters: serde_json::Value,
/// Timestamp when applied
pub timestamp: DateTime<Utc>,
/// Description
pub description: Option<String>,
}
impl ProcessingStep {
/// Create a new processing step
#[must_use]
pub fn new(function: impl Into<String>, parameters: serde_json::Value) -> Self {
Self {
function: function.into(),
parameters,
timestamp: Utc::now(),
description: None,
}
}
/// Add description
#[must_use]
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_detection() {
assert_eq!(RecordingFormat::from_extension("edf"), RecordingFormat::Edf);
assert_eq!(RecordingFormat::from_extension("BDF"), RecordingFormat::Bdf);
assert_eq!(
RecordingFormat::from_extension("vhdr"),
RecordingFormat::BrainVision
);
}
#[test]
fn test_format_capabilities() {
assert!(RecordingFormat::Edf.is_eeg());
assert!(!RecordingFormat::Edf.is_meg());
assert!(RecordingFormat::Fif.is_meg());
assert!(RecordingFormat::Edf.supports_raw());
}
}