rfgen.waveforms

The domain-neutral half of the emitter catalog: the waveform-generator contract, the pool factory, and the modulation families that are defined by signal structure rather than by a standard. Protocol recipes – cellular, LoRa, ADS-B, the gr-OOT links and the drone links – live under rfgen.domains, and the selector inventory below covers both. Every concrete class subclasses BaseEmitter and produces a baseband SignalMetadata-tagged IQ tensor. TorchSig-backed emitters are optional adapters under rfgen.integrations.torchsig.emitters; they are selected explicitly in EmitterFamilyConfig.selector, rather than imported by the core package.

Scientific validation

The emitter backends have been scientifically validated against published references. See the per-backend reports:

Each report covers construct validity, mathematical correctness against cited equations, empirical comparison to published reference numbers, literature grounding, experimental methodology, operating envelope, and documented limitations.

Module summary

import torch
from rfgen.domains.radar.chirp_emitter import ChirpRadarEmitter, ChirpRadarParams

emitter = ChirpRadarEmitter()  # always available (scipy is a runtime dep)
signal = emitter.generate(
    class_label="lfm_chirp",
    sample_rate=10_000_000,
    duration_s=0.001,
    f_offset_hz=0.0,
    rng=torch.Generator().manual_seed(0),
    # `bandwidth_hz` defaults to 10 MHz, which is this call's whole sample
    # rate; the emitter requires it strictly below Nyquist, so the default
    # cannot be used at this rate and the parameters are passed explicitly.
    params=ChirpRadarParams(bandwidth_hz=2_000_000),
)

Every emitter has the same surface (the generate keyword-only signature, the family and supported_classes ClassVars, schema() returning a Pydantic model). What differs is the synthesis math, the parameter schema, and the dependency tier.

Choosing a record length for shaped waveforms

When generating a shaped digital waveform, request at least 64 IQ samples so the record contains more than filter startup and the reported occupied-bandwidth interval is useful. A one-sample record intentionally reports no interval; records from 2 through 63 samples still return the standard numerical estimate, but it is too low-resolution for waveform or occupancy analysis. Their metadata therefore retains that numerical occupied-bandwidth estimate and additionally sets occupied_interval_analysis_status="short_record" and occupied_interval_minimum_recommended_samples=64.

For the target-constellation, APSK, and generic OQPSK emitters, choose bandwidth_hz >= sample_rate_hz / max(N, 64), where N is the rounded requested record length. This keeps the resampling ratio bounded while preserving the explicit one-sample occupancy outcome. These are generation-envelope requirements, not claims about a transmitter or protocol rate.

Discovering the rfgen.emitters entry-point group does NOT force-import Sionna, srsRAN, RadarSimPy, gr-lora_sdr, or any other optional dependency; missing-extra emitters raise BackendUnavailableError only when the class is actually instantiated. (rfgen.emitters is an entry-point group name, not an importable module.)

Class index

Class

Family

supported_classes

Backend / extra

Notes

BaseEmitter

abstract

abstract

abc

ABC; subclass to add a custom emitter

AdsBEmitter

adsb

("adsb_extended_squitter",)

pyModeS; rfgen[adsb]

1090 MHz Mode-S; stub (generate() raises)

AnalogFpvVideoEmitter

drone

("analog_fpv_video",)

pure NumPy/torch (always available)

Analog FM FPV video envelope; fully implemented

BLEEmitter

iot

("ble_adv",)

GNU Radio OOT; rfgen[ble]

BLE; stub

ChirpRadarEmitter

radar

("lfm_chirp",)

scipy.signal.chirp (always available)

LFM chirp; fully implemented

ConformantOFDMEmitter

comms

12 "nr-grid-<N>rb" resource-block-count labels

sionna.phy.ofdm; rfgen[sionna]

Standards-shaped OFDM; fully implemented, explicitly uncoded (real resource grid and pilot pattern, no transport-block encoding)

DroneIdEmitter

drone

("droneid",)

pure NumPy/torch (always available)

DJI DroneID broadcast frame; fully implemented, clean-room from published specs

FECConstellationEmitter

comms

("ldpc", "polar", "turbo", "convolutional")

sionna.phy.fec; rfgen[sionna], rfgen[torchsig]

