Skip to content

Python Measurement API

doppler.measure.ToneMeasure analyses one time-domain capture (real or complex) into the full single-tone ADC / spectral metric bag — SNR, SINAD, THD, THD+N, SFDR, ENOB, noise floor and the worst spur — plus the accuracy/resolution metadata. Each component's power is integrated over its window main lobe (IEEE Std 1241); see the design guide for the equations and conventions, and the Spectral & Measurement API Map for how this module composes with the shared PSD core.

Source: src/doppler/measure/__init__.py


Examples

Single-tone metrics

import numpy as np
from doppler.measure import ToneMeasure

fs, n = 100e6, 1 << 14
x = np.cos(2 * np.pi * 10.017e6 * np.arange(n) / fs).astype(np.float32)

m = ToneMeasure(n=n, fs=fs)      # auto Kaiser window, sized from bits/DR
r = m.analyze(x)                 # named ToneMetrics result
r.enob, r.sfdr_dbc, r.fund_dbfs  # attribute access
snr, sinad, *_ = r               # ...and tuple unpacking

ENOB of an ADC

from doppler.cvt import ADC

codes = ADC(12, 0.0, 0).steps(x).astype(np.float32)
m = ToneMeasure(n=n, fs=fs, bits=12)   # bits sets the dBFS reference
print(round(m.analyze(codes).enob, 2))   # ≈ 12.0 for an ideal 12-bit ADC

All three analyzers take the bits (ADC depth → 2**(bits-1)) or full_scale dBFS knob and read it back from the shared PSD core, so bits=B is identical to full_scale=2**(B-1) — one source of truth. Each also exposes spectrum_dbfs(x): the same averaged-PSD dBFS trace its metrics use, for a display backdrop (no hand-rolled periodogram needed).

Complex baseband, accuracy metadata, and the spectrum

iq = np.exp(2j * np.pi * 13e6 * np.arange(n) / fs).astype(np.complex64)
r = m.analyze_complex(iq)        # two-sided analysis

m.rbw, m.bin_hz, r.lobe_bins     # resolution vs interpolation grid
spec = m.spectrum_dbfs(x)        # DC-centred dBFS trace (length nfft) for plots
ts = m.time_stats(x)             # crest_db / papr_db / dc_offset / fs_util_pct

Resolution vs bin spacing

m.rbw (resolution bandwidth) is derived from the un-padded length n; m.bin_hz is the zero-padded interpolation grid. Padding sharpens the frequency estimate and the plot, but does not improve resolution.

Two-tone IMD and notched-noise NPR

from doppler.measure import IMDMeasure, NPRMeasure

# Two equal tones -> IMD2/IMD3 and the third-order intercept
t = np.arange(n)
two_tone_capture = (
    0.5 * np.cos(2 * np.pi * 10.0e6 * t / fs)
    + 0.5 * np.cos(2 * np.pi * 11.0e6 * t / fs)
).astype(np.float32)
imd = IMDMeasure(n=n, fs=fs)
r = imd.analyze(two_tone_capture)        # r.imd3_dbc, r.toi_dbfs, ...

# Notched-noise loading -> NPR (band/notch geometry are analyze() args)
noise = np.random.randn(n).astype(np.float32)
active_lo, active_hi = 1.0e6, 40.0e6     # loaded band edges (Hz)
notch_lo, notch_hi = 19.0e6, 21.0e6      # the cleared notch (Hz)
guard_hz = 1.0e6
npr = NPRMeasure(n=n, fs=fs, bits=10)
g = npr.analyze(noise, active_lo, active_hi, notch_lo, notch_hi, guard_hz)
print(g.npr_db)

# both expose the same display-spectrum method as ToneMeasure
imd_spec = imd.spectrum_dbfs(two_tone_capture)   # DC-centred dBFS, length nfft
npr_spec = npr.spectrum_dbfs(noise)

Capture planning

from doppler.measure import (
    dp_coherent_freq,
    measure_min_samples,
    measure_proc_gain,
    measure_rec_nfft,
)

n = measure_min_samples(
    fs, target_rbw=1e3, bits=12, dynamic_range_db=0.0, complex_input=0
)
nfft = measure_rec_nfft(n, pad=2)
pg = measure_proc_gain(nfft)
f0 = dp_coherent_freq(fs, 10e6, n)       # leakage-free coherent test tone

