Observation API

The rfgen.observation package defines the persistence-neutral aggregate record, projection plugin, coordinate, and Signal Dataset adapter contracts. Use these APIs when implementing a projection or consuming a unified scene observation. Use Generation for the emitter and composer contracts.

Module layout

Module

Responsibility

rfgen.observation.contracts

ABCs, immutable fields, coordinates, projection results, records, and compatibility predicates.

rfgen.observation.persistence

Strict SDS encoding/decoding, metadata view, and aggregate tensor budget.

rfgen.observation.runtime

Resolution, deterministic seeds, two-phase execution, and one-record assembly.

rfgen.scene.projections

Built-in communications and radar implementations.

Record contract

BaseRFRecord is the abstract persistence boundary. Implementations provide a stable record_type, a schema version, globally namespaced fields, and a complete RFRecordEnvelope.

SceneObservationRecord is RFGen’s aggregate implementation. It binds:

  • one immutable ScenePlan and its content-derived time reference;

  • one or more unique, canonically ordered ProjectionResult values;

  • projection-owned fields under projections/<projection_id>/<local_field_name>; and

  • invocation metadata used by observation identity.

Constructors reject an empty projection set, duplicate projection IDs, fields whose clock differs from the plan, and descriptors that disagree with the invocation. BaseRFRecord is not the external Signal Dataset class; SDSObservationAdapter performs that conversion centrally.

Unless a narrower exception is named below, invalid constructor values and failed coordinate, field, record, or compatibility predicates raise rfgen.core.errors.ValidationError.

Field and coordinate contracts

BaseObservationField owns an immutable NumPy payload, semantic axes, a MeasurementDescriptor, optional time coordinates, and JSON-safe metadata. Concrete field types are ComplexCapture, RealValuedCapture, LabelField, and RealValuedLabelField. A BaseCapture additionally requires a CaptureAlignmentDescriptor; a label carries none, because it is derived from declared metadata rather than measured.

Time coordinates implement BaseTimeCoordinates:

Type

Use

UniformTimeCoordinates

One uniformly sampled time axis.

PulsedTimeCoordinates

Pulse and fast-time axes with one interval per pulse.

SegmentedTimeCoordinates

A finite sequence of uniform segments.

ExplicitTimeCoordinates

Explicit offsets for a nonuniform schedule.

Non-time axes may use UniformAxisCoordinates or ExplicitAxisCoordinates. Axis cardinality, coordinate cardinality, sample support, and plan-clock membership are validated before persistence.

Projection plugin contract

Subclass BaseObservationProjection and provide these public members:

Member

Contract

name: ClassVar[str]

Portable namespaced selector; it must match the entry-point name.

contract_version: ClassVar[int]

Positive wire-contract version, matched exactly by configuration.

ParamsModel: ClassVar[type[BaseModel]]

Strict Pydantic model for a frozen validated parameter snapshot.

interaction_selectors: ClassVar[frozenset[str]]

Interactions the plugin can classify.

required_store_capabilities: ClassVar[frozenset[str]] = frozenset()

Capabilities added to aggregate store requirements.

params: BaseModel

Retained instance of the declared ParamsModel.

from_params(cls, params: BaseModel) -> BaseObservationProjection

Build only from the exact parameter model.

applicable_interactions(cls, plan: ScenePlan) -> frozenset[str]

Contribute plan-derived relevant interactions; default empty.

preflight(self, plan: ScenePlan, context: ProjectionContext) -> tuple[InteractionStatement, ...]

Classify every relevant interaction before expensive work.

project(self, plan: ScenePlan, context: ProjectionContext, interactions: tuple[InteractionStatement, ...]) -> ProjectionResult

Return one descriptor-bound result.

run(self, plan: ScenePlan, context: ProjectionContext) -> ProjectionResult

Sealed lifecycle; subclasses are rejected if they override it.