FEC-coded QAM; fully implemented (real Sionna encoder per family, TorchSig pulse shaping)

FhssRcLinkEmitter

drone

("fhss_rc_link",)

pure NumPy/torch (always available)

Frequency-hopping RC control link; fully implemented, register-verified presets

LTEEmitter

cellular

("lte_pusch", "lte_pdsch")

srsRAN over ZMQ; rfgen[lte]

LTE; stub

LoRaPHYEmitter

iot

("lora",)

loraphy pure-Python; rfgen[lora-phy]

LoRa fallback backend; stub

LoRaSdrEmitter

iot

("lora",)

gr-lora_sdr GNU Radio OOT; rfgen[lora-sdr]

Default LoRa backend; stub

NRPuschEmitter

cellular

("nr_pusch",)

sionna.phy.nr; rfgen[sionna]

5G NR PUSCH; fully implemented (delegates to sionna.phy.nr.PUSCHTransmitter)

OcuSyncSurrogateEmitter

drone

("ocusync_surrogate",)

pure NumPy/torch (always available)

Labeled non-protocol-faithful OcuSync detection surrogate; fully implemented

PlaybackSigMFEmitter

comms

("capture_replay",)

SigMF reference reader; rfgen[sigmf]

Gated raw CF32/CF64 capture replay. It is not an accepted-current generator or validation source until a supplied local consumer/schema fixture qualifies the adapter.

RemoteIdEmitter

drone

("remote_id",)

pure NumPy/torch (always available)

ASTM F3411 Remote ID over BLE advertising; fully implemented, BLE-transport only

TorchSig*Emitter

comms

TorchSig signal builders

rfgen[torchsig]

Optional adapters; import from rfgen.integrations.torchsig.emitters

WifiEmitter

comms

("wifi_11g", "wifi_11n")

GNU Radio OOT; rfgen[wifi]

802.11g/n; stub

ZigbeeEmitter

iot

("zigbee_oqpsk",)

GNU Radio OOT; rfgen[zigbee]

802.15.4 OQPSK; stub

Each emitter ships a matching *Params Pydantic v2 model (e.g. ChirpRadarParams, LoRaParams, LTEParams) returned from schema(). Import it from the emitter’s own module — rfgen.domains.radar.chirp_emitter, rfgen.domains.comms.lora, and so on, as each class section below states. There is no rfgen.emitters module to import from.

Built-in selector inventory

This is the complete current set of 26 built-in rfgen.emitters entry-point selectors, discovered through importlib.metadata.entry_points, and their exact supported_classes strings. It is the reference for checking whether an exact native class exists; a listed stub declares a class contract but raises its documented backend or implementation error when used.

Selector

Emitter

Exact supported_classes

torchsig_comms

TorchSigCommsEmitter

bpsk, qpsk, 8psk, 16psk, 16qam, 32qam, 64qam, 256qam, ook, 4ask, 8ask, 64psk, 1024qam, 16ask, 32ask, 64ask

torchsig_fsk

TorchSigFSKEmitter

2fsk, 2gfsk, 2msk, 2gmsk, 4fsk, 4gfsk, 4msk, 4gmsk, 8fsk, 8gfsk, 8msk, 8gmsk, 16fsk, 16gfsk, 16msk, 16gmsk

torchsig_ofdm

TorchSigOFDMEmitter

ofdm-64, ofdm-72, ofdm-128, ofdm-180, ofdm-256, ofdm-300, ofdm-512, ofdm-600, ofdm-900, ofdm-1024, ofdm-1200, ofdm-2048

torchsig_am

TorchSigAMEmitter

am-dsb-fc, am-dsb-sc, am-ssb-usb, am-ssb-lsb

torchsig_fm

TorchSigFMEmitter

nbfm, wbfm

torchsig_chirp

TorchSigChirpEmitter

chirpss, lfm_data, lfm_radar

torchsig_tone

TorchSigToneEmitter

cw

torchsig_target_constellations

TorchSigTargetConstellationEmitter

32psk, 128qam_cross, 32qam_cross, 512qam_cross

apsk

APSKEmitter

16apsk, 32apsk, 64apsk, 128apsk

oqpsk

OQPSKEmitter

oqpsk

adsb

AdsBEmitter

adsb_extended_squitter

