Reference

MCAP Import Mapping

Find the typed elements that each Foxglove and ROS 2 topic of an MCAP recording imports as, with their time and frame fields.

An MCAP recording imports as one row, and each of its topics as one list-valued column of elements, one element per message. Your application reads poses, point clouds and video packets as Spiral data types instead of decoding Protobuf or ROS 2 messages itself. The mapping is fixed for each message schema, so every Table imported from MCAP names and types its elements the same way.

A /tf topic of tf2_msgs/msg/TFMessage messages, each holding two transforms, imports as one element per transform:

log_time                       publish_time                   stamp                          transform
2026-10-20T09:00:00.000000000  2026-10-20T09:00:00.000001000  2026-10-20T08:59:59.999000000  {translation: {x: 1.0, y: 0.0, z: 0.0}, rotation: {x: 0.0, y: 0.0, z: 0.0, w: 1.0}, parent_frame_id: world, child_frame_id: base_link}
2026-10-20T09:00:00.000000000  2026-10-20T09:00:00.000001000  2026-10-20T08:59:59.999000000  {translation: {x: 0.1, y: 0.0, z: 0.4}, rotation: {x: 0.0, y: 0.0, z: 0.0, w: 1.0}, parent_frame_id: base_link, child_frame_id: camera}

A topic reads through the recording's chunk index, so reading one topic opens only the chunks that hold it. Recordings without a summary section and chunk index cannot be read by topic.

The recordings Table

An import writes one row per MCAP file. The Table stores each recording's file and, for each topic, what a read needs to decode it. A topic's elements are decoded when you read them, not when you import.

ColumnTypeMeaning
recordingstringThe row's key: the file's path below the imported directory.
filespql.BlobThe MCAP file, copied into the Table's storage.
start_time, end_timetimestamp, nanoseconds, nullableThe first and last log_time in the file. Null when the file records no statistics.
message_countunsigned integer, nullableMessages in the file.
one column per topicstruct, nullableThe topic's reference: topic, schema_name, message_encoding, message_count and dtype, the element type its reads return. Null when the recording does not publish the topic.

A topic column's name is the topic without its leading and trailing /, with every character other than a letter, digit or _ replaced by _: /camera/front is stored as camera_front. Topics that several channels of one file publish, and topics that are neither Protobuf nor ROS 2 CDR, are left out of that file's row, and the import reports them.

Reading a topic in a log_time range returns the elements logged in that half-open range, and opens only the chunks whose messages overlap it. A recording that does not publish the topic reads as null. A playback window or a training clip reads a small part of a long camera topic; reading the whole of such a topic at once is refused when its chunks hold more than 64 MiB once decompressed.

Time and frame fields

Every element carries the recording's two clocks and the message's own timestamp, because they answer different questions: when the recorder saw the message, when the publisher sent it, and when the measurement was taken.

FieldTypeMeaning
log_timetimestamp, nanosecondsWhen the message was recorded. MCAP indexes are keyed on it, so time-range reads of the source file follow this field.
publish_timetimestamp, nanosecondsWhen the message was published. A recorder that does not know it writes log_time.
stamptimestamp, nanoseconds, nullableThe message's own time: the ROS 2 header.stamp, or the Foxglove timestamp. Null when a Foxglove message leaves it unset.
frame_idstring, nullableThe coordinate frame of the value: the ROS 2 header.frame_id, or the Foxglove frame_id. Null when the source string is empty.

The timestamps carry no time zone, because MCAP does not fix the epoch: a recording can count from the Unix epoch or from the robot's boot. Only values that are expressed in a frame get a frame_id field. A transform names its parent and child frames inside the value, and joint states and log records are not positions in space, so their elements have no frame_id.

Values follow REP 103 units: metres, radians and seconds. Elements are in the order of the file.

Message mappings

A topic's schema name and message encoding select its mapping. ROS 2 schema names are accepted with or without the msg/ segment, so sensor_msgs/PointCloud2 and sensor_msgs/msg/PointCloud2 are the same.

Source messageEncodingValue fieldTypeframe_id
foxglove.CompressedVideoprotobufpacketspql.EncodedPacketyes
foxglove.CompressedImageprotobufimagespql.EncodedImageyes
sensor_msgs/msg/CompressedImagecdrimagespql.EncodedImageyes
foxglove.PointCloudprotobufpoint_cloudspql.PointCloudyes
sensor_msgs/msg/PointCloud2cdrpoint_cloudspql.PointCloudyes
foxglove.FrameTransformprotobuftransformspql.Transform3Dno
foxglove.FrameTransformsprotobuftransform, one element per transformspql.Transform3Dno
geometry_msgs/msg/TransformStampedcdrtransformspql.Transform3Dno
tf2_msgs/msg/TFMessagecdrtransform, one element per transformspql.Transform3Dno
foxglove.PoseInFrameprotobufposespql.Pose3Dyes
geometry_msgs/msg/PoseStampedcdrposespql.Pose3Dyes
foxglove.LocationFixprotobuffixspql.LocationFixyes
sensor_msgs/msg/NavSatFixcdrfixspql.LocationFixyes
foxglove.JointStatesprotobufjointslist of spql.JointStateno
sensor_msgs/msg/JointStatecdrjointslist of spql.JointStateno
foxglove.Logprotobuflogspql.LogEntryno
rcl_interfaces/msg/Logcdrlogspql.LogEntryno

