Skip to content

Python Spectrum Analyzer API

doppler.analyzer is a single CPython extension exposing Specan — a streaming spectrum analyzer driven by the instrument parameters an operator already knows (center, span, RBW, reference level) instead of the DSP knobs (window length, Kaiser beta, zero-pad) underneath them.

It is the C-first home for the natural-parameter → DSP mapping: a Specan composes the DDC tuner/decimator and the PSD averaging-PSD core, so the whole chain lives in C exactly once and the doppler.specan application is a thin display/transport shell over it.


Specan

import numpy as np
from doppler.analyzer import Specan
from doppler.spectral import find_peaks_f32

# 200 kHz span, 500 Hz RBW around DC of a 2.048 MHz cf32 stream
sa = Specan(fs=2.048e6, span=200e3, rbw=500.0, center=0.0)
sa.fs_out, sa.n, sa.nfft, sa.display_size   # derived DSP grid
sa.rbw, sa.beta                              # realised RBW + the Kaiser beta

rng = np.random.default_rng(0)                # example cf32 source
iq_stream = [(rng.standard_normal(1 << 16)
              + 1j * rng.standard_normal(1 << 16)).astype(np.complex64)
             for _ in range(4)]
for chunk in iq_stream:                       # any cf32 block size
    db = sa.execute(chunk.astype(np.complex64))
    if db is None:                            # not enough samples for a frame
        continue
    # db is a DC-centred dB display band, length sa.display_size.
    # bin i → center + (i − display_size/2)·fs_out/nfft  Hz
    peaks = find_peaks_f32(db, 5, -60.0)      # peaks compose on the trace

sa.retune(50e3)        # cheap, seamless LO retune (no rebuild)
sa.destroy()

The constructor maps the instrument parameters to the DSP grid: the span sets the decimation rate (fs_out = span·1.28), the RBW sets the window length (coarse) and the Kaiser beta (fine, solved so the window ENBW realises the requested RBW), and the display is cropped to the central ±span/2 band. Changing the center is a cheap retune (the same instance keeps its filter history); changing the span or RBW alters the decimation rate and window length, so build a new Specan.

fs, span and rbw are required — omitting them raises TypeError rather than constructing an unusable analyzer. The display reads in dBFS against the PSD core's 0-dBFS reference — set it with bits (an ADC depth → 2**(bits-1)) or full_scale, the same single-source knob the measurement analyzers use. offset_db is an additive offset applied on top (e.g. a dBm calibration the application computes from a reference level). navg averages that many segments per emitted frame (navg=1 is a responsive single periodogram, larger navg trades update rate for a smoother, lower-variance floor). Peaks are intentionally not computed in the core — compose find_peaks_f32 on the returned dB band.

Specan

Create a natural-parameter spectrum analyzer.

Parameters:

Name Type Description Default
fs float

Input sample rate (Hz). Must be > 0.

required
span float

Display span (Hz). Must be > 0.

required
rbw float

Resolution bandwidth (Hz). Must be > 0.

required
src_center float

Source center frequency (Hz); the input band is centred here, so the analyzer mixes (center − src_center) to DC.

0.0
center float

Desired display center frequency (Hz).

0.0
offset_db float

Additive dB offset on the display spectrum, applied on top of dBFS (e.g. a dBm calibration the application computes from a reference level).

0.0
full_scale float

Amplitude that reads 0 dBFS (> 0). Ignored if bits > 0.

1.0
bits int

ADC depth: bits>0 sets the 0-dBFS reference to 2^(bits-1) in the shared PSD core (the single source of truth for the dBFS reference).

0
window Literal['hann', 'kaiser']

Window index: 0 = Hann, 1 = Kaiser (RBW-trimmable).

"kaiser"
navg int

Segments averaged per emitted frame (>= 1).

1

Examples:

>>> from doppler.analyzer import Specan
>>> sa = Specan(fs=2.048e6, span=200e3, rbw=500.0)
>>> sa.fs_out
256000.0
>>> sa.nfft == 2 * sa.n
True

fs_out property

fs_out: float

Decimated rate, Hz (= span·1.28, ≤ fs_in).

span property

span: float

Display span, Hz.

rbw property

rbw: float

Requested resolution bandwidth, Hz.

center property

center: float

Display center frequency, Hz.

beta property

beta: float

Kaiser beta realising rbw.

n property

n: int

Segment / window length (samples).

nfft property

nfft: int

Zero-padded transform length.

navg property

navg: int

