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 |
|---|---|
|
Hydra composed the config tree, Pydantic validated typed config objects, and the local single-process executor ran generation |
Per sample |
BaseEmitter.generate → BaseChannel.apply → BaseLabeler.label → StoreHandle.write |
|
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 →.