← Back to Autonomy

Time-series & spectral analysis · vibration-based condition monitoring

Listening to the Machine

A field guide to the indicators that turn a raw vibration signal into a diagnosis: its correlation structure, its frequency content, its impulsiveness, and where in time a transient lives.

A turning machine writes its condition into its vibration. The art is reading it: pulling a fault's signature out of noise and other healthy motion, and tying it to the kinematics so a peak becomes a named defect. The indicators below organize by the question they answer:

Question 1
What structure?

What periodicity and memory does the signal carry, and what model order describes it?

Question 2
Which frequencies?

Which lines and bands hold the energy, and which match a shaft, mesh, or bearing defect rate?

Question 3
How impulsive, and when?

Is the signal impulsive or nonlinear, and where in time does a transient occur?

Each card gives the idea in plain words, a system-health example, a figure, and runnable Python. Five live explorers, a worked case study on a bearing fault, and a closing "going further" set, automatic band selection, order tracking, synchronous averaging, and the cyclic spectrum, round it out. A decision map keys every method back to the question it answers.

Interactive explorers

Three live tools. Shape an AR process and watch the PACF read off its order, sweep a bearing defect frequency through noise and watch the envelope spectrum recover it, or add impacts and watch kurtosis climb.

A

ACF and PACF of an AR process

correlation

Set the two AR coefficients and watch the ACF decay while the PACF cuts off after lag 2, the rule that reads model order off the data.

B

Envelope analysis of a bearing fault

spectral

Impulsive bursts ring a resonance at the defect rate. Sweep the defect frequency and the noise, and watch the envelope spectrum recover the line and its harmonics, or lose it in noise.

C

Impulsiveness and kurtosis

higher-order

Add sharp impacts to a near-Gaussian signal and watch the kurtosis and crest factor climb past their healthy values, the early sign of a bearing or gear defect.

Correlation structure, what memory does the signal carry?

Before the frequency domain, the autocorrelation says how a signal relates to its own past. That structure reveals periodicity, sets the order of a model, and is the basis of the whiteness checks that model-based detectors rely on.

01

Autocorrelation function (ACF)

correlation

The correlation of a signal with lagged copies of itself, lag by lag, with a white-noise band. It exposes periodicity and memory; for a moving-average process it cuts off at the order, and a slowly decaying ACF means strong serial dependence.

In plain words

The autocorrelation function asks, for each lag, how much a sample looks like the sample that many steps earlier. A pure random signal correlates with nothing but itself at lag zero, so every other bar sits inside a narrow band around zero. Real signals carry memory: a rotating machine repeats, so its ACF shows peaks at the rotation period and its multiples, and a damped resonance shows a decaying oscillation. The shape is diagnostic, a sharp cutoff after a few lags points to a moving-average structure, a slow decay to a long memory, and any bar that clears the band is correlation worth modeling.

Health-management example

A gearbox vibration signal repeats once per shaft revolution. Its autocorrelation shows a clear peak at the shaft period and smaller peaks at the harmonics, confirming the dominant periodicity before any spectrum is computed and giving the lag at which to look for sidebands.

2026-06-20T00:26:26.459127 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
import numpy as np

def acf(x, nlags=40):
    x = np.asarray(x, float) - np.mean(x); n = len(x)
    c0 = x @ x / n
    r = [ (x[:n-k] @ x[k:]) / n / c0 for k in range(nlags+1) ]
    band = 1.96 / np.sqrt(n)          # white-noise 95% band
    return np.array(r), band

r, band = acf(vibration, 40)
Practical note

Remove the mean and any trend before computing, or low-frequency drift dominates the early lags. Read the band as a guide, not a verdict, since at many lags a few bars clear it by chance. For a fault indicator, trend the autocorrelation at the kinematic lag rather than scanning all lags each time.

Box, Jenkins & Reinsel (2008), Time Series Analysis; Brockwell & Davis (1991), Time Series.

02

Partial autocorrelation and order

correlation

The autocorrelation at a lag after removing the effect of all shorter lags. A sharp cutoff after lag p is the fingerprint of an order-p autoregressive structure, and an information criterion confirms the order.

In plain words

A plain autocorrelation at lag three is inflated by the chain of correlations running through lags one and two. The partial autocorrelation strips those out, reporting only the direct relationship at each lag, computed efficiently by the Durbin-Levinson recursion. That makes it the natural order-selection tool: an autoregressive process of order p has a partial autocorrelation that is large up to lag p and then drops inside the band. To confirm the choice rather than read it by eye, fit autoregressive models of increasing order and score them with an information criterion, where AIC is efficient but tends to over-fit and BIC is parsimonious, taking the order at the minimum.

Health-management example

A bearing housing behaves like a lightly damped resonator driven by noise, well described by a low-order autoregressive model. The partial autocorrelation clears the band only at the first two lags, and BIC bottoms out at order two, so a compact AR(2) captures the dynamics and its residual becomes the input to a whiteness-based detector.

2026-06-20T00:26:26.603261 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
import numpy as np

def pacf(x, nlags=40):
    x = np.asarray(x, float) - np.mean(x); n = len(x)
    c0 = x @ x / n
    r = np.array([(x[:n-k] @ x[k:])/n/c0 for k in range(nlags+1)])
    phi = np.zeros((nlags+1, nlags+1)); pac = np.zeros(nlags+1)
    phi[1,1] = r[1]; pac[1] = r[1]
    for k in range(2, nlags+1):
        num = r[k] - sum(phi[k-1,j]*r[k-j] for j in range(1,k))
        den = 1 - sum(phi[k-1,j]*r[j] for j in range(1,k))
        phi[k,k] = num/den
        for j in range(1,k):
            phi[k,j] = phi[k-1,j] - phi[k,k]*phi[k-1,k-j]
        pac[k] = phi[k,k]
    return pac[1:], 1.96/np.sqrt(n)
Practical note

The partial autocorrelation is for autoregressive order, the autocorrelation for moving-average order; use them together to read a mixed model. Confirm the visual cutoff with an information criterion, preferring BIC for a parsimonious order, and remember both assume a stationary series, so analyze within one operating regime.

