Quickstart

Generate 100 single-emitter samples in under a minute.

Warning

Pre-implementation. Commands below are the target v0 surface. They do not work today.

1. Install

See Install. Minimum:

pip install "rfgen[torchsig]"

2. Generate a tiny dataset

The +preset=... argument is Hydra override syntax for adding a named recipe to the config. This command selects the narrowband_classifier_baseline preset, then overrides the sample count and output path.

rfgen generate \
    +preset=narrowband_classifier_baseline \
    run.num_samples=100 \
    storage.path=./out/quickstart

Output:

Note

Illustrative. Specific values below (config hash, byte counts, percentile SNRs, sample counts in the class-balance report) show the target output shape, not measured behavior. Numbers will be regenerated from real runs once the v0 implementation lands.

Resolved config (sha256:<digest>):
  emitter_zoo: comms_only (BPSK, QPSK, 16-QAM, 64-QAM classifier labels)
  channel:     torchsig_impairments (level=1)
  scene:       narrowband (1 emitter, 1 MHz bandwidth @ 10 million samples/s)
  label:       class only
  annotator:   none
  storage:     zarr_local

Generating 100 samples in 1 shard ...
[████████████████████████] 100/100  [<elapsed>]

Done.
Wrote ./out/quickstart/
  manifest.json       <size>
  samples.zarr/       <size>    (100 samples × 100 ms × 10 Msps × 8 bytes compressed ~10×)
  stats.json          <size>

3. Inspect

rfgen inspect ./out/quickstart summary
# Sample count:    100
# Shard count:     1
# Total bytes:     <size> (compressed); <size> raw IQ
# Sample rate:     10 Msps
# Duration each:   100 ms
# Schema version:  1.0.0

rfgen inspect ./out/quickstart distribution --bins 10
# Class balance:   <per-class counts>
# SNR distribution:
#   p10  <value> dB
#   p50  <value> dB
#   p90  <value> dB

4. Read a sample in Python

Use the framework loader for application code. It returns stable Record objects through a StoreHandle and hides the backend’s file layout.

from rfgen.storage import open_store

with open_store("./out/quickstart") as store:
    for sample in store:
        print(sample.scene.preset, sample.emitters[0].class_name, sample.emitters[0].snr_db)

For low-level storage inspection, read the proposed v0 Zarr layout directly:

import zarr

ds = zarr.open("./out/quickstart/samples.zarr", mode="r")
sample = ds["samples/000000"]

iq = sample["iq.zarr"][:]                         # shape (1_000_000,) complex64
metadata = sample.attrs["metadata_json"]          # proposed v0 per-sample metadata
class_name = metadata["per_emitter"][0]["class_name"]   # e.g. "qpsk"
snr_db = metadata["per_emitter"][0]["snr_db"]           # e.g. 18.4

print(f"Sample 0: {class_name} at {snr_db:.1f} dB SNR, {iq.shape[0]} IQ samples")

5. Add captions

Captions are Phase 2 text annotations. They require configured external LLM credentials and may send metadata to a paid provider; the annotator reads metadata and labels, not IQ. See Annotate an existing dataset for the full workflow and Annotation Templates for the caption contract. The caption_only config selects the caption annotation type without changing IQ or structured labels.

export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json

rfgen annotate ./out/quickstart \
    annotator=caption_only \
    --bulk-model gemini-3.1-flash-lite

The same store now carries captions:

sample = zarr.open("./out/quickstart/samples.zarr/samples/0", mode="r")
print(sample["text/caption"][...])
# (LLM-generated caption describing the QPSK signal, SNR, occupied bandwidth, and channel.)

What just happened

Step

What ran

rfgen generate +preset=...

Hydra composed the config tree, Pydantic validated typed config objects, and the local single-process executor ran generation

Per sample

BaseEmitter.generateBaseChannel.applyBaseLabeler.labelStoreHandle.write

rfgen annotate

Re-opened the store, ran BaseAnnotator.annotate_store for each sample, and appended a caption row through StoreHandle.append_annotation_row

For the multi-emitter wideband path, see Your First Scene →.