Visualization Platform

Before you can build great robotics and AI systems, you need to see what they're doing. A text log that says "robot moved to position X" tells you almost nothing. But watching a live 3D point cloud of your robot's lidar scan, overlaid with its camera feed and battery level — that tells you everything.

That's what the CredVault Visualization Platform is for.

It lets you log any kind of data — 3D scenes, images, sensor readings, GPS tracks, model metrics, text events — and stream it to a visual viewer in real time. You can also save recordings to file and replay them later, frame by frame, to debug exactly what went wrong and when.


How It Works

The core idea is simple: you instrument your code with logging calls, and the viewer shows you everything as it happens.

Your code  →  rr.log(...)  →  CredVault Viewer

You decide what to log, when to log it, and how to organize it. The viewer handles the rest — rendering 3D scenes, plotting time-series, displaying images, and letting you scrub through time.

Data is organized into entities (like robot/camera, robot/lidar, sensor/temperature) and timelines (like frame, epoch, or wall-clock time). This means you can have dozens of data streams all synchronized on the same timeline, and scrub through them together.


Installation

Python

The Python SDK is the most common way to log data. Install it with pip:

pip install credvault-rerun-sdk

Requires Python 3.10 or higher. Once installed, import it as rerun:

import rerun as rr

JavaScript / Node.js

If you're building a web application or Node.js service:

npm install @credvault/rerun-viewer

React

If you want to embed the visualization viewer directly inside a React dashboard:

npm install @credvault/rerun-viewer-react

Your First Visualization

Let's start with the simplest possible example — logging a 3D point and opening the viewer.

import rerun as rr

# Initialize with a name for your recording
rr.init("my_first_visualization")

# Open the viewer (spawns a child process)
rr.spawn()

# Log a 3D point cloud
rr.log("scene/points", rr.Points3D(positions=[[0, 0, 0], [1, 0, 0], [0, 1, 0]]))

Run this and the CredVault viewer opens automatically, showing three points in 3D space.

Now let's make it more interesting by adding a timeline so you can scrub through frames:

import rerun as rr
import numpy as np

rr.init("timeline_example")
rr.spawn()

for frame in range(100):
    # Tell the viewer which frame this data belongs to
    rr.set_time("frame", sequence=frame)

    # Log a point that moves over time
    x = np.sin(frame * 0.1)
    y = np.cos(frame * 0.1)
    rr.log("scene/moving_point", rr.Points3D(positions=[[x, y, 0]]))

Now you can drag the timeline slider in the viewer and watch the point move.


Understanding Entities and Paths

Every piece of data you log goes to an entity path — a slash-separated string that organizes your data like a file system.

rr.log("robot/camera/rgb", ...)        # camera image
rr.log("robot/camera/depth", ...)      # depth image
rr.log("robot/lidar", ...)             # lidar point cloud
rr.log("robot/transform", ...)         # robot position
rr.log("metrics/loss", ...)            # training loss
rr.log("metrics/accuracy", ...)        # training accuracy

The viewer shows these as a tree on the left side. You can expand and collapse branches, toggle visibility, and inspect individual entities. Think of it like a scene graph — everything has a place in the hierarchy.

Good entity paths make your recordings much easier to navigate. Use descriptive names and group related data under the same parent path.


Understanding Timelines

A timeline is how you associate data with a point in time. You can have multiple timelines in the same recording.

# Frame-based timeline (for robotics, video)
rr.set_time("frame", sequence=42)

# Step-based timeline (for ML training)
rr.set_time("training_step", sequence=1000)

# Wall-clock time
import time
rr.set_time("wall_time", duration=time.time())

After calling set_time, every rr.log call until the next set_time will be associated with that timestamp. This is how the viewer knows where to place your data on the timeline.

You can also have static data — data that has no time and exists across all timelines. Use this for things that don't change, like camera calibration parameters or a 3D model of your environment:

# This camera calibration is valid for the entire recording
rr.log("robot/camera", rr.Pinhole(focal_length=500, width=640, height=480), static=True)

Logging 3D Data

Point Clouds

Point clouds are the most common 3D data type in robotics. They come from lidar sensors, depth cameras, and 3D reconstruction algorithms.

import numpy as np

# Generate a simple point cloud
positions = np.random.uniform(-1, 1, (1000, 3))
colors = np.random.uniform(0, 255, (1000, 3)).astype(np.uint8)

rr.log("scene/lidar", rr.Points3D(
    positions=positions,
    colors=colors,
    radii=0.02  # size of each point
))

3D Bounding Boxes

Use bounding boxes to show detected objects in 3D space:

rr.log("scene/detections", rr.Boxes3D(
    centers=[[1.0, 0.0, 0.5], [3.0, 1.0, 0.5]],
    half_sizes=[[0.5, 0.3, 0.5], [0.4, 0.4, 0.5]],
    labels=["robot_arm", "conveyor_belt"],
    colors=[[0, 255, 0], [255, 165, 0]]
))

Transforms and Coordinate Frames

