Record and export sequences tutorial
In this tutorial, you will record time windows of data from a machine as sequences, review them in the Viam app, collect them into a sequence dataset, and export the dataset as Parquet files. By the end, you will have followed the whole path that a training script starts from: from capture on the machine to files you can train on.
Time: ~20 minutes
What you need:
- A machine connected to the Viam app (if you don’t have one yet, follow Set up a machine)
- The Viam CLI, installed and authenticated on your computer
- Python 3 installed on your computer
We will use a fake camera and a fake sensor, so this tutorial works without physical hardware.
You can follow each step in the Viam app or from a terminal. The CLI and Python tabs let you script the whole tutorial, or hand it to an AI coding agent to run for you. If you use those tabs, expand Set up for the CLI and Python path and finish it first.
1. Add a fake camera and a fake sensor
We need components that produce data.
- Go to your machine’s page in the Viam app.
- Click the + button in the left sidebar and select Blocks.
- Search for camera/fake and select the result.
- Name it
test-cameraand click Add to machine. - Repeat to add sensor/fake, and name it
test-sensor. - Click Save in the upper right.
Expand the Test section on each component card to confirm the camera shows an image and the sensor returns readings.
viam machines part add-resource --part=$VIAM_PART_ID \
--name=test-camera --model-name=fake --resource-subtype=camera
viam machines part add-resource --part=$VIAM_PART_ID \
--name=test-sensor --model-name=fake --resource-subtype=sensor
Wait a few seconds for the machine to pick up the change, then confirm the sensor returns readings:
viam machines part run --part=$VIAM_PART_ID \
--component=test-sensor --method=GetReadings
Don’t configure data capture on either component. In the next steps, a capture control sensor will turn capture on only while you record.
2. Add the capture control sensor
A sequence starts and stops when a capture control sensor says so.
We will use the capture-control module, which you switch on and off by hand.
On the CONFIGURE tab, click + and select Blocks.
Search for capture-control and select capture-control/capture-control-sensor.
Name it
my-capture-sensorand click Add to machine.In its attributes, list the two components to control:
{ "resources": [ { "resource_name": "test-camera", "method": "GetImages" }, { "resource_name": "test-sensor", "method": "Readings" } ] }Click Save.
The CLI can’t add a registry module to a machine.
viam machines part add-resource adds the sensor’s entry but not the viam:capture-control module entry, so viam-server can’t build the sensor.
You can follow the Viam app instructions, or use the Viam MCP server instead, which adds the module for you.
Ask your MCP client something like:
On my machine
<machine-name>, add a sensor namedmy-capture-sensorwith the modelviam:capture-control:capture-control-sensorand these attributes:{ "resources": [ { "resource_name": "test-camera", "method": "GetImages" }, { "resource_name": "test-sensor", "method": "Readings" } ] }
The client calls the add_machine_config_item tool.
Its result includes a module_added entry for viam:capture-control.
3. Connect the sensor to the data manager
On the CONFIGURE tab, click +, select Blocks, and search for data_manager. Choose the data_manager/builtin service and name it
data-manager.Switch to JSON mode.
Find the
data-managerservice and add the sensor to its attributes:{ "name": "data-manager", "api": "rdk:service:data_manager", "model": "rdk:builtin:builtin", "attributes": { "sync_interval_mins": 0.1, "capture_control_sensor": { "name": "my-capture-sensor", "key": "overrides" } } }Click Save.
Add the data management service, then set its attributes.
The service’s API is rdk:service:data_manager, which --resource-subtype doesn’t accept, so pass --api:
viam machines part add-resource --part=$VIAM_PART_ID \
--name=data-manager --api=rdk:service:data_manager --model-name=builtin
viam resource update --part=$VIAM_PART_ID --resource-name=data-manager \
--config '{"sync_interval_mins": 0.1, "capture_control_sensor": {"name": "my-capture-sensor", "key": "overrides"}}'
If your machine already has a data management service under another name, remove it first. A machine can have only one.
Nothing is captured yet, because the sensor isn’t recording.
4. Record three sequences
Now we will record three demonstrations, 10 seconds each.
Expand the Test section of
my-capture-sensor, find DoCommand, and send:{ "start_capture": true, "frequency_hz": 2, "tags": ["demo-1"] }Wait 10 seconds.
Send
{"stop_capture": true}.Repeat with the tags
demo-2anddemo-3.
for tag in demo-1 demo-2 demo-3; do
viam machines part run --part=$VIAM_PART_ID \
--component=my-capture-sensor --method=DoCommand \
--data="{\"command\": {\"start_capture\": true, \"frequency_hz\": 2, \"tags\": [\"$tag\"]}}"
sleep 10
viam machines part run --part=$VIAM_PART_ID \
--component=my-capture-sensor --method=DoCommand \
--data='{"command": {"stop_capture": true}}'
sleep 2
done
The data manager watches the sensor’s readings.
Each start_capture began capture at 2 Hz and opened a sequence, and each stop_capture ended capture and closed the sequence.
The data manager uploads each finished sequence on the next sync.
5. Review the sequences
Wait about 30 seconds, then:
- Click the DATA tab in the Viam app.
- Click SEQUENCES.
- You should see three rows, the time range for each sequence, your machine part, the fake camera and sensor resources, and the tag
demo-1,demo-2, ordemo-3. - Click a sequence. You should see both the recorded images from
test-camera, and the readings fromtest-sensor.
The CLI can’t list sequences yet, so this step uses the Python SDK.
Save this as list_sequences.py.
It prints each matching sequence’s details, then its ID on standard output:
import asyncio
import os
import sys
from viam.app.viam_client import ViamClient
from viam.rpc.dial import DialOptions
TAGS = {"demo-1", "demo-2", "demo-3"}
async def main():
client = await ViamClient.create_from_dial_options(
DialOptions.with_api_key(
os.environ["VIAM_API_KEY"], os.environ["VIAM_API_KEY_ID"]
)
)
data = client.data_client
page_token = None
while True:
sequences, next_page_token = await data.list_sequences(
organization_id=os.environ["VIAM_ORG_ID"], page_token=page_token
)
for s in sequences:
if s.part_id != os.environ["VIAM_PART_ID"]:
continue
if not TAGS.intersection(s.sequence_tags):
continue
resources = ", ".join(
f"{r.resource_name} · {r.method_name}" for r in s.resources
)
print(
",".join(s.sequence_tags),
s.start_time.ToDatetime().isoformat(),
s.end_time.ToDatetime().isoformat(),
resources,
file=sys.stderr,
)
print(s.id)
if not next_page_token:
break
page_token = next_page_token
client.close()
asyncio.run(main())
Run it and keep the IDs for the next step:
SEQUENCE_IDS=$(python list_sequences.py)
echo $SEQUENCE_IDS
You should see three sequences, one for each tag, each about 15 seconds long, with test-camera · GetImages and test-sensor · Readings as resources.
The script matches on tags, so if you run the tutorial again, it also lists the sequences from earlier runs.
What just happened?
Behind the scenes:
- The data manager polled
my-capture-sensor10 times per second. - The sensor returns an overrides list on every poll, at 0 Hz while it isn’t recording. After
start_capture, the frequency in that list changed to 2 Hz, so the data manager began capturingtest-cameraandtest-sensor, even though neither has capture configured. - The
sequencesentry opened a sequence. - After
stop_capture, the entry disappeared, so the sequence closed and its start and end times were saved. - Sync uploaded the captured data and the sequence.
The sequence doesn’t contain the data. It is a saved filter: one machine part, a time window, and two components.
6. Collect the sequences into a dataset
- On the DATA tab, click DATASETS.
- Click Create dataset.
- In the Dataset Name field, enter
demos. - Set Data type to Sequence Data, and click Create dataset again.
- Open the dataset and click Add Data.
- Select all three sequences and click Add.
The dataset’s sidebar now shows 3 sequences.
viam dataset create can’t set a dataset’s type yet, so this step uses the Python SDK.
Save this as create_dataset.py.
It creates a sequence dataset, adds the sequences you pass it, and prints the dataset’s ID.
The dataset is named demos unless you set DATASET_NAME:
import asyncio
import os
import sys
from viam.app.viam_client import ViamClient
from viam.proto.app.dataset import DatasetType
from viam.rpc.dial import DialOptions
async def main():
# Split the arguments too, because zsh passes $SEQUENCE_IDS as one argument.
sequence_ids = " ".join(sys.argv[1:]).split()
client = await ViamClient.create_from_dial_options(
DialOptions.with_api_key(
os.environ["VIAM_API_KEY"], os.environ["VIAM_API_KEY_ID"]
)
)
data = client.data_client
dataset_id = await data.create_dataset(
name=os.environ.get("DATASET_NAME", "demos"),
organization_id=os.environ["VIAM_ORG_ID"],
type=DatasetType.DATASET_TYPE_SEQUENCE_DATA,
)
await data.add_sequences_to_dataset(
dataset_id=dataset_id, sequence_ids=sequence_ids
)
print(dataset_id)
client.close()
asyncio.run(main())
Run it with the IDs from step 5:
DATASET_ID=$(python create_dataset.py $SEQUENCE_IDS)
echo $DATASET_ID
Dataset names must be unique in your organization.
If demos already exists, pick another name, for example export DATASET_NAME=demos-2, and run the script again.
See Create a dataset.
7. Export the dataset
On both tabs, the export and inspection steps run in a terminal.
Run every command in this step from the same directory, because the inspection script reads the export from ./demos.
Copy the dataset’s ID from the dataset page, or use $DATASET_ID from the CLI and Python tab, then run:
viam dataset export --dataset-id=<dataset-id> --destination=./demos
The CLI starts an export job, waits for it, and downloads the result.
When it finishes, ./demos contains <dataset-id>.zip and a binary_data/ folder with the camera images.
To look inside, install pandas and pyarrow (pip install pandas pyarrow), then save this as inspect_export.py:
import sys
import zipfile
import pandas as pd
zip_path = sys.argv[1]
with zipfile.ZipFile(zip_path) as z:
z.extractall("demos/parquet")
sequences = pd.read_parquet("demos/parquet/sequences.parquet")
binary = pd.read_parquet("demos/parquet/binary_data.parquet")
tabular = pd.read_parquet("demos/parquet/tabular_data.parquet")
print(sequences[["sequence_id", "tags", "start_at", "end_at"]].to_string())
print("images per sequence:")
print(binary.groupby("sequence_id").size())
print("readings per sequence:")
print(tabular.groupby("sequence_id").size())
Run it:
python inspect_export.py demos/<dataset-id>.zip
You should see three sequences, each with roughly 30 images and about as many readings: two per second of recording, for about 15 seconds.
The rows in the two data files link to a sequence through sequence_id.
See Sequence dataset format for every column.
What you learned
You built the whole path from capture to training data:
- Capture control: a sensor turned capture on for two components only while it was recording.
- Sequences: the same sensor opened and closed a sequence around each recording, so each demonstration is one item.
- Dataset: you collected sequences into a sequence dataset instead of picking individual images.
- Export: you downloaded the dataset as Parquet files, the input for a custom training script.
What’s next
- Capture on demand: all of the
capture-controlmodule’s commands, and how to write your own capture control sensor. - Sequences: recording, viewing, and creating sequences from code.
- Sequence dataset format: the columns in each Parquet file.
- Custom training scripts: train a model on the exported data.
Was this page helpful?
Glad to hear it! If you have any other feedback please let us know:
We're sorry about that. To help us improve, please tell us what we can do better:
Thank you!