rusty_brain/read/
brainvision_core.rs1use core::{f32, str};
4use std::{fs, io::Read, path::Path, str::Split, u32};
5
6use ndarray::{Array2, ArrayView1};
7
8use super::BIDSPath;
9
10mod locked {
11 pub(crate) trait Locked {}
12
13 impl Locked for f32 {}
14 impl Locked for i16 {}
15}
16
17pub struct Header {
18 data_file: String,
19 marker_file: String,
20 num_channels: u32,
21 sampling_interval: f64,
22 averaged: bool,
23 averaged_segms: u32,
24 num_data_points: u32,
25 segmentation_type: String,
26 binary_format: BinaryFormatType,
27 channels: Vec<ChannelInfo>,
28 channel_coords: Option<Vec<Coordinates>>,
29 comment: Option<String>,
30}
31
32impl Header {
33 pub fn load<P: AsRef<Path>>(
34 path: &BIDSPath<P>,
35 task: &str,
36 acquisition: Option<&str>,
37 run: Option<&str>,
38 ) -> Header {
39 let mut buf = String::new();
40 let _ = fs::File::open(path.path.join(format!(
41 "sub-{}{}_task-{}{}{}_{}.vhdr",
42 path.subject,
43 if let Some(session) = path.session {
44 format!("_ses-{}", session)
45 } else {
46 String::default()
47 },
48 task,
49 if let Some(acquisition) = acquisition {
50 format!("_acq-{}", acquisition)
51 } else {
52 String::default()
53 },
54 if let Some(run) = run {
55 format!("_run-{}", run)
56 } else {
57 String::default()
58 },
59 path.datatype
60 )))
61 .unwrap()
62 .read_to_string(&mut buf);
63 let comment = buf
65 .match_indices("[Comment]")
66 .next()
67 .map(|(idx, _)| buf[idx + "[Comment]".len()..buf.len()].to_string());
68 buf = buf.lines().skip(1).collect::<Vec<&str>>().join("\n");
70
71 let file = ini::Ini::load_from_str(&buf).unwrap();
72
73 let common_infos = file.section(Some("Common Infos")).unwrap();
74 let binary_infos = file.section(Some("Binary Infos")).unwrap();
75 let channel_infos = file.section(Some("Channel Infos")).unwrap();
76 let coordinates = file.section(Some("Coordinates"));
77
78 let data_file = common_infos.get("DataFile").unwrap().into();
79 let marker_file = common_infos.get("MarkerFile").unwrap().into();
80 let num_channels = common_infos
81 .get("NumberOfChannels")
82 .map(|s| s.parse::<u32>().unwrap())
83 .unwrap();
84 let sampling_interval = common_infos
85 .get("SamplingInterval")
86 .map(|s| s.parse::<f64>().unwrap())
87 .unwrap();
88 let averaged = common_infos.get("Averaged").map_or_else(
89 || false,
90 |s| match s {
91 "YES" => true,
92 "NO" | _ => false,
93 },
94 );
95 let averaged_segms = match averaged {
96 false => 0u32,
97 true => common_infos
98 .get("AveragedSegments")
99 .map(|s| s.parse::<u32>().unwrap())
100 .unwrap(),
101 };
102 let segmentation_type = match averaged {
103 false => "NOTSEGMENTED",
104 true => common_infos.get("SegmentationType").unwrap(),
105 }
106 .into();
107 let num_data_points = if averaged && segmentation_type == "MARKERBASED" {
108 common_infos
109 .get("SegmentDataPoints")
110 .map(|s| s.parse::<u32>().unwrap())
111 .unwrap()
112 } else {
113 0
114 };
115
116 let binary_format = match binary_infos.get("BinaryFormat").unwrap() {
117 "IEEE_FLOAT_32" => BinaryFormatType::IeeeFloat32,
118 "INT_16" => BinaryFormatType::Int16,
119 _ => panic!("Invalid binary format !"),
120 };
121
122 let channels = channel_infos
123 .iter()
124 .map(|(_, v)| ChannelInfo::from(v.split(',')))
125 .collect::<Vec<ChannelInfo>>();
126
127 let channel_coords = coordinates.map(|coords| {
128 coords
129 .iter()
130 .map(|(_, v)| Coordinates::from(v.split(',')))
131 .collect::<Vec<Coordinates>>()
132 });
133
134 Header {
135 data_file,
136 marker_file,
137 num_channels,
138 sampling_interval,
139 averaged,
140 averaged_segms,
141 num_data_points,
142 segmentation_type,
143 binary_format,
144 channels,
145 channel_coords,
146 comment,
147 }
148 }
149
150 pub fn data_file(&self) -> &str {
151 &self.data_file
152 }
153
154 pub fn num_channels(&self) -> u32 {
155 self.num_channels
156 }
157}
158
159pub(crate) trait BinaryFormat: locked::Locked + Sized {
160 const BYTES: usize;
161
162 fn from_bytes(bytes: &[u8]) -> Self;
163}
164
165impl BinaryFormat for f32 {
166 const BYTES: usize = 4;
167
168 fn from_bytes(bytes: &[u8]) -> Self {
169 let mut rep = [0u8; 4];
170 rep.copy_from_slice(bytes);
171 f32::from_le_bytes(rep)
172 }
173}
174
175impl BinaryFormat for i16 {
176 const BYTES: usize = 2;
177
178 fn from_bytes(bytes: &[u8]) -> Self {
179 let mut rep = [0u8; 2];
180 rep.copy_from_slice(bytes);
181 i16::from_le_bytes(rep)
182 }
183}
184
185#[derive(Clone, Copy)]
186pub enum BinaryFormatType {
187 IeeeFloat32,
188 Int16,
189}
190
191struct ChannelInfo {
192 name: String,
193 ref_name: String,
194 resolution: f64,
195 unit: String,
196}
197
198impl From<Split<'_, char>> for ChannelInfo {
199 fn from(mut value: Split<char>) -> Self {
200 let name = value.next().unwrap().into();
201 let ref_name = value
202 .next()
203 .map(|s| if s.is_empty() { "Cz" } else { s })
204 .unwrap()
205 .into();
206 let resolution = value
207 .next()
208 .map(|s| {
209 if s.is_empty() {
210 1.0f64
211 } else {
212 s.parse::<f64>().unwrap()
213 }
214 })
215 .unwrap();
216 let unit = value
217 .next()
218 .map(|s| if s.is_empty() { "μV" } else { s })
219 .unwrap()
220 .into();
221
222 Self {
223 name,
224 ref_name,
225 resolution,
226 unit,
227 }
228 }
229}
230
231struct Coordinates {
232 radius: f64,
233 theta: f64,
234 phi: f64,
235}
236
237impl From<Split<'_, char>> for Coordinates {
238 fn from(mut value: Split<'_, char>) -> Self {
239 Coordinates {
240 radius: value.next().map(|s| s.parse::<f64>().unwrap()).unwrap(),
241 theta: value.next().map(|s| s.parse::<f64>().unwrap()).unwrap(),
242 phi: value.next().map(|s| s.parse::<f64>().unwrap()).unwrap(),
243 }
244 }
245}
246
247#[allow(private_bounds)]
248pub struct Data<T: BinaryFormat> {
249 data: Array2<T>,
250}
251
252#[allow(private_bounds)]
253impl<T: BinaryFormat> Data<T> {
254 pub fn load<P: AsRef<Path>>(path: &BIDSPath<P>, header: &Header) -> Data<T> {
255 let rawdata = fs::read(path.path.join(header.data_file())).unwrap();
256 let num_channels = header.num_channels() as usize;
257 let chunks = rawdata.chunks_exact(T::BYTES);
258 let effective_data_points = chunks.len() / num_channels;
260 let data = Array2::from_shape_vec(
261 (num_channels, effective_data_points),
262 chunks.map(|c| T::from_bytes(c)).collect::<Vec<T>>(),
263 )
264 .unwrap();
265
266 Data { data }
267 }
268
269 pub fn channel(&self, index: usize) -> ArrayView1<T> {
270 self.data.column(index)
271 }
272}