Engee documentation
Notebook

Processing of electroencephalography (EEG) data

The example shows the process of digital signal processing of electroencephalography (EEG), implemented using the specialized EngeeDSP library. Using the example of real data recorded in the standard EDF format, the key stages of the analysis are consistently illustrated.

Function engee.clear() cleans up workspaces:

In [ ]:
engee.clear()

Two graph rendering modes are available to visualize the results.:

  • For static, fast rendering, use gr().

  • For interactive, dynamic visualization with the ability to scale and view values, use plotlyjs().

Select one of the commands by commenting out the second one. Both backends are mutually exclusive, and the last one called will be activated.

In [ ]:
# gr()
plotlyjs()
Out[0]:
Plots.PlotlyJSBackend()

Reading an EDF file

We will connect using the function include the file "edfread.jl" for reading EDF files:

In [ ]:
include("$(@__DIR__)/edfread.jl")
Out[0]:
edfread (generic function with 1 method)

Function edfread It is designed to read data in the EDF (European Data Format) format, the standard format for storing biomedical signals. Structure hdr The information returned by this function contains the full meta information about the record:

General recording parameters:

  • ver — EDF format version

  • patientID — patient ID

  • recordID — record ID

  • startdate and starttime — date and time of the start of recording

  • bytes — header size in bytes

  • records — the number of data blocks in the file

  • duration — the duration of one block in seconds

  • ns — the number of channels in the recording

Parameters of each channel:

  • labels — channel names

  • transducers — type of sensors

  • physicalDims — physical units of measurement

  • physicalMins and physicalMaxs — minimum and maximum physical values

  • digitalMins and digitalMaxs — minimum and maximum digital values

  • prefilters — filters applied during recording

  • samples — the number of samples in one block for each channel

Electroencephalography (EEG) data will serve as an example of biomedical signal processing. Let's start by uploading the file S004R02.edf to study its structure and build waveforms of signals.

In [ ]:
hdr, record = edfread("$(@__DIR__)/S004R02.edf")
Out[0]:
(EDFHeader(0.0, "X X X X", "Startdate 12-AUG-2009 X X BCI2000", "12.08.09", "16.15.00", 16896, 61, 1.0, 65, ["Fc5", "Fc3", "Fc1", "Fcz", "Fc2", "Fc4", "Fc6", "C5", "C3", "C1"  …  "Po7", "Po3", "Poz", "Po4", "Po8", "O1", "Oz", "O2", "Iz", "EDFAnnotations"], ["BCI2000", "BCI2000", "BCI2000", "BCI2000", "BCI2000", "BCI2000", "BCI2000", "BCI2000", "BCI2000", "BCI2000"  …  "BCI2000", "BCI2000", "BCI2000", "BCI2000", "BCI2000", "BCI2000", "BCI2000", "BCI2000", "BCI2000", ""], ["uV", "uV", "uV", "uV", "uV", "uV", "uV", "uV", "uV", "uV"  …  "uV", "uV", "uV", "uV", "uV", "uV", "uV", "uV", "uV", "-"], [-8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0  …  -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -32768.0], [8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0  …  8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 32767.0], [-8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0  …  -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -8092.0, -32768.0], [8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0  …  8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 8092.0, 32767.0], ["HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz"  …  "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz", "HP:0Hz LP:0Hz N:0Hz"], [160, 160, 160, 160, 160, 160, 160, 160, 160, 160  …  160, 160, 160, 160, 160, 160, 160, 160, 160, 80]), [-16.0 -13.0 … 0.0 0.0; -11.0 -10.0 … 0.0 0.0; … ; 52.0 23.0 … 0.0 0.0; 12331.0 5140.0 … NaN NaN])

The next step is to analyze the metadata of the EDF file. The key parameters that we will extract for further processing include the sampling rate, the number of channels, and the recording duration.

In [ ]:
println("Format version:         ", hdr.ver)
println("Patient ID:            ", strip(hdr.patientID))
println("Description of the record:        ", strip(hdr.recordID))
println("Start date/time:      ", hdr.startdate, " ", hdr.starttime)
println("Number of channels:     ", hdr.ns)
println("Number of entries:     ", hdr.records)
println("Total duration:     ", hdr.records * hdr.duration, " sec")
Версия формата:         0.0
ID пациента:            X X X X
Описание записи:        Startdate 12-AUG-2009 X X BCI2000
Дата/время начала:      12.08.09 16.15.00
Количество каналов:     65
Количество записей:     61
Общая длительность:     61.0 сек

