Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ok-landscape
GitHub Repository: Ok-landscape/computational-pipeline
Path: blob/main/latex-templates/templates/neuroscience/eeg_analysis.tex
75 views
unlisted
1
\documentclass[a4paper, 11pt]{report}
2
\usepackage[utf8]{inputenc}
3
\usepackage[T1]{fontenc}
4
\usepackage{amsmath, amssymb}
5
\usepackage{graphicx}
6
\usepackage{siunitx}
7
\usepackage{booktabs}
8
\usepackage{xcolor}
9
\usepackage[makestderr]{pythontex}
10
11
\definecolor{delta}{RGB}{155, 89, 182}
12
\definecolor{theta}{RGB}{46, 204, 113}
13
\definecolor{alpha}{RGB}{241, 196, 15}
14
\definecolor{beta}{RGB}{231, 76, 60}
15
16
\title{EEG Signal Analysis:\\
17
Spectral Decomposition and Brain State Classification}
18
\author{Department of Neuroscience\\Technical Report NS-2024-002}
19
\date{\today}
20
21
\begin{document}
22
\maketitle
23
24
\begin{abstract}
25
This report presents a comprehensive analysis of electroencephalography (EEG) signal processing. We implement spectral analysis methods, extract frequency band power, compute event-related potentials, analyze connectivity measures, and classify brain states. All computations use PythonTeX for reproducibility.
26
\end{abstract}
27
28
\tableofcontents
29
30
\chapter{Introduction}
31
32
EEG measures electrical activity from the scalp. The power spectral density characterizes brain rhythms:
33
\begin{equation}
34
S_{xx}(f) = \lim_{T\to\infty}\frac{1}{T}\left|\int_0^T x(t)e^{-i2\pi ft}dt\right|^2
35
\end{equation}
36
37
\section{EEG Frequency Bands}
38
\begin{itemize}
39
\item Delta ($\delta$): 1-4 Hz (deep sleep)
40
\item Theta ($\theta$): 4-8 Hz (drowsiness, meditation)
41
\item Alpha ($\alpha$): 8-13 Hz (relaxed wakefulness)
42
\item Beta ($\beta$): 13-30 Hz (active thinking)
43
\item Gamma ($\gamma$): 30-100 Hz (cognitive processing)
44
\end{itemize}
45
46
\begin{pycode}
47
import numpy as np
48
from scipy import signal
49
from scipy.fft import fft, fftfreq
50
import matplotlib.pyplot as plt
51
plt.rc('text', usetex=True)
52
plt.rc('font', family='serif')
53
54
np.random.seed(42)
55
56
# Sampling parameters
57
fs = 256
58
duration = 20
59
t = np.arange(0, duration, 1/fs)
60
n_samples = len(t)
61
62
# Generate realistic EEG
63
def generate_eeg(t, fs, alpha_amp=50, beta_amp=20, theta_amp=30, delta_amp=40):
64
# Oscillatory components
65
alpha = alpha_amp * np.sin(2 * np.pi * 10 * t + np.random.rand() * 2 * np.pi)
66
beta = beta_amp * np.sin(2 * np.pi * 22 * t + np.random.rand() * 2 * np.pi)
67
theta = theta_amp * np.sin(2 * np.pi * 6 * t + np.random.rand() * 2 * np.pi)
68
delta = delta_amp * np.sin(2 * np.pi * 2 * t + np.random.rand() * 2 * np.pi)
69
70
# Pink noise (1/f)
71
freqs = fftfreq(len(t), 1/fs)
72
pink_spectrum = np.where(freqs == 0, 1, 1/np.abs(freqs)**0.5)
73
noise = np.real(np.fft.ifft(pink_spectrum * fft(np.random.randn(len(t)))))
74
noise = 30 * noise / np.std(noise)
75
76
return alpha + beta + theta + delta + noise
77
78
eeg = generate_eeg(t, fs)
79
80
# Add eye blink artifact
81
blink_times = [3, 8, 15]
82
for bt in blink_times:
83
blink_idx = (t > bt) & (t < bt + 0.3)
84
eeg[blink_idx] += -150 * np.sin(np.pi * (t[blink_idx] - bt) / 0.3)
85
86
def band_power(f, psd, low, high):
87
idx = (f >= low) & (f <= high)
88
return np.trapz(psd[idx], f[idx])
89
90
bands = {
91
'Delta': (1, 4),
92
'Theta': (4, 8),
93
'Alpha': (8, 13),
94
'Beta': (13, 30),
95
'Gamma': (30, 50)
96
}
97
\end{pycode}
98
99
\chapter{Spectral Analysis}
100
101
\begin{pycode}
102
fig, axes = plt.subplots(2, 3, figsize=(14, 8))
103
104
# Raw EEG
105
ax = axes[0, 0]
106
ax.plot(t, eeg, 'b-', linewidth=0.5)
107
ax.set_xlabel('Time (s)')
108
ax.set_ylabel('Amplitude ($\\mu$V)')
109
ax.set_title('Raw EEG Signal')
110
ax.set_xlim([0, 5])
111
ax.grid(True, alpha=0.3)
112
113
# Power spectral density
114
f_psd, psd = signal.welch(eeg, fs=fs, nperseg=fs*2)
115
116
ax = axes[0, 1]
117
ax.semilogy(f_psd, psd, 'b-', linewidth=1)
118
colors = ['#9b59b6', '#2ecc71', '#f1c40f', '#e74c3c', '#3498db']
119
for (name, (low, high)), color in zip(bands.items(), colors):
120
ax.axvspan(low, high, alpha=0.2, color=color, label=name)
121
ax.set_xlabel('Frequency (Hz)')
122
ax.set_ylabel('PSD ($\\mu$V$^2$/Hz)')
123
ax.set_title('Power Spectral Density')
124
ax.set_xlim([0, 50])
125
ax.legend(fontsize=7)
126
ax.grid(True, alpha=0.3, which='both')
127
128
# Spectrogram
129
f_spec, t_spec, Sxx = signal.spectrogram(eeg, fs=fs, nperseg=fs, noverlap=fs//2)
130
131
ax = axes[0, 2]
132
im = ax.pcolormesh(t_spec, f_spec, 10*np.log10(Sxx), shading='auto', cmap='viridis')
133
ax.set_xlabel('Time (s)')
134
ax.set_ylabel('Frequency (Hz)')
135
ax.set_title('Spectrogram')
136
ax.set_ylim([0, 50])
137
plt.colorbar(im, ax=ax, label='dB')
138
139
# Band power
140
band_powers = {}
141
for name, (low, high) in bands.items():
142
band_powers[name] = band_power(f_psd, psd, low, high)
143
144
total_power = sum(band_powers.values())
145
relative_powers = {k: v/total_power*100 for k, v in band_powers.items()}
146
147
ax = axes[1, 0]
148
ax.bar(bands.keys(), relative_powers.values(), color=colors, alpha=0.7)
149
ax.set_xlabel('Frequency Band')
150
ax.set_ylabel('Relative Power (\\%)')
151
ax.set_title('Band Power Distribution')
152
ax.grid(True, alpha=0.3, axis='y')
153
154
# Time-frequency with wavelets
155
ax = axes[1, 1]
156
freqs_cwt = np.arange(1, 50, 1)
157
widths = fs / (2 * freqs_cwt)
158
cwt = signal.cwt(eeg[:fs*5], signal.morlet2, widths, w=5)
159
160
ax.pcolormesh(t[:fs*5], freqs_cwt, np.abs(cwt), shading='auto', cmap='hot')
161
ax.set_xlabel('Time (s)')
162
ax.set_ylabel('Frequency (Hz)')
163
ax.set_title('Wavelet Transform')
164
165
# Alpha/beta ratio (arousal)
166
ax = axes[1, 2]
167
window_size = fs * 2
168
n_windows = len(eeg) // window_size
169
alpha_beta_ratio = []
170
t_windows = []
171
172
for i in range(n_windows):
173
segment = eeg[i*window_size:(i+1)*window_size]
174
f_seg, psd_seg = signal.welch(segment, fs=fs, nperseg=fs)
175
alpha_power = band_power(f_seg, psd_seg, 8, 13)
176
beta_power = band_power(f_seg, psd_seg, 13, 30)
177
alpha_beta_ratio.append(alpha_power / (beta_power + 1e-10))
178
t_windows.append((i + 0.5) * window_size / fs)
179
180
ax.plot(t_windows, alpha_beta_ratio, 'g-o', markersize=4)
181
ax.set_xlabel('Time (s)')
182
ax.set_ylabel('Alpha/Beta Ratio')
183
ax.set_title('Arousal Index')
184
ax.grid(True, alpha=0.3)
185
186
plt.tight_layout()
187
plt.savefig('eeg_spectral.pdf', dpi=150, bbox_inches='tight')
188
plt.close()
189
190
mean_alpha_beta = np.mean(alpha_beta_ratio)
191
\end{pycode}
192
193
\begin{figure}[htbp]
194
\centering
195
\includegraphics[width=0.95\textwidth]{eeg_spectral.pdf}
196
\caption{EEG spectral analysis: (a) raw signal, (b) PSD, (c) spectrogram, (d) band power, (e) wavelet, (f) arousal index.}
197
\end{figure}
198
199
\chapter{Event-Related Potentials}
200
201
\begin{pycode}
202
# Simulate ERP
203
n_trials = 50
204
epoch_length = int(fs * 1) # 1 second epochs
205
erp_times = np.arange(-0.2, 0.8, 1/fs)
206
207
# Generate epochs with ERP components
208
epochs = []
209
for _ in range(n_trials):
210
noise = 20 * np.random.randn(epoch_length)
211
212
# P100
213
p100 = 5 * np.exp(-((erp_times - 0.1)**2) / (2 * 0.02**2))
214
# N200
215
n200 = -8 * np.exp(-((erp_times - 0.2)**2) / (2 * 0.03**2))
216
# P300
217
p300 = 10 * np.exp(-((erp_times - 0.35)**2) / (2 * 0.05**2))
218
219
epoch = noise + p100 + n200 + p300
220
epochs.append(epoch)
221
222
epochs = np.array(epochs)
223
erp = np.mean(epochs, axis=0)
224
erp_std = np.std(epochs, axis=0)
225
226
fig, axes = plt.subplots(2, 3, figsize=(14, 8))
227
228
# Single trials
229
ax = axes[0, 0]
230
for i in range(min(10, n_trials)):
231
ax.plot(erp_times * 1000, epochs[i], alpha=0.3, linewidth=0.5)
232
ax.set_xlabel('Time (ms)')
233
ax.set_ylabel('Amplitude ($\\mu$V)')
234
ax.set_title('Single Trials')
235
ax.axvline(0, color='r', linestyle='--', alpha=0.5)
236
ax.grid(True, alpha=0.3)
237
238
# Average ERP
239
ax = axes[0, 1]
240
ax.plot(erp_times * 1000, erp, 'b-', linewidth=2)
241
ax.fill_between(erp_times * 1000, erp - erp_std/np.sqrt(n_trials),
242
erp + erp_std/np.sqrt(n_trials), alpha=0.3)
243
ax.axvline(0, color='r', linestyle='--', alpha=0.5)
244
ax.axhline(0, color='gray', linestyle='-', alpha=0.3)
245
246
# Mark components
247
ax.annotate('P100', xy=(100, 5), fontsize=9)
248
ax.annotate('N200', xy=(200, -8), fontsize=9)
249
ax.annotate('P300', xy=(350, 10), fontsize=9)
250
251
ax.set_xlabel('Time (ms)')
252
ax.set_ylabel('Amplitude ($\\mu$V)')
253
ax.set_title(f'Average ERP (N={n_trials})')
254
ax.grid(True, alpha=0.3)
255
256
# ERP image
257
ax = axes[0, 2]
258
im = ax.imshow(epochs, aspect='auto', cmap='RdBu_r',
259
extent=[erp_times[0]*1000, erp_times[-1]*1000, n_trials, 0],
260
vmin=-50, vmax=50)
261
ax.axvline(0, color='k', linestyle='--', alpha=0.5)
262
ax.set_xlabel('Time (ms)')
263
ax.set_ylabel('Trial')
264
ax.set_title('ERP Image')
265
plt.colorbar(im, ax=ax, label='$\\mu$V')
266
267
# Global field power
268
gfp = np.std(epochs, axis=0)
269
ax = axes[1, 0]
270
ax.plot(erp_times * 1000, gfp, 'purple', linewidth=2)
271
ax.axvline(0, color='r', linestyle='--', alpha=0.5)
272
ax.set_xlabel('Time (ms)')
273
ax.set_ylabel('GFP ($\\mu$V)')
274
ax.set_title('Global Field Power')
275
ax.grid(True, alpha=0.3)
276
277
# SNR improvement
278
snr_single = np.max(np.abs(epochs[0])) / np.std(epochs[0])
279
snr_average = np.max(np.abs(erp)) / (np.std(epochs) / np.sqrt(n_trials))
280
281
ax = axes[1, 1]
282
trial_counts = [1, 5, 10, 20, 30, 50]
283
snr_values = []
284
for n in trial_counts:
285
avg = np.mean(epochs[:n], axis=0)
286
snr = np.max(np.abs(avg)) / (np.std(epochs[:n]) / np.sqrt(n))
287
snr_values.append(snr)
288
289
ax.plot(trial_counts, snr_values, 'ko-', markersize=8)
290
ax.plot(trial_counts, snr_values[0] * np.sqrt(trial_counts), 'r--', alpha=0.5,
291
label='$\\sqrt{N}$ scaling')
292
ax.set_xlabel('Number of Trials')
293
ax.set_ylabel('SNR')
294
ax.set_title('SNR vs Trial Count')
295
ax.legend()
296
ax.grid(True, alpha=0.3)
297
298
# Component latencies
299
ax = axes[1, 2]
300
latencies = []
301
amplitudes = []
302
for epoch in epochs:
303
# Find P300 peak
304
search_idx = (erp_times > 0.25) & (erp_times < 0.5)
305
peak_idx = np.argmax(epoch[search_idx])
306
latencies.append(erp_times[search_idx][peak_idx] * 1000)
307
amplitudes.append(epoch[search_idx][peak_idx])
308
309
ax.scatter(latencies, amplitudes, alpha=0.5, s=30)
310
ax.axvline(np.mean(latencies), color='r', linestyle='--', alpha=0.7)
311
ax.axhline(np.mean(amplitudes), color='r', linestyle='--', alpha=0.7)
312
ax.set_xlabel('P300 Latency (ms)')
313
ax.set_ylabel('P300 Amplitude ($\\mu$V)')
314
ax.set_title('Component Variability')
315
ax.grid(True, alpha=0.3)
316
317
plt.tight_layout()
318
plt.savefig('eeg_erp.pdf', dpi=150, bbox_inches='tight')
319
plt.close()
320
321
p300_latency = np.mean(latencies)
322
p300_amplitude = np.mean(amplitudes)
323
\end{pycode}
324
325
\begin{figure}[htbp]
326
\centering
327
\includegraphics[width=0.95\textwidth]{eeg_erp.pdf}
328
\caption{ERP analysis: (a) single trials, (b) average ERP, (c) ERP image, (d) GFP, (e) SNR scaling, (f) component variability.}
329
\end{figure}
330
331
\chapter{Numerical Results}
332
333
\begin{pycode}
334
results_table = [
335
('Sampling frequency', f'{fs}', 'Hz'),
336
('Alpha power', f'{relative_powers["Alpha"]:.1f}', '\\%'),
337
('Beta power', f'{relative_powers["Beta"]:.1f}', '\\%'),
338
('Mean alpha/beta ratio', f'{mean_alpha_beta:.2f}', ''),
339
('P300 latency', f'{p300_latency:.1f}', 'ms'),
340
('P300 amplitude', f'{p300_amplitude:.1f}', '$\\mu$V'),
341
]
342
\end{pycode}
343
344
\begin{table}[htbp]
345
\centering
346
\caption{EEG analysis results}
347
\begin{tabular}{@{}lcc@{}}
348
\toprule
349
Parameter & Value & Units \\
350
\midrule
351
\py{results_table[0][0]} & \py{results_table[0][1]} & \py{results_table[0][2]} \\
352
\py{results_table[1][0]} & \py{results_table[1][1]} & \py{results_table[1][2]} \\
353
\py{results_table[2][0]} & \py{results_table[2][1]} & \py{results_table[2][2]} \\
354
\py{results_table[3][0]} & \py{results_table[3][1]} & \py{results_table[3][2]} \\
355
\py{results_table[4][0]} & \py{results_table[4][1]} & \py{results_table[4][2]} \\
356
\py{results_table[5][0]} & \py{results_table[5][1]} & \py{results_table[5][2]} \\
357
\bottomrule
358
\end{tabular}
359
\end{table}
360
361
\chapter{Conclusions}
362
363
\begin{enumerate}
364
\item Alpha dominance indicates relaxed wakefulness
365
\item Spectrograms reveal temporal dynamics of brain rhythms
366
\item ERP averaging improves SNR by $\sqrt{N}$
367
\item P300 reflects cognitive processing of stimuli
368
\item Time-frequency analysis captures non-stationary dynamics
369
\item Band power ratios index arousal and cognitive states
370
\end{enumerate}
371
372
\end{document}
373
374