The two transform lists become one element per transform, each with its message's log_time and publish_time.

Other schemas

A Protobuf or ROS 2 topic whose schema has no mapping still imports. Its elements hold log_time, publish_time and message, the message decoded by the schema the recording embeds, with the schema's field names. A sensor_msgs/msg/Temperature topic imports as elements such as {log_time, publish_time, message: {header: {stamp: {sec, nanosec}, frame_id}, temperature, variance}}.

  • Proto3 scalars without presence use their zero, false or empty default. Absent message fields, proto3 optional fields and proto2 optional fields are null, including proto2 fields with declared defaults. Missing required proto2 fields fail the read. Repeated fields are empty when absent. Recursive schemas, real oneofs and protobuf Editions are unsupported.
  • ROS 2 fields are never null. Arrays of uint8 or byte decode as binary, other arrays as lists, and nested messages as structs. Topics with wstring fields cannot be read.

Trossen recordings use both mapped and generic Protobuf topics. Task descriptions (foxglove.KeyValuePair), joint states (schemas.proto.JointState), gripper status (schemas.proto.GripperStatus) and camera calibration (foxglove.CameraCalibration) keep their source fields inside message. foxglove.FrameTransforms becomes one transform element per transform, and foxglove.CompressedVideo becomes one packet element per message. The embedded descriptors supply nested timestamps, repeated joint values and calibration arrays; applications need no external .proto files.

Field mappings

Video packets. A foxglove.CompressedVideo message is one access unit. payload holds its bytes inline, codec is the message's format, and framing is annex_b for h264 and h265 and obu for av1. pts is the message's timestamp in nanoseconds, or log_time when it is unset, with a time base of 1/1,000,000,000. keyframe is true when an H.264 or H.265 packet holds an IDR, CRA or BLA picture, and null for other codecs. stream, dts, duration_ticks and codec_config are null: the topic's packets are one stream in decode order, and parameter sets travel in band, as Foxglove requires.

Images. source holds the compressed bytes inline. format is jpeg, png, webp or avif, with the matching mime_type; any other format string is kept, lowercased, with a null mime_type. ROS 2 image_transport strings such as bgr8; jpeg compressed bgr8 import as jpeg. width and height are null until the image is decoded.

Point clouds. The points keep the source's packed layout: data is the message's point bytes, point_stride the bytes from one point to the next, and fields one entry for each point field with its name, offset, dtype (u8, i8, u16, i16, u32, i32, f32 or f64) and count. A foxglove.PointCloud keeps its pose. A PointCloud2 has no pose, and its rows are joined so that points are point_stride apart; is_dense is not kept. Big-endian clouds are refused, because spql.PointCloud is little-endian.

Transforms. translation and rotation move the child frame onto the parent frame. A TransformStamped takes its parent_frame_id from header.frame_id. scale is null. A Foxglove transform without a translation or rotation is null.

Poses. position and orientation are the pose in the element's frame_id. A Foxglove pose without a position or orientation is null.

Location fixes. position holds latitude and longitude in degrees and altitude in metres. covariance is the 3×3 ENU position covariance in row-major order, and covariance_type keeps the source's code (0 unknown, 1 approximated, 2 diagonal known, 3 known). A NavSatFix altitude of NaN, which ROS 2 uses for "no altitude", is null, and its NavSatStatus fields are kept in metadata under the keys status and service. A foxglove.LocationFix keeps its heading, velocity, color and metadata, which are null when the recording's schema predates them.

Joint states. Each joint is one element with its name and its position, velocity, acceleration and effort. A ROS 2 JointState leaves a quantity's array empty when it does not report it, so that quantity is null for every joint, and acceleration is always null. A non-empty array whose length differs from the number of names fails the import.

Logs. level uses Foxglove's levels: 1 debug, 2 info, 3 warning, 4 error, 5 fatal, and 0 unknown. The ROS 2 levels 10 to 50 import as 1 to 5. name, file and line are null when the source leaves them empty or zero.

Errors

Reading a topic fails, and returns no elements, when the recording has no chunk index, when the topic's encoding is neither Protobuf nor ROS 2 CDR, when a message does not decode as its schema, or when a decoded value breaks its type, such as a point field type the mapping does not know. A read whose chunks hold more than 64 MiB once decompressed fails; read a narrower log_time range.

Availability

A public import command and SDK methods for topic reads are planned. The published clients do not yet expose this import workflow. Reads of a whole topic larger than 64 MiB are also planned. Until then, topic reads require time windows.

Found a problem? Edit this page or report an issue.

On this page