1use anyhow::Result;
2use std::collections::HashMap;
3
4use std::cell::RefCell;
5use std::fs::File;
6use std::io;
7use std::io::prelude::*;
8use std::io::Cursor;
9use std::io::SeekFrom;
10use std::mem;
11use std::rc::Rc;
12use std::slice;
13use thiserror::Error;
14
15#[derive(Error, Debug)]
16pub enum ParserError {
17 #[error("magic word not unmatched, might not be a c3d file")]
18 UnmatchMagic,
19 #[error("unable to parse paramter block")]
20 ParseParameterError,
21 #[error("io error")]
22 IoError(#[from] io::Error),
23 #[error("missing header/parameter")]
24 MissingField,
25}
26
27pub struct C3dAdapter<T: Read + Seek> {
28 pub header: Option<HeaderBlock>,
29 pub parameter: Option<ParameterBlock>,
30 handle: Rc<RefCell<T>>,
31}
32
33impl<T: Read + Seek> C3dAdapter<T> {
34 pub fn new(mut file: T) -> Result<Self, ParserError> {
35 file.seek(SeekFrom::Start(0))?;
36 let handle = Rc::new(RefCell::new(file));
37
38 Ok(C3dAdapter {
39 header: None,
40 parameter: None,
41 handle,
42 })
43 }
44
45 pub fn construct(mut self) -> Result<Self, ParserError> {
46 let header = HeaderBlock::from_reader(&mut *self.handle.borrow_mut());
47 let parameter = ParameterBlock::from_reader(&mut *self.handle.borrow_mut());
48
49 if header.magic_word != 0x50 {
51 return Err(ParserError::UnmatchMagic);
52 }
53
54 if parameter.header.magic_word != 0x50 + 4 {
56 dbg!(parameter.header.magic_word);
57 return Err(ParserError::UnmatchMagic);
58 }
59
60 self.header.replace(header);
61 self.parameter.replace(parameter);
62
63 Ok(self)
64 }
65}
66
67pub struct C3dReader<'a, R: Read + Seek> {
68 header: &'a HeaderBlock,
69 parameter: &'a ParameterBlock,
70 handle: std::cell::RefMut<'a, R>,
71 points_buffer: Vec<u8>,
72 analog_buffer: Vec<u8>,
73 frame_idx: u16,
74 analog_unsigned: bool,
75 analog_offset: Option<Vec<f32>>,
76 analog_scale: Option<Vec<f32>>,
77 analog_gen_scale: Option<f32>,
78}
79
80impl<'a, R: Read + Seek> C3dReader<'a, R> {
81 pub fn new(
82 header: &'a HeaderBlock,
83 parameter: &'a ParameterBlock,
84 mut handle: std::cell::RefMut<'a, R>,
85 ) -> Result<Self, ParserError> {
86 (*handle).seek(SeekFrom::Start((header.data_start as u64 - 1) * 512))?;
87 let points_buffer: Vec<u8> = vec![];
88 let analog_buffer: Vec<u8> = vec![];
89
90 let analog_unsigned = parameter
91 .get("ANALOG:FORMAT")
92 .map(|param| {
93 param
94 .parameter_data
95 .values
96 .iter()
97 .filter_map(|v| v.as_char())
98 .filter(|c| !c.is_whitespace())
99 .collect::<String>()
100 })
101 .and(Some("UNSIGNED"))
102 .is_some();
103
104 let analog_offset = parameter.get("ANALOG:OFFSET").map(|v| {
105 v.parameter_data
106 .values
107 .iter()
108 .filter_map(|arr| arr.as_i16())
109 .map(|a| *a as f32)
110 .collect::<Vec<f32>>()
111 });
112
113 let analog_scale = parameter.get("ANALOG:SCALE").map(|v| {
114 v.parameter_data
115 .values
116 .iter()
117 .filter_map(|arr| arr.as_f32())
118 .map(|a| *a)
119 .collect::<Vec<f32>>()
120 });
121
122 let analog_gen_scale = parameter
123 .get("ANALOG:GEN_SCALE")
124 .map(|v| {
125 v.parameter_data
126 .values
127 .iter()
128 .filter_map(|arr| arr.as_f32())
129 .map(|a| *a)
130 .next()
131 })
132 .flatten();
133
134 log::debug!("analog offsets: {:?}", analog_offset);
135 log::debug!("scale factors: {:?}", analog_scale);
136 log::debug!("genral scale factor: {:?}", analog_gen_scale);
137
138 Ok(C3dReader {
139 header,
140 parameter,
141 handle,
142 points_buffer,
143 analog_buffer,
144 frame_idx: header.frame_first,
145 analog_unsigned,
146 analog_offset,
147 analog_scale,
148 analog_gen_scale,
149 })
150 }
151}
152
153impl<'a, R: Read + Seek> Iterator for C3dReader<'a, R> {
154 type Item = (u16, PointData, Option<AnalogData>);
155
156 fn next(&mut self) -> Option<Self::Item> {
157 if self.frame_idx > self.header.frame_last {
158 return None;
159 }
160
161 let point_scale = self.header.scale;
162 let is_float = point_scale <= 0.0;
163
164 let point_data_length = if is_float { 4 } else { 2 };
165
166 let point_scale = if is_float { 1.0 } else { point_scale.abs() };
167
168 let analog_data_length = if is_float { 4 } else { 2 };
169
170 let points_n = 4 * self.header.point_counts;
171 let analog_n = self.header.analog_counts;
172
173 self.points_buffer
174 .resize((points_n * point_data_length) as usize, 0_u8);
175
176 if self.handle.read_exact(&mut self.points_buffer[..]).is_err() {
177 return None;
178 }
179
180 let values = self
181 .points_buffer
182 .chunks_exact(4 * point_data_length as usize)
183 .map(|arr| {
184 let mut points_vec = [0_f32; 5];
185 let raw_vec = arr
186 .chunks_exact(point_data_length as usize)
187 .filter_map(|arr| {
188 Some(if is_float {
189 let mut buf = [0_u8; 4];
190 arr.clone().read_exact(&mut buf).unwrap();
191 f32::from_le_bytes(buf)
192 } else {
193 let mut buf = [0_u8; 2];
194 arr.clone().read_exact(&mut buf).unwrap();
195 i16::from_le_bytes(buf) as f32 * point_scale
196 })
197 })
198 .collect::<Vec<f32>>();
199 points_vec[..4].copy_from_slice(&raw_vec);
200 points_vec
201 })
202 .map(|mut arr| {
203 let fourth = &arr[3];
204
205 if *fourth <= -0.01_f32 {
206 arr[3..5].iter_mut().for_each(|v| {
207 *v = -0.01_f32;
208 });
209 } else {
210 let err = arr[3] as i16;
211 arr[3] = (err & 0xff) as f32 * self.header.scale.abs();
213
214 arr[4] = (8..17)
216 .map(|idx| {
217 let mask = idx << 8;
219
220 (err & mask) as f32
221 })
222 .sum();
223 }
224 arr
225 })
226 .collect::<Vec<_>>();
227
228 let point_data = PointData { values };
229 let analog_data = if analog_n > 0 {
230 self.analog_buffer
231 .resize((analog_n * analog_data_length) as usize, 0_u8);
232 if self.handle.read_exact(&mut self.analog_buffer[..]).is_err() {
233 return None;
234 }
235
236 let mut values: Vec<f32> = self
237 .analog_buffer
238 .chunks_exact(analog_data_length as usize)
239 .map(|arr| {
240 if is_float {
241 let mut buf = [0_u8; 4];
242 (arr.clone()).read_exact(&mut buf).unwrap();
243 f32::from_le_bytes(buf)
244 } else {
245 let mut buf = [0_u8; 2];
246 if self.analog_unsigned {
247 (arr.clone()).read_exact(&mut buf).unwrap();
248 u16::from_le_bytes(buf) as f32
249 } else {
250 (arr.clone()).read_exact(&mut buf).unwrap();
251 i16::from_le_bytes(buf) as f32
252 }
253 }
254 })
255 .collect();
256 if let Some(offsets) = self.analog_offset.as_ref() {
257 values.iter_mut().zip(offsets.iter()).for_each(|(v, off)| {
258 *v -= *off;
259 });
260 }
261
262 if let Some(scales) = self.analog_scale.as_ref() {
263 values.iter_mut().zip(scales.iter()).for_each(|(v, off)| {
264 *v *= *off;
265 });
266 }
267
268 if let Some(gen_scale) = self.analog_gen_scale.as_ref() {
269 values.iter_mut().for_each(|v| {
270 *v *= *gen_scale;
271 });
272 }
273
274 Some(AnalogData { values })
275 } else {
276 None
277 };
278
279 let frame_idx = self.frame_idx;
280 self.frame_idx += 1;
281
282 Some((frame_idx, point_data, analog_data))
283 }
284}
285
286impl<T: Read + Seek> C3dAdapter<T> {
287 pub fn get_point_labels(&self) -> Option<Vec<String>> {
288 let mut rv = None;
289 if let Some(parameter) = self.parameter.as_ref() {
290 rv = parameter.get("POINT:LABELS").map(|param| {
291 param
292 .parameter_data
293 .values
294 .chunks_exact(param.dimensions[0] as usize)
295 .map(|arr| {
296 arr.iter()
297 .filter_map(|v| v.as_char())
298 .filter(|c| !c.is_whitespace())
299 .collect::<String>()
300 })
301 .collect::<Vec<String>>()
302 });
303 }
304 rv
305 }
306
307 pub fn get_analog_labels(&self) -> Option<Vec<String>> {
308 let mut rv = None;
309 if let Some(parameter) = self.parameter.as_ref() {
310 if let Some(analog) = parameter.groups.get("ANALOG") {
311 let mut keys = analog
312 .params
313 .keys()
314 .filter_map(|v| if v.contains("LABEL") { Some(v) } else { None })
315 .collect::<Vec<_>>();
316
317 keys.sort();
318
319 rv = Some(
320 keys.iter()
321 .filter_map(|v| {
322 parameter.get(&format!("ANALOG:{}", v)).map(|param| {
323 param
324 .parameter_data
325 .values
326 .chunks_exact(param.dimensions[0] as usize)
327 .map(|arr| {
328 arr.iter()
329 .filter_map(|c| c.as_char())
330 .filter(|c| !c.is_whitespace())
331 .collect::<String>()
332 })
333 .collect::<Vec<String>>()
334 })
335 })
336 .flatten()
337 .collect::<Vec<_>>(),
338 );
339 }
340 }
341 rv
342 }
343
344 pub fn reader<'a>(&'a self) -> Result<C3dReader<'a, T>, ParserError> {
345 if let Some(header) = self.header.as_ref() {
346 if let Some(parameter) = self.parameter.as_ref() {
347 let handle = self.handle.borrow_mut();
348 return C3dReader::new(header, parameter, handle);
349 }
350 }
351 Err(ParserError::MissingField)
352 }
353}
354
355#[repr(C, packed)]
356#[derive(Copy, Clone)]
357pub struct HeaderBlock {
358 parameter_start: u8,
359 magic_word: u8,
360 pub point_counts: u16,
361 pub analog_counts: u16,
362 pub frame_first: u16,
364 pub frame_last: u16,
366 pub max_gap: u16,
367 pub scale: f32,
368 data_start: u16,
369 pub analog_per_frame: u16,
370 pub frame_rate: f32,
371 reserved: [u8; 274],
372 event_lables_long: u16,
373 event_counts: u16,
374 reserved_two: u16,
375 event_times: [f32; 18],
376 event_display_flags: [u8; 18],
377 reserved_three: u16,
378 event_labels: [u8; 72],
379 reserved_four: [u8; 44],
380}
381
382trait FromReader {
383 fn from_reader<R: Read + Seek>(r: &mut R) -> Self;
384}
385
386impl FromReader for HeaderBlock {
387 fn from_reader<R: Read + Seek>(r: &mut R) -> Self {
388 let mut header: HeaderBlock = unsafe { mem::zeroed() };
389 let header_size = mem::size_of::<HeaderBlock>();
390
391 unsafe {
392 let header_slice =
394 slice::from_raw_parts_mut(&mut header as *mut _ as *mut u8, header_size);
395
396 r.read_exact(header_slice).unwrap();
397 }
398
399 header
400 }
401}
402
403impl FromReader for ParameterBlockHeader {
404 fn from_reader<R: Read + Seek>(r: &mut R) -> Self {
405 let mut parameter: ParameterBlockHeader = unsafe { mem::zeroed() };
406
407 unsafe {
408 let parameter_slice = slice::from_raw_parts_mut(&mut parameter as *mut _ as *mut u8, 4);
409 r.read_exact(parameter_slice).unwrap();
410 }
411
412 parameter
413 }
414}
415
416impl FromReader for ParameterBlock {
417 fn from_reader<R: Read + Seek>(r: &mut R) -> Self {
418 let header = ParameterBlockHeader::from_reader(r);
419
420 let mut u8_buffer = [0_u8];
421 let mut i8_buffer = [0_u8];
422 let mut i16_buffer = [0_u8; 2];
423 let mut string_buffer: Vec<u8> = vec![];
424 let mut parameter_buf: Vec<u8> = vec![];
425
426 parameter_buf.resize(header.parameter_block_counts as usize * 512 - 4, 0);
427 r.read_exact(&mut parameter_buf).unwrap();
428 let mut parameter_block_cursor = Cursor::new(¶meter_buf[..]);
429
430 let mut groups = HashMap::<u8, GroupFormat>::new();
431
432 loop {
433 parameter_block_cursor.read_exact(&mut i8_buffer).unwrap();
434 let name_chars_size = i8::from_le_bytes(i8_buffer);
435
436 let locked = name_chars_size < 0;
437 let name_chars_size = name_chars_size.abs() as usize;
438
439 parameter_block_cursor.read_exact(&mut i8_buffer).unwrap();
440 let id: i8 = i8::from_le_bytes(i8_buffer);
441
442 if id == 0 || name_chars_size == 0 {
443 break;
444 }
445
446 string_buffer.resize(name_chars_size, 0_u8);
447 parameter_block_cursor
448 .read_exact(&mut string_buffer)
449 .unwrap();
450
451 let name = String::from_utf8(string_buffer.clone()).unwrap();
452
453 parameter_block_cursor.read_exact(&mut i16_buffer).unwrap();
454 let offset = i16::from_le_bytes(i16_buffer);
455
456 let is_param = id > 0;
457 if is_param {
458 parameter_block_cursor.read_exact(&mut i8_buffer).unwrap();
460 let data_length = i8::from_le_bytes(i8_buffer);
461
462 parameter_block_cursor.read_exact(&mut u8_buffer).unwrap();
464 let num_dimensions = u8_buffer[0];
465
466 let mut num_elements = 1_i32;
467 let mut buf = [0_u8; 1];
468
469 let mut dimensions = vec![];
470 for _ in 0..num_dimensions {
471 parameter_block_cursor.read_exact(&mut buf).unwrap();
472 dimensions.push(buf[0]);
473 num_elements *= buf[0] as i32;
474 }
475
476 let total_data_length = num_elements as i16 * data_length.abs() as i16;
477
478 let mut data_buffer = vec![0_u8; total_data_length as usize];
479 parameter_block_cursor.read_exact(&mut data_buffer).unwrap();
480
481 let datas: Vec<Box<dyn ParamValue>> = data_buffer
482 .chunks_exact(data_length.abs() as usize)
483 .filter_map(|arr| match data_length {
484 1 => Some(Box::new(arr[0] as u8) as Box<dyn ParamValue>),
485 2 => {
486 let mut buf = [0_u8; 2];
487 (arr.clone()).read_exact(&mut buf).unwrap();
488 let val = i16::from_le_bytes(buf);
489 Some(Box::new(val) as Box<dyn ParamValue>)
490 }
491 4 => {
492 let mut buf = [0_u8; 4];
493 (arr.clone()).read_exact(&mut buf).unwrap();
494 let val = f32::from_le_bytes(buf);
495 Some(Box::new(val))
496 }
497 -1 => Some(Box::new(arr[0] as char) as Box<dyn ParamValue>),
498 _ => None,
499 })
500 .collect();
501 let param_data = ParamData { values: datas };
502
503 parameter_block_cursor.read_exact(&mut u8_buffer).unwrap();
504 let desc_chars_size = u8_buffer[0];
505 string_buffer.resize(desc_chars_size as usize, 0);
506 let desc = String::from_utf8(string_buffer.clone()).unwrap();
507
508 let param = ParameterFormat {
509 id,
510 name_chars_size: name_chars_size as u8,
511 name: name.clone(),
512 offset,
513 data_length,
514 num_dimensions,
515 dimensions,
516 parameter_data: param_data,
517 desc_chars_size,
518 description: desc,
519 locked,
520 };
521
522 let group_id = id as u8;
523
524 if let Some(group) = groups.get_mut(&group_id) {
525 group.params.insert(name, param);
526 } else {
527 let mut group = GroupFormat::default();
528 group.params.insert(name, param);
529 groups.insert(group_id, group);
530 }
531 } else {
532 let group_id = id.abs() as u8;
533 parameter_block_cursor.read_exact(&mut u8_buffer).unwrap();
534 let desc_chars_size = u8_buffer[0];
535 string_buffer.resize(desc_chars_size as usize, 0);
536 let desc = String::from_utf8(string_buffer.clone()).unwrap();
537
538 if let Some(group) = groups.get_mut(&group_id) {
539 group.name = name;
540 group.description = desc;
541 } else {
542 let new_group = GroupFormat {
543 name,
544 description: desc,
545 locked,
546 ..Default::default()
547 };
548 groups.insert(group_id, new_group);
549 }
550 }
551
552 parameter_buf = parameter_buf.split_off(2 + name_chars_size as usize + offset as usize);
553 parameter_block_cursor = Cursor::new(¶meter_buf[..]);
554 }
555
556 let groups: HashMap<String, GroupFormat> = groups
557 .into_iter()
558 .map(|(_, v)| (v.name.clone(), v))
559 .collect();
560
561 let parameter_block = ParameterBlock { header, groups };
562
563 parameter_block
564 }
565}
566
567#[derive(Debug)]
568pub struct ParameterBlock {
569 header: ParameterBlockHeader,
570 pub groups: HashMap<String, GroupFormat>,
571}
572
573impl ParameterBlock {
574 pub fn get(&self, key: &str) -> Option<&ParameterFormat> {
575 let split_key: &'static str;
576
577 if key.contains(".") {
578 split_key = ".";
579 } else if key.contains(":") {
580 split_key = ":";
581 } else {
582 return None;
583 }
584
585 let mut iter = key.split(split_key);
586 let group_key = iter.next().unwrap();
587 let param_key = iter.next().unwrap();
588
589 if let Some(group) = self.groups.get(group_key) {
590 if let Some(param) = group.params.get(param_key) {
591 return Some(¶m);
592 }
593 }
594
595 None
596 }
597}
598
599#[repr(C, packed)]
600#[derive(Copy, Clone, Debug)]
601struct ParameterBlockHeader {
602 reserved_one: u8,
603 reserved_two: u8,
604 parameter_block_counts: u8,
605 magic_word: u8,
606}
607
608#[derive(Debug)]
609pub struct ParameterFormat {
610 name_chars_size: u8,
612 id: i8,
613 pub name: String,
614 offset: i16,
615 pub data_length: i8,
616 pub num_dimensions: u8,
617 pub dimensions: Vec<u8>,
618 pub parameter_data: ParamData,
619 desc_chars_size: u8,
620 pub description: String,
621 pub locked: bool,
622}
623
624#[derive(Default, Debug)]
625pub struct GroupFormat {
626 pub name: String,
627 pub description: String,
628 pub locked: bool,
629 pub params: HashMap<String, ParameterFormat>,
630}
631
632#[derive(Debug)]
633pub struct ParamData {
634 pub values: Vec<Box<dyn ParamValue>>,
635}
636
637#[derive(Debug)]
638pub struct PointData {
639 pub values: Vec<[f32; 5]>,
640}
641
642#[derive(Debug)]
643pub struct AnalogData {
644 pub values: Vec<f32>,
645}
646
647pub trait ParamValue: std::fmt::Debug + ParamClone {
648 fn as_char(&self) -> Option<&char> {
649 None
650 }
651 fn as_f32(&self) -> Option<&f32> {
652 None
653 }
654 fn as_i16(&self) -> Option<&i16> {
655 None
656 }
657 fn as_u8(&self) -> Option<&u8> {
658 None
659 }
660}
661
662pub trait ParamClone {
663 fn clone_box(&self) -> Box<dyn ParamValue>;
664}
665
666impl<T> ParamClone for T
667where
668 T: 'static + ParamValue + Clone,
669{
670 fn clone_box(&self) -> Box<dyn ParamValue> {
671 Box::new(self.clone())
672 }
673}
674
675impl ParamValue for char {
676 fn as_char(&self) -> Option<&char> {
677 Some(self)
678 }
679}
680impl ParamValue for f32 {
681 fn as_f32(&self) -> Option<&f32> {
682 Some(self)
683 }
684}
685
686impl ParamValue for i16 {
687 fn as_i16(&self) -> Option<&i16> {
688 Some(&self)
689 }
690}
691
692impl ParamValue for u8 {
693 fn as_u8(&self) -> Option<&u8> {
694 Some(&self)
695 }
696}
697
698#[cfg(test)]
699mod tests {
700 use super::*;
701
702 fn set_logger() {
703 femme::with_level(log::LevelFilter::Debug);
704 }
705
706 #[test]
707 fn test_parser() -> Result<()> {
708 set_logger();
709
710 let mut file = File::open("test_data/vicon_trial.c3d")?;
711
712 let mut buf: Vec<u8> = vec![];
713 file.read_to_end(&mut buf)?;
714
715 let mut cursor = Cursor::new(&buf[..]);
716
717 let adapter = C3dAdapter::new(&mut cursor)?.construct()?;
718 for (i, p, a) in adapter.reader()?.into_iter() {
719 dbg!(i, p, a);
720 }
721
722 adapter.get_point_labels().unwrap();
723 adapter.get_analog_labels().unwrap();
724
725 let mut file = File::open("test_data/motion_shadow.c3d")?;
726 let adapter = C3dAdapter::new(&mut file)?.construct()?;
727 for (i, p, a) in adapter.reader()?.into_iter() {
728 dbg!(i, p, a);
729 }
730
731 Ok(())
732 }
733}