ble

BLEEmitter

ble_adv

wifi

WifiEmitter

wifi_11g, wifi_11n

zigbee

ZigbeeEmitter

zigbee_oqpsk

lte

LTEEmitter

lte_pusch, lte_pdsch

nr_pusch

NRPuschEmitter

nr_pusch

nr_ofdm_conformant

ConformantOFDMEmitter

nr-grid-1rb, nr-grid-2rb, nr-grid-3rb, nr-grid-4rb, nr-grid-5rb, nr-grid-6rb, nr-grid-8rb, nr-grid-10rb, nr-grid-12rb, nr-grid-16rb, nr-grid-20rb, nr-grid-25rb

fec_constellation

FECConstellationEmitter

ldpc, polar, turbo, convolutional

lora_sdr

LoRaSdrEmitter

lora

lora_phy

LoRaPHYEmitter

lora

chirp_radar

ChirpRadarEmitter

lfm_chirp

droneid

DroneIdEmitter

droneid

fhss_rc_link

FhssRcLinkEmitter

fhss_rc_link

analog_fpv_video

AnalogFpvVideoEmitter

analog_fpv_video

remote_id

RemoteIdEmitter

remote_id

ocusync_surrogate

OcuSyncSurrogateEmitter

ocusync_surrogate

zigbee_oqpsk is a protocol-specific IEEE 802.15.4 Zigbee waveform class. It does not provide the standalone, protocol-neutral exact class oqpsk. The generic oqpsk source is instead a bounded augmentation primitive.

For the conceptual map of supported families and cataloged availability boundaries (GSM, captured playback, NR downlink, and extended radar waveforms), see Signal Catalog. For synthesis algorithms, see Reference / Algorithms.

Waveform sources for bounded augmentation

Use these optional sources from the Emitter concept when an augmentation experiment needs a specific clean-baseband geometry or modulation primitive. They feed the ordinary emitter-to-scene-to-channel pipeline; they do not define a corpus projection and they do not replay a capture.

All four implement the keyword-only BaseEmitter.generate() signature:

generate(*, class_label, sample_rate, duration_s, f_offset_hz, rng, device_id=None, params=None) -> Signal.

sample_rate is finite and positive in Hz, duration_s must round to at least one sample, and f_offset_hz must be finite and fit the source’s complex baseband budget. The output is float32 IQ shaped (2, round(sample_rate * duration_s)), with clean snr_db=+inf and its measured 99%-power occupied interval in metadata. The requested bandwidth_hz is not reported as the measured interval. Invalid labels, coordinates, record lengths, parameter models, or resampling requests raise EmitterError; backend construction fails with an EmitterError whose context identifies the missing torchsig extra. Install rfgen[torchsig] before instantiating any source in this section.

All shaped sources accept short records, but at least 64 samples is the practical minimum for waveform or occupancy analysis. A one-sample record has no occupied interval (occupied_interval_reason="insufficient_samples"), and records of 2–63 samples carry occupied_interval_analysis_status="short_record". For the target-constellation, APSK, and OQPSK sources, use bandwidth_hz >= sample_rate / max(N, 64), where N is the rounded output length.

TorchSigTargetConstellationEmitter

Import the canonical optional adapter with from rfgen.integrations.torchsig.emitters.torchsig_target_constellations import TorchSigTargetConstellationEmitter, TorchSigTargetConstellationParams. rfgen.integrations.torchsig.emitters.torchsig_target_constellations is a convenience facade for the same two names. The source accepts only 32psk and 128qam_cross; those are TorchSig geometry identifiers.

TorchSigTargetConstellationParams is the TorchSigCommsParams model: bandwidth_hz: float = 200000.0 (> 0), pulse_shape_name: str = "srrc", and alpha_rolloff: float = 0.35 (strictly between 0 and 1). It delegates constellation synthesis, pulse shaping, and resampling to TorchSig, preserves the common output contract above, and uses the supplied torch.Generator for repeatable generation. It establishes only the listed finite geometries; it does not establish a protocol bit order, pulse shape, physical sample rate, or corpus equivalence.

APSKEmitter

