Rerun.io for Robotics Telemetry: 2026 Tutorial (Updated)
Rerun.io robotics telemetry visualization is the first viewer that treats spatiotemporal robot data as a first-class type system rather than a bag of plots, ROS topics, and screenshots glued together with matplotlib. After three years on a homegrown stack of rosbag2, rviz, rqt_plot, and seven hand-rolled matplotlib notebooks, switching a perception team to Rerun typically cuts cross-modal bug investigation time from days to hours, and the reason is the abstraction, not the UI polish. Where rviz forces you into a single live TF tree, and Foxglove Studio is built around the message-stream metaphor of MCAP, Rerun starts from a different axiom: every value you log has an entity path, a timeline, and an archetype, and everything else (views, blueprints, RRD chunks, fleet dashboards) is a derived projection of that core. This tutorial walks through installing the Rerun SDK 0.x for Python and Rust, recording multimodal streams from a real robot, understanding the RRD column store on disk, wiring up a ROS 2 bridge for sensor_msgs and TF, and scaling from a single robot laptop to fleet-wide telemetry with tiered storage. By the end you will have a 30-line script that logs a full robot scene end-to-end, plus a mental model for when Rerun is the right call and when you should reach for PlotJuggler, Foxglove, or a custom Grafana stack instead.
Architecture at a glance





By way of orientation, this tutorial assumes you have written at least a small ROS 2 node, you understand TF and basic 3D geometry, and you have at least skimmed a rosbag2 recording in your career. You do not need prior Rerun experience — the SDK surface area you need fits on one screen — but you do need to be comfortable enough with Python or Rust to read 30 lines of logging code and understand it. The same content applies to non-ROS robotics stacks (Drake, Isaac Sim, custom in-house frameworks), with the integration patterns swapped accordingly; the core Rerun mental model does not depend on ROS at all.
Why Robot Telemetry Needs a New Viewer
Robot telemetry is fundamentally spatiotemporal multimodal data, and legacy tools force engineers to flatten it into single-modality views that lose the cross-correlations debugging actually needs. A typical robot frame contains a depth point cloud, two RGB streams, a base TF, joint states, a planner cost map, IMU at 200 Hz, and a node-graph log line — all timestamped, all related, none easily co-visualized in rviz.
The classic stack is rosbag2 plus rviz2 plus rqt_plot plus matplotlib notebooks. Each tool owns one axis of the data: rviz owns 3D, rqt_plot owns time series, notebooks own tensors. Cross-tool correlation is a manual exercise in clipboard timestamps. The newer entrants — Foxglove Studio, PlotJuggler, and Rerun — all attack this fragmentation, but they make different bets. Foxglove bets on MCAP and panels-over-streams. PlotJuggler bets on time-series ergonomics. Rerun bets on a unified type system rooted in Apache Arrow, columnar on-disk, with deterministic replay.
That bet has implications you only see after a few months of use. Because every value is typed and lives in an entity-path tree, search and filter become first-class — “show me every Image archetype under world/robot/cameras between frame_id 4000 and 5000″ is one query call. Because the on-disk format is columnar Arrow, a 30 GB recording can be sliced into a 200 MB share-with-a-colleague reproducer in seconds, without re-encoding. Because the viewer reads the same data structure the SDK writes, “did this look the same yesterday?” is a real question with a real answer rather than a screenshot diff exercise.
The original Rerun thesis, articulated in the Rerun architecture docs, is that spatiotemporal data deserves a typed log the way structured logs replaced printf. You do not log “string with a timestamp”; you log a Points3D with a timeline assignment, an entity path, and component arrays. Once that is your atomic unit, view composition becomes a query problem rather than a wiring problem. That shift is what makes Rerun usable for debugging perception stacks where a single bad transform breaks five downstream views.
The deeper reason this matters is that robot bugs are almost always join bugs. The depth camera was fine; the IMU was fine; but the relative timestamp between them drifted by 35 ms and the EKF diverged. The planner published a valid cost map; the controller was fine; but the TF tree had a stale leg and the robot drove into the shelf. None of those bugs are visible if you look at each modality in isolation. A typed log with shared timelines and an entity-path tree gives you the join key for free — scroll the scrub bar and every view re-resolves against the same time, every transform re-inherits down the same tree. The cost of that property in 2020 was prohibitive; the bet Rerun made is that columnar Arrow plus modern GPUs have made it cheap enough to be the default.
Rerun.io Robotics Telemetry Visualization: Reference Architecture
Rerun.io robotics telemetry visualization is organized as a logging SDK that produces Arrow record batches, a chunk-based RRD store that holds them, and a viewer process that queries the store through a typed entity-path tree. The Python SDK 0.21+, Rust crate re_sdk, and the C++ rerun_cpp library all emit the same wire format, which means a Rust robot stack and a Python notebook can write into the same recording.