Box, Jenkins & Reinsel (2008), Time Series Analysis; Akaike (1974), IEEE TAC 19; Schwarz (1978), Ann. Statist. 6.

Frequency domain, which lines and bands carry the fault?

A fault advertises itself at a frequency tied to the machine kinematics: a shaft rate, a gear-mesh frequency, a bearing defect rate. The frequency-domain tools move those signatures into view, including the ones, like a bearing rate, that hide as modulation rather than a line.

03

Power spectral density (Welch)

spectral

The distribution of power over frequency, estimated by averaging the periodograms of overlapping windowed segments for a smooth, low-variance result. The basic frequency-domain condition indicator.

In plain words

A single Fourier transform of a noisy signal gives a spectrum so erratic that real peaks are hard to separate from scatter. Welch's method tames that by splitting the record into overlapping segments, windowing each to control leakage, transforming them, and averaging the resulting power spectra. The price is coarser frequency resolution; the reward is a stable estimate in which a genuine line stands clear of the floor. In condition monitoring the lines sit at known kinematic frequencies, so a new line, a rising harmonic, or a growing band is read directly as a developing fault.

Health-management example

An induction motor develops a broken rotor bar, which plants sidebands around the supply frequency at twice the slip frequency. Averaged over many segments, the Welch spectrum resolves those small sidebands from the noise floor, where a raw periodogram would bury them in variance.

2026-06-20T00:26:26.863933 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
from scipy import signal

def psd_welch(x, fs, nperseg=1024):
    f, Pxx = signal.welch(x, fs=fs, nperseg=nperseg)
    return f, Pxx

f, Pxx = psd_welch(vibration, fs=12000, nperseg=1024)
# inspect Pxx at kinematic frequencies (shaft, mesh, sidebands)
Practical note

The segment length trades resolution against variance: longer segments resolve close lines but average fewer, so the estimate is noisier. Always window to control leakage, and read peaks at the machine's kinematic frequencies rather than hunting blindly. Express axes in orders of shaft speed when the speed varies, so a line does not smear.

Welch (1967), IEEE Trans. Audio Electroacoust. 15; Oppenheim & Schafer (2010), Discrete-Time Signal Processing.

04

Cross-spectrum and coherence

spectral

The frequency-by-frequency linear relationship between two signals. Coherence near one at a frequency means a shared source, which helps trace a fault from where it is sensed back to where it originates.

In plain words

A single sensor cannot say whether two peaks at the same frequency on two channels come from one source or two coincidences. The cross-spectrum and its normalized form, the magnitude-squared coherence, answer that: coherence runs between zero and one and measures how consistently the phase between the channels holds at each frequency. A value near one means the two are linearly linked there, sharing a common source or a transmission path; a value near zero means they are independent. It is the tool for path analysis, deciding which of several measurement points actually drives a resonance and which merely feel it.

Health-management example

Two accelerometers on a pump both show a peak near a structural resonance. High coherence between them at that frequency confirms a single shared excitation traveling through the structure, rather than two local sources, focusing the search for the root cause on the common path.

2026-06-20T00:26:26.979904 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
from scipy import signal
import numpy as np

def coherence_xy(x, y, fs, nperseg=1024):
    f, Pxy = signal.csd(x, y, fs=fs, nperseg=nperseg)
    fc, Cxy = signal.coherence(x, y, fs=fs, nperseg=nperseg)
    return f, np.abs(Pxy), Cxy

f, csd_mag, coh = coherence_xy(ch1, ch2, fs=12000)
Practical note

Coherence needs averaging over several segments to be meaningful; a single segment gives a coherence of one everywhere and tells you nothing. A low coherence can mean independence or simply too little signal-to-noise, so check the cross-power magnitude alongside it, and remember coherence finds linear association, not causation or direction.

Bendat & Piersol (2010), Random Data; Carter (1987), Proc. IEEE 75.

05

Cepstrum

spectral

The inverse transform of the log spectrum. By turning an evenly spaced family of harmonics or sidebands into a single peak at their spacing, it isolates periodic structure within the spectrum itself, the signature of gear sidebands and echoes.

In plain words

Some faults do not add a single line but a whole comb: a gear with a local defect modulates the mesh frequency, planting a family of evenly spaced sidebands around it. Reading the spacing off a crowded spectrum is hard. The cepstrum takes the logarithm of the spectrum and transforms it again, so a regularly spaced family of peaks in frequency becomes one peak in the new axis, called quefrency, located at the reciprocal of the spacing. It collapses a sideband family or a repeated echo into a single, easy-to-track number, which is why it is a staple of gearbox diagnostics.

Health-management example

A cracked gear tooth modulates the mesh frequency, scattering sidebands at the shaft rate across the spectrum. The cepstrum gathers that whole family into one rahmonic at the shaft period, turning a smear of small sidebands into a single peak that can be trended over time.

2026-06-20T00:26:27.057385 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
import numpy as np

def real_cepstrum(x):
    spec = np.fft.rfft(x * np.hanning(len(x)))
    logmag = np.log(np.abs(spec) + 1e-12)
    ceps = np.fft.irfft(logmag)
    return ceps                      # peak at quefrency = 1 / spacing

ceps = real_cepstrum(vibration)
# a peak at quefrency q means a spectral spacing of fs/len * (1/q)
Practical note

Window before transforming and work with the log magnitude, since the log is what turns a multiplicative harmonic family into an additive peak. Strong single tones and the overall spectral shape produce low-quefrency content that can mask the rahmonics you want, so lifter (filter in quefrency) to suppress them. A cepstral peak gives the spacing, not which sideband family it belongs to.

Bogert, Healy & Tukey (1963), Proc. Symp. Time Series; Randall (2017), Mech. Syst. Signal Process. 97.

06

Envelope analysis (Hilbert)

spectral

The workhorse of bearing diagnostics. A localized defect rings the structure once per impact, so the fault rate appears as the modulation of a high resonance, not as a line. Band-passing the resonance and transforming its Hilbert envelope brings the defect frequency into plain view.

In plain words

