Virtual Channels

Initialize NeuroAnalyzer
using NeuroAnalyzer
using Statistics
eeg = import_edf("files/eeg/eeg.edf")
# filtered 0.5-35 Hz (removes the DC offsets and the strong line noise
# of the raw recording)
NeuroAnalyzer.filter!(eeg;
                      ch = "all",
                      fprototype = :butterworth,
                      ftype = :hp,
                      cutoff = 0.5,
                      order = 4)
NeuroAnalyzer.filter!(eeg;
                      ch = "all",
                      fprototype = :fir,
                      ftype = :lp,
                      cutoff = 35,
                      bw = 10)

Virtual channels are channels computed from the recorded channels, e.g. a bipolar derivation, a local Laplacian, the average of a region or an EOG derivation. They are used to:

Purpose Example
change the reference bipolar derivations, linked-ears reference
improve spatial resolution local (Hjorth) Laplacian: reduces the contribution of distant sources
summarize a region average of several channels (e.g. frontal, occipital)
derive artifact channels horizontal and vertical EOG
compute derived signals rectified signal, instantaneous power, ratios

Formulas

vch() computes a virtual channel from a formula written with the channel labels:

v = vch(eeg; f = "Fp1 - F7")
println("size: ", size(v))
size: (1, 308480, 1)

The result is an array of size 1 × samples × epochs, which can be added to the object as a new channel with add_channel!() (see Basic editing).

Rules

  • Channel labels are written as in the object (case-sensitive): labels(eeg).
  • All Julia operators (+ - * / ^) and mathematical functions (abs, sqrt, log, exp, sin, …) and numbers can be used.
  • The formula is evaluated sample by sample (element-wise): a channel label stands for the value of the channel at one sample. Functions that operate on whole signals, such as mean() or std(), therefore receive a single number, not a channel or a time series. The average of four channels must be written as (C3 + C4 + Fz + Pz) / 4; mean(C3 + C4 + Fz + Pz) is the mean of a single number, i.e. the sum of the channels.
  • Formulas with errors (e.g. an unknown channel label) raise an ArgumentError.

Examples

Bipolar derivation

The difference of two neighboring electrodes; common sources (including the reference) cancel, and the derivation reflects the local potential gradient:

\[ V_{\text{Fp1-F7}} = V_{\text{Fp1}} - V_{\text{F7}} \]

fp1_f7 = vch(eeg; f = "Fp1 - F7")

Complete bipolar montages (e.g. the longitudinal “double banana”) are created with reference_custom(); see Referencing.

Local (Hjorth) Laplacian

The channel minus the average of its nearest neighbors; it attenuates activity spread over large areas (e.g. by volume conduction) and emphasizes local sources:

\[ V_{\text{Lap}}(i) = V(i) - \frac{1}{N} \sum_{j \in \text{neighbors}(i)} V(j) \]

For Cz with four neighbors:

cz_lap = vch(eeg; f = "Cz - (C3 + C4 + Fz + Pz) / 4")

The surface Laplacian of all channels, computed from the electrode positions, is available in reference_slap() and reference_plap().

Re-referencing

Cz referenced to the linked ears (the average of the two earlobe electrodes):

cz_le = vch(eeg; f = "Cz - (A1 + A2) / 2")

Regional average

The average of the frontal electrodes:

frontal = vch(eeg; f = "(Fp1 + Fp2 + F3 + F4 + F7 + F8 + Fz) / 7")

EOG derivations

Horizontal EOG (the difference of the electrodes at the outer canthi) and vertical EOG (an electrode above the eye minus one below it, or a frontopolar electrode minus an EOG electrode):

heog = vch(eeg; f = "EOG1 - EOG2")
veog = vch(eeg; f = "Fp1 - EOG1")

Nonlinear derivations

Rectified signal and instantaneous power of a channel:

o1_rect = vch(eeg; f = "abs(O1)")
o1_pow = vch(eeg; f = "O1^2")

Adding Virtual Channels

The virtual channels are added to a copy of the object, with labels, types and units:

e = keep_channel(eeg; ch = ["Fp1", "F7", "Cz", "A1", "A2"])
add_channel!(e;
             data = vcat(fp1_f7, cz_lap, cz_le),
             label = ["Fp1-F7", "Cz-Lap", "Cz-LE"],
             type = ["eeg", "eeg", "eeg"],
             unit = ["μV", "μV", "μV"])
# 4-second segment (the amplitude scale is set by the plotted signal)
NeuroAnalyzer.plot(crop(e; seg = (100, 104));
                   ch = ["Cz", "Cz-LE", "Cz-Lap", "Fp1-F7"],
                   gui = false)
println("standard deviation [μV]:")
for ch in ["Cz", "Cz-LE", "Cz-Lap"]
    println("  ", rpad(ch, 7),
            round(std(extract_channel(e; ch = ch)); digits = 1))
end
standard deviation [μV]:
  Cz     7.0
  Cz-LE  10.1
  Cz-Lap 2.4

The traces in the plot are scaled separately; their amplitudes are compared by the standard deviations. The Laplacian removes the activity shared with the neighboring electrodes and has the smallest amplitude, while the linked-ears reference adds the activity of the earlobe electrodes.

Virtual channels can be computed for epoched objects as well; the formula is applied to each epoch.