For clarity, we also present the key characteristics of the first three channels.

In [ ]:
for i in 1:min(3, size(record, 1))
    channel_data = record[i, :]
    fs = hdr.samples[i] / hdr.duration
    t = (1:length(channel_data)) ./ fs
    
    println("  $(i). $(hdr.labels[i]):")
    println("    The frequency is discretionary.:   ", round(fs, digits=1), " Hz")
    println("    Signal length:      ", length(channel_data), " counts")
    println("    Duration:       ", round(t[end], digits=2), " sec")
    println("    Min/max amplitude:  ", round(minimum(channel_data), digits=2), " .. ", round(maximum(channel_data), digits=2))
end
  1. Fc5:
    Частота дискрет.:   160.0 Гц
    Длина сигнала:      9760 отсчётов
    Длительность:       61.0 сек
    Амплитуда min/max:  -93.0 .. 100.0
  2. Fc3:
    Частота дискрет.:   160.0 Гц
    Длина сигнала:      9760 отсчётов
    Длительность:       61.0 сек
    Амплитуда min/max:  -111.0 .. 197.0
  3. Fc1:
    Частота дискрет.:   160.0 Гц
    Длина сигнала:      9760 отсчётов
    Длительность:       61.0 сек
    Амплитуда min/max:  -165.0 .. 159.0

Next, the signals of the selected EEG channels are visualized. The construction of waveforms allows you to visually assess the nature of brain activity and the presence of artifacts.

In [ ]:
# Channel selection
Nk1 = 1
Nk2 = 17
eegNk1 = record[Nk1, :]
eegNk2 = record[Nk2, :]

# Signal Parameters
fs = hdr.samples[1] / hdr.duration    # Sampling rate
Tn = 60.0     # Observation time
N1 = Int64(Tn * fs)    # Sample Length
dims = hdr.physicalDims[Nk1]

# Signal extraction
y1 = eegNk1[1:N1]
y2 = eegNk2[1:N1]
t = range(0, step=1/fs, length=N1)
Out[0]:
0.0:0.00625:59.99375
In [ ]:
p1 = plot(t, y1, 
          title="EEG waveform (channel $Nk1)", 
          xlabel="Time, from", 
          ylabel="Amplitude, $dims", 
          grid=true)
Out[0]:
In [ ]:
p2 = plot(t, y2, 
          title="EEG Waveform ($Nk2 channel)", 
          xlabel="Time, from", 
          ylabel="Amplitude, $dims", 
          grid=true)
Out[0]:

EEG signal processing

Spectral analysis of the EEG signal

Next, we switch to frequency data analysis using the EngeeDSP library. For the selected channels, a fast Fourier transform is applied, which converts the signals from a time representation to a frequency representation. The obtained spectra are centered for the convenience of subsequent visualization and analysis of the frequency components.

In [ ]:
nsamp = length(y1)
df = fs / nsamp

freq_vec = -fs/2 : df : fs/2 - df
Out[0]:
-80.0:0.016666666666666666:79.98333333333333
In [ ]:
fft_y1 = EngeeDSP.Functions.fft(y1)
fft_y2 = EngeeDSP.Functions.fft(y2)

# Centered spectrum
fft_Y1 = EngeeDSP.Functions.fftshift(fft_y1)
fft_Y2 = EngeeDSP.Functions.fftshift(fft_y2)
Out[0]:
9600-element Vector{ComplexF64}:
              -421.0 + 0.0im
 -167.45381690274462 - 4.689631736961019im
 -127.26103917783621 - 39.072676398338444im
  126.26479397906041 - 51.17359412739461im
 -173.70319686493804 - 61.30850404859848im
  -49.36668159247347 + 265.5510857894187im
 -178.81333356772302 - 54.330408165395056im
   -176.633080970405 - 68.62540635410005im
   90.29976763091145 + 176.30961270506486im
  -127.8082895730895 + 30.20710593311196im
 -124.23375485648694 + 116.3338724542482im
  13.046073715318926 + 108.89195101464429im
  -2.054308247030349 + 174.3480287311877im
                     ⋮
  -2.054308247030349 - 174.3480287311877im
  13.046073715318926 - 108.89195101464429im
 -124.23375485648694 - 116.3338724542482im
  -127.8082895730895 - 30.20710593311196im
   90.29976763091145 - 176.30961270506486im
   -176.633080970405 + 68.62540635410005im
 -178.81333356772302 + 54.330408165395056im
  -49.36668159247347 - 265.5510857894187im
 -173.70319686493804 + 61.30850404859848im
  126.26479397906041 + 51.17359412739461im
 -127.26103917783621 + 39.072676398338444im
 -167.45381690274462 + 4.689631736961019im