Import with from rfgen.waveforms.apsk import APSKEmitter, APSKParams. Its accepted labels are 16apsk, 32apsk, 64apsk, and 128apsk. APSKParams has bandwidth_hz: float = 200000.0 (> 0) and alpha_rolloff: float = 0.35 (strictly between 0 and 1); extra fields are rejected. The emitter uses fixed selected DVB-S2/S2X ring geometry, then TorchSig pulse shaping and resampling. Metadata includes target_geometry="ETSI_DVB_S2_S2X" in addition to the common fields. It is not a DVB FECFRAME generator, complete MODCOD configuration, or receiver test vector.

OQPSKEmitter

Import with from rfgen.waveforms.oqpsk import OQPSKEmitter, OQPSKParams. It accepts only the lowercase API label oqpsk. OQPSKParams has bandwidth_hz: float = 200000.0 (> 0) and rejects extra fields. The emitter generates generic half-sine-shaped OQPSK, delays Q by half of its four-sample native symbol, normalizes output power, then resamples with TorchSig. Its metadata records the native samples per symbol, Q delay, half-sine pulse shape, and power-normalization scale. This is a modulation primitive, not IEEE 802.15.4 packet or DSSS synthesis.

ConformantOFDMEmitter

Import with from rfgen.waveforms.ofdm_conformant import ConformantOFDMEmitter, ConformantOFDMParams. Builds a genuine OFDM resource grid via sionna.phy.ofdm.ResourceGrid (14 OFDM symbols, 15 kHz subcarrier spacing, 3GPP numerology 0), inserts a real orthogonal pilot pattern through sionna.phy.ofdm.KroneckerPilotPattern, and maps uncoded bits to QPSK via sionna.phy.mapping.Mapper. It is distinct from TorchSigOFDMEmitter’s waveform-envelope OFDM (no pilots, no resource grid, no guard carriers): its "nr-grid-<N>rb" class labels have zero string overlap with TorchSigOFDMEmitter’s "ofdm-<subcarrier_count>" labels.

Conformance is scoped, not full. This emitter is standards-shaped, not fully 3GPP-conformant: the resource grid and pilot pattern sit at correct 3GPP numerology, but data is explicitly uncoded (no transport-block encoding, scrambling, or rate matching), and its full-band Kronecker pilot pattern is standards-plausible rather than a specific named 3GPP DMRS comb pattern. See the Phase-2 physics validation for the evidence. signal.metadata.extras carries no code-rate or transport-block-size key, confirming the uncoded claim structurally, not only in prose.

class_label selects a resource-block allocation, encoded as "nr-grid-<N>rb" for N in {1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 25} (one resource block is 12 subcarriers, 3GPP TS38211 section 4.4.4.1).

ConformantOFDMParams:

Field

Type

Default

Constraint

fft_size

int

512

64 <= fft_size <= 4096; must be at least the class_label’s resource-block allocation (n_rb * 12)

cyclic_prefix_length

int

36

0 <= cyclic_prefix_length <= 4096; must not exceed fft_size

pilot_spacing_symbols

int

7

1 <= pilot_spacing_symbols <= 14; pilot OFDM symbols are range(0, 14, pilot_spacing_symbols)

occupied_bandwidth_hz

float | None

None

When set, > 0; overrides the class_label’s resource-block allocation (see resource_blocks_for_bandwidth below)

signal.metadata.extras records ofdm_conformant_n_rb, ofdm_conformant_fft_size, ofdm_conformant_cyclic_prefix_length, ofdm_conformant_pilot_spacing_symbols, ofdm_conformant_num_slots_generated, and ofdm_conformant_class_label_n_rb. Sionna construction failures (an invalid fft_size/cyclic_prefix_length/pilot_spacing_symbols combination) raise EmitterError.

FECConstellationEmitter

Import with from rfgen.waveforms.fec_constellation import FECConstellationEmitter, FECConstellationParams, FECFamily. A single-carrier, pulse-shaped, FEC-coded bit stream: no resource grid or pilots (unlike ConformantOFDMEmitter). FECFamily names the four supported code families, all genuinely encoded by a real Sionna encoder: ldpc (sionna.phy.fec.ldpc.LDPC5GEncoder), polar (sionna.phy.fec.polar.Polar5GEncoder), turbo (sionna.phy.fec.turbo.TurboEncoder), and convolutional (sionna.phy.fec.conv.ConvEncoder). Coded bits are mapped to QAM with sionna.phy.mapping.Mapper, then pulse-shaped and resampled with the same TorchSig SRRC taps and polyphase resampler APSKEmitter uses (rfgen[torchsig] required in addition to rfgen[sionna]). See the Phase-2 physics validation for a real encode-map-AWGN-demap-decode round trip confirming genuine bit recovery for all four families.

