Skip to content

Analysis Module

Analysis functions for frequency domain analysis, transfer functions, and modal analysis.

FFT Analysis

calculate_fft

calculate_fft(time_data, time_range=None, window=None)

Provenance: the result is stamped with source_signature (a hash of the source samples, see pydvma._signature) and source_settings — the knobs of this call, with time_range recorded as the EFFECTIVE range used (the whole record when the argument was None).

Parameters:

  • time_data (<TimeData> object) –

    time series data

  • time_range (list or ndarray, default: None ) –

    2x1 numpy array to specify data segment to use

  • window (str, default: None ) –

    window function name (e.g., 'hann', 'hamming', 'blackman'), or None for rectangular (boxcar) window

Transfer Functions

calculate_tf

calculate_tf(time_data, ch_in=0, time_range=None, window=None, N_frames=1, overlap=0.5)

Transfer function of one capture (H1 estimator).

Provenance: the result is stamped with source_signature (a hash of the source samples, see pydvma._signature) and source_settings — the knobs of this call, with time_range recorded as the EFFECTIVE range used (the whole record when the argument was None).

Parameters:

  • time_data (<TimeData> object) –

    time series data

  • ch_in (int, default: 0 ) –

    index of input channel

  • time_range (list or ndarray, default: None ) –

    2x1 numpy array to specify data segment to use

  • window (None or str, default: None ) –

    apply filter to data before fft or not

  • N_frames (int, default: 1 ) –

    number of frames to average over

  • overlap (float, default: 0.5 ) –

    frame overlap fraction between 0 and 1

calculate_tf_averaged

calculate_tf_averaged(time_data_list, ch_in=0, time_range=None, window=None)

Calculates transfer function averaged across an ensemble of separate measurements. Note that this expects a object.

Takes each time series as an independent measurement: the cross-spectra are averaged across the ensemble, then the H1 estimator Pxy[ch_in, ch_out] / Pxy[ch_in, ch_in] is formed — the same phase convention as calculate_tf.

Intended for averaged transfer functions from separate measurements, e.g. impulse hammer tests.

Does not average data across sub-frames.