Do not override run. The base lifecycle validates context, capabilities, interaction coverage, preflight output, and the returned descriptor. The orchestrator preflights every configured projection before it renders any of them. An unsupported interaction fails the complete observation. Lifecycle contract violations and unsupported projection results raise rfgen.core.errors.ValidationError. Defining a subclass that overrides the sealed run method is rejected immediately by __init_subclass__ with TypeError.

Register an out-of-tree implementation in the distribution’s pyproject.toml:

[project.entry-points."rfgen.observation_projections"]
"example.passive_radar.receiver" = "example_rf.projection:PassiveRadarProjection"

Then configure that exact selector and contract version:

projections:
  - projection_id: passive_radar_rx
    selector: example.passive_radar.receiver
    contract_version: 1
    params: {}

Projection IDs are instance names, not plugin selectors. They must be unique and become field-namespace and seed-tree components. Configuration is an ordered list for explicit authorship, while runtime assembly canonicalizes by projection ID. Reordering the list therefore does not change projection streams or aggregate identity.

Persistence adapter

SDSObservationAdapter.encode(record) validates a BaseRFRecord and produces exactly one content-addressed signal_dataset.Record. It stores the strict rfgen.scene_observation.v1 envelope under metadata["rfgen"], including field ownership, projection descriptors, interaction coverage, the ScenePlan, and invocation metadata.

decode(record) reconstructs a typed SceneObservationRecord and rejects unknown or malformed envelope shapes. metadata_view(record_or_metadata) reads the common identity and field catalog without loading field tensors. ObservationEncodingPolicy.max_tensor_bytes bounds one complete aggregate record before allocation and again after encoding.

encode(observation: BaseRFRecord) -> signal_dataset.Record raises TypeError for a non-RFGen record, ValidationError for an invalid record/envelope/field, and RecordIntegrityError when estimated or encoded tensors exceed policy. decode(source: signal_dataset.Record) -> SceneObservationRecord raises ValidationError for unknown schema versions, malformed ownership, coordinates, identity, or content. metadata_view(source: Record | RecordMetadata) -> ObservationMetadataView raises TypeError for another input type and ValidationError for a malformed envelope; it does not load tensor values.

Signal Dataset reads full records. Consumers that only need catalog or identity data should use store metadata access or metadata_view; consumers that load a record must budget memory for all of its fields.

Complete public contract catalog

Records, fields, and descriptors

Object

Constructor and contract

BaseRFRecord

Abstract properties record_type, schema_version, fields; abstract envelope(). Custom records may be encoded only when the complete envelope and flattened fields satisfy RFGen’s scene-observation integrity schema.

RFRecordEnvelope

(observation_id, scene_id, scene_plan_reference, scene_plan, projections, metadata).

SceneObservationRecord

(scene_id, scene_plan_reference, scene_plan, projections, metadata={}); nonempty unique projections, canonical ID order, exact plan reference and clock membership.

BaseObservationField

(name, values, axes, descriptor, time_coordinates=None, metadata={}); abstract payload_kind and dtype validation. The SDS encoder fails closed for an unknown custom payload kind.

BaseCapture

Field ABC adding required alignment and optional acquisition_coordinates; element or acquisition time coordinates are mandatory.

ComplexCapture

Field/capture signature; complex64 only.

RealValuedCapture

Field/capture signature; float16, float32, or float64.

LabelField

Field signature; Boolean or signed/unsigned integer dtype.

RealValuedLabelField

Field signature; float16, float32, or float64, all finite. For a label whose values are continuous — a bounding box’s frequency edges — which LabelField cannot hold.

ObservationAxis

(name, length, role, coordinates=None); positive length, namespaced role, array and coordinate cardinalities must agree.

MeasurementDescriptor

(selector, contract_version, quantity, unit=None, addition_disposition=NON_ADDITIVE); selector and quantity are namespaced, version positive.

CaptureAlignmentDescriptor

