use std::collections::BTreeSet;
use std::io::{Read, Seek};
use mcap::Summary;
use mcap::sans_io::{SummaryReadEvent, SummaryReader};
use re_chunk::TimePoint;
use re_log_types::{TimeCell, TimeType};
use saturating_cast::SaturatingCast as _;
use crate::Error;
use crate::parsers::ChannelId;
pub fn read_summary<R: Read + Seek>(mut reader: R) -> anyhow::Result<Option<Summary>> {
let mut summary_reader = SummaryReader::new();
while let Some(event) = summary_reader.next_event() {
match event? {
SummaryReadEvent::SeekRequest(pos) => {
summary_reader.notify_seeked(reader.seek(pos)?);
}
SummaryReadEvent::ReadRequest(need) => {
let read = reader.read(summary_reader.insert(need))?;
summary_reader.notify_read(read);
}
}
}
Ok(summary_reader.finish())
}
pub fn collect_empty_channels(
mcap_bytes: &[u8],
summary: &mcap::Summary,
) -> Result<BTreeSet<ChannelId>, Error> {
let all_channels = summary
.channels
.keys()
.copied()
.map(ChannelId)
.collect::<BTreeSet<_>>();
if let Some(stats) = &summary.stats {
let nonempty_channels = stats
.channel_message_counts
.iter()
.filter_map(|(&channel_id, &count)| (count > 0).then_some(ChannelId(channel_id)))
.collect::<BTreeSet<_>>();
return Ok(all_channels
.difference(&nonempty_channels)
.copied()
.collect());
}
let mut empty_channels = all_channels;
for chunk in &summary.chunk_indexes {
for (channel, msg_offsets) in summary.read_message_indexes(mcap_bytes, chunk)? {
if !msg_offsets.is_empty() {
empty_channels.remove(&ChannelId(channel.id));
}
}
}
Ok(empty_channels)
}
pub fn log_and_publish_timepoint_from_msg(
msg: &mcap::Message<'_>,
time_type: TimeType,
) -> TimePoint {
let log_time_cell = crate::util::TimestampCell::from_nanos_default(msg.log_time, time_type);
let publish_time_cell =
crate::util::TimestampCell::from_nanos_default(msg.publish_time, time_type);
re_chunk::TimePoint::from([
("message_log_time", log_time_cell.into_time_cell()),
("message_publish_time", publish_time_cell.into_time_cell()),
])
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TimestampCell {
pub timeline: String,
pub time: TimeCell,
}
impl TimestampCell {
pub fn from_nanos(timestamp_ns: u64, timeline: impl Into<String>) -> Self {
let ns = timestamp_ns.saturating_cast::<i64>();
Self {
timeline: timeline.into(),
time: TimeCell::from_timestamp_nanos_since_epoch(ns),
}
}
pub fn from_nanos_with_type(
nanos: u64,
timeline: impl Into<String>,
time_type: TimeType,
) -> Self {
let ns = nanos.saturating_cast::<i64>();
let time = match time_type {
TimeType::TimestampNs => TimeCell::from_timestamp_nanos_since_epoch(ns),
TimeType::DurationNs => TimeCell::from_duration_nanos(ns),
TimeType::Sequence => TimeCell::from_sequence(ns),
};
Self {
timeline: timeline.into(),
time,
}
}
pub fn from_nanos_default(timestamp_ns: u64, time_type: TimeType) -> Self {
Self::from_nanos_with_type(timestamp_ns, "timestamp", time_type)
}
pub fn from_nanos_ros2(timestamp_ns: u64, time_type: TimeType) -> Self {
Self::from_nanos_with_type(timestamp_ns, "ros2_timestamp", time_type)
}
pub fn timeline_name(&self) -> &str {
&self.timeline
}
pub fn into_time_cell(self) -> TimeCell {
self.time
}
}
#[cfg(test)]
mod tests {
#![expect(clippy::cast_possible_wrap)]
use re_log_types::TimeType;
use super::*;
#[test]
fn test_from_nanos() {
let ts: u64 = 1_672_531_200_000_000_000; let cell = TimestampCell::from_nanos_default(ts, TimeType::TimestampNs);
assert_eq!(cell.timeline_name(), "timestamp");
assert!(matches!(cell.time.typ, TimeType::TimestampNs));
assert_eq!(
cell.time,
TimeCell::from_timestamp_nanos_since_epoch(ts as i64)
);
let cell = TimestampCell::from_nanos_default(ts, TimeType::DurationNs);
assert_eq!(cell.timeline_name(), "timestamp");
assert!(matches!(cell.time.typ, TimeType::DurationNs));
assert_eq!(cell.time, TimeCell::from_duration_nanos(ts as i64));
}
#[test]
fn test_from_nanos_ros2() {
let ts: u64 = 1_672_531_200_000_000_000;
let cell = TimestampCell::from_nanos_ros2(ts, TimeType::TimestampNs);
assert_eq!(cell.timeline_name(), "ros2_timestamp");
assert!(matches!(cell.time.typ, TimeType::TimestampNs));
}
#[test]
fn test_from_nanos_custom_timeline() {
let cell = TimestampCell::from_nanos(42, "my_timeline");
assert_eq!(cell.timeline_name(), "my_timeline");
assert_eq!(
cell.into_time_cell(),
TimeCell::from_timestamp_nanos_since_epoch(42)
);
}
}