Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
124 views
ubuntu2404
1
\documentclass[11pt,a4paper]{article}
2
3
% Signal Processing and Fourier Analysis Template
4
% SEO Keywords: signal processing latex template, fourier analysis latex, digital signal processing latex
5
6
\usepackage[utf8]{inputenc}
7
\usepackage[T1]{fontenc}
8
\usepackage{amsmath,amsfonts,amssymb}
9
\usepackage{mathtools}
10
\usepackage{siunitx}
11
\usepackage{graphicx}
12
\usepackage{float}
13
\usepackage{hyperref}
14
\usepackage{cleveref}
15
\usepackage{booktabs}
16
\usepackage{pythontex}
17
18
\usepackage[style=numeric,backend=biber]{biblatex}
19
\addbibresource{references.bib}
20
21
\title{Signal Processing and Fourier Analysis:\\
22
Digital Methods and Spectral Techniques}
23
\author{CoCalc Scientific Templates}
24
\date{\today}
25
26
\begin{document}
27
28
\maketitle
29
30
\begin{abstract}
31
This comprehensive signal processing template demonstrates Fourier analysis, digital filter design, spectral estimation, and time-frequency analysis. Features include FFT algorithms, window functions, filter implementations, and wavelet transforms with applications to real-world signal analysis problems and professional visualizations.
32
33
\textbf{Keywords:} signal processing, Fourier analysis, digital filters, spectral estimation, FFT, wavelets, time-frequency analysis
34
\end{abstract}
35
36
\tableofcontents
37
\newpage
38
39
\section{Fourier Transform and Spectral Analysis}
40
\label{sec:fourier-analysis}
41
42
\begin{pycode}
43
import numpy as np
44
import matplotlib.pyplot as plt
45
import matplotlib
46
matplotlib.use('Agg')
47
from scipy import signal
48
from scipy.signal import windows
49
from scipy.fft import fft, fftfreq, fftshift
50
51
# Set random seed for reproducibility
52
np.random.seed(42)
53
54
def generate_test_signal():
55
"""Generate complex test signal for analysis"""
56
57
# Time parameters
58
fs = 1000 # Sampling frequency (Hz)
59
T = 2.0 # Duration (seconds)
60
t = np.arange(0, T, 1/fs)
61
N = len(t)
62
63
# Signal components
64
f1, f2, f3 = 50, 120, 300 # Frequencies (Hz)
65
66
# Construct signal: sinusoids + chirp + noise
67
signal_clean = (np.sin(2*np.pi*f1*t) +
68
0.5*np.sin(2*np.pi*f2*t + np.pi/4) +
69
0.3*np.sin(2*np.pi*f3*t))
70
71
# Add frequency-modulated chirp
72
chirp = signal.chirp(t, f0=10, f1=200, t1=T, method='linear')
73
74
# Add noise
75
noise = 0.2 * np.random.randn(N)
76
77
test_signal = signal_clean + 0.3*chirp + noise
78
79
print(f"Signal Processing: Test Signal Generation")
80
print(f" Sampling frequency: {fs} Hz")
81
print(f" Duration: {T} s")
82
print(f" Samples: {N}")
83
print(f" Component frequencies: {f1}, {f2}, {f3} Hz")
84
85
return t, test_signal, signal_clean, fs
86
87
def fourier_analysis(t, x, fs):
88
"""Perform comprehensive Fourier analysis"""
89
90
N = len(x)
91
92
# Compute FFT
93
X = fft(x)
94
freqs = fftfreq(N, 1/fs)
95
96
# Magnitude and phase spectra
97
magnitude = np.abs(X)
98
phase = np.angle(X)
99
100
# Power spectral density
101
psd = magnitude**2 / (fs * N)
102
103
# Only positive frequencies for display
104
positive_freqs = freqs[:N//2]
105
magnitude_positive = magnitude[:N//2]
106
psd_positive = psd[:N//2]
107
108
print(f"\nFourier Analysis Results:")
109
print(f" Frequency resolution: {freqs[1]:.2f} Hz")
110
print(f" Maximum frequency: {positive_freqs[-1]:.1f} Hz")
111
112
# Find spectral peaks
113
peaks, properties = signal.find_peaks(magnitude_positive,
114
height=np.max(magnitude_positive)*0.1,
115
distance=10)
116
117
peak_freqs = positive_freqs[peaks]
118
peak_magnitudes = magnitude_positive[peaks]
119
120
print(f" Detected peaks at frequencies:")
121
for freq, mag in zip(peak_freqs, peak_magnitudes):
122
print(f" {freq:.1f} Hz (magnitude: {mag:.0f})")
123
124
return freqs, X, magnitude, phase, psd, positive_freqs, magnitude_positive, psd_positive
125
126
# Generate and analyze test signal
127
t, test_sig, clean_sig, fs = generate_test_signal()
128
freqs, X, mag, phase, psd, pos_freqs, mag_pos, psd_pos = fourier_analysis(t, test_sig, fs)
129
\end{pycode}
130
131
\section{Digital Filter Design}
132
\label{sec:digital-filters}
133
134
\begin{pycode}
135
# Digital filter design and implementation
136
def design_filters(fs):
137
"""Design various digital filters"""
138
139
nyquist = fs / 2
140
141
# Low-pass Butterworth filter
142
cutoff_lp = 100 # Hz
143
order_lp = 4
144
b_lp, a_lp = signal.butter(order_lp, cutoff_lp/nyquist, btype='low')
145
146
# High-pass Butterworth filter
147
cutoff_hp = 200 # Hz
148
order_hp = 4
149
b_hp, a_hp = signal.butter(order_hp, cutoff_hp/nyquist, btype='high')
150
151
# Band-pass Chebyshev filter
152
low_band, high_band = 80, 150 # Hz
153
order_bp = 6
154
b_bp, a_bp = signal.cheby1(order_bp, 1, [low_band/nyquist, high_band/nyquist], btype='band')
155
156
# FIR filter using window method
157
numtaps = 101
158
cutoff_fir = 150 # Hz
159
b_fir = signal.firwin(numtaps, cutoff_fir/nyquist, window='hamming')
160
a_fir = [1.0] # FIR filters have a_k = [1, 0, 0, ...]
161
162
filters = {
163
'Low-pass': (b_lp, a_lp),
164
'High-pass': (b_hp, a_hp),
165
'Band-pass': (b_bp, a_bp),
166
'FIR': (b_fir, a_fir)
167
}
168
169
print(f"\nDigital Filter Design:")
170
print(f" Low-pass: Order {order_lp}, cutoff {cutoff_lp} Hz")
171
print(f" High-pass: Order {order_hp}, cutoff {cutoff_hp} Hz")
172
print(f" Band-pass: Order {order_bp}, band {low_band}-{high_band} Hz")
173
print(f" FIR: {numtaps} taps, cutoff {cutoff_fir} Hz")
174
175
return filters
176
177
def apply_filters(signal_data, filters, fs):
178
"""Apply filters to signal and analyze results"""
179
180
filtered_signals = {}
181
182
for name, (b, a) in filters.items():
183
# Apply filter
184
filtered = signal.filtfilt(b, a, signal_data)
185
filtered_signals[name] = filtered
186
187
# Compute filter response
188
w, h = signal.freqz(b, a, fs=fs)
189
190
# Store frequency response
191
filters[name] = (b, a, w, h)
192
193
return filtered_signals
194
195
# Design filters and apply to test signal
196
filters = design_filters(fs)
197
filtered_sigs = apply_filters(test_sig, filters, fs)
198
\end{pycode}
199
200
\section{Window Functions and Spectral Estimation}
201
\label{sec:windows-spectral}
202
203
\begin{pycode}
204
# Window functions and spectral estimation
205
def compare_window_functions(N=256):
206
"""Compare different window functions"""
207
208
window_funcs = {
209
'Rectangular': np.ones(N),
210
'Hamming': windows.hamming(N),
211
'Hann': windows.hann(N),
212
'Blackman': windows.blackman(N),
213
'Kaiser': windows.kaiser(N, beta=8.6)
214
}
215
216
# Compute frequency responses
217
window_spectra = {}
218
freqs_win = np.linspace(-0.5, 0.5, 1024)
219
220
for name, window in window_funcs.items():
221
# Zero-pad for better frequency resolution
222
padded = np.zeros(1024)
223
padded[:N] = window
224
225
# Compute FFT and shift
226
spectrum = fftshift(fft(padded))
227
window_spectra[name] = spectrum
228
229
# Calculate metrics
230
main_lobe_width = np.sum(np.abs(spectrum) > 0.5 * np.max(np.abs(spectrum))) / 1024
231
side_lobe_level = 20 * np.log10(np.max(np.abs(spectrum[np.abs(spectrum) < 0.9*np.max(np.abs(spectrum))])) / np.max(np.abs(spectrum)))
232
233
print(f" {name}: Main lobe width ~= {main_lobe_width:.3f}, Side lobe level ~= {side_lobe_level:.1f} dB")
234
235
print(f"\nWindow Function Analysis (N = {N}):")
236
237
return window_funcs, window_spectra, freqs_win
238
239
def welch_spectral_estimation(signal_data, fs, nperseg=256, overlap_factor=0.5):
240
"""Welch's method for spectral estimation"""
241
242
# Different window types
243
window_types = ['hamming', 'hann', 'blackman']
244
245
spectral_estimates = {}
246
247
for window in window_types:
248
freqs, psd = signal.welch(signal_data, fs, window=window,
249
nperseg=nperseg,
250
noverlap=int(nperseg * overlap_factor),
251
return_onesided=True)
252
spectral_estimates[window] = (freqs, psd)
253
254
print(f"\nWelch Spectral Estimation:")
255
print(f" Segment length: {nperseg}")
256
print(f" Overlap: {overlap_factor*100:.0f}%")
257
print(f" Windows tested: {window_types}")
258
259
return spectral_estimates
260
261
# Analyze window functions and spectral estimation
262
window_funcs, win_spectra, freqs_win = compare_window_functions()
263
welch_estimates = welch_spectral_estimation(test_sig, fs)
264
\end{pycode}
265
266
\section{Time-Frequency Analysis}
267
\label{sec:time-frequency}
268
269
\begin{pycode}
270
# Time-frequency analysis using spectrograms and wavelets
271
def compute_spectrogram(signal_data, fs, nperseg=256, noverlap=None):
272
"""Compute and analyze spectrogram"""
273
274
if noverlap is None:
275
noverlap = nperseg // 2
276
277
f, t_spec, Sxx = signal.spectrogram(signal_data, fs,
278
nperseg=nperseg,
279
noverlap=noverlap)
280
281
print(f"\nSpectrogram Analysis:")
282
print(f" Time resolution: {t_spec[1] - t_spec[0]:.4f} s")
283
print(f" Frequency resolution: {f[1] - f[0]:.2f} Hz")
284
print(f" Time bins: {len(t_spec)}")
285
print(f" Frequency bins: {len(f)}")
286
287
return f, t_spec, Sxx
288
289
def continuous_wavelet_transform(signal_data, fs, wavelet='morl'):
290
"""Simplified continuous wavelet transform demonstration"""
291
292
# Define scales for CWT
293
scales = np.arange(1, 128)
294
295
# Create a simple Morlet-like wavelet manually
296
def morlet_wavelet(M, w=5.0, s=1.0, complete=True):
297
"""
298
Simple Morlet wavelet implementation
299
"""
300
x = np.arange(0, M) - (M - 1.0) / 2
301
x = x / s
302
output = np.exp(1j * w * x) * np.exp(-0.5 * x**2) * np.pi**(-0.25)
303
if not complete:
304
output -= np.exp(-0.5 * w**2) * np.exp(-0.5 * x**2) * np.pi**(-0.25)
305
return output
306
307
# Compute CWT manually using convolution
308
cwt_matrix = np.zeros((len(scales), len(signal_data)), dtype=complex)
309
310
for i, scale in enumerate(scales):
311
# Create wavelet at this scale
312
wavelet_length = min(int(10 * scale), len(signal_data))
313
if wavelet_length % 2 == 0:
314
wavelet_length += 1
315
316
psi = morlet_wavelet(wavelet_length, s=scale)
317
318
# Normalize
319
psi = psi / np.sqrt(scale)
320
321
# Convolve signal with wavelet
322
cwt_row = np.convolve(signal_data, np.conj(psi[::-1]), mode='same')
323
cwt_matrix[i, :] = cwt_row
324
325
# Convert scales to frequencies (approximate)
326
frequencies = fs / (2 * scales)
327
328
print(f"\nContinuous Wavelet Transform:")
329
print(f" Wavelet: {wavelet}")
330
print(f" Scales: {len(scales)}")
331
print(f" Frequency range: {frequencies[-1]:.1f} - {frequencies[0]:.1f} Hz")
332
333
return frequencies, cwt_matrix
334
335
# Perform time-frequency analysis
336
f_spec, t_spec, Sxx = compute_spectrogram(test_sig, fs)
337
freq_cwt, cwt_coeffs = continuous_wavelet_transform(test_sig, fs)
338
\end{pycode}
339
340
\begin{pycode}
341
# Create comprehensive signal processing visualization
342
import os
343
os.makedirs('assets', exist_ok=True)
344
345
fig = plt.figure(figsize=(20, 15))
346
gs = fig.add_gridspec(4, 4, hspace=0.3, wspace=0.3)
347
348
# Time domain signal
349
ax1 = fig.add_subplot(gs[0, :2])
350
ax1.plot(t[:500], test_sig[:500], 'b-', linewidth=1.5, label='Test signal')
351
ax1.plot(t[:500], clean_sig[:500], 'r--', linewidth=1.5, alpha=0.7, label='Clean components')
352
ax1.set_xlabel('Time (s)')
353
ax1.set_ylabel('Amplitude')
354
ax1.set_title('Time Domain Signal')
355
ax1.legend()
356
ax1.grid(True, alpha=0.3)
357
358
# Frequency domain (FFT)
359
ax2 = fig.add_subplot(gs[0, 2:])
360
ax2.loglog(pos_freqs[1:], psd_pos[1:], 'b-', linewidth=2)
361
ax2.set_xlabel('Frequency (Hz)')
362
ax2.set_ylabel('Power Spectral Density')
363
ax2.set_title('Power Spectrum (FFT)')
364
ax2.grid(True, alpha=0.3)
365
366
# Filter frequency responses
367
ax3 = fig.add_subplot(gs[1, :2])
368
for name, (b, a, w, h) in filters.items():
369
if len(w) > 0: # Check if frequency response was computed
370
ax3.plot(w, 20*np.log10(np.abs(h)), linewidth=2, label=name)
371
ax3.set_xlabel('Frequency (Hz)')
372
ax3.set_ylabel('Magnitude (dB)')
373
ax3.set_title('Filter Frequency Responses')
374
ax3.legend()
375
ax3.grid(True, alpha=0.3)
376
377
# Filtered signals (time domain)
378
ax4 = fig.add_subplot(gs[1, 2:])
379
time_slice = slice(0, 1000) # Show first second
380
ax4.plot(t[time_slice], test_sig[time_slice], 'k-', alpha=0.5, label='Original')
381
colors = ['red', 'blue', 'green', 'orange']
382
for i, (name, filt_sig) in enumerate(filtered_sigs.items()):
383
if i < len(colors):
384
ax4.plot(t[time_slice], filt_sig[time_slice],
385
color=colors[i], linewidth=1.5, label=name)
386
ax4.set_xlabel('Time (s)')
387
ax4.set_ylabel('Amplitude')
388
ax4.set_title('Filtered Signals')
389
ax4.legend()
390
ax4.grid(True, alpha=0.3)
391
392
# Window functions
393
ax5 = fig.add_subplot(gs[2, :2])
394
for name, window in window_funcs.items():
395
ax5.plot(window, linewidth=2, label=name)
396
ax5.set_xlabel('Sample')
397
ax5.set_ylabel('Amplitude')
398
ax5.set_title('Window Functions')
399
ax5.legend()
400
ax5.grid(True, alpha=0.3)
401
402
# Window frequency responses
403
ax6 = fig.add_subplot(gs[2, 2:])
404
for name, spectrum in win_spectra.items():
405
ax6.plot(freqs_win, 20*np.log10(np.abs(spectrum)/np.max(np.abs(spectrum))),
406
linewidth=2, label=name)
407
ax6.set_xlabel('Normalized Frequency')
408
ax6.set_ylabel('Magnitude (dB)')
409
ax6.set_title('Window Frequency Responses')
410
ax6.set_ylim([-80, 5])
411
ax6.legend()
412
ax6.grid(True, alpha=0.3)
413
414
# Spectrogram
415
ax7 = fig.add_subplot(gs[3, :2])
416
pcm = ax7.pcolormesh(t_spec, f_spec, 10*np.log10(Sxx), shading='gouraud', cmap='hot')
417
ax7.set_xlabel('Time (s)')
418
ax7.set_ylabel('Frequency (Hz)')
419
ax7.set_title('Spectrogram')
420
plt.colorbar(pcm, ax=ax7, label='Power (dB)')
421
422
# Wavelet transform
423
ax8 = fig.add_subplot(gs[3, 2:])
424
cwt_magnitude = np.abs(cwt_coeffs)
425
pcm2 = ax8.pcolormesh(t, freq_cwt, cwt_magnitude, shading='gouraud', cmap='viridis')
426
ax8.set_xlabel('Time (s)')
427
ax8.set_ylabel('Frequency (Hz)')
428
ax8.set_title('Continuous Wavelet Transform')
429
ax8.set_ylim([0, 400]) # Limit frequency range for visibility
430
plt.colorbar(pcm2, ax=ax8, label='|CWT|')
431
432
plt.suptitle('Signal Processing and Fourier Analysis', fontsize=16, fontweight='bold')
433
plt.savefig('assets/signal-processing-analysis.pdf', dpi=300, bbox_inches='tight')
434
plt.close()
435
436
print("Signal processing analysis saved to assets/signal-processing-analysis.pdf")
437
\end{pycode}
438
439
\begin{figure}[H]
440
\centering
441
\includegraphics[width=0.95\textwidth]{assets/signal-processing-analysis.pdf}
442
\caption{Comprehensive signal processing and Fourier analysis including (top row) time domain signal with clean components and power spectrum via FFT, (second row) digital filter frequency responses and filtered signal comparisons, (third row) window function shapes and their frequency responses, and (bottom row) spectrogram and continuous wavelet transform providing time-frequency representations of the test signal.}
443
\label{fig:signal-processing-analysis}
444
\end{figure}
445
446
\section{Conclusion}
447
\label{sec:conclusion}
448
449
This signal processing template demonstrates comprehensive spectral analysis techniques including:
450
451
\begin{itemize}
452
\item Fourier transform theory and FFT implementation
453
\item Digital filter design (IIR and FIR)
454
\item Window functions and their spectral properties
455
\item Time-frequency analysis via spectrograms and wavelets
456
\item Professional visualization of signal characteristics
457
\end{itemize}
458
459
The computational methods provide a foundation for advanced signal processing applications in communications, audio processing, and scientific instrumentation.
460
461
\printbibliography
462
463
\end{document}
464