FFT / DCT -- Signal Processing Fundamentals¶
fft_dct ¶
FFT and DCT — frequency-domain signal processing with NumPy/SciPy.
ASI relevance
ADS-B signal analysis, weather radar processing, sensor fusion — all depend on extracting frequency content from time-domain signals.
Core ideas
- FFT converts a time-domain signal into its frequency components.
- DCT is similar but uses only real numbers — basis of JPEG & MP3.
- Filtering = zeroing unwanted FFT coefficients, then inverting.
- Nyquist theorem: maximum detectable frequency = sample_rate / 2.
References
https://numpy.org/doc/stable/reference/routines.fft.html https://docs.scipy.org/doc/scipy/reference/generated/scipy.fft.dct.html https://www.youtube.com/watch?v=spUNpyF58BY (3Blue1Brown Fourier)
generate_test_signal ¶
generate_test_signal(
frequencies: list[float],
amplitudes: list[float],
sample_rate: float,
duration: float,
) -> tuple[NDArray[np.float64], NDArray[np.float64]]
Create a synthetic signal that is the sum of sinusoids.
Each sinusoid has a frequency from frequencies and a corresponding amplitude from amplitudes. The returned arrays are the time axis and the composite signal.
t, sig = generate_test_signal([440.0], [1.0], 8000.0, 0.01) len(t) == len(sig) == int(8000.0 * 0.01) True
Source code in src/concepts/fft_dct.py
compute_fft ¶
compute_fft(
signal: NDArray[float64], sample_rate: float
) -> tuple[NDArray[np.float64], NDArray[np.float64]]
Compute FFT and return positive-frequency bins with magnitudes.
Returns (frequency_bins, magnitudes) where both arrays cover only the range [0, sample_rate/2). Magnitudes are normalized by N.
_, sig = generate_test_signal([100.0], [1.0], 1000.0, 1.0) freqs, mags = compute_fft(sig, 1000.0) float(freqs[np.argmax(mags)]) 100.0
Source code in src/concepts/fft_dct.py
filter_signal ¶
filter_signal(
signal: NDArray[float64],
sample_rate: float,
cutoff_freq: float,
filter_type: str = "lowpass",
) -> NDArray[np.float64]
Low-pass or high-pass filter by zeroing FFT coefficients.
filter_type must be "lowpass" (keep below cutoff) or
"highpass" (keep above cutoff).
_, sig = generate_test_signal([50.0, 500.0], [1.0, 1.0], 2000.0, 1.0) filtered = filter_signal(sig, 2000.0, 200.0, "lowpass") len(filtered) == len(sig) True
Source code in src/concepts/fft_dct.py
compute_dct ¶
Compute the type-II DCT of signal.
x = np.array([1.0, 2.0, 3.0, 4.0]) c = compute_dct(x) len© == len(x) True
Source code in src/concepts/fft_dct.py
compute_idct ¶
Inverse DCT (type-II) — reconstruct signal from DCT coefficients.
x = np.array([1.0, 2.0, 3.0, 4.0]) np.allclose(compute_idct(compute_dct(x)), x) True
Source code in src/concepts/fft_dct.py
spectral_analysis ¶
Summarise the spectral content of signal.
Returns a dict with
dominant_frequency: frequency (Hz) with highest magnitudetotal_power: sum of squared magnitudes (Parseval's theorem)bandwidth: range (Hz) that contains 90 % of total power
_, sig = generate_test_signal([440.0], [1.0], 8000.0, 1.0) info = spectral_analysis(sig, 8000.0) abs(info["dominant_frequency"] - 440.0) < 1.0 True