Provenance: the result is stamped with source_signature (hashing every source's samples, concatenated in list order) and source_settings. That settings snapshot records time_range as PASSED — None meaning "the whole of each record" — whereas calculate_tf records the effective range it resolved, because an ensemble of records has no single effective range.

Parameters:

  • time_data_list (<TimeDataList> object) –

    a list of time series data

  • ch_in (int, default: 0 ) –

    index of input channel

  • time_range (list or ndarray, default: None ) –

    2x1 numpy array to specify data segment to use

  • window (None or str, default: None ) –

    type of window to use, default is None.

calculate_bla

calculate_bla(time_data_list, run_spec)

Best Linear Approximation with noise/nonlinearity separation (Schoukens random-phase multisine method, SISO and MISO unified).

Consumes the M x n_exc captures of a BLA run (ordering [(m, e) for m in range(M) for e in range(n_exc)]), slices exact N-sample periods after discarding t_periods transients, DFTs each period (no window — the data is periodic by construction) and estimates, per response channel: the BLA frequency-response matrix (mean over realisations of the per-realisation n_exc x n_exc solve), the noise standard deviation bla_sigma_n (period-to- period scatter propagated through the input-matrix inverse, with the 1/P of the period average folded in) and the nonlinear-distortion standard deviation bla_sigma_nl (realisation scatter minus that noise variance, floored at zero).

Only the excited bins are analysed, so the returned frequency axis is k * fs / N over k1..k2 — a sparse, exactly-on-bin axis, not the full rfft grid.

Start-time offsets between captures are harmless for measured x: x and y share the ADC clock, so the common phase rotation exp(-j*w*tau) cancels in the solve. For x_mode='commanded' the input spectra are instead regenerated analytically from the seed, using the same phase and scaling law as acquisition.multisine_generator; that is only valid when AO and AI are hardware-synced (the caller enforces that), because otherwise per-capture start jitter leaks into the realisation scatter and corrupts sigma_NL.

Unlike the other ensemble entry points this takes a plain list, not a or a method: the captures carry no record of their own (m, e) indices, so the ORDER of the list is load-bearing input, and a DataSet — an unordered bag the user can add to, reorder or partly delete — cannot express it. The caller assembles the list in run order; a wrong length is caught here, a wrong order is not.

Parameters:

  • time_data_list (list) –

    The M * n_exc captures of the run, in (m, e) order — realisation outer, experiment inner.

  • run_spec (dict) –

    BlaRunSpec — multisine (a dict of n_samples, k1, k2, p_periods, t_periods, seed, amp_rms, n_exc, M), x_mode ('measured' or 'commanded'), x_channels (per-excitation input channel indices, or None for commanded x), resp_channels (response channel indices) and fs (the actual capture rate in Hz).

Returns a of n_exc objects, one per excitation, each holding every response channel as a column of tf_data and carrying bla_sigma_nl, bla_sigma_n and the bla run-spec dict. tf_coherence is None: coherence is not the right quality measure here — the sigma pair is. Both sigmas are PER-REALISATION standard deviations in linear FRF units — the distortion and noise level of a single realisation, not the error bar on the returned BLA, which is sqrt(M) smaller (sigma_BLA = sigma_tot / sqrt(M)).

Cross-Spectrum Analysis

calculate_cross_spectrum_matrix

calculate_cross_spectrum_matrix(time_data, time_range=None, window=None, N_frames=1, overlap=0.5)

Compute the full cross-spectrum matrix and coherence matrix of a multi-channel TimeData block using Welch's method.

Equivalent to looping scipy.signal.csd (with scaling='spectrum') and scipy.signal.coherence over every channel pair, but vectorised: each segment is FFT'd once across all channels, and the cross-spectrum matrix is formed as a tensor outer product conj(X[:,f,i]) * X[:,f,j] averaged over segments. Output is byte-equivalent to the scipy reference to within FFT round-off.

The DC bin (and, for an even segment length, the Nyquist bin) of the coherence matrix is undefined under a boxcar window: constant detrending zeroes each segment's mean, so the auto-spectra there are pure round-off and Cxy is a 0/0 ratio. It is returned as 0 in that degenerate case rather than NaN; treat it as "no information at DC", not a real coherence.

Memory: the per-segment windows are built with as_strided directly at the final (N_chans, N_seg, nperseg) shape rather than via sliding_window_view + slicing. The latter materialises an intermediate whose nominal size is N_chans * (N_samples - nperseg + 1) * nperseg; on a 32-bit build (pyodide/WASM, npy_intp = int32) numpy rejects that view with "array is too big" for a large nperseg on a long, high-rate record, even though it is only a view. The direct stride keeps the nominal size at N_chans * N_seg * nperseg and is numerically byte-identical.

Parameters:

  • time_data (<TimeData> object) –

    time series data

  • time_range (list or ndarray, default: None ) –

    2x1 numpy array to specify data segment to use

  • window (None or str, default: None ) –

    window function name; None defaults to 'boxcar'

  • N_frames (int, default: 1 ) –

    number of frames to average over

  • overlap (float, default: 0.5 ) –

    frame overlap fraction between 0 and 1

calculate_cross_spectra_averaged

calculate_cross_spectra_averaged(time_data_list, time_range=None, window=None)

Calculates cross spectra averaged across ensemble of time_data_list. Note that this expects a of objects.

Takes each time series as an independent measurement.

Intended for averaged transfer functions from separate measurements, e.g. impulse hammer tests.

Does not average data across sub-frames.

Parameters:

  • time_data_list (<TimeDataList> object) –

    a list of time series data

  • time_range (list or ndarray, default: None ) –

    2x1 numpy array to specify data segment to use

  • window (None or str, default: None ) –

    type of window to use, default is None.

Time-Frequency Analysis

calculate_sonogram

calculate_sonogram(time_data, nperseg=None, noverlap=None)

Calculates a complex STFT spectrogram (sonogram) for every channel of a object using a Hann window, and returns a .

Provenance: the result is stamped with source_signature (a hash of the source samples, see pydvma._signature) and source_settings{'calc': 'sonogram', 'method': 'stft', ...} with the EFFECTIVE nperseg / noverlap recorded (the values derived from the record length when either argument was None).

Channel calibration factors and units are copied from the source, and id_link is set to the source's unique_id (same provenance convention as the other calculate_* functions).

The segmentation is done by _spectrogram_complex_lowmem rather than scipy.signal.spectrogram directly: scipy's internal sliding_window_view builds a huge NOMINAL intermediate that the 32-bit WASM/pyodide engine rejects with "array is too big" for a large nperseg on a long, high-rate record. The low-memory helper strides directly to the decimated windows and is byte-identical to scipy (pinned in the test suite). See that helper's docstring for the full rationale.

Parameters:

  • time_data (<TimeData> object) –

    time series data

  • nperseg (int, default: None ) –

    STFT segment length; defaults to ~1/50th of the time series so roughly 50 segments span the data

  • noverlap (int, default: None ) –

    overlap between segments, default nperseg // 2

calculate_damping_from_sono

calculate_damping_from_sono(time_data, n_chan=1, nperseg=None, start_time=None, peak_threshold=None)

Calculate damping from an STFT sonogram.

Computes a Hann-window sonogram (noverlap=0) and fits the free-decay of each detected band. See _fit_modes_from_image for the fit core (this is the phase_has_carrier=False / STFT case) and calculate_damping_from_cwt for the wavelet alternative.

Parameters:

  • time_data (<TimeData> object) –

    time series data

  • n_chan (int, default: 1 ) –

    channel index to analyze, default is 1

  • nperseg (int, default: None ) –

    number of samples per segment for spectrogram

  • start_time (float, default: None ) –

    start time (seconds) for analysis; None infers it from the pretrigger (see _resolve_damping_start_slice)

  • peak_threshold (float, default: None ) –

    normalised peak-picking threshold in 0..1 (fraction of the start-slice magnitude's min→max range, as peakutils.indexes interprets it); None keeps the automatic 10 * median / max choice (see _fit_modes_from_image)

Returns:

  • fn ( ndarray ) –

    array of natural frequencies (Hz)

  • Qn ( ndarray ) –

    array of Q factors (1/(2*zeta))

  • fit_data ( dict ) –

    dict containing data needed for plotting the fits: - 't': time axis - 'fits': list of dicts, each with keys: - 't_fit': time values for the fit region - 'real_fit': fitted real part values - 'real_data': actual real part data values - 'f_peak': peak frequency (Hz) - 'Qn': Q factor for this mode - 'time_slice' / 'start_time': the fit-start frame index and its time (s) — the resolved free-decay start - 'threshold': the normalised peak threshold actually used - 'slice_freq' / 'slice_mag': the start-slice magnitude spectrum the peak picker scanned - 'peaks_freq' / 'peaks_mag': the candidate peaks it detected (before any per-peak fit failures are dropped)

Signal Processing

multiply_by_power_of_iw

multiply_by_power_of_iw(data, power, channel_list)

clean_impulse

clean_impulse(time_data, ch_impulse=0)

Sets all data outside of impulse to zero.

Pulse width is estimated by assuming half cosine impulse, using width of half peak amplitude.

Data before peak is unchanged. Data after estimated end of impulse is ramped to zero using half cosine pulse of width 10x estimated pulse width.

best_match

best_match(tf_data_list, freq_range=None, set_ref=0, ch_ref=0)

Parameters:

  • tf_data_list (<TfDataList> object) –

    transfer function data

  • freq_range (list or ndarray, default: None ) –

    2x1 numpy array to specify data segment to use

  • set_ref (int, default: 0 ) –

    reference set index, default is 0

  • ch_ref (int, default: 0 ) –

    reference channel index, default is 0

Helper Functions

func_real

func_real(t, A, B, N)

func_imag

func_imag(t, W, C)