Put an accelerometer on a spindle housing and record a second of data, and you get a squiggle — thousands of numbers that look like static. The information is in there, but it's written in the wrong domain. The FFT (Fast Fourier Transform) re-writes that squiggle as how much vibration at each frequency, and suddenly it reads like a diagnosis: a spike at exactly the spindle's rotation speed means runout or imbalance, spikes at bearing-geometry frequencies mean a failing bearing, a spike near a natural frequency during a cut means chatter. No PhD required — the whole translation is one NumPy call plus some discipline around it.
The idea in one paragraph
Any repeating signal can be described as a stack of sine waves at different frequencies and strengths. The time trace shows you the stack all mixed together; the FFT un-mixes it. A machine is full of things that repeat at known rates — the spindle at once per rev, each bearing ball passing a flaw, each flute of a cutter entering the cut — so when energy shows up at one of those known frequencies, you know which component is talking.
The code
import numpy as np
fs = 10_000 # sample rate, Hz — you MUST know this
x = np.loadtxt("spindle_z.csv") # accel samples, one per line
x = x - x.mean() # remove DC offset
window = np.hanning(len(x)) # taper the ends to stop leakage
X = np.fft.rfft(x * window)
freqs = np.fft.rfftfreq(len(x), d=1 / fs)
amp = 2 * np.abs(X) / (len(x) * window.mean())
# the five strongest peaks
top = np.argsort(amp)[-5:]
for i in sorted(top):
print(f"{freqs[i]:7.1f} Hz {amp[i]:.4f} g")- `fs` is sacred. The frequency axis is derived from the sample rate. If you guess it wrong, every peak lands on the wrong frequency and every diagnosis is wrong. It also caps what you can see: the highest visible frequency is
fs / 2(Nyquist), so sampling at 10 kHz sees nothing above 5 kHz. - Subtract the mean. Otherwise a giant DC spike at 0 Hz dwarfs everything you care about.
- The Hann window matters. The FFT assumes your snippet repeats forever; a raw chop makes the ends discontinuous and smears energy across the spectrum (leakage). Tapering with
np.hanningfixes it — thewindow.mean()term in the amplitude line compensates for the energy the taper removes. - Longer capture = finer resolution. Frequency resolution is
fs / N. One second of data resolves 1 Hz; a tenth of a second only 10 Hz. To separate two close bearing tones, record longer, not faster.
Reading the spectrum
Say the spindle runs 12,000 rpm — that's 200 Hz, your 1× line. Now the spectrum becomes a checklist: a tall peak at 200 Hz is imbalance or runout; 2× (400 Hz) suggests misalignment or looseness; a four-flute cutter in the cut puts its tooth-passing frequency at 800 Hz, which is normal — unless it's growing shift over shift. Bearing defects are the special case: each bearing geometry has characteristic defect frequencies (BPFO, BPFI — the manufacturers publish them per part number) that sit at non-integer multiples of shaft speed. Energy at a non-integer multiple is the classic early-warning signature, and it shows up in the spectrum weeks before you can hear anything.
Trend it, don't read it once
A single spectrum tells you what's vibrating today; it can't tell you what's changed. Capture the same measurement — same point, same speed, no cut — weekly, and plot the amplitude at the frequencies you care about over time. Flat lines are boring and wonderful. A bearing tone quietly doubling month over month is a repair you get to schedule instead of discover.
Measure like you mean it
Garbage in is worse than no data: mount the sensor rigidly (stud or hard magnet on bare metal — never on a sheet-metal cover), keep the mounting point identical between captures, and sample at least 2× the highest frequency you care about, in practice 5–10×.
The hardware side of this — a usable accelerometer rig for well under $100 — deserves its own post. But the analysis above doesn't care where the samples came from: the moment you have numbers at a known sample rate, fifteen lines of NumPy turn them into a story about your spindle. Pair it with load data off the control and you have the raw material of real predictive maintenance — no subscription attached. Want help wiring it up? Get in touch.


