Reference
Every name a .dreamrc can use, with its config keys. The
architecture is why the system has this shape, the
spec is the grammar, and the
contract says what your data must look like; this
page is the lookup table — the names here are exactly the registries the
validator checks, so anything not listed fails validation with a did-you-mean.
For the pipeline and the registries in code, see the architecture's
under-the-hood section.
Kinds — the catalog's, and the view's
Both say "kind" about the same field, and they mean different things. Keeping the two apart is the whole design.
Inventory kinds — what the catalog states
Field.kind says how the BYTES ARE ADDRESSED and nothing about what they
mean. Six values, closed, and nothing turns a name or a shape into one of them:
| kind | the container said | meta carries |
|---|---|---|
video | an encoded stream, seekable | codec / dimensions when reported |
frames | encoded images, one per frame | count, fps?, ext |
image | one encoded image | ext |
tensor | numbers | dtype, shape, names — verbatim |
text | strings | |
file | an address, and nothing claimed | ext |
For a loose file the extension picks the addressing and only that: .mp4 /
.webm → video, .jpg / .png → image, .csv / .parquet → tensor.
.vtt, .glb and .json are all file — "labelled spans", "geometry"
and "keypoints" are meanings, and meanings come from the .dreamrc.
meta is raw facts, recorded because they are free: dtype, shape, names,
ext, schema, codec. A decoder reads them to do its job — the schema name
selects an MCAP decoder, the extension selects a parser — and nothing reads
them to decide what a field is.
Payload kinds — what a view asks for
read(ref, { as }) decodes into one of these. The as comes from the slot the
field was bound to, so the interpretation is written in the .dreamrc:
| payload | what comes back | the field must be |
|---|---|---|
video | { url, window? } — streamable; window is this episode's span of a shared file | a video field |
image | { url } | an image field |
frames | { count, fps?, frameAt(i) } — lazy per-frame fetch | a frames field |
series | { timestamps, columns } — per-dim numeric traces | any numeric scalar or [n]; names label the traces |
segments | { segments: [{ start, end, label }] } — any time-range → label | an int column plus its label table, a string column, or a .vtt / .srt |
keypoints | { keypoints } — pixel space, fps, sparse frames, skeleton | float [J,2] / [J,3], or a COCO .json |
depth | { count, fps?, at(i) } — raw values, never pre-colorized | float [H,W] or [H,W,1], or frames of encoded 16-bit PNGs |
pointcloud | { count, fps?, at(i) } | float [N,3] or [N,6] (xyz, or xyz + rgb) |
transform3d | { timestamps, values, layout } — position + quaternion | float [7] or [N,7] |
vertices3d | { count, fps?, vertexCount, at(i) } — lazy | float [V,3] |
pose3d | { timestamps, joints, shape } — per-frame point sets | float [J,3] or a flat [J*3] |
mesh3d | { url, format } — static geometry; its node names bind the tracks above | a .glb / .gltf / .obj / .stl |
file | { url, ext? } — an address a component parses itself | anything |
Ask for one the bytes cannot become and the read throws naming both sides
(observation.state is float32 [6]; keypoints needs [J,2] or [J,3]) instead of
drawing something wrong.
Which payloads a field can serve — and when you write as:
The bridge between the two tables. It reads the addressing kind and nothing else — no name, no shape, no extension:
| addressed as | can be read as |
|---|---|
video | video |
image | image |
frames | frames, depth — depth shipped as 16-bit PNGs is a codec question, not a meaning |
tensor | series, segments, keypoints, pose3d, transform3d, vertices3d, depth, pointcloud |
text | segments |
file | file, mesh3d, segments, keypoints |
Intersect a field's row with the payloads its slot reads (per component):
- exactly one survives — nothing to write; the container already settled it
(a
videobound tovideoStack.cameras, atensorbound topointCloud); - more than one — the binding says which with
as:, and until it does the panel refuses and prints the choice (a.jsonbound tooverlaysis a skeleton file or a caption track, and nothing about the bytes says which); - none — that slot cannot show that field, and it says so by name.
A declared as: short-circuits the table entirely: it is the author speaking,
and it is how a LeRobot subtask_index is bound as: segments and joins its
label table.
Where a payload comes from — a feature in the dataset's own container, or a standard file beside it (WebVTT/SRT, COCO, glTF, parquet) — is in the contract. No payload requires a format of our own — where a DreamLake structure exists (the motion-track Parquet profile v1), an existing standard carries the same payload.
Data the library does not know
The payload set versions with the library, but shipping your own end to end needs no library change:
- Declare where it is —
dataset.annotations: { gaze: "annotations/gaze_{episode_index:06d}.json" }. The field lands in every episode's catalog as afilewith itsextinmeta. Nothing is claimed about the contents. - Render it — the host registers a component
(
registerComponent({ name: 'gazePanel', component, reads: { fields: ['file'] } })) whose slot readsfile, fetches the URL and parses its own wire format. The.dreamrcwritesview: gazePaneland binds the field — no schema change, no library change. - Promote it — once a payload proves general (the way
segmentscovers tasks/subtasks/actions/phases), it graduates into the library: aPayloadmember, a decoder that accepts the wire shapes found in the wild (seenormalizeSegments), and a built-in component with a slot that reads it.
The same registries extend the other axes: registerFormat for a new
dataset layout, registerStorage for a new backend — including composite
backends (an overlay bucket layered over a read-only base is just another
two-method Storage whose list merges and whose resolveUrl checks the
overlay first).
Payload contracts in full
The exact structures a read(ref, { as }) returns — what a component is
handed, and therefore what a host-registered component should expect. These
are the in-memory contract: our one self-defined schema layer, versioned
with the library (why it exists).
Every type below is exported from @dreamlake/viz/dataset-viz, and the
source (dataset-viz/types.ts) is the authority — this listing mirrors it.
Storage drivers
Declared in a standalone file's storage: block, or injected by the host for
a file at its dataset root (who writes it).
Configs carry identifiers only — never credentials.
| driver | config keys | notes |
|---|---|---|
http | url required — the dataset root URL (absolute, or site-relative when co-hosted) | resolveUrl joins url + path. Listing reads a co-located index.json manifest per directory — a static host cannot enumerate itself, so glob enumeration needs the manifests; episodes: auto formats don't. |
hf | repo required (e.g. lerobot/pusht) · root subpath within the repo · revision (default main) · repoType (default datasets) | Public HuggingFace repos, credential-free. Listing walks the Hub tree API; resolved URLs support CORS + Range, so parquet/zarr reads work in-browser. |
dlSource | slug (namespace) · sourceId · root subpath | Registered by the DreamLake app — a source browsed in the platform (S3, GCS, …). Listing via the source browse API, URLs via presign; the session token is injected at registration, never configured. |
dlProject | namespace + project, or root (a node id) | Registered by the DreamLake app — a project folder as the dataset root. |
Hosts add drivers with registerStorage(name, factory); the whole contract is
list(path) + resolveUrl(path). (The legacy schema-viz pages use
basePath/id for their own http/hf drivers — a different subsystem; a
.dreamrc always uses the keys above.)
The http index.json manifest
A static host can't enumerate itself, so the http driver lists a directory
by fetching <dir>/index.json. Every directory a glob walks needs one —
for episodes: "episodes/*/" that is episodes/index.json plus one inside
each episode folder (formats with episodes: auto fetch known paths and need
none). The shape mirrors what list() returns:
type ("file" | "dir") defaults to "file", name falls back to the
last segment of path, and a bare JSON array of filename strings lists
plain files; path is the storage-relative (or absolute) location —
entries may point anywhere, which is how a manifest can reference files
hosted elsewhere. Don't list
index.json itself; .dreamrc need not be listed either (the app fetches
it directly).
Formats
The dataset.format adapters. Common to all: the catalog is an inventory —
each field's address plus the facts the container reported — and read(ref, { as }) is where anything is decoded. dataset.annotations declares extra
tracks (spec),
merged in by the core — never a per-format concern.
lerobot
LeRobot v2.0 / v2.1 / v3.0. The entry file is meta/info.json.
| expected layout | meta/info.json + meta/tasks* + data/…parquet + videos/…mp4 (the paths info.json itself declares) |
| episodes | auto — total_episodes from info.json. A glob is never needed. |
| config keys | none |
The inventory is meta/info.json's features, verbatim — one entry per
feature, carrying its dtype, shape and names as found. dtype is the
only thing read, and only to decide addressing: video → video, image →
frames, language / string → text, any numeric dtype → tensor.
Anything else is omitted with a warning naming it, including a depth VIDEO
(the feature's own video.is_depth_map flag) — no browser decodes 12-bit
log-quantized H.265.
Exactly four bookkeeping columns are dropped — timestamp, frame_index,
episode_index, index — and nothing else. task_index and subtask_index
stay: an integer pointing into a label table is data, and a view binding one
as: segments is what fetches meta/tasks* and joins it. Timeline:
per-episode length at the dataset's fps.
folder
Folder-per-episode, no manifest — the directory layout is the contract.
| expected layout | anything: each matched folder is one episode, its files are the fields |
| episodes | a glob, e.g. "episodes/*/" — auto errors (a bare folder cannot enumerate itself) |
| config keys | fps — the clock for numbered still runs and depth stills (default 30). A directory of JPEGs states no frame rate, so the author does; without it, tracks that carry a real clock (a COCO file's fps, parquet timestamps) drift away from the pictures. |
The inventory is the directory listing and nothing more: one field per file,
addressed by its basename, with path, ext and size in meta. The
extension picks the ADDRESSING only — mp4/webm → video, jpg/png →
image, csv/parquet → tensor, and everything else — .vtt, .glb,
.json — is file. Consecutive numbered stills collapse into one frames
field. annotations/ is listed too, and a file there replaces a same-named one
at the root.
No file is opened and no name is read for meaning; a view binding
subtasks.vtt as segments is what parses it, and the extension's second say
— at read time — picks only which parser. Series tables take their x-axis from
a timestamp-like column, row index otherwise. Timeline is null — media
components probe durations themselves.
umi
Zarr stores, two modes detected from the store itself.
| expected layout | a *.zarr.zip ReplayBuffer (UMI: data/ arrays + meta/episode_ends) or a *.zarr v3 directory store with a root zarr.json manifest (EgoVerse-style, one episode per store) |
| episodes | auto — episode_ends slices the ReplayBuffer; a directory store is a single episode |
| config keys | path — store path when not at the root or ambiguous (default: first *.zarr.zip / *.zarr found) · fps — ReplayBuffer clock (default 60) |
The inventory reads each array's own metadata. An image codec is the store
declaring that one chunk is one encoded image, so that array is frames
(byte-ranged a frame at a time — the store is never downloaded whole); numeric
arrays are tensor, carrying dtype, shape and codec. Nothing is
synthesized: a names list whose length does not match the array's width names
nothing, and a [T,J,3] keypoint array is a tensor like any other until a view
binds it as: pose3d. Arrays this reader cannot window (three dims or more)
are omitted with a warning. Timeline from frame counts at fps.
mcap
MCAP v1 indexed containers — one episode per *.mcap file, read in place
over HTTP range requests (a 512MB file costs ~130KB before any field is read).
| expected layout | *.mcap files at the dataset root — indexed/chunked, lz4- or zstd-compressed chunks |
| episodes | auto — one per *.mcap at the root, name-sorted (the http driver needs its index.json manifest to list); a glob ("runs/*.mcap") also works |
| config keys | path — a single .mcap at a subpath (skips listing) |
The multi-file arrangement has a live worked example — a folder of nuScenes scenes, one episode per file — as template 3 and in the gallery.
The inventory is the channel list, each channel carrying its schema name and
encoding in meta. Addressing follows what the schema says a message IS:
Foxglove image schemas → frames (one message is one image, byte-ranged per
frame); FrameTransform → one tensor per child frame (/tf::box7); a
SceneUpdate → one file field per model-bearing entity
(/scene::box7), which a 3D view joins to its /tf::box7 track by the ::
segment. Schema-name dispatch applies across wire formats: a json channel
bearing a well-known name decodes exactly like its protobuf twin (byte
fields arrive base64 and are converted by name — a stub jsonschema costs
nothing), so json writers get images and 3D too. Other json channels list by
their leaves: numeric → tensor (dot paths are the columns, e.g.
linear_accel.x; dims come from the jsonschema or the first message),
string-only → text.
ROS bags decode through the same paths — ROS 2 (cdr messages with
ros2msg schema text, rosbag2's own MCAP output) and ROS 1 (ros1 +
ros1msg, rosbag conversions). WELL-KNOWN ROS types are normalized to their
foxglove TWIN's shape and dispatch on the twin name (recorded in meta.twin
beside the honest meta.schema): tf2_msgs/TFMessage →
FrameTransforms, sensor_msgs CompressedImage/Image/PointCloud2 →
CompressedImage/RawImage/PointCloud, geometry_msgs/PoseStamped and
nav_msgs/Odometry → PoseInFrame. Every other ros channel takes
first-message leaf discovery like json (JointState, IMU, …). A tf child read
as: transform3d composes its LOCAL parent→child transforms down the
declared parent_frame_id chain (across /tf + /tf_static) into
fixed-frame poses — so a tf-only humanoid recording renders as a moving
skeleton (axis triads + bones between tracked frames), and a models: URDF
turns that skeleton into the robot itself.
The schema name is then a DECODER selector at read time — a
foxglove.PointCloud channel bound as: pointcloud, a tf channel bound
as: transform3d — never a conclusion at catalog time. Channels outside this
v1 scope — ros1msg/ros2idl decoding, Grid, attachments, bz2 chunks — are
omitted from the catalog with a console warning naming topic + encoding,
never mislisted. Unindexed files fail with a clear error (repack with mcap recover). Timeline: message start→end from the summary statistics.
The default mcap view
defaultMcapViews(fields) (exported from @dreamlake/viz/dataset-viz)
turns one episode's catalog into a ready layout: image schemas →
frameStack, each foxglove.PointCloud → a pointCloud pane, tf children
and PoseInFrame → one recon3d (with as: transform3d written
explicitly, up: z — the mcap ecosystem's own convention, ROS REP-103 /
the Foxglove 3D panel's fixed +Z-up display — and originMarker: axes,
since a world-frame recording's origin is the floor, not a recording
camera), remaining numeric channels
→ lineCharts, string channels →
a timeline, closed by the fields catalog (no metaPanel — the preview
chrome already carries the file's name and duration). Every 3D pane gets a
scrubbable timeline ruler directly under it: the 3D views follow the
cursor but cannot set it, and on a page tall enough to scroll, the playhead
must never be out of reach — span-shaped tracks ride those rulers instead
of a separate tail timeline. Cloud panes are
bounded like charts: the busiest four (they are live WebGL contexts,
and AV recordings carry eight lidar topics), folded into a two-column grid
when there are more than two. Charts are bounded by three mechanical rules (addressing and shape
facts, never a guess at meaning): at most six panes ranked by recorded
volume (messages × dims), topics wider than 48 dims never chart by default,
and a topic that path-prefixes another (/plan over /plan/oracle) is the
writer's own grouping — the pair shares one chart while the combined width
stays under 12 traces. Three or more charts fold into a two-column grid;
everything that didn't chart is still in the fields catalog. A
sensor_msgs/JointState topic also binds to the 3D pane's joints: slot, and
its joint names — like a tf tree's frame ids — can select a known robot
description from robot-presets.ts, which then turns those angles into a pose
per link. It returns [] when nothing yields a data view, so
callers fall back honestly instead of rendering empty chrome.
mcapDreamrcYaml(views) serializes the result as a complete .dreamrc.
This is the one sanctioned default layout, and the precedent is precise:
a declarative standard schema may drive a default view; a name or a shape
never may. A Foxglove schema name is the writer declaring meaning by
published spec — reading it is reading, the same trust lerobot places in
dtype: video. The generator consumes only such declarations plus each
kind's meaning-free reading (numbers chart as numbers), so a plain
[512,6] tensor is only ever a chart here — nothing short of the file
declaring foxglove.PointCloud, or a human writing as: pointcloud, makes
it a point cloud. It is a caller-side helper: views: stays required in a
.dreamrc, an authored config always wins, and no other format gains a
fallback by its existence.
View components
Live example of each — a minimal .dreamrc and its render — on the
view components catalog. Here: the config surface.
A slot is where meaning is stated. Each component declares, per binding
slot, the payload kinds it reads — and that one declaration is what
read(ref, { as }) is called with, what a * glob is filtered by, and what an
author's as: is checked against. Bind a field to series and it is read as
traces; bind the same field to tracks and it is read as spans. A slot a
component does not declare is rejected rather than ignored: overlays: on a
lineChart is an author expecting to see something, and silence there is the
failure mode this design removes.
Whether a binding needs an as: follows from the field, not from the
view — the rule.
The media/geometry slots (cameras, cloud, geometry) take bare field names with
nowhere to write one, so they are always settled by the field's own
addressing kind; series, tracks and overlays take { field, as }
entries. A * glob selects only the fields whose addressing kind can serve
the slot — cameras: ["*"] on videoStack takes the cameras and leaves the
tensors — while an explicit ref always passes and fails loudly if it cannot
be read.
Bindings are the keys the view interprets — its slot names (cameras /
cloud / geometry / series / tracks / overlays); every other key
passes through as a prop to the underlying Episode* component, so its
documented props are all available.
Every view takes width / height / aspectRatio — a sized view
fills its box with row-strip fill semantics
(how); the per-view
height defaults below apply when nothing sizes the view. A split: row
is a fixed-height strip (height on the split, default 280) — its
per-child keys (width fixed box · aspectRatio width from strip height ·
flex stretch share · minWidth squish floor · child height override)
are consumed by the layout, not the component; media sizes its width from
its aspect, charts fill their slot, and overflow scrolls horizontally,
synced across episodes under a SyncScrollProvider.
| view | binds | slot → payload | keys of note |
|---|---|---|---|
videoStack | cameras (a * glob expands to the container's media fields) · overlays: [ { field, as, on? } ] — as picks skeleton or captions, on pins one to a specific camera | cameras → video, image · overlays → keypoints, segments | columns (default 3) · tileAspect — force one ratio; by default each tile uses its video's intrinsic ratio. Plus EpisodeVideoStack props. Probes video durations when the format has no timeline. |
frameStack | cameras · overlays (same form as videoStack — the tiles take the same layer) | cameras → frames · overlays → keypoints, segments | columns (default 3) · tileAspect — force one ratio; by default each frame’s own |
depthStack | cameras — name the depth columns; a bare * would ask every tensor in the episode for a depth map · overlays | cameras → depth · overlays → keypoints, segments | colormap: turbo | gray (default turbo) · min / max (raw units) pin the color range; by default each frame maps its own min/max over valid readings (>0), invalid renders transparent · columns (default 3). Corner chip shows the mapped range — metres when the format knows the depth scale, raw units otherwise · tileAspect — force one ratio (the frame letterboxes inside); by default each frame’s own |
lineChart | series: [ ref | { field, label?, color?, dash?, … } ] — field is feature (all dims) or [feature, dim]; * globs work in both halves | series → series | height (default 180) · title, caption + EpisodeLineChart props |
trajectory2d | series: [ ref | { field, label?, color? } ] — each entry is one path; x/y dims are the columns named x/y (case-insensitive) or the first two | series → series | invertY: false — math convention (y up); default is image convention (top-left origin) · window: { ahead?, behind? } — pin the drawn path to the seconds around the cursor (defaults 1 / 0 when given; omitted, the full path draws) · height (default 260) |
timeline | tracks — required; nothing in an inventory says a column holds spans | tracks → segments | controls: false — hides the playback bar rendered above the ruler by default (play/pause, frame steps, jump to ends, speed menu, mono time readout; it drives the episode's shared clock, so hover-scrub and natural-speed playback coexist) · EpisodeTimeline props |
bandTrack | series: [ ref | { field, label? } ] — same field addressing as lineChart; each resolved column becomes one band row | series → series | maxLevels (default 12) — a column is discrete when its unique values (rounded to 6 decimals) fit, busier columns get a one-line "use lineChart" note · bandHeight (default 18) per-band px. Runs of equal value become colored rects; value→color legend below; natural height. |
metaPanel | — (renders EpisodeInfo: name, duration, frames, fps, task strings) | — | note — a free-text line · showTasks: false hides the task strings (single-task datasets repeat one sentence per episode otherwise) |
fields | — | — (prints the inventory itself) | title (default Fields) |
recon3d | geometry — the geometry file(s) that make the scene · tracks — the per-frame motion, each entry naming its as; a track binds to the glTF node whose name matches its ref · joints — scalar joint ANGLES (sensor_msgs/JointState, or any series whose columns are joint names), read through a models: URDF into a pose per link | geometry → mesh3d · tracks → transform3d, vertices3d, pose3d · joints → series | models — EXTERNAL geometry by URL: a .urdf robot description loads one named node per link (links join /tf::<link> tracks by name; unmatched links ride their nearest tracked ancestor, else stand at their zero-angle rest pose; meshes resolve relative to the .urdf, stl/obj/dae/glb; presets live at live9080/dreamlake-robots on HF), or a glb/gltf/obj/stl URL; entries are "url" or { url, format?, as?, packages? }, where packages: { <ros package>: <base url> } locates that description's package:// mesh references (unneeded when the URDF's own URL contains the package name as a path segment) · up — gravity vector in the data's own frame (uprights the grid; default [0, -1, 0], the OpenCV camera frame's up — y points down there) · trail: { ahead?, behind? } — motion-trail window in seconds around the playhead (default { ahead: 1, behind: 0 } — pure future, so the trail runs out exactly when the clip does; behind opts into a dim past tail; false turns it off) · height (default 360) |
pointCloud | cloud — name the column; the first bound field renders (one cloud per panel in v1) | cloud → pointcloud | up — y / z or an [x, y, z] vector (default z, robot-lab convention → −90° X rotation) · height (default 360). Per-point color when the data carries rgb; camera auto-fits the first frame |
Hosts add views with
registerComponent({ name, component, reads, slotNames? }) — reads
is the slot → payload declaration above (canonical keys: fields, series,
tracks, overlays), slotNames renames a slot's config-surface key the
way videoStack surfaces fields as cameras, and a component that omits
reads opts out of binding checks. A component receives
{ fields, info?, read, timeline, cursor, config }.