A spalled bearing does not hum at its defect frequency; it strikes, and each strike rings a high structural resonance that decays before the next. The defect rate is therefore the rate at which the resonance is amplitude-modulated, invisible as a line in the ordinary spectrum, which only shows the resonance. Envelope analysis recovers it: band-pass the signal around the resonance to isolate the rung region, take the magnitude of its analytic signal with the Hilbert transform to get the envelope that traces the impacts, and transform that envelope. The bearing defect frequencies, computed from the geometry as the outer-race, inner-race, ball, and cage rates, then appear as clear lines and harmonics.

Health-management example

An outer-race spall on a fan bearing produces impacts at the outer-race defect frequency, each ringing a resonance near three kilohertz. The raw spectrum shows only the resonance hump; band-passing it and taking the envelope spectrum reveals a sharp line at the outer-race frequency and its harmonics, identifying the fault and its location.

2026-06-20T00:26:27.184213 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
import numpy as np
from scipy import signal

def envelope_spectrum(x, fs, band):
    b, a = signal.butter(4, [band[0]/(fs/2), band[1]/(fs/2)], "band")
    xb = signal.filtfilt(b, a, x)
    env = np.abs(signal.hilbert(xb)); env -= env.mean()
    f = np.fft.rfftfreq(len(env), 1/fs)
    return f, np.abs(np.fft.rfft(env * np.hanning(len(env))))

def bearing_frequencies(fr, n, d, D, phi=0.0):
    r = (d/D)*np.cos(phi)
    return dict(BPFO=0.5*n*fr*(1-r), BPFI=0.5*n*fr*(1+r),
                BSF=(D/(2*d))*fr*(1-r**2), FTF=0.5*fr*(1-r))
Practical note

Everything depends on choosing the right resonance band to demodulate; pick it where the impulsiveness is highest, which a spectral-kurtosis or kurtogram scan finds automatically. Compute the defect frequencies from the actual geometry and shaft speed, and expect the measured lines to sit a percent or two off because of slip. Inner-race and ball faults are modulated by shaft and cage rotation, adding sidebands, so look for families, not single lines.

Randall & Antoni (2011), Mech. Syst. Signal Process. 25; McFadden & Smith (1984), J. Sound Vib. 96.

Higher-order statistics, how impulsive and how nonlinear?

The mean and variance describe a Gaussian signal completely; a faulty one is rarely Gaussian. Higher moments measure the impulsiveness that bearing and gear defects create, and the third-order spectrum measures the nonlinear coupling they introduce.

07

Kurtosis, skewness, crest factor

higher-order

Scalar shape indicators of the amplitude distribution. Kurtosis and crest factor climb sharply when a defect makes a signal impulsive, often well before RMS energy moves, which makes them sensitive early indicators of bearing and gear faults.

In plain words

A healthy machine's vibration is close to Gaussian, and its kurtosis, the normalized fourth moment, sits near three. A developing bearing or gear defect adds brief, sharp impacts that barely raise the overall energy but fatten the tails of the amplitude distribution, so kurtosis rises well above three and the crest factor, the ratio of peak to RMS, grows with it. Skewness, the third moment, reports asymmetry, useful when a fault pushes the signal preferentially one way. These are single numbers computed straight from the waveform, cheap to trend, and they react to impulsive faults earlier than energy-based indicators like RMS, which a few short spikes hardly change.

Health-management example

A bearing in early spalling produces occasional sharp impacts in an otherwise normal signal. The RMS is almost unchanged, so an energy threshold stays silent, but the kurtosis jumps from about three to six and the crest factor rises, flagging the impulsive defect at an early stage.

2026-06-20T00:26:27.393162 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
import numpy as np
from scipy import stats

def time_stats(x):
    x = np.asarray(x, float)
    rms = np.sqrt(np.mean(x**2)); peak = np.max(np.abs(x))
    return dict(rms=rms, peak=peak, crest=peak/rms,
                kurtosis=stats.kurtosis(x, fisher=False),  # normal = 3
                skewness=stats.skew(x))

s = time_stats(vibration)   # trend s['kurtosis'] and s['crest']
Practical note

Kurtosis is sensitive but not specific and is easily corrupted by a single outlier or electrical spike, so clean the signal and confirm with the envelope spectrum before calling a fault. It also saturates: once a fault is well developed and impacts are frequent, kurtosis can fall back toward three even as damage grows, so trend it alongside RMS rather than alone.

Dyer & Stewart (1978), J. Mech. Design 100; Antoni (2006), Mech. Syst. Signal Process. 20 (spectral kurtosis).

08

Bispectrum

higher-order

The third-order spectrum, nonzero only when frequency components are quadratically phase-coupled. Where the power spectrum treats two tones and their sum as independent, the bispectrum reveals whether they share a phase relationship, a fingerprint of nonlinear faults.

In plain words

The power spectrum discards phase, so it cannot tell whether a component at the sum of two frequencies arose independently or was generated by a nonlinear interaction between them. The bispectrum keeps that information: it correlates the phases of three frequencies, one being the sum of the other two, and is large only when they are phase-locked, the signature of quadratic coupling. Many mechanical and electrical faults are nonlinear, looseness, rub, and certain electrical asymmetries generate exactly this kind of coupling, so a bispectral peak at a pair of fault-related frequencies is evidence of a nonlinear mechanism that a linear spectrum would miss.

Health-management example

A loose mechanical joint rectifies and mixes two running frequencies, generating a phase-coupled component at their sum. The power spectrum shows three separate lines with no hint they are related; the bispectrum lights up at the corresponding frequency pair, revealing the nonlinear looseness behind them.

2026-06-20T00:26:27.584096 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
import numpy as np

def bispectrum(x, nfft=128, noverlap=None):
    x = np.asarray(x, float); noverlap = noverlap or nfft//2
    step = nfft - noverlap; win = np.hanning(nfft); half = nfft//2
    B = np.zeros((half, half), complex); count = 0
    for s in range(0, len(x)-nfft+1, step):
        X = np.fft.fft((x[s:s+nfft]-x[s:s+nfft].mean())*win)
        for i in range(half):
            for j in range(half-i):
                B[i,j] += X[i]*X[j]*np.conj(X[i+j])
        count += 1
    return np.abs(B/max(count,1))
Practical note

The bispectrum needs many segments to average down its variance and is expensive to compute, so restrict the frequency grid to the region of interest. It detects quadratic phase coupling specifically, not every nonlinearity, and a peak shows association, not mechanism, so pair it with a physical hypothesis about what could couple those frequencies.

