SEAM Studio
Guide 06 · SEAM Studio docs

ML datasets and exports

SEAM Studio is not just a viewer — everything you simulate can leave the app as files: NumPy .npz ground-truth datasets for training ML models, an AODT-viewer-compatible RFData bundle, WYSIWYG viewport captures, and per-chart CSV/PNG/SVG exports. This guide walks through each export path.

Everything here works with the Mock backend too — if Sionna RT is not installed, dataset generation and all exports still run end to end (the numbers come from the mock solver, which is fine for testing pipelines).

1. The ML dataset panel

The dataset generator lives in the ML dataset panel (its header renders as ML DATASET). You can reach it two ways:

Floating ML DATASET panel over a radio-map heatmap, showing sampling controls, region fields, the Generate dataset button, and two existing dataset rows with npz/json links
The ML DATASET panel floated over a computed radio map — sampling controls on top, the region picker in the middle, and the dataset list with npz / json downloads at the bottom.

Generate a dataset step by step

  1. Name — the dataset's display name (it also appears in the list below).
  2. Sampling mode — how UE positions are chosen:
    • random — uniform random positions inside the region,
    • grid — a regular grid over the region (adds a Grid spacing field, in m),
    • trajectory — points along a straight start→end line.
  3. Actor flight path — optionally sample along a scene actor's authored trajectory (a car, pedestrian, or UAV with waypoints). Pick an actor here and it overrides the region / start-end below; leave it at — none — to use the sampling mode's own geometry. If no actor has a trajectory yet, the panel says so — assign one in Visual mode first. The hint under the field spells out the precedence: waypoints > actor flight path > start/end (or region).
  4. dt (s) — the finite-difference time step behind the velocity / Doppler labels the backend adds to moving samples.
  5. Num samples — how many UE positions to solve (1–20000). CFR points — frequency-response samples per position (2–4096). Height (m) — the UE sampling height.
  6. Set the region (for random / grid):
    • ⌖ Pick region in viewport — the button switches to Click 2 corners… (Esc); click two opposite corners of the region on the scene surface and the XY fields fill in (Esc cancels).
    • Fit to scene — sets the region and height to cover the whole scene (the panel also seeds itself from the real scene bounds when a project loads, so you rarely start from garbage values).
    • Region min / Region max — the XY corners as numbers.
    • Watch the hint line, e.g. "Scene spans [-40.0, -40.0]…[40.0, 40.0] m — samples outside it get zero paths." Sampling outside the geometry is the #1 cause of useless datasets.

    For trajectory mode you instead get ⌖ Pick path in viewport plus Start / End XYZ fields.

  7. Seed — makes the random sampling reproducible; record it for papers.
  8. Include paths — additionally dumps every sample's full ray paths (vertices + interactions) as paths.jsonl. Large; off by default.
  9. Follow terrain — snaps each sample's height to the surface below it plus the height offset. Use it on sloped outdoor scenes; leave it off indoors (it would snap to the roof).
  10. Press Generate dataset. The button shows Generating… while the solver sweeps the positions.

The dataset list

Finished datasets appear in the table below the button, one row each:

On disk, datasets live under the project folder at export/datasets/<dataset_id>/.

What is in the labels

The .npz contains per-sample positions, complex CFR, per-path CIR gains and delays, LOS flags, RSS, and dispersion metrics — the exact array schema, the AODT field mapping, and a ready-to-run training example (examples/ml/train_channel_estimator.py) are documented in ML ground-truth datasets.

2. RFData export (AODT-viewer bundle)

To hand results to an external AODT-style viewer or your own pipeline, use the toolbar: Actions ▾ → Export RFData. It writes a bundle to export/rfdata/ inside the project folder:

FileContent
scenario_meta.jsonunits, frequency, coordinate transform, time window
devices.jsontransmitters + receivers (positions in meters)
paths.jsontime-indexed ray paths
trajectory.csvper-waypoint UE metrics (time_s, ue_id, x_m, y_m, z_m, rss_dbm, sinr_db, path_gain_db)
radio_map.csvplane heatmap samples
calibration_points.json3 coordinate-check reference points