FECConstellationParams:

Field

Type

Default

Constraint

fec_family

FECFamily | None

None

When set, must equal class_label; when None, the effective family is derived from class_label

constellation_order

int

4

2 <= constellation_order <= 8, even (bits per QAM symbol: QPSK=2, 16-QAM=4, 64-QAM=6, 256-QAM=8)

code_rate

float

0.5

0.0 < code_rate < 1.0; per-family achievable range differs (LDPC needs >= 1/5; Turbo/convolutional need a value close to exactly 1/3 or 1/2)

bandwidth_hz

float

200000.0

> 0; TorchSig resampler target (symbol-rate budget), the same role as APSKParams.bandwidth_hz

alpha_rolloff

float

0.35

0.0 < alpha_rolloff < 1.0; SRRC pulse-shaping rolloff

A requested code_rate below a family’s real achievable floor is silently floor-clamped, never rejected: generate() reports the ACHIEVED rate (computed from the real constructed encoder’s k / n) under extras["fec_constellation_code_rate"], keeping the caller’s original request separately under extras["fec_constellation_requested_code_rate"] for traceability. extras also carries fec_constellation_family, fec_constellation_order, fec_constellation_k, fec_constellation_n, fec_constellation_num_blocks_generated, and (for Turbo/convolutional) fec_constellation_design_rate.

Amendment-4 bandwidth sizing: resource_blocks_for_bandwidth

NRPuschParams.occupied_bandwidth_hz and ConformantOFDMParams.occupied_bandwidth_hz are both implemented on top of one shared, pure-arithmetic helper in rfgen.waveforms._resource_grid (no backend calls; 3GPP TS38211 section 4.4.4.1’s “one resource block is 12 subcarriers” is the only fact it encodes):

def resource_blocks_for_bandwidth(
    occupied_bandwidth_hz: float,
    *,
    subcarrier_spacing_hz: float,
    n_rb_min: int,
    n_rb_max: int,
) -> int: ...

Rounds occupied_bandwidth_hz / (12 * subcarrier_spacing_hz) to the nearest whole resource-block count, clamped to [n_rb_min, n_rb_max]. Raises ValueError if occupied_bandwidth_hz is non-positive, non-finite, or outside [n_rb_min, n_rb_max] * 12 * subcarrier_spacing_hz (each caller re-raises this as EmitterError). Each emitter exposes its own physical bounds through a matching helper: nr_pusch_occupied_bandwidth_bounds_hz(subcarrier_spacing_hz) and conformant_ofdm_occupied_bandwidth_bounds_hz(fft_size=4096, *, cyclic_prefix_length=0), both returning (min_hz, max_hz). These are the same bounds the signal-atlas-comms-v1 use case’s own sampler-level bandwidth constraint reads to exclude a below-floor bandwidth-ladder rung rather than let an emitter silently realize an oversized channel; see Amendment-4 bandwidth-to-resource-block mapping is physically sound for the full evidence.


class rfgen.waveforms.base.BaseEmitter

Produces IQ for one emitter on a shared time/frequency grid. Stateless across calls; all randomness comes from the supplied rng.

from abc import ABC, abstractmethod
from typing import ClassVar
import torch
from pydantic import BaseModel

class BaseEmitter(ABC):
    """One emitter family. Implementations are stateless; call producing IQ."""

    family: ClassVar[EmitterFamily]
    supported_classes: ClassVar[tuple[str, ...]]

    @abstractmethod
    def generate(
        self,
        *,
        class_label: str,
        sample_rate: float,
        duration_s: float,
        f_offset_hz: float,
        rng: torch.Generator,
        device_id: str | None = None,
        params: BaseModel | None = None,
    ) -> Signal: ...

    @abstractmethod
    def schema(self) -> type[BaseModel]: ...