Nikias & Mendel (1993), IEEE Signal Process. Mag. 10; Collis, White & Hammond (1998), Mech. Syst. Signal Process. 12.

Time-frequency, where in time does the fault live?

A Fourier transform assumes the spectrum holds for the whole record. A fault transient does not: it happens at a moment. Time-frequency transforms map how the spectrum evolves, localizing a transient in both time and frequency.

09

STFT spectrogram

time-frequency

The short-time Fourier transform: slide a window along the signal and transform each piece, mapping how the spectrum evolves in time. The first tool for a non-stationary signal where a fault lives at a particular time and frequency.

In plain words

The ordinary spectrum collapses the whole record into one frequency picture, which is wrong the moment the signal changes in time, during a run-up, a load change, or a transient fault. The short-time Fourier transform restores time by sliding a window along the signal and transforming each windowed piece, producing a spectrogram, a map of frequency against time. Its one compromise is fixed resolution: a short window localizes events in time but blurs frequency, a long window does the reverse, and you must choose. Within that limit it is the standard way to see a frequency sweep during start-up or a fault that appears only under certain conditions.

Health-management example

A variable-speed drive is run up from rest while vibration is recorded. The spectrogram shows the order lines sweeping upward with shaft speed and a resonance band staying fixed, so the speed at which an order crosses the resonance, the critical speed, is read directly off the time-frequency map.

2026-06-20T00:26:27.678739 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
from scipy import signal

def spectrogram_stft(x, fs, nperseg=256, noverlap=None):
    noverlap = noverlap or nperseg*3//4
    f, t, S = signal.spectrogram(x, fs=fs, nperseg=nperseg,
                                 noverlap=noverlap)
    return f, t, S          # short window: time-sharp; long: freq-sharp

f, t, S = spectrogram_stft(runup, fs=12000, nperseg=256)
Practical note

The window length is the whole trade-off: short for sharp timing of transients, long for resolving close frequencies, and you cannot have both at once. Overlap the windows to smooth the time axis, window each segment to limit leakage, and for rotating machinery order-track against speed so a run-up sweep does not blur a fixed resonance.

Allen & Rabiner (1977), Proc. IEEE 65; Cohen (1995), Time-Frequency Analysis.

10

Wavelet transform (CWT)

time-frequency

The continuous wavelet transform with a Morlet wavelet. Unlike the fixed window of the STFT, the wavelet narrows at high frequency and widens at low, so it localizes a short broadband transient in time while still resolving slow components. The right lens for impulsive, non-stationary faults.

In plain words

The short-time Fourier transform must use one window length for the whole signal, which is a poor fit when a record holds both slow oscillations and sharp impacts. The wavelet transform adapts: it correlates the signal with scaled copies of a single prototype wave, the Morlet wavelet being a common choice, so high-frequency analysis uses short wavelets that pinpoint impacts in time and low-frequency analysis uses long ones that resolve slow components in frequency. The result, a scalogram, gives sharp time resolution exactly where impulsive faults need it. It is the natural tool for bearing impacts, gear transients, and any fault whose signature is a brief, broadband burst.

Health-management example

A chipped gear tooth produces a brief impact once per revolution, smeared across frequency. On a fixed-window spectrogram each impact is blurred; the wavelet scalogram resolves each one as a sharp vertical ridge at its instant, so the impacts can be counted and timed against the shaft angle to locate the damaged tooth.

2026-06-20T00:26:27.918151 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
import numpy as np

def cwt_morlet(x, fs, freqs, w0=6.0):
    x = np.asarray(x, float); n = len(x); X = np.fft.fft(x)
    omega = 2*np.pi*np.fft.fftfreq(n, 1/fs)
    scalo = np.zeros((len(freqs), n))
    for k, fc in enumerate(freqs):
        s = w0/(2*np.pi*fc)
        psi = np.pi**-0.25 * np.sqrt(2) * (omega>0) * \
              np.exp(-0.5*(s*omega - w0)**2)
        scalo[k] = np.abs(np.fft.ifft(X * psi))
    return scalo
Practical note

Choose the analysis frequencies and the wavelet's bandwidth from the transient you expect; a higher Morlet center frequency sharpens frequency at the cost of time. The continuous transform is redundant and can be heavy, so limit the frequency range. Mind edge effects at the start and end of the record, where the wavelet runs off the data and the scalogram is unreliable.

Mallat (2008), A Wavelet Tour of Signal Processing; Peng & Chu (2004), Mech. Syst. Signal Process. 18.

Decision map

Start from the question you actually have. The badge marks the domain: correlation, spectral, higher-order, or time-frequency.

If you are asking...Reach forDomain
What is the correlation structure or AR order?ACF / PACFcorrelation
Which frequencies carry the energy?Welch PSDspectral
Do two channels share a source or path?cross-spectrum / coherencespectral
Is there a harmonic or sideband family?cepstrumspectral
What is a rolling-element bearing's defect rate?envelope analysisspectral
Is the signal impulsive (early bearing/gear)?kurtosis / crest factorhigher-order
Are frequencies nonlinearly phase-coupled?bispectrumhigher-order
How does the spectrum evolve during a run-up?STFT spectrogramtime-frequency
Where is a short broadband transient?wavelet (CWT)time-frequency

Choosing a condition indicator

is the signal stationary? yes no (transient) stationary: what are you after? transient: what kind? structure ACF / PACF spectrum Welch PSD impulsive kurtosis periodic which? close lines STFT sharp impacts wavelet envelope (bearing) cepstrum (gear) bispectrum Then read at the kinematics: compute shaft, mesh, and bearing defect frequencies from geometry and speed, then look there.

A working order for vibration diagnostics

  1. Detrend and window the record, and compute the kinematic frequencies from geometry and shaft speed.
  2. Screen the waveform with kurtosis and crest factor for impulsiveness, cheaper and earlier than energy.
  3. Take a Welch PSD to see the lines and locate any resonance.
  4. For a bearing, choose the resonance band by the kurtogram and run envelope analysis to read the defect rate.
  5. For a gear, synchronously average, then use the cepstrum and the residual gear indicators.
  6. If the signal is non-stationary, order-track a run-up or use the wavelet for short transients.