ToneMeasure

Create a ToneMeasure analyser (auto Kaiser window).

Parameters:

Name Type Description Default
n int

Capture/frame length (>= 2).

8192
fs float

Sample rate (Hz, > 0).

1.0
n_harmonics int

Harmonics to track (k = 2..n_harmonics).

8
full_scale float

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

1.0
bits int

ADC depth: bits>0 sets the 0-dBFS reference to 2^(bits-1) and, unless overridden, the dynamic-range target (6.02*bits + 1.76 + headroom).

0
dynamic_range_db float

Explicit sidelobe/dynamic-range target (dB); used when > 0, else derived from bits (or a deep default when both are 0).

0.0
dc_guard int

Extra bins excluded beyond L around DC.

0

Examples:

Create with defaults:

>>> from doppler.measure import ToneMeasure
>>> obj = ToneMeasure(
...     n=8192,
...     fs=1.0,
...     n_harmonics=8,
...     full_scale=1.0,
...     bits=0,
...     dynamic_range_db=0.0,
...     dc_guard=0,
... )

n property

n: int

Window / frame length (samples).

nfft property

nfft: int

Zero-padded transform length.

fs property

fs: float

Sample rate, Hz.

enbw property

enbw: float

Equivalent noise bandwidth, bins.

lobe_bins property

lobe_bins: int

Window main-lobe half-width L (bins).

spur_guard_bins property

spur_guard_bins: int

Spur guard bins.

beta property

beta: float

Beta.

rbw property

rbw: float

Rbw.

bin_hz property

bin_hz: float

FFT bin spacing = fs/nfft (Hz).

proc_gain_db property

proc_gain_db: float

FFT processing gain = 10log10(nfft/2) (dB).

reset

reset() -> None

Reset the analyser (a no-op: it holds no state between calls).

Every analyze() / analyze_complex() / time_stats() / spectrum_dbfs() call re-windows and re-transforms its own capture from scratch, so there is nothing carried between calls to clear. The method exists only so ToneMeasure honours the same reset() contract as every other doppler object, letting a generic pipeline reset each stage uniformly.

Examples:

>>> from doppler.measure import ToneMeasure
>>> m = ToneMeasure(n=4096, fs=1.0)
>>> m.reset()            # stateless: provided only for API uniformity
>>> m.reset() is None    # returns nothing; safe to call anytime
True

analyze

analyze(x: float) -> ToneMetrics

Analyze a real time-domain capture; returns a ToneMetrics result.

Parameters:

Name Type Description Default
x float

Input.

required

Returns:

Type Description
ToneMetrics

the metric record (by value).

Examples:

>>> from doppler.measure import ToneMeasure
>>> import numpy as np
>>> n, t = 4096, np.arange(4096)
>>> # full-scale tone at 300 cycles + a 2nd harmonic 40 dB down
>>> x = (np.cos(2*np.pi*300*t/n)
...      + 0.01*np.cos(2*np.pi*600*t/n)).astype(np.float32)
>>> r = ToneMeasure(n=n, fs=1.0).analyze(x)
>>> type(r).__name__
'ToneMetrics'
>>> abs(r.fund_dbfs) < 0.1, round(r.thd, 1)  # 0 dBFS tone, THD -40
(True, -40.0)

analyze_complex

analyze_complex(x: complex) -> ToneMetrics

Analyze a complex baseband capture (two-sided spectrum).

Parameters:

Name Type Description Default
x complex

Input.

required

Returns:

Type Description
ToneMetrics

Output.

Examples:

>>> from doppler.measure import ToneMeasure
>>> import numpy as np
>>> i = np.arange(4096)
>>> x = np.exp(2j*np.pi*137*i/4096).astype(np.complex64)
>>> r = ToneMeasure(n=4096, fs=1.0).analyze_complex(x)
>>> round(r.fund_freq, 4), abs(r.fund_dbfs) < 0.2
(0.0334, True)

time_stats

time_stats(x: float) -> TimeStats

Time-domain stats: RMS, peak, crest/PAPR, DC offset, FS utilisation.

Parameters:

Name Type Description Default
x float

Input.

required

Returns:

Type Description
TimeStats

Output.

Examples:

>>> from doppler.measure import ToneMeasure
>>> import numpy as np
>>> t = np.arange(4096)
>>> x = (0.8*np.cos(2*np.pi*50*t/4096)).astype(np.float32)
>>> ts = ToneMeasure(n=4096, fs=1.0).time_stats(x)
>>> round(ts.crest_db, 2), round(ts.fs_util_pct, 0)  # crest ~3.01 dB
(3.01, 80.0)

spectrum_dbfs

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

DC-centred dBFS magnitude spectrum of a capture (length nfft, for plots).

The windowed, zero-padded magnitude spectrum behind the metrics, laid out DC-centred (fftshifted) and normalised to dBFS so it drops straight under an analyzer trace. Use it to eyeball where the fundamental, harmonics and spurs that analyze() quantifies actually sit.

Parameters:

Name Type Description Default
x NDArray[float32]

Real time-domain capture (length x_len).

required
out NDArray[float32] | None

Destination buffer (length >= max_out).

None

Returns:

Type Description
NDArray[float32]

DC-centred dBFS magnitude spectrum, one value per FFT bin (nfft).

Examples:

>>> from doppler.measure import ToneMeasure
>>> import numpy as np
>>> t = np.arange(4096)
>>> x = np.cos(2*np.pi*300*t/4096).astype(np.float32)  # full-scale
>>> s = ToneMeasure(n=4096, fs=1.0).spectrum_dbfs(x)  # DC-centred dBFS
>>> s.shape                     # zero-padded to next power of two
(8192,)
>>> round(float(s.max()), 1)   # two real images, ~6 dB each
-6.0

spectrum_dbfs_max_out

spectrum_dbfs_max_out() -> int

Capacity (== nfft) of the spectrum_dbfs output buffer.

Returns:

Type Description
int

Output.

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__() -> ToneMeasure

Enter a context manager, returning this object.

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

Returns:

Type Description
ToneMeasure

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 ToneMeasure.

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.

...

TimeStats

Bases: tuple[float, float, float, float, float, float]

AC-coupled time-domain capture statistics (crest factor / PAPR).

Attributes:

Name Type Description
rms float

Root-mean-square amplitude (DC included).

peak float

Peak deviation, max|x - DC|.

crest_db float

Crest factor, 20log10(peak_ac / rms_ac) (dB).

papr_db float

Peak-to-average power ratio (= crest) (dB).

dc_offset float

DC offset, mean(x).

fs_util_pct float

Full-scale use, 100*max|x|/full_scale (%).

rms property

rms: float

Root-mean-square amplitude (DC included).

peak property

peak: float

Peak deviation, max|x - DC|.

crest_db property

crest_db: float

Crest factor, 20log10(peak_ac / rms_ac) (dB).

papr_db property

papr_db: float

Peak-to-average power ratio (= crest) (dB).

dc_offset property

dc_offset: float

DC offset, mean(x).

fs_util_pct property

fs_util_pct: float

Full-scale use, 100*max|x|/full_scale (%).


IMDMeasure

Create an IMDMeasure analyser (auto Kaiser window).

Parameters:

Name Type Description Default
n int

Capture/frame length (>= 2).

8192
fs float

Sample rate (Hz, > 0).

1.0
full_scale float

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

1.0
bits int

ADC depth: bits>0 sets the 0-dBFS reference to 2^(bits-1) and, unless overridden, the dynamic-range target.

0
dynamic_range_db float

Explicit sidelobe/dynamic-range target (dB); used when > 0, else derived from bits.

0.0

Examples:

Create with defaults:

>>> from doppler.measure import IMDMeasure
>>> obj = IMDMeasure(
...     n=8192,
...     fs=1.0,
...     full_scale=1.0,
...     bits=0,
...     dynamic_range_db=0.0,
... )

n property

n: int

Window / frame length (samples).

nfft property

nfft: int

Zero-padded transform length.

fs property

fs: float

Sample rate, Hz.

reset

reset() -> None

Reset the analyser (a no-op: each analyze() call is independent).

Every analyze() / spectrum_dbfs() call re-windows and re-transforms its own capture from scratch, so nothing is carried between calls to clear. The method exists only so IMDMeasure honours the same reset() contract as every other doppler object, letting a generic pipeline reset each stage uniformly.

Examples:

>>> from doppler.measure import IMDMeasure
>>> m = IMDMeasure(n=4096, fs=1.0)
>>> m.reset()            # stateless: provided only for API uniformity
>>> m.reset() is None    # returns nothing; safe to call anytime
True

