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 resampling and
frequency placement. Per-emitter and geometry-backed propagation is prepared
from that emitted waveform before a time start is drawn. Strict containment
then validates the pre-propagation emitted footprint, and an accepted start
controls where the prepared received result is mixed. Statistical propagation
configured for scene application remains post-sum; receive (RX) steps follow
receiver summation. Baseband means complex samples
centered at 0 Hz with the carrier frequency carried as metadata.
component_signals[] is the list of per-event signal records preserved inside
the scene output. Each record corresponds to one accepted placement of one
emitted event. A selected emitter slot can therefore produce zero records when
an allowed-empty time strategy returns no starts, or multiple records when it
returns multiple accepted starts; records follow accepted-placement order rather
than a one-record-per-slot plan.
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"]
tx["Apply TX processing<br/>per-emitter baseband"]
prep["Resample + frequency placement<br/>prepare per-emitter propagation"]
place["Draw/validate time start<br/>strict emitted-event fit, then mix prepared IQ"]
sum["Sum receivers<br/>scene-mode statistical propagation post-sum"]
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 --> tx --> prep --> place --> sum --> post --> out
The phases are:
Phase |
What happens |
What becomes metadata |
|---|---|---|
Realize scene plan |
Choose emitter-slot count, classes, per-slot parameters, time/frequency placement rules, power or SNR target, device identity, optional position. |
Selected-slot count, class names, parameter values, placement rules, 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. |
TX processing |
Apply TX impairments to each emitter’s native baseband waveform. |
Fingerprint values. |
Resample, frequency-place, and prepare propagation |
Resample to scene rate, apply the frequency offset, then prepare per-emitter or geometry-backed propagation for each receiver path. The emitted waveform, not any channel-lengthened output, remains the authority for event duration and label extent. |
Carrier and propagation profile/path-channel realization IDs. |
Draw start and mix |
Draw and validate each time start against the actual pre-propagation emitted length. Each accepted start creates one emitted-event record and determines where the already-prepared result is mixed; an allowed-empty strategy creates none. |
Start sample and emitted duration for every accepted event. |
Sum receivers |
The receiver buffers contain the sum of prepared per-emitter results. A statistical propagation backend configured for scene application runs once on this summed buffer. |
Overlap outcomes and component-to-scene alignment. |
Apply post-sum channels |
Apply the nine-stage core RX chain once to each summed receiver buffer: LO error, intended mixing, IF filter, resampling, LNA noise, ADC, phase noise, IQ imbalance, and digital AGC. RX frontend validation records its evidence and boundaries. |
Realized receiver state, noise floor, realized SNR. |
Output scene signal |
Preserve scene IQ and |
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.
Compose an Already-Resolved Event¶
Use an explicit composer plan when an upstream planning job has already chosen
an event’s duration and capture interval. The composer then uses
derive_rng(run_seed, sample_id, event_id) for that event instead of
redrawing its duration or start. The emitter pool and channel pipeline still
create the IQ capture.
One scene can contain several atomic TX-side events. Each event gets its own
seed key and can fan out through the existing receiver/channel pipeline. The
composer writes their durable identity records to
artifacts/plans/<scene_id>/composer-provenance.json. Omit composer_plan
for ordinary distribution-driven composition; that legacy path is unchanged.
A resolved source plan can also pin each event to a configured emitter key and class, placement-support bandwidth, frequency offset, typed TX pose, and finite TX power. These source/placement values are supplied together; they are checked against the configured emitter pool and scene band before IQ is mixed. The resulting version-2 provenance records the derived absolute carrier. This remains a composition control surface, not a protocol simulator, captured replay, or measured occupied-bandwidth claim.
Input-construction fragment; this does not create a composer, scene, emitter pool, or channel pipeline:
from rfgen.planning import DurationPlan, ResolvedEvent
from rfgen.scene import ComposerEvent, ComposerPlan
plan = ComposerPlan(
run_seed=73,
sample_id="sample-0001",
scene_id="capture-0001",
events=(ComposerEvent(
planned_event_id="event-0", system_id="system-0", device_id="device-0",
transmitter_role="uplink", link_id="link-0", link_direction="device-to-receiver",
event=ResolvedEvent(event_id="event-0", anchor="absolute", start_sample=100,
duration_samples=100, stop_sample=200, contained=True),
duration=DurationPlan(policy_kind="fixed", min_s=0.00005, max_s=0.00005,
seed=7, requested_duration_s=0.00005,
realized_duration_samples=100, issuer="scene", contained=True),
),), # add further independently keyed ComposerEvent values as needed
)
# Pass this object as composer_plan=plan to an already configured
# DefaultSceneComposer.build(...) call.
The system, device, role, and link fields identify a planned transmission. They are provenance, not a protocol simulation or a measurement claim. See the Scene API for the exact input and artifact.
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 show the composition call. Build emitter_pool from explicit
emitter selectors and channel_chain from the configured channel entries; 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.core.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),
)
Generic fixed-duration bursts¶
Set scene.event_duration when each generated event should occupy only part
of a longer capture. This is a scene-wide fixed policy: every selected emitter
slot uses the same duration. The composer generates the event at that duration,
then applies TX processing, resampling/frequency placement, and per-emitter or
geometry-backed propagation preparation. It draws and validates time starts
from the emitted scene-rate footprint, then mixes the prepared result at each
accepted start; statistical scene-mode propagation and RX processing still run
once on the complete capture. Omit the policy to keep
full-scene event generation. In either case, time placement is drawn after
resampling and every returned start must retain the whole final event; invalid
starts fail scene generation rather than cropping the event.
See Scene Capture Boundaries for the precise meanings of
sample, event, and scene, and for the fixed-capture policy: emitted events are
never cropped, while a channel-induced propagation tail beyond the receiver
capture edge is not recorded. Component metadata and labels remain the emitted
event extent. Coordinate Systems is the canonical
reference for the scene-rate start_sample coordinate and its mapping to
per-receiver label coordinates.
import torch
from rfgen.engine.propagation_generic import AWGNChannel
from rfgen.config import SceneConfig
from rfgen.core.enums import DensityMode, TimePlacementStrategy
from rfgen.domains.radar.chirp_emitter import ChirpRadarEmitter
from rfgen.scene import DefaultSceneComposer
composer = DefaultSceneComposer()
scene = SceneConfig(
sample_rate_hz=25_000_000.0,
bandwidth_hz=10_000_000.0,
duration_s=0.002,
event_duration={"mode": "fixed", "duration_s": 0.0005},
density={"mode": DensityMode.FIXED, "min_emitters": 1, "max_emitters": 1},
time_placement=TimePlacementStrategy.IID_UNIFORM,
)
signal = composer.build(
scene_cfg=scene,
emitter_pool={"radar.lfm": ChirpRadarEmitter()},
channel=AWGNChannel(),
rng=torch.Generator().manual_seed(7),
)
assert all(
component.metadata.start_sample + component.metadata.duration_samples
<= signal.iq.shape[-1]
for component in signal.component_signals
)
print(
f"scene_samples={signal.iq.shape[-1]}, "
f"event_samples={signal.component_signals[0].metadata.duration_samples}"
)
# scene_samples=50000, event_samples=12500
This example requires Python with rfgen and its normal torch dependency
installed. To verify the fixed-burst composer behavior from a source checkout,
run PYTHONPATH=src python -m pytest -q tests/unit/test_scene_composer.py;
Test Execution explains the
repository’s test commands and environments.
This is a generic RF-emission capability. It does not by itself model
Wi-Fi, Bluetooth, drone, or other protocol packet cadence, hopping, framing,
or calibrated captures. The resolved event_duration object is part of the
normal resolved configuration provenance, so stored
records can be traced to the exact fixed-duration policy used.
Standards-inspired industrial, scientific, and medical (ISM) timing-template scene:
The placement settings below use a generic fixed-cadence timing template and a Wi-Fi channel-plan label. They are useful for constructing a non-uniform RF mixture, but they do not reproduce Wi-Fi TBTT behavior, BLE advertising, packet framing, hopping state, device contention, or any other protocol state machine. Use a protocol simulator or a purpose-built emitter when that level of fidelity is required.
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_seconds": 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 shipped 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_placemententry-point group.Frequency placement: subclass BaseFrequencyPlacement and register under the
rfgen.freq_placemententry-point group.
Custom time-placement strategies do not require a custom composer; they plug
in by name through SceneConfig.time_placement, with constructor kwargs in
SceneConfig.time_placement_params. Registry construction reserves and
injects the scene’s scene_duration_samples (and sample_rate_hz when the
constructor declares it); a length-aware plugin must accept and use the former.
Conflicting user values fail instead of authorizing capture-edge cropping. A
time_planner_factory receives no injected kwargs and must construct its
planner with its own context; regardless of the path, the composer validates
the resulting starts against the actual event length. The full extension
contract, including the required schema() -> type[BaseModel], is in the
Scene API.
Frequency-placement plugins may be
registered, but the current SceneConfig.frequency_placement contract selects
only shipped enum values; its parameter field is
SceneConfig.frequency_placement_params.
Python entry points are how installed packages advertise plugin classes to rfgen. The Scene API 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 configured generic timing templates |
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 |
The output remains compatible with downstream RFML tooling where possible:
component_signals[] carries one accepted placed/emitted event’s metadata per
record 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 |
|---|---|---|
|
|
Uniform draw over |
|
|
Equal-width bin selection with optional per-bin weights. |
|
|
Draws from the per-family channel plan loaded through a |
|
|
Picks an anchor from |
|
|
With probability |
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 six shipped members are:
Member |
Class |
Description |
|---|---|---|
|
|
Uniform start-sample draw satisfying |
|
|
Fixed-PRI pulse train with per-pulse |
|
|
Generic fixed-cadence schedule; phase chosen by |
|
|
Generic multi-start cadence whose independently capped Pareto-derived on/off increments are summed between starts. The finite caps bound the realized cadence; they do not define event duration or protocol traffic behavior. |
|
|
Fixed-dwell FHSS schedule; dwell length drawn from a configured set via |
|
|
Generic Hawkes self-exciting point-process schedule with an exponential kernel. Use it only when empirically clustered event starts are an appropriate synthetic prior; it is not a Wi-Fi, Bluetooth, cellular, radar, or other protocol simulator. |
Source: Reference / Scene Composition Algorithm § Frequency placement strategies and § Time placement strategies define the local draw contracts and parameter tables. The following standards and reference texts can motivate generic timing or channel-plan parameters; citing them does not make a generic placement strategy protocol-faithful:
IEEE 802.11-2020 specifies target beacon transmission time (TBTT).
Bluetooth Core Specification 5.4 describes BLE advertising and Bluetooth hopping.
ICAO Annex 10 Volume IV Chapter 3.1.2.8.1.1 describes Mode S extended squitter.
Skolnik’s Radar Handbook discusses radar PRI scheduling.
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¶
Power is stated one of two ways, and there is no enum selecting between them.
ChannelConfig.snr_db_range (default (-10.0, 30.0)) is a relative target:
it asks the channel chain to reach a received signal-to-noise ratio after
propagation and receiver noise. SceneConfig.tx_power_dbm (float | None,
default None) is absolute: it keeps transmit power in physical units and
lets the propagation backend determine the received level. A plan-driven
activity may also draw tx_power_dbm per sample through its sampling policy,
which is what the shipped narrowband-baseline template does.
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.
A scene is composed whole, in memory. A ChunkedSignal wrapper and a memory threshold stood here; they recorded chunk geometry without ever streaming, and both are removed. Bound a scene to available memory through scene.duration_s and scene.sample_rate_hz.
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 configuration examples. Sionna-backed UMa, UMi, and RMa examples 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 rejection-loop timing or backend import order. Full seed-flow rules live in Reference / Determinism.
See Also¶
Scenes overview for the parent concept.
Scene Geometry for 3D scene assets consumed by SionnaRT.
Concepts / Channels for pre-sum and post-sum channel transformations.
Concepts / Emitters for the waveform generators populated into emitter slots.
Reference / Scene Composition Algorithm for the normative algorithm.
Reference / TorchSig Interop for benchmark compatibility.
Sionna integration for Sionna PHY and RT mapping.