Dalaran docs
Everything from the first install to the shape of the wire protocol. If you're new, start at the quickstart and come back for the concepts once something surprises you.
0.29.x line. The data model changed in
0.26; recordings written before that need dalaran migrate.Installation
The SDK and the viewer ship together. Installing the Python package gives you both.
pip install dalaran-sdk # verify — prints the version of both the SDK and the bundled viewer dalaran --version
uv add dalaran-sdk uv run dalaran --version
cargo add dalaran cargo install dalaran-cli # the standalone viewer binary
include(FetchContent) FetchContent_Declare(dalaran URL https://github.com/dalaran-labs/dalaran/releases/download/v0.29.1/dalaran_cpp_sdk.zip) FetchContent_MakeAvailable(dalaran) target_link_libraries(my_app PRIVATE dalaran::dalaran)
Supported platforms: macOS 12+ (arm64, x86-64), Linux glibc 2.28+ (x86-64, aarch64), Windows 10+. Jetson and Raspberry Pi wheels are published for aarch64.
Quickstart
Three things happen in every Dalaran program: you initialize a recording stream, you set a time, and you log an entity. Everything else is elaboration.
import dalaran as dl
import numpy as np
# 1. Open a stream. spawn=True launches the viewer and connects to it.
dl.init("quickstart", spawn=True)
for i in range(200):
# 2. Position everything that follows on a timeline.
dl.set_time("frame", sequence=i)
theta = i * 0.05
pts = np.stack([
np.cos(np.linspace(0, 6.28, 2000) + theta),
np.sin(np.linspace(0, 6.28, 2000) * 2 + theta),
np.linspace(-1, 1, 2000),
], axis=1)
# 3. Log an entity. The archetype decides how it renders.
dl.log("world/helix", dl.Points3D(pts, colors=[110, 231, 183], radii=0.008))
dl.log("metrics/theta", dl.Scalar(theta))
Run it. The viewer opens, the helix twists, and the scalar plot fills in beside it. Drag the timeline scrubber and both views move together — that synchronization is the whole point.
Opening the viewer
There are four ways to get pixels on screen:
dl.init(..., spawn=True)— launch and connect to the native viewerdl.connect("127.0.0.1:9876")— attach to a viewer that's already runningdl.save("run.dlr")— write a file, open it later withdalaran run.dlrdl.notebook_show()— embed the viewer in a Jupyter output cell
dl.save() or a Hub connection over
spawn. A viewer process that dies shouldn't take your logging with it — but if it does, the SDK
buffers and drops rather than blocking your main loop.Entities & paths
An entity path is a slash-separated address in your world: world/robot/arm/gripper. It is the
unit that almost every feature operates on.
- Transforms logged at a path apply to everything beneath it
- Visibility, color overrides and blueprint queries select by glob:
world/**/cam_* - Paths are created implicitly — there's no registration step
Choose paths that describe physical containment, not data provenance. world/robot/cam_front
composes correctly; logs/camera_node/output does not.
Components
A component is one typed, columnar array stored against an entity at a point in time —
Position3D, Color, Radius, Text. Components are the
physical storage; archetypes are the ergonomic API on top.
You can log components directly when you need to update just one:
# Log geometry once...
dl.log("world/cloud", dl.Points3D(pts), static=True)
# ...then recolor it every frame without resending positions
for i, colors in enumerate(color_stream):
dl.set_time("frame", sequence=i)
dl.log("world/cloud", [dl.components.Color(colors)])
Timelines
Every log call is stamped on whatever timelines are currently set. Three kinds exist:
| Kind | Call | Use for |
|---|---|---|
| Sequence | set_time(name, sequence=i) | Frame or step indices |
| Timestamp | set_time(name, timestamp=t) | Wall clock, sensor clocks, epochs |
| Duration | set_time(name, duration=s) | Time since start, simulation time |
Queries use latest-at semantics: at time t, each component takes the most recent value logged at or before t. This is why you only need to log what changed.
Static data
Data marked static=True has no timestamp and is visible at every point on every timeline. Use it
for camera intrinsics, meshes, coordinate-system declarations and annotation contexts.
Archetypes
The full set, grouped by what they draw:
| Group | Archetypes |
|---|---|
| 3D | Points3D · Boxes3D · LineStrips3D · Mesh3D · Arrows3D · Asset3D · Ellipsoids3D · Capsules3D |
| 2D | Image · DepthImage · SegmentationImage · EncodedImage · Boxes2D · Points2D · LineStrips2D |
| Spatial | Transform3D · Pinhole · ViewCoordinates · InstancePoses3D |
| Plots | Scalar · SeriesLine · SeriesPoint · BarChart |
| Other | Tensor · TextDocument · TextLog · AnnotationContext · Clear |
Transforms
Log each frame's transform relative to its parent, never to the world. The viewer composes the chain.
dl.log("world", dl.ViewCoordinates.RIGHT_HAND_Z_UP, static=True)
dl.log("world/base", dl.Transform3D(translation=[x, y, 0.0],
rotation=dl.Quaternion(xyzw=q)))
dl.log("world/base/lidar", dl.Transform3D(translation=[0.0, 0.0, 0.42]))
# Logged in the lidar's own frame — no manual matrix math required
dl.log("world/base/lidar/points", dl.Points3D(raw_returns))
Images & cameras
Log a Pinhole at a path to declare it a camera. Any 3D entity beneath that path is projected
into the image plane; any 2D entity is drawn in pixel coordinates.
dl.log("world/cam", dl.Pinhole(focal=[725.0, 725.0],
principal_point=[960.0, 540.0],
width=1920, height=1080), static=True)
dl.log("world/cam", dl.EncodedImage(path="frame_0412.jpg")) # no decode cost
dl.log("world/cam/depth", dl.DepthImage(depth_m, meter=1.0))
dl.log("world/cam/mask", dl.SegmentationImage(labels))
Prefer EncodedImage for video-rate streams: it stores the compressed bytes and decodes lazily
on the GPU, which typically cuts recording size by an order of magnitude.
Scalars & tensors
Scalar is a single number per timestamp; the viewer accumulates them into a series. Style the
series once with SeriesLine, logged statically.
dl.log("metrics/loss", dl.SeriesLine(color=[110, 231, 183], name="train loss"), static=True)
for step, loss in enumerate(history):
dl.set_time("step", sequence=step)
dl.log("metrics/loss", dl.Scalar(loss))
# N-dimensional data goes to the tensor inspector
dl.log("debug/attn", dl.Tensor(attn, dim_names=["head", "query", "key"]))
Blueprints
A blueprint describes the viewer layout as data: which views exist, what each one contains, and how they're
arranged. Pass one to dl.init or send it to a running viewer.
import dalaran.blueprint as dlb
bp = dlb.Blueprint(
dlb.Tabs(
dlb.Spatial3DView(origin="world", name="Scene",
contents=["world/**", "-world/debug/**"]),
dlb.Grid(
dlb.Spatial2DView(origin="world/cam_front"),
dlb.Spatial2DView(origin="world/cam_rear"),
dlb.TensorView(origin="debug/attn"),
dlb.TimeSeriesView(origin="metrics"),
),
),
collapse_panels=True,
)
dl.send_blueprint(bp) # apply to the running viewer
Recordings & files
A .dlr file is a self-describing columnar archive: schema, data and metadata in one place, with
no external dependency on the code that wrote it. Files are append-friendly and memory-mapped on open.
- Recording ID — a UUID per stream; two processes can write to the same viewer without colliding
- Application ID — groups recordings that belong to the same program
- Chunks — data is batched before flush; tune with
DALARAN_FLUSH_NUM_BYTES
CLI
dalaran run.dlr # open in the native viewer dalaran --serve-web --port 8080 # serve the wasm viewer dalaran ls run.dlr # entities, components, time ranges dalaran filter run.dlr --entity 'world/cam/**' -o cam_only.dlr dalaran merge left.dlr right.dlr -o combined.dlr dalaran compare base.dlr candidate.dlr # synced side-by-side dalaran migrate old.dlr -o new.dlr # upgrade a pre-0.26 recording dalaran analytics disable # opt out of telemetry, permanently
API reference
dl.init(application_id, *, recording_id=None, spawn=False, default_blueprint=None)
Creates the global recording stream. Call once per process, before any log. Returns the stream
so you can hold it explicitly in library code instead of relying on the global.
dl.log(entity_path, *archetypes_or_components, static=False)
Writes data to an entity at the current time. Accepts one or more archetypes, or a list of raw components. Non-blocking: data is batched and flushed on a background thread.
dl.set_time(timeline, *, sequence=None, timestamp=None, duration=None)
Sets the current position on a timeline for subsequent log calls. Exactly one of the three keyword arguments
must be given. Call dl.disable_timeline(name) to stop stamping on it.
dl.save(path) · dl.connect(addr) · dl.serve_web(port)
Sinks. A stream has exactly one active sink; calling a second replaces the first and flushes the buffer.
dl.notebook_show(*, width=960, height=540, blueprint=None)
Renders the WebAssembly viewer inline in a notebook cell, bound to the current recording.
Troubleshooting
set_time
lands on an implicit "log time" timeline that the current view may not be showing. Also confirm the
entity isn't filtered out by the active blueprint's contents query.ViewCoordinates declaration, so Z-up data is drawn Y-up.Image where EncodedImage would do, and
for static geometry being re-logged every frame. dalaran ls --sizes run.dlr ranks entities by
bytes on disk.DALARAN_FLUSH_NUM_BYTES, or log at a lower rate for the
heaviest entity. Setting DALARAN_STRICT=1 in development surfaces the warnings that are
otherwise silent.Something wrong or missing on this page? Tell us