After receiving the frequency representation of the signal, its amplitude spectrum is visualized.

In [ ]:
Amp1 = abs.(fft_Y1)./ nsamp

p3 = plot(
    freq_vec, Amp1,
    xlabel="Frequency, Hz",
    ylabel="Amplitude, $dims",
    title="Amplitude spectrum (channel $Nk1)",
    grid=true,
    legend=false
)
Out[0]:

For the other channel, an additional conversion of the spectrum to decibels is performed.

In [ ]:
Amp2 = abs.(fft_Y2)./nsamp
# amplitude in dB (relative to 1 mv)
Amp2_dB = 20 .* log10.(Amp2 .+ eps())   # add eps to avoid -Inf

p4 = plot(
    freq_vec, Amp2_dB,
    xlabel="Frequency, Hz",
    ylabel="Amplitude, dB (rel. 1 $dims)",
    title="Amplitude spectrum in dB (channel $Nk2)",
    grid=true,
    legend=false
)
Out[0]:

We calculate the periodogram for the first channel using the function periodogram to estimate the spectral power density (SPM). The graph shows the frequency distribution of the signal energy.

In [ ]:
power,freq = EngeePhased.Functions.periodogram(y1, fs=fs,out=:data)

p5 = plot(vec(freq), power, title="EEG periodogram ($Nk1 channel)", xlabel="Frequency, Hz", ylabel="SPM MV^2/Hz", grid=true)
Out[0]:

Pronounced network interference (60 Hz) is observed on the spectra. Before analyzing EEG rhythms, it is required to eliminate it by filtering.

Filtering the EEG signal

The results of the spectral analysis revealed a pronounced peak at a frequency of 60 Hz, corresponding to a tip from the AC network. To eliminate it using the EngeeDSP library, a 10th-order Butterworth notch filter is used. The filter is configured to suppress the frequency band from 57 Hz to 63 Hz with a resonant frequency of 60 Hz, which eliminates network interference.

In [ ]:
f_center = 60.0            # resonant frequency, Hz
bw = 6.0                   # bandwidth, Hz
f1 = f_center - bw/2       # lower limit frequency, Hz
f2 = f_center + bw/2       # upper limit frequency, Hz

wn1 = f1 / (fs/2)
wn2 = f2 / (fs/2)

order = 10  # filter order

b, a = EngeeDSP.Functions.butter(order, [wn1, wn2], "stop")
Out[0]:
2-element Vector{Any}:
 [0.4698292983238282 6.6907669963375955 … 6.6907669963375955 0.4698292983238282]
 [1.0 13.168543560078367 … 3.3802180890456617 0.22073956956346077]

The signals of both channels are processed by the notch filter through the function filter EngeeDSP libraries. This makes it possible to suppress network interference at 60 Hz.

In [ ]:
filt_y1 = EngeeDSP.Functions.filter(b, a, y1)
filt_y2 = EngeeDSP.Functions.filter(b, a, y2)
Out[0]:
9600-element Vector{Float64}:
   1.8793171932953128
   2.01519766223684
 -11.161084001659852
 -22.223250803947558
 -17.419966985844184
  -5.378208460928653
 -15.976140468269232
 -17.489645349799662
 -12.366007559676508
 -19.02299470607572
 -20.260811820031957
   1.8435917754153994
  20.34792173591789
   ⋮
 -37.158101916029956
 -26.422698608435752
  -3.218062926172324
  -3.2807346339035037
 -10.572428911745424
  -8.136393100657186
  -9.502412037543898
 -22.962737096877152
 -23.034456665437208
 -29.53225578379856
 -45.0151022958248
 -50.55831627614732

After network interference is suppressed, an EEG waveform is constructed.

