Signal, Capture, Record: the containers that hold the samples¶
Reading RFGen source or reference pages, you meet several objects that all
appear to “hold the samples”: Signal, ComplexCapture, LabeledScene, and
signal_dataset.Record. They are not alternatives and you do not choose between
them. Each owns a different stretch of the pipeline, and only the last one is
something you consume.
This page says which container holds your data where, why there is more than one, and what changed in the receiver unification work so that the reference pages and the code agree.
If you only consume datasets, you need one sentence from this page: the stored
object is a signal_dataset.Record, its contract is frozen, and nothing
described below changed it. The rest matters when you write an extension or
read a stack trace.
The containers at a glance¶
Container |
Where it lives |
Who touches it |
Do you see it? |
|---|---|---|---|
Transmit side: emitter output, transmit hardware impairments, one-way propagation |
Emitters, TX stages, propagation backends |
Only if you write an emitter or a transmit-side stage |
|
|
Receive side: everything from the receiver frontend to the projection, in both domains |
The shared receiver chain, both composers |
Only if you write a receiver stage or a scene renderer |
In flight between composition and projection: the composed scene plus its labels |
Labelers, projections |
Only if you write a labeler or a projection |
|
|
Storage: the unit written to disk and read by training code |
The store, your dataloader |
Yes. This is the product |
Two vocabulary notes, because the rest of the page leans on them:
IQ (“in-phase and quadrature”) is the pair of numbers that represents one complex sample of a radio signal. RFGen carries it either as two interleaved real channels shaped
[..., 2, N]or as one complex array, depending on the container.Baseband means the signal has already been shifted down so that the carrier frequency you tuned to sits at zero. A “complex baseband capture” is what a software-defined radio hands you.
The flow, end to end¶
flowchart TB
EM[Emitters<br/>one Signal per emitted event]
TX[Transmit hardware impairments<br/>Signal in, Signal out]
PROP[One-way propagation<br/>Signal in, Signal out]
SUM[Scene composition<br/>place, delay, scale, sum per receiver]
SEAM[The receiver seam<br/>each receiver's summed baseband<br/>is wrapped as a ComplexCapture]
RADAR[Radar response backend<br/>emits component ComplexCaptures directly]
CAP[Shared receiver chain, capture plane<br/>local-oscillator error, mixer, IF filter,<br/>resampler, clock offset, low-noise-amplifier noise]
BG[Joint receiver background<br/>injected once across all receivers]
HW[Shared receiver chain, hardware plane<br/>ADC, phase noise, IQ imbalance, gain control]
COMP[Composition and labelling<br/>one LabeledScene]
PROJ[Projections<br/>typed observation fields]
REC[(One signal_dataset.Record)]
EM --> TX --> PROP --> SUM --> SEAM --> CAP --> BG --> HW --> COMP --> PROJ --> REC
RADAR --> CAP
Read left to right down the page, the containers hand off three times: at the receiver seam, at composition, and at the store.
Signal carries the transmit side. Every emitter’s generate returns a
Signal: IQ samples plus the metadata that
travels with them (sample rate, bandwidth, class name, realized carrier, start
sample, duration, signal-to-noise ratio). Transmit hardware stages and
propagation backends take a Signal and return a Signal. Scene composition
places each emitted event on the scene clock, applies the per-receiver channel,
and sums everything that arrives at each receiver.
The handoff happens at the receiver seam. The moment a receiver’s summed
baseband is ready, the composer wraps it as a ComplexCapture and the Signal
is done. Everything downstream of that point, in both domains, is capture work.
ComplexCapture carries the receive side. It is the in-flight container
inside the pipeline: complex samples, named axes with one of them declared as
the time axis, the sample rate, receiver and hardware identity, the alignment
block (time origin, carrier, local-oscillator frequency, phase reference), the
measurement plane,
and an append-only transformation_log recording which receiver stages ran.
The shared receiver chain runs in two segments with the joint receiver
background injected between them, because the background is the receiver’s noise
floor and has to be present before the gain control and the analog-to-digital
converter, which are the two stages whose whole job is to react to the total
level.
LabeledScene is what leaves composition. It carries the IQ payload, scene
metadata, per-emitter metadata, and a LabelSet. A projection turns it into the
typed observation fields the store writes, namespaced under that projection’s
id; the record on disk is a signal_dataset.Record holding those fields.
Why the radar branch has no Signal at all¶
Active radar never produces one. A radar response backend returns component captures directly, already at the radar measurement plane (a dechirped beat signal, not a communications baseband), and they enter the same shared receiver chain that the communications captures enter. See Cross-Domain Architecture for why the two planes coexist in one record and are never added together.
This is the point of the convergence. Before it, the communications path ran the
receiver stages through adapters that dressed a capture up as a Signal and back
again, and the radar path ran the same stage objects natively on captures. Those
adapters are gone. One container, one chain, both domains.
What Signal retiring at the receiver seam does and does not mean¶
Signal is not deleted and is not deprecated. It is scoped: it is now the
transmit-side and propagation container, and nothing on the receive side
accepts or returns one.
Concretely, Signal is still what you work with when you:
write an emitter, because
BaseEmitter.generatereturns aSignal;write a transmit hardware stage or a propagation backend, because
BaseChannel.applytakes aSignaland returns aSignal;read component signals out of scene composition, because the per-emitter components used for labels and signal-to-noise bookkeeping are
Signalobjects.
What no longer exists is a Signal anywhere past the receiver seam. If you are
writing a receiver stage, you write against
the stage core and the frontend chain:
your stage transforms interleaved IQ and knows nothing about any container, and
the chain owns the ComplexCapture around it.
Why the stored record did not move¶
The stored signal_dataset.Record is the convergence point, and it is a frozen
contract: both domains project into it, and the whole receiver unification was
carried out under the constraint that the stored bytes do not change. That
constraint was measured rather than asserted. Nine pinned configurations
covering both domains produce
byte-identical stored payloads before and after the convergence, and the two
things that did move on the communications path (the realized receiver metadata,
which is now projected from the capture, and the capture alignment, which exists
on that path for the first time) are metadata fields, not samples.
So the practical answer for a consumer is that this page describes an internal reorganization. The record layout in Records, Receivers, and Assets and the schema in Core Types are what they were.
See also¶
Core Types for the
SignalandLabeledScenefield lists.rfgen.receiver.stagesandrfgen.receiver.frontendfor theComplexCapturecontract, its compatibility preflights, and the ten receiver stages.Cross-Domain Architecture for measurement planes and why two captures can coexist in one record without being summable.
RX capture and RX hardware for the physics of the two receiver planes.
Records, Receivers, and Assets for the stored multi-receiver record layout.