Contract

  • Output IQ shape MUST be (2, int(round(sample_rate * duration_s))).

  • Output IQ MUST be at baseband. A direct caller may supply f_offset_hz for an emitter-specific native/provisional offset, subject to that emitter’s contract. The scene composer instead invokes every emitter with f_offset_hz=0.0, applies its own scene-relative placement shift after TX impairments and resampling, and stamps the final absolute carrier on the placed component. Intrinsic sweeps such as LoRa CSS and FMCW retain their slope under that whole-waveform placement shift.

  • Returned Signal.metadata MUST be self-consistent: start_sample within [0, int(sample_rate * duration_s)), frequency-extent fields within [-sample_rate/2, +sample_rate/2].

  • Implementations MUST consume randomness only from rng for determinism under shard re-runs.

  • params is a backend-specific Pydantic model (e.g. ChirpRadarParams); callers validate it against the emitter’s schema() before calling.

  • device_id, when set, is used by the fingerprint module to pick a deterministic CFO/IQ-imb/phase-noise profile per virtual device.

Class attributes

Attribute

Type

Description

family

ClassVar[EmitterFamily]

Top-level family tag, copied into every emitted SignalMetadata.family

supported_classes

ClassVar[tuple[str, ...]]

Whitelist of class_label values this emitter accepts; _check_class_label() raises EmitterError if a call uses an unknown label

BaseEmitter.__init_subclass__ validates concrete subclasses at import time: they must declare an EmitterFamily family and a non-empty tuple of non-empty string supported_classes. The emitter-pool factory additionally verifies that the configured selector resolves to a class, belongs to the configured family, and supports every requested class label before generation starts.

Method: generate

Abstract method on BaseEmitter. Produces one emitter’s IQ plus its ground-truth metadata. See the full normative contract above.

Method: schema

Abstract method on BaseEmitter. Returns the Pydantic v2 model describing valid params for this emitter, used by the config validator and programmatic consumers.

Why stateless?

Local runs may instantiate emitters repeatedly; statelessness avoids reset coupling. State that should persist across emitters (per-device fingerprint parameters) lives in rfgen.calibration.fingerprint.DeviceRegistry, keyed by device_id.

Why does BaseEmitter.generate() still accept f_offset_hz?

The direct-emitter API supports native/provisional offsets where an emitter’s own contract needs one. That does not make the emitter the owner of final scene placement: the shipped scene composer calls it with zero and performs the scene-relative whole-waveform shift itself. Intrinsic sweeps retain their modulation slope under this shift; the composer then records the final absolute carrier on the placed component.


Class-reference anchors

The catalog rows above each follow the same template as ChirpRadarEmitter (the fully implemented reference example). Emitter availability and any backend limitations are documented in each class section; TorchSig adapters remain optional and are selected explicitly.

The anchor labels below give every shipped class a stable cross-reference target so concept pages, glossary entries, and how-to guides can use {ref} links per the STYLE.md code-span linking rule.

Per-protocol algorithm references:

Compatibility anchors

The anchors below preserve inbound links for names that are not exported classes. The class index states each supported replacement or availability boundary.

Waveform evidence primitive

rfgen.evidence.waveform.estimate_occupied_bandwidth is the shared measurement used by raw capture replay and later waveform owners. It accepts complex (N,) samples or float IQ shaped (2, N) and returns the narrowest 99%-power interval measured with scipy.signal.welch: Hann window, nperseg=min(4096,N), 50% overlap, constant detrending, density scaling, and two-sided frequencies. The returned OccupiedBandwidth contains low_hz, high_hz, method, and a one-bin confidence_tolerance_hz; for N < 2 it returns None, which capture replay records as occupied_interval_reason="insufficient_samples". The companion measure_occupied_bandwidth exposes this as an explicit OccupiedBandwidthMeasurement, so any waveform owner can emit either the interval or a null/reason pair. Its defined null reasons are insufficient_samples, zero_power, dc_removed_by_policy (constant detrending removed a nonzero DC-only record), numerical_overflow (the finite-input PSD calculation overflowed), and crosses_nyquist. seed_1337_waveform_fixture is the pinned (2, 4096) float32 fixture; the contract test verifies its C-order IQ-byte SHA-256 before validating estimator behavior.

rfgen.evidence.waveform API

