Interactive FFT Filtering¶
Interactive FFT-based Filter Explorer¶
This is an interactive version of the FFT-based filtering notebook. Adjust the cutoff frequency and filter order with the sliders below — the plots update automatically.
```{marimo} python import marimo as mo import numpy as np from io import StringIO from numpy.fft import fft, ifft from matplotlib import pyplot as plt
Load the data file. The data contains a periodic time signal with noise:
```{marimo} python
_data_text = """0 -0.18822
0.0002 0.60113
0.0004 0.45327
0.0006 1.19276
0.0008 0.93189
0.001 1.57244
0.0012 1.31815
0.0014 1.59449
0.0016 1.38258
0.0018 1.21084
0.002 1.19737
0.0022 0.66882
0.0024 0.71818
0.0026 -0.01132
0.0028 0.10428
0.003 -0.73875
0.0032 -0.51546
0.0034 -1.20108
0.0036 -1.10572
0.0038 -1.47717
0.004 -1.46034
0.0042 -1.38484
0.0044 -1.50782
0.0046 -1.02745
0.0048 -1.21494
0.005 -0.50897
0.0052 -0.71103
0.0054 0.13826
0.0056 0.03174
0.0058 0.73393
0.006 0.73724
0.0062 1.15604
0.0064 1.32632
0.0066 1.36105
0.0068 1.6201
0.007 1.22967
0.0072 1.53813
0.0074 0.89377
0.0076 1.22004
0.0078 0.4177
0.008 0.51782
0.0082 -0.13641
0.0084 -0.24634
0.0086 -0.69659
0.0088 -0.9331
0.009 -1.05244
0.0092 -1.45909
0.0094 -1.22737
0.0096 -1.74607
0.0098 -1.12691
0.01 -1.52308
0.0102 -0.87454
0.0104 -1.06115
0.0106 -0.40656
0.0108 -0.33919
0.011 0.12863
0.0112 0.52043
0.0114 0.65791
0.0116 1.15151
0.0118 1.00483
0.012 1.64267
0.0122 1.16368
0.0124 1.69284
0.0126 1.16689
0.0128 1.42699
0.013 0.91208
0.0132 0.80908
0.0134 0.4615
0.0136 0.02024
0.0138 -0.09959
0.014 -0.73276
0.0142 -0.61375
0.0144 -1.36303
0.0146 -0.99177
0.0148 -1.63091
0.015 -1.24057
0.0152 -1.58571
0.0154 -1.21427
0.0156 -1.22923
0.0158 -0.95034
0.016 -0.59254
0.0162 -0.54901
0.0164 0.18049
0.0166 0.04291
0.0168 0.88318
0.017 0.62508
0.0172 1.34581
0.0174 1.10863
0.0176 1.58567
0.0178 1.36448
0.018 1.41133
0.0182 1.40281
0.0184 0.9725
0.0186 1.07331
0.0188 0.35729
0.019 0.47338
0.0192 -0.32595
0.0194 -0.15517
0.0196 -0.89795
0.0198 -0.81
0.02 -1.32011
0.0202 -1.3329
0.0204 -1.46457
0.0206 -1.5088
0.0208 -1.25584
0.021 -1.41834
0.0212 -0.80054
0.0214 -1.03503
0.0216 -0.26568
0.0218 -0.40684
0.022 0.39098
0.0222 0.33642
0.0224 0.88039
0.0226 1.03935
0.0228 1.19749
0.023 1.50967
0.0232 1.27277
0.0234 1.67769
0.0236 1.113
0.0238 1.50726
0.024 0.67855
0.0242 0.94517
0.0244 0.17933
0.0246 0.18766
0.0248 -0.34771
0.025 -0.55524
0.0252 -0.84049
0.0254 -1.2199
0.0256 -1.13919
0.0258 -1.63503
0.026 -1.23375
0.0262 -1.63884
0.0264 -1.07498
0.0266 -1.36599
0.0268 -0.66886
0.027 -0.76377
0.0272 -0.20036
0.0274 0.06287
0.0276 0.29361
0.0278 0.82451
0.028 0.80271
0.0282 1.36728
0.0284 1.11265
0.0286 1.7234
0.0288 1.20526
0.029 1.64154
0.0292 1.09344
0.0294 1.18542
0.0296 0.74726
0.0298 0.4748
0.03 0.29301
0.0302 -0.31095
0.0304 -0.25653
0.0306 -1.01587
0.0308 -0.7841
0.031 -1.50059
0.0312 -1.15803
0.0314 -1.65251
0.0316 -1.26977
0.0318 -1.44339
0.032 -1.15929
0.0322 -0.93883
0.0324 -0.89001
0.0326 -0.2447
0.0328 -0.30224
0.033 0.47329
0.0332 0.31487
0.0334 1.13445
0.0336 0.88865
0.0338 1.51262
0.034 1.25543
0.0342 1.51197
0.0344 1.44496
0.0346 1.27454
0.0348 1.25654
0.035 0.75551
0.0352 0.84823
0.0354 0.06841
0.0356 0.24299
0.0358 -0.61103
0.036 -0.45793
0.0362 -1.11401
0.0364 -1.04454
0.0366 -1.39819
0.0368 -1.48419
0.037 -1.37106
0.0372 -1.57053
0.0374 -1.07802
0.0376 -1.35818
0.0378 -0.58147
0.038 -0.76988
0.0382 0.03132
0.0384 -0.11342
0.0386 0.62785
0.0388 0.68718
0.039 1.07273
0.0392 1.28712
0.0394 1.29013
0.0396 1.58349
0.0398 1.26206
0.04 1.59576
0.0402 0.96326
0.0404 1.26929
0.0406 0.55245
0.0408 0.65059
0.041 -0.03102
0.0412 -0.10159
0.0414 -0.50434
0.0416 -0.87265
0.0418 -0.96413
0.042 -1.48192
0.0422 -1.15721
0.0424 -1.69715
0.0426 -1.1873
0.0428 -1.5702
0.043 -0.95428
0.0432 -1.15045
0.0434 -0.55311
0.0436 -0.43194
0.0438 -0.0187
0.044 0.42386
0.0442 0.46679
0.0444 1.08008
0.0446 0.901
0.0448 1.57031
0.045 1.17272
0.0452 1.71103
0.0454 1.22081
0.0456 1.43725
0.0458 1.04306
0.046 0.87526
0.0462 0.62435
0.0464 0.18087
0.0466 0.06491
0.0468 -0.6484
0.047 -0.47703
0.0472 -1.30202
0.0474 -0.94168
0.0476 -1.63756
0.0478 -1.2583
0.048 -1.58029
0.0482 -1.33214
0.0484 -1.2971
0.0486 -1.12036
0.0488 -0.69213
0.049 -0.69833
0.0492 0.09209
0.0494 -0.05334
0.0496 0.75563
0.0498 0.51767
0.05 1.24746
0.0502 1.10162
0.0504 1.54724
0.0506 1.38528
0.0508 1.42412
0.051 1.41204"""
data = np.loadtxt(StringIO(_data_text))
t = data[:, 0]
f = data[:, 1]
N = len(t)
f_s = 1.0 / (t[1] - t[0]) # sampling frequency
T = N / f_s # total sample time
del_f = 1.0 / T # frequency resolution
f_fold = f_s / 2.0 # folding frequency
N_disc = int(N / 2.0)
# frequency axis
f_incr = np.arange(0, f_fold + del_f, del_f)
f_dcr = np.arange(f_fold - del_f, 0, -del_f)
frequency = np.r_[f_incr, f_dcr]
# FFT
FFT1 = fft(f)
magnitude1 = np.abs(FFT1) / N_disc
magnitude1[0] /= 2.0
Use the sliders to adjust the Butterworth filter parameters:
```{marimo} python f_cut_slider = mo.ui.slider( start=50, stop=2000, step=10, value=500, label="Cutoff frequency (Hz)" ) order_slider = mo.ui.slider( start=2, stop=20, step=1, value=10, label="Filter order" ) mo.md("Filter parameters\n\n{f_cut_slider}\n\n{order_slider}").batch( f_cut_slider=f_cut_slider, order_slider=order_slider, )
Apply the filter and plot the results:
```{marimo} python
f_cut = f_cut_slider.value
order_n = order_slider.value
# Butterworth gain
G = 1.0 / np.sqrt(1 + ((frequency / f_cut) ** (2 * order_n)))
# Filter
FFT_filtered = FFT1 * G
FFT_inv = ifft(FFT_filtered)
FFT2 = fft(np.real(FFT_inv))
magnitude2 = np.abs(FFT2) / N_disc
magnitude2[0] /= 2.0
fig, axes = plt.subplots(3, 1, figsize=(10, 10), tight_layout=True)
# Original time signal
axes[0].plot(t, f, marker='o', markerfacecolor='b',
linestyle=':', color='lightgrey')
axes[0].set_title(f'Periodic time signal (f_s = {f_s:.0f} Hz, N = {N})')
axes[0].set_xlabel('$t$ (s)')
axes[0].set_ylabel('$g_i$ (V)')
# FFT comparison
axes[1].plot(frequency, magnitude1, '-b', label='Unfiltered FFT')
axes[1].plot(frequency, magnitude2, '--r', label='Filtered FFT')
axes[1].set_title(
f'Filtered and Unfiltered FFT (f_cut = {f_cut} Hz, order = {order_n})')
axes[1].set_xlabel('Frequency (Hz)')
axes[1].set_ylabel('Magnitude (V)')
axes[1].legend()
axes[1].set_xlim([0, f_fold])
# Filtered vs unfiltered time trace
axes[2].plot(t, f, '-b', label='Unfiltered')
axes[2].plot(t, np.real(FFT_inv), '-r', label='Filtered')
axes[2].set_title('Filtered and Unfiltered Time Trace')
axes[2].set_xlabel('Time (s)')
axes[2].set_ylabel('Amplitude')
axes[2].legend()
axes[2].set_xlim([0, 0.051])
plt.close()
fig