Receiver, geometry, clock, carrier/LO, phase/oscillator, dechirp, calibration, noise, and state facts. Phase-bearing and noise-bearing captures must provide their references.

The inherited concrete-field constructor is (name, values, axes, descriptor, time_coordinates=None, metadata={}, alignment=None, acquisition_coordinates=None) for ComplexCapture and RealValuedCapture; the two label types end after metadata. Captures require a non-null alignment despite the dataclass inheritance default.

CaptureAlignmentDescriptor(receiver_id, receiver_geometry_id, clock_reference, phase_reference, oscillator_reference, carrier_hz, lo_hz, dechirp_reference=None, calibration_reference=None, phase_disposition=APPLICABLE, noise_disposition=NONE, noise_reference=None, state_policy=None) exposes all 13 persisted facts. IDs/references are nonblank when present, carrier/LO are finite, applicable phase requires phase and oscillator references, and non-NONE noise requires noise_reference.

AdditionDisposition is non_additive, coherent_amplitude, or additive_measurement; PhaseDisposition is applicable or not_applicable; NoiseDisposition is none, shared_component, or independent_realization. These are declared contracts, not inference from a field name.

Coordinates

Object

Signature and validation

BaseTimeCoordinates

(time_reference) ABC; index mapping, support intervals, origin, and metadata are abstract.

UniformTimeCoordinates

(time_reference, axis_name, count, origin_s, sample_rate_hz); positive count/rate and representable samples/support.

PulsedTimeCoordinates

(time_reference, pulse_axis_name, fast_time_axis_name, event_origin_s, pulse_repetition_intervals_s, fast_time_count, sample_rate_hz, event_start_offset_s=0); distinct axes, one positive PRI per pulse, representable pulse/fast-time support.

SegmentedTimeCoordinates

(time_reference, axis_name, origin_s, segments); nonempty ordered, nonoverlapping segments.

ExplicitTimeCoordinates

(time_reference, axis_name, origin_s, offsets_s, final_interval_s); increasing finite offsets and positive final support.

TimeSegment

(start_index, count, origin_offset_s, sample_rate_hz); nonnegative start, positive count/rate.

SceneTimeInstant

(reference_origin_s, offset_s); finite components retained separately; approximate_s is lossy.

BaseAxisCoordinates

(quantity, unit) ABC for finite non-time coordinates.

UniformAxisCoordinates

(quantity, unit, length, start, step); positive length, finite nonzero step.

ExplicitAxisCoordinates

(quantity, unit, values); nonempty finite tuple.

Projection invocation values

Object

Signature and validation

ProjectionDescriptor

(projection_id, selector, contract_version, implementation_identity, params_identity); portable IDs/selectors, positive version, nonblank identities. Runtime identity is embedded in implementation_identity, not stored as another descriptor key.

ProjectionContext

(descriptor, sample_id, sample_ordinal, seed, relevant_interactions=()); nonnegative ordinal, uint64 seed, unique namespaced interactions.

InteractionStatement

(selector, disposition, reason_code=None, qualification_version=None); excluded/unsupported requires reason, modeled requires qualification version.

InteractionDisposition

modeled, excluded_by_configuration, unsupported, not_applicable.

ProjectionResult

(descriptor, fields, interactions, metadata={}); nonempty exact local-name mapping, unique canonical coverage, no unsupported result.

projection_params_identity

(params: BaseModel) -> str; canonical sha256: identity of the validated snapshot.

ObservationEncodingPolicy

(max_tensor_bytes=1073741824); positive integer.

ObservationMetadataView

(record_id, observation_id, scene_id, scene_plan_reference, projection_ids, field_names); tensor-free decoded catalog.

SDSObservationAdapter

(policy=None); encode, decode, metadata_view, size estimation, and field naming.

Dataproc’s runtime artifact identity is a control-plane attestation over the exact image and ordered package bytes. Workers verify it before rebuilding the same projection descriptors; it is not an extra persisted descriptor field.

