Files
rustytorch/crates/specialized/rtx-neuro-lsl/src/stream.rs
T
2026-03-04 00:08:42 +00:00

151 lines
4.1 KiB
Rust

//! LSL stream information and types
use serde::{Deserialize, Serialize};
/// Channel format for LSL streams
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ChannelFormat {
/// 32-bit floating point
Float32,
/// 64-bit floating point
Float64,
/// 32-bit signed integer
Int32,
/// 16-bit signed integer
Int16,
/// 8-bit signed integer
Int8,
/// Variable-length strings
String,
}
impl Default for ChannelFormat {
fn default() -> Self {
Self::Float32
}
}
/// Information about an LSL stream
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LslStreamInfo {
/// Stream name (e.g., "MyBiosemi")
pub name: String,
/// Stream type (e.g., "EEG", "MEG", "Markers")
pub stream_type: String,
/// Number of channels
pub channel_count: usize,
/// Nominal sampling rate in Hz (0 for irregular rate)
pub nominal_srate: f64,
/// Data format
pub channel_format: ChannelFormat,
/// Unique source identifier (e.g., machine ID + serial number)
pub source_id: String,
/// Hostname of the source machine
pub hostname: String,
/// Version of the stream
pub version: i32,
/// Creation timestamp
pub created_at: f64,
/// Session ID
pub session_id: String,
/// Unique identifier for this stream instance
pub uid: String,
}
impl LslStreamInfo {
/// Create a new stream info
pub fn new(name: &str, stream_type: &str, channel_count: usize, srate: f64) -> Self {
Self {
name: name.to_string(),
stream_type: stream_type.to_string(),
channel_count,
nominal_srate: srate,
channel_format: ChannelFormat::Float32,
source_id: String::new(),
hostname: String::new(),
version: 1,
created_at: 0.0,
session_id: String::new(),
uid: uuid::Uuid::new_v4().to_string(),
}
}
/// Check if this is an EEG stream
pub fn is_eeg(&self) -> bool {
self.stream_type.to_uppercase() == "EEG"
}
/// Check if this is an MEG stream
pub fn is_meg(&self) -> bool {
self.stream_type.to_uppercase() == "MEG"
}
/// Check if this is a marker/event stream
pub fn is_markers(&self) -> bool {
let t = self.stream_type.to_uppercase();
t == "MARKERS" || t == "EVENTS" || t == "STIM"
}
/// Get the expected sample size in bytes
pub fn sample_size(&self) -> usize {
let bytes_per_channel = match self.channel_format {
ChannelFormat::Float32 => 4,
ChannelFormat::Float64 => 8,
ChannelFormat::Int32 => 4,
ChannelFormat::Int16 => 2,
ChannelFormat::Int8 => 1,
ChannelFormat::String => 0, // Variable
};
self.channel_count * bytes_per_channel
}
}
/// A single sample from an LSL stream
#[derive(Debug, Clone)]
pub struct LslSample {
/// Sample data (one value per channel)
pub data: Vec<f64>,
/// LSL timestamp of the sample
pub timestamp: f64,
}
impl LslSample {
/// Create a new sample
pub fn new(data: Vec<f64>, timestamp: f64) -> Self {
Self { data, timestamp }
}
/// Get the number of channels
pub fn n_channels(&self) -> usize {
self.data.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stream_info_creation() {
let info = LslStreamInfo::new("TestEEG", "EEG", 64, 500.0);
assert_eq!(info.name, "TestEEG");
assert_eq!(info.channel_count, 64);
assert_eq!(info.nominal_srate, 500.0);
assert!(info.is_eeg());
assert!(!info.is_meg());
}
#[test]
fn test_sample_size() {
let mut info = LslStreamInfo::new("Test", "EEG", 32, 256.0);
info.channel_format = ChannelFormat::Float32;
assert_eq!(info.sample_size(), 32 * 4);
info.channel_format = ChannelFormat::Float64;
assert_eq!(info.sample_size(), 32 * 8);
info.channel_format = ChannelFormat::Int16;
assert_eq!(info.sample_size(), 32 * 2);
}
}