Module 3 · Section 10 of 11
Lesson 3.9 - Signal Processing Basics
Target: ~9 min read - 20 min hands-on
Overview
The Fast Fourier Transform (FFT) decomposes a signal into its constituent frequencies - essential whenever you need to know which frequency dominates a noisy sensor stream. The same technique reads a machine's running speed from an accelerometer, a fault signature from motor current, a harmonic from a power-quality log, or a resonance from an acoustic record. We'll analyze a synthetic vibration signal and apply a simple low-pass filter.
Why This Matters (Engineering Context)
Frequency-domain monitoring is spreading across PH manufacturing, power, and process plants - a shift in a dominant frequency is often the earliest warning of a developing fault, well before an amplitude threshold trips.
Code-Along
import numpy as np
np.random.seed(5)
# Build a synthetic vibration signal: 30 Hz fundamental + 60 Hz harmonic + noise
fs = 1000 # sampling frequency, Hz (samples per second)
duration = 2.0
# time axis: fs*duration samples, endpoint=False so it tiles cleanly for the FFT
t = np.linspace(0, duration, int(fs * duration), endpoint=False)
signal = (
1.0 * np.sin(2 * np.pi * 30 * t) # amplitude 1.0 at 30 Hz
+ 0.35 * np.sin(2 * np.pi * 60 * t) # amplitude 0.35 at 60 Hz
+ 0.4 * np.random.normal(0, 1, len(t)) # broadband noise
)
# rfft = FFT for real input; rfftfreq gives the frequency (Hz) of each FFT bin
fft_vals = np.fft.rfft(signal)
fft_freqs = np.fft.rfftfreq(len(signal), d=1 / fs)
magnitude = np.abs(fft_vals) / len(signal) # |complex| -> amplitude, normalised
# argmax = index of the largest magnitude -> the dominant frequency bin
dominant_freq = fft_freqs[np.argmax(magnitude)]
print(f"Dominant frequency: {dominant_freq:.1f} Hz")
print(f"Implied running speed: {dominant_freq * 60:.0f} RPM") # Hz * 60 = rev/min
# argsort returns indices low->high; [-3:] = the 3 biggest, [::-1] reverses to high->low
top3 = np.argsort(magnitude)[-3:][::-1]
print("\nTop 3 frequency components:")
for idx in top3:
print(f" {fft_freqs[idx]:6.1f} Hz magnitude={magnitude[idx]:.3f}")
# Simple low-pass filter: zero the FFT bins above 45 Hz, then irfft back to time domain
fft_filtered = fft_vals.copy()
fft_filtered[fft_freqs > 45] = 0
signal_filtered = np.fft.irfft(fft_filtered, n=len(signal))
print(f"\nOriginal signal std dev: {np.std(signal):.3f}")
print(f"Filtered signal std dev: {np.std(signal_filtered):.3f} (noise reduced)")
Run it: the dominant frequency lands at (or very close to) 30 Hz, with the second-strongest peak near 60 Hz. After low-pass filtering above 45 Hz, the signal's standard deviation drops, because most of the random-noise energy sits at higher frequencies and gets removed while the 30 Hz component is preserved.
Practice Exercises
- Change the low-pass cutoff to 25 Hz - what happens to the 30 Hz component itself?
- Add a third component at 90 Hz with amplitude 0.15 and confirm it appears as a (smaller) peak in the FFT.
- Compute the running speed in RPM for a machine whose dominant vibration frequency is measured at 24.5 Hz.
# Try the practice exercises here
Knowledge Check
- What does the FFT convert a signal from and into?
- Why might a secondary frequency peak matter diagnostically?
- What's the risk of setting a low-pass cutoff too close to the signal's own dominant frequency?
Answer key
- From the time domain into the frequency domain
- Specific harmonic patterns map to specific faults, so they say what is wrong, not just that something is
- You filter out part of the genuine signal, not just the noise, distorting the measurement
32 / 63 sections · Course home · Join the coaching cohort