Scene Composition

Scene composition draws one training example from a configured population. For one sample_idx, it chooses which transmitters appear, when and where their signals land, how strongly they arrive, and what metadata explains the draw.

In contract terms, composition realizes one concrete scene from a scenario distribution: how many emitters appear, which classes they use, where they land in time and frequency, how they overlap, and what power or signal-to-noise ratio (SNR) prior they follow. A prior is the configured distribution or target range that the composer samples before channel effects produce the final received signal.

Composition is independent of 3D geometry. A non-geometric scene can use a statistical propagation backend, meaning a library channel model between each transmitter and receiver with no loaded 3D asset. A geometric scene uses the same placement and orchestration model, plus Scene Geometry for SionnaRT ray tracing (RT).

What Composition Does

The composer turns a scene config, emitter pool, channel chain, and random number generator (RNG) seed into one populated scene-level Signal. The scene config is the typed Python object or YAML-derived settings for the population. The emitter pool is the set of waveform generators available for selection. The channel chain is the configured transmit, propagation, receive-capture, and receiver-hardware pipeline applied to selected signals. The important distinction is:

  • Config / preset: describes a dataset distribution. Fields can be fixed values, ranges, weights, or named distributions.

  • Scene plan: one concrete realization of that distribution for one sample_idx.

  • Scene signal: in-phase/quadrature (IQ) tensor plus metadata produced for this one draw.

In the diagram below, transmit (TX) steps happen before propagation, receive (RX) steps happen after receiver summation, and baseband means complex samples centered at 0 Hz with the carrier frequency carried as metadata. component_signals[] is the list of per-emitter signal records preserved inside the scene output.

        flowchart TD
    cfg["Scenario config<br/>fixed values + ranges + weights"]
    pool["Emitter pool<br/>available waveform classes"]
    seed["Seed<br/>global_seed + shard_id + sample_idx"]
    plan["Realize scene plan<br/>count, classes, params, time, freq, power"]
    gen["Generate components<br/>clean baseband Signal per emitter"]
    chan["Apply pre-sum channels<br/>TX impairments → channel propagation"]
    sum["Sum receivers<br/>one composite scene buffer per RX"]
    post["Apply post-sum channels<br/>RX capture → RX hardware"]
    out["Output scene Signal<br/>component_signals[] + realized metadata"]

    cfg --> plan
    pool --> plan
    seed --> plan
    plan --> gen --> chan --> sum --> post --> out
    

The phases are:

Phase

What happens

What becomes metadata

Realize scene plan

Choose emitter count, classes, per-emitter parameters, time/frequency placement, power or SNR target, device identity, optional position.

Realized count, class names, parameter values, start samples, center offsets, target SNR or power, device IDs, positions.

Generate components

Call emitter backends for clean baseband waveforms.

Per-emitter waveform metadata and source backend provenance.

Apply pre-sum channels

Apply TX impairments and channel propagation before receiver summation.

Fingerprint values, propagation profile, path/channel realization IDs.

Sum receivers

Combine propagated components into one scene buffer per receiver.

Overlap outcomes and component-to-scene alignment.

Apply post-sum channels

Apply RX capture, which mixes to the receiver frame, applies an intermediate-frequency (IF) filter, resamples, sums, and injects low-noise-amplifier (LNA) noise, then apply RX hardware, including analog-to-digital converter (ADC), phase-noise, IQ-imbalance, and automatic-gain-control (AGC) effects.

Realized receiver state, noise floor, realized SNR.

Output scene signal

Preserve scene IQ and component_signals[].

Ground truth consumed by labelers, storage, audits, and annotations.

Composition owns orchestration and metadata. Emitters own waveform generation. Channels own RF impairments, propagation, RX capture, and RX hardware effects.

Use fixed config values when every record should share the same structure. Use ranges, weights, and distributions when the dataset should cover a population of possible scenes. Both modes use the same composer and the same deterministic seed flow.