In [ ]:
p6 = plot(
    t, filt_y1,
    xlabel = "Time, c",
    ylabel = "Amplitude, MV",
    title  = "EEG waveform after filtering (channel $Nk1)",
    grid = true,
    legend = false
)
Out[0]:

Using the function EngeeDSP.Functions.fft A fast Fourier transform is performed, then the spectrum is centered. The constructed amplitude spectrum allows you to visually assess the absence of a peak at a frequency of 60 Hz.

In [ ]:
fft_filt_y1 = EngeeDSP.Functions.fft(filt_y1)
fft_filt_y1_shift = EngeeDSP.Functions.fftshift(fft_filt_y1)

Amp_y1_filt = abs.(fft_filt_y1_shift)./nsamp

p7 = plot(
    freq_vec, Amp_y1_filt,
    xlabel = "Frequency, Hz)",
    ylabel = "Amplitude, $dims",
    title  = "The spectrum of the EEG signal after filtering (channel $Nk1)",
    grid = true,
    legend = false
)
Out[0]:

To visually evaluate the operation of the notch filter, the amplitude spectra of the signal are compared before and after interference suppression. One graph shows the original spectrum and the spectrum after filtering.

In [ ]:
p8 = plot(freq_vec, Amp1, 
    label = "Without filtering"
)

plot!(freq_vec, Amp_y1_filt,
    label = "After filtering"
)

xlabel!("Frequency, Hz")
ylabel!("Amplitude, $dims")
title!("Amplitude spectrum ($Nk1 channel)")
Out[0]:

Isolation of brain rhythms

A band-pass FIR filter (finite impulse response) is used to extract the alpha rhythm (8-12 Hz) from the purified EEG signal. Using the function EngeeDSP.Functions.fir1 A 200th order filter with a given bandwidth is being designed. Then the function EngeeDSP.Functions.filter applies this filter to the signal filt_y1 by isolating components in the alpha rhythm range and forming a new signal alpha_y1.

In [ ]:
f_low  = 8.0    # lower limit frequency, Hz
f_high = 12.0   # upper limit frequency, Hz

wn = [f_low, f_high] ./ (fs/2)

fir_order = 200 # filter order

b_alpha = EngeeDSP.Functions.fir1(fir_order, wn)

alpha_y1 = EngeeDSP.Functions.filter(b_alpha, [1.0], filt_y1)
Out[0]:
9600-element Vector{Float64}:
  -1.2642257727728393e-17
  -0.0014772799882990105
  -0.005539546070600877
  -0.015279098406798688
  -0.028659168288808372
  -0.041483379140291855
  -0.045984402374753056
  -0.0444267402821369
  -0.03603825284940148
  -0.022191236447088324
  -0.007060365828623284
   0.004409769212829775
   0.015129422954688276
   ⋮
  -4.904243363704513
  -8.618264246193597
 -10.850745206304234
 -11.218257659774965
  -9.665142972402778
  -6.472970349632717
  -2.2091004739029922
   2.372356741708738
   6.4585955829921815
   9.322794813175799
  10.464216600544383
   9.691176482531793

An oscillogram of the selected alpha rhythm is constructed - alpha_y1.

In [ ]:
# The alpha rhythm waveform
p9 = plot(
    t, alpha_y1,
    xlabel = "Time, from",
    ylabel = "Amplitude, MV",
    title  = "Alpha rhythm (8-12 Hz), (channel $Nk1)",
    legend = false,
    grid   = true
)

display(p9)

For further analysis, the characteristic frequency ranges of the main rhythms of brain activity are set.:

  • Delta rhythm: 0.5 – 4 Hz

  • Theta rhythm: 4 – 8 Hz

  • Alpha Rhythm: 8 – 12 Hz

  • Beta Rhythm: 13 – 30 Hz

These ranges will be used to adjust bandpass filters in order to isolate the corresponding rhythmic components from the EEG signal.

In [ ]:
delta_band = (0.5, 4.0)
theta_band = (4.0, 8.0)
alpha_band = (8.0, 12.0)
beta_band  = (13.0, 30.0)
Out[0]:
(13.0, 30.0)

To make it easier to distinguish different EEG rhythms, a function has been created band_fir_filter, which implements a typical bandpass FIR filtering operation using the EngeeDSP library

In [ ]:
function band_fir_filter(sig, fs, band, order)
    f1, f2 = band
    wn = [f1, f2] ./ (fs/2)
    b = EngeeDSP.Functions.fir1(order, wn)
    y = EngeeDSP.Functions.filter(b, [1.0], sig)
    return y
