SDK

Five lines to
your first frame

One schema, four bindings, no build step. Add it to a script you already have and the viewer opens itself.

$ pip install dalaran-sdk

Python

3.9 โ€“ 3.13. NumPy-native: pass arrays straight through, zero copies where the dtype allows.

pip install dalaran-sdk

Rust

The reference implementation. Everything else binds to this crate; no FFI overhead on the hot path.

cargo add dalaran

C++

C++17, header-first with a prebuilt static core. CMake and Bazel targets, or vendor the amalgamation.

FetchContent_Declare(dalaran ...)

Wire protocol

gRPC + Arrow IPC, fully specified. Write a binding for any language in an afternoon.

dalaran.proto ยท v3

Quickstart

Hello, world-space

import dalaran as dl
import numpy as np

dl.init("hello-world", spawn=True)     # spawns the viewer if it isn't running

pts = np.random.normal(size=(50_000, 3))
rgb = ((pts - pts.min()) / pts.ptp() * 255).astype(np.uint8)

dl.log("world/cloud", dl.Points3D(pts, colors=rgb, radii=0.01))
Recipes

Patterns you'll reach for on day one

Camera + detections, correctly projected

Log the intrinsics once and every 3D entity under that path is drawn in the image with the right perspective.

dl.log("world/cam", dl.Pinhole(focal=725.0, width=1920, height=1080), static=True)
dl.log("world/cam", dl.Transform3D(translation=t, rotation=q))

dl.log("world/cam/rgb", dl.Image(frame))
dl.log("world/cam/rgb/dets", dl.Boxes2D(
    array=boxes, array_format="XYWH",
    labels=names, class_ids=ids,
))

A transform tree that actually holds

Each joint logs only its own local transform. The viewer composes the chain, so a fix at the base propagates everywhere.

dl.log("world/base",                 dl.Transform3D(translation=odom))
dl.log("world/base/arm/j0",          dl.Transform3D(rotation=dl.RotationAxisAngle([0,0,1], q0)))
dl.log("world/base/arm/j0/j1",       dl.Transform3D(rotation=dl.RotationAxisAngle([0,1,0], q1)))
dl.log("world/base/arm/j0/j1/tool",  dl.Transform3D(translation=[0, 0, 0.19]))

# Anything logged under .../tool now moves with the gripper
dl.log("world/base/arm/j0/j1/tool/grasp", dl.Points3D(contact_pts))

Metrics beside the geometry

Scalars land on the same timeline as your point clouds, so a spike in error and the frame that caused it are one click apart.

dl.log("metrics/loss",      dl.Scalar(loss))
dl.log("metrics/grad_norm", dl.Scalar(gn))
dl.log("metrics/lr",        dl.Scalar(sched.get_last_lr()[0]))

dl.log("debug/activations", dl.Tensor(feat.detach().cpu().numpy(),
                                      dim_names=["batch", "ch", "h", "w"]))
dl.log("logs/train", dl.TextLog(f"step {step}: {loss:.4f}", level=dl.TextLogLevel.INFO))
Integrations

Meets your stack where it is

ROS 2

A bridge node subscribes to your topics and maps standard message types onto archetypes automatically. TF becomes a transform tree; PointCloud2 becomes a cloud.

PyTorch & JAX

Log tensors without leaving the training loop. Detach, stride and colormap handling are built in.

Open3D & trimesh

Pass geometry objects directly; the SDK reads vertices, normals and texture coordinates off the object.

MuJoCo & Isaac

Stream simulator state each step and compare a rollout against the physical run on a shared clock.

Jupyter & Colab

dl.notebook_show() embeds the real viewer in an output cell โ€” interactive, not a static image.

CI

Write .dlr artifacts from your test suite and attach them to the run. Failures arrive as recordings.

CLI

Everything from the terminal

CommandWhat it does
dalaran <file.dlr>Open a recording in the native viewer
dalaran --serve-webServe the WebAssembly viewer on localhost
dalaran ls run.dlrList entities, components and timeline ranges
dalaran filter --entity 'world/cam/**'Extract a subtree into a smaller recording
dalaran merge a.dlr b.dlr -o both.dlrCombine recordings onto one timeline
dalaran compare base.dlr new.dlrOpen two runs in a synced side-by-side layout
dalaran analytics disableTurn off usage telemetry permanently

Ready when you are