Generate a dense urban dataset

Run the dense_urban_2_4ghz preset end to end: 1 M samples of dense 2.4 GHz ISM with full annotations, distributed on GCP Managed Spark.

Warning

Pre-implementation. Commands target v1+. Steps that interact with GCP resources will incur cost when implementation lands.

What you’ll get

  • 1 M samples × 100 ms × 200 Msps wideband recordings (samples.zarr/ ≈ 8 TB)

  • ~50 emitters per scene drawn from comms / IoT (Wi-Fi, BLE, Zigbee, LoRa)

  • Sionna-RT ray-traced multipath over the Munich downtown OSM scene

  • Joint labels (bbox + segmentation + per-emitter + scene metadata)

  • Full annotation suite (caption + Q&A + reasoning + scene_report + contrastive pairs) via Gemini Flash bulk + Sonnet 4.6 verifier subset

Prerequisites

pip install "rfgen[sionna,torchsig,annotator,spark]"

# GCP auth
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa.json
export GCP_PROJECT=my-rf-fm-project

You’ll need:

  • A GCS bucket (e.g., gs://rf-fm-datasets-synth)

  • Vertex AI API enabled in your project

  • Managed Spark serverless API enabled

  • Sionna’s Munich OSM scene downloaded to gs://your-bucket/sionna-scenes/munich/

1. Inspect the preset

rfgen show-preset dense_urban_2_4ghz

Confirm parameters look right (especially run.num_samples, storage.path, orchestration.spark.project_id).

2. Validate first (free, ~5 seconds)

rfgen validate +preset=dense_urban_2_4ghz \
    storage.path=gs://rf-fm-datasets-synth/dense_urban_2_4ghz/v0.5.0 \
    orchestration.spark.project_id=$GCP_PROJECT

Output:

Note

Illustrative. Specific values below (config hash, cost estimates, timestamps, audit metrics) show the target output shape, not measured behavior. Real numbers come from real runs once v0 lands.

✓ emitter_zoo (4 families, 22 classes)
✓ channel: sionna_rt (scene: munich.xml, 100 paths max)
✓ scene: dense (sample_rate=200e6, bw=100e6, density=poisson(50))
✓ label: joint (bbox + seg + metadata)
✓ annotator: full_suite (bulk: gemini-3.1-flash-lite, verifier: claude-sonnet-4-6 @ 5%)
✓ storage: zarr_gcs
✓ orchestration: spark_serverless
Resolved config hash: sha256:<digest>
Estimated cost: ~\$<compute> compute + ~\$<llm> LLM = ~\$<total> total

3. Phase 1: generate IQ + structured labels

rfgen generate +preset=dense_urban_2_4ghz \
    storage.path=gs://rf-fm-datasets-synth/dense_urban_2_4ghz/v0.5.0 \
    orchestration.spark.project_id=$GCP_PROJECT

Output:

Submitting Spark batch job: rfgen-dense-urban-<timestamp>
  region: us-central1
  template: managed-spark-rfgen-v0.5.0
  executors: autoscale 8–32 × n2-standard-8
  
Streaming job logs ... (Ctrl-C to detach; job continues)

[<t0>] Worker pool warming up ...
[<t1>] Shard 0 / 1000 (sample 0 / 1_000_000) started
...
[<tN>] All shards complete
[<tN+1>] Cross-modality consistency: <pct> (<excluded> samples excluded)
[<tN+2>] Statistics audit: PASS
[<tN+3>] Phase 1 complete. Wrote <kept> of 1000 shards.

gs://rf-fm-datasets-synth/dense_urban_2_4ghz/v0.5.0/
  manifest.json
  samples.zarr/        <size>
  shards/              <size>
  stats.json
  errors.jsonl         (<excluded> records; see audit report)

Wall-clock: ~4 hours on autoscaling Spark with 8–32 n2-standard-8 executors. Cost: ~$.

4. Phase 2: annotate

rfgen annotate gs://rf-fm-datasets-synth/dense_urban_2_4ghz/v0.5.0 \
    annotator=full_suite \
    orchestration=vertex_batch

Output:

Submitting Vertex AI Batch Prediction job: rfgen-annotate-<timestamp>
  bulk model: gemini-3.1-flash-lite
  verifier: claude-sonnet-4-6 @ 5% subset
  total samples: 1_000_000
  total annotation calls: 5_000_000 (5 types × bulk) + 250_000 (verifier)

[<t0>] Templating Phase-1 records → JSONL ...
[<t1>] Submitting bulk batch (gemini-3.1-flash-lite, 5 sub-jobs) ...
[<t2>] Bulk done. PAES estimate (verifier subset): <value>
[<t3>] Submitting verifier batch (claude-sonnet-4-6, 5 sub-jobs) ...
[<t4>] Verifier done. PAES: <value>. Hallucination Count: <value> / sample.
[<t5>] Appending annotations to Zarr ...
[<t6>] Phase 2 complete.

Wall-clock: ~1.5 hours via Vertex Batch (rate-limited but parallelized across sub-jobs). Cost: ~$.

5. Verify

rfgen inspect gs://rf-fm-datasets-synth/dense_urban_2_4ghz/v0.5.0 distribution

Audit checks (target shape; specific values come from a real run):

  • ✓ Class balance: every class within 50–200% of expected weight

  • ✓ SNR distribution: log-uniform within 0.05 KL-divergence

  • ✓ Density: realized within tolerance of target

  • ✓ Delay-spread RMS: within tolerance of target

  • ✓ Annotation PAES: ≥ 0.80 per class (per-template ≥ 0.85, dataset ≥ 0.90)

  • ✓ Hallucination Count: ≤ 0.5 / sample on verifier subset

If any audit fails, the dataset version is not promoted to the published prefix.

6. Train

The dataset is now ready to consume in any of three ways:

# WebDataset (recommended for GPU pretraining)
from torch.utils.data import DataLoader
import webdataset as wds

ds = (
    wds.WebDataset("gs://rf-fm-datasets-synth/dense_urban_2_4ghz/v0.5.0/shards/shard-{000000..000999}.tar")
    .decode()
    .to_tuple("iq.npy", "bbox.json", "caption.txt")
)
loader = DataLoader(ds, batch_size=8, num_workers=4)
# Zarr (recommended for analysis / random access)
from rfgen.storage import open_store

with open_store("gs://rf-fm-datasets-synth/dense_urban_2_4ghz/v0.5.0") as store:
    for sample in store.iter_random(n=1000, seed=0):
        ...
# HDF5 (recommended for TorchSig-trained model interop)
from torchsig.utils.dataset import StaticTorchSigDataset

ds = StaticTorchSigDataset(root="gs://rf-fm-datasets-synth/dense_urban_2_4ghz/v0.5.0/hdf5/")

Tips

Generating a smaller variant for prototyping

Override run.num_samples and use the _md (100K) or _xs (10K) variant:

rfgen generate +preset=dense_urban_2_4ghz size=md

The _md variant runs in ~30 minutes on Spark for ~$.

Streaming mode for very long scenes

For scenes > 256 MB IQ payload, the framework automatically switches to ChunkedSignal. Configure the threshold via scene.chunk_threshold_mb. Spark workers handle chunking transparently.

Resuming a partial run

Phase 1 and Phase 2 are both idempotent. Re-run the same rfgen generate / rfgen annotate command; already-completed shards / annotation columns are skipped automatically. Useful when a worker dies mid-run or a quota is hit.