end
Out[0]:
band_fir_filter (generic function with 1 method)

Using a previously created function band_fir_filter Three main rhythmic components are sequentially distinguished from the EEG signal: delta rhythm (0.5–4 Hz), theta rhythm (4-8 Hz) and beta rhythm (13-30 Hz).

In [ ]:
# Filtering the first signal
delta_y1 = band_fir_filter(filt_y1, fs, delta_band, fir_order)
theta_y1 = band_fir_filter(filt_y1, fs, theta_band, fir_order)
beta_y1  = band_fir_filter(filt_y1, fs, beta_band,  fir_order)
Out[0]:
9600-element Vector{Float64}:
   0.0032654379858672727
   0.007422172824834053
   0.014801690250850997
   0.01165754148359467
  -0.0031911525904436255
  -0.02693768353113933
  -0.025993363024740108
  -0.014631477180530721
  -0.0074227076199889115
  -0.00622183076701969
  -0.0002976026605465516
   0.003940445906677193
   0.018343208019203788
   ⋮
   2.4802134675647785
   5.3437746345359844
   6.485972782545103
   6.051318593077311
   2.7836681642821213
  -4.1124061256879605
 -12.035547405119221
 -15.21028471340803
  -9.187943810884414
   4.28918838990696
  17.187421337929987
  20.690486728872056

Similarly, for the second signal, the main rhythms of the brain are isolated using the same bandpass filtering function. From the signal filt_y2 The corresponding components are obtained: delta, theta, alpha and beta rhythms.

In [ ]:
# Filtering the second signal
delta_y2 = band_fir_filter(filt_y2, fs, delta_band, fir_order)
theta_y2 = band_fir_filter(filt_y2, fs, theta_band, fir_order)
alpha_y2 = band_fir_filter(filt_y2, fs, theta_band, fir_order)
beta_y1  = band_fir_filter(filt_y2, fs, beta_band,  fir_order)
Out[0]:
9600-element Vector{Float64}:
  -0.0008163594964668182
  -0.0011922511153292235
   0.00497267421639784
   0.012843450207834648
   0.009921533738226282
  -0.0044660220059472245
  -0.008916031800116834
  -0.004127870318159943
  -0.002099704456340565
  -0.005045801669564937
  -0.01255520255552034
  -0.02411127441779878
  -0.02587110552048713
   ⋮
  -4.483366022584483
  -1.6915932100886193
   3.3538114998521458
   8.008266102529634
   7.965540927375576
   1.1699021121243658
  -8.904363025305269
 -14.764620414594361
 -10.857934387212628
   1.3189661692662014
  13.757997160850445
  17.945613846215153

Visualization of brain rhythms for the first signal.

In [ ]:
p10 = plot(
    plot(t, delta_y1,
         xlabel = "Time, from",
         ylabel = "Amlituda, mkV",
         title  = "Delta rhythm (0.5–4 Hz)",
         legend = false,
         grid   = true),
    plot(t, theta_y1,
         xlabel = "Time, from",
         ylabel = "Amlituda, mkV",
         title  = "Theta rhythm (4-8 Hz)",
         legend = false,
         grid   = true),
    plot(t, alpha_y1,
         xlabel = "Time, from",
         ylabel = "Amlituda, mkV",
         title  = "Alpha rhythm (8-12 Hz)",
         legend = false,
         grid   = true),
    plot(t, beta_y1,
         xlabel = "Time, from",
         ylabel = "Amlituda, mkV",
         title  = "Beta rhythm (13-30 Hz)",
         legend = false,
         grid   = true),
    layout = (4, 1),
    size   = (900, 900),
    margin = 40*Plots.px
)

display(p10)

When analyzing an electroencephalogram, the signal is divided into standard frequency ranges, each of which is associated with specific brain conditions.:

  1. Delta rhythm (0.5–4 Hz) – the slowest rhythm. Predominates during deep sleep.

  2. Theta rhythm (4-8 Hz) – is associated with the state of drowsiness, relaxation and memory processes.

  3. Alpha rhythm (8-12 Hz) – the rhythm of calm wakefulness is most pronounced when the eyes are closed.

  4. Beta rhythm (13-30 Hz) – is associated with active wakefulness, concentration and mental activity.