analyze

analyze(x: float) -> IMDMetrics

Two-tone IMD/TOI of a real capture (finds the two strongest tones).

Parameters:

Name Type Description Default
x float

Input.

required

Returns:

Type Description
IMDMetrics

the IMD metric record (by value; zeroed if no two tones are found).

Examples:

>>> from doppler.measure import IMDMeasure
>>> import numpy as np
>>> t = np.arange(4096)
>>> # two equal tones at 200 & 250 cycles, plus 3rd-order
>>> # products 40 dB down
>>> x = (np.cos(2*np.pi*200*t/4096) + np.cos(2*np.pi*250*t/4096)
...      + 0.01*np.cos(2*np.pi*150*t/4096)
...      + 0.01*np.cos(2*np.pi*300*t/4096)).astype(np.float32)
>>> r = IMDMeasure(n=4096, fs=1.0).analyze(x)
>>> round(r.f1, 4), round(r.f2, 4), round(r.imd3_dbc, 0)
(0.0488, 0.061, -40.0)

spectrum_dbfs

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

DC-centred dBFS magnitude spectrum of a capture (length nfft, for plots).

The same windowed, zero-padded PSD the IMD metrics are read off, laid out DC-centred (fftshifted) and normalised to dBFS for an analyzer-display backdrop. Use it to see the two fundamentals and the intermodulation products that analyze() integrates.

Parameters:

Name Type Description Default
x NDArray[float32]

Real time-domain capture (length x_len).

required
out NDArray[float32] | None

Destination buffer (length >= max_out).

None

Returns:

Type Description
NDArray[float32]

DC-centred dBFS magnitude spectrum, one value per FFT bin (nfft).

Examples:

>>> from doppler.measure import IMDMeasure
>>> import numpy as np
>>> t = np.arange(4096)
>>> x = (0.5*np.cos(2*np.pi*200*t/4096)
...      + 0.5*np.cos(2*np.pi*250*t/4096)).astype(np.float32)
>>> s = IMDMeasure(n=4096, fs=1.0).spectrum_dbfs(x)  # DC-centred dBFS
>>> s.shape
(8192,)
>>> round(float(s.max()), 1)   # each tone splits into two images
-12.0

spectrum_dbfs_max_out

spectrum_dbfs_max_out() -> int

Capacity (== nfft) of the spectrum_dbfs output buffer.

Returns:

Type Description
int

Output.

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__() -> IMDMeasure

Enter a context manager, returning this object.

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

Returns:

Type Description
IMDMeasure

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 IMDMeasure.

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.

...

IMDMetrics

Bases: tuple[float, float, float, float, float, float, float, float, float, float, float, float]

Two-tone intermodulation metrics (IMD2, IMD3, second/third-order intercepts).

Attributes:

Name Type Description
f1 float

Lower tone frequency (Hz).

f2 float

Upper tone frequency (Hz).

p1_dbfs float

Lower tone level (dBFS).

p2_dbfs float

Upper tone level (dBFS).

imd2_dbc float

2nd-order product (f2-f1) vs mean tone (dBc).

imd3_dbc float

Worst 3rd-order product vs mean tone (dBc).

imd2_freq float

2nd-order product frequency (Hz).

imd3_lo_freq float

3rd-order (2f1-f2) product frequency (Hz).

imd3_hi_freq float

3rd-order (2f2-f1) product frequency (Hz).

toi_dbfs float

Third-order intercept (dBFS).

soi_dbfs float

Second-order intercept (dBFS).

rbw_hz float

Resolution bandwidth = enbw*fs/n (Hz).

f1 property

f1: float

Lower tone frequency (Hz).

f2 property

f2: float

Upper tone frequency (Hz).

p1_dbfs property

p1_dbfs: float

Lower tone level (dBFS).

p2_dbfs property

p2_dbfs: float

Upper tone level (dBFS).

imd2_dbc property

imd2_dbc: float

2nd-order product (f2-f1) vs mean tone (dBc).

imd3_dbc property

imd3_dbc: float

Worst 3rd-order product vs mean tone (dBc).

imd2_freq property

imd2_freq: float

2nd-order product frequency (Hz).

imd3_lo_freq property

imd3_lo_freq: float

3rd-order (2f1-f2) product frequency (Hz).

imd3_hi_freq property

imd3_hi_freq: float

