Unified Scene Observations¶
Use a unified scene observation when one generated sample must preserve several views of the same RF scene. Communications receiver IQ, active-radar response, labels, and future domain outputs remain separate named fields, but they share one clock, one scene identity, and one persisted record.
Here IQ means in-phase/quadrature complex samples; baseband means the
frequency-translated representation around 0 Hz. The RF glossary
defines IQ, local oscillator (LO), phase, noise, and pulse repetition interval
(PRI). On this page, an epoch is only SceneClock.time_origin_s, the
numerical origin of a plan. It makes no UTC, TAI, Unix-time, or civil-timescale
claim. In pulsed radar, fast time indexes samples within one pulse and the
pulse axis is slow time across pulses. A measurement plane identifies
where in the receive chain a sample is defined, including its phase,
oscillator, dechirp, and noise meaning. Dechirping mixes a received
frequency sweep with a reference copy of the transmitted sweep so its delay
and Doppler appear as lower-frequency beat components.
flowchart LR
PLAN["ScenePlan<br/>one scene · one SceneClock"]
COMM["Communications projection<br/>receiver IQ · labels"]
RADAR["Radar projection<br/>baseband · response components"]
FUTURE["Future projection plugin<br/>new fields · native axes"]
OBS["SceneObservationRecord<br/>one aggregate observation"]
ADAPTER["Central SDSObservationAdapter"]
SDS["exactly one<br/>signal_dataset.Record"]
PLAN --> COMM
PLAN --> RADAR
PLAN -. "rfgen.observation_projections" .-> FUTURE
COMM --> OBS
RADAR --> OBS
FUTURE -.-> OBS
OBS --> ADAPTER --> SDS
The projections do not pass samples to each other. Each reads the same
immutable ScenePlan, renders fields with its own physics, and returns a
ProjectionResult. The runtime validates all projections before expensive
work, then assembles their results into one SceneObservationRecord. The
central SDSObservationAdapter is the only
observation-to-Signal-Dataset conversion boundary.
What each projection does between the plan and the record¶
The diagram above says the two projections share a plan and rejoin at one record. Between those points they run unrelated physics, and the shapes they produce reflect that.
flowchart TD
PLAN["Minted ScenePlan<br/>clock · systems · activities · entities · world"]
SEED["derive_projection_seed(sample_seed, projection_id)<br/>one independent stream per projection"]
subgraph COMMS["Communications projection"]
CE["Emitters generate clean baseband<br/>TorchSig · one per planned emission"]
CT["TX impairments per emitter<br/>DAC · PA · TX phase noise · TX IQ · CFO"]
CP["Normalize any plugin-returned rate,<br/>then frequency placement and propagation<br/>Sionna or AWGN"]
CS["Select emitters inside each receiver's band,<br/>then sum"]
CR["Receiver chain per RX, post-sum<br/>capture plane, then hardware plane"]
CL["Labelers derive boxes and segmentation<br/>from declared geometry, not from sample energy"]
CE --> CT --> CP --> CS --> CR --> CL
end
subgraph RADAR["Active-radar projection"]
RS["Build this sample's radar system from the plan"]
RT["Read the plan's targets<br/>propagated to the event instant"]
RB["Backend solves visibility and paths<br/>sionna_rt"]
RC["Closed-form signal chain<br/>echo · Doppler · dechirp to beat frequency"]
RF["Optional receiver frontend per component"]
RS --> RT --> RB --> RC --> RF
end
REC["One SceneObservationRecord<br/>fields namespaced by projection_id"]
PLAN --> SEED
SEED --> CE
SEED --> RS
CL --> REC
RF --> REC
Three things about that picture carry weight.
Neither branch can see the other. A projection receives the plan and its
own seed, and returns a ProjectionResult. There is no channel between them,
so a radar return cannot leak into a communications capture — and if a scene
needs that coupling, it is a declared interaction the record must state, not
something a projection may do quietly. Every record carries each projection’s
disposition per interaction; cross_projection_rf_coupling is reported as
not_applicable or excluded_by_configuration rather than left unsaid.
Communications sums, radar separates. The communications branch sums
emitters into each receiver’s band, because that is what a receiver does. The
radar branch keeps baseband and noise apart, because a consumer can add
them and cannot subtract them.
Labels come from declared geometry, not from sample energy. A labeler reads what the plan and the realized metadata declare and rasterizes it. The segmentation labeler does take the IQ tensor, but only to fix the capture shape, the receiver axis, and the device the raster is built on — no label value is derived from a sample value. That is why a label is a statement about the scene rather than a measurement of the output.
Communications reception is one-way: an independent transmitter emits and a receiver captures the propagated signal. Active radar is coupled: the sensing system transmits its own waveform and observes target-reflected returns. The active-radar projection is available, but scientific qualification is bounded; consult Radar Response before making dataset claims.
Owner |
Responsibility |
Verification |
|---|---|---|
RFGen |
ScenePlan, projection lifecycle, typed contracts, deterministic identity, and orchestration. |
Observation API and focused observation contract tests. |
TorchSig |
Built-in communications waveform generation selected by communications configuration. |
|
Sionna |
Selected propagation/ray-tracing library boundary; RFGen adapts outputs rather than reimplementing its solver. |
|
RFGen |
Composes library-derived visibility/path facts into deterministic analytic point-target returns; custom because no selected library owns this exact cross-backend contract. |
Radar Response / signal chain and its bounded qualification section. |
RadarSimPy |
Validation-only radar oracle, never the normal generation backend. |
|
Signal Dataset |
Immutable shard/root format, record encoding, indexed and metadata-only reads. |
Storage and storage layout. |
One clock keeps domains synchronized¶
ScenePlan.clock is the shared SceneClock. Its time_origin_s is the scene epoch;
its duration_s bounds the observation. Activities use offsets from that
origin. Keeping the epoch and small, subsecond activity and sample offsets
separate avoids losing those offsets when an epoch is large.
Every time-bearing field refers to the plan’s content-bound time_reference.
Communications commonly uses uniform time coordinates. Pulsed radar commonly
uses pulse and fast-time coordinates. Segmented and explicit coordinates cover
nonuniform schedules. These coordinate contracts map native indices back to
the same scene clock, so synchronization does not require flattening fields to
one rate or shape.
For the stored contract — each kind’s time_composition, the arithmetic it
names, how to read the block off a field, and a worked cross-domain alignment —
see Time coordinates.
Measurement planes are load-bearing¶
Clock alignment is not permission to add arrays. A field declares its measurement meaning, axes, clock, receiver geometry, phase and oscillator references, and noise relationship. RFGen permits numeric addition only when the complete declared-contract compatibility predicate accepts it. This is summation eligibility, not proof of physical correctness. Otherwise, aligned fields coexist in the record without being summed.
Fields keep their native axes¶
There is no universal RF tensor shape. Each projection owns one or more
BaseObservationField values:
ComplexCapturestores complex64 measurements such as communications IQ or radar baseband.RealValuedCapturestores real-valued measurements.LabelFieldstores integer or Boolean supervision.
Each field carries semantic ObservationAxis values whose lengths must match
the array dimensions. An axis may also carry numeric coordinates. A
communications field might have receiver and time axes; a radar field might
have channel, pulse, and fast-time axes. The aggregate namespace is
projections/<projection_id>/<local_field_name>. Generated coordinate arrays
are auxiliary fields and remain associated with the same projection owner.
One envelope makes ownership and identity explicit¶
The Signal Dataset record stores the observation envelope under
metadata["rfgen"]. It includes:
the complete canonical scene plan and
scene_plan_reference;one descriptor per projection, including its selector, contract version, validated-parameter identity, and implementation identity; the runtime identity is bound inside that implementation identity;
field_owners,primary_fields, andauxiliary_fields, so a consumer can distinguish payloads from generated coordinate fields;interaction coverage and projection-owned metadata; and
invocation provenance: sample ordinal, sample seed, and the independently derived seed for each projection.
The observation identity covers the plan, projection descriptors, and invocation provenance. Projection seeds are derived from projection IDs, so adding or reordering another projection does not perturb an existing projection’s random stream. The final Signal Dataset record ID is derived from the complete encoded record. Retries therefore reproduce the same identity only when their persisted content and runtime contract agree.
One record is also a memory boundary¶
One scene observation becomes exactly one signal_dataset.Record; RFGen does
not fan an aggregate observation out by receiver. Signal Dataset loads a
record’s numerical fields as one record. It does not lazily page an individual
field tensor from within that record.
Set observation.max_tensor_bytes to bound the aggregate primary fields and
generated coordinate arrays. RFGen estimates the encoded size and checks the
actual encoded size before publication. Metadata-only reads remain available
through the store and SDSObservationAdapter.metadata_view(...), but a full
record read must fit in the consumer’s memory.
Projection plugins are an open seam¶
Built-in selectors are rfgen.communications.receiver and
rfgen.radar.receiver. A third-party projection subclasses
BaseObservationProjection, declares a strict Pydantic ParamsModel, a
contract version, interactions, and required store capabilities, then
registers its selector in the rfgen.observation_projections Python entry-point
group. See Observation API for the lifecycle
and Generate one unified observation dataset
for configuration and remote packaging.
The core does not branch on domain names. Domain facts live in strict, versioned ScenePlan extensions owned by systems, activities, or entities. A plugin must classify every relevant interaction before rendering. Unsupported interactions stop the complete observation before any projection publishes a partial result.
Compatibility and qualification boundaries¶
There is one architecture. A configuration without projections is refused
before anything is composed — a run declares what it observes through
projections and nothing else — so the second pipeline this section once
contrasted against no longer exists, and neither does the exclusivity rule that
kept the two apart. One ScenePlan produces one record, with every receiver held
inside it as a named subtree. Records written by earlier releases are not
rewritten and this build cannot read them.
Local and Dataproc Serverless execution both use the same projection resolver, preflight lifecycle, aggregate adapter, and identity rules. Dataproc adds an image/runtime handshake and verifies staged plugin distributions before cloud work. A projection-enabled submission fails before billing when its image is not explicitly qualified, its remote contract version is wrong, or its local plugin distribution does not match the staged package. Availability in a source checkout is not a claim that a particular image digest is qualified; see Dataproc Serverless.
RFGen’s declared-evidence annotation builder still targets the retained legacy record metadata. It does not yet interpret SceneObservationRecord fields or their projection envelope. Generate and inspect unified observations, but do not treat the legacy annotation path as aggregate-observation annotation; that integration is a separate milestone.