Correlation analysis

Let's move on to the correlation data processing section. Correlation analysis is used to quantify the internal structure and frequency of the selected rhythms.

The autocorrelation function

Using the function EngeeDSP.Functions.xcorr the autocorrelation of the signal is calculated alpha_y1. The function returns two arrays:

  1. R_aa — the values of the correlation coefficients for each shift.

  2. lag_aa — the corresponding values of the time shift in the counts.

In [ ]:
maxlag = length(alpha_y1) - 1

R_aa, lag_aa = EngeeDSP.Functions.xcorr(alpha_y1, alpha_y1, maxlag)
Out[0]:
([-2.0790449801918666e-12, -0.01431658111363992, -0.0691432963450996, -0.21981182733943047, -0.49881005826080144, -0.883644715207206, -1.2554774059748002, -1.498024805586091, -1.4948728866986274, -1.1694355430628611  …  -1.1694355430941927, -1.4948728866654877, -1.4980248056160164, -1.2554774059928664, -0.8836447151822767, -0.4988100582848648, -0.2198118273108758, -0.06914329639196168, -0.014316581095402736, -1.1522072915149645e-11], [-9599 -9598 … 9598 9599])
In [ ]:
tau_aa = vec(lag_aa) ./ fs 
Out[0]:
19199-element Vector{Float64}:
 -59.99375
 -59.9875
 -59.98125
 -59.975
 -59.96875
 -59.9625
 -59.95625
 -59.95
 -59.94375
 -59.9375
 -59.93125
 -59.925
 -59.91875
   ⋮
  59.925
  59.93125
  59.9375
  59.94375
  59.95
  59.95625
  59.9625
  59.96875
  59.975
  59.98125
  59.9875
  59.99375

Visualization of the autocorrelation function of the alpha rhythm

In [ ]:
R_aa_norm = R_aa ./ maximum(R_aa)

plot(
    tau_aa, R_aa_norm,
    xlabel = "The shift, from",
    ylabel = "ACF, mkV^2",
    title  = "Alpha Rhythm ACF (Channel $Nk1)",
    grid = true,
    legend = false
)
Out[0]:
The cross-correlation function

To analyze the relationship of alpha activity in different brain regions, the cross-correlation function (VCF) between the alpha rhythm signals of the first and second channels is calculated.

In [ ]:
R_ab, lag_ab = EngeeDSP.Functions.xcorr(alpha_y1, alpha_y2, maxlag)

tau_ab = vec(lag_ab) ./ fs
Out[0]:
19199-element Vector{Float64}:
 -59.99375
 -59.9875
 -59.98125
 -59.975
 -59.96875
 -59.9625
 -59.95625
 -59.95
 -59.94375
 -59.9375
 -59.93125
 -59.925
 -59.91875
   ⋮
  59.925
  59.93125
  59.9375
  59.94375
  59.95
  59.95625
  59.9625
  59.96875
  59.975
  59.98125
  59.9875
  59.99375

Visualization of the mutual correlation of alpha rhythms

In [ ]:
R_ab_norm = R_ab ./ maximum(R_ab)

plot(
    tau_ab, R_ab_norm,
    xlabel = "The shift, from",
    ylabel = "VKF, mkV^2",
    title  = "The mutual correlation function of alpha rhythms",
    grid   = true,
    legend = false
)
Out[0]:

Conclusion

In this example, the processing of EEG data recorded in the standard EDF format using the EngeeDSP library was demonstrated. All the work is based on its functions and executed in a logical sequence.:

  1. Data download: The data was read from the EDF file.

  2. Spectral analysis: Using the functions EngeeDSP.Functions.fft and EngeeDSP.Functions.fftshift The signals were converted to the frequency domain, which revealed the presence of network interference.

  3. Filtering: To suppress 60 Hz interference, a Butterworth notch filter was designed and applied using the following functions EngeeDSP.Functions.butter and EngeeDSP.Functions.filter.

  4. Highlighting rhythms: The main rhythms of the brain (delta, theta, alpha, beta) were isolated using bandpass FIR filters created by the function EngeeDSP.Functions.fir1 and applied via EngeeDSP.Functions.filter.

  5. Correlation analysis: To study the temporal structure and relationships of the signals, the autocorrelation (ACF) and cross-correlation (VCF) functions were calculated using EngeeDSP.Functions.xcorr.