Skip to content

metrology.time_frequency

Calibrated time-frequency analysis: STFT spectrogram and zoom FFT.

Fine-band time-frequency views of a record, following Bendat & Piersol, Random Data: Analysis and Measurement Procedures (4th ed., 2010):

  • the spectrogram (Section 12.6.4.2): short-time Fourier transforms of contiguous, tapered, possibly overlapped segments, displayed over the time-frequency plane (Eq. 12.173 defines the unweighted magnitude version; this module computes the power version with the exact 'density'/'spectrum' calibration of power_spectral_density, so a signal in pascals reads directly in Pa²/Hz or Pa² and averaging the columns reproduces the Welch estimate bin by bin). Each cell trades the time resolution T_B = nperseg/fs against the frequency resolution 1/T_B at a resolution-bandwidth-time product of one, so a single cell of random data is an unaveraged estimate: the power carries a normalized random error of 1/√nd = 1 with nd = 1 (Eq. 8.158), and the magnitude display an error of √2/1.25 ≈ 1.13 - the Rayleigh-ratio result Bendat & Piersol quote in Section 12.6.4.2. Deterministic structure (tones, sweeps, transients) is unaffected by that caveat and is what the spectrogram is for.

  • the zoom FFT (Section 11.5.4): the spectrum of a narrow band [f_min, f_max] computed on an arbitrarily fine frequency grid without the giant FFT block a full-band analysis would need (Eq. 11.122). The book’s procedure - bandpass, complex demodulation by exp(-j2πf₁t), decimation by d = k₂/(k₂-k₁) and an FFT of the decimated record (Eqs. 11.123-11.130) - is realized here in its exact single-pass digital equivalent, the chirp-Z evaluation of the DFT on the zoom grid (scipy.signal.zoom_fft): both compute the same DFT samples of the record, which the test suite verifies to machine precision against the demodulate-decimate-DFT chain. The bin spacing can be made arbitrarily fine, but the true resolution stays set by the record length and taper (the reported effective noise bandwidth Bₑ = fs·Σw²/(Σw)²): zooming refines the grid, only a longer record refines the resolution (Eq. 11.127).

Amplitudes are calibrated so that a sine of peak amplitude A on an analysis frequency reads |spectrum| = A, power = A²/2 (its mean square) - consistent with the 'spectrum' scaling of the Welch module.

Auto-generated from the source docstrings by scripts/generate_api_docs.py (make api-docs). Do not edit by hand.

spectrogram(
x: NDArray[np.float64] | list[float],
fs: float,
*,
window: str = 'hann',
nperseg: int | None = None,
overlap: float = 0.5,
scaling: Literal['density', 'spectrum'] = 'density',
) -> SpectrogramResult

Calibrated STFT power spectrogram (Bendat & Piersol 12.6.4.2).

The record is split into tapered (Hann by default), overlapped segments - exactly the segmentation of power_spectral_density - and each segment’s one-sided periodogram becomes one column of the time-frequency display, without the averaging that the Welch estimate applies (averaging the columns reproduces it bin by bin). No detrending is applied, so absolute calibration is preserved: a signal in pascals yields Pa²/Hz ('density') or Pa² ('spectrum'), and a sine of amplitude A on an analysis frequency reads A²/2 - its mean square - in every 'spectrum' column it spans.

The display trades time against frequency resolution through the segment length: T_B = nperseg/fs of time resolution against Bₑ ≈ 1/T_B of frequency resolution (Section 12.6.4.2). Because each cell is a single unaveraged estimate (BₑT_B ≈ 1), random data carries a per-cell normalized random error of 1 (Eq. 8.158 with nd = 1): the spectrogram is a tool for deterministic structure - tones, sweeps, transients - not a low-variance spectral estimator.

Parameters

NameDescription
xSignal, 1-D.
fsSample rate, in Hz.
windowSegment taper (any scipy window name; default Hann).
npersegSegment length; None picks a length giving a bin spacing of at most 4 Hz (the Welch-module default).
overlapSegment overlap fraction in [0, 1) (default 0.5).
scaling'density' (units²/Hz) or 'spectrum' (units²).

Returns: A SpectrogramResult.

Raises

ExceptionWhen
ValueErrorIf the inputs or parameters are invalid.
SpectrogramResult(
times: NDArray[np.float64],
frequencies: NDArray[np.float64],
power: NDArray[np.float64],
time_resolution: float,
resolution_bandwidth: float,
random_error: float,
n_segments: int,
hop: int,
window: str,
nperseg: int,
overlap: float,
scaling: str,
)

Calibrated STFT power spectrogram (B&P Section 12.6.4.2).

Attributes