The frequency in scenario_meta.json and devices.json is the one the stored results were solved with (the latest paths result's config snapshot), not the project's first stored config; over the API (POST /api/projects/{project_id}/export/rfdata) a config_id or an inline config overrides it.

After the export, a dismissible row appears in Results — "Exported RFData to export/rfdata" — with a download link per file, so you don't have to dig through the project folder.

AODT results-schema export

The bundle above is the viewer contract. For tooling written against NVIDIA AODT's own results schemas, there is a second export that writes those tables as Parquet. In the UI: Actions ▾ → AODT export (parquet) (pick the source and, optionally, a result id; blank = the latest). Over the API:

POST /api/projects/{project_id}/export/aodt
     {"source": "paths", "result_id": null, "fft_size": 64,
      "subcarrier_spacing_hz": 30000}

It writes one <table>.parquet per AODT table into export/aodt/: ues, scatterers, rus, dus, panels, patterns, time_info, cfrs, cirs, raypaths — column names and types verbatim from the AODT docs. source: "paths" writes a single snapshot (time_idx 0) from a stored paths result; source: "playback" writes one time_idx per frame of a stored playback pack, so time_info, raypaths, cirs and cfrs span the drive and the UE's route_* columns carry the frame positions. source: "sensing" writes a single snapshot of a stored sensing result (sensing.md). Carrier frequency, bandwidth (and so the cfrs tone grid) and the panel spacing follow the config the exported result was solved with; a config_id in the request overrides it.

Two AODT tables are never written: telemetry and ran_config. Both are RAN-simulation outputs (scheduler/PHY KPIs, gNB configuration) that a ray-tracing pipeline does not produce — emitting empty ones would claim coverage SEAM does not have.

Alongside the tables, id_map.json records the string ↔ integer id mapping the Parquet rows cannot carry: device id → ru_id/ue_id, prim id → prim_ids index, actor id → scatterer ID, and device → panel index.

Caveats worth knowing before you consume the tables:

This export needs pyarrow (pip install "seam-studio[parquet]", the parquet extra; a source checkout installed with backend[dev,parquet] has it); without it the endpoint answers 409 with that command, and it answers 404 when the project has no stored result of the requested source kind. The written raypaths.parquet reads back through SEAM's own AODT importer (POST /results/import-aodt, or Actions ▾ → AODT import (parquet) with a folder path on the machine running SEAM), which rebuilds one interaction per interior point (diffuse becomes scattering).

The ML dataset in section 1 is a per-position dataset (CFR, aggregated KPIs). When you need the raw per-link multipath in the layout NVIDIA AODT / HYRAY reference files use — one row per UE, one column per TRP, one entry per path — use the toolbar: Actions ▾ → Channel dataset (.npz). It writes export/channel_npz/channel_dataset.npz plus a metadata.json sidecar.

The UE grid is chosen for you: the latest stored trajectory run if the project has one (many UE rows), otherwise the scene's receiver devices — which is exactly what a UE list imported through POST /import/devices lands in. The transient notice and the dismissible Results row both say which grid was used, together with the link count. Because the export runs one paths solve per UE, it reports progress in the solve-progress card and is cancellable.

The npz carries the 13 canonical keys, nothing more:

keyshapedtype
num_TRP, num_trajectory, batchscalarint64
dataset_TRP_pos(T, 3)float32
dataset_TRP_ori_angle(T, 2)float32
dataset_UE_pos(U, 3)float32
sorted_dataset_ampl(U, T, P)complex128
sorted_dataset_azimuth / _elevation(U, T, P)float32
sorted_dataset_toa(U, T, P)float32
is_nlos(U, T)bool
ue_id, time_idx(U,)int32

T = transmitters, U = UE positions, P = max_paths (default 500). Each link's path axis is sorted strongest-first by |amplitude| and zero-padded to P; a link the solver found no path for stays all-zero with is_nlos = True.

Conventions you must know before consuming the arrays:

The whole surface is available over HTTP for scripted runs:

POST /api/projects/{project_id}/export/channel-npz
     {"ue_source": "explicit",
      "ue_positions": [[25, 5, 1.5], [40, -12, 1.5]],
      "tx_ids": null,          // null = every tx device, in scene order
      "max_paths": 500,
      "normalization_db": 0.0,
      "ue_ids": null, "time_idx": null, "batch": 0}

ue_source is "explicit" (the ue_positions list), "devices" (every rx device, ordered by id) or "trajectory" (a stored trajectory result's per-sample positions — ue_result_id picks one, null takes the latest). ue_ids / time_idx are optional passthrough columns (defaults arange(U) and zeros(U)); when given they must be exactly U long. A single export is capped at 5000 UE positions.

The export never touches your scene: each UE is solved on an in-memory copy whose only receiver is an ephemeral probe, and no result set is persisted — only export/channel_npz/ is written, plus one export_channel_npz provenance event.

Pair this with the Max paths / TX field in the Paths solver panel (max_num_paths_per_src) when you want the solver itself to stop at the same path count the export keeps.

4. Viewport captures — Snapshot and Render

The two icon buttons in the bottom-right cluster of the viewport save scene images:

The entity POV inset (the live first-person view from a device or actor) has its own camera button that saves the POV frame as a full-resolution PNG.

5. CSV and figure exports from the dashboards