# common stack
pip install numpy scipy
# acf, pacf, psd_welch, envelope_spectrum, time_stats, fast_kurtogram, order_track, time_synchronous_average, cyclic_modulation_spectrum ship in the kit engine

Rules of thumb

Heuristics that hold across most vibration-based condition monitoring, before the method-specific detail.

  1. Detrend and window before any spectral estimate. Low-frequency drift and edge discontinuities leak across the spectrum and masquerade as faults.
  2. Read peaks at the machine's kinematic frequencies, not blindly. Compute the shaft, mesh, and bearing defect frequencies from the geometry and speed first, then look there.
  3. A bearing rate is a modulation, not a line. Use envelope analysis, not the raw spectrum, to find outer-race, inner-race, ball, and cage defect frequencies.
  4. Choose the envelope band where impulsiveness is highest. A spectral-kurtosis or kurtogram scan picks the resonance to demodulate better than a guess.
  5. Kurtosis and crest factor catch impulsive faults early but saturate. Trend them alongside RMS, since kurtosis can fall back toward three as a fault matures.
  6. Match the transform to stationarity. PSD for a steady regime, STFT for a run-up, the wavelet for short broadband transients; a Fourier spectrum of a changing signal is misleading.
  7. Resolution is a trade-off you must choose. Longer windows resolve close frequencies, shorter ones localize transients; there is no setting that does both.
  8. Express frequency in orders when speed varies. Order-tracking against shaft speed keeps a kinematic line sharp instead of smearing it across a run-up.
Pick by scenario
ScenarioPick & whyDomain
Find the dominant periodicity and model orderACF / PACFcorrelation structure and AR ordercorrelation
Spot a new line or rising harmonicWelch PSDsmooth, low-variance spectrumspectral
Decide if two sensors share a sourcecoherencelinear association per frequencyspectral
Track a gear sideband familycepstrumcollapses the family to one peakspectral
Find a rolling-bearing defect frequencyenvelope analysisdemodulates the resonancespectral
Catch an early impulsive bearing faultkurtosis / crest factorimpulsiveness before RMS moveshigher-order
Expose nonlinear frequency couplingbispectrumphase coupling the PSD misseshigher-order
Analyze a variable-speed run-upSTFT spectrogramspectrum evolving in timetime-frequency
Localize a brief broadband transientwavelet (CWT)adaptive time-frequency resolutiontime-frequency

At a glance

The indicators split by domain: correlation structure, frequency content, distribution shape, and time-frequency.

MethodDomainRevealsOutputKey assumption
ACFlagperiodicity, memorycorrelation vs lagstationary
PACFlagAR orderpartial corr + orderstationary
Welch PSDfrequencyspectral lines and bandspower vs frequencystationary in window
Coherencefrequencyshared source between channels0 to 1 vs frequencyneeds averaging
Cepstrumquefrencyharmonic / sideband spacingpeak at spacingperiodic structure
Envelopefrequencybearing defect ratedefect lines + harmonicsresonance band chosen
Kurtosis / crestamplitudeimpulsivenessscalar indicatoroutlier-sensitive
Bispectrumbifrequencyquadratic phase couplingmagnitude on (f1,f2)needs many segments
STFTtime-frequencyspectrum over timespectrogramfixed resolution
Wavelettime-frequencylocalized transientsscalogramadaptive resolution

Worked case study: an outer-race bearing fault

A fan bearing develops an outer-race spall. From its geometry the defect frequencies are BPFO 96.9 Hz, BPFI 143.1 Hz, BSF 75.1 Hz, and cage 12.1 Hz. Every number below is computed by the engine on a 1-second record sampled at 12000 Hz.

Step 1

Flag impulsiveness with scalar indicators

The cheap indicators move first. Kurtosis rises from 2.8 on the healthy signal to 3.8 on the faulty one, and the crest factor from 3.7 to 5.3, while RMS barely changes (0.45 to 0.48). The signal has become impulsive, the hallmark of a developing bearing defect, even though its energy is almost unchanged.

2026-06-20T00:26:27.393162 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
Step 2

Find the resonance, choose the band

The raw Welch spectrum is dominated by the shaft line and a broadband resonance near 3393 Hz, with no line at any bearing rate: the defect is hidden as modulation. Scanning candidate bands by the kurtosis of their envelope picks the band around the resonance, where impulsiveness peaks at a band kurtosis of 6.0, as the one to demodulate.

2026-06-20T00:26:26.863933 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
Step 3

Demodulate, and read the defect frequency

Band-passing that resonance, taking the Hilbert envelope, and transforming it moves the defect rate into plain view: the envelope spectrum peaks at 97.0 Hz, within a percent of the computed BPFO of 96.9 Hz, with harmonics following. The match to the outer-race frequency, rather than the inner-race or ball rate, identifies both the fault and its location on the bearing.

2026-06-20T00:26:27.184213 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
Verdict

What the chain delivered

Scalar higher-order statistics flagged an impulsive change that energy thresholds missed; a spectral-kurtosis scan chose the resonance band; and envelope analysis turned a hidden modulation into a labeled outer-race defect frequency. The reflex order for vibration diagnostics is: screen with kurtosis, locate the resonance, demodulate with the envelope, and read the answer at the kinematic frequencies. The time domain says how impulsive; the right transform says which fault.

Going further

Beyond the core indicators, a set of methods that complete the bearing, run-up, gear, and cyclostationary stories: automatic band selection, angular resampling, synchronous averaging, the cyclic spectrum, and tools for decomposition, deconvolution, and severity. Two more explorers first.

D

Time-synchronous averaging

going further

Average more revolutions and watch the gear mesh and the once-per-rev fault rise out of noise and non-synchronous content, as the noise falls with the square root of the count.

E

Order tracking

going further

Increase the speed variation and watch the raw spectral peak smear while the order-domain peak, after resampling to shaft angle, stays sharp at its order.

Choosing the band: spectral kurtosis and the kurtogram

Envelope analysis only works if you demodulate the right band. The kurtogram automates that choice, the one judgement call the envelope card left open.

Spectral kurtosis and the kurtogram

going further

