├── NewEEGSignal.mat ├── LICENSE ├── README.md └── Analysis_of_EEG_Data.py /NewEEGSignal.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AryaKoureshi/Signal-Processing-and-Analysis-of-EEG-Data-Using-Python/HEAD/NewEEGSignal.mat -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Arya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Signal Processing and Analysis of EEG Data Using Python 2 | 3 | This project demonstrates various signal processing techniques, such as signal generation, window functions, filtering, downsampling, zero-padding, and the application of time-frequency analysis using the Short-Time Fourier Transform (STFT). The project uses Python and its libraries, such as NumPy, SciPy, and Matplotlib, to implement and visualize the methods. The project also analyzes an EEG signal sampled at a rate of 256 Hz and explores its time-domain, frequency-domain, and time-frequency characteristics. 4 | 5 | ## Project Structure 6 | 7 | The project consists of two main parts: 8 | 9 | - **Part 1**: This part covers the basics of signal processing, such as generating a chirp signal, applying different window functions, and performing time-frequency analysis using the STFT. It also investigates how different parameters, such as window length, overlapping points, and number of DFT points, affect the time and frequency resolution of the spectrogram. 10 | - **Part 2**: This part focuses on the analysis of an EEG signal, which is a type of biomedical signal that measures the electrical activity of the brain. It applies low-pass filtering, downsampling, zero-padding, and the DFT to the EEG signal and examines how these techniques influence the signal's representation and frequency content. It also uses the STFT to provide a comprehensive time-frequency analysis of the EEG signal. 11 | 12 | ## Project Requirements 13 | 14 | The project requires the following Python libraries: 15 | 16 | - NumPy: A library for scientific computing and working with arrays. 17 | - SciPy: A library for scientific and technical computing, such as signal processing, linear algebra, optimization, and statistics. 18 | - Matplotlib: A library for creating plots and visualizations. 19 | - Scipy.io: A module for reading and writing MATLAB files. 20 | 21 | ## Project Files 22 | 23 | The project files include: 24 | 25 | - **NewEEGSignal.mat**: A MATLAB file that contains the EEG signal data. 26 | - **Analysis_of_EEG_Data.ipynb**: A Jupyter notebook that contains the Python code and the results for both questions. 27 | -------------------------------------------------------------------------------- /Analysis_of_EEG_Data.py: -------------------------------------------------------------------------------- 1 | #%% Question 1 2 | # part a 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from scipy.signal import chirp, get_window, spectrogram 6 | 7 | T = 2.0 # sec 8 | fs = 1000 # Hz 9 | t = np.linspace(0, T, int(T * fs), endpoint=False) 10 | f0 = 100 # Hz 11 | beta = 100 12 | f_t = f0 + beta * t**2 13 | 14 | x_t = chirp(t, f0=f0, f1=500, t1=T, method='quadratic') 15 | x_t2= np.cos(2 * np.pi * f_t * t) 16 | 17 | plt.figure(figsize=(26, 8)) 18 | plt.plot(t, x_t, 'r') 19 | plt.xlabel('Time (sec)') 20 | plt.ylabel('Amp') 21 | plt.title('Chirp Signal x(t)') 22 | plt.grid(True) 23 | plt.xlim([0, 2]) 24 | plt.tight_layout() 25 | plt.show() 26 | 27 | #%% part b 28 | L = 128 29 | fs = 1000 # Hz 30 | std = 10 # for gaussian 31 | 32 | rectangular_window = get_window('boxcar', L) 33 | triangular_window = get_window('triang', L) 34 | gauss_window = get_window(('gaussian', std), L) 35 | hamming_window = get_window('hamming', L) 36 | 37 | t = np.arange(0, L) / fs 38 | 39 | plt.figure(figsize=(26, 8)) 40 | plt.subplot(2, 2, 1) 41 | plt.plot(t, rectangular_window) 42 | plt.title('Rectangular Window') 43 | plt.xlabel('Time (s)') 44 | plt.ylabel('Amplitude') 45 | 46 | plt.subplot(2, 2, 2) 47 | plt.plot(t, triangular_window) 48 | plt.title('Triangular Window') 49 | plt.xlabel('Time (s)') 50 | plt.ylabel('Amplitude') 51 | 52 | plt.subplot(2, 2, 3) 53 | plt.plot(t, gauss_window) 54 | plt.title('Gaussian Window') 55 | plt.xlabel('Time (s)') 56 | plt.ylabel('Amplitude') 57 | 58 | plt.subplot(2, 2, 4) 59 | plt.plot(t, hamming_window) 60 | plt.title('Hamming Window') 61 | plt.xlabel('Time (s)') 62 | plt.ylabel('Amplitude') 63 | 64 | plt.tight_layout() 65 | plt.show() 66 | 67 | def normalized_frequency_axis(N): 68 | return np.arange(N) / N 69 | 70 | N = len(x_t) 71 | plt.figure(figsize=(12, 8)) 72 | cnt = 1 73 | names = ["Rectangular", "Triangular", "Gaussian", "Hamming"] 74 | for i in [rectangular_window, triangular_window, gauss_window, hamming_window]: 75 | X = np.fft.fft(i, N) 76 | X_mag = np.abs(X) 77 | 78 | X_dB = 20 * np.log10(X_mag) 79 | 80 | freq_axis = normalized_frequency_axis(N) 81 | 82 | plt.subplot(2, 2, cnt) 83 | plt.plot(freq_axis, X_dB) 84 | plt.title('{} Window Frequency Transform'.format(names[cnt-1])) 85 | plt.xlabel('Normalized Frequency (x π rads/sample)') 86 | plt.ylabel('Magnitude (dB)') 87 | plt.grid(True) 88 | plt.xlim([0, freq_axis[1000]]) 89 | cnt += 1 90 | 91 | plt.tight_layout() 92 | plt.show() 93 | 94 | #%% part c 95 | noverlap = 0 96 | NFFT = L 97 | 98 | plt.figure(figsize=(26, 8)) 99 | plt.subplot(2, 2, 1) 100 | f, t, Sxx = spectrogram(x_t, fs, window=rectangular_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 101 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 102 | plt.title('Rectangular Window Frequency Transform') 103 | plt.xlabel('Time (s)') 104 | plt.ylabel('Frequency (Hz)') 105 | plt.colorbar(label="Power/Frequency (dB/Hz)") 106 | 107 | plt.subplot(2, 2, 2) 108 | f, t, Sxx = spectrogram(x_t, fs, window=triangular_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 109 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 110 | plt.title('Triangular Window Frequency Transform') 111 | plt.xlabel('Time (s)') 112 | plt.ylabel('Frequency (Hz)') 113 | plt.colorbar(label="Power/Frequency (dB/Hz)") 114 | 115 | plt.subplot(2, 2, 3) 116 | f, t, Sxx = spectrogram(x_t, fs, window=gauss_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 117 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 118 | plt.title('Gaussian Window Frequency Transform') 119 | plt.xlabel('Time (s)') 120 | plt.ylabel('Frequency (Hz)') 121 | plt.colorbar(label="Power/Frequency (dB/Hz)") 122 | 123 | plt.subplot(2, 2, 4) 124 | f, t, Sxx = spectrogram(x_t, fs, window=hamming_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 125 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 126 | plt.title('Hamming Window Frequency Transform') 127 | plt.xlabel('Time (s)') 128 | plt.ylabel('Frequency (Hz)') 129 | plt.colorbar(label="Power/Frequency (dB/Hz)") 130 | 131 | plt.tight_layout() 132 | plt.show() 133 | 134 | #%% part d 135 | noverlaps = [0, 64, 127] 136 | NFFT = L 137 | 138 | for noverlap in noverlaps: 139 | plt.figure(figsize=(26, 8)) 140 | plt.subplot(2, 2, 1) 141 | f, t, Sxx = spectrogram(x_t, fs, window=rectangular_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 142 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 143 | plt.title('Rectangular Window Frequency Transform') 144 | plt.xlabel('Time (s)') 145 | plt.ylabel('Frequency (Hz)') 146 | plt.colorbar(label="Power/Frequency (dB/Hz)") 147 | 148 | plt.subplot(2, 2, 2) 149 | f, t, Sxx = spectrogram(x_t, fs, window=triangular_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 150 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 151 | plt.title('Triangular Window Frequency Transform') 152 | plt.xlabel('Time (s)') 153 | plt.ylabel('Frequency (Hz)') 154 | plt.colorbar(label="Power/Frequency (dB/Hz)") 155 | 156 | plt.subplot(2, 2, 3) 157 | f, t, Sxx = spectrogram(x_t, fs, window=gauss_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 158 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 159 | plt.title('Gaussian Window Frequency Transform') 160 | plt.xlabel('Time (s)') 161 | plt.ylabel('Frequency (Hz)') 162 | plt.colorbar(label="Power/Frequency (dB/Hz)") 163 | 164 | plt.subplot(2, 2, 4) 165 | f, t, Sxx = spectrogram(x_t, fs, window=hamming_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 166 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 167 | plt.title('Hamming Window Frequency Transform') 168 | plt.xlabel('Time (s)') 169 | plt.ylabel('Frequency (Hz)') 170 | plt.colorbar(label="Power/Frequency (dB/Hz)") 171 | 172 | plt.suptitle("N_overlap = {}".format(noverlap)) 173 | plt.tight_layout() 174 | plt.show() 175 | 176 | #%% part e 177 | Ls = [32, 128, 512] 178 | 179 | for L in Ls: 180 | noverlap = L - 1 181 | NFFT = L 182 | rectangular_window = get_window('boxcar', L) 183 | triangular_window = get_window('triang', L) 184 | gauss_window = get_window(('gaussian', std), L) 185 | hamming_window = get_window('hamming', L) 186 | 187 | plt.figure(figsize=(26, 8)) 188 | plt.subplot(2, 2, 1) 189 | f, t, Sxx = spectrogram(x_t, fs, window=rectangular_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 190 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 191 | plt.title('Rectangular Window Frequency Transform') 192 | plt.xlabel('Time (s)') 193 | plt.ylabel('Frequency (Hz)') 194 | plt.colorbar(label="Power/Frequency (dB/Hz)") 195 | 196 | plt.subplot(2, 2, 2) 197 | f, t, Sxx = spectrogram(x_t, fs, window=triangular_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 198 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 199 | plt.title('Triangular Window Frequency Transform') 200 | plt.xlabel('Time (s)') 201 | plt.ylabel('Frequency (Hz)') 202 | plt.colorbar(label="Power/Frequency (dB/Hz)") 203 | 204 | plt.subplot(2, 2, 3) 205 | f, t, Sxx = spectrogram(x_t, fs, window=gauss_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 206 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 207 | plt.title('Gaussian Window Frequency Transform') 208 | plt.xlabel('Time (s)') 209 | plt.ylabel('Frequency (Hz)') 210 | plt.colorbar(label="Power/Frequency (dB/Hz)") 211 | 212 | plt.subplot(2, 2, 4) 213 | f, t, Sxx = spectrogram(x_t, fs, window=hamming_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 214 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 215 | plt.title('Hamming Window Frequency Transform') 216 | plt.xlabel('Time (s)') 217 | plt.ylabel('Frequency (Hz)') 218 | plt.colorbar(label="Power/Frequency (dB/Hz)") 219 | 220 | plt.suptitle("L = {}".format(L)) 221 | plt.tight_layout() 222 | plt.show() 223 | 224 | #%% part f 225 | L = 128 226 | NFFTs = [L, 2*L, 4*L] 227 | noverlap = L/2 228 | rectangular_window = get_window('boxcar', L) 229 | triangular_window = get_window('triang', L) 230 | gauss_window = get_window(('gaussian', std), L) 231 | hamming_window = get_window('hamming', L) 232 | 233 | for NFFT in NFFTs: 234 | plt.figure(figsize=(26, 8)) 235 | plt.subplot(2, 2, 1) 236 | f, t, Sxx = spectrogram(x_t, fs, window=rectangular_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 237 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 238 | plt.title('Rectangular Window Frequency Transform') 239 | plt.xlabel('Time (s)') 240 | plt.ylabel('Frequency (Hz)') 241 | plt.colorbar(label="Power/Frequency (dB/Hz)") 242 | 243 | plt.subplot(2, 2, 2) 244 | f, t, Sxx = spectrogram(x_t, fs, window=triangular_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 245 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 246 | plt.title('Triangular Window Frequency Transform') 247 | plt.xlabel('Time (s)') 248 | plt.ylabel('Frequency (Hz)') 249 | plt.colorbar(label="Power/Frequency (dB/Hz)") 250 | 251 | plt.subplot(2, 2, 3) 252 | f, t, Sxx = spectrogram(x_t, fs, window=gauss_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 253 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 254 | plt.title('Gaussian Window Frequency Transform') 255 | plt.xlabel('Time (s)') 256 | plt.ylabel('Frequency (Hz)') 257 | plt.colorbar(label="Power/Frequency (dB/Hz)") 258 | 259 | plt.subplot(2, 2, 4) 260 | f, t, Sxx = spectrogram(x_t, fs, window=hamming_window, nperseg=L, noverlap=noverlap, nfft=NFFT) 261 | plt.pcolormesh(t, f, 10 * np.log10(Sxx)) 262 | plt.title('Hamming Window Frequency Transform') 263 | plt.xlabel('Time (s)') 264 | plt.ylabel('Frequency (Hz)') 265 | plt.colorbar(label="Power/Frequency (dB/Hz)") 266 | 267 | plt.suptitle("NFFT = {}".format(NFFT)) 268 | plt.tight_layout() 269 | plt.show() 270 | 271 | 272 | #%% Question 2 273 | # part a 274 | from scipy.io import loadmat 275 | from scipy.fft import fft 276 | from scipy.signal import stft 277 | 278 | # Plot the time-domain signal 279 | signal = loadmat('D:/Sharif/Term 3/EEG/Comp HWs/Comp_HW1/NewEEGSignal.mat')['NewEEGSignal'][0] 280 | fs = 256 #Hz 281 | t = np.arange(0, 512) / fs 282 | 283 | plt.figure(figsize=(10, 4)) 284 | plt.plot(t, signal, 'k') 285 | plt.title("Time-Domain Signal") 286 | plt.xlabel("Time (sec)") 287 | plt.ylabel("Amplitude") 288 | plt.grid(True) 289 | plt.xlim(0, t[-1]) 290 | plt.tight_layout() 291 | plt.show() 292 | 293 | # Plot the frequency spectrum (DFT) 294 | dft = fft(signal) 295 | n = len(signal) 296 | freq = np.fft.fftfreq(n, 1/fs) 297 | positive_freq_indices = np.where(freq >= 0) 298 | plt.figure(figsize=(10, 4)) 299 | plt.plot(freq[positive_freq_indices], np.abs(dft[positive_freq_indices]), 'r') 300 | plt.title("Frequency Spectrum (DFT)") 301 | plt.xlabel("Frequency (Hz)") 302 | plt.ylabel("Amplitude") 303 | plt.grid(True) 304 | plt.xlim(0, fs/2) 305 | plt.tight_layout() 306 | plt.show() 307 | 308 | # Plot the STFT 309 | f, t, Zxx = stft(signal, fs=fs, nperseg=64) # You can adjust nperseg for desired time and frequency resolution 310 | plt.figure(figsize=(10, 4)) 311 | plt.pcolormesh(t, f, 20 * np.log10(np.abs(Zxx))) 312 | plt.title("Short-Time Fourier Transform (STFT)") 313 | plt.xlabel("Time (sec)") 314 | plt.ylabel("Frequency (Hz)") 315 | plt.colorbar(label="dB") 316 | plt.grid(True) 317 | plt.tight_layout() 318 | plt.show() 319 | 320 | #%% part b 321 | from scipy.signal import butter, lfilter, resample 322 | 323 | M = [1, 2, 4, 6, 8, 10] 324 | desireds_fs = np.array(M)*fs/10 325 | 326 | for desired_fs in desireds_fs: 327 | # Define the desired lower sampling rate 328 | t = np.arange(0, 512) / fs 329 | 330 | # Step 1: Apply a low-pass filter 331 | # Design a Butterworth low-pass filter 332 | nyquist = 0.5 * fs 333 | low_cutoff = 0.4999 * desired_fs 334 | b, a = butter(3, low_cutoff / nyquist, btype='low') 335 | 336 | # Apply the filter to the original signal 337 | filtered_signal = lfilter(b, a, signal) 338 | 339 | # Step 2: Downsample the filtered signal 340 | downsampled_signal = resample(filtered_signal, int(len(filtered_signal) * (desired_fs / fs)),) 341 | 342 | # Step 3: Plot the time signal, frequency spectrum (DFT), and STFT of the downsampled signal 343 | # Time signal 344 | plt.figure(figsize=(26, 8)) 345 | plt.subplot(2, 1, 1) 346 | plt.plot(t, signal, 'k', label="Original Signal", lw=2) 347 | plt.plot(np.arange(0, len(downsampled_signal)) / desired_fs, downsampled_signal, c='b', label="Downsampled Signal") 348 | plt.title("Time-Domain Signal") 349 | plt.xlabel("Time (sec)") 350 | plt.ylabel("Amplitude") 351 | plt.grid(True) 352 | plt.xlim(0, t[-1]) 353 | plt.legend() 354 | plt.tight_layout() 355 | plt.show() 356 | 357 | # Frequency Spectrum (DFT) of ds_EEG 358 | plt.subplot(2, 1, 2) 359 | dft = fft(signal) 360 | n = len(signal) 361 | freq = np.fft.fftfreq(n, 1/fs) 362 | positive_freq_indices = np.where(freq >= 0) 363 | plt.plot(freq[positive_freq_indices], np.abs(dft[positive_freq_indices]), 'r', label="Original Signal", lw=2) 364 | dft_ds_EEG = fft(downsampled_signal) 365 | freq_ds_EEG = np.fft.fftfreq(len(downsampled_signal), 1 / desired_fs) 366 | positive_freq_indices = np.where(freq_ds_EEG >= 0) 367 | 368 | plt.plot(freq_ds_EEG[positive_freq_indices], np.abs(dft_ds_EEG[positive_freq_indices]), c='b', label="Downsampled Signal") 369 | plt.title("Frequency Spectrum (DFT) of ds_EEG") 370 | plt.xlabel("Frequency (Hz)") 371 | plt.ylabel("Amplitude") 372 | plt.grid(True) 373 | plt.suptitle("Desired Fs = {}".format(desired_fs)) 374 | plt.tight_layout() 375 | plt.legend() 376 | plt.xlim(0, fs/2) 377 | plt.show() 378 | 379 | # STFT of ds_EEG 380 | f_ds_EEG, t_ds_EEG, Zxx_ds_EEG = stft(downsampled_signal, fs=desired_fs, nperseg=64) 381 | plt.figure(figsize=(26, 8)) 382 | plt.pcolormesh(t_ds_EEG, f_ds_EEG, 20 * np.log10(np.abs(Zxx_ds_EEG))) 383 | plt.title("STFT of ds_EEG") 384 | plt.xlabel("Time (s)") 385 | plt.ylabel("Frequency (Hz)") 386 | plt.colorbar(label="dB") 387 | plt.grid(True) 388 | plt.suptitle("Desired Fs = {} Hz".format(desired_fs)) 389 | plt.show() 390 | 391 | #%% part c 392 | 393 | M = [8] # factor downsample 394 | desireds_fs = np.array(M)*fs/10 395 | 396 | for desired_fs in desireds_fs: 397 | # Define the desired lower sampling rate 398 | t = np.arange(0, 512) / fs 399 | 400 | # Step 1: Apply a low-pass filter 401 | # Design a Butterworth low-pass filter 402 | nyquist = 0.5 * fs 403 | low_cutoff = 0.4999 * desired_fs 404 | b, a = butter(3, low_cutoff / nyquist, btype='low') 405 | 406 | # Apply the filter to the original signal 407 | filtered_signal = lfilter(b, a, signal) 408 | 409 | # Step 2: Downsample the filtered signal 410 | downsampled_signal = resample(filtered_signal, int(len(filtered_signal) * (desired_fs / fs)),) 411 | 412 | # Step 3: Plot the time signal, frequency spectrum (DFT), and STFT of the downsampled signal 413 | # Time signal 414 | plt.figure(figsize=(26, 8)) 415 | plt.subplot(2, 1, 1) 416 | plt.plot(t, signal, 'k', label="Original Signal", lw=2) 417 | plt.plot(np.arange(0, len(downsampled_signal)) / desired_fs, downsampled_signal, c='b', label="Downsampled Signal") 418 | plt.title("Time-Domain Signal") 419 | plt.xlabel("Time (sec)") 420 | plt.ylabel("Amplitude") 421 | plt.grid(True) 422 | plt.xlim(0, t[-1]) 423 | plt.legend() 424 | plt.tight_layout() 425 | plt.show() 426 | 427 | # Frequency Spectrum (DFT) of ds_EEG 428 | plt.subplot(2, 1, 2) 429 | 430 | dft = fft(signal) 431 | freq = np.fft.fftfreq(len(signal), 1/fs) 432 | positive_freq_indices = np.where(freq >= 0) 433 | plt.plot(freq[positive_freq_indices], np.abs(dft[positive_freq_indices]), 'k', label="Original Signal (NFFT = {})".format(len(signal)), lw=2) 434 | 435 | dft = fft(downsampled_signal) 436 | freq = np.fft.fftfreq(len(downsampled_signal), 1/desired_fs) 437 | positive_freq_indices = np.where(freq >= 0) 438 | plt.plot(freq[positive_freq_indices], np.abs(dft[positive_freq_indices]), 'r', label="Downsampled Signal (NFFT = {})".format(len(downsampled_signal)), lw=1.5) 439 | 440 | NFFT0 = len(downsampled_signal) 441 | NFFTs = [NFFT0//2, NFFT0//4, NFFT0//8] 442 | 443 | for NFFT in NFFTs: 444 | dft_ds_EEG = fft(downsampled_signal, NFFT) 445 | freq_ds_EEG = np.fft.fftfreq(len(dft_ds_EEG), 1/desired_fs) 446 | positive_freq_indices = np.where(freq_ds_EEG >= 0) 447 | plt.plot(freq_ds_EEG[positive_freq_indices], np.abs(dft_ds_EEG[positive_freq_indices]), label="Downsampled Signal (NFFT = {})".format(NFFT)) 448 | 449 | plt.title("Frequency Spectrum (DFT) of ds_EEG") 450 | plt.xlabel("Frequency (Hz)") 451 | plt.ylabel("Amplitude") 452 | plt.grid(True) 453 | plt.suptitle("Desired Fs = {} Hz".format(desired_fs)) 454 | plt.tight_layout() 455 | plt.legend() 456 | plt.xlim(0, fs/2) 457 | plt.show() 458 | 459 | # STFT of ds_EEG 460 | f_ds_EEG, t_ds_EEG, Zxx_ds_EEG = stft(downsampled_signal, fs=desired_fs, nperseg=64) 461 | plt.figure(figsize=(26, 8)) 462 | plt.pcolormesh(t_ds_EEG, f_ds_EEG, 20 * np.log10(np.abs(Zxx_ds_EEG))) 463 | plt.title("STFT of ds_EEG") 464 | plt.xlabel("Time (s)") 465 | plt.ylabel("Frequency (Hz)") 466 | plt.colorbar(label="dB") 467 | plt.grid(True) 468 | plt.suptitle("Desired Fs = {} Hz".format(desired_fs)) 469 | plt.show() 470 | 471 | #%% part d 472 | 473 | M = [8] # factor downsample 474 | desireds_fs = np.array(M)*fs/10 475 | 476 | for desired_fs in desireds_fs: 477 | t = np.arange(0, 512) / fs 478 | 479 | nyquist = 0.5 * fs 480 | low_cutoff = 0.4999 * desired_fs 481 | b, a = butter(3, low_cutoff / nyquist, btype='low') 482 | 483 | filtered_signal = lfilter(b, a, signal) 484 | 485 | downsampled_signal = resample(filtered_signal, int(len(filtered_signal) * (desired_fs / fs)),) 486 | 487 | plt.figure(figsize=(26, 8)) 488 | plt.subplot(2, 1, 1) 489 | plt.plot(t, signal, 'k', label="Original Signal", lw=2) 490 | plt.plot(np.arange(0, len(downsampled_signal)) / desired_fs, downsampled_signal, c='b', label="Downsampled Signal") 491 | plt.title("Time-Domain Signal") 492 | plt.xlabel("Time (sec)") 493 | plt.ylabel("Amplitude") 494 | plt.grid(True) 495 | plt.xlim(0, t[-1]) 496 | plt.legend() 497 | plt.tight_layout() 498 | plt.show() 499 | 500 | plt.figure() 501 | dft = fft(signal) 502 | freq = np.fft.fftfreq(len(signal), 1/fs) 503 | positive_freq_indices = np.where(freq >= 0) 504 | plt.plot(freq[positive_freq_indices], np.abs(dft[positive_freq_indices]), 'k', label="Original Signal (NFFT = {})".format(len(signal)), lw=2) 505 | 506 | dft = fft(downsampled_signal) 507 | freq = np.fft.fftfreq(len(downsampled_signal), 1/desired_fs) 508 | positive_freq_indices = np.where(freq >= 0) 509 | plt.plot(freq[positive_freq_indices], np.abs(dft[positive_freq_indices]), 'r', label="Downsampled Signal (NFFT = {})".format(len(downsampled_signal)), lw=1.5) 510 | 511 | NFFT0 = len(downsampled_signal) 512 | NFFTs = [NFFT0//2, NFFT0//4, NFFT0//8] 513 | 514 | for NFFT in NFFTs: 515 | EEG_ds2 = np.zeros(NFFT0) 516 | EEG_ds2[:NFFT] = downsampled_signal[:NFFT] 517 | dft_ds_EEG = fft(EEG_ds2, NFFT0) 518 | freq_ds_EEG = np.fft.fftfreq(len(dft_ds_EEG), 1/desired_fs) 519 | positive_freq_indices = np.where(freq_ds_EEG >= 0) 520 | plt.plot(freq_ds_EEG[positive_freq_indices], np.abs(dft_ds_EEG[positive_freq_indices]), label="Downsampled Signal (NFFT = {})".format(NFFT)) 521 | 522 | plt.title("Frequency Spectrum (DFT) of ds_EEG") 523 | plt.xlabel("Frequency (Hz)") 524 | plt.ylabel("Amplitude") 525 | plt.grid(True) 526 | plt.suptitle("Desired Fs = {} Hz".format(desired_fs)) 527 | plt.tight_layout() 528 | plt.legend() 529 | plt.xlim(0, fs/2) 530 | plt.show() 531 | 532 | # STFT of ds_EEG 533 | f_ds_EEG, t_ds_EEG, Zxx_ds_EEG = stft(downsampled_signal, fs=desired_fs, nperseg=64) 534 | plt.figure(figsize=(26, 8)) 535 | plt.pcolormesh(t_ds_EEG, f_ds_EEG, 20 * np.log10(np.abs(Zxx_ds_EEG))) 536 | plt.title("STFT of ds_EEG") 537 | plt.xlabel("Time (s)") 538 | plt.ylabel("Frequency (Hz)") 539 | plt.colorbar(label="dB") 540 | plt.grid(True) 541 | plt.suptitle("Desired Fs = {} Hz".format(desired_fs)) 542 | plt.show() 543 | 544 | #%% Question 3 545 | import scipy.io as sio 546 | import numpy as np 547 | import matplotlib.pyplot as plt 548 | from scipy.signal import periodogram, welch 549 | from scipy.io import loadmat 550 | from scipy.fft import fft 551 | from scipy.signal import stft 552 | 553 | # Load the signal from the .mat file 554 | signal = loadmat('D:/Sharif/Term 3/EEG/Comp HWs/Comp_HW1/NewEEGSignal.mat')['NewEEGSignal'][0] 555 | fs = 256 #Hz 556 | t = np.arange(0, 512) / fs 557 | 558 | # a) Autocorrelation method 559 | autocorr = np.correlate(signal, signal, mode='full') 560 | power_autocorr = np.abs(np.fft.fft(autocorr)) 561 | freqs = np.fft.fftfreq(len(power_autocorr), 1/fs) 562 | plt.figure() 563 | plt.plot(freqs[:512], power_autocorr[:512]) 564 | plt.title('Power Spectrum Density (Autocorrelation Method)') 565 | plt.xlabel('Frequency (Hz)') 566 | plt.ylabel('Power') 567 | plt.xlim([0, 129]) 568 | 569 | # b) Periodogram method 570 | freqs, power_periodogram = periodogram(signal) 571 | plt.figure() 572 | plt.plot(freqs*fs, power_periodogram, 'r') 573 | plt.title('Power Spectrum Density (Periodogram Method)') 574 | plt.xlabel('Frequency') 575 | plt.ylabel('Power') 576 | plt.xlim([0, 129]) 577 | 578 | # c) Welch method 579 | freqs, power_welch = welch(signal) 580 | plt.figure() 581 | plt.plot(freqs*fs, power_welch, 'g') 582 | plt.title('Power Spectrum Density (Welch Method)') 583 | plt.xlabel('Frequency') 584 | plt.ylabel('Power') 585 | plt.xlim([0, 129]) 586 | plt.show() 587 | 588 | 589 | --------------------------------------------------------------------------------