1use regex::Regex;
2
3const DEFREQ: f32 = 250_f32;
4
5#[allow(non_camel_case_types)]
6#[derive(Copy, Clone, PartialEq, Debug)]
7pub enum StorageFormat {
8 _8bit_first_difference = 8,
9 _16bit_twos_complement = 16,
10 _24bit_twos_complement_lsb = 24,
11 _32bit_twos_complement_lsb = 32,
12 _16bit_twos_complement_msb = 61,
13 _8bit_offset_binary = 80,
14 _16bit_offset_binary = 160,
15 _12bit_twos_complement = 212,
16 _10bit_twos_complement_sets_of_11 = 310,
17 _10bit_twos_complement_sets_of_4 = 311,
18}
19
20#[derive(PartialEq, Debug)]
21pub struct RecordLine {
22 pub record_name: String,
23 pub number_of_segments: Option<u32>,
24 pub number_of_signals: u32,
25 pub sampling_frequency: Option<f32>,
26 pub counter_frequency: Option<f32>,
27 pub base_counter_value: Option<f32>,
28 pub samples_per_signal: Option<u32>,
29 pub base_time: Option<String>,
30 pub base_date: Option<String>,
31}
32
33#[derive(PartialEq, Debug)]
34pub struct SignalSpecLine {
35 pub filename: String,
36 pub format: StorageFormat,
37 pub samples_per_frame: Option<u32>,
38 pub skew: Option<u32>,
39 pub byte_offset: Option<u32>,
40 pub adc_gain: Option<f32>,
41 pub baseline: Option<u32>,
42 pub units: Option<String>,
43 pub adc_resolution: Option<u32>,
44 pub adc_zero: Option<u32>,
45 pub initial_value: Option<u32>,
46 pub checksum: Option<i16>,
47 pub block_size: Option<u32>,
48 pub description: Option<String>,
49}
50
51#[derive(PartialEq, Debug)]
52pub struct Header {
53 pub record: RecordLine,
54 pub signal_specs: Vec<SignalSpecLine>,
55}
56
57pub fn parse_record_line(record_line: &str) -> RecordLine {
58 let tokens: Vec<&str> = record_line.split_whitespace().collect();
59 let record_name;
60 let mut number_of_segments = None;
61
62 let record_name_tokens: Vec<&str> = tokens[0].split('/').collect();
63 if record_name_tokens.len() > 1 {
64 record_name = String::from(record_name_tokens[0]);
65 number_of_segments = record_name_tokens[1].parse::<u32>().ok();
66 } else {
67 record_name = String::from(record_name_tokens[0]);
68 }
69
70 let number_of_signals = tokens[1]
71 .parse::<u32>()
72 .expect("Invalid header: cannot parse number of signals.");
73
74 let mut sampling_frequency = Some(DEFREQ);
75 let mut counter_frequency = None;
76 let mut base_counter_value = None;
77 if tokens.len() > 2 {
78 let frequency_tokens: Vec<&str> = tokens[2].split('/').collect();
79 if frequency_tokens.len() > 1 {
80 let counter_regex = Regex::new(r"(\d+)\((\d+)\)").unwrap();
81 if counter_regex.is_match(frequency_tokens[1]) {
82 let captures = counter_regex.captures(frequency_tokens[1]).unwrap();
83 counter_frequency = Some(
84 captures[1]
85 .parse::<f32>()
86 .expect("Invalid header: counter frequency specified, but not parseable."),
87 );
88 base_counter_value = Some(
89 captures[2]
90 .parse::<f32>()
91 .expect("Invalid header: base counter value specified, but not parseable"),
92 );
93 } else {
94 counter_frequency = Some(
95 frequency_tokens[0]
96 .parse::<f32>()
97 .expect("Invalid header: counter frequency specified, but not parseable."),
98 );
99 }
100 }
101 sampling_frequency = Some(
102 frequency_tokens[0]
103 .parse::<f32>()
104 .expect("Invalid header: Sampling frequency field present, but not parseable"),
105 );
106 }
107
108 let mut samples_per_signal = None;
109 if tokens.len() > 3 {
110 samples_per_signal = Some(
111 tokens[3]
112 .parse::<u32>()
113 .expect("Invalid header: samples per signal specified, but not parseable"),
114 );
115 }
116
117 RecordLine {
120 record_name,
121 number_of_segments,
122 number_of_signals,
123 sampling_frequency,
124 counter_frequency: if counter_frequency == None {
125 sampling_frequency
126 } else {
127 counter_frequency
128 },
129 base_counter_value: if base_counter_value == None {
130 Some(0_f32)
131 } else {
132 base_counter_value
133 },
134 samples_per_signal,
135 base_time: None,
136 base_date: None,
137 }
138}
139
140pub fn parse_signal_line(signal_line: &str) -> SignalSpecLine {
141 let tokens: Vec<&str> = signal_line.split_whitespace().collect();
142 if tokens.len() < 2 {
143 panic!("Invalid header: signal specification line missing required fields.");
144 }
145
146 let filename = String::from(tokens[0]);
147 let format;
148 let mut samples_per_frame = None;
149 let mut skew = None;
150 let mut byte_offset = None;
151 let format_regex = Regex::new(r"(\d+)(?:x(\d+)(?::(\d+)(?:\+(\d+)))?)?").unwrap();
152 if format_regex.is_match(tokens[1]) {
153 let format_captures = format_regex.captures(tokens[1]).unwrap();
154 format = match &format_captures[1] {
155 "8" => StorageFormat::_8bit_first_difference,
156 "16" => StorageFormat::_16bit_twos_complement,
157 "24" => StorageFormat::_24bit_twos_complement_lsb,
158 "32" => StorageFormat::_32bit_twos_complement_lsb,
159 "61" => StorageFormat::_16bit_twos_complement_msb,
160 "80" => StorageFormat::_8bit_offset_binary,
161 "160" => StorageFormat::_16bit_offset_binary,
162 "212" => StorageFormat::_12bit_twos_complement,
163 "310" => StorageFormat::_10bit_twos_complement_sets_of_11,
164 "311" => StorageFormat::_10bit_twos_complement_sets_of_4,
165 _ => panic!("Unknown storage format!"),
166 };
167 if format_captures.get(2) != None {
168 samples_per_frame =
169 Some(format_captures[2].parse::<u32>().expect(
170 "Invalid header: samples per frame specified, but could not be parsed",
171 ));
172 }
173 if format_captures.get(3) != None {
174 skew = Some(
175 format_captures[3]
176 .parse::<u32>()
177 .expect("Invalid header: skew specified, but could not be parsed"),
178 );
179 }
180 if format_captures.get(4) != None {
181 byte_offset = Some(
182 format_captures[4]
183 .parse::<u32>()
184 .expect("Invalid header: byte offset specified, but could not be parsed"),
185 );
186 }
187 } else {
188 panic!("Invalid header: signal format not properly specified.");
189 }
190
191 let mut adc_gain = None;
192 let mut baseline = None;
193 let mut units = Some(String::from("mV"));
194 let mut adc_resolution = Some(12);
197 let mut adc_zero = Some(0);
198 let mut initial_value = None;
199 let mut checksum = None;
200 let mut block_size = None;
201 let mut description = None;
202 if tokens.len() > 2 {
203 let adc_regex = Regex::new(r"(\d+)(?:\((?P<baseline>\d+)\))?(?:/(?P<units>\S+))?").unwrap();
204 let adc_tokens = adc_regex.captures(tokens[2]).unwrap();
205 adc_gain = Some(
206 adc_tokens[1]
207 .parse::<f32>()
208 .expect("Invalid header: adc gain specified, but not parseable"),
209 );
210 if adc_tokens.name("baseline") != None {
211 baseline = Some(
212 adc_tokens
213 .name("baseline")
214 .unwrap()
215 .as_str()
216 .parse::<u32>()
217 .expect("Invalid header: baseline specified, but could not be parsed"),
218 );
219 }
220 if adc_tokens.name("units") != None {
221 units = Some(String::from(adc_tokens.name("units").unwrap().as_str()));
222 }
223
224 if tokens.len() > 3 {
225 adc_resolution = Some(
226 tokens[3]
227 .parse::<u32>()
228 .expect("Invalid header: ADC resolution specified but not parseable."),
229 );
230 }
231
232 if tokens.len() > 4 {
233 adc_zero = Some(
234 tokens[4]
235 .parse::<u32>()
236 .expect("Invalid header: ADC zero specified but not parseable."),
237 );
238 }
239
240 if tokens.len() > 5 {
241 initial_value = Some(
242 tokens[5]
243 .parse::<u32>()
244 .expect("Invalid header: initial value specified but not parseable."),
245 );
246 }
247
248 if tokens.len() > 6 {
249 checksum = Some(
250 tokens[6]
251 .parse::<i16>()
252 .expect("Invalid header: checksum specified but not parseable."),
253 );
254 }
255
256 if tokens.len() > 7 {
257 block_size = Some(
258 tokens[7]
259 .parse::<u32>()
260 .expect("Invalid header: block size specified but not parseable."),
261 );
262 }
263
264 if tokens.len() > 8 {
265 description = Some(String::from(tokens[8]));
266 }
267 }
268
269 SignalSpecLine {
270 filename,
271 format,
272 samples_per_frame,
273 skew,
274 byte_offset,
275 adc_gain,
276 baseline: if baseline == None { adc_zero } else { baseline },
277 units,
278 adc_resolution,
279 adc_zero,
280 initial_value: if initial_value == None {
281 adc_zero
282 } else {
283 initial_value
284 },
285 checksum,
286 block_size,
287 description,
288 }
289}
290
291pub fn read_header(header_string: &str) -> Header {
292 let mut header_lines = header_string.lines().filter(|&line| !line.starts_with("#"));
293 Header {
294 record: parse_record_line(header_lines.next().unwrap()),
295 signal_specs: header_lines.map(parse_signal_line).collect(),
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302 #[test]
303 fn test_basic_header() {
304 let basic_header = "100 2 360 650000
305 100.dat 212 200 11 1024 995 -22131 0 MLII
306 100.dat 212 200 11 1024 1011 20052 0 V5";
307 let parsed = read_header(basic_header);
308 assert_eq!(
309 parsed,
310 Header {
311 record: RecordLine {
312 record_name: String::from("100"),
313 number_of_segments: None,
314 number_of_signals: 2,
315 sampling_frequency: Some(360_f32),
316 counter_frequency: Some(360_f32),
317 base_counter_value: Some(0.0),
318 samples_per_signal: Some(650000),
319 base_time: None,
320 base_date: None,
321 },
322 signal_specs: vec![
323 SignalSpecLine {
324 filename: String::from("100.dat"),
325 format: StorageFormat::_12bit_twos_complement,
326 samples_per_frame: None,
327 skew: None,
328 byte_offset: None,
329 adc_gain: Some(200_f32),
330 baseline: Some(1024),
331 units: Some(String::from("mV")),
332 adc_resolution: Some(11),
333 adc_zero: Some(1024),
334 initial_value: Some(995),
335 checksum: Some(-22131),
336 block_size: Some(0),
337 description: Some(String::from("MLII")),
338 },
339 SignalSpecLine {
340 filename: String::from("100.dat"),
341 format: StorageFormat::_12bit_twos_complement,
342 samples_per_frame: None,
343 skew: None,
344 byte_offset: None,
345 adc_gain: Some(200_f32),
346 baseline: Some(1024),
347 units: Some(String::from("mV")),
348 adc_resolution: Some(11),
349 adc_zero: Some(1024),
350 initial_value: Some(1011),
351 checksum: Some(20052),
352 block_size: Some(0),
353 description: Some(String::from("V5")),
354 },
355 ],
356 }
357 )
358 }
359
360 #[test]
361 fn test_mit_record_line() {
362 let basic_record_line = "100 2 360 650000 0:0:0 0/0/0";
363 let parsed = parse_record_line(basic_record_line);
364 assert_eq!(
365 parsed,
366 RecordLine {
367 record_name: String::from("100"),
368 number_of_segments: None,
369 number_of_signals: 2,
370 sampling_frequency: Some(360_f32),
371 counter_frequency: Some(360_f32),
372 base_counter_value: Some(0.0),
373 samples_per_signal: Some(650000),
374 base_time: None,
375 base_date: None,
376 }
377 )
378 }
379
380 #[test]
381 fn test_custom_mit_record_line() {
382 let basic_record_line = "100/4 2 360/24(5) 650000 0:0:0 0/0/0";
383 let parsed = parse_record_line(basic_record_line);
384 assert_eq!(
385 parsed,
386 RecordLine {
387 record_name: String::from("100"),
388 number_of_segments: Some(4),
389 number_of_signals: 2,
390 sampling_frequency: Some(360.0),
391 counter_frequency: Some(24.0),
392 base_counter_value: Some(5.0),
393 samples_per_signal: Some(650000),
394 base_time: None,
395 base_date: None,
396 }
397 )
398 }
399
400 #[test]
401 fn test_aha_record_line() {
402 let basic_record_line = "7001 2 250 525000";
403 let parsed = parse_record_line(basic_record_line);
404 assert_eq!(
405 parsed,
406 RecordLine {
407 record_name: String::from("7001"),
408 number_of_segments: None,
409 number_of_signals: 2,
410 sampling_frequency: Some(250_f32),
411 counter_frequency: Some(250_f32),
412 base_counter_value: Some(0.0),
413 samples_per_signal: Some(525000),
414 base_time: None,
415 base_date: None,
416 }
417 )
418 }
419
420 #[test]
421 fn test_mit_signal_spec_line() {
422 let signal_line = "100.dat 212 200 11 1024 995 -22131 0 MLII";
423 assert_eq!(
424 parse_signal_line(signal_line),
425 SignalSpecLine {
426 filename: String::from("100.dat"),
427 format: StorageFormat::_12bit_twos_complement,
428 samples_per_frame: None,
429 skew: None,
430 byte_offset: None,
431 adc_gain: Some(200_f32),
432 baseline: Some(1024),
433 units: Some(String::from("mV")),
434 adc_resolution: Some(11),
435 adc_zero: Some(1024),
436 initial_value: Some(995),
437 checksum: Some(-22131),
438 block_size: Some(0),
439 description: Some(String::from("MLII")),
440 }
441 )
442 }
443
444 #[test]
445 fn test_custom_mit_signal_spec_line() {
446 let signal_line = "100.dat 212x3:2+53 200(2)/cm 11 1024 995 -22131 0 MLII";
447 assert_eq!(
448 parse_signal_line(signal_line),
449 SignalSpecLine {
450 filename: String::from("100.dat"),
451 format: StorageFormat::_12bit_twos_complement,
452 samples_per_frame: Some(3),
453 skew: Some(2),
454 byte_offset: Some(53),
455 adc_gain: Some(200.0),
456 baseline: Some(2),
457 units: Some(String::from("cm")),
458 adc_resolution: Some(11),
459 adc_zero: Some(1024),
460 initial_value: Some(995),
461 checksum: Some(-22131),
462 block_size: Some(0),
463 description: Some(String::from("MLII")),
464 }
465 )
466 }
467}