Spectral kurtosis measures impulsiveness frequency by frequency; the kurtogram scans center frequency and bandwidth to find the single most impulsive band, the optimal one to band-pass before envelope analysis.

In plain words

Envelope analysis depends on demodulating the resonance a bearing's impacts excite, and choosing that band by eye is unreliable. Spectral kurtosis turns the choice into a measurement: for each frequency it takes the kurtosis of the short-time spectral magnitude over time, large where the signal is intermittently impulsive, near zero where it is stationary. The kurtogram extends this over a grid of center frequencies and bandwidths, so the brightest cell names both where to filter and how wide. Feeding that band to the envelope spectrum is the modern, automated bearing pipeline.

Health-management example

A gearbox bearing fault rings a resonance whose frequency is not known in advance. The kurtogram lights up a band near three kilohertz as the most impulsive; band-passing there and enveloping recovers the defect frequency a guessed band would have missed.

2026-06-20T00:26:28.201264 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
import numpy as np
from scipy import signal, stats

def fast_kurtogram(x, fs, nlevel=4):
    nyq = fs/2; best = dict(kurt=-np.inf)
    for lvl in range(1, nlevel+1):
        bw = nyq/2**lvl; lo = 0.0
        while lo <= nyq - bw + 1:
            loc, hic = max(lo, fs*0.004), min(lo+bw, nyq*0.998)
            if hic-loc >= fs*0.004:
                b, a = signal.butter(3, [loc/nyq, hic/nyq], "band")
                env = np.abs(signal.hilbert(signal.filtfilt(b, a, x)))
                k = stats.kurtosis(env, fisher=False)
                if k > best["kurt"]:
                    best = dict(kurt=k, band=(loc, hic))
            lo += bw/2
    return best          # band -> feed to envelope_spectrum
Practical note

The kurtosis can be maximized by a band so narrow it cannot resolve the defect-frequency sidebands, so cap the level or require the bandwidth to exceed several times the expected defect rate. Strong deterministic components like gear-mesh lines distort the kurtogram, so remove them first by synchronous averaging or cepstrum editing.

Antoni (2006), Mech. Syst. Signal Process. 20; Antoni & Randall (2006), MSSP 20.

Variable speed: order tracking

A Fourier spectrum assumes constant speed. On a run-up the kinematic lines sweep and smear. Order tracking resamples to shaft angle so they stand still.

Computed order tracking

going further

Resample a signal from equal time steps to equal increments of shaft angle, using the instantaneous speed, so a shaft-locked component becomes a fixed order regardless of how the speed varies. It un-smears a run-up.

In plain words

When shaft speed changes, every component locked to the shaft changes frequency with it, so a Fourier spectrum of a run-up smears each order into a broad hump and resolves nothing. Order tracking removes the speed: using a tachometer or an estimate of instantaneous phase, it resamples the signal at equal increments of shaft angle rather than equal time. In that angle domain a shaft-order component sits at a fixed number of cycles per revolution no matter how the speed varies, so the smeared sweeps collapse back into sharp order lines. Structural resonances, fixed in frequency, do the opposite and smear, which itself helps tell a forced order from a resonance.

Health-management example

A turbocharger is run up from idle while vibration is recorded. In the raw spectrum the blade-pass order is an unreadable smear; after order tracking it is a sharp line at the blade count per revolution, and a fixed resonance band that now smears is exposed as structural rather than forced.

2026-06-20T00:26:28.361968 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
import numpy as np

def order_track(x, fs, f_inst, spr=64):
    theta = np.cumsum(f_inst)/fs           # revolutions vs time
    t = np.arange(len(x))/fs
    ang = np.arange(0, int(theta[-1]), 1.0/spr)
    xr = np.interp(np.interp(ang, theta, t), t, x)   # resample to angle
    X = np.abs(np.fft.rfft((xr-xr.mean())*np.hanning(len(xr))))
    orders = np.fft.rfftfreq(len(xr), 1.0/spr)
    return orders, X, xr
Practical note

Order tracking needs an accurate angle reference; phase errors blur the orders just as speed variation blurred the spectrum. Resample with enough samples per revolution to cover the highest order of interest without aliasing. Remember resonances stay at fixed frequencies and therefore smear in the order domain, the mirror image of the time-domain picture.

Fyfe & Munck (1997), Mech. Syst. Signal Process. 11; Bossley et al. (1999), MSSP 13.

Gears: time-synchronous averaging and condition indicators

A gear signal is buried in noise and the vibration of everything else on the shaft. Averaging synchronized to rotation extracts it, and its residual yields the classical gear indicators.

Synchronous averaging and gear indicators

going further

Averaging the signal synchronized to shaft rotation keeps the part that repeats every revolution and averages away noise and non-synchronous content. The residual left after removing the regular mesh yields FM4, M6A, and M8A.

In plain words

Everything locked to a shaft repeats every revolution; everything else does not. Time-synchronous averaging exploits that by resampling to shaft angle and averaging over many revolutions, so the gear-mesh signal and any once-per-revolution fault reinforce while noise and components from other shafts average toward zero. The averaged revolution is a clean picture of that gear. Subtracting its regular mesh harmonics leaves a residual that is flat for a healthy gear but spikes where a local defect, a cracked or spalled tooth, disturbs a few teeth; the normalized fourth, sixth, and eighth moments of that residual, FM4, M6A, and M8A, turn the disturbance into trendable numbers.

Health-management example

One gear in a multi-stage box has a cracked tooth. Synchronous averaging at that shaft's rate pulls its mesh pattern out of the mixture, and the once-per-revolution disturbance from the crack drives FM4 well above its healthy value of three while the overall spectrum looks unchanged.

2026-06-20T00:26:28.507550 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
import numpy as np

def time_synchronous_average(x, fs, fr, spr=256):
    total = int((len(x)/fs)*fr)
    ang = np.arange(total*spr)/spr
    xr = np.interp(ang/fr, np.arange(len(x))/fs, x)
    return xr.reshape(total, spr).mean(axis=0)   # one clean revolution

