import numpy as np
import matplotlib.pyplot as plt
from scipy.io import loadmat
from scipy.signal import firls, filtfilt
from scipy.fft import fft, ifft
Chapter 12
Chapter 12
Analyzing Neural Time Series Data
Python code for Chapter 12 – converted from original Matlab by AE Studio (and ChatGPT)
Original Matlab code by Mike X Cohen
This code accompanies the book, titled “Analyzing Neural Time Series Data” (MIT Press).
Using the code without following the book may lead to confusion, incorrect data analyses, and misinterpretations of results.
Mike X Cohen and AE Studio assume no responsibility for inappropriate or incorrect use of this code.
Import necessary libraries
Figure 12.1
# Load sample EEG data
= loadmat('../data/sampleEEGdata.mat')['EEG'][0, 0]
EEG
# Define time and frequency for the sine wave
= np.arange(-1, 1 + 1/EEG['srate'], 1/EEG['srate'])
time = 4 # frequency of sine wave in Hz
f
# Create sine wave (actually, a cosine wave)
= np.cos(2 * np.pi * f * time)
sine_wave
# Make a Gaussian
= 4 / (2 * np.pi * f)
s = np.exp(-time**2 / (2 * s**2))
gaussian_win
# Plot the sine wave multiplied by the Gaussian window
plt.figure()* gaussian_win)
plt.plot(time, sine_wave plt.show()
Figure 12.2
# Plotting EEG data and sine waves with different envelopes
=(10, 8))
plt.figure(figsize
# Plot EEG data from one electrode and one trial
511)
plt.subplot('data'][46,:,0]))
plt.plot(np.squeeze(EEG[=True, axis='both', tight=True)
plt.gca().autoscale(enable'EEG data (electrode 47, trial 1)')
plt.title(
# Plot sine wave at 12 Hz
512)
plt.subplot(= np.cos(2 * np.pi * 12 * time)
sine_wave
plt.plot(sine_wave)=True, axis='both', tight=True)
plt.gca().autoscale(enable'12 Hz sine wave')
plt.title(
# Plot sine wave with boxcar envelope (short duration)
513)
plt.subplot(= np.zeros_like(sine_wave)
boxcar = (len(time) - 1) // 2
midpoint - round(EEG['srate'][0,0] / 12 / 5):midpoint + round(EEG['srate'][0,0] / 12 / 1.25)] = 1
boxcar[midpoint * boxcar)
plt.plot(sine_wave =True, axis='both', tight=True)
plt.gca().autoscale(enable'Sine wave with short boxcar envelope')
plt.title(
# Plot sine wave with boxcar envelope (long duration)
514)
plt.subplot(= np.zeros_like(sine_wave)
boxcar - 50:midpoint + 50] = 1
boxcar[midpoint * boxcar)
plt.plot(sine_wave =True, axis='both', tight=True)
plt.gca().autoscale(enable'Sine wave with long boxcar envelope')
plt.title(
# Plot sine wave with Gaussian envelope
515)
plt.subplot(= 1.5 / (2 * np.pi * 12)
s = np.exp(-time**2 / (2 * s**2))
gaussian_win * gaussian_win)
plt.plot(time, sine_wave =True, axis='both', tight=True)
plt.gca().autoscale(enable'Sine wave with Gaussian envelope')
plt.title(
plt.tight_layout() plt.show()
Figure 12.3
# Create a complex wavelet and plot its components
= 500 # sampling rate in Hz
srate = 10 # frequency of the sine wave in Hz
f = np.arange(-1, 1 + 1/srate, 1/srate) # time vector
time
# Create a complex sine wave (wavelet)
= np.exp(2 * np.pi * 1j * f * time)
sine_wave
# Make a Gaussian
= 6 / (2 * np.pi * f)
s = np.exp(-time**2 / (2 * s**2))
gaussian_win
# Combine sine wave and Gaussian to create a wavelet
= sine_wave * gaussian_win
wavelet
# Plot each component and the wavelet
=(8, 6))
plt.figure(figsize
311)
plt.subplot(
plt.plot(time, np.real(sine_wave))=True, axis='both', tight=True)
plt.gca().autoscale(enable'Sine wave')
plt.title(
312)
plt.subplot(
plt.plot(time, gaussian_win)=True, axis='both', tight=True)
plt.gca().autoscale(enable'Gaussian window')
plt.title(
313)
plt.subplot(
plt.plot(time, np.real(wavelet))'My first wavelet!')
plt.title(-1, 1])
plt.xlim([-1, 1])
plt.ylim(['Time (ms)')
plt.xlabel(
plt.tight_layout() plt.show()
Figure 12.4
# Create a family of wavelets
= 80
num_wavelets = 2
lowest_frequency = 100
highest_frequency
# Define frequencies
= np.linspace(lowest_frequency, highest_frequency, num_wavelets)
frequencies
# Plot frequency order vs. frequency in Hz
plt.figure()'-*')
plt.plot(frequencies, 'Frequency order')
plt.xlabel('Frequency in Hz')
plt.ylabel(
plt.show()
# Initialize wavelet family
= np.zeros((num_wavelets, len(time)), dtype=complex)
wavelet_family
# Loop through frequencies and create wavelets
for fi in range(num_wavelets):
= np.exp(2 * 1j * np.pi * frequencies[fi] * time)
sinewave = np.exp(-time**2 / (2 * (6 / (2 * np.pi * frequencies[fi]))**2))
gaus_win = sinewave * gaus_win
wavelet_family[fi, :]
# Plot a few wavelets
=(8, 6))
plt.figure(figsize
211)
plt.subplot(round(np.random.rand() * 30), :]).T)
plt.plot(time, np.real(wavelet_family[::-1, 1])
plt.xlim([-1, 1])
plt.ylim(['A few wavelets...')
plt.title(
212)
plt.subplot(29, :]))
plt.plot(time, np.real(wavelet_family[29, :]), ':')
plt.plot(time, np.imag(wavelet_family[-1, 1])
plt.xlim([-1, 1])
plt.ylim(['Real and imaginary parts of one wavelet')
plt.title('real', 'imaginary'])
plt.legend([
plt.tight_layout()
plt.show()
# Image the wavelet family
plt.figure()='auto', extent=[time[0], time[-1], frequencies[0], frequencies[-1]], origin='lower')
plt.imshow(np.real(wavelet_family), aspect'Time (s)')
plt.xlabel('Frequency (Hz)')
plt.ylabel( plt.show()
Figure 12.5
# EEG data from one trial (electrode FCz)
= EEG['data'][46, :, 9]
eegdata
# Create wavelet
= np.arange(-1, 1+1/EEG['srate'][0, 0], 1/EEG['srate'][0, 0])
time = 6 # frequency of sine wave in Hz
f = np.exp(1j * 2 * np.pi * f * time)
sine_wave = 4.5 / (2 * np.pi * f)
s = np.exp(-time**2 / (2 * s**2))
gaussian_win = sine_wave * gaussian_win
wavelet = int(np.ceil(len(wavelet) / 2))
halfwaveletsize
# Convolve with data
= len(wavelet) + EEG['pnts'][0, 0] - 1
n_conv = fft(wavelet, n_conv)
fft_w = fft(eegdata, n_conv)
fft_e = ifft(fft_e * fft_w, n_conv) * np.sqrt(s) / 10 # empirical scaling factor
ift = np.real(ift[halfwaveletsize-1:-halfwaveletsize+1])
wavelet_conv_data
# Create filter and apply to data
= EEG['srate'][0, 0] / 2
nyquist = 0.2 # percent
transition_width = 4 # Hz
filter_low = 8 # Hz
filter_high = [0, filter_low * (1 - transition_width), filter_low, filter_high, filter_high * (1 + transition_width), nyquist] / nyquist
ffrequencies = [0, 0, 1, 1, 0, 0]
idealresponse = firls(round(3 * (EEG['srate'][0, 0] / filter_low))+1, ffrequencies, idealresponse)
filterweights = filtfilt(filterweights, 1, eegdata)
eeg_4to8
# Plot raw data, wavelet convolved data, and band-pass filtered data
plt.figure()'times'][0], eegdata)
plt.plot(EEG['times'][0], wavelet_conv_data, 'r', linewidth=2)
plt.plot(EEG['times'][0], eeg_4to8, 'm', linewidth=2)
plt.plot(EEG[-200, 1200])
plt.xlim([-50, 40])
plt.ylim(['Time (ms)')
plt.xlabel('Voltage (µV)')
plt.ylabel(
plt.gca().invert_yaxis()'Raw data', 'Wavelet convolved', 'Band-pass filtered'])
plt.legend([ plt.show()
Figure 12.6
# Make a theta-band-centered wavelet
= np.arange(-1, 1+1/EEG['srate'][0, 0], 1/EEG['srate'][0, 0])
time = EEG['pnts'][0, 0] + len(time) - 1
n_conv = n_conv // 2 + 1
n2p1
= 6
f = 6 / (2 * np.pi * f)
s = np.exp(2 * np.pi * 1j * f * time) * np.exp(-time**2 / (2 * s**2))
wavelet = int(np.ceil(len(wavelet) / 2))
halfwaveletsize
= EEG['data'][46,:,9]
eegdata
=(10, 8))
plt.figure(figsize
311)
plt.subplot('times'][0], eegdata)
plt.plot(EEG[-500, 1200])
plt.xlim([-50, 50])
plt.ylim([
323)
plt.subplot(= fft(wavelet, n_conv)
fft_w = np.linspace(0, EEG['srate'][0, 0] / 2, n2p1)
hz abs(fft_w[:n2p1]) / np.max(np.abs(fft_w[:n2p1])), 'k')
plt.plot(hz, np.
= fft(eegdata, n_conv)
fft_e abs(fft_e[:n2p1]) / np.max(np.abs(fft_e[:n2p1])), 'r')
plt.plot(hz, np.0, 40])
plt.xlim([0, 1.05])
plt.ylim(['Individual power spectra')
plt.title(
324)
plt.subplot(abs(fft_e[:n2p1]) * np.abs(fft_w[:n2p1]))
plt.plot(hz, np.0, 40])
plt.xlim([
313)
plt.subplot('times'][0], eegdata)
plt.plot(EEG[= ifft(fft_e * fft_w, n_conv) * np.sqrt(s) / 10
ift 'times'][0], np.real(ift[halfwaveletsize-1:-halfwaveletsize+1]), 'r')
plt.plot(EEG[-500, 1200])
plt.xlim([-50, 50])
plt.ylim([
plt.tight_layout() plt.show()
Figure 12.7
# Create 10 Hz wavelet (kernel)
= np.arange(-(EEG['pnts'][0, 0]/EEG['srate'][0, 0]/2), EEG['pnts'][0, 0]/EEG['srate'][0, 0]/2, 1/EEG['srate'][0, 0])
time = 10 # frequency of sine wave in Hz
f = 4 / (2 * np.pi * f)
s = np.cos(2 * np.pi * f * time) * np.exp(-time**2 / (2 * s**2))
wavelet
# Signal is one sine cycle
= np.arange(0, 1/f, 1/EEG['srate'][0, 0])
timeS = np.sin(2 * np.pi * f * timeS)
signal
# Zero-pad signal
= np.concatenate((np.zeros(EEG['pnts'][0, 0]//2 - len(timeS)//2), signal, np.zeros(EEG['pnts'][0, 0]//2 - len(timeS)//2)))
signal
=(10, 8))
plt.figure(figsize
# Plot wavelet
321)
plt.subplot(
plt.plot(wavelet)200, len(time) - 200])
plt.xlim([
# Plot signal
323)
plt.subplot(
plt.plot(signal)200, len(time) - 200])
plt.xlim([
# Plot convolution of wavelet and signal
325)
plt.subplot('same'))
plt.plot(np.convolve(wavelet, signal, 200, len(time) - 200])
plt.xlim([-12, 12])
plt.ylim([
# Plot dot products at selected phase lags
322)
plt.subplot(round(100/f)-2:], 'r')
plt.plot(wavelet[
plt.plot(signal)200, len(time) - 200])
plt.xlim([f'Dot product: {int(np.fix(np.sum(wavelet[round(100/f)-3:] * signal[:-round(100/f)+3])))}')
plt.title(
324)
plt.subplot(round(2.3*(100/f)-2):], 'r')
plt.plot(wavelet[
plt.plot(signal)200, len(time) - 200])
plt.xlim([f'Dot product: {int(np.fix(np.sum(wavelet[round(2.3*(100/f)-3):] * signal[:-round(2.3*(100/f)-3)])))}')
plt.title(
326)
plt.subplot('r')
plt.plot(wavelet,
plt.plot(signal)200, len(time) - 200])
plt.xlim([f'Dot product: {int(np.fix(np.sum(wavelet * signal)))}')
plt.title(
plt.tight_layout() plt.show()