c3d_rs/
lib.rs

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        // 0x50 if header is of correct format.
50        if header.magic_word != 0x50 {
51            return Err(ParserError::UnmatchMagic);
52        }
53
54        // 0x50 + 4 if parameter is of correct format.
55        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                    // the right eight bits are for error estimation.
212                    arr[3] = (err & 0xff) as f32 * self.header.scale.abs();
213
214                    // the left eight bits are for reporting total number of camera that obsered
215                    arr[4] = (8..17)
216                        .map(|idx| {
217                            // camera mask to check wheter the bits are sets
218                            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    // index start from 1
363    pub frame_first: u16,
364    // index start from 1
365    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            // directly mutate the memory slice under Header.
393            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(&parameter_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                // length of each data element
459                parameter_block_cursor.read_exact(&mut i8_buffer).unwrap();
460                let data_length = i8::from_le_bytes(i8_buffer);
461
462                // number of dimensions to read
463                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(&parameter_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(&param);
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    // indicates "locked" if value is negative.
611    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}