Minimal Examples

Python examples use enum members for framework-owned closed choices. YAML can still use enum string values because Pydantic, the validation library that turns config dictionaries or YAML into typed SceneConfig objects, deserializes them at the config boundary. Exact enum members live in Reference / API / Enums.

These examples are contract skeletons. The emitter_pool and channel_chain placeholders come from emitter and channel setup code, and the PyTorch torch.Generator supplies a deterministic RNG for this one scene draw.

Sparse random scene:

import torch

from rfgen.config import SceneConfig
from rfgen.enums import (
    DensityMode,
    FrequencyPlacementStrategy,
    SceneOverlapPolicy,
    TimePlacementStrategy,
)
from rfgen.scene import DefaultSceneComposer

composer = DefaultSceneComposer()

scene = SceneConfig(
    bandwidth_hz=20e6,
    duration_s=0.020,
    # Mean of 0.2 emitters per scene draw (before Poisson variation and min/max clipping).
    density={"mode": DensityMode.POISSON, "poisson_rate": 0.2},
    frequency_placement=FrequencyPlacementStrategy.IID_UNIFORM,
    time_placement=TimePlacementStrategy.IID_UNIFORM,
)

signal = composer.build(
    scene_cfg=scene,
    emitter_pool=emitter_pool,
    channel=channel_chain,
    rng=torch.Generator().manual_seed(0),
)

Protocol-aware industrial, scientific, and medical (ISM) scene:

Protocol-aware means time or frequency placement follows protocol timing or band conventions instead of independent uniform draws.

composer = DefaultSceneComposer()

scene = SceneConfig(
    bandwidth_hz=100e6,
    duration_s=0.100,
    density={"mode": DensityMode.POISSON, "poisson_rate": 1.5},
    frequency_placement=FrequencyPlacementStrategy.REALISTIC_DENSITY,
    frequency_placement_params={"taxonomy": "wifi-5ghz"},
    time_placement=TimePlacementStrategy.EVENT_PERIODIC_BEACON,
    time_placement_params={"period_s": 0.1024},
    geometry={"overlap_policy": SceneOverlapPolicy.ALLOW},
)

signal = composer.build(
    scene_cfg=scene,
    emitter_pool=emitter_pool,
    channel=channel_chain,
    rng=torch.Generator().manual_seed(1),
)

Ray-traced scene composition still uses the same composer. Geometry appears in the scene and propagation configuration, not in a separate composition algorithm.

Available Implementations

DefaultSceneComposer is the only planned built-in composer. It coordinates heterogeneous emitter selection, time-frequency placement, channel routing, summing, and metadata preservation.

The composer interface is extensible: subclassing BaseSceneComposer is the path for a specialized scene-population engine, such as reproducing a specific radio-frequency machine learning (RFML) baseline or delegating population to an upstream system-level simulator, an external tool that decides which devices transmit when. This is not required for normal dataset generation. The contract that any composer must preserve is documented at BaseSceneComposer and on the Scene Composition Algorithm reference page.

The placement strategies are also extensible as standalone plugins:

  • Time placement: subclass BaseTimePlacement and register under the rfgen.time_placement entry-point group.

  • Frequency placement: subclass BaseFrequencyPlacement and register under the rfgen.freq_placement entry-point group.

Custom placement strategies do not require a custom composer; they plug in by name through SceneConfig.time_placement / SceneConfig.frequency_placement, with constructor kwargs carried in SceneConfig.time_placement_params / SceneConfig.frequency_placement_params.

Python entry points are how installed packages advertise plugin classes to rfgen. The custom placement strategy how-to covers the registration workflow.

Why rfgen Owns Heterogeneous Composition

TorchSig-style RFML baselines are an important compatibility target, but rfgen’s scene composer has a broader job: heterogeneous, metadata-rich scenes that can feed detection, segmentation, captioning, reasoning, and downstream training workflows.