Sealed lifecycle and extension checklist

BaseObservationProjection declares name, contract_version, ParamsModel, interaction_selectors, optional required_store_capabilities, abstract from_params, preflight, and project, plus optional applicable_interactions. A subclass cannot override run.

Malformed lifecycle inputs, incomplete interaction coverage, drift from the resolved descriptor, and an unsupported returned result raise rfgen.core.errors.ValidationError; overriding run on a subclass raises TypeError during class creation.

  1. Resolve all selectors and strict parameter snapshots; bind implementation, distribution, parameters, and runtime into descriptors.

  2. Validate store capabilities and plan-derived interaction contributions.

  3. Build canonical-ID contexts with independently derived projection seeds.

  4. Preflight every plugin; any unsupported interaction stops before rendering.

  5. Render with the exact normalized coverage and reject any contract drift.

  6. Assemble one canonical aggregate and encode exactly once.

A minimal compliant plugin is:

from __future__ import annotations

from typing import ClassVar
import numpy as np
from pydantic import BaseModel, ConfigDict
from rfgen.observation import (
    BaseObservationProjection, InteractionStatement, LabelField, MeasurementDescriptor,
    ObservationAxis, ProjectionContext, ProjectionResult,
)
from rfgen.scene.plan import ScenePlan

class Params(BaseModel):
    model_config = ConfigDict(extra="forbid", frozen=True)
    class_id: int

class PassiveProjection(BaseObservationProjection):
    name: ClassVar[str] = "example.passive.receiver"
    contract_version: ClassVar[int] = 1
    ParamsModel: ClassVar[type[BaseModel]] = Params
    interaction_selectors: ClassVar[frozenset[str]] = frozenset()
    def __init__(self, params: Params) -> None:
        self.params = params

    @classmethod
    def from_params(cls, params: BaseModel) -> PassiveProjection:
        if not isinstance(params, Params):
            raise TypeError("Params required")
        return cls(params)

    def preflight(
        self, plan: ScenePlan, context: ProjectionContext
    ) -> tuple[InteractionStatement, ...]:
        return ()

    def project(
        self,
        plan: ScenePlan,
        context: ProjectionContext,
        interactions: tuple[InteractionStatement, ...],
    ) -> ProjectionResult:
        field = LabelField(
            "class_id", np.asarray([self.params.class_id], dtype=np.int64),
            (ObservationAxis("class", 1, "example.axis.class"),),
            MeasurementDescriptor("example.measurement.class", 1,
                                  "example.quantity.class"),
        )
        return ProjectionResult(context.descriptor, {field.name: field}, interactions)

Built-ins and scientific boundary

Selector

Parameters and output

rfgen.communications.receiver

No authored params; resolver freezes communications generation config. Produces receivers/rxN/iq, labels/boxes/extent and labels/boxes/identity whenever the scene declares emitters, and optional labels/segmentation. Requires communications activity.

rfgen.radar.receiver

params.source retains backend and renderer-private waveform/receiver/frontend values. Plan owns geometry, targets, apertures, PRI, and timing. Produces components/<name> captures with pulsed coordinates. Requires one radar activity.

Both built-ins declare cross-domain RF coupling excluded; synchronization does not claim mutual interference. Radar scientific qualification remains bounded by Radar Response.

Compatibility predicates

Predicate

Declared eligibility proved

require_same_scene

One equal qualified ScenePlan reference only.

require_sample_grid_compatibility

Same scene plus located axes, shape, and time-coordinate mapping. It does not compare dtype.

require_phase_coherence

Applicable equal phase and oscillator references.

require_numeric_sum_compatibility

Grid, equal dtype/payload kind/descriptor, addition disposition, receiver/measurement coordinates, state/noise algebra, and phase eligibility.

Passing the final predicate means declared contracts permit numeric summation; it is not empirical proof that the physical models are correct.

Public objects