Build your first dataset¶
This tutorial produces a small dataset for a familiar ML task: predict a digital modulation class from complex baseband IQ. It is deliberately small so you can inspect every layer of the data product before adapting the configuration for your own study.
Before running the template, install the optional TorchSig and Sionna
integrations because the shipped configuration selects its communications
emitter and the 3GPP TR 38.901 Urban Microcell (UMi) propagation
model, an urban-microcell propagation scenario: uv pip install -e '.[torchsig,sionna]'. The core uv pip install -e .
installation remains sufficient for native local Signal Dataset workflows that
do not select TorchSig.
What you will make¶
The retained narrowband-baseline template generates 100 synthetic,
single-emitter records. Each record contains:
complex baseband IQ: paired in-phase and quadrature samples that preserve amplitude and phase;
a structured label identifying one of BPSK, QPSK, 16-QAM, or 64-QAM;
scene and generator provenance explaining how the example was made;
a local Signal Dataset snapshot that supports inspection and ordinal training access.
This is a controlled synthetic baseline, not a balanced benchmark, a claim about over-the-air realism, or a promise of classifier performance.
1. Materialize and validate a configuration¶
Modulation classification means training a model to map an IQ observation to the modulation format used to produce it (for this tutorial, one of the four structured labels below).
rfgen init copies a maintained starting configuration into a directory you
own. Read and edit this YAML when you start a new experiment; rfgen does not
ask you to select from a catalog of hidden scenarios.
rfgen init narrowband-baseline ./narrowband-config
cd ./narrowband-config
rfgen validate --config-dir .
Validation checks that the configuration can be composed and satisfies rfgen’s generation contract. It does not write records.
2. Generate and inspect the dataset¶
rfgen generate --config-dir .
rfgen inspect ./rfgen-output --sample-size 1
generate exits 0 on success and normally prints nothing, so silence is not a
sign it did nothing. It does print a warning when --num-samples is not a
multiple of the configured shard size, which is a note about the last shard
being partial, not a failure. inspect is how you confirm the result.
Generation publishes a Signal Dataset snapshot at
./narrowband-config/rfgen-output. Inspection is read-only and reports its
record count, field inventory, and a bounded ordinal prefix of record IDs. The
display classes BPSK, QPSK, 16-QAM, and
64-QAM use the canonical class_name strings bpsk, qpsk, 16qam, and
64qam, respectively, in record labels and metadata. Treat the output as a
dataset with labels and provenance, not as a loose directory of waveform
files.
3. Open record 0¶
SignalDatasetStore opens the
published snapshot, and signal_dataset.open reads it directly. Ordinal access
is lazy. Convert to tensors only at the training boundary, which your
application owns.
import torch
import signal_dataset as sd
dataset = sd.open("./rfgen-output")
assert len(dataset) == 100
record = dataset[0]
# Field names are namespaced by the projection that produced them; `rfgen
# inspect` printed the full list.
iq = record["projections/receiver/receivers/rx0/iq"].data
tensors = {"iq": torch.from_numpy(iq.copy())}
print(record.id, tensors["iq"].shape)
The record ID and IQ shape are the success signal: you reopened the published item by ordinal. Tensor conversion, batching, and split policy belong to your training application — rfgen stops at the published snapshot.
RFGen scene and emitter details are nested under record.metadata["rfgen"],
inside the projection that produced them. Read them through the two accessors
rather than walking the envelope by hand:
from rfgen.core.metadata import decode_rfgen_metadata
from rfgen.record_reconstruction import scene_provenance_of
declared = scene_provenance_of(decode_rfgen_metadata(record.metadata["rfgen"]))
print(declared["scene"]["scene_id"], declared["emitters"][0]["class_name"])
decode_rfgen_metadata restores permitted non-finite RF metrics as Python
floats; scene_provenance_of returns the scene, emitters, and boxes the record
declares, and refuses a record whose envelope does not name exactly one scene.
What to do next¶
Annotate the snapshot you just produced with Annotate an existing dataset.
Read Labels for what the boxes and the segmentation raster in that record mean, and Label schema for the frames they are stated in — the box edges are offsets from the receiver’s tuned centre, not absolute RF, which is the single easiest thing to get wrong about this data.
Read Generation Jobs for the editable configuration, validation, execution, and provenance lifecycle you just used.
Read RF & ML Quick Overview for the relationship between a scene, IQ, labels, annotation overlays, storage, and training.
Use the narrowband Golden Path for the exact qualification boundary and safe customization handoff.
Use How-to Guides when you have a focused task.