In this table, independent and identically distributed (i.i.d.) placement means each draw is sampled independently from the same configured distribution.

Need

Benchmark-style mixture

rfgen composer

Carrier placement

i.i.d. uniform over configured range

i.i.d., stratified, clustered, ISM-realistic, forced-overlap

Time placement

i.i.d. start sample

i.i.d. plus protocol-aware event timing

Emitter count

signal probabilities

density-aware count, e.g. fixed, range, or Poisson-mean per scene

Overlap handling

scalar cochannel probability and retry loop

explicit reject / allow / force policies with metadata

Dense-scene failure mode

retry exhaustion can truncate scenes

truncation recorded in metadata; non-rejection policies available

Multi-RX

not the primary artifact shape

optional multi-RX scenes

Streaming

in-memory generation

ChunkedSignal for large captures

The output remains compatible with downstream RFML tooling where possible: component_signals[] carries per-emitter metadata in the scene reference frame.

Placement Strategies

FrequencyPlacementStrategy selects how emitters are placed in frequency. The five shipped members and their ABCs are:

Member

Class

Description

IID_UNIFORM

IIDUniformFreq

Uniform draw over [freq_min_hz, freq_max_hz] with optional min_spacing_hz rejection.

STRATIFIED

StratifiedFreq

Equal-width bin selection with optional per-bin weights.

REALISTIC_DENSITY

RealisticDensityFreq

Draws from the per-family channel plan loaded through a BaseChannelPlanSource. Also accessible as ISM_REALISTIC / ISMRealistic.

CLUSTERED

ClusteredFreq

Picks an anchor from anchors_hz then adds Gaussian jitter.

FORCED_OVERLAP

ForcedOverlap

With probability p_force, draws inside a previously-placed emitter’s occupied band.

All five strategies use rejection sampling with _MAX_RETRIES = 64. On exhaustion, the strategy raises PlacementError naming the strategy, the exhausted retry budget, and the configured min_spacing_hz. There is no nearest-feasible fallback.

TimePlacementStrategy selects how bursts are scheduled in time. The five shipped members are:

Member

Class

Description

IID_UNIFORM

IIDUniformTime

Uniform start-sample draw over [0, scene_duration_samples).

EVENT_RADAR_PRI

EventRadarPRI

Fixed-PRI pulse train with per-pulse scipy.stats.norm jitter.

EVENT_PERIODIC_BEACON

EventPeriodicBeacon

Fixed-cadence schedule; phase chosen by numpy.random.Generator.uniform(0, period). Covers Wi-Fi TBTT, BLE advertising, ADS-B squitter, and cellular SSB.

EVENT_BURST

EventBurst

Heavy-tailed Pareto on/off durations via scipy.stats.pareto.rvs.

EVENT_FHSS_HOP

EventFhssHop

Fixed-dwell FHSS schedule; dwell length drawn from a configured set via numpy.random.Generator.choice.

Source: Reference / Scene Composition Algorithm § Frequency placement strategies and § Time placement strategies define the local draw contracts and parameter tables. Protocol timing comes from standards and reference texts:

  • Wi-Fi timing uses IEEE 802.11-2020 target beacon transmission time (TBTT).

  • Bluetooth timing uses the Bluetooth Core Specification 5.4 for BLE advertising and Bluetooth hopping.

  • ADS-B timing uses ICAO Annex 10 Volume IV Chapter 3.1.2.8.1.1 for Mode S extended squitter.

  • Radar PRI scheduling uses Skolnik’s Radar Handbook.

Channel-plan defaults are grounded by per-band JSON files loaded via JsonChannelPlanSource. Shipped bands: wifi-2.4ghz (IEEE 802.11-2020 Table 17-9, non-overlapping channels 1/6/11 with higher weights), wifi-5ghz (IEEE 802.11-2020 Tables 17-12/17-13), ble (Bluetooth Core 5.4 Vol 6 Part B 1.4, advertising channels 0/12/39), lora-us915 (LoRa Alliance RP002-1.0.4 Table 2-3), lora-eu868 (RP002-1.0.4 Table 2-7), adsb-1090mhz (ICAO Annex 10 Volume IV).