def gear_indicators(tsa, n_teeth, n_harm=3):
    X = np.fft.rfft(tsa); reg = np.zeros_like(X)
    for h in range(1, n_harm+1):
        if n_teeth*h < len(X): reg[n_teeth*h] = X[n_teeth*h]
    d = tsa - np.fft.irfft(reg, len(tsa)); v = np.mean(d**2)
    return dict(FM4=np.mean(d**4)/v**2, M6A=np.mean(d**6)/v**3)
Practical note

TSA needs an accurate once-per-rev trigger and many revolutions, since noise falls only as the square root of the count. The indicators need a healthy baseline: FM4 and NA4 are meant to be trended against a learned normal, since their absolute value depends on the gear. In a multi-stage box, average at each shaft's own rate to separate the gears.

McFadden (1987), Mech. Syst. Signal Process. 1; Zakrajsek et al. (1993), NASA TM 105950.

The frontier: cyclostationary analysis

Envelope analysis is a special case of a deeper idea: a bearing fault is cyclostationary, its energy modulated at the defect rate. The cyclic spectrum makes that structure explicit and separates random bearing content from deterministic gears.

Cyclic modulation spectrum

going further

A fast estimator of the spectral correlation. It maps energy against two frequencies, the carrier f and the cyclic modulation frequency alpha, so a bearing fault appears as a ridge at alpha equal to the defect rate across its resonance, separating random impulsive content from deterministic lines.

In plain words

A bearing fault is not stationary and not periodic but cyclostationary: the random ringing is repeated, with statistics that cycle at the defect rate. The cyclic modulation spectrum captures that by asking, for each carrier frequency, how strongly its energy is modulated and at what rate. The result is a map over the carrier frequency f and the cyclic frequency alpha, on which a bearing fault is a vertical ridge at alpha equal to its defect frequency, spread across the resonance band in f. Deterministic gear and shaft lines sit at their own cyclic frequencies and so separate out, which is why the cyclic approach succeeds on mixed signals where a plain envelope spectrum blends everything together.

Health-management example

A signal carries both a strong gear mesh and a weak bearing fault. The envelope spectrum is dominated by the gear and its sidebands; the cyclic modulation spectrum places the gear content at the shaft cyclic frequency and reveals the bearing as a separate ridge at its defect rate across the resonance.

2026-06-20T00:26:28.634055 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
import numpy as np
from scipy import signal

