1use std::collections::HashMap;
2use std::fs::File;
3use std::io::{BufReader, BufWriter, Write};
4use std::path::{Path, PathBuf};
5use std::sync::Mutex;
6
7use rosbag::{ChunkRecord, IndexRecord, MessageRecord, RosBag};
8
9use super::{IoError, LoadOptions, SliceSource};
10use crate::representation::{EventFrame, EventFrameData};
11use crate::{EventStream, EventStreamBuilder};
12
13const DEFAULT_TOPIC: &str = "/davis/left/events";
14const EVENT_ARRAY_TYPE: &str = "dvs_msgs/EventArray";
15const EVENT_ARRAY_MD5: &str = "5e8beee5759d85e9f5a8c1f5ad1cd00b";
18const EVENT_ARRAY_DEF: &str =
20 "std_msgs/Header header\nuint32 height\nuint32 width\ndvs_msgs/Event[] events\n";
21const MESSAGE_EVENTS: usize = 1_000_000;
23const BAG_MAGIC: &[u8] = b"#ROSBAG V2.0\n";
25
26pub fn read_bag(path: impl AsRef<Path>, options: &LoadOptions) -> Result<EventStream, IoError> {
30 let topic = options.topic.as_deref().unwrap_or(DEFAULT_TOPIC);
31 let bag = RosBag::new(path).map_err(IoError::Io)?;
32
33 let mut wanted: HashMap<u32, bool> = HashMap::new();
34 let mut builder: Option<EventStreamBuilder> = None;
35
36 'outer: for record in bag.chunk_records() {
37 let ChunkRecord::Chunk(chunk) = record.map_err(map_bag_error)? else {
38 continue;
39 };
40 for message in chunk.messages() {
41 match message.map_err(map_bag_error)? {
42 MessageRecord::Connection(connection) => {
43 let matches = connection.topic == topic && connection.tp == EVENT_ARRAY_TYPE;
44 wanted.insert(connection.id, matches);
45 }
46 MessageRecord::MessageData(data) => {
47 if !wanted.get(&data.conn_id).copied().unwrap_or(false) {
48 continue;
49 }
50 let (width, height) = match options.sensor_size {
51 Some(size) => size,
52 None => read_event_array_header(data.data)?,
53 };
54 let builder = builder
55 .get_or_insert_with(|| EventStreamBuilder::new(width, height, 0.001));
56 let stopped = decode_event_array(data.data, &mut |x, y, t, p| {
57 builder.push(x, y, t, p);
58 options.max_events.is_some_and(|max| builder.len() >= max)
59 })?;
60 if stopped {
61 break 'outer;
62 }
63 }
64 }
65 }
66 }
67
68 builder.map(EventStreamBuilder::build).ok_or_else(|| {
69 IoError::Format(format!(
70 "no {EVENT_ARRAY_TYPE} messages found on topic {topic}"
71 ))
72 })
73}
74
75fn read_event_array_header(bytes: &[u8]) -> Result<(usize, usize), IoError> {
78 let mut reader = ByteReader::new(bytes);
79 reader.skip(4 + 8)?; let frame_id_len = reader.u32()? as usize;
81 reader.skip(frame_id_len)?; let height = reader.u32()? as usize;
83 let width = reader.u32()? as usize;
84 Ok((width, height))
85}
86
87fn decode_event_array(
91 bytes: &[u8],
92 on_event: &mut dyn FnMut(u16, u16, i64, bool) -> bool,
93) -> Result<bool, IoError> {
94 let mut reader = ByteReader::new(bytes);
95 reader.skip(4 + 8)?;
96 let frame_id_len = reader.u32()? as usize;
97 reader.skip(frame_id_len)?;
98 reader.skip(4 + 4)?; let count = reader.u32()? as usize;
100 for _ in 0..count {
101 let x = reader.u16()?;
102 let y = reader.u16()?;
103 let seconds = i64::from(reader.u32()?);
104 let nanoseconds = i64::from(reader.u32()?);
105 let polarity = reader.u8()? != 0;
106 if on_event(x, y, seconds * 1_000_000 + nanoseconds / 1000, polarity) {
107 return Ok(true);
108 }
109 }
110 Ok(false)
111}
112
113pub fn write_bag(
119 path: impl AsRef<Path>,
120 stream: &EventStream,
121 topic: Option<&str>,
122) -> Result<(), IoError> {
123 let topic = topic.unwrap_or(DEFAULT_TOPIC);
124 let conn_id: u32 = 0;
125 let ts = stream.ts();
126
127 let mut payload: Vec<u8> =
130 make_record(&connection_fields(conn_id, topic), &connection_data(topic));
131 let mut index_entries: Vec<(i64, u32)> = Vec::new();
132 let mut start = 0;
133 while start < stream.len() {
134 let end = (start + MESSAGE_EVENTS).min(stream.len());
135 let offset = payload.len() as u32;
136 let message = serialize_event_array(stream, start, end);
137 write_record(&mut payload, &message_fields(conn_id, ts[start]), &message)
138 .expect("writing to a Vec never fails");
139 index_entries.push((ts[start], offset));
140 start = end;
141 }
142 if stream.is_empty() {
143 let offset = payload.len() as u32;
146 let message = serialize_event_array(stream, 0, 0);
147 write_record(&mut payload, &message_fields(conn_id, 0), &message)
148 .expect("writing to a Vec never fails");
149 index_entries.push((0, offset));
150 }
151 let message_count = index_entries.len() as u32;
152 let (start_us, end_us) = match (ts.iter().min(), ts.iter().max()) {
153 (Some(&lo), Some(&hi)) => (lo, hi),
154 _ => (0, 0),
155 };
156
157 let bag_header_len = make_record(&bag_header_fields(0, 1, 1), &[]).len();
160 let chunk_pos = BAG_MAGIC.len() + bag_header_len;
161 let chunk_fields = chunk_fields(payload.len() as u32);
162 let mut index_data: Vec<u8> = Vec::with_capacity(index_entries.len() * 12);
163 for (time_us, offset) in &index_entries {
164 index_data.extend_from_slice(&ros_time(*time_us));
165 index_data.extend_from_slice(&offset.to_le_bytes());
166 }
167 let index_record = make_record(&index_data_fields(conn_id, message_count), &index_data);
168 let index_pos = chunk_pos + record_len(&chunk_fields, payload.len()) + index_record.len();
169
170 let mut writer = BufWriter::new(File::create(path).map_err(IoError::Io)?);
171 writer.write_all(BAG_MAGIC).map_err(IoError::Io)?;
172 write_record(&mut writer, &bag_header_fields(index_pos as u64, 1, 1), &[])
173 .map_err(IoError::Io)?;
174 write_record(&mut writer, &chunk_fields, &payload).map_err(IoError::Io)?;
175 writer.write_all(&index_record).map_err(IoError::Io)?;
176 write_record(
178 &mut writer,
179 &connection_fields(conn_id, topic),
180 &connection_data(topic),
181 )
182 .map_err(IoError::Io)?;
183 let chunk_info_data = {
184 let mut data = Vec::with_capacity(8);
185 data.extend_from_slice(&conn_id.to_le_bytes());
186 data.extend_from_slice(&message_count.to_le_bytes());
187 data
188 };
189 write_record(
190 &mut writer,
191 &chunk_info_fields(chunk_pos as u64, start_us, end_us),
192 &chunk_info_data,
193 )
194 .map_err(IoError::Io)?;
195 writer.flush().map_err(IoError::Io)
196}
197
198pub struct BagEventSink {
209 path: PathBuf,
210 scratch: PathBuf,
211 payload: Option<BufWriter<File>>,
214 payload_len: u64,
215 topic: String,
216 index_entries: Vec<(i64, u32)>,
218 sensor_size: Option<(usize, usize)>,
220 span: Option<(i64, i64)>,
221 n_events: usize,
222}
223
224const CONN_ID: u32 = 0;
226
227impl BagEventSink {
228 pub fn create(path: impl AsRef<Path>, topic: Option<&str>) -> Result<Self, IoError> {
229 let path = path.as_ref().to_path_buf();
230 let scratch = path.with_extension("chunk.part");
231 let topic = topic.unwrap_or(DEFAULT_TOPIC).to_owned();
232 let mut payload = BufWriter::new(File::create(&scratch).map_err(IoError::Io)?);
233 let connection = make_record(&connection_fields(CONN_ID, &topic), &connection_data(&topic));
235 payload.write_all(&connection).map_err(IoError::Io)?;
236 Ok(Self {
237 path,
238 scratch,
239 payload: Some(payload),
240 payload_len: connection.len() as u64,
241 topic,
242 index_entries: Vec::new(),
243 sensor_size: None,
244 span: None,
245 n_events: 0,
246 })
247 }
248
249 fn payload(&mut self) -> Result<&mut BufWriter<File>, IoError> {
251 self.payload.as_mut().ok_or_else(|| {
252 IoError::Io(std::io::Error::other("this rosbag sink has already been finished"))
253 })
254 }
255
256 fn write_message(
258 &mut self,
259 stream: &EventStream,
260 start: usize,
261 end: usize,
262 ) -> Result<(), IoError> {
263 let stamp = stream.ts().get(start).copied().unwrap_or(0);
264 let offset = u32::try_from(self.payload_len).map_err(|_| {
265 IoError::Unsupported(
266 "a ROS bag chunk is limited to 4 GB by its 32-bit size field; split the recording \
267 across files, or record to .h5/.aedat4 which have no such ceiling"
268 .to_owned(),
269 )
270 })?;
271 let message = serialize_event_array(stream, start, end);
272 let record = make_record(&message_fields(CONN_ID, stamp), &message);
273 self.payload()?.write_all(&record).map_err(IoError::Io)?;
274 self.payload_len += record.len() as u64;
275 self.index_entries.push((stamp, offset));
276 Ok(())
277 }
278}
279
280impl Drop for BagEventSink {
281 fn drop(&mut self) {
282 drop(self.payload.take());
285 let _ = std::fs::remove_file(&self.scratch);
286 }
287}
288
289impl super::EventSink for BagEventSink {
290 fn append(&mut self, stream: &EventStream) -> Result<(), IoError> {
291 if stream.is_empty() {
292 return Ok(());
293 }
294 self.sensor_size.get_or_insert_with(|| stream.sensor_size());
295 let ts = stream.ts();
296 if let (Some(&lo), Some(&hi)) = (ts.iter().min(), ts.iter().max()) {
297 self.span = Some(match self.span {
298 Some((start, end)) => (start.min(lo), end.max(hi)),
299 None => (lo, hi),
300 });
301 }
302 let mut start = 0;
305 while start < stream.len() {
306 let end = (start + MESSAGE_EVENTS).min(stream.len());
307 self.write_message(stream, start, end)?;
308 start = end;
309 }
310 self.n_events += stream.len();
311 Ok(())
312 }
313
314 fn n_events(&self) -> usize {
315 self.n_events
316 }
317
318 fn flush(&mut self) -> Result<(), IoError> {
319 self.payload()?.flush().map_err(IoError::Io)
320 }
321
322 fn finish(mut self: Box<Self>) -> Result<(), IoError> {
323 if self.index_entries.is_empty() {
324 let (width, height) = self.sensor_size.unwrap_or((1, 1));
327 let empty = EventStreamBuilder::new(width, height, 0.001).build();
328 self.write_message(&empty, 0, 0)?;
329 }
330 if let Some(mut payload) = self.payload.take() {
333 payload.flush().map_err(IoError::Io)?;
334 }
335 let payload_len = u32::try_from(self.payload_len).map_err(|_| {
336 IoError::Unsupported(
337 "a ROS bag chunk is limited to 4 GB by its 32-bit size field; record to \
338 .h5/.aedat4 instead"
339 .to_owned(),
340 )
341 })?;
342
343 let message_count = self.index_entries.len() as u32;
344 let (start_us, end_us) = self.span.unwrap_or((0, 0));
345 let bag_header_len = make_record(&bag_header_fields(0, 1, 1), &[]).len();
346 let chunk_pos = BAG_MAGIC.len() + bag_header_len;
347 let chunk_fields = chunk_fields(payload_len);
348 let mut index_data: Vec<u8> = Vec::with_capacity(self.index_entries.len() * 12);
349 for (time_us, offset) in &self.index_entries {
350 index_data.extend_from_slice(&ros_time(*time_us));
351 index_data.extend_from_slice(&offset.to_le_bytes());
352 }
353 let index_record = make_record(&index_data_fields(CONN_ID, message_count), &index_data);
354 let index_pos =
355 chunk_pos + record_len(&chunk_fields, payload_len as usize) + index_record.len();
356
357 let mut writer = BufWriter::new(File::create(&self.path).map_err(IoError::Io)?);
358 writer.write_all(BAG_MAGIC).map_err(IoError::Io)?;
359 write_record(&mut writer, &bag_header_fields(index_pos as u64, 1, 1), &[])
360 .map_err(IoError::Io)?;
361 let header_len: usize = chunk_fields.iter().map(Vec::len).sum();
365 writer
366 .write_all(&(header_len as u32).to_le_bytes())
367 .map_err(IoError::Io)?;
368 for field in &chunk_fields {
369 writer.write_all(field).map_err(IoError::Io)?;
370 }
371 writer
372 .write_all(&payload_len.to_le_bytes())
373 .map_err(IoError::Io)?;
374 let mut scratch = BufReader::new(File::open(&self.scratch).map_err(IoError::Io)?);
375 std::io::copy(&mut scratch, &mut writer).map_err(IoError::Io)?;
376
377 writer.write_all(&index_record).map_err(IoError::Io)?;
378 write_record(
380 &mut writer,
381 &connection_fields(CONN_ID, &self.topic),
382 &connection_data(&self.topic),
383 )
384 .map_err(IoError::Io)?;
385 let mut chunk_info_data = Vec::with_capacity(8);
386 chunk_info_data.extend_from_slice(&CONN_ID.to_le_bytes());
387 chunk_info_data.extend_from_slice(&message_count.to_le_bytes());
388 write_record(
389 &mut writer,
390 &chunk_info_fields(chunk_pos as u64, start_us, end_us),
391 &chunk_info_data,
392 )
393 .map_err(IoError::Io)?;
394 writer.flush().map_err(IoError::Io)
395 }
396}
397
398fn ros_time(t_us: i64) -> [u8; 8] {
401 let t_us = t_us.max(0);
402 let seconds = (t_us / 1_000_000) as u32;
403 let nanoseconds = ((t_us % 1_000_000) * 1000) as u32;
404 let mut bytes = [0u8; 8];
405 bytes[..4].copy_from_slice(&seconds.to_le_bytes());
406 bytes[4..].copy_from_slice(&nanoseconds.to_le_bytes());
407 bytes
408}
409
410fn serialize_event_array(stream: &EventStream, start: usize, end: usize) -> Vec<u8> {
412 let (width, height) = stream.sensor_size();
413 let (xs, ys, ts, ps) = (stream.xs(), stream.ys(), stream.ts(), stream.ps());
414 let stamp = ts.get(start).copied().unwrap_or(0);
415 let mut message = Vec::with_capacity(24 + (end - start) * 13);
416 message.extend_from_slice(&0u32.to_le_bytes()); message.extend_from_slice(&ros_time(stamp)); message.extend_from_slice(&0u32.to_le_bytes()); message.extend_from_slice(&(height as u32).to_le_bytes());
420 message.extend_from_slice(&(width as u32).to_le_bytes());
421 message.extend_from_slice(&((end - start) as u32).to_le_bytes());
422 for index in start..end {
423 message.extend_from_slice(&xs[index].to_le_bytes());
424 message.extend_from_slice(&ys[index].to_le_bytes());
425 message.extend_from_slice(&ros_time(ts[index]));
426 message.push(u8::from(ps[index]));
427 }
428 message
429}
430
431fn field(name: &str, value: &[u8]) -> Vec<u8> {
433 let mut bytes = Vec::with_capacity(4 + name.len() + 1 + value.len());
434 let body_len = (name.len() + 1 + value.len()) as u32;
435 bytes.extend_from_slice(&body_len.to_le_bytes());
436 bytes.extend_from_slice(name.as_bytes());
437 bytes.push(b'=');
438 bytes.extend_from_slice(value);
439 bytes
440}
441
442fn record_len(fields: &[Vec<u8>], data_len: usize) -> usize {
444 4 + fields.iter().map(Vec::len).sum::<usize>() + 4 + data_len
445}
446
447fn make_record(fields: &[Vec<u8>], data: &[u8]) -> Vec<u8> {
449 let mut record = Vec::with_capacity(record_len(fields, data.len()));
450 write_record(&mut record, fields, data).expect("writing to a Vec never fails");
451 record
452}
453
454fn write_record<W: Write>(writer: &mut W, fields: &[Vec<u8>], data: &[u8]) -> std::io::Result<()> {
456 let header_len: usize = fields.iter().map(Vec::len).sum();
457 writer.write_all(&(header_len as u32).to_le_bytes())?;
458 for field in fields {
459 writer.write_all(field)?;
460 }
461 writer.write_all(&(data.len() as u32).to_le_bytes())?;
462 writer.write_all(data)
463}
464
465fn bag_header_fields(index_pos: u64, conn_count: u32, chunk_count: u32) -> Vec<Vec<u8>> {
466 vec![
467 field("op", &[0x03]),
468 field("index_pos", &index_pos.to_le_bytes()),
469 field("conn_count", &conn_count.to_le_bytes()),
470 field("chunk_count", &chunk_count.to_le_bytes()),
471 ]
472}
473
474fn chunk_fields(uncompressed_size: u32) -> Vec<Vec<u8>> {
475 vec![
476 field("op", &[0x05]),
477 field("compression", b"none"),
478 field("size", &uncompressed_size.to_le_bytes()),
479 ]
480}
481
482fn connection_fields(conn_id: u32, topic: &str) -> Vec<Vec<u8>> {
483 vec![
484 field("op", &[0x07]),
485 field("conn", &conn_id.to_le_bytes()),
486 field("topic", topic.as_bytes()),
487 ]
488}
489
490fn connection_data(topic: &str) -> Vec<u8> {
491 let fields = [
492 field("topic", topic.as_bytes()),
493 field("type", EVENT_ARRAY_TYPE.as_bytes()),
494 field("md5sum", EVENT_ARRAY_MD5.as_bytes()),
495 field("message_definition", EVENT_ARRAY_DEF.as_bytes()),
496 ];
497 fields.concat()
498}
499
500fn message_fields(conn_id: u32, time_us: i64) -> Vec<Vec<u8>> {
501 vec![
502 field("op", &[0x02]),
503 field("conn", &conn_id.to_le_bytes()),
504 field("time", &ros_time(time_us)),
505 ]
506}
507
508fn index_data_fields(conn_id: u32, count: u32) -> Vec<Vec<u8>> {
509 vec![
510 field("op", &[0x04]),
511 field("ver", &1u32.to_le_bytes()),
512 field("conn", &conn_id.to_le_bytes()),
513 field("count", &count.to_le_bytes()),
514 ]
515}
516
517fn chunk_info_fields(chunk_pos: u64, start_us: i64, end_us: i64) -> Vec<Vec<u8>> {
518 vec![
519 field("op", &[0x06]),
520 field("ver", &1u32.to_le_bytes()),
521 field("chunk_pos", &chunk_pos.to_le_bytes()),
522 field("start_time", &ros_time(start_us)),
523 field("end_time", &ros_time(end_us)),
524 field("count", &1u32.to_le_bytes()),
525 ]
526}
527
528#[derive(Clone, Copy, Debug, PartialEq, Eq)]
531struct ChunkMeta {
532 pos: u64,
533 start_us: i64,
534 end_us: i64,
535}
536
537pub struct BagSliceSource {
543 bag: RosBag,
544 path: std::path::PathBuf,
547 conn_ids: Vec<u32>,
548 chunks: Vec<ChunkMeta>,
549 sensor: (usize, usize),
550 span_us: (i64, i64),
551 counts: Mutex<Option<Vec<usize>>>,
552}
553
554pub fn open_bag_slice(
558 path: impl AsRef<Path>,
559 options: &LoadOptions,
560) -> Result<BagSliceSource, IoError> {
561 let topic = options.topic.as_deref().unwrap_or(DEFAULT_TOPIC);
562 let path = path.as_ref();
563 let bag = RosBag::new(path).map_err(IoError::Io)?;
564
565 let mut conn_ids: Vec<u32> = Vec::new();
566 for record in bag.index_records() {
567 if let IndexRecord::Connection(connection) = record.map_err(map_bag_error)? {
568 if connection.topic == topic && connection.tp == EVENT_ARRAY_TYPE {
569 conn_ids.push(connection.id);
570 }
571 }
572 }
573 if conn_ids.is_empty() {
574 return Err(IoError::Format(format!(
575 "no {EVENT_ARRAY_TYPE} connection on topic {topic}"
576 )));
577 }
578
579 let mut chunks: Vec<ChunkMeta> = Vec::new();
580 for record in bag.index_records() {
581 if let IndexRecord::ChunkInfo(info) = record.map_err(map_bag_error)? {
582 if info
583 .entries()
584 .any(|entry| conn_ids.contains(&entry.conn_id))
585 {
586 chunks.push(ChunkMeta {
587 pos: info.chunk_pos,
588 start_us: (info.start_time / 1000) as i64,
589 end_us: (info.end_time / 1000) as i64,
590 });
591 }
592 }
593 }
594 chunks.sort_by_key(|chunk| chunk.start_us);
595
596 let span_us = match (
597 chunks.iter().map(|c| c.start_us).min(),
598 chunks.iter().map(|c| c.end_us).max(),
599 ) {
600 (Some(lo), Some(hi)) => (lo, hi),
601 _ => (0, 0),
602 };
603 let sensor = match options.sensor_size {
604 Some(size) => size,
605 None => detect_sensor(&bag, &chunks, &conn_ids)?,
606 };
607
608 Ok(BagSliceSource {
609 bag,
610 path: path.to_path_buf(),
611 conn_ids,
612 chunks,
613 sensor,
614 span_us,
615 counts: Mutex::new(None),
616 })
617}
618
619fn detect_sensor(
621 bag: &RosBag,
622 chunks: &[ChunkMeta],
623 conn_ids: &[u32],
624) -> Result<(usize, usize), IoError> {
625 let Some(first) = chunks.first() else {
626 return Ok((1, 1));
627 };
628 let mut iterator = bag.chunk_records();
629 iterator.seek(first.pos).map_err(map_bag_error)?;
630 if let Some(record) = iterator.next() {
631 if let ChunkRecord::Chunk(chunk) = record.map_err(map_bag_error)? {
632 for message in chunk.messages() {
633 if let MessageRecord::MessageData(data) = message.map_err(map_bag_error)? {
634 if conn_ids.contains(&data.conn_id) {
635 return read_event_array_header(data.data);
636 }
637 }
638 }
639 }
640 }
641 Ok((1, 1))
642}
643
644fn select_chunks(chunks: &[ChunkMeta], t0: i64, t1: i64) -> impl Iterator<Item = &ChunkMeta> {
646 chunks
647 .iter()
648 .filter(move |chunk| chunk.start_us < t1 && chunk.end_us >= t0)
649}
650
651fn locate_chunk(counts: &[usize], i: usize) -> usize {
654 counts
655 .partition_point(|&count| count <= i)
656 .saturating_sub(1)
657}
658
659impl BagSliceSource {
660 fn for_each_event(
663 &self,
664 chunk: &ChunkMeta,
665 on_event: &mut dyn FnMut(u16, u16, i64, bool) -> bool,
666 ) -> Result<(), IoError> {
667 let mut iterator = self.bag.chunk_records();
668 iterator.seek(chunk.pos).map_err(map_bag_error)?;
669 let Some(record) = iterator.next() else {
670 return Ok(());
671 };
672 let ChunkRecord::Chunk(decoded) = record.map_err(map_bag_error)? else {
673 return Ok(());
674 };
675 for message in decoded.messages() {
676 if let MessageRecord::MessageData(data) = message.map_err(map_bag_error)? {
677 if self.conn_ids.contains(&data.conn_id) && decode_event_array(data.data, on_event)?
678 {
679 break;
680 }
681 }
682 }
683 Ok(())
684 }
685
686 fn cumulative_counts(&self) -> Result<Vec<usize>, IoError> {
688 let mut guard = self.counts.lock().unwrap();
689 if let Some(counts) = guard.as_ref() {
690 return Ok(counts.clone());
691 }
692 let mut counts = Vec::with_capacity(self.chunks.len() + 1);
693 counts.push(0);
694 for chunk in &self.chunks {
695 let mut events = 0usize;
696 self.for_each_event(chunk, &mut |_, _, _, _| {
697 events += 1;
698 false
699 })?;
700 counts.push(counts.last().unwrap() + events);
701 }
702 *guard = Some(counts.clone());
703 Ok(counts)
704 }
705}
706
707impl SliceSource for BagSliceSource {
708 fn sensor_size(&self) -> (usize, usize) {
709 self.sensor
710 }
711
712 fn timestamp_scale_ms(&self) -> f64 {
713 0.001
714 }
715
716 fn n_events(&self) -> usize {
717 self.cumulative_counts()
719 .ok()
720 .and_then(|counts| counts.last().copied())
721 .unwrap_or(0)
722 }
723
724 fn time_span(&self) -> (i64, i64) {
725 self.span_us
726 }
727
728 fn frames(&self, t0: i64, t1: i64) -> Result<Vec<(i64, EventFrame)>, IoError> {
732 read_bag_frames(&self.path, None, t0, t1)
733 }
734
735 fn imu(&self, t0: i64, t1: i64) -> Result<Vec<ImuSample>, IoError> {
736 read_bag_imu(&self.path, None, t0, t1)
737 }
738
739 fn camera(&self) -> Result<Option<crate::camera::Camera>, IoError> {
740 read_bag_camera_info(&self.path, None)
741 }
742
743 fn slice_time(&self, t0: i64, t1: i64) -> Result<EventStream, IoError> {
744 let mut builder = EventStreamBuilder::new(self.sensor.0, self.sensor.1, 0.001);
745 for chunk in select_chunks(&self.chunks, t0, t1) {
746 self.for_each_event(chunk, &mut |x, y, t, p| {
747 if t >= t1 {
748 return true; }
750 if t >= t0 {
751 builder.push(x, y, t, p);
752 }
753 false
754 })?;
755 }
756 Ok(builder.build())
757 }
758
759 fn slice_index(&self, i0: usize, i1: usize) -> Result<EventStream, IoError> {
760 let counts = self.cumulative_counts()?;
761 let total = counts.last().copied().unwrap_or(0);
762 let i0 = i0.min(total);
763 let i1 = i1.clamp(i0, total);
764 let mut builder = EventStreamBuilder::new(self.sensor.0, self.sensor.1, 0.001);
765 if i0 == i1 {
766 return Ok(builder.build());
767 }
768 for (offset, chunk) in self
769 .chunks
770 .iter()
771 .enumerate()
772 .skip(locate_chunk(&counts, i0))
773 {
774 if counts[offset] >= i1 {
775 break;
776 }
777 let mut index = counts[offset];
778 self.for_each_event(chunk, &mut |x, y, t, p| {
779 if (i0..i1).contains(&index) {
780 builder.push(x, y, t, p);
781 }
782 index += 1;
783 index >= i1
784 })?;
785 }
786 Ok(builder.build())
787 }
788}
789
790fn map_bag_error(error: rosbag::Error) -> IoError {
791 IoError::Format(format!("rosbag: {error}"))
792}
793
794const IMAGE_TYPE: &str = "sensor_msgs/Image";
805const IMU_TYPE: &str = "sensor_msgs/Imu";
806const CAMERA_INFO_TYPE: &str = "sensor_msgs/CameraInfo";
807
808#[derive(Clone, Copy, Debug, PartialEq)]
813pub struct ImuSample {
814 pub t_us: i64,
815 pub angular_velocity: [f64; 3],
817 pub linear_acceleration: [f64; 3],
819}
820
821pub fn bag_topics(path: impl AsRef<Path>) -> Result<Vec<(String, String)>, IoError> {
828 let bag = RosBag::new(path).map_err(IoError::Io)?;
829 let mut topics: Vec<(String, String)> = Vec::new();
830 for record in bag.index_records() {
831 if let IndexRecord::Connection(connection) = record.map_err(map_bag_error)? {
832 let entry = (connection.topic.to_owned(), connection.tp.to_owned());
833 if !topics.contains(&entry) {
834 topics.push(entry);
835 }
836 }
837 }
838 topics.sort();
839 Ok(topics)
840}
841
842fn connections_of_type(
848 bag: &RosBag,
849 wanted_type: &str,
850 topic: Option<&str>,
851) -> Result<(Vec<u32>, String), IoError> {
852 let mut by_topic: HashMap<String, Vec<u32>> = HashMap::new();
853 for record in bag.index_records() {
854 if let IndexRecord::Connection(connection) = record.map_err(map_bag_error)? {
855 if connection.tp == wanted_type {
856 by_topic
857 .entry(connection.topic.to_owned())
858 .or_default()
859 .push(connection.id);
860 }
861 }
862 }
863 if let Some(topic) = topic {
864 if let Some(ids) = by_topic.get(topic) {
865 return Ok((ids.clone(), topic.to_owned()));
866 }
867 }
868 match by_topic.len() {
869 1 => {
870 let (found, ids) = by_topic.into_iter().next().expect("checked len");
871 Ok((ids, found))
872 }
873 0 => Err(IoError::Unsupported(format!(
874 "the bag has no {wanted_type} topic"
875 ))),
876 _ => {
877 let mut names: Vec<String> = by_topic.into_keys().collect();
878 names.sort();
879 Err(IoError::Unsupported(format!(
880 "the bag has several {wanted_type} topics ({}); pass one explicitly",
881 names.join(", ")
882 )))
883 }
884 }
885}
886
887fn chunk_metadata(bag: &RosBag) -> Result<Vec<ChunkMeta>, IoError> {
889 let mut chunks = Vec::new();
890 for record in bag.index_records() {
891 if let IndexRecord::ChunkInfo(info) = record.map_err(map_bag_error)? {
892 chunks.push(ChunkMeta {
893 pos: info.chunk_pos,
894 start_us: (info.start_time / 1_000) as i64,
895 end_us: (info.end_time / 1_000) as i64,
896 });
897 }
898 }
899 chunks.sort_by_key(|chunk| chunk.start_us);
900 Ok(chunks)
901}
902
903fn for_each_message(
914 bag: &RosBag,
915 conn_ids: &[u32],
916 t0_us: i64,
917 t1_us: i64,
918 visit: &mut dyn FnMut(&[u8]) -> Result<bool, IoError>,
919) -> Result<(), IoError> {
920 let chunks = chunk_metadata(bag)?;
921 for chunk in select_chunks(&chunks, t0_us, t1_us) {
922 let mut iterator = bag.chunk_records();
923 iterator.seek(chunk.pos).map_err(map_bag_error)?;
924 let Some(record) = iterator.next() else {
925 continue;
926 };
927 let ChunkRecord::Chunk(decoded) = record.map_err(map_bag_error)? else {
928 continue;
929 };
930 for message in decoded.messages() {
931 if let MessageRecord::MessageData(data) = message.map_err(map_bag_error)? {
932 if !conn_ids.contains(&data.conn_id) {
933 continue;
934 }
935 if visit(data.data)? {
936 return Ok(());
937 }
938 }
939 }
940 }
941 Ok(())
942}
943
944pub fn read_bag_frames(
949 path: impl AsRef<Path>,
950 topic: Option<&str>,
951 t0_us: i64,
952 t1_us: i64,
953) -> Result<Vec<(i64, EventFrame)>, IoError> {
954 let bag = RosBag::new(path).map_err(IoError::Io)?;
955 let (conn_ids, _) = connections_of_type(&bag, IMAGE_TYPE, topic)?;
956 let mut frames = Vec::new();
957 for_each_message(&bag, &conn_ids, t0_us, t1_us, &mut |bytes| {
958 let mut reader = ByteReader::new(bytes);
959 let t_us = reader.ros_header()?;
960 if t_us < t0_us || t_us >= t1_us {
961 return Ok(false);
962 }
963 let height = reader.u32()? as usize;
964 let width = reader.u32()? as usize;
965 let encoding = reader.string()?;
966 reader.skip(1)?; let step = reader.u32()? as usize;
968 let length = reader.u32()? as usize;
969 let data = reader.take(length)?;
970
971 let channels = match encoding.as_str() {
976 "mono8" => 1usize,
977 "rgb8" | "bgr8" => 3,
978 other => {
979 return Err(IoError::Unsupported(format!(
980 "frame encoding {other:?} is not supported; this reader handles mono8, rgb8 \
981 and bgr8"
982 )))
983 }
984 };
985 let swap_rb = encoding == "bgr8";
986 let mut samples = Vec::with_capacity(width * height);
989 for row in 0..height {
990 let start = row * step;
991 let line = data.get(start..start + width * channels).ok_or_else(|| {
992 IoError::Format("image row runs past the message payload".to_owned())
993 })?;
994 for pixel in line.chunks_exact(channels) {
995 samples.push(match (channels, swap_rb) {
996 (1, _) => pixel[0],
997 (_, false) => super::luma(pixel),
998 (_, true) => super::luma(&[pixel[2], pixel[1], pixel[0]]),
999 });
1000 }
1001 }
1002 let frame = EventFrame::intensity(EventFrameData::U8(samples), width, height)
1003 .map_err(|error| IoError::Format(error.to_string()))?;
1004 frames.push((t_us, frame));
1005 Ok(false)
1006 })?;
1007 Ok(frames)
1008}
1009
1010pub fn read_bag_imu(
1012 path: impl AsRef<Path>,
1013 topic: Option<&str>,
1014 t0_us: i64,
1015 t1_us: i64,
1016) -> Result<Vec<ImuSample>, IoError> {
1017 let bag = RosBag::new(path).map_err(IoError::Io)?;
1018 let (conn_ids, _) = connections_of_type(&bag, IMU_TYPE, topic)?;
1019 let mut samples = Vec::new();
1020 for_each_message(&bag, &conn_ids, t0_us, t1_us, &mut |bytes| {
1021 let mut reader = ByteReader::new(bytes);
1022 let t_us = reader.ros_header()?;
1023 if t_us < t0_us || t_us >= t1_us {
1024 return Ok(false);
1025 }
1026 reader.skip(4 * 8)?; reader.skip(9 * 8)?; let angular_velocity = reader.vector3()?;
1029 reader.skip(9 * 8)?; let linear_acceleration = reader.vector3()?;
1031 samples.push(ImuSample {
1032 t_us,
1033 angular_velocity,
1034 linear_acceleration,
1035 });
1036 Ok(false)
1037 })?;
1038 Ok(samples)
1039}
1040
1041pub fn read_bag_camera_info(
1047 path: impl AsRef<Path>,
1048 topic: Option<&str>,
1049) -> Result<Option<crate::camera::Camera>, IoError> {
1050 let bag = RosBag::new(path).map_err(IoError::Io)?;
1051 let (conn_ids, _) = connections_of_type(&bag, CAMERA_INFO_TYPE, topic)?;
1052 let mut camera = None;
1053 for_each_message(&bag, &conn_ids, i64::MIN, i64::MAX, &mut |bytes| {
1054 let mut reader = ByteReader::new(bytes);
1055 reader.ros_header()?;
1056 reader.skip(4 + 4)?; let _distortion_model = reader.string()?;
1058 let coefficients = reader.u32()? as usize;
1059 reader.skip(coefficients * 8)?; let k: Vec<f64> = (0..9).map(|_| reader.f64()).collect::<Result<_, _>>()?;
1062 camera = Some(crate::camera::Camera::new(k[0], k[4], k[2], k[5]));
1063 Ok(true) })?;
1065 Ok(camera)
1066}
1067
1068struct ByteReader<'a> {
1070 bytes: &'a [u8],
1071 position: usize,
1072}
1073
1074impl<'a> ByteReader<'a> {
1075 fn new(bytes: &'a [u8]) -> Self {
1076 Self { bytes, position: 0 }
1077 }
1078
1079 fn take(&mut self, count: usize) -> Result<&'a [u8], IoError> {
1080 let end = self
1081 .position
1082 .checked_add(count)
1083 .filter(|&end| end <= self.bytes.len())
1084 .ok_or_else(|| IoError::Format("truncated dvs_msgs/EventArray message".to_owned()))?;
1085 let slice = &self.bytes[self.position..end];
1086 self.position = end;
1087 Ok(slice)
1088 }
1089
1090 fn skip(&mut self, count: usize) -> Result<(), IoError> {
1091 self.take(count).map(|_| ())
1092 }
1093
1094 fn u8(&mut self) -> Result<u8, IoError> {
1095 Ok(self.take(1)?[0])
1096 }
1097
1098 fn u16(&mut self) -> Result<u16, IoError> {
1099 Ok(u16::from_le_bytes(self.take(2)?.try_into().unwrap()))
1100 }
1101
1102 fn u32(&mut self) -> Result<u32, IoError> {
1103 Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
1104 }
1105
1106 fn f64(&mut self) -> Result<f64, IoError> {
1107 Ok(f64::from_le_bytes(self.take(8)?.try_into().unwrap()))
1108 }
1109
1110 fn string(&mut self) -> Result<String, IoError> {
1113 let length = self.u32()? as usize;
1114 Ok(String::from_utf8_lossy(self.take(length)?).into_owned())
1115 }
1116
1117 fn ros_header(&mut self) -> Result<i64, IoError> {
1120 self.skip(4)?; let seconds = i64::from(self.u32()?);
1122 let nanoseconds = i64::from(self.u32()?);
1123 let _frame_id = self.string()?;
1124 Ok(seconds * 1_000_000 + nanoseconds / 1_000)
1125 }
1126
1127 fn vector3(&mut self) -> Result<[f64; 3], IoError> {
1128 Ok([self.f64()?, self.f64()?, self.f64()?])
1129 }
1130}
1131
1132#[cfg(test)]
1133mod tests {
1134 use super::{locate_chunk, open_bag_slice, read_bag, select_chunks, write_bag, ChunkMeta};
1135 use crate::io::{LoadOptions, SliceSource};
1136 use crate::{EventStream, EventStreamBuilder};
1137
1138 fn temp_path(tag: &str) -> std::path::PathBuf {
1139 let nanos = std::time::SystemTime::now()
1140 .duration_since(std::time::UNIX_EPOCH)
1141 .unwrap()
1142 .as_nanos();
1143 std::env::temp_dir().join(format!("eventcv_{tag}_{nanos}.bag"))
1144 }
1145
1146 fn sample_stream() -> EventStream {
1147 let mut builder = EventStreamBuilder::new(16, 12, 0.001);
1148 for &(x, y, t, p) in &[
1149 (0u16, 0u16, 5i64, true),
1150 (3, 4, 1_000_001, false),
1151 (15, 11, 2_500_000, true),
1152 (7, 8, 2_500_000, false),
1153 ] {
1154 builder.push(x, y, t, p);
1155 }
1156 builder.build()
1157 }
1158
1159 #[test]
1160 fn bag_round_trips_through_the_reader() {
1161 let stream = sample_stream();
1162 let path = temp_path("bag_rt");
1163 write_bag(&path, &stream, None).unwrap();
1164
1165 let loaded = read_bag(&path, &LoadOptions::default()).unwrap();
1166 assert_eq!(loaded.sensor_size(), (16, 12));
1167 assert_eq!(loaded.xs(), stream.xs());
1168 assert_eq!(loaded.ys(), stream.ys());
1169 assert_eq!(loaded.ts(), stream.ts()); assert_eq!(loaded.ps(), stream.ps());
1171
1172 let reader = open_bag_slice(&path, &LoadOptions::default()).unwrap();
1174 assert_eq!(reader.sensor_size(), (16, 12));
1175 assert_eq!(reader.n_events(), stream.len());
1176 assert_eq!(reader.time_span(), (5, 2_500_000));
1177 let window = reader.slice_time(1_000_000, 2_000_000).unwrap();
1178 assert_eq!(window.ts(), &[1_000_001]);
1179
1180 std::fs::remove_file(&path).ok();
1181 }
1182
1183 #[test]
1184 fn bag_round_trips_an_empty_stream() {
1185 let stream = EventStreamBuilder::new(8, 6, 0.001).build();
1186 let path = temp_path("bag_empty");
1187 write_bag(&path, &stream, Some("/cam/events")).unwrap();
1188
1189 let options = LoadOptions {
1190 topic: Some("/cam/events".to_owned()),
1191 ..LoadOptions::default()
1192 };
1193 let loaded = read_bag(&path, &options).unwrap();
1194 assert!(loaded.is_empty());
1195 assert_eq!(loaded.sensor_size(), (8, 6));
1196
1197 std::fs::remove_file(&path).ok();
1198 }
1199
1200 fn meta(pos: u64, start_us: i64, end_us: i64) -> ChunkMeta {
1201 ChunkMeta {
1202 pos,
1203 start_us,
1204 end_us,
1205 }
1206 }
1207
1208 #[test]
1209 fn select_chunks_picks_overlapping_windows() {
1210 let chunks = [meta(0, 0, 100), meta(1, 100, 200), meta(2, 200, 300)];
1211
1212 let picked: Vec<u64> = select_chunks(&chunks, 150, 250).map(|c| c.pos).collect();
1213 assert_eq!(picked, [1, 2]); let picked: Vec<u64> = select_chunks(&chunks, 0, 100).map(|c| c.pos).collect();
1217 assert_eq!(picked, [0]);
1218
1219 assert!(select_chunks(&chunks, 1000, 2000).next().is_none());
1220 }
1221
1222 #[test]
1223 fn locate_chunk_finds_the_containing_chunk() {
1224 let counts = [0usize, 10, 25, 40]; assert_eq!(locate_chunk(&counts, 0), 0);
1227 assert_eq!(locate_chunk(&counts, 9), 0);
1228 assert_eq!(locate_chunk(&counts, 10), 1);
1229 assert_eq!(locate_chunk(&counts, 24), 1);
1230 assert_eq!(locate_chunk(&counts, 39), 2);
1231 }
1232}