3rd-order (2f2-f1) product frequency (Hz).

toi_dbfs property

toi_dbfs: float

Third-order intercept (dBFS).

soi_dbfs property

soi_dbfs: float

Second-order intercept (dBFS).

rbw_hz property

rbw_hz: float

Resolution bandwidth = enbw*fs/n (Hz).


NPRMeasure

Create an NPRMeasure analyser (auto Kaiser window).

Parameters:

Name Type Description Default
n int

Capture/frame length (>= 2).

8192
fs float

Sample rate (Hz, > 0).

1.0
full_scale float

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

1.0
bits int

ADC depth: bits>0 sets the 0-dBFS reference to 2^(bits-1) and, unless overridden, the dynamic-range target.

0
dynamic_range_db float

Explicit sidelobe/dynamic-range target (dB); used when > 0, else derived from bits.

0.0

Examples:

Create with defaults:

>>> from doppler.measure import NPRMeasure
>>> obj = NPRMeasure(
...     n=8192,
...     fs=1.0,
...     full_scale=1.0,
...     bits=0,
...     dynamic_range_db=0.0,
... )

n property

n: int

Window / frame length (samples).

nfft property

nfft: int

Zero-padded transform length.

fs property

fs: float

Sample rate, Hz.

rbw property

rbw: float

Rbw.

reset

reset() -> None

Reset the analyser (a no-op: each analyze() call is independent).

Every analyze() / spectrum_dbfs() call re-windows and re-transforms its own capture from scratch, so nothing is carried between calls to clear. The method exists only so NPRMeasure honours the same reset() contract as every other doppler object, letting a generic pipeline reset each stage uniformly.

Examples:

>>> from doppler.measure import NPRMeasure
>>> m = NPRMeasure(n=8192, fs=1.0)
>>> m.reset()            # stateless: provided only for API uniformity
>>> m.reset() is None    # returns nothing; safe to call anytime
True

analyze

analyze(
    x: float,
    active_lo: float,
    active_hi: float,
    notch_lo: float,
    notch_hi: float,
    guard_hz: float = 0.0,
) -> NPRMetrics

NPR of a notched-noise capture over [active_lo,active_hi] with a notch [notch_lo,notch_hi] (Hz) and guard keep-out.

Parameters:

Name Type Description Default
x float

Real time-domain capture.

required
active_lo float

Active noise band lower edge (Hz).

required
active_hi float

Active noise band upper edge (Hz).

required
notch_lo float

Notch lower edge (Hz).

required
notch_hi float

Notch upper edge (Hz).

required
guard_hz float

Keep-out around the notch edges (Hz).

0.0

Returns:

Type Description
NPRMetrics

the NPR metric record (by value).

Examples:

>>> from doppler.measure import NPRMeasure
>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> n = 1 << 15
>>> F = np.fft.rfft(rng.standard_normal(n))
>>> f = np.fft.rfftfreq(n)
>>> F[(f < 0.05) | (f > 0.45)] = 0  # band-limit to [0.05,0.45]
>>> F[(f >= 0.20) & (f <= 0.25)] *= 10**(-50/20)   # notch 50 dB deep
>>> x = np.fft.irfft(F, n)
>>> x = (0.3*x/np.std(x)).astype(np.float32)
>>> r = NPRMeasure(n=n, fs=1.0).analyze(
...     x, 0.05, 0.45, 0.20, 0.25, 0.01)
>>> 45 < r.npr_db < 55, r.notch_psd_dbfs < r.inband_psd_dbfs
(True, True)

spectrum_dbfs

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

DC-centred dBFS magnitude spectrum of a capture (length nfft, for plots).

The same windowed, zero-padded PSD the NPR metrics are read off, laid out DC-centred (fftshifted) and normalised to dBFS for an analyzer-display backdrop. Use it to see the notch and the active band that analyze() integrates over.

Parameters:

Name Type Description Default
x NDArray[float32]

Real time-domain capture (length x_len).

required
out NDArray[float32] | None

Destination buffer (length >= max_out).

None

Returns:

Type Description
NDArray[float32]

DC-centred dBFS magnitude spectrum, one value per FFT bin (nfft).

Examples:

>>> from doppler.measure import NPRMeasure
>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> x = (0.3*rng.standard_normal(8192)).astype(np.float32)  # noise
>>> s = NPRMeasure(n=8192, fs=1.0).spectrum_dbfs(x)  # DC-centred dBFS
>>> s.shape                                          # zero-padded nfft
(16384,)
>>> round(float(np.median(s)), 0)   # broadband floor, below 0 dBFS
-48.0

spectrum_dbfs_max_out

spectrum_dbfs_max_out() -> int

Capacity (== nfft) of the spectrum_dbfs output buffer.

Returns:

Type Description
int

Output.

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__() -> NPRMeasure

Enter a context manager, returning this object.

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

Returns:

Type Description
NPRMeasure

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 NPRMeasure.

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.

...

NPRMetrics

Bases: tuple[float, float, float, int, int, float]

Noise-power-ratio metrics from a notched-noise-loading test.

Attributes:

Name Type Description
npr_db float

NPR = 10log10(in-band PSD / notch PSD) (dB).

inband_psd_dbfs float

Mean in-band noise power per bin (dBFS).

notch_psd_dbfs float

Mean power folded into the notch (dBFS).

n_inband_bins int

Bins averaged in the active band.

n_notch_bins int

Bins averaged inside the notch.

rbw_hz float

Resolution bandwidth = enbw*fs/n (Hz).

npr_db property

npr_db: float

NPR = 10log10(in-band PSD / notch PSD) (dB).

inband_psd_dbfs property

inband_psd_dbfs: float

Mean in-band noise power per bin (dBFS).

notch_psd_dbfs property

notch_psd_dbfs: float

Mean power folded into the notch (dBFS).

n_inband_bins property

n_inband_bins: int

Bins averaged in the active band.

n_notch_bins property

n_notch_bins: int

Bins averaged inside the notch.

rbw_hz property

rbw_hz: float

Resolution bandwidth = enbw*fs/n (Hz).


measure_min_samples

measure_min_samples(
    fs: float,
    target_rbw: float,
    bits: int,
    dynamic_range_db: float,
    complex_input: int,
) -> int

Samples for a target RBW (auto Kaiser from bits/dynamic_range_db; target_rbw<=0 -> span/1000).

Plans a capture for the same auto-Kaiser window the measurement objects use: the dynamic-range target (from dynamic_range_db, else bits) selects the Kaiser beta, whose ENBW (measured via kaiser_enbw) sets the bins-per-RBW. RBW = ENBW * fs / n, so n = ceil(ENBW * fs / target_rbw).

Parameters:

Name Type Description Default
fs float

Sample rate (Hz, > 0).

required
target_rbw float

Desired resolution bandwidth (Hz). When <= 0 it defaults to span/1000, where span = fs/2 for real captures and fs for complex (complex_input).

required
bits int

ADC depth: sets the dynamic-range target when no explicit override is given.

required
dynamic_range_db float

Explicit dynamic-range target (dB); used when > 0.

required
complex_input int

Non-zero if the capture is complex (span = fs).

required

Returns:

Type Description
int

Required capture length, or 0 on bad args.

measure_rec_nfft

measure_rec_nfft(n: int, pad: int) -> int

Recommended zero-padded transform length: next_pow2(n * pad).

Parameters:

Name Type Description Default
n int

Input.

required
pad int

Input.

required

Returns:

Type Description
int

Output.

measure_proc_gain

measure_proc_gain(nfft: int) -> float

FFT processing gain in dB: 10*log10(nfft / 2).

Parameters:

Name Type Description Default
nfft int

Input.

required

Returns:

Type Description
float

Output.

dp_coherent_freq

dp_coherent_freq(
    fs: float, f_target: float, N: int
) -> float

Nearest leakage-free coherent test frequency (J cycles, J coprime N).

Snaps f_target to J * fs / N where J is the nearest integer cycle count that is coprime with N — an integer number of cycles in the capture (no leakage) with J coprime to N (so quantisation-noise correlation is minimised).

Parameters:

Name Type Description Default
fs float

Input.

required
f_target float

Input.

required
N int

Input.

required

Returns:

Type Description
float

The coherent frequency (Hz), or 0 on bad args.

GalleryGallery, Measurement Suite — two-tone IMD/TOI & notched-noise NPR, Measurement Suite — ADC characterisation GuidesPower Spectra & Measurements DesignAPI taxonomy: the DSP building-block hierarchy and its naming axis, Measurement Suite — single-tone ADC / spectral metrics, Spectral & Measurement API Map