Generate multi channel data
Here you will learn how to simulate EEG data for multiple channels/electrodes. The idea is to specify a signal on source level and then use a head model or a manual projection matrix to project the source signal to a number of electrodes.
Setup
Click to expand
# Load required packages
using UnfoldSim
using UnfoldMakie
using CairoMakie
using DataFrames
using Random
Specifying a design
We are using a one-level design for testing here.
design = SingleSubjectDesign(conditions = Dict(:condA => ["levelA"]));
Next we generate two simple components at two different times without any formula attached (we have a single condition anyway)
c = LinearModelComponent(; basis = p100(), formula = @formula(0 ~ 1), β = [1]);
c2 = LinearModelComponent(; basis = p300(), formula = @formula(0 ~ 1), β = [1]);
The multichannel component
Next, similar to the nested design above, we can nest the component in a MultichannelComponent
. We could either provide the projection matrix manually, e.g.:
mc = UnfoldSim.MultichannelComponent(c, [1, 2, -1, 3, 5, 2.3, 1])
MultichannelComponent
component: LinearModelComponent
projection: Array{Float64}((7,)) [1.0, 2.0, -1.0, 3.0, 5.0, 2.3, 1.0]
noise: NoNoise NoNoise()
or maybe more convenient: use the pair-syntax: Headmodel=>Label which makes use of a headmodel (HaRTmuT is currently easily available in UnfoldSim)
hart = Hartmut()
mc = UnfoldSim.MultichannelComponent(c, hart => "Left Postcentral Gyrus")
mc2 = UnfoldSim.MultichannelComponent(c2, hart => "Right Occipital Pole")
MultichannelComponent
component: LinearModelComponent
projection: Array{Float64}((227,)) [-0.03461859471337842, -0.04321094803502425, 0.0037088347968313525, -0.014722528968861278, -0.0234889834534478, 0.02731807504242923, 0.038863688452528036, 0.1190531258070562, -0.09956890221613562, -0.0867729334438599 … 0.37435404409695094, -0.020863789022627935, 0.25627478723535513, -0.05777985212119245, 0.37104376432271147, -0.19446620423767172, 0.2590764703721097, -0.12923837607416555, 0.1732886690359311, 0.4703016561960567]
noise: NoNoise NoNoise()
You could also specify a noise-specific component which is applied prior to projection & summing with other components.
finally we need to define the onsets of the signal
onset = UniformOnset(; width = 20, offset = 4);
Simulation
Now as usual we simulate data. Inspecting data shows our result is now indeed ~230 Electrodes large! Nice!
data, events =
simulate(MersenneTwister(1), design, [mc, mc2], onset, PinkNoise(noiselevel = 0.05));
size(data)
(227, 61)
The noise declared in the simulate
function is added after mixing to channels, each channel receives independent noise. It is also possible to add noise to each individual component+source prior to projection. This would introduce correlated noise.
Plotting
Let's plot using Butterfly & Topoplot first we convert the electrodes to positions usable in TopoPlots.jl
pos3d = hart.electrodes["pos"]
pos2d = to_positions(pos3d')
pos2d = [Point2f(p[1] + 0.5, p[2] + 0.5) for p in pos2d];
now plot!
f = Figure()
df = DataFrame(
:estimate => data[:],
:channel => repeat(1:size(data, 1), outer = size(data, 2)),
:time => repeat(1:size(data, 2), inner = size(data, 1)),
)
plot_butterfly!(f[1, 1:2], df; positions = pos2d)
plot_topoplot!(
f[2, 1],
df[df.time.==28, :];
positions = pos2d,
visual = (; enlarge = 0.5, label_scatter = false),
axis = (; limits = ((0, 1), (0, 0.9))),
)
plot_topoplot!(
f[2, 2],
df[df.time.==48, :];
positions = pos2d,
visual = (; enlarge = 0.5, label_scatter = false),
axis = (; limits = ((0, 1), (0, 0.9))),
)
f

This page was generated using Literate.jl.