import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import detrend
from scipy.signal.windows import dpss
from scipy.fft import fft
from scipy.io import loadmat
Chapter 16
Chapter 16
Analyzing Neural Time Series Data
Python code for Chapter 16 – 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 16.1
# Load sample EEG data
= loadmat('../data/sampleEEGdata.mat')['EEG'][0, 0]
EEG
# Define parameters
= 'O1'
channel2plot = 400 # in ms
timewin
# Calculate the number of indices that correspond to the time window
= round(timewin / (1000 / EEG['srate'][0][0]))
timewinidx = dpss(timewinidx, 5, 5)
tapers
# Extract a bit of EEG data
= detrend(np.squeeze(EEG['data'][EEG['chanlocs'][0]['labels']==channel2plot, 199:199 + timewinidx, 9]))
d
# Plot EEG data snippet
=(10, 10))
plt.figure(figsize5, 2, 1)
plt.subplot(
plt.plot(d)=True, axis='both', tight=True)
plt.gca().autoscale(enable'off')
plt.axis(
# Plot tapers
for i in range(5):
5, 2, (2 * (i)) + 2)
plt.subplot(
plt.plot(tapers[i, :])=True, axis='both', tight=True)
plt.gca().autoscale(enable'off')
plt.axis(
plt.tight_layout()
plt.show()
# Plot taper.*data
=(10, 10))
plt.figure(figsizefor i in range(5):
5, 2, (2 * (i)) + 1)
plt.subplot(* d)
plt.plot(tapers[i, :] =True, axis='both', tight=True)
plt.gca().autoscale(enable'off')
plt.axis(
# Plot fft of taper.*data
= np.zeros((5, timewinidx)) * 1j
f for i in range(5):
5, 2, (2 * (i)) + 2)
plt.subplot(= fft(tapers[i, :] * d)
f[i, :] abs(f[i, :timewinidx // 2]) ** 2)
plt.plot(np.=True, axis='both', tight=True)
plt.gca().autoscale(enable'off')
plt.axis(
plt.tight_layout()
plt.show()
=(10, 10))
plt.figure(figsize5, 2, 2)
plt.subplot(abs(f[:, :timewinidx // 2]) ** 2, axis=0))
plt.plot(np.mean(np.=True, axis='both', tight=True)
plt.gca().autoscale(enable'off')
plt.axis(
5, 2, 3)
plt.subplot(= 0.5 * (1 - np.cos(2 * np.pi * np.arange(1, timewinidx + 1) / (timewinidx - 1)))
hann
plt.plot(hann)=True, axis='both', tight=True)
plt.gca().autoscale(enable'off')
plt.axis(
5, 2, 5)
plt.subplot(* d)
plt.plot(hann =True, axis='both', tight=True)
plt.gca().autoscale(enable'off')
plt.axis(
5, 2, 6)
plt.subplot(= fft(hann * d)
ff abs(ff[:timewinidx // 2]) ** 2)
plt.plot(np.=True, axis='both', tight=True)
plt.gca().autoscale(enable'off')
plt.axis(
plt.tight_layout() plt.show()
Figure 16.2
# Define parameters
= 'P7'
channel2plot = 15 # in Hz
frequency2plot = 200 # ms
timepoint2plot
= 3 # determines the frequency smoothing, given a specified time window
nw_product = np.arange(-300, 1050, 50)
times2save = [-200, 0]
baseline_range = 400 # in ms
timewin
# Convert time points to indices
= np.array([np.argmin(np.abs(EEG['times'][0] - t)) for t in times2save])
times2saveidx = round(timewin / (1000 / EEG['srate'][0][0]))
timewinidx
# Find baseline time points
= [np.argmin(np.abs(times2save - br)) for br in baseline_range]
baseidx
# Define tapers
= dpss(timewinidx, nw_product, 6)
tapers # Define frequencies for FFT
= np.linspace(0, EEG['srate'][0][0] / 2, int(np.floor(timewinidx / 2)) + 1)
f
# Find logical channel index
= EEG['chanlocs'][0]['labels']==channel2plot
chanidx
# Initialize output matrix
= np.zeros((int(np.floor(timewinidx / 2) + 1), len(times2save)))
multitaper_tf
# Loop through time bins
for ti, idx in enumerate(times2saveidx):
# Initialize power vector (over tapers)
= np.zeros(int(np.floor(timewinidx / 2) + 1)) * 1j
taperpow
# Loop through tapers
for tapi in range(tapers.shape[0] - 1):
# Window and taper data, and get power spectrum
= np.squeeze(EEG['data'][chanidx, idx - int(np.floor(timewinidx / 2)) + 1:idx + int(np.ceil(timewinidx / 2)) + 1, :]) * tapers[tapi, :][:, np.newaxis]
data pow = fft(data, timewinidx, axis=0) / timewinidx
pow = pow[:int(np.floor(timewinidx / 2) + 1), :]
+= np.mean(pow * np.conj(pow), axis=1)
taperpow
# Finally, get power from closest frequency
= np.real(taperpow / (tapi + 1))
multitaper_tf[:, ti]
# dB-correct
= 10 * np.log10(multitaper_tf / np.mean(multitaper_tf[:, baseidx[0]:baseidx[1]+1], axis=1)[:, np.newaxis])
db_multitaper_tf
# Plot time courses at one frequency band
=(12, 6))
plt.figure(figsize121)
plt.subplot(= np.argmin(np.abs(f - frequency2plot)) + 1
freq2plotidx - 3:freq2plotidx + 2, :]), axis=0))
plt.plot(times2save, np.mean(np.log10(multitaper_tf[freq2plotidx f'Sensor {channel2plot}, {frequency2plot} Hz')
plt.title(0], times2save[-1])
plt.xlim(times2save[
122)
plt.subplot(= np.argmin(np.abs(times2save - timepoint2plot))
time2plotidx
plt.plot(f, np.log10(multitaper_tf[:, time2plotidx]))f'Sensor {channel2plot}, {timepoint2plot} ms')
plt.title(0], 40)
plt.xlim(f[
# Plot full TF map
=(8, 6))
plt.figure(figsize40, cmap='viridis')
plt.contourf(times2save, f, db_multitaper_tf, -2, 2)
plt.clim('Time (ms)')
plt.xlabel('Frequency (Hz)')
plt.ylabel(f'Power via multitaper from channel {channel2plot}')
plt.title( plt.show()