#![deny(unsafe_code)]
pub mod layout;
pub mod metadata;
use bids_core::error::Result;
use bids_io::tsv::read_tsv;
use serde::{Deserialize, Serialize};
pub use layout::IeegLayout;
pub use metadata::IeegMetadata;
pub use bids_eeg::{Annotation, EegData, ReadOptions, read_brainvision, read_edf, read_eeg_data};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IeegElectrode {
pub name: String,
pub x: Option<f64>,
pub y: Option<f64>,
pub z: Option<f64>,
pub size: Option<f64>,
pub hemisphere: Option<String>,
pub group: Option<String>,
pub electrode_type: Option<String>,
pub manufacturer: Option<String>,
}
impl IeegElectrode {
pub fn has_position(&self) -> bool {
self.x.is_some() && self.y.is_some() && self.z.is_some()
}
pub fn position(&self) -> Option<(f64, f64, f64)> {
match (self.x, self.y, self.z) {
(Some(x), Some(y), Some(z)) => Some((x, y, z)),
_ => None,
}
}
}
pub fn read_ieeg_electrodes(path: &std::path::Path) -> Result<Vec<IeegElectrode>> {
let rows = read_tsv(path)?;
Ok(rows
.iter()
.map(|r| {
let get = |k: &str| {
r.get(k)
.filter(|s| !s.is_empty() && s.as_str() != "n/a")
.cloned()
};
let getf = |k: &str| r.get(k).and_then(|v| v.parse().ok());
IeegElectrode {
name: r.get("name").cloned().unwrap_or_default(),
x: getf("x"),
y: getf("y"),
z: getf("z"),
size: getf("size"),
hemisphere: get("hemisphere"),
group: get("group"),
electrode_type: get("type"),
manufacturer: get("manufacturer"),
}
})
.collect())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct IeegCoordSystem {
#[serde(rename = "iEEGCoordinateSystem")]
pub coordinate_system: String,
#[serde(rename = "iEEGCoordinateUnits")]
pub coordinate_units: String,
#[serde(rename = "iEEGCoordinateSystemDescription", default)]
pub coordinate_system_description: Option<String>,
#[serde(rename = "iEEGCoordinateProcessingDescription", default)]
pub processing_description: Option<String>,
#[serde(rename = "IntendedFor", default)]
pub intended_for: Option<serde_json::Value>,
}
impl IeegCoordSystem {
pub fn from_file(path: &std::path::Path) -> Result<Self> {
let contents = std::fs::read_to_string(path)?;
Ok(serde_json::from_str(&contents)?)
}
}
#[derive(Debug)]
pub struct IeegSummary {
pub n_subjects: usize,
pub n_recordings: usize,
pub subjects: Vec<String>,
pub tasks: Vec<String>,
pub sampling_frequency: Option<f64>,
pub channel_count: Option<usize>,
}
impl std::fmt::Display for IeegSummary {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "iEEG Dataset Summary:")?;
writeln!(f, " Subjects: {}", self.n_subjects)?;
writeln!(f, " Recordings: {}", self.n_recordings)?;
writeln!(f, " Tasks: {:?}", self.tasks)?;
if let Some(sf) = self.sampling_frequency {
writeln!(f, " Sampling Frequency: {sf} Hz")?;
}
if let Some(cc) = self.channel_count {
writeln!(f, " Channels: {cc}")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ieeg_metadata() {
let json = r#"{"SamplingFrequency":512,"SEEGChannelCount":124,"EEGChannelCount":2,"ECGChannelCount":2}"#;
let md: IeegMetadata = serde_json::from_str(json).unwrap();
assert_eq!(md.sampling_frequency, 512.0);
assert_eq!(md.seeg_channel_count, Some(124));
assert_eq!(md.total_channel_count(), 128);
}
#[test]
fn test_ieeg_coordsystem() {
let json = r#"{"iEEGCoordinateSystem":"ACPC","iEEGCoordinateUnits":"mm"}"#;
let cs: IeegCoordSystem = serde_json::from_str(json).unwrap();
assert_eq!(cs.coordinate_system, "ACPC");
assert_eq!(cs.coordinate_units, "mm");
}
}