The four pieces that matter on a robot are the SDK, the recording stream, the viewer, and the storage layer. The SDK exposes rr.log(entity_path, archetype) as the single ingestion call. Entity paths look like filesystem paths — world/robot/base_link/camera_front — and form a tree that the viewer uses to scope views and inherit transforms. Archetypes are the typed records: Image, DepthImage, Points3D, Mesh3D, Transform3D, Boxes3D, TextLog, Tensor, BarChart, Scalar, LineStrips3D, and around 30 more. Every archetype decomposes into Arrow component arrays — positions, colors, radii, class IDs — so adding a custom archetype is a matter of declaring component schemas.
The recording stream is the buffered conduit between SDK and sink. It has three operating modes. In-process means the viewer is spawned in the same Python interpreter via rr.init(spawn=True), useful for notebooks. TCP mode connects to a separate viewer process at rerun+http://localhost:9876 — this is the standard robot-laptop split. RRD-file mode (rr.save("trace.rrd")) writes Arrow chunks to disk for later replay. All three modes share the same RecordingStream object, so swapping is a one-line change.
The viewer is a Rust application built on the egui immediate-mode GUI and wgpu. It runs natively, in the browser via WebAssembly, or as a Jupyter widget. The viewer holds a query engine that resolves entity paths against the active timeline and produces frames for each open space view. Views are not hand-wired to topics; they are declarative blueprints that say “show me entities matching world/** in a 3D view, with world/robot/camera/* highlighted.” A blueprint is itself stored as a versioned record, so you can ship a default blueprint with your robot package and version it in Git.
Because the viewer is built on wgpu, it runs on Vulkan, Metal, DirectX 12, and WebGPU with the same code path. That matters in practice because a single recorded RRD opens identically on a Linux dev box, a MacBook, and a Chrome tab on the project manager’s laptop with no per-platform tweak. The WASM build is the secret weapon — sending a debug link to a colleague is a URL, not a “first install these dependencies” thread. Teams that have not used a WASM-deployed viewer before underestimate how much friction this removes from cross-functional debugging.
Storage is the layer most engineers underestimate. Rerun’s RRD file format is a sequence of Arrow chunks with index columns for time and entity path. Chunks are the unit of write, query, and retention. A typical robot run might produce one chunk per second per entity, with each chunk holding ten to a hundred rows. Because Arrow is columnar, queries like “give me all depth images on timeline frame_id between 100 and 200 under world/robot/cameras/” can skip irrelevant chunks via min/max metadata, the same trick that makes Apache Parquet fast.
Deep Dive: Recording, Replay, and the RRD Format
Recording in Rerun is a side-effect-free operation that pushes Arrow record batches into a stream — there is no callback registry, no topic discovery, no rosmaster equivalent, which makes the API safe to call from any thread or coroutine. Replay reverses the dataflow: chunks are read back from RRD, fed into the same query engine the live recorder uses, and rendered identically. This symmetry is what makes “log once, view live or offline” actually work, and it is the property that rviz never had.
A worthwhile mental exercise before writing any code: list every modality you want to see when something goes wrong on your robot. Most teams come up with eight to twelve — RGB cameras, depth, LiDAR, IMU, joint states, TF, planner cost map, control torques, autonomy state machine, log lines, network health, battery. The job of the telemetry viewer is to make all of those scrubbable on one synchronized timeline, with hierarchical transforms applied automatically and with the option to drill into the raw numpy arrays when the visualization is not enough. If the viewer cannot do that, you will end up cross-referencing screenshots, which is the failure mode every team eventually outgrows.
Installing the SDK and First Stream
Install with pip install rerun-sdk==0.21.0 for Python or add rerun = "0.21" to Cargo.toml for Rust. The viewer ships as a separate binary, rerun, installable via pip install rerun-sdk (it bundles the viewer) or cargo install rerun-cli. As of Rerun 0.21 (April 2026), the SDK and viewer must match on minor version — a 0.20 SDK cannot stream to a 0.21 viewer cleanly.
The minimum recording is three lines:
import rerun as rr
import numpy as np
rr.init("warehouse_robot", spawn=True) # spawns viewer
rr.log("world/robot/lidar", rr.Points3D(
positions=np.random.rand(2048, 3) * 5.0,
colors=np.random.randint(0, 255, (2048, 3), dtype=np.uint8),
radii=0.02,
))
That call sends one Points3D record on the default timeline log_time and shows up immediately in the viewer’s 3D space view. The entity path world/robot/lidar is created lazily; no schema declaration is required.
The 30-Line End-to-End Robot Scene
Below is a complete, runnable example that logs a synthetic robot scene with TF, an RGB camera, a depth map, a 2D occupancy grid, planner cost as a scalar, and a status log line — across two timelines.
import rerun as rr, numpy as np, time
rr.init("amr_demo", spawn=True)
rr.log("world", rr.ViewCoordinates.RIGHT_HAND_Z_UP, static=True)
for frame in range(60):
t_robot = time.time()
rr.set_time_seconds("sim_time", frame * 0.1)
rr.set_time_sequence("frame_id", frame)
# base TF
rr.log("world/robot", rr.Transform3D(
translation=[0.05 * frame, 0.0, 0.0],
rotation=rr.RotationAxisAngle(axis=[0, 0, 1], radians=0.02 * frame),
))
# RGB
img = np.random.randint(0, 255, (240, 320, 3), dtype=np.uint8)
rr.log("world/robot/camera/rgb", rr.Image(img))
# Depth
depth = np.random.rand(240, 320).astype(np.float32) * 5.0
rr.log("world/robot/camera/depth", rr.DepthImage(depth, meter=1.0))
# Occupancy
grid = (np.random.rand(100, 100) > 0.7).astype(np.uint8) * 255
rr.log("world/map/occupancy", rr.SegmentationImage(grid))
# Planner cost
rr.log("metrics/planner/cost", rr.Scalar(np.exp(-frame / 30.0)))
# Status
rr.log("logs/nav2", rr.TextLog(f"frame {frame} ok", level="INFO"))
That script writes ~60 frames across six entities on two timelines (sim_time, frame_id) and lets you scrub time in the viewer with a slider that affects every view simultaneously. The same code, with rr.save("amr_demo.rrd") substituted for spawn=True, produces a portable file that opens with rerun amr_demo.rrd.
Timelines, Entity Paths, and Archetypes
Timelines are first-class. Every record carries assignments on any number of named timelines — log_time, frame_id, ros_time, sim_time, episode_step. The viewer’s time panel lets you pick which timeline drives the scrub bar, and queries return the closest record on that timeline. This is the right model for robotics where the wall clock, the simulation clock, and the message-sequence index disagree by design.
Entity paths inherit transforms hierarchically. If you log world/robot as a Transform3D, every child entity — world/robot/camera, world/robot/lidar — is rendered in the robot frame automatically. This is the same TF inheritance as ROS but without the lookup race conditions, because the transform is just data on a timeline rather than a separately-buffered tree.
There is a subtle but important consequence: because the entity tree is part of the data, not part of a separate configuration file, refactoring the robot’s coordinate hierarchy is a one-line change in the logging code rather than a coordinated edit across URDF, TF publishers, and rviz config. If you decide tomorrow that world/robot/arm/gripper should be world/robot/arm/wrist/gripper, you change the entity path and every downstream view, blueprint, and query re-resolves automatically. This is the kind of property that sounds minor in a slide deck and saves several engineer-weeks per year in practice.
Archetypes are the typed records that flow through the system. Each archetype is a struct of Arrow component arrays. Image is {buffer, format, draw_order}. Points3D is {positions, colors, radii, labels, keypoint_ids, class_ids}. You can log a subset of components and the renderer fills in defaults. You can also log custom components by registering a name and a schema, which is how teams attach domain-specific fields like confidence or tracker_id.
RRD File Format Internals
RRD on disk is a sequence of Arrow IPC chunks plus a small header. Each chunk holds rows for one entity path and one archetype, with all timeline indices stored as columns. Chunk size targets the 1–10 MB range in 0.21 — small enough for streaming, large enough for columnar scan throughput. The format is documented in the Rerun chunk store reference.

Retention is chunk-granular. You can drop chunks older than N seconds on a timeline by calling rr.send_blueprint(rr.GarbageCollectionPolicy(...)) or by configuring the recorder with a chunk_max_rows and chunk_max_age cap. Because chunks are immutable Arrow blobs, retention is a delete-files operation, not a record-level rewrite — which means TB-scale logs can be aged out without rewriting anything.
For analytics, RRD chunks can be converted to Parquet with rr.dataframe.load_recording("trace.rrd").to_parquet("trace.parquet") and queried with DuckDB or Polars. This is the seam that makes Rerun usable in offline ML pipelines: the same RRD that a roboticist scrubs in the viewer is a SQL-queryable table for a data scientist.
A practical consequence of the chunked Arrow layout is that compression and dedup are storage-engine concerns rather than application concerns. Image buffers can be stored as compressed JPEG blobs inside an Image archetype’s buffer component — the viewer decodes on read — which cuts a 240p RGB stream by 15x compared with raw uint8 storage. Point clouds are typically stored as native float32 arrays without compression because the viewer scans them at scrub speed and a Snappy decompress would dominate the frame budget. The point is that you, the robot engineer, do not have to choose between storage cost and replay speed at every callsite; you choose once per archetype and the chunk store does the rest.
ROS 2 Bridge: Wiring sensor_msgs and TF
The official path is the rerun-ros2-bridge, a ROS 2 node that subscribes to configurable topics and emits Rerun archetypes. It currently supports sensor_msgs/Image, sensor_msgs/PointCloud2, sensor_msgs/CompressedImage, nav_msgs/OccupancyGrid, tf2_msgs/TFMessage, geometry_msgs/PoseStamped, and visualization_msgs/MarkerArray. The bridge runs as a regular ROS 2 node, so DDS QoS profiles apply.

For unsupported message types, manual mapping is straightforward. A PointCloud2 with fields x, y, z, intensity becomes:
import sensor_msgs_py.point_cloud2 as pc2
def on_cloud(msg):
pts = np.array(list(pc2.read_points(msg, field_names=["x","y","z"])), dtype=np.float32)
intens = np.array(list(pc2.read_points(msg, field_names=["intensity"])), dtype=np.float32)
rr.set_time_nanos("ros_time", msg.header.stamp.sec * 10**9 + msg.header.stamp.nanosec)
rr.log(f"world/{msg.header.frame_id}/lidar",
rr.Points3D(positions=pts, colors=plt.cm.viridis(intens / intens.max())[:, :3]))
The pattern is: extract numpy arrays from the ROS message, set the timeline to the ROS header stamp, log under an entity path derived from frame_id. TF gets the same treatment — subscribe to /tf and /tf_static, walk the transform tree, log each parent→child edge as a Transform3D under the child’s entity path. The bridge maintainers recommend filtering by frame namespace to avoid logging 50 TF edges per tick on a humanoid.
For comparison, the ROS 2 message definitions themselves are stable across distros — the bridge mapping is portable from Humble to Jazzy.
Blueprints, Views, and Custom Layouts
Blueprints are the layer where Rerun stops being just a viewer and starts being a debugging environment. A blueprint declares the set of views, their layout, which entity paths each view subscribes to, what archetypes it renders, and how the time panel and selection panel behave. Blueprints are themselves Rerun records, transmitted over the same SDK channel, so they can be sent from a robot launch script, generated by a CI step, or stored as a blueprint.rrd file beside the data RRD.
The practical pattern is to define one blueprint per debugging scenario. A perception-debug blueprint pins a 3D space view showing all world/** entities, a 2D image view for world/robot/camera/rgb, a depth view for world/robot/camera/depth, a text log panel for logs/perception/*, and a time-series panel for confidence scalars. A motion-planning-debug blueprint pins a 3D view with planner cost as a color overlay, a graph view of joint scalars, and a tree of Transform3D entities. Switching between them is a single dropdown action because both are just records the viewer applies declaratively.
import rerun.blueprint as rrb
blueprint = rrb.Blueprint(
rrb.Vertical(
rrb.Spatial3DView(origin="/world", name="Robot scene"),
rrb.Horizontal(
rrb.Spatial2DView(origin="/world/robot/camera/rgb"),
rrb.TimeSeriesView(origin="/metrics"),
),
),
rrb.SelectionPanel(state="collapsed"),
)
rr.send_blueprint(blueprint)
Versioned blueprints checked into the robot’s repo are how you keep dashboards consistent across engineers, branches, and CI runs. Without them, every team member rebuilds the layout from scratch on first open and you lose the institutional memory of “this is the view that catches the bug.”
Live Stream vs Replay vs Embedded
Three deployment patterns dominate. Live stream is the on-robot SDK plus laptop viewer over TCP — useful during teleop sessions when latency matters and you want to interact with the robot while watching. RRD-file replay is recording on the robot to local NVMe, then scp-ing the file to a workstation for offline analysis — the default for night runs and CI playback. Embedded means the viewer is bundled into a notebook or web app via the Jupyter integration or WASM build — useful for sharing a specific bug repro with a colleague without setting up the full stack.
The trade is bandwidth versus interactivity. Live stream over Gigabit Ethernet handles a couple of HD cameras plus LiDAR at 10 Hz; beyond that, drop frames or compress. RRD-file replay scales with disk; a 50 GB RRD is normal for an hour-long autonomy run. Embedded is the lowest friction for debrief meetings but cannot show data it does not have.
A fourth, less-discussed pattern is split-mode: write to a local RRD on the robot while simultaneously streaming a downsampled summary to a workstation viewer. The SDK supports multiple sinks per RecordingStream since 0.19, so this is one call — rr.connect() plus rr.save() from the same script. The summary stream typically carries scalars, status text, and one downsampled image stream at 1 Hz, which fits comfortably in 5 Mbit/s. The full multimodal RRD stays on disk for post-run analysis. This mode is what most production robot teams in 2026 default to once they hit a few cameras.
Fleet Scale: From One Robot to a Hundred
Scaling Rerun.io robotics telemetry visualization from a single robot to a fleet means treating RRD files as the primary artifact, building tiered storage around them, and adding a server tier that can multiplex viewer connections — the SDK itself does not change, but the surrounding stack does. As of Rerun 0.21, there is no first-party hosted backend; teams build their own around the open-source viewer plus S3-compatible storage.

The tiered pattern that works in production at robotics shops in 2026 has four layers. Hot tier is the robot’s onboard SSD, storing the last 24 hours of RRD chunks for in-vehicle replay. Warm tier is a regional MinIO or S3 bucket where chunks are uploaded after a run completes, with object keys partitioned by robot_id/date/run_id. Cold tier is Glacier or B2 archive, holding everything older than 90 days. The catalog tier is a Postgres or DuckDB table that indexes runs by metadata — robot, route, software version, autonomy intervention count — so engineers can find the relevant RRD without scanning storage.
For real-time fleet ops, the pattern is to stream a low-cardinality summary timeline (scalar metrics, status logs, a downsampled trajectory) to a server-mode Rerun instance, while the full multimodal stream stays on the robot until a triage trigger. This keeps backhaul bandwidth manageable; a 50-robot fleet would otherwise saturate 10 GbE. The Rerun server mode docs describe the rerun --serve flag that exposes a WebSocket the viewer connects to.
Dashboards built on top of RRD are doable today by loading RRD as a Polars DataFrame and feeding the result to a Grafana or Streamlit panel. The seam is rr.dataframe.load_recording(path).filter(...).to_pandas(). This is how teams build “miles between interventions” charts that update as new RRDs land in the warm tier.
Cataloging is the layer that quietly determines whether your fleet telemetry stays useful at scale. A bucket of RRDs with no metadata is a write-only archive. A bucket of RRDs with a Postgres or DuckDB index that records, per run, the robot ID, route name, software version, total distance, intervention count, ambient temperature, and a freeform tag list, becomes a queryable corpus. The robotics engineering teams that get the most value out of Rerun in 2026 invest in this catalog early, and they ingest it from the same RRD via the DataFrame API rather than maintaining a separate metadata pipeline. The RRD becomes the source of truth for both visual debugging and aggregate fleet analytics, which is the prize the Arrow bet pays out.
Performance Profile: What to Expect on Real Hardware
On a 2024-class robot compute module (12-core Xeon, 32 GB RAM, NVMe), the Python SDK 0.21 sustains roughly 1500 archetype logs per second per thread before the Arrow encoding becomes the bottleneck. With three logging threads and chunk batching tuned to 50 ms flush intervals, that comfortably handles two 1080p cameras at 30 Hz, a 64-beam LiDAR at 10 Hz, a 16-DOF joint scalar stream at 200 Hz, and a steady drumbeat of text logs. The Rust SDK, with its zero-copy Arrow paths, pushes about 4x that throughput on the same hardware, which is why low-level perception nodes often choose Rust even in otherwise Python-heavy stacks.
The viewer’s frame budget on a developer laptop is dominated by 3D rendering rather than query cost. A scene with 100k points at 30 Hz scrub feels smooth on an integrated GPU; a million points at 30 Hz needs a discrete GPU. The viewer’s adaptive level-of-detail kicks in past ~500k points by default, which is a sensible tradeoff for debugging but worth knowing about when you are looking at a coverage map and wondering why a few points seem to flicker — that is sampling, not data loss.
Memory footprint is the other number to watch. Replaying a 30 GB RRD does not load 30 GB of RAM because the viewer mmaps the file and pages chunks on demand. Steady-state working set is typically a few hundred MB even for very large recordings, which is what makes it possible to open a multi-hour autonomy log on a 16 GB laptop without swapping.
How Rerun Compares to PlotJuggler, Foxglove Studio, and rviz
| Tool | Primary axis | On-disk format | Multimodal 3D | Browser | Best for |
|---|---|---|---|---|---|
| Rerun 0.21 | Spatiotemporal typed log | RRD (Arrow chunks) | First-class | WASM viewer | Perception, multimodal debug |
| Foxglove Studio | Panels over message streams | MCAP | Good | Native + web | ROS-centric mission debrief |
| PlotJuggler | Time-series ergonomics | CSV, rosbag, MCAP | Limited | No | Scalar-heavy signal analysis |
