FiftyOne Multimodal#
FiftyOne provides native support for multimodal datasets, which represent rich, time-synchronized sensor recordings such as robotics and autonomous vehicle logs stored in the MCAP container format.
A single multimodal sample can contain many concurrent data streams — camera images, LIDAR point clouds, IMU readings, GPS fixes, coordinate frame transforms, diagnostics, and more — and FiftyOne lets you visualize, play back, tag, and query all of them in lockstep.
Note
Multimodal visualization is available to all FiftyOne users. With FiftyOne Enterprise you can additionally index your MCAP data (currently in beta) into columnar tables that power scalable search, filtering, and event mining across your entire fleet of recordings.
Overview#
A multimodal dataset is a FiftyOne dataset whose samples point to .mcap
files. MCAP is a self-describing container format for heterogeneous,
timestamped robotics data that stores messages organized into channels
(topics), each with an associated schema describing how to decode its
payloads.
When you add samples whose filepaths end in .mcap, the dataset’s media type
is automatically inferred as "multimodal":
1import fiftyone as fo
2
3dataset = fo.Dataset("robot-teleop-episodes")
4dataset.add_samples(
5 [
6 fo.Sample(filepath="/path/to/episode-0001.mcap"),
7 fo.Sample(filepath="/path/to/episode-0002.mcap"),
8 ]
9)
10
11print(dataset.media_type) # multimodal
When you open a multimodal sample in the App, FiftyOne reads the MCAP file directly via efficient byte-range reads — no server-side conversion is required — discovers its channels and schemas, decodes the messages it knows how to interpret, and renders them in a configurable, tiled viewer with a shared playback clock.
Key concepts:
Episodes: each sample in a multimodal dataset is an episode — one MCAP recording
Streams: each MCAP channel (topic) becomes a stream that can be bound to one or more tiles in the viewer
Time tracks: every message carries timestamps (log time, publish time, or a decoded header stamp) that drive synchronized playback across all tiles
Grid previews#
In the App’s sample grid, each multimodal sample displays a preview rendered from one of its streams, and you can use the stream selector to choose which stream is used for grid previews.
Tiles#
Multimodal samples open in a configurable, mosaic-style viewer composed of tiles. You can add, remove, resize, and rearrange tiles, and bind each tile to any compatible stream in the recording. All tiles share a common playback clock, so scrubbing the timeline updates every tile in sync.
Image tile#
Image tiles render camera streams, including raw and compressed images from
ROS and Foxglove schemas as well as compressed video streams (currently
H.264). Image annotations (e.g. foxglove.ImageAnnotations) can be overlaid
on their corresponding camera stream, and when camera calibration data is
available, hovering over an image tile highlights that camera’s frustum in
the 3D tile.
3D tile#
The 3D tile renders the spatial content of your recording in a shared world frame: point clouds (with configurable colormaps and color-by fields such as intensity), laser scans, occupancy grids, scene-update primitives, pose trajectories, and camera frustums. Coordinate frame transforms from the recording are used to place everything correctly, and you can select the reference frame, track a moving frame with the camera, measure distances, and inspect points via hover tooltips.
Map tile#
The map tile plots GNSS location streams (e.g. foxglove.LocationFix or
sensor_msgs/msg/NavSatFix) as tracks on an interactive map. The current
position follows the playback clock, and you can hover to inspect points
along the track and measure distances between locations.
Plot tile#
Plot tiles chart numeric series extracted from any topic and field path in the recording — IMU rates, vehicle speed, steering angle, diagnostics values, etc. — over the full duration of the recording. A playhead marks the current playback position, and clicking anywhere in the plot seeks the shared clock to that time.
Message tile#
The message tile is the escape hatch for any channel, decoded or not: it displays the most recent message on a selected topic at the current playhead as a collapsible record tree, so you can inspect exact field values as you scrub through the recording.
Logs tile#
The logs tile is a console view for log topics (foxglove.Log,
rcl_interfaces/msg/Log, rosgraph_msgs/Log). Log entries scroll in sync
with the playback clock, and you can pause following to scan the history or
seek the recording to an entry of interest.
Configuring tiles#
To add a new tile to the viewer, click the Add tile button (the grid icon) in the viewer’s header and choose the tile type you want — Image, 3D, Map, Logs, Plot, or Message. The same menu also offers Auto Layout, which automatically arranges your tiles.
The left sidebar of the viewer is where you configure what each tile is showing. It contains the following tabs:
Scene: settings that apply to the whole recording:
Playback: choose how signals behave between recorded samples —
Smoothinterpolates continuous signals (transforms and 2D/3D label geometry) for fluid playback, whileAs recordednever synthesizes values and holds each signal at its latest recorded sampleAdvanced timing: fine-grained control over how messages are matched to the playback clock
Topics: a searchable inventory of every topic in the recording, grouped by category (Sensors, Annotations & Planning, Transforms & Poses, Diagnostics, Telemetry, and Custom/Unknown). Each topic shows how it can be visualized, and you can open a topic directly in a compatible tile from here
Tile settings: when you focus a tile, a tab named for that tile appears with its specific options — for example, which streams and overlays an image tile displays, the 3D tile’s colormaps and camera behavior, the topic/field series charted by a plot tile, or the topic shown in a message tile
Inspecting objects#
The right sidebar of the viewer is an inspector for objects in the scene. Click any object in any tile — a 3D box in the 3D tile or an annotation in an image tile — to view its details:
For 3D scene objects: the object’s label, entity ID, topic, coordinate frame, and any metadata attached to the object
For image annotations: the object’s label, primitive kind, topic, and exact geometry
Any fields not covered by the structured view are shown as raw JSON. Press
Esc or click Clear selection to clear the current selection.
Timeline tracks#
Beneath the playback timeline, the viewer displays tracks: rows of time-anchored context that scrub in sync with the recording. Tracks are organized into sections that only appear when they have content:
Temporal tags: the temporal tags on the current sample, which you can create directly on the timeline
Events: intervals for derived events computed by MCAP indexing, e.g. “high steering” or “pedestrian while moving”. Event tracks only appear when the recording has derived events
Labels: annotations over time, one track per annotation topic. Label tracks only appear while annotations are currently visible in one of your tiles
Note
Event and label tracks are only available in FiftyOne Enterprise; event tracks additionally require MCAP indexing to be enabled.
MCAP Explorer#
FiftyOne also includes a standalone MCAP Explorer panel that lets you
open an arbitrary local .mcap file (via drag-and-drop or file browser) or a
remote URL without creating a dataset first. Local files stay in your browser
session and are read directly — nothing is uploaded.
Supported schemas#
FiftyOne ships with built-in decoders for visualizing the message schemas below in the App. Any channel whose schema is not recognized remains fully accessible via the Message tile, so you can always inspect your data even before a dedicated decoder exists.
ROS#
Both ROS 1 and ROS 2 messages are supported for the following schemas, listed here in their ROS 2 form (the corresponding ROS 1 schemas are also supported):
Schema |
Description |
|---|---|
|
Raw camera images, including common pixel encodings ( |
|
Compressed (e.g. JPEG/PNG) camera images |
|
Camera intrinsics and distortion parameters, rendered as camera frustums in the 3D tile |
|
LIDAR and other point clouds, with support for per-point scalar fields such as intensity |
|
Planar laser range scans |
|
GNSS position fixes, rendered in the map tile |
|
Odometry poses with velocity/acceleration kinematics |
|
Pose sequences such as planned or traveled paths |
|
Occupancy grids, rendered as textured planes in 3D |
|
Single timestamped poses |
|
Batches of poses |
|
Single coordinate frame transforms |
|
Coordinate frame transforms that define the scene’s frame graph |
|
Scene markers (cubes, spheres, lines, text, meshes) rendered in the 3D tile |
|
2D detections overlaid on camera images |
|
3D detections rendered in the 3D tile |
|
Diagnostics status arrays |
|
Log messages ( |
Foxglove#
All of the core Foxglove schemas are supported, in both their protobuf and ROS (CDR) encodings:
Schema |
Description |
|---|---|
|
Raw camera images |
|
Compressed camera images |
|
Compressed video streams (currently H.264) |
|
2D annotations (points, circles, text) overlaid on camera images |
|
Camera intrinsics and distortion, rendered as camera frustums |
|
Point clouds with per-field data |
|
Planar laser range scans |
|
2D data grids (e.g. occupancy/cost maps), rendered as textured planes in 3D |
|
Scene-graph primitives (arrows, cubes, spheres, lines, text, models) |
|
A single coordinate frame transform |
|
A batch of coordinate frame transforms |
|
SE(3) poses (translation + quaternion) |
|
GNSS position fixes (latitude/longitude/altitude), rendered in the map tile |
|
Log messages, shown in the logs tile |
JSON#
Channels containing JSON-encoded messages are supported for the following schemas:
Schema |
Description |
|---|---|
|
JSON-encoded pose/odometry data |
JSON-encoded ROS schemas |
JSON-encoded versions of the ROS schemas above (e.g.
|
Indexing MCAP data BETA#
Note
MCAP indexing is only available in FiftyOne Enterprise. It is currently in beta and is disabled by default; contact your deployment administrator or Voxel51 support to enable the feature for your deployment.
MCAP files are optimized for recording and playback, not for analytical queries. Questions like “find every episode where a pedestrian was visible while the vehicle was moving faster than 5 m/s” would otherwise require scanning and decoding every file in your fleet.
FiftyOne Enterprise solves this by indexing your MCAP data: a projection pipeline reads each recording once, decodes the channels you declare, and writes the results to columnar Parquet tables (managed via Apache Iceberg) called projections. These tables power fast, scalable filtering, aggregation, and event search across your entire dataset — in the App’s grid, sidebar, and query interfaces — without ever re-reading the source MCAPs.
The indexing pipeline maintains its own decoder registry, which currently covers the core ROS 2, Foxglove, and JSON message schemas (e.g. images, point clouds, IMU readings, poses, diagnostics, and image annotations).
You control exactly what gets indexed by authoring a projection manifest. Four kinds (grains) of projections are supported:
labels: per-message rows extracted from annotation streams, e.g. the text and geometry of every image annotation
signals: numeric time series sampled from message fields, e.g. vehicle speed, steering angle, or IMU rates, with configurable sampling strategies
events: derived time intervals computed from other projections using expressions, e.g. “windows of high steering lasting at least 500ms”
summaries: per-episode scalar rollups, e.g. the max speed or whether any pedestrian was observed
Indexing runs as delegated operations that are
automatically scheduled and orchestrated across your deployment’s compute.
Projection tables can be written to local storage or directly to cloud
buckets (s3://, gs://, az://).
Enabling indexing#
Any dataset containing MCAP samples is automatically registered as a multimodal dataset. To index it, configure it with a projection manifest and enable projections:
1import fiftyone as fo
2
3dataset = fo.Dataset("robot-teleop-episodes")
4dataset.add_samples(
5 [
6 fo.Sample(filepath="/path/to/episode-0001.mcap"),
7 fo.Sample(filepath="/path/to/episode-0002.mcap"),
8 ]
9)
10
11# Configure the dataset with your manifest and enable indexing
12with open("/path/to/manifest.yaml", "r") as f:
13 dataset.projections.enable(f.read())
Indexing is then scheduled and executed automatically. You can check on its progress, disable it, or retry a stuck run at any time:
1# The active run's status, including per-sample progress
2print(dataset.projections.get_projection().status())
3
4# Disable indexing, abandoning any active run
5dataset.projections.disable()
6
7# Reset a run that was interrupted (e.g. its worker crashed) so that it
8# is automatically requeued
9dataset.projections.retry()