Core Types

Core types are the stable data contracts between layers. Emitters, channels, scenes, labels, annotations, and storage should exchange these objects rather than raw tensors or layer-specific dictionaries.

What It Does

The core type layer provides:

  • Signal: in-phase/quadrature (IQ) sample stream plus signal metadata, used while generation is still in memory. IQ is the complex-valued RF waveform representation that the framework passes between layers.

  • SignalMetadata: ground truth for one signal, including one accepted placed event in a composed scene.

  • SceneMetadata: scene-level ground truth for records.

  • LabeledScene: one composed scene plus the labels derived from it, carried from composition to projection.

  • StoredRecord: the same value with IQ stripped, used by read-only audit and annotation paths.

  • BBox: time-frequency target used by detectors and label exporters.

  • Emitters return clean baseband Signal objects: the emitter’s complex IQ centered at 0 Hz before channel propagation, receiver capture, or receiver hardware transformations.

  • Channels transform Signal -> Signal.

  • Scene composition returns receiver IQ plus the component Signal objects for accepted placed events.

  • Labels read the scene-level Signal and its component_signals[].

The key invariant is that IQ and metadata always move together during generation. A layer should not pass a naked tensor and rely on side channels to recover sample rate, bandwidth, class name, device identity, frequency offset, or placement. This invariant scopes to the in-memory generation pipeline; the LabeledScene (below) flattens the structure before a projection encodes it. Signal is the transmit-side and propagation container: past the receiver seam the in-memory container is ComplexCapture instead, in both domains. See Signal, Capture, Record for the handoff.

Component Signals

Each entry in component_signals[] is itself a Signal; the type is recursive. The “Component Signals” name refers to this list of nested Signal objects, not to a separate class. Multi-emitter scenes use component_signals[]:

scene_signal: Signal
├── iq                          # summed scene IQ
├── metadata                    # SceneMetadata
└── component_signals[]
    ├── event 0: Signal         # metadata in scene reference frame
    ├── event 1: Signal
    └── ...

The scene-level iq is the receiver waveform after each emitter’s contribution is routed to each receiver, emitter contributions are summed per receiver, and the post-sum receiver frontend runs, first its capture plane (ReceiverStagePlane.CAPTURE) and then its hardware plane (ReceiverStagePlane.HARDWARE). Those receiver-side planes cover mixing to the receiver frame, intermediate-frequency (IF) filtering, resampling, low-noise amplifier (LNA) noise, analog-to-digital converter (ADC) quantization, receiver (RX) phase noise, RX IQ imbalance, and automatic gain control (AGC). Component IQ may exist as in-memory composition state, but each component Signal’s metadata is the stable contract that labels and storage depend on.

Each component Signal carries metadata after scene placement and channel routing. Placement realizes concrete time, frequency, and power fields from the scene plan: scene-relative start_sample, absolute realized_carrier_hz, bandwidth, realized signal-to-noise ratio (SNR) or power fields, device identity, receiver association when applicable, and any backend-specific channel state needed downstream. “Realized” means the sampled value after scene planning, not the unresolved distribution in config.

component_signals[] is cardinal by accepted placement, not by planned emitter slot: an allowed-empty time strategy can leave a selected slot with no component, while a strategy that returns several valid starts yields one component per accepted event. The event-fit rule for those starts is defined in Scene Capture Boundaries; their scene-rate and per-RX coordinate meanings are defined in Coordinate Systems.

Note: “component Signal objects” are not LabeledScene instances. LabeledScene is the composed-and-labeled scene defined below; components are in-memory Signal objects produced during scene composition. Per-component IQ is not part of the storage contract (see the LabeledScene section).

This follows TorchSig’s nested-signal pattern where practical. TorchSig is an RF machine-learning dataset and signal-generation library; matching its nesting model lets RF machine-learning (RFML) tooling consume generated data with minimal conversion. See TorchSig Interop for the conversion contract.

LabeledScene

LabeledScene is the value that leaves composition, not an in-memory Signal. It carries what a projection needs in order to encode a record:

  • receiver IQ,

  • scene metadata,

  • per-event metadata,

  • a LabelSet: bounding boxes (bboxes) and an optional segmentation raster,

  • references to external assets, through scene metadata, when the scene depends on large geometry/material blobs.

LabeledScene.iq is the composed receiver IQ. LabeledScene.emitters is tuple[SignalMetadata, ...], not tuple[Signal, ...]; per-event IQs are not part of the storage contract. The pairing of receiver IQ with scene metadata happens through LabeledScene.iq plus LabeledScene.scene (a SceneMetadata); per-emitter IQs are intentionally not stored.

LabeledScene is not itself the on-disk shape. A projection turns it into typed observation fields namespaced under that projection’s id, and the store writes them as one signal_dataset.Record. Read-only audit and annotation paths take StoredRecord instead, built with StoredRecord.from_labeled_scene(scene): the same scene, emitters, and boxes with the IQ payload dropped. See Signal, Capture, Record.

Labels are derived while in-memory component Signal objects are still available to the labeler. Bounding boxes come from per-event metadata such as start_sample, duration_samples, realized_carrier_hz, and bandwidth_hz; segmentation masks are rasterized onto the stored receiver IQ grid before storage. The record then stores the derived labels plus per-event metadata, not the component IQ. See Reference / API / Core Types for the full field surface, Label Schema for box and mask semantics, and Records, Receivers, and Assets for why per-emitter IQ is not persisted.

In particular, a component’s start_sample and duration_samples preserve the scene/channel-rate placement, whereas a BBox and its segmentation mask use the active receiver-record sample rate. The labeler maps the same half-open event interval outward onto that receiver grid; it does not overwrite component provenance or crop an invalid interval. Coordinate Systems § Component-to-record time mapping is the single detailed conversion contract.

Scenes and records stand one to one. A multi-RX scene still publishes one record, carrying each receiver as its own named subtree. See Records, Receivers, and Assets.

Metadata Rules

  • Metadata is schema-versioned. Adding fields is non-breaking; removing or changing field meaning requires a schema bump.

  • Canonical fields stay small and stable.

  • Backend-specific fields live in extras.

  • Values that select a framework-owned closed choice should be enum members in Python and enum string values in YAML. See Reference / API / Enums.

See Also