NameDescription
timesSegment-centre times, in seconds (one per column).
frequenciesOne-sided frequency axis, in Hz (one per row).
powerPower spectrogram, shape (frequencies, times) (units²/Hz for 'density' scaling, units² for 'spectrum'). Each column is the tapered periodogram of one segment, with the exact calibration of power_spectral_density: the column mean over time reproduces the Welch spectrum bin by bin. Integrating a 'density' column over frequency gives that segment’s taper-weighted mean square Σ(x·w)²/Σw²; summing those over time and multiplying by the hop duration hop/fs recovers the record energy Σx²/fs when the squared taper overlap-adds to a constant (e.g. Hann at 75 % overlap), up to the taper roll-off at the record edges (the first and last segments are under-weighted: about 1-2 % low for typical records).
time_resolutionSegment duration T_B = nperseg/fs, in seconds - the time resolution of the display.
resolution_bandwidthEffective noise bandwidth Bₑ of the tapered segment, in Hz - the frequency resolution (≈ 1/T_B for a light taper; the BₑT_B product per cell is close to 1).
random_errorNormalized random error of each (unaveraged) power cell for random data, 1/√nd = 1 with nd = 1 (Eq. 8.158); Bendat & Piersol quote √2/1.25 ≈ 1.13 for the magnitude display (Section 12.6.4.2). Deterministic components are unaffected.
n_segmentsNumber of segments (columns).
hopHop between segment starts, in samples.
windowTaper name.
npersegSegment length, in samples.
overlapSegment overlap fraction.
scaling'density' or 'spectrum'.
SpectrogramResult.plot(
ax: Axes | None = None,
*,
language: str = 'en',
**kwargs: Any,
) -> Axes

Plot the spectrogram in dB over the time-frequency plane.

Parameters

NameDescription
languageLabel language, "en" (default) or "es".
zoom_fft(
x: NDArray[np.float64] | list[float],
fs: float,
f_min: float,
f_max: float,
*,
n_points: int | None = None,
window: str = 'hann',
) -> ZoomFFTResult

Zoom FFT: the spectrum of a narrow band on a fine grid (B&P 11.5.4).

Resolves closely spaced tones - gear sidebands, twin machines, power hum - separated by less than a practical full-band FFT bin, without the giant block size Eq. 11.122 would demand. Bendat & Piersol’s procedure (bandpass, complex demodulation to shift f_min to zero, decimation by the bandwidth ratio, FFT of the decimated record; Eqs. 11.123-11.130) is computed here in its exact single-pass digital equivalent: the chirp-Z evaluation of the tapered record’s DFT on the zoom grid (scipy.signal.zoom_fft), which yields the same DFT samples to machine precision.

Amplitudes are calibrated per taper coherent gain (2·X/Σw), so a sine of peak amplitude A on an analysis frequency reads amplitude = A and power = A²/2 exactly. The grid can be made arbitrarily fine, but the true resolution remains the reported effective noise bandwidth of the tapered record (1/T for no taper): the zoom refines the grid; only a longer record separates tones closer than Bₑ (Eq. 11.127).

Parameters

NameDescription
xSignal, 1-D.
fsSample rate, in Hz.
f_minLower edge of the zoom band, in Hz (≥ 0).
f_maxUpper edge of the zoom band, in Hz (≤ fs/2).
n_pointsGrid points across [f_min, f_max] (endpoints included); None places one point per record-length resolution fs/N.
windowRecord taper (any scipy window name; default Hann; 'boxcar' for none).

Returns: A ZoomFFTResult.

Raises

ExceptionWhen
ValueErrorIf the inputs or parameters are invalid.
ZoomFFTResult(
frequencies: NDArray[np.float64],
spectrum: NDArray[np.complex128],
amplitude: NDArray[np.float64],
power: NDArray[np.float64],
bin_spacing: float,
resolution_bandwidth: float,
window: str,
n_points: int,
)

Narrow-band zoom spectrum on a fine frequency grid (B&P 11.5.4).

Attributes

NameDescription
frequenciesZoom frequency grid from f_min to f_max inclusive, in Hz.
spectrumComplex amplitude-calibrated coefficients: a sine of peak amplitude A on an analysis frequency reads |spectrum| = A (calibration 2·X(f)/Σw; no one-sided doubling at exactly 0 Hz or the Nyquist frequency).
amplitude|spectrum| - peak-amplitude spectrum.
powerMean-square spectrum amplitude²/2 (amplitude² at DC/Nyquist), consistent with the 'spectrum' scaling of the Welch module: a tone reads its mean square A²/2.
bin_spacingGrid spacing, in Hz - freely chosen, finer than fs/N if requested (the zoom gain of Eq. 11.127).
resolution_bandwidthEffective noise bandwidth Bₑ = fs·Σw²/(Σw)² of the tapered record, in Hz - the true resolution, set by the record length and taper, that no grid refinement improves.
windowTaper name.
n_pointsNumber of grid points.
ZoomFFTResult.plot(
ax: Axes | None = None,
*,
language: str = 'en',
**kwargs: Any,
) -> Axes

Plot the zoom power spectrum in dB over the zoom band.

Parameters

NameDescription
languageLabel language, "en" (default) or "es".
Created and maintained by· GitHub· PyPI· All projects