def estimate_occupied_bandwidth(
    iq: torch.Tensor | np.ndarray, *, sample_rate_hz: float
) -> OccupiedBandwidth | None: ...

def measure_occupied_bandwidth(
    iq: torch.Tensor | np.ndarray, *, sample_rate_hz: float
) -> OccupiedBandwidthMeasurement: ...

Both functions accept complex samples shaped (N,) or real I/Q samples shaped (2, N). sample_rate_hz is a finite positive value in Hz. The convenience function returns an interval or None; use measure_occupied_bandwidth when the caller must retain why no linear interval is representable.

Type or function

Primary behavior

OccupiedBandwidth(low_hz, high_hz, method, confidence_tolerance_hz)

Immutable measured interval in Hz relative to baseband; as_dict() returns stable metadata fields.

OccupiedBandwidthMeasurement(interval, reason)

Immutable interval-or-reason result. Exactly one field is set; as_metadata() returns occupied_interval and, for null results, occupied_interval_reason.

estimate_occupied_bandwidth

Returns the interval only, or None for a null result.

measure_occupied_bandwidth

Returns the complete outcome and one of insufficient_samples, zero_power, dc_removed_by_policy, numerical_overflow, or crosses_nyquist when null.

seed_1337_waveform_fixture()

Returns the pinned (2, 4096) float32 fixture used to test I/Q layout and estimator reproducibility.

waveform_sha256(iq)

Returns the SHA-256 of C-order float32 (2, N) storage; accepts the same I/Q forms and raises ValueError for another shape.

estimate_occupied_bandwidth and measure_occupied_bandwidth raise ValueError for a non-positive/non-finite sample rate, non-numeric I/Q, or non-finite samples. They do not synthesize a signal or validate a waveform against a regulatory measurement procedure.

PlaybackSigMFEmitter API

class PlaybackSigMFParams(BaseModel):
    source_uri: str

class PlaybackSigMFEmitter(BaseEmitter):
    def __init__(self, *, source_uri: str | None = None) -> None: ...
    def schema(self) -> type[PlaybackSigMFParams]: ...
    def generate(
        self, *, class_label: str, sample_rate: float, duration_s: float,
        f_offset_hz: float, rng: torch.Generator, device_id: str | None = None,
        params: PlaybackSigMFParams | BaseModel | Mapping[str, object] | None = None,
    ) -> Signal: ...

def load_sigmf_capture(source_uri: str) -> SigMFCapture: ...

PlaybackSigMFEmitter replays a complete local cf32_le or cf64_le capture through the SigMF reference reader. The adapter is gated: do not use it as an accepted-current generation or validation source until a supplied local consumer/schema fixture qualifies it. Its source_uri may be supplied to the constructor or per call through the frozen PlaybackSigMFParams; per-call parameters take precedence.

generate requires class_label="capture_replay", a positive sample_rate that exactly matches the capture, and f_offset_hz=0.0. duration_s and rng exist for the shared emitter interface but do not alter raw replay; output length is the capture’s native sample count. The returned Signal carries float32 I/Q, capture provenance, and the shared occupied-bandwidth outcome. It raises BackendUnavailableError when rfgen[sigmf] is unavailable and EmitterError for a missing URI, invalid metadata, unsupported samples, non-stationary capture frequency, non-finite/out-of-range I/Q, or a requested transform.

load_sigmf_capture returns the immutable SigMFCapture record with I/Q, sample rate, capture frequency, native-byte SHA-256, source URI, licence, and collection authority. It has the same reader and metadata error behavior as the emitter, without constructing a Signal.

# This is a call-shape example, not a qualified capture-validation workflow.
params = PlaybackSigMFParams(source_uri="/captures/example.sigmf-meta")
emitter = PlaybackSigMFEmitter()
signal = emitter.generate(
    class_label="capture_replay",
    sample_rate=2_000_000.0,
    duration_s=0.0,
    f_offset_hz=0.0,
    rng=torch.Generator().manual_seed(0),
    params=params,
)

The URI in the example must name a local capture with the required metadata and sample rate. RFGen does not verify that its declared licence or collection authority permits the intended use; the caller or external package must assess those questions. Because this adapter is gated, the example does not qualify a capture for generation or validation.

The Signal Catalog distinguishes current registered selectors from runnable implementations.