Per-strategy parameters and validation rules live in Reference / Scene Composition Algorithm.

Power and SNR Policies

ScenePowerMode selects the power prior before channel realization. A relative SNR target asks the channel chain to reach a received signal-to-noise ratio after propagation and receiver noise. Absolute-power mode keeps transmit or receive power in physical units and lets Sionna, the delegated channel simulator, determine the received level. Members: UNIFORM_DB (controlled uniform ranges in decibels, dB, for classifier benchmarks), LOG_UNIFORM, LOG_NORMAL_HEAVY_TAIL (realistic mixtures), SIONNA_ABSOLUTE_POWER (absolute-power mode where Sionna propagation and receiver noise determine received power).

The realized SNR is determined after propagation and RX-capture LNA noise injection. Composition samples the prior or target; the channel chain realizes the actual received signal and noise floor.

Source: Reference / Scene Composition Algorithm § SNR and power defines the local prior and reconciliation modes. The receiver-noise operation is the T10 LNA-noise transformation documented in Channels / RX Capture, whose point-of-claim source is the thermal-noise relation used by the local noise-floor table.

Overlap Policies

Cochannel overlap means two emitters occupy the same time-frequency rectangle in the same receiver band. SceneOverlapPolicy selects how the composer handles that case. Members: REJECT, ALLOW (probabilistic; per-draw probability lives on the SceneOverlapPolicyConfig’s p_overlap field), FORCE (deliberate cochannel collisions for stress tests).

Overlap decisions are metadata-bearing events. If rejection budgets are exhausted, the composer must record the realized count and truncation reason so downstream consumers know what was generated. The SceneOverlapPolicyConfig sub-model carries the full parameter surface (p_overlap, retry_budget, margin_hz, overlap_target_strategy).

Source: Reference / Scene Composition Algorithm § Overlap policy defines the local rectangle-overlap semantics, retry behavior, and TorchSig compatibility target. TorchSig’s signal type source is the upstream compatibility anchor for time-frequency rectangle metadata.

Multi-RX and Streaming

Multiple-receiver scenes produce IQ with a receiver axis in the tensor. Each receiver gets its own channel and front-end realization, meaning its own propagation result and receiver-side hardware state. Geometry-aware multi-RX uses the same RX positions and arrays consumed by SionnaRT; statistical multi-RX uses the selected statistical backend’s topology and correlation behavior. Output shape conventions live in Records, Receivers, and Assets.

Large scenes use ChunkedSignal once predicted IQ size exceeds the configured memory threshold. Chunking is part of the scene contract because long captures at high sample rates exceed memory quickly.

Open Questions

  • Measured density priors. Published measured density anchors for industrial, scientific, and medical (ISM) bands, Automatic Dependent Surveillance-Broadcast (ADS-B), LoRaWAN, Narrowband Internet of Things (NB-IoT), Long-Term Evolution for Machines (LTE-M), and vehicle-to-everything (V2X) coexistence need a separate verification pass.

  • 3GPP scenario presets. Sionna-backed UMa, UMi, and RMa presets could bundle topology defaults, array defaults, BS/UE heights, and indoor/outdoor state.

  • Protocol coordination. Carrier-sense, cellular handover, coordinated multi-point, and retransmission behavior remain out of scope until mature upstream protocol simulators are selected.

Determinism

Same (global_seed, shard_id, sample_idx) produces the same scene composition. Per-emitter sub-seeds derive from (scene_seed, "emitter", emitter_idx). Per-RX sub-seeds derive from (scene_seed, "rx", rx_idx).

Determinism must not depend on Spark partition order, rejection-loop timing, or backend import order. Full seed-flow rules live in Reference / Determinism.

See Also