1use std::fs::File;
21use std::io::{Read, Seek, SeekFrom, Write};
22use std::path::{Path, PathBuf};
23
24use rayon::prelude::*;
25
26use super::{ImuSample, IoError, LoadOptions, SliceSource};
27use crate::representation::{EventFrame, EventFrameData};
28use crate::{EventStream, EventStreamBuilder};
29
30const TIMESTAMP_SCALE_MS: f64 = 0.001;
32
33const MAGIC: &[u8] = b"#!AER-DAT4.0\r\n";
34const PACKET_HEADER: usize = 8;
36
37const EVENT_STRIDE: usize = 16;
40
41const STANDARD_GRAVITY: f64 = 9.806_65;
43
44mod slot {
47 pub const COMPRESSION: usize = 4;
49 pub const DATA_TABLE_POSITION: usize = 6;
50 pub const INFO_NODE: usize = 8;
51
52 pub const ELEMENTS: usize = 4;
54
55 pub const FRAME_TIMESTAMP: usize = 4;
58 pub const FRAME_FORMAT: usize = 14;
59 pub const FRAME_SIZE_X: usize = 16;
60 pub const FRAME_SIZE_Y: usize = 18;
61 pub const FRAME_PIXELS: usize = 24;
62
63 pub const IMU_TIMESTAMP: usize = 4;
65 pub const IMU_ACCELEROMETER: usize = 8; pub const IMU_GYROSCOPE: usize = 14;
67
68 pub const DEFINITION_BYTE_OFFSET: usize = 4;
70 pub const DEFINITION_PACKET_INFO: usize = 6;
71 pub const DEFINITION_NUM_ELEMENTS: usize = 8;
72 pub const DEFINITION_TIMESTAMP_START: usize = 10;
73 pub const DEFINITION_TIMESTAMP_END: usize = 12;
74
75 pub const TABLE: usize = 4;
77}
78
79const FORMAT_GREY: i8 = 0; const FORMAT_BGR: i8 = 16; const FORMAT_BGRA: i8 = 24; fn scalar<const N: usize>(buffer: &[u8], at: usize) -> Option<[u8; N]> {
85 buffer.get(at..at.checked_add(N)?)?.try_into().ok()
86}
87
88#[derive(Clone, Copy)]
90struct Table<'a> {
91 buffer: &'a [u8],
92 at: usize,
93}
94
95impl<'a> Table<'a> {
96 fn size_prefixed_root(buffer: &'a [u8]) -> Option<Self> {
98 Self::root_at(buffer, 4)
99 }
100
101 fn root(buffer: &'a [u8]) -> Option<Self> {
103 Self::root_at(buffer, 0)
104 }
105
106 fn root_at(buffer: &'a [u8], at: usize) -> Option<Self> {
107 let offset = u32::from_le_bytes(scalar(buffer, at)?) as usize;
108 let at = at.checked_add(offset)?;
109 (at < buffer.len()).then_some(Self { buffer, at })
110 }
111
112 fn field(&self, slot: usize) -> Option<usize> {
115 let soffset = i32::from_le_bytes(scalar(self.buffer, self.at)?);
116 let vtable = usize::try_from(i64::try_from(self.at).ok()? - i64::from(soffset)).ok()?;
117 let length = u16::from_le_bytes(scalar(self.buffer, vtable)?) as usize;
118 if slot + 2 > length {
119 return None; }
121 let offset = u16::from_le_bytes(scalar(self.buffer, vtable.checked_add(slot)?)?) as usize;
122 (offset != 0).then(|| self.at.checked_add(offset)).flatten()
123 }
124
125 fn i8(&self, slot: usize, default: i8) -> i8 {
126 self.field(slot)
127 .and_then(|at| scalar::<1>(self.buffer, at))
128 .map_or(default, i8::from_le_bytes)
129 }
130
131 fn i16(&self, slot: usize) -> i16 {
132 self.field(slot)
133 .and_then(|at| scalar::<2>(self.buffer, at))
134 .map_or(0, i16::from_le_bytes)
135 }
136
137 fn i32(&self, slot: usize) -> i32 {
138 self.field(slot)
139 .and_then(|at| scalar::<4>(self.buffer, at))
140 .map_or(0, i32::from_le_bytes)
141 }
142
143 fn i64(&self, slot: usize, default: i64) -> i64 {
144 self.field(slot)
145 .and_then(|at| scalar::<8>(self.buffer, at))
146 .map_or(default, i64::from_le_bytes)
147 }
148
149 fn f32(&self, slot: usize) -> f32 {
150 self.field(slot)
151 .and_then(|at| scalar::<4>(self.buffer, at))
152 .map_or(0.0, f32::from_le_bytes)
153 }
154
155 fn indirect(&self, slot: usize) -> Option<usize> {
157 let at = self.field(slot)?;
158 let offset = u32::from_le_bytes(scalar(self.buffer, at)?) as usize;
159 at.checked_add(offset)
160 }
161
162 fn string(&self, slot: usize) -> Option<&'a str> {
163 let (bytes, _) = self.vector(slot, 1)?;
164 std::str::from_utf8(bytes).ok()
165 }
166
167 fn vector(&self, slot: usize, stride: usize) -> Option<(&'a [u8], usize)> {
170 let at = self.indirect(slot)?;
171 let elements = u32::from_le_bytes(scalar(self.buffer, at)?) as usize;
172 let start = at.checked_add(4)?;
173 let end = start.checked_add(elements.checked_mul(stride)?)?;
174 Some((self.buffer.get(start..end)?, elements))
175 }
176
177 fn tables(&self, slot: usize) -> Vec<Table<'a>> {
179 let Some((bytes, elements)) = self.vector(slot, 4) else {
180 return Vec::new();
181 };
182 let start = bytes.as_ptr() as usize - self.buffer.as_ptr() as usize;
183 (0..elements)
184 .filter_map(|index| {
185 let at = start + index * 4;
186 let offset = u32::from_le_bytes(scalar(self.buffer, at)?) as usize;
187 Some(Table {
188 buffer: self.buffer,
189 at: at.checked_add(offset)?,
190 })
191 })
192 .collect()
193 }
194}
195
196#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
199pub enum Compression {
200 None,
201 #[default]
202 Lz4,
203 Zstd,
204}
205
206impl Compression {
207 fn to_header(self) -> i32 {
212 match self {
213 Self::None => 0,
214 Self::Lz4 => 1,
215 Self::Zstd => 3,
216 }
217 }
218
219 fn encode(self, body: &[u8]) -> Result<Vec<u8>, IoError> {
222 match self {
223 Self::None => Ok(body.to_vec()),
224 Self::Lz4 => {
225 let mut encoder = lz4::EncoderBuilder::new()
226 .build(Vec::new())
227 .map_err(IoError::Io)?;
228 encoder.write_all(body).map_err(IoError::Io)?;
229 let (out, result) = encoder.finish();
230 result.map_err(IoError::Io)?;
231 Ok(out)
232 }
233 Self::Zstd => zstd::stream::encode_all(body, 3).map_err(IoError::Io),
234 }
235 }
236
237 fn from_header(value: i32) -> Result<Self, IoError> {
238 match value {
239 0 => Ok(Self::None),
240 1 | 2 => Ok(Self::Lz4), 3 | 4 => Ok(Self::Zstd), other => Err(IoError::Unsupported(format!(
243 "unknown AEDAT4 compression type {other}"
244 ))),
245 }
246 }
247
248 fn decode_into(self, body: &[u8], lz4: &mut Lz4Frame, out: &mut Vec<u8>) -> Result<(), IoError> {
252 out.clear();
253 match self {
254 Self::None => out.extend_from_slice(body),
255 Self::Lz4 => lz4.decompress_into(body, out)?,
257 Self::Zstd => {
258 zstd::stream::copy_decode(body, &mut *out).map_err(|error| {
259 IoError::Format(format!("AEDAT4 zstd packet is corrupt: {error}"))
260 })?;
261 }
262 }
263 Ok(())
264 }
265}
266
267struct Lz4Frame {
275 ctx: lz4_sys::LZ4FDecompressionContext,
276}
277
278impl Lz4Frame {
279 fn new() -> Self {
280 let mut ctx = lz4_sys::LZ4FDecompressionContext(std::ptr::null_mut());
281 let code = unsafe { lz4_sys::LZ4F_createDecompressionContext(&mut ctx, lz4_sys::LZ4F_VERSION) };
284 if unsafe { lz4_sys::LZ4F_isError(code) } != 0 {
285 ctx = lz4_sys::LZ4FDecompressionContext(std::ptr::null_mut());
286 }
287 Self { ctx }
288 }
289
290 fn decompress_into(&mut self, src: &[u8], out: &mut Vec<u8>) -> Result<(), IoError> {
292 if self.ctx.0.is_null() {
293 return Err(IoError::Format(
294 "AEDAT4 LZ4 decompression context could not be created".to_owned(),
295 ));
296 }
297 if out.capacity() == 0 {
300 out.reserve(256 * 1024);
301 }
302 let mut read = 0usize;
303 loop {
304 if out.len() == out.capacity() {
305 out.reserve(out.capacity().max(64 * 1024));
306 }
307 let written = out.len();
308 let mut dst_size = out.capacity() - written;
309 let mut src_size = src.len() - read;
310 let hint = unsafe {
315 lz4_sys::LZ4F_decompress(
316 self.ctx,
317 out.as_mut_ptr().add(written),
318 &mut dst_size,
319 src.as_ptr().add(read),
320 &mut src_size,
321 std::ptr::null(),
322 )
323 };
324 if unsafe { lz4_sys::LZ4F_isError(hint) } != 0 {
325 *self = Self::new();
328 return Err(IoError::Format("AEDAT4 LZ4 packet is corrupt".to_owned()));
329 }
330 unsafe { out.set_len(written + dst_size) };
332 read += src_size;
333 if hint == 0 {
334 return Ok(()); }
336 if src_size == 0 && dst_size == 0 {
337 return Err(IoError::Format(
338 "AEDAT4 LZ4 packet ended mid-frame".to_owned(),
339 ));
340 }
341 }
342 }
343}
344
345impl Drop for Lz4Frame {
346 fn drop(&mut self) {
347 if !self.ctx.0.is_null() {
348 unsafe { lz4_sys::LZ4F_freeDecompressionContext(self.ctx) };
351 }
352 }
353}
354
355impl Default for Lz4Frame {
356 fn default() -> Self {
357 Self::new()
358 }
359}
360
361#[derive(Clone, Copy, Debug)]
363struct Packet {
364 offset: u64,
365 size: usize,
366 first: usize,
368 elements: usize,
369 start: i64,
370 end: i64,
371}
372
373#[derive(Clone, Copy, Debug, PartialEq, Eq)]
375enum Kind {
376 Events,
377 Frames,
378 Imu,
379 Other,
380}
381
382impl Kind {
383 fn from_identifier(identifier: &str) -> Self {
384 match identifier {
385 "EVTS" => Self::Events,
386 "FRME" => Self::Frames,
387 "IMUS" => Self::Imu,
388 _ => Self::Other,
389 }
390 }
391}
392
393#[derive(Clone, Debug)]
395struct Stream {
396 id: i32,
397 kind: Kind,
398 size: Option<(usize, usize)>,
399}
400
401fn parse_streams(info: &str) -> Vec<Stream> {
408 const NODE: &str = "<node name=\"";
409 let mut marks: Vec<(usize, i32)> = Vec::new();
410 let mut at = 0;
411 while let Some(found) = info[at..].find(NODE) {
412 let start = at + found + NODE.len();
413 at = start;
414 let Some(end) = info[start..].find('"') else {
415 break;
416 };
417 if let Ok(id) = info[start..start + end].parse::<i32>() {
419 marks.push((start, id));
420 }
421 }
422 marks
423 .iter()
424 .enumerate()
425 .map(|(index, &(start, id))| {
426 let end = marks.get(index + 1).map_or(info.len(), |&(next, _)| next);
427 let block = &info[start..end];
428 Stream {
429 id,
430 kind: Kind::from_identifier(attribute(block, "typeIdentifier").unwrap_or("")),
431 size: attribute(block, "sizeX")
432 .and_then(|value| value.parse().ok())
433 .zip(attribute(block, "sizeY").and_then(|value| value.parse().ok())),
434 }
435 })
436 .collect()
437}
438
439fn attribute<'a>(block: &'a str, key: &str) -> Option<&'a str> {
441 let after = &block[block.find(&format!("key=\"{key}\""))?..];
442 let open = after.find('>')? + 1;
443 let close = after[open..].find('<')?;
444 Some(&after[open..open + close])
445}
446
447struct Header {
449 compression: Compression,
450 data_table_position: i64,
451 streams: Vec<Stream>,
452 body_offset: u64,
453}
454
455fn read_header(file: &mut File) -> Result<Header, IoError> {
456 let mut magic = [0u8; MAGIC.len()];
457 file.read_exact(&mut magic).map_err(|_| {
458 IoError::Format("not an AEDAT 4 file (too short for a version line)".to_owned())
459 })?;
460 if magic != MAGIC {
461 return Err(IoError::Format(format!(
462 "not an AEDAT 4.0 file (version line is {:?})",
463 String::from_utf8_lossy(&magic).trim_end()
464 )));
465 }
466 let mut length = [0u8; 4];
467 file.read_exact(&mut length)?;
468 let length = u32::from_le_bytes(length) as usize;
469 let mut buffer = vec![0u8; length];
470 file.read_exact(&mut buffer)?;
471
472 let header = Table::root(&buffer)
473 .ok_or_else(|| IoError::Format("AEDAT4 header is not a readable FlatBuffer".to_owned()))?;
474 Ok(Header {
475 compression: Compression::from_header(header.i32(slot::COMPRESSION))?,
476 data_table_position: header.i64(slot::DATA_TABLE_POSITION, -1),
477 streams: parse_streams(header.string(slot::INFO_NODE).unwrap_or("")),
478 body_offset: (MAGIC.len() + 4 + length) as u64,
479 })
480}
481
482fn read_body(
484 path: &Path,
485 compression: Compression,
486 offset: u64,
487 size: usize,
488) -> Result<Vec<u8>, IoError> {
489 let mut file = File::open(path)?;
490 let mut buffers = Buffers::default();
491 read_body_with(&mut file, compression, offset, size, &mut buffers)?;
492 Ok(std::mem::take(&mut buffers.plain))
493}
494
495fn read_body_with(
500 file: &mut File,
501 compression: Compression,
502 offset: u64,
503 size: usize,
504 buffers: &mut Buffers,
505) -> Result<(), IoError> {
506 file.seek(SeekFrom::Start(offset))?;
507 buffers.compressed.resize(size, 0);
511 buffers.compressed.truncate(size);
512 file.read_exact(&mut buffers.compressed)?;
513 let Buffers {
514 compressed,
515 plain,
516 lz4,
517 } = buffers;
518 compression.decode_into(compressed, lz4, plain)
519}
520
521#[derive(Default)]
524struct Buffers {
525 compressed: Vec<u8>,
526 plain: Vec<u8>,
527 lz4: Lz4Frame,
528}
529
530const PARALLEL_PACKET_THRESHOLD: usize = 8;
534
535type PacketTask<'a> = (&'a Packet, usize, usize, &'a mut [u16], &'a mut [u16], &'a mut [i64], &'a mut [bool]);
538
539pub struct Aedat4SliceSource {
541 path: PathBuf,
542 compression: Compression,
543 width: usize,
544 height: usize,
545 events: Vec<Packet>,
546 frames: Vec<Packet>,
547 imu: Vec<Packet>,
548 n_events: usize,
549 time_span: (i64, i64),
550}
551
552impl Aedat4SliceSource {
553 fn open(path: &Path, options: &LoadOptions) -> Result<Self, IoError> {
554 let mut file = File::open(path)?;
555 let header = read_header(&mut file)?;
556 let total = file.metadata()?.len();
557
558 let packets = match header.data_table_position {
559 position if position >= 0 && (position as u64) < total => read_data_table(
560 path,
561 header.compression,
562 position as u64,
563 total - position as u64,
564 )?,
565 _ => walk_packets(path, header.compression, header.body_offset, total)?,
568 };
569
570 let of_kind = |kind: Kind| -> Vec<Packet> {
571 let ids: Vec<i32> = header
572 .streams
573 .iter()
574 .filter(|stream| stream.kind == kind)
575 .map(|stream| stream.id)
576 .collect();
577 let mut selected: Vec<Packet> = packets
578 .iter()
579 .filter(|(id, _)| ids.contains(id))
580 .map(|(_, packet)| *packet)
581 .collect();
582 selected.sort_by_key(|packet| packet.start);
583 let mut first = 0;
584 for packet in &mut selected {
585 packet.first = first;
586 first += packet.elements;
587 }
588 selected
589 };
590 let events = of_kind(Kind::Events);
591 let (width, height) = options
592 .sensor_size
593 .or_else(|| {
594 header
595 .streams
596 .iter()
597 .find(|stream| stream.kind == Kind::Events)
598 .and_then(|stream| stream.size)
599 })
600 .ok_or(IoError::InvalidSensorSize)?;
601 if width == 0 || height == 0 {
602 return Err(IoError::InvalidSensorSize);
603 }
604
605 let n_events = events.last().map_or(0, |last| last.first + last.elements);
606 let time_span = match (events.first(), events.last()) {
607 (Some(first), Some(last)) => (first.start, last.end),
608 _ => (0, 0),
609 };
610 Ok(Self {
611 path: path.to_path_buf(),
612 compression: header.compression,
613 width,
614 height,
615 events,
616 frames: of_kind(Kind::Frames),
617 imu: of_kind(Kind::Imu),
618 n_events,
619 time_span,
620 })
621 }
622
623 fn body(&self, packet: &Packet) -> Result<Vec<u8>, IoError> {
624 read_body(&self.path, self.compression, packet.offset, packet.size)
625 }
626
627 fn body_with(
629 &self,
630 file: &mut File,
631 buffers: &mut Buffers,
632 packet: &Packet,
633 ) -> Result<(), IoError> {
634 read_body_with(file, self.compression, packet.offset, packet.size, buffers)
635 }
636
637 fn fill_slots(
644 &self,
645 file: &mut File,
646 buffers: &mut Buffers,
647 task: PacketTask<'_>,
648 ) -> Result<usize, IoError> {
649 let (packet, lo, hi, x, y, t, p) = task;
650 let mut kept = 0usize;
651 self.each_event_with(file, buffers, packet, |index, ex, ey, et, ep| {
652 if index >= lo
653 && index < hi
654 && usize::from(ex) < self.width
655 && usize::from(ey) < self.height
656 {
657 x[kept] = ex;
658 y[kept] = ey;
659 t[kept] = et;
660 p[kept] = ep;
661 kept += 1;
662 }
663 })?;
664 Ok(kept)
665 }
666
667 fn each_event_with(
670 &self,
671 file: &mut File,
672 buffers: &mut Buffers,
673 packet: &Packet,
674 mut visit: impl FnMut(usize, u16, u16, i64, bool),
675 ) -> Result<(), IoError> {
676 self.body_with(file, buffers, packet)?;
677 let body = &buffers.plain;
678 let table = Table::size_prefixed_root(body)
679 .ok_or_else(|| IoError::Format("AEDAT4 event packet is not readable".to_owned()))?;
680 let Some((bytes, elements)) = table.vector(slot::ELEMENTS, EVENT_STRIDE) else {
681 return Ok(());
682 };
683 let (events, _) = bytes.as_chunks::<EVENT_STRIDE>();
684 for (index, event) in events.iter().enumerate().take(elements) {
685 let t = i64::from_le_bytes(event[0..8].try_into().expect("eight bytes"));
686 let x = i16::from_le_bytes(event[8..10].try_into().expect("two bytes"));
687 let y = i16::from_le_bytes(event[10..12].try_into().expect("two bytes"));
688 if x < 0 || y < 0 {
689 continue;
690 }
691 visit(packet.first + index, x as u16, y as u16, t, event[12] != 0);
692 }
693 Ok(())
694 }
695}
696
697impl SliceSource for Aedat4SliceSource {
698 fn sensor_size(&self) -> (usize, usize) {
699 (self.width, self.height)
700 }
701
702 fn timestamp_scale_ms(&self) -> f64 {
703 TIMESTAMP_SCALE_MS
704 }
705
706 fn n_events(&self) -> usize {
707 self.n_events
708 }
709
710 fn time_span(&self) -> (i64, i64) {
711 self.time_span
712 }
713
714 fn slice_index(&self, i0: usize, i1: usize) -> Result<EventStream, IoError> {
715 let i0 = i0.min(self.n_events);
716 let i1 = i1.clamp(i0, self.n_events);
717 let selected: Vec<&Packet> = self
718 .events
719 .iter()
720 .filter(|packet| packet.first < i1 && packet.first + packet.elements > i0)
721 .collect();
722
723 let total = i1 - i0;
734 let mut xs = vec![0u16; total];
735 let mut ys = vec![0u16; total];
736 let mut ts = vec![0i64; total];
737 let mut ps = vec![false; total];
738
739 let mut tasks = Vec::with_capacity(selected.len());
743 let mut slots = Vec::with_capacity(selected.len());
744 let (mut xs_rest, mut ys_rest) = (xs.as_mut_slice(), ys.as_mut_slice());
745 let (mut ts_rest, mut ps_rest) = (ts.as_mut_slice(), ps.as_mut_slice());
746 for packet in &selected {
747 let lo = i0.max(packet.first);
748 let hi = i1.min(packet.first + packet.elements);
749 let room = hi - lo;
750 let (x, x_rest) = xs_rest.split_at_mut(room);
751 let (y, y_rest) = ys_rest.split_at_mut(room);
752 let (t, t_rest) = ts_rest.split_at_mut(room);
753 let (p, p_rest) = ps_rest.split_at_mut(room);
754 xs_rest = x_rest;
755 ys_rest = y_rest;
756 ts_rest = t_rest;
757 ps_rest = p_rest;
758 slots.push(room);
759 tasks.push((*packet, lo, hi, x, y, t, p));
760 }
761
762 let wide = selected.len() >= PARALLEL_PACKET_THRESHOLD && rayon::current_num_threads() > 1;
771 let kept: Result<Vec<usize>, IoError> = if wide {
772 tasks
773 .into_par_iter()
774 .map_init(
775 || (File::open(&self.path), Buffers::default()),
776 |(file, buffers), task| {
777 let file = file.as_mut().map_err(|error| {
778 IoError::Io(std::io::Error::new(error.kind(), error.to_string()))
779 })?;
780 self.fill_slots(file, buffers, task)
781 },
782 )
783 .collect()
784 } else {
785 let mut file = File::open(&self.path)?;
786 let mut buffers = Buffers::default();
787 tasks
788 .into_iter()
789 .map(|task| self.fill_slots(&mut file, &mut buffers, task))
790 .collect()
791 };
792 let kept = kept?;
793
794 let filled: usize = kept.iter().sum();
797 if filled < total {
798 let (mut write, mut read) = (0usize, 0usize);
799 for (&n, &room) in kept.iter().zip(&slots) {
800 if write != read {
801 xs.copy_within(read..read + n, write);
802 ys.copy_within(read..read + n, write);
803 ts.copy_within(read..read + n, write);
804 ps.copy_within(read..read + n, write);
805 }
806 write += n;
807 read += room;
808 }
809 }
810 xs.truncate(filled);
811 ys.truncate(filled);
812 ts.truncate(filled);
813 ps.truncate(filled);
814
815 Ok(
816 EventStreamBuilder::from_columns(
817 self.width,
818 self.height,
819 TIMESTAMP_SCALE_MS,
820 xs,
821 ys,
822 ts,
823 ps,
824 )
825 .build(),
826 )
827 }
828
829 fn slice_time(&self, t0: i64, t1: i64) -> Result<EventStream, IoError> {
830 let mut builder = EventStreamBuilder::new(self.width, self.height, TIMESTAMP_SCALE_MS);
831 let mut file = File::open(&self.path)?;
832 let mut buffers = Buffers::default();
833 for packet in overlapping(&self.events, t0, t1) {
834 self.each_event_with(&mut file, &mut buffers, packet, |_, x, y, t, p| {
835 if t >= t0 && t < t1 {
836 builder.push(x, y, t, p);
837 }
838 })?;
839 }
840 Ok(builder.build())
841 }
842
843 fn frames(&self, t0: i64, t1: i64) -> Result<Vec<(i64, EventFrame)>, IoError> {
844 let mut frames = Vec::new();
845 for packet in overlapping(&self.frames, t0, t1) {
846 let body = self.body(packet)?;
847 let table = Table::size_prefixed_root(&body)
850 .ok_or_else(|| IoError::Format("AEDAT4 frame packet is not readable".to_owned()))?;
851 let t = table.i64(slot::FRAME_TIMESTAMP, 0);
852 if t < t0 || t >= t1 {
853 continue;
854 }
855 frames.push((t, decode_frame(&table)?));
856 }
857 Ok(frames)
858 }
859
860 fn imu(&self, t0: i64, t1: i64) -> Result<Vec<ImuSample>, IoError> {
861 let mut samples = Vec::new();
862 for packet in overlapping(&self.imu, t0, t1) {
863 let body = self.body(packet)?;
864 let table = Table::size_prefixed_root(&body)
865 .ok_or_else(|| IoError::Format("AEDAT4 IMU packet is not readable".to_owned()))?;
866 for element in table.tables(slot::ELEMENTS) {
867 let t = element.i64(slot::IMU_TIMESTAMP, 0);
868 if t < t0 || t >= t1 {
869 continue;
870 }
871 let axes = |base: usize, scale: f64| {
873 [0, 1, 2].map(|axis| f64::from(element.f32(base + axis * 2)) * scale)
874 };
875 samples.push(ImuSample {
876 t_us: t,
877 linear_acceleration: axes(slot::IMU_ACCELEROMETER, STANDARD_GRAVITY),
878 angular_velocity: axes(slot::IMU_GYROSCOPE, std::f64::consts::PI / 180.0),
879 });
880 }
881 }
882 Ok(samples)
883 }
884}
885
886fn overlapping(packets: &[Packet], t0: i64, t1: i64) -> impl Iterator<Item = &Packet> {
888 packets
889 .iter()
890 .filter(move |packet| packet.end >= t0 && packet.start < t1)
891}
892
893fn decode_frame(table: &Table<'_>) -> Result<EventFrame, IoError> {
895 let width = table.i16(slot::FRAME_SIZE_X) as usize;
896 let height = table.i16(slot::FRAME_SIZE_Y) as usize;
897 let format = table.i8(slot::FRAME_FORMAT, FORMAT_GREY);
898 let channels = match format {
899 FORMAT_GREY => 1,
900 FORMAT_BGR => 3,
901 FORMAT_BGRA => 4,
902 other => {
903 return Err(IoError::Unsupported(format!(
904 "AEDAT4 frame format {other} is not an 8-bit image this reader decodes"
905 )))
906 }
907 };
908 let (pixels, _) = table
909 .vector(slot::FRAME_PIXELS, 1)
910 .ok_or_else(|| IoError::Format("AEDAT4 frame has no pixels".to_owned()))?;
911 if pixels.len() != width * height * channels {
912 return Err(IoError::Format(format!(
913 "AEDAT4 frame declares {width}x{height}x{channels} but carries {} bytes",
914 pixels.len()
915 )));
916 }
917 let samples: Vec<u8> = match channels {
919 1 => pixels.to_vec(),
920 _ => pixels
921 .chunks_exact(channels)
922 .map(|bgr| super::luma(&[bgr[2], bgr[1], bgr[0]]))
923 .collect(),
924 };
925 EventFrame::intensity(EventFrameData::U8(samples), width, height)
926 .map_err(|error| IoError::Format(format!("AEDAT4 frame: {error}")))
927}
928
929fn read_data_table(
932 path: &Path,
933 compression: Compression,
934 position: u64,
935 size: u64,
936) -> Result<Vec<(i32, Packet)>, IoError> {
937 let body = read_body(path, compression, position, size as usize)?;
938 let table = Table::size_prefixed_root(&body)
939 .ok_or_else(|| IoError::Format("AEDAT4 data table is not readable".to_owned()))?;
940 Ok(table
941 .tables(slot::TABLE)
942 .iter()
943 .filter_map(|entry| {
944 let info = entry.field(slot::DEFINITION_PACKET_INFO)?;
946 let id = i32::from_le_bytes(scalar(entry.buffer, info)?);
947 let size = i32::from_le_bytes(scalar(entry.buffer, info + 4)?);
948 Some((
949 id,
950 Packet {
951 offset: entry.i64(slot::DEFINITION_BYTE_OFFSET, 0).try_into().ok()?,
952 size: size.try_into().ok()?,
953 first: 0,
954 elements: entry
955 .i64(slot::DEFINITION_NUM_ELEMENTS, 0)
956 .try_into()
957 .ok()?,
958 start: entry.i64(slot::DEFINITION_TIMESTAMP_START, 0),
959 end: entry.i64(slot::DEFINITION_TIMESTAMP_END, 0),
960 },
961 ))
962 })
963 .collect())
964}
965
966fn walk_packets(
969 path: &Path,
970 compression: Compression,
971 body_offset: u64,
972 total: u64,
973) -> Result<Vec<(i32, Packet)>, IoError> {
974 let mut file = File::open(path)?;
975 file.seek(SeekFrom::Start(body_offset))?;
976 let mut packets = Vec::new();
977 let mut offset = body_offset;
978 let mut header = [0u8; PACKET_HEADER];
979 while offset + PACKET_HEADER as u64 <= total {
980 file.read_exact(&mut header)?;
981 let id = i32::from_le_bytes(header[0..4].try_into().expect("four bytes"));
982 let size = i32::from_le_bytes(header[4..8].try_into().expect("four bytes"));
983 let Ok(size) = usize::try_from(size) else {
984 break;
985 };
986 offset += PACKET_HEADER as u64;
987 if offset + size as u64 > total {
988 break; }
990 let mut packet = Packet {
991 offset,
992 size,
993 first: 0,
994 elements: 0,
995 start: 0,
996 end: 0,
997 };
998 if let Ok(body) = read_body(path, compression, offset, size) {
999 (packet.elements, packet.start, packet.end) = span_of(&body);
1000 }
1001 packets.push((id, packet));
1002 offset += size as u64;
1003 file.seek(SeekFrom::Start(offset))?;
1004 }
1005 Ok(packets)
1006}
1007
1008fn span_of(body: &[u8]) -> (usize, i64, i64) {
1011 let Some(table) = Table::size_prefixed_root(body) else {
1012 return (0, 0, 0);
1013 };
1014 if let Some((bytes, elements)) = table.vector(slot::ELEMENTS, EVENT_STRIDE) {
1015 if elements > 0 && bytes.len() >= EVENT_STRIDE {
1016 let at = |chunk: &[u8]| i64::from_le_bytes(chunk[0..8].try_into().expect("eight bytes"));
1017 return (
1018 elements,
1019 at(&bytes[..EVENT_STRIDE]),
1020 at(&bytes[bytes.len() - EVENT_STRIDE..]),
1021 );
1022 }
1023 }
1024 let elements = table.tables(slot::ELEMENTS);
1025 if let (Some(first), Some(last)) = (elements.first(), elements.last()) {
1026 return (
1027 elements.len(),
1028 first.i64(slot::IMU_TIMESTAMP, 0),
1029 last.i64(slot::IMU_TIMESTAMP, 0),
1030 );
1031 }
1032 let t = table.i64(slot::FRAME_TIMESTAMP, 0);
1033 (1, t, t)
1034}
1035
1036pub fn open_aedat4_slice(
1038 path: impl AsRef<Path>,
1039 options: &LoadOptions,
1040) -> Result<Aedat4SliceSource, IoError> {
1041 Aedat4SliceSource::open(path.as_ref(), options)
1042}
1043
1044pub fn read_aedat4(path: impl AsRef<Path>, options: &LoadOptions) -> Result<EventStream, IoError> {
1047 let source = open_aedat4_slice(path, options)?;
1048 let limit = options.max_events.unwrap_or(source.n_events());
1049 source.slice_index(0, limit)
1050}
1051
1052#[cfg(test)]
1053mod tests {
1054 use super::*;
1055
1056 const SAMPLE: &str = concat!(
1060 env!("CARGO_MANIFEST_DIR"),
1061 "/../../data/test/sample.aedat4"
1062 );
1063 const SAMPLE_ZSTD: &str = concat!(
1064 env!("CARGO_MANIFEST_DIR"),
1065 "/../../data/test/sample_zstd.aedat4"
1066 );
1067
1068 fn sample() -> Aedat4SliceSource {
1069 open_aedat4_slice(SAMPLE, &LoadOptions::default()).unwrap()
1070 }
1071
1072 #[test]
1073 fn reads_a_recording_written_by_dv() {
1074 let source = sample();
1075 assert_eq!(source.sensor_size(), (64, 48)); assert_eq!(source.n_events(), 64);
1077 assert_eq!(source.time_span(), (1_000_000, 1_063_000));
1078
1079 let stream = source.slice_index(0, source.n_events()).unwrap();
1080 assert_eq!(
1081 (stream.xs()[0], stream.ys()[0], stream.ts()[0], stream.ps()[0]),
1082 (0, 0, 1_000_000, false)
1083 );
1084 assert_eq!(
1085 (
1086 stream.xs()[63],
1087 stream.ys()[63],
1088 stream.ts()[63],
1089 stream.ps()[63]
1090 ),
1091 (63, 45, 1_063_000, true)
1092 );
1093 }
1094
1095 #[test]
1096 fn slices_agree_with_a_whole_file_read() {
1097 let source = sample();
1098 let whole = source.slice_index(0, source.n_events()).unwrap();
1099 let head = source.slice_index(0, 10).unwrap();
1100 assert_eq!(head.ts(), &whole.ts()[..10]);
1101 let window = source.slice_time(1_010_000, 1_015_000).unwrap();
1102 assert_eq!(window.ts(), &[1_010_000, 1_011_000, 1_012_000, 1_013_000, 1_014_000]);
1103 }
1104
1105 #[test]
1106 fn frames_and_imu_come_back_beside_the_events() {
1107 let source = sample();
1108 let frames = source.frames(0, i64::MAX).unwrap();
1109 assert_eq!(frames.len(), 2);
1110 assert_eq!(frames[0].0, 1_000_000);
1111 assert_eq!(frames[0].1.shape(), (1, 48, 64));
1112 let EventFrameData::U8(pixels) = frames[0].1.data() else {
1113 panic!("an 8-bit APS frame");
1114 };
1115 assert_eq!((pixels[0], pixels[pixels.len() - 1]), (0, 59));
1117
1118 let samples = source.imu(0, i64::MAX).unwrap();
1119 assert_eq!(samples.len(), 8);
1120 assert_eq!(samples[0].t_us, 1_000_000);
1121 assert!((samples[0].linear_acceleration[1] + STANDARD_GRAVITY).abs() < 1e-6);
1123 assert_eq!(samples[0].angular_velocity, [0.0; 3]);
1124
1125 assert_eq!(source.frames(1_020_000, i64::MAX).unwrap().len(), 1);
1127 assert_eq!(source.imu(0, 1_010_000).unwrap().len(), 2);
1128 }
1129
1130 #[test]
1131 fn compression_does_not_change_what_is_read() {
1132 let lz4 = sample();
1135 let zstd = open_aedat4_slice(SAMPLE_ZSTD, &LoadOptions::default()).unwrap();
1136 assert_eq!(zstd.sensor_size(), lz4.sensor_size());
1137 assert_eq!(zstd.n_events(), lz4.n_events());
1138 assert_eq!(zstd.time_span(), lz4.time_span());
1139
1140 let (a, b) = (
1141 lz4.slice_index(0, lz4.n_events()).unwrap(),
1142 zstd.slice_index(0, zstd.n_events()).unwrap(),
1143 );
1144 assert_eq!((a.xs(), a.ys(), a.ts(), a.ps()), (b.xs(), b.ys(), b.ts(), b.ps()));
1145 assert_eq!(
1146 lz4.frames(0, i64::MAX).unwrap()[0].1.data(),
1147 zstd.frames(0, i64::MAX).unwrap()[0].1.data()
1148 );
1149 assert_eq!(lz4.imu(0, i64::MAX).unwrap(), zstd.imu(0, i64::MAX).unwrap());
1150 }
1151
1152 fn minimal_file(events: &[(i64, i16, i16, bool)]) -> Vec<u8> {
1156 minimal_file_packets(&[events.to_vec()])
1157 }
1158
1159
1160 #[test]
1161 fn a_multi_packet_file_decodes_the_same_in_parallel_as_in_sequence() {
1162 let packets: Vec<Vec<(i64, i16, i16, bool)>> = (0..PARALLEL_PACKET_THRESHOLD + 4)
1168 .map(|packet| {
1169 (0..5)
1170 .map(|index| {
1171 let n = (packet * 5 + index) as i64;
1172 (1_000 + n, (n % 8) as i16, (n % 4) as i16, n % 2 == 0)
1173 })
1174 .collect()
1175 })
1176 .collect();
1177 let total: usize = packets.iter().map(Vec::len).sum();
1178 let path = write_temporary(&minimal_file_packets(&packets), "multi_packet");
1179 let source = open_aedat4_slice(&path, &LoadOptions::default()).unwrap();
1180 assert_eq!(source.n_events(), total);
1181
1182 let whole = source.slice_index(0, total).unwrap();
1183 assert_eq!(whole.len(), total, "no event may be lost across packets");
1184 let expected: Vec<(i64, u16, u16, bool)> = packets
1186 .iter()
1187 .flatten()
1188 .map(|&(t, x, y, p)| (t, x as u16, y as u16, p))
1189 .collect();
1190 for (index, &(t, x, y, p)) in expected.iter().enumerate() {
1191 assert_eq!(
1192 (whole.ts()[index], whole.xs()[index], whole.ys()[index], whole.ps()[index]),
1193 (t, x, y, p),
1194 "event {index}"
1195 );
1196 }
1197
1198 let part = source.slice_index(7, total - 3).unwrap();
1201 assert_eq!(part.len(), total - 10);
1202 assert_eq!(part.ts()[0], expected[7].0);
1203 assert_eq!(part.xs()[0], expected[7].1);
1204 assert_eq!(*part.ts().last().unwrap(), expected[total - 4].0);
1205 std::fs::remove_file(path).ok();
1206 }
1207
1208 #[test]
1209 fn off_sensor_events_are_dropped_without_disturbing_the_rest() {
1210 let packets: Vec<Vec<(i64, i16, i16, bool)>> = (0..PARALLEL_PACKET_THRESHOLD + 4)
1214 .map(|packet| {
1215 let base = (packet * 3) as i64;
1216 vec![
1217 (1_000 + base, (packet % 8) as i16, 1, true),
1218 (1_001 + base, 100, 1, false), (1_002 + base, (packet % 8) as i16, 2, false),
1220 ]
1221 })
1222 .collect();
1223 let indexed: usize = packets.iter().map(Vec::len).sum();
1224 let path = write_temporary(&minimal_file_packets(&packets), "multi_packet_dropped");
1225 let source = open_aedat4_slice(&path, &LoadOptions::default()).unwrap();
1226 assert_eq!(source.n_events(), indexed, "the index counts every element");
1227
1228 let whole = source.slice_index(0, indexed).unwrap();
1229 let expected: Vec<(i64, u16, u16)> = packets
1230 .iter()
1231 .flatten()
1232 .filter(|&&(_, x, _, _)| x < 8)
1233 .map(|&(t, x, y, _)| (t, x as u16, y as u16))
1234 .collect();
1235 assert_eq!(whole.len(), expected.len(), "two survivors a packet");
1236 for (index, &(t, x, y)) in expected.iter().enumerate() {
1237 assert_eq!(
1238 (whole.ts()[index], whole.xs()[index], whole.ys()[index]),
1239 (t, x, y),
1240 "event {index} after compaction"
1241 );
1242 }
1243 assert!(
1244 whole.ts().windows(2).all(|w| w[0] <= w[1]),
1245 "compaction must not reorder"
1246 );
1247 std::fs::remove_file(path).ok();
1248 }
1249
1250 fn minimal_file_packets(packets: &[Vec<(i64, i16, i16, bool)>]) -> Vec<u8> {
1253 let info = "<node name=\"0\" path=\"/outInfo/0/\">\
1254 <attr key=\"typeIdentifier\" type=\"string\">EVTS</attr>\
1255 <node name=\"info\"><attr key=\"sizeX\" type=\"int\">8</attr>\
1256 <attr key=\"sizeY\" type=\"int\">4</attr></node></node>";
1257
1258 let mut header = Vec::new();
1260 header.extend_from_slice(&14u32.to_le_bytes()); header.extend_from_slice(&10u16.to_le_bytes()); header.extend_from_slice(&20u16.to_le_bytes()); for offset in [4u16, 8, 16] {
1264 header.extend_from_slice(&offset.to_le_bytes());
1265 }
1266 header.extend_from_slice(&10i32.to_le_bytes()); header.extend_from_slice(&0i32.to_le_bytes()); header.extend_from_slice(&(-1i64).to_le_bytes()); header.extend_from_slice(&4u32.to_le_bytes()); header.extend_from_slice(&(info.len() as u32).to_le_bytes());
1271 header.extend_from_slice(info.as_bytes());
1272 header.push(0); let build = |events: &[(i64, i16, i16, bool)]| {
1276 let mut packet = Vec::new();
1277 packet.extend_from_slice(&10u32.to_le_bytes()); packet.extend_from_slice(&6u16.to_le_bytes()); packet.extend_from_slice(&8u16.to_le_bytes()); packet.extend_from_slice(&4u16.to_le_bytes()); packet.extend_from_slice(&6i32.to_le_bytes()); packet.extend_from_slice(&4u32.to_le_bytes()); packet.extend_from_slice(&(events.len() as u32).to_le_bytes());
1284 for &(t, x, y, polarity) in events {
1285 packet.extend_from_slice(&t.to_le_bytes());
1286 packet.extend_from_slice(&x.to_le_bytes());
1287 packet.extend_from_slice(&y.to_le_bytes());
1288 packet.push(polarity as u8);
1289 packet.extend_from_slice(&[0; 3]); }
1291 packet
1292 };
1293
1294 let mut file = MAGIC.to_vec();
1295 file.extend_from_slice(&(header.len() as u32).to_le_bytes());
1296 file.extend_from_slice(&header);
1297 for events in packets {
1299 let packet = build(events);
1300 file.extend_from_slice(&0i32.to_le_bytes()); file.extend_from_slice(&((packet.len() + 4) as i32).to_le_bytes()); file.extend_from_slice(&(packet.len() as u32).to_le_bytes()); file.extend_from_slice(&packet);
1304 }
1305 file
1306 }
1307
1308 fn write_temporary(data: &[u8], tag: &str) -> PathBuf {
1309 use std::io::Write;
1310 let path = std::env::temp_dir().join(format!(
1311 "eventcv_aedat4_{}_{tag}.aedat4",
1312 std::process::id()
1313 ));
1314 File::create(&path).unwrap().write_all(data).unwrap();
1315 path
1316 }
1317
1318 #[test]
1319 fn a_file_without_a_data_table_is_indexed_by_walking_its_packets() {
1320 let events = [(10i64, 1i16, 2i16, true), (20, 3, 0, false), (30, 7, 3, true)];
1321 let path = write_temporary(&minimal_file(&events), "walk");
1322 let source = open_aedat4_slice(&path, &LoadOptions::default()).unwrap();
1323
1324 assert_eq!(source.sensor_size(), (8, 4));
1325 assert_eq!(source.n_events(), 3);
1326 assert_eq!(source.time_span(), (10, 30));
1327 let stream = source.slice_index(0, 3).unwrap();
1328 assert_eq!(stream.ts(), &[10, 20, 30]);
1329 assert_eq!(stream.xs(), &[1, 3, 7]);
1330 assert_eq!(stream.ps(), &[true, false, true]);
1331 std::fs::remove_file(&path).ok();
1332 }
1333
1334 #[test]
1335 fn a_truncated_file_errors_rather_than_reading_past_its_end() {
1336 let full = minimal_file(&[(10, 1, 2, true), (20, 3, 0, false)]);
1337 for cut in [4, MAGIC.len(), MAGIC.len() + 8, full.len() - 40, full.len() - 8] {
1339 let path = write_temporary(&full[..cut], "cut");
1340 match open_aedat4_slice(&path, &LoadOptions::default()) {
1341 Err(_) => {}
1342 Ok(source) => {
1343 let _ = source.slice_index(0, source.n_events()).unwrap();
1345 }
1346 }
1347 std::fs::remove_file(&path).ok();
1348 }
1349 }
1350
1351 #[test]
1352 fn a_file_that_is_not_aedat4_is_a_format_error() {
1353 let path = write_temporary(b"#!AER-DAT2.0\r\nrubbish", "wrong");
1354 match open_aedat4_slice(&path, &LoadOptions::default()) {
1355 Err(IoError::Format(message)) => assert!(message.contains("AEDAT 4")),
1356 other => panic!("expected a format error, got {:?}", other.map(|_| ())),
1357 }
1358 std::fs::remove_file(&path).ok();
1359 }
1360
1361 #[test]
1362 fn the_header_xml_is_scanned_for_stream_types_and_resolution() {
1363 let info = "<node name=\"outInfo\"><node name=\"0\">\
1364 <attr key=\"typeIdentifier\" type=\"string\">EVTS</attr>\
1365 <node name=\"info\"><attr key=\"sizeX\" type=\"int\">640</attr>\
1366 <attr key=\"sizeY\" type=\"int\">480</attr></node></node>\
1367 <node name=\"1\"><attr key=\"typeIdentifier\" type=\"string\">FRME</attr></node>\
1368 <node name=\"2\"><attr key=\"typeIdentifier\" type=\"string\">TRIG</attr></node>\
1369 </node>";
1370 let streams = parse_streams(info);
1371 assert_eq!(streams.len(), 3); assert_eq!(streams[0].id, 0);
1373 assert_eq!(streams[0].kind, Kind::Events);
1374 assert_eq!(streams[0].size, Some((640, 480)));
1375 assert_eq!(streams[1].kind, Kind::Frames);
1376 assert_eq!(streams[1].size, None);
1377 assert_eq!(streams[2].kind, Kind::Other); }
1379
1380 #[test]
1381 fn unknown_compression_is_rejected_by_name() {
1382 assert_eq!(Compression::from_header(0).unwrap(), Compression::None);
1383 assert_eq!(Compression::from_header(2).unwrap(), Compression::Lz4);
1384 assert_eq!(Compression::from_header(4).unwrap(), Compression::Zstd);
1385 match Compression::from_header(9) {
1386 Err(IoError::Unsupported(message)) => assert!(message.contains("compression")),
1387 other => panic!("expected an unsupported-compression error, got {other:?}"),
1388 }
1389 }
1390
1391 #[test]
1392 fn table_reads_are_bounds_checked() {
1393 assert!(Table::root(&[0xFF, 0xFF, 0xFF, 0x7F]).is_none());
1396 let table = Table::root(&[4, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0x7F]).unwrap();
1397 assert!(table.field(slot::ELEMENTS).is_none());
1398 assert_eq!(table.i64(slot::ELEMENTS, -7), -7); }
1400}
1401
1402const IDENT_HEADER: &[u8; 4] = b"IOHE";
1409const IDENT_EVENTS: &[u8; 4] = b"EVTS";
1410const IDENT_TABLE: &[u8; 4] = b"FTAB";
1411
1412const EVENT_STREAM_ID: i32 = 0;
1415
1416const PACKET_EVENTS: usize = 100_000;
1420
1421struct FlatBuilder {
1432 rev: Vec<u8>,
1434 min_align: usize,
1436}
1437
1438impl FlatBuilder {
1439 fn new() -> Self {
1440 Self {
1441 rev: Vec::new(),
1442 min_align: 4,
1443 }
1444 }
1445
1446 fn len(&self) -> usize {
1447 self.rev.len()
1448 }
1449
1450 fn pre_align(&mut self, size: usize, align: usize) {
1452 self.min_align = self.min_align.max(align);
1453 let pad = (align - ((self.len() + size) % align)) % align;
1454 self.rev.resize(self.len() + pad, 0);
1455 }
1456
1457 fn push(&mut self, bytes: &[u8]) {
1459 self.rev.extend(bytes.iter().rev());
1460 }
1461
1462 fn push_scalar(&mut self, bytes: &[u8]) -> usize {
1464 self.pre_align(bytes.len(), bytes.len());
1465 self.push(bytes);
1466 self.len()
1467 }
1468
1469 fn push_uoffset(&mut self, target: usize) -> usize {
1471 self.pre_align(4, 4);
1472 let value = (self.len() + 4 - target) as u32;
1473 self.push(&value.to_le_bytes());
1474 self.len()
1475 }
1476
1477 fn push_string(&mut self, value: &str) -> usize {
1479 self.pre_align(value.len() + 1, 4);
1480 self.push(&[0]);
1481 self.push(value.as_bytes());
1482 self.push_scalar(&(value.len() as u32).to_le_bytes())
1483 }
1484
1485 fn push_struct_vector(&mut self, data: &[u8], count: usize, align: usize) -> usize {
1488 self.pre_align(data.len(), align);
1489 self.push(data);
1490 self.push_scalar(&(count as u32).to_le_bytes())
1491 }
1492
1493 fn end_table(&mut self, start: usize, fields: &[(usize, usize)]) -> usize {
1499 self.pre_align(4, 4);
1500 self.push(&0i32.to_le_bytes()); let table = self.len();
1502
1503 let slots = fields.iter().map(|(slot, _)| *slot).max().unwrap_or(2);
1504 let entries = (slots.saturating_sub(4)) / 2 + 1;
1505 let mut vtable = vec![0u16; 2 + entries];
1506 vtable[0] = (vtable.len() * 2) as u16;
1507 vtable[1] = (table - start) as u16;
1508 for (slot, offset) in fields {
1509 vtable[2 + (slot - 4) / 2] = (table - offset) as u16;
1510 }
1511 self.pre_align(vtable.len() * 2, 2);
1512 for value in vtable.iter().rev() {
1513 self.push(&value.to_le_bytes());
1514 }
1515 let vtable_at = self.len();
1516
1517 let soffset = (vtable_at - table) as i32;
1519 let bytes = soffset.to_le_bytes();
1520 self.rev[table - 4..table].copy_from_slice(&[bytes[3], bytes[2], bytes[1], bytes[0]]);
1521 table
1522 }
1523
1524 fn finish(mut self, root: usize, identifier: &[u8; 4], size_prefixed: bool) -> Vec<u8> {
1527 let prefix = 4 + 4 + usize::from(size_prefixed) * 4;
1528 self.pre_align(prefix, self.min_align);
1529 self.push(identifier);
1530 self.push_uoffset(root);
1531 if size_prefixed {
1532 let size = self.len() as u32;
1533 self.push(&size.to_le_bytes());
1534 }
1535 self.rev.reverse();
1536 self.rev
1537 }
1538}
1539
1540fn info_node(width: usize, height: usize, compression: Compression) -> String {
1543 let compression = match compression {
1544 Compression::None => "NONE",
1545 Compression::Lz4 => "LZ4",
1546 Compression::Zstd => "ZSTD",
1547 };
1548 format!(
1549 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n\
1550 <dv version=\"2.0\">\n\
1551 \x20 <node name=\"outInfo\" path=\"/outInfo/\">\n\
1552 \x20 <node name=\"{EVENT_STREAM_ID}\" path=\"/outInfo/{EVENT_STREAM_ID}/\">\n\
1553 \x20 <attr key=\"compression\" type=\"string\">{compression}</attr>\n\
1554 \x20 <attr key=\"originalModuleName\" type=\"string\">eventcv</attr>\n\
1555 \x20 <attr key=\"originalOutputName\" type=\"string\">events</attr>\n\
1556 \x20 <attr key=\"typeDescription\" type=\"string\">Event data.</attr>\n\
1557 \x20 <attr key=\"typeIdentifier\" type=\"string\">EVTS</attr>\n\
1558 \x20 <node name=\"info\" path=\"/outInfo/{EVENT_STREAM_ID}/info/\">\n\
1559 \x20 <attr key=\"sizeX\" type=\"int\">{width}</attr>\n\
1560 \x20 <attr key=\"sizeY\" type=\"int\">{height}</attr>\n\
1561 \x20 <attr key=\"source\" type=\"string\">eventcv</attr>\n\
1562 \x20 </node>\n\
1563 \x20 </node>\n\
1564 \x20 </node>\n\
1565 </dv>\n"
1566 )
1567}
1568
1569fn build_header(width: usize, height: usize, compression: Compression) -> (Vec<u8>, usize) {
1572 let mut builder = FlatBuilder::new();
1573 let info = builder.push_string(&info_node(width, height, compression));
1574 let start = builder.len();
1575 let compression_at = builder.push_scalar(&compression.to_header().to_le_bytes());
1576 let position_at = builder.push_scalar(&(-1i64).to_le_bytes());
1579 let info_at = builder.push_uoffset(info);
1580 let root = builder.end_table(
1581 start,
1582 &[
1583 (slot::COMPRESSION, compression_at),
1584 (slot::DATA_TABLE_POSITION, position_at),
1585 (slot::INFO_NODE, info_at),
1586 ],
1587 );
1588 let buffer = builder.finish(root, IDENT_HEADER, false);
1589 let field = buffer.len() - position_at;
1591 (buffer, field)
1592}
1593
1594#[derive(Clone, Copy, Debug)]
1596struct Definition {
1597 byte_offset: i64,
1598 size: i32,
1599 elements: i64,
1600 start: i64,
1601 end: i64,
1602}
1603
1604fn build_data_table(definitions: &[Definition]) -> Vec<u8> {
1607 let mut builder = FlatBuilder::new();
1608 let mut offsets = Vec::with_capacity(definitions.len());
1611 for definition in definitions.iter().rev() {
1612 let start = builder.len();
1613 let byte_offset = builder.push_scalar(&definition.byte_offset.to_le_bytes());
1614 let elements = builder.push_scalar(&definition.elements.to_le_bytes());
1615 let start_at = builder.push_scalar(&definition.start.to_le_bytes());
1616 let end_at = builder.push_scalar(&definition.end.to_le_bytes());
1617 builder.pre_align(8, 4);
1619 builder.push(&definition.size.to_le_bytes());
1620 builder.push(&EVENT_STREAM_ID.to_le_bytes());
1621 let info = builder.len();
1622 offsets.push(builder.end_table(
1623 start,
1624 &[
1625 (slot::DEFINITION_BYTE_OFFSET, byte_offset),
1626 (slot::DEFINITION_PACKET_INFO, info),
1627 (slot::DEFINITION_NUM_ELEMENTS, elements),
1628 (slot::DEFINITION_TIMESTAMP_START, start_at),
1629 (slot::DEFINITION_TIMESTAMP_END, end_at),
1630 ],
1631 ));
1632 }
1633 offsets.reverse();
1634
1635 builder.pre_align(offsets.len() * 4, 4);
1636 let mut vector_at = builder.len();
1637 for offset in offsets.iter().rev() {
1638 vector_at = builder.push_uoffset(*offset);
1639 }
1640 let _ = vector_at;
1641 let vector = builder.push_scalar(&(offsets.len() as u32).to_le_bytes());
1642
1643 let start = builder.len();
1644 let table_at = builder.push_uoffset(vector);
1645 let root = builder.end_table(start, &[(slot::TABLE, table_at)]);
1646 builder.finish(root, IDENT_TABLE, true)
1647}
1648
1649fn build_event_packet(stream: &EventStream, range: std::ops::Range<usize>) -> Vec<u8> {
1651 let (xs, ys, ts, ps) = (stream.xs(), stream.ys(), stream.ts(), stream.ps());
1652 let mut elements = Vec::with_capacity(range.len() * EVENT_STRIDE);
1653 for index in range.clone() {
1654 elements.extend_from_slice(&ts[index].to_le_bytes());
1655 elements.extend_from_slice(&(xs[index] as i16).to_le_bytes());
1656 elements.extend_from_slice(&(ys[index] as i16).to_le_bytes());
1657 elements.push(u8::from(ps[index]));
1658 elements.extend_from_slice(&[0; 3]); }
1660 let mut builder = FlatBuilder::new();
1661 let vector = builder.push_struct_vector(&elements, range.len(), 8);
1662 let start = builder.len();
1663 let elements_at = builder.push_uoffset(vector);
1664 let root = builder.end_table(start, &[(slot::ELEMENTS, elements_at)]);
1665 builder.finish(root, IDENT_EVENTS, true)
1666}
1667
1668pub struct Aedat4EventSink {
1680 file: File,
1681 compression: Compression,
1682 position_field: Option<u64>,
1684 definitions: Vec<Definition>,
1685 n_events: usize,
1686}
1687
1688impl Aedat4EventSink {
1689 pub fn create(path: impl AsRef<Path>, compression: Compression) -> Result<Self, IoError> {
1690 Ok(Self {
1691 file: File::create(path.as_ref())?,
1692 compression,
1693 position_field: None,
1694 definitions: Vec::new(),
1695 n_events: 0,
1696 })
1697 }
1698
1699 fn write_packet(&mut self, stream: &EventStream, range: std::ops::Range<usize>) -> Result<(), IoError> {
1701 let ts = stream.ts();
1702 let body = self
1703 .compression
1704 .encode(&build_event_packet(stream, range.clone()))?;
1705 let size = i32::try_from(body.len()).map_err(|_| {
1706 IoError::Unsupported("an AEDAT4 packet body exceeds 2 GB".to_owned())
1707 })?;
1708 self.file.write_all(&EVENT_STREAM_ID.to_le_bytes())?;
1709 self.file.write_all(&size.to_le_bytes())?;
1710 let byte_offset = self.file.stream_position()?;
1711 self.file.write_all(&body)?;
1712 self.definitions.push(Definition {
1713 byte_offset: byte_offset as i64,
1714 size,
1715 elements: range.len() as i64,
1716 start: ts[range.start],
1717 end: ts[range.end - 1],
1718 });
1719 Ok(())
1720 }
1721}
1722
1723impl super::EventSink for Aedat4EventSink {
1724 fn append(&mut self, stream: &EventStream) -> Result<(), IoError> {
1725 if stream.is_empty() {
1726 return Ok(());
1727 }
1728 if self.position_field.is_none() {
1729 let (width, height) = stream.sensor_size();
1730 let (header, field) = build_header(width, height, self.compression);
1731 self.file.write_all(MAGIC)?;
1732 self.file.write_all(&(header.len() as u32).to_le_bytes())?;
1733 self.file.write_all(&header)?;
1734 self.position_field = Some((MAGIC.len() + 4 + field) as u64);
1735 }
1736 let mut start = 0;
1737 while start < stream.len() {
1738 let end = (start + PACKET_EVENTS).min(stream.len());
1739 self.write_packet(stream, start..end)?;
1740 start = end;
1741 }
1742 self.n_events += stream.len();
1743 Ok(())
1744 }
1745
1746 fn n_events(&self) -> usize {
1747 self.n_events
1748 }
1749
1750 fn flush(&mut self) -> Result<(), IoError> {
1751 self.file.flush().map_err(IoError::Io)
1752 }
1753
1754 fn finish(mut self: Box<Self>) -> Result<(), IoError> {
1755 let Some(field) = self.position_field else {
1756 let (header, _) = build_header(1, 1, self.compression);
1760 self.file.write_all(MAGIC)?;
1761 self.file.write_all(&(header.len() as u32).to_le_bytes())?;
1762 self.file.write_all(&header)?;
1763 return self.file.flush().map_err(IoError::Io);
1764 };
1765 let position = self.file.stream_position()?;
1766 let table = self.compression.encode(&build_data_table(&self.definitions))?;
1767 self.file.write_all(&table)?;
1768 self.file.seek(SeekFrom::Start(field))?;
1769 self.file.write_all(&(position as i64).to_le_bytes())?;
1770 self.file.flush().map_err(IoError::Io)
1771 }
1772}