1#![deny(unsafe_code)]
2pub mod layout;
17pub mod metadata;
18
19use bids_core::error::Result;
20use bids_io::tsv::read_tsv;
21use serde::{Deserialize, Serialize};
22
23pub use layout::IeegLayout;
24pub use metadata::IeegMetadata;
25
26pub use bids_eeg::{Annotation, EegData, ReadOptions, read_brainvision, read_edf, read_eeg_data};
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct IeegElectrode {
32 pub name: String,
33 pub x: Option<f64>,
34 pub y: Option<f64>,
35 pub z: Option<f64>,
36 pub size: Option<f64>,
37 pub hemisphere: Option<String>,
38 pub group: Option<String>,
39 pub electrode_type: Option<String>,
40 pub manufacturer: Option<String>,
41}
42
43impl IeegElectrode {
44 pub fn has_position(&self) -> bool {
45 self.x.is_some() && self.y.is_some() && self.z.is_some()
46 }
47 pub fn position(&self) -> Option<(f64, f64, f64)> {
48 match (self.x, self.y, self.z) {
49 (Some(x), Some(y), Some(z)) => Some((x, y, z)),
50 _ => None,
51 }
52 }
53}
54
55pub fn read_ieeg_electrodes(path: &std::path::Path) -> Result<Vec<IeegElectrode>> {
56 let rows = read_tsv(path)?;
57 Ok(rows
58 .iter()
59 .map(|r| {
60 let get = |k: &str| {
61 r.get(k)
62 .filter(|s| !s.is_empty() && s.as_str() != "n/a")
63 .cloned()
64 };
65 let getf = |k: &str| r.get(k).and_then(|v| v.parse().ok());
66 IeegElectrode {
67 name: r.get("name").cloned().unwrap_or_default(),
68 x: getf("x"),
69 y: getf("y"),
70 z: getf("z"),
71 size: getf("size"),
72 hemisphere: get("hemisphere"),
73 group: get("group"),
74 electrode_type: get("type"),
75 manufacturer: get("manufacturer"),
76 }
77 })
78 .collect())
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83#[serde(rename_all = "PascalCase")]
84pub struct IeegCoordSystem {
85 #[serde(rename = "iEEGCoordinateSystem")]
86 pub coordinate_system: String,
87 #[serde(rename = "iEEGCoordinateUnits")]
88 pub coordinate_units: String,
89 #[serde(rename = "iEEGCoordinateSystemDescription", default)]
90 pub coordinate_system_description: Option<String>,
91 #[serde(rename = "iEEGCoordinateProcessingDescription", default)]
92 pub processing_description: Option<String>,
93 #[serde(rename = "IntendedFor", default)]
94 pub intended_for: Option<serde_json::Value>,
95}
96
97impl IeegCoordSystem {
98 pub fn from_file(path: &std::path::Path) -> Result<Self> {
99 let contents = std::fs::read_to_string(path)?;
100 Ok(serde_json::from_str(&contents)?)
101 }
102}
103
104#[derive(Debug)]
105pub struct IeegSummary {
106 pub n_subjects: usize,
107 pub n_recordings: usize,
108 pub subjects: Vec<String>,
109 pub tasks: Vec<String>,
110 pub sampling_frequency: Option<f64>,
111 pub channel_count: Option<usize>,
112}
113impl std::fmt::Display for IeegSummary {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 writeln!(f, "iEEG Dataset Summary:")?;
116 writeln!(f, " Subjects: {}", self.n_subjects)?;
117 writeln!(f, " Recordings: {}", self.n_recordings)?;
118 writeln!(f, " Tasks: {:?}", self.tasks)?;
119 if let Some(sf) = self.sampling_frequency {
120 writeln!(f, " Sampling Frequency: {sf} Hz")?;
121 }
122 if let Some(cc) = self.channel_count {
123 writeln!(f, " Channels: {cc}")?;
124 }
125 Ok(())
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_ieeg_metadata() {
135 let json = r#"{"SamplingFrequency":512,"SEEGChannelCount":124,"EEGChannelCount":2,"ECGChannelCount":2}"#;
136 let md: IeegMetadata = serde_json::from_str(json).unwrap();
137 assert_eq!(md.sampling_frequency, 512.0);
138 assert_eq!(md.seeg_channel_count, Some(124));
139 assert_eq!(md.total_channel_count(), 128);
140 }
141
142 #[test]
143 fn test_ieeg_coordsystem() {
144 let json = r#"{"iEEGCoordinateSystem":"ACPC","iEEGCoordinateUnits":"mm"}"#;
145 let cs: IeegCoordSystem = serde_json::from_str(json).unwrap();
146 assert_eq!(cs.coordinate_system, "ACPC");
147 assert_eq!(cs.coordinate_units, "mm");
148 }
149}