When you have a robot with multiple joints or sensors, you need to track how each part relates to the others. Use Transform3D to define the position and orientation of each frame:

# Robot base position in the world
rr.log("robot/base", rr.Transform3D(
    translation=[x, y, 0],
    rotation=rr.Quaternion(xyzw=[0, 0, np.sin(yaw/2), np.cos(yaw/2)])
))

# Camera mounted on the robot (relative to base)
rr.log("robot/base/camera", rr.Transform3D(
    translation=[0.2, 0, 0.5],  # 20cm forward, 50cm up
    rotation=rr.Quaternion(xyzw=[0, 0, 0, 1])  # no rotation
))

The viewer understands the parent-child relationship between robot/base and robot/base/camera, so the camera moves with the robot automatically.

Arrows and Vectors

Useful for showing velocity, force, or direction:

rr.log("robot/velocity", rr.Arrows3D(
    origins=[[0, 0, 0]],
    vectors=[[vx, vy, vz]],
    colors=[[0, 200, 255]]
))

3D Meshes

Load and display 3D models of your robots or environment:

rr.log("scene/robot_model", rr.Mesh3D(
    vertex_positions=vertices,      # Nx3 array
    triangle_indices=faces,         # Mx3 array of vertex indices
    vertex_colors=colors            # optional per-vertex colors
))

Logging Images and Video

RGB Images

import numpy as np

# From a numpy array (H x W x 3, uint8)
image = get_camera_frame()  # your function that returns a numpy array
rr.log("robot/camera/rgb", rr.Image(image))

Depth Images

Depth images store distance values per pixel. The meter parameter tells the viewer what value represents 1 meter:

depth = get_depth_frame()  # H x W array of uint16 values in millimeters
rr.log("robot/camera/depth", rr.DepthImage(depth, meter=1000.0))

Segmentation Masks

Segmentation images use integer labels per pixel. You can define what each label means using an annotation context:

# Define what each class label means
rr.log("annotations", rr.AnnotationContext([
    (0, "background"),
    (1, "robot"),
    (2, "obstacle"),
    (3, "target"),
]), static=True)

# Log the segmentation mask
rr.log("robot/camera/segmentation", rr.SegmentationImage(mask_array))

Camera Calibration

If you log a Pinhole to the same entity as your image, the viewer can correctly project 3D points onto the image plane:

# Log camera intrinsics once (static)
rr.log("robot/camera", rr.Pinhole(
    focal_length=[fx, fy],
    principal_point=[cx, cy],
    width=640,
    height=480
), static=True)

# Log images on the same entity
rr.log("robot/camera", rr.Image(frame))

Logging Scalar Metrics

Scalars are single numeric values over time. Perfect for training metrics, sensor readings, and system stats.

# Training metrics
rr.set_time("epoch", sequence=epoch)
rr.log("train/loss", rr.Scalars(loss))
rr.log("train/accuracy", rr.Scalars(accuracy))
rr.log("train/learning_rate", rr.Scalars(lr))

# Robot sensors
rr.set_time("frame", sequence=frame)
rr.log("robot/battery", rr.Scalars(battery_pct))
rr.log("robot/speed", rr.Scalars(speed_ms))
rr.log("robot/temperature", rr.Scalars(motor_temp))

The viewer plots these as line charts automatically. You can view multiple scalars on the same chart by selecting them together.


Logging Text and Events

Text Logs

Use text logs to record events, warnings, and errors alongside your data:

rr.log("logs/system", rr.TextLog("Connected to cluster", level=rr.TextLogLevel.INFO))
rr.log("logs/system", rr.TextLog("High memory usage: 87%", level=rr.TextLogLevel.WARN))
rr.log("logs/system", rr.TextLog("Motor fault on joint 3", level=rr.TextLogLevel.ERROR))
rr.log("logs/robot", rr.TextLog("Reached waypoint 5"))

Text logs appear in the viewer's log panel and are synchronized with the timeline, so you can see exactly what was happening when an event occurred.

Rich Text Documents

For longer content like reports or markdown:

rr.log("docs/summary", rr.TextDocument(
    "## Run Summary\n\nAll systems nominal.\n\n- Battery: 87%\n- Speed: 1.2 m/s",
    media_type="text/markdown"
))

Logging GPS and Geographic Data

If your robots operate outdoors, you can log GPS coordinates and see them on a map in the viewer.

# A single GPS position
rr.log("robot/gps", rr.GeoPoints(
    lat_lon=[[-1.286389, 36.817223]],  # latitude, longitude
    radii=10.0
))

# A GPS track (path the robot has taken)
rr.log("robot/path", rr.GeoLineStrings(
    lat_lon=[[
        [-1.286, 36.817],
        [-1.290, 36.820],
        [-1.295, 36.825],
        [-1.300, 36.830],
    ]]
))

Logging Graphs and Networks

Useful for visualizing pipeline dependencies, neural network architectures, or any graph-structured data:

rr.log("pipeline/graph", rr.GraphNodes(
    node_ids=["input", "conv1", "relu1", "conv2", "relu2", "output"],
    labels=["Input", "Conv 3x3", "ReLU", "Conv 3x3", "ReLU", "Output"]
))

rr.log("pipeline/graph", rr.GraphEdges(
    edges=[
        ("input", "conv1"),
        ("conv1", "relu1"),
        ("relu1", "conv2"),
        ("conv2", "relu2"),
        ("relu2", "output"),
    ]
))

Embedding the Viewer in React

If you want to embed the visualization viewer directly in your CredVault dashboard or web app, use the React component:

import { WebViewer } from "@credvault/rerun-viewer-react"

export default function RobotDashboard() {
  return (
    <div style={{ width: "100%", height: "600px" }}>
      <WebViewer
        width="100%"
        height="600px"
        rrd="https://credvault-production.up.railway.app/api/rerun/recording/abc123"
      />
    </div>
  )
}

The rrd prop accepts a URL to a .rrd recording file. You can generate these from Python using rr.save("recording.rrd") and then serve them from your backend.


Complete Example: Robot Telemetry

Here's a complete example that logs everything you'd want from a mobile robot:

import rerun as rr
import numpy as np
import time

rr.init("robot_telemetry")
rr.spawn()

# Log static camera calibration once
rr.log("robot/camera", rr.Pinhole(
    focal_length=500, width=640, height=480
), static=True)

frame = 0
while True:
    rr.set_time("frame", sequence=frame)

    # Robot position and orientation
    x = frame * 0.01
    y = np.sin(frame * 0.05) * 2
    yaw = frame * 0.02
    rr.log("robot/transform", rr.Transform3D(
        translation=[x, y, 0],
        rotation=rr.Quaternion(xyzw=[0, 0, np.sin(yaw/2), np.cos(yaw/2)])
    ))

    # Camera image
    rr.log("robot/camera", rr.Image(get_camera_frame()))

    # Lidar scan
    lidar_points = get_lidar_scan()
    rr.log("robot/lidar", rr.Points3D(positions=lidar_points, radii=0.02))

    # Sensor readings
    rr.log("robot/battery", rr.Scalars(get_battery_level()))
    rr.log("robot/speed", rr.Scalars(get_speed()))
    rr.log("robot/motor_temp", rr.Scalars(get_motor_temperature()))

    # Status log
    rr.log("robot/logs", rr.TextLog(f"Frame {frame}: position ({x:.2f}, {y:.2f})"))

    frame += 1
    time.sleep(0.05)  # 20 fps

Complete Example: ML Training

Log your training run and watch metrics in real time:

import rerun as rr

rr.init("training_run_001")
rr.spawn()

model = build_model()
optimizer = build_optimizer()

for epoch in range(200):
    train_loss, train_acc = train_one_epoch(model, optimizer)
    val_loss, val_acc = validate(model)

    rr.set_time("epoch", sequence=epoch)

    # Training metrics
    rr.log("metrics/train/loss", rr.Scalars(train_loss))
    rr.log("metrics/train/accuracy", rr.Scalars(train_acc))

    # Validation metrics
    rr.log("metrics/val/loss", rr.Scalars(val_loss))
    rr.log("metrics/val/accuracy", rr.Scalars(val_acc))

    # Log a sample prediction every 10 epochs
    if epoch % 10 == 0:
        sample_input, sample_pred = get_sample_prediction(model)
        rr.log("predictions/input", rr.Image(sample_input))
        rr.log("predictions/output", rr.Image(sample_pred))
        rr.log("training/events", rr.TextLog(f"Epoch {epoch}: val_loss={val_loss:.4f}"))

Saving and Replaying Recordings

Instead of streaming to a live viewer, you can save everything to a .rrd file and replay it later:

rr.init("my_recording")
rr.save("robot_run_2026_05_11.rrd")  # all logs go to this file

# ... your logging code ...

To replay it:

rerun robot_run_2026_05_11.rrd

Or load it in the React viewer:

<WebViewer rrd="/recordings/robot_run_2026_05_11.rrd" width="100%" height="600px" />

Using in Jupyter Notebooks

The viewer embeds directly in Jupyter notebooks — no separate window needed:

import rerun as rr

rr.init("notebook_demo")
rr.notebook_show(width=900, height=500)

# Everything you log appears inline in the notebook
rr.log("points", rr.Points3D([[1, 2, 3], [4, 5, 6], [7, 8, 9]]))
rr.log("metrics/value", rr.Scalars(42.0))

Output Options Summary

What you wantHow
Open viewer on your machinerr.spawn()
Save to file for laterrr.save("recording.rrd")
Stream to a remote viewerrr.connect_grpc("ip:port")
Embed in a web app@credvault/rerun-viewer-react
Use in a Jupyter notebookrr.notebook_show()
Get raw bytes (for custom transport)rr.memory_recording()

Support

Questions or issues with the visualization platform? File a support ticket from your dashboard or email credvault.net/support.