ML-Dash

Track API

Tracks are timestamp-indexed, multi-modal streams: robot poses, sensor readings, per-step state. Each entry carries a float timestamp and an arbitrary dict payload, and entries that share a timestamp on the same topic merge into one row.

Tracks vs. Metrics

Use a track when entries are timestamp-indexed and the schema may vary across topics (poses, cameras, lidar, RL transitions). Use a metric when you have step-indexed scalars for plotting (loss, accuracy). See /metrics.

Basic Usage

python
from ml_dash import Experiment

with Experiment("robotics/training").run as exp:
    for step in range(1000):
        t = step / 30.0  # 30 Hz simulator clock
        exp.tracks("robot/pose").append(
            q=[0.1, -0.22, 0.45],
            e=[0.5, 0.0, 0.6],
            _ts=t,
        )

exp.tracks(topic) returns a TrackBuilder bound to a topic path (e.g. "robot/pose"). append(**fields, _ts=...) writes one entry.

Timestamps

_ts is required on every append call and must be numeric (cast to float internally). There is no auto-generated or inherited timestamp — omitting _ts raises ValueError. Pick a consistent clock per experiment (simulator time, wall clock, sensor timestamp).

Two append calls to the same topic at the same _ts merge: later fields overwrite earlier ones at the same keys. This lets you split a sample across calls without duplicating rows:

python
exp.tracks("camera/rgb").append(frame_id=0, _ts=0.0)
exp.tracks("camera/rgb").append(path="frame_0.png", _ts=0.0)
# -> one row at _ts=0.0 with {frame_id: 0, path: "frame_0.png"}

Different topics keep independent timestamp tables, so log multi-modal samples at the same _ts across topics to align them later.

Flexible Schema

The data dict is free-form per call — different fields per entry are allowed. The backend reconciles columns at read time.

Reading

python
TrackBuilder.read(
    start_timestamp: float | None = None,
    end_timestamp: float | None = None,
    columns: list[str] | None = None,
    format: str = "json",  # "json" | "jsonl" | "parquet" | "mocap"
)

read() returns the topic's entries (optionally filtered by timestamp range and projected to selected columns). Flush before reading in the same process.

python
exp.tracks.flush()

data    = exp.tracks("robot/pose").read()
window  = exp.tracks("robot/pose").read(start_timestamp=0.0, end_timestamp=10.0)
parquet = exp.tracks("robot/pose").read(format="parquet")

Flushing

python
exp.tracks.flush()                  # flush all topics
exp.tracks("robot/pose").flush()    # flush one topic

Appends are non-blocking and batched by the background uploader. See /buffering for batch size and flush interval configuration.

Aligning with Frames

To pair a track entry with an image, log the filename alongside the data and use a consistent zero-padded index. See /images.

python
for step in range(1000):
    t = step / 30.0
    fname = f"frame_{step:05d}.jpg"
    exp.files("frames").save_image(frame, to=fname)
    exp.tracks("robot/pose").append(frame=fname, q=q_step, _ts=t)

In MDX prose, wrap path templates like {step} or {i:05d} in backticks so the renderer doesn't treat them as expressions.