Segments averaged per emitted frame.

display_size property

display_size: int

Display size.

execute

execute(
    x: NDArray[complex64],
    out: NDArray[float32] | None = None,
) -> NDArray[np.float32]

Mix, decimate, average; return one DC-centred dB display frame, or None.

Feeds x through the Ddc, buffers the decimated output, and once n·navg decimated samples are available windows + FFTs + averages them into a fresh frame, crops the central ±span/2 band and writes it in dB (+ ref_db). Returns 0 (writing nothing) until a frame is ready — the binding maps that to Python None.

Parameters:

Name Type Description Default
x NDArray[complex64]

cf32 input block (C-only; the binding passes it).

required
out NDArray[float32] | None

Display-spectrum buffer, dB (C-only).

None

Returns:

Type Description
NDArray[float32]

Display bins written (disp_n), or 0 if no frame is ready yet.

Examples:

>>> from doppler.analyzer import Specan
>>> import numpy as np
>>> sa = Specan(fs=2.048e6, span=200e3, rbw=500.0, navg=1)
>>> sa.execute(np.zeros(64, dtype=np.complex64)) is None  # too few
True
>>> frame = sa.execute(np.zeros(65536, dtype=np.complex64))
>>> frame.shape, frame.dtype
((801,), dtype('float32'))

execute_max_out

execute_max_out() -> int

Output capacity hint for specan_execute(); equals disp_n.

Returns:

Type Description
int

Output.

retune

retune(center: float) -> None

Move the display center frequency (seamless LO retune; no rebuild).

Updates the Ddc LO phase increment (seamless across blocks — no resampler or window reset) and drops pending samples so the next frame reflects only the new tuning. Changing the span or RBW requires a destroy + create (the decimation rate and window length change).

Parameters:

Name Type Description Default
center float

New display center frequency (Hz).

required

reset

reset() -> None

Drop pending samples and the running average; zero LO/filter history.

state_bytes

state_bytes() -> int

Size in bytes of this object's serialized state.

The exact length get_state returns and set_state requires. It depends on how the object was constructed (state arrays are sized at construction), so read it from the instance rather than assuming a constant.

Raises RuntimeError if the Specan has already been destroyed.

Returns:

Type Description
int

Byte length of one serialized state blob.

get_state

get_state() -> bytes

Serialize this object's mutable state to bytes.

Captures exactly the state that evolves as the object runs, so a blob taken now and restored later resumes from this point. Construction parameters are not included: restore into an object built the same way.

The blob is opaque and always state_bytes() long. Its layout is an implementation detail of the C core and is not a stable format across builds.

Raises RuntimeError if the Specan has already been destroyed.

Returns:

Type Description
bytes

Opaque snapshot, state_bytes() bytes long.

set_state

set_state(blob: bytes) -> None

Restore mutable state from a get_state() blob.

Overwrites the live state in place; the object keeps the parameters it was constructed with. Length is validated against state_bytes() before the blob is handed to the C core, and the core may reject it as well.

Raises TypeError if blob is not bytes, ValueError if its length differs from state_bytes() or the core rejects it, and RuntimeError if the Specan has already been destroyed.

Parameters:

Name Type Description Default
blob bytes

A get_state() blob from this type, exactly state_bytes() long.

required

destroy

destroy() -> None

Release the underlying C resources immediately.

Ordinarily unnecessary: the resources are freed when the object is garbage-collected. Call this to release them at a definite point instead, or use the object as a context manager, which calls it on exit.

Idempotent: calling it again on an already-released object does nothing. Every other method raises RuntimeError once it has run.

__enter__

__enter__() -> Specan

Enter a context manager, returning this object.

Lets a Specan be used in a with statement so its C resources are released deterministically on exit rather than at collection time.

Returns:

Type Description
Specan

This same object, not a copy.

__exit__

__exit__(
    exc_type: object | None = ...,
    exc: object | None = ...,
    tb: object | None = ...,
) -> None

Exit a context manager, releasing the Specan.

Equivalent to calling destroy(). Returns None, so an exception raised inside the with body propagates normally; this never suppresses one.

Parameters:

Name Type Description Default
exc_type object | None

Exception class, or None. Ignored.

...
exc object | None

Exception instance, or None. Ignored.

...
tb object | None

Traceback object, or None. Ignored.

...

See also

GuidesPower Spectra & Measurements DesignAPI taxonomy: the DSP building-block hierarchy and its naming axis, Spectral & Measurement API Map