def cyclic_modulation_spectrum(x, fs, nperseg=256):
    noverlap = nperseg - max(8, nperseg//16)      # small hop -> wide alpha
    f, t, Z = signal.stft(x, fs, nperseg=nperseg, noverlap=noverlap)
    S = np.abs(Z)**2; S = S - S.mean(axis=1, keepdims=True)
    C = np.abs(np.fft.rfft(S*np.hanning(S.shape[1]), axis=1))
    alpha = np.fft.rfftfreq(S.shape[1], t[1]-t[0])
    return alpha, f, C        # ridge at alpha = defect rate
Practical note

The cyclic frequency range is set by the analysis frame rate, so use enough overlap to reach the defect frequencies and their harmonics. Deterministic components can still mask faint faults, so the normalized form, the cyclic spectral coherence, is preferred for weak signatures. It costs more than envelope analysis but separates sources the envelope blends.

Antoni (2009), Mech. Syst. Signal Process. 23; Borghesani & Antoni (2018), MSSP 105.

More tools: decomposition, deconvolution, liftering, severity

Four shorter entries that round out the kit: an adaptive decomposition, a blind deconvolution, a spectrum-cleaning edit, and a standard severity scale.

Empirical mode decomposition

going further

Without assuming any basis, EMD peels a signal into a few intrinsic mode functions, fastest to slowest, plus a trend. It adapts to non-stationary, nonlinear signals, separating a fast fault transient from slow background.

In plain words

Where the wavelet uses a fixed prototype, empirical mode decomposition builds its basis from the data itself. It repeatedly identifies the local extrema, fits smooth envelopes through the maxima and minima, and subtracts their mean, peeling off the fastest oscillation as the first intrinsic mode function, then the next, down to a slow residual trend. The result is a data-driven filter bank that follows a non-stationary or nonlinear signal, so a brief fault transient lands in an early mode separated from the slow operating background. Combined with the Hilbert transform it gives instantaneous frequencies, the Hilbert-Huang view.

Health-management example

A vibration record mixes a slow load oscillation, a steady tone, and a short impact. EMD separates them: the impact appears alone in the first intrinsic mode, where it can be enveloped or timed, while the slow load lands in the trend.

2026-06-20T00:26:28.921065 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
# see scripts/spectral.py: emd(x, max_imf=6)
# sift: extrema -> cubic-spline envelopes -> subtract the mean
# repeat per mode; returns [IMF1, IMF2, ..., residual]
imfs = emd(vibration, max_imf=4)
transient = imfs[0]          # fastest mode isolates the impact
Practical note

EMD is adaptive but fragile: mode mixing can split one component across modes or merge two, and it is sensitive to noise and to endpoint handling. Ensemble EMD and variational mode decomposition are more robust. Treat the modes as a filter bank to inspect, not as physically labeled components.

Huang et al. (1998), Proc. R. Soc. A 454; Lei et al. (2013), Mech. Syst. Signal Process. 35.

Minimum entropy deconvolution

going further

A fault impulse train is blurred by the path it travels to the sensor. MED estimates the inverse filter that makes the output as impulsive as possible, by maximizing output kurtosis, recovering the buried impacts before envelope analysis.

In plain words

By the time a bearing impact reaches the accelerometer it has been smeared by the transmission path, its sharp edge spread into a ring. Minimum entropy deconvolution tries to undo that: it searches for the finite impulse-response filter that, applied to the signal, makes the output as impulsive, as low-entropy, as possible, which it measures by kurtosis and maximizes by a short iteration. The deconvolved signal restores the periodic spikes the path had blurred, sharpening a faint impulse train so the subsequent envelope spectrum reads cleanly.

Health-management example

A bearing fault is so smeared by a long structural path that its impacts are invisible and its kurtosis barely raised. MED estimates the inverse path, the deconvolved signal shows a clean train of spikes at the defect period, and the kurtosis jumps, confirming the fault.

2026-06-20T00:26:29.078279 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
# see scripts/spectral.py: med_deconvolve(x, L=40, iters=15)
# iterate: y = X f ; f = R^-1 (X^T y^3) ; normalize  (R = X^T X)
out = med_deconvolve(vibration, L=50, iters=20)
sharpened = out["deconvolved"]   # then envelope_spectrum(sharpened, ...)
Practical note

MED maximizes kurtosis, so it favors one large impulse and can lock onto a single outlier rather than the periodic train; maximum correlated kurtosis deconvolution, which targets a specified period, is more robust for bearings. Choose the filter length long enough to invert the path but short enough to stay stable, and follow with envelope analysis rather than reading the deconvolved signal directly.

Wiggins (1978), Geophys. Prospect. 26; McDonald et al. (2012), Mech. Syst. Signal Process. 33.

Cepstrum editing (liftering)

going further

A regular harmonic or sideband family is a single peak in the cepstrum, so notching that rahmonic removes the whole family from the spectrum, cleaning a dominant gear-mesh comb to expose weaker structure underneath.

In plain words

The cepstrum gathers an evenly spaced family of spectral lines into one peak at their spacing, which makes it a precise editing tool: zero that peak and its multiples, transform back, and the entire family vanishes from the spectrum while everything else is untouched. This liftering is the standard way to strip a dominant gear-mesh comb that would otherwise swamp a weak bearing signature, a prewhitening step that lets the spectral kurtosis and the envelope work on what remains.

Health-management example

A strong gear mesh and its sidebands bury a small bearing fault. Liftering the mesh family out in the cepstral domain removes the comb and leaves the bearing's resonance, which the kurtogram and envelope then pick up cleanly.

2026-06-20T00:26:29.174320 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
# see scripts/spectral.py: cepstrum_edit(x, q_remove)
# real cepstrum -> zero the rahmonic at q = fs / spacing -> back to spectrum
ce = cepstrum_edit(vibration, [int(fs/sideband_spacing)])
cleaned_spectrum = ce["log_edited"]   # mesh family removed
Practical note

Editing the real cepstrum removes magnitude structure but discards phase, so to reconstruct a time signal use the complex cepstrum, which preserves phase but needs careful unwrapping. Liftering a dominant mesh family is a common prewhitening step before bearing analysis, removing the deterministic comb that would otherwise dominate the kurtosis and the envelope.

Randall (2017), Mech. Syst. Signal Process. 97; Borghesani et al. (2013), MSSP 36.

ISO 20816 severity zones

going further

Map a broadband vibration velocity RMS to a standard severity zone, A newly commissioned, B unrestricted operation, C short-term only, D damaging. It turns a measured level into an actionable alarm.

In plain words

Diagnosis says what is wrong; severity assessment says how urgent it is. The ISO 20816 family (formerly 10816) defines velocity RMS boundaries that sort a machine into four zones from newly commissioned through damaging, set by the machine's size, power, and mounting. A single broadband number placed against those boundaries gives operations a clear, standardized action: keep running, plan maintenance, or stop. It is the coarse overall gauge that sits above the diagnostic indicators, telling you when the detailed analysis becomes urgent.

Health-management example

A pump's overall velocity RMS climbs from one millimetre per second into zone C over several months. The level alone does not say why, but it triggers the diagnostic chain, where the envelope spectrum then identifies a bearing outer-race fault as the cause.

2026-06-20T00:26:29.237368 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
def iso10816_zone(v_rms_mm_s, machine_class="medium"):
    table = {"small": (0.71, 1.8, 4.5), "medium": (1.4, 2.8, 7.1),
             "large_rigid": (2.3, 4.5, 11.0), "large_soft": (3.5, 7.1, 18.0)}
    ab, bc, cd = table[machine_class]
    z = "A" if v_rms_mm_s<ab else "B" if v_rms_mm_s<bc else \
        "C" if v_rms_mm_s<cd else "D"
    return z, dict(AB=ab, BC=bc, CD=cd)
Practical note

The boundaries depend on machine size, power, and mounting, rigid or flexible, so use the class that matches; the zones here are illustrative. Broadband velocity RMS is a coarse gauge, not a diagnosis: it can sit in zone A while a localized bearing fault, which kurtosis and the envelope catch, develops. Trend the overall level and the diagnostic indicators together.

ISO 20816-1 (2016); ISO 10816-3 (2009).

References

  1. Box, Jenkins & Reinsel (2008), Time Series Analysis; Brockwell & Davis (1991), Time Series.
  2. Box, Jenkins & Reinsel (2008), Time Series Analysis; Akaike (1974), IEEE TAC 19; Schwarz (1978), Ann. Statist. 6.
  3. Welch (1967), IEEE Trans. Audio Electroacoust. 15; Oppenheim & Schafer (2010), Discrete-Time Signal Processing.
  4. Bendat & Piersol (2010), Random Data; Carter (1987), Proc. IEEE 75.
  5. Bogert, Healy & Tukey (1963), Proc. Symp. Time Series; Randall (2017), Mech. Syst. Signal Process. 97.
  6. Randall & Antoni (2011), Mech. Syst. Signal Process. 25; McFadden & Smith (1984), J. Sound Vib. 96.
  7. Dyer & Stewart (1978), J. Mech. Design 100; Antoni (2006), Mech. Syst. Signal Process. 20 (spectral kurtosis).
  8. Nikias & Mendel (1993), IEEE Signal Process. Mag. 10; Collis, White & Hammond (1998), Mech. Syst. Signal Process. 12.
  9. Allen & Rabiner (1977), Proc. IEEE 65; Cohen (1995), Time-Frequency Analysis.
  10. Mallat (2008), A Wavelet Tour of Signal Processing; Peng & Chu (2004), Mech. Syst. Signal Process. 18.

Take it into your editor

The same content is packaged as a VS Code Copilot skill in the progressive-disclosure layout: a routing SKILL.md, per-topic reference docs, a tested spectral.py engine that implements every indicator with numpy and scipy only, and Copilot integration files (a vibration-analyst chat mode, a /diagnose prompt, and path-scoped instructions). Drop the folder into your kit, or point Copilot at the skill directly.

spectral-analysis-kit.zip
SKILL.md · references · spectral.py · Copilot agent + prompt

Link resolves when the zip sits beside this file. Run python scripts/spectral.py for the self-test on a synthetic bearing signal.