From 8e3189ce2e1d2227b59cfc4384880246d0469528 Mon Sep 17 00:00:00 2001 From: "Documenter.jl" Date: Mon, 20 Nov 2023 19:48:27 +0000 Subject: [PATCH] build based on 2b9cd37 --- dev/.documenter-siteinfo.json | 2 +- dev/api/index.html | 12 ++--- dev/generated/HowTo/multichannel/index.html | 43 ++++-------------- dev/generated/HowTo/newComponent/08f54f01.png | Bin 52298 -> 0 bytes dev/generated/HowTo/newComponent/faaca575.png | Bin 0 -> 48927 bytes dev/generated/HowTo/newComponent/index.html | 21 ++++----- dev/generated/HowTo/newDesign/index.html | 6 +-- dev/generated/HowTo/repeatTrials/index.html | 14 +++--- dev/generated/reference/basistypes/index.html | 11 ++--- dev/generated/reference/noisetypes/index.html | 4 +- dev/generated/reference/overview/index.html | 8 ++-- .../tutorials/poweranalysis/index.html | 2 +- dev/generated/tutorials/quickstart/index.html | 10 ++-- .../tutorials/simulateERP/index.html | 34 +++----------- dev/index.html | 2 +- dev/search_index.js | 2 +- 16 files changed, 57 insertions(+), 114 deletions(-) delete mode 100644 dev/generated/HowTo/newComponent/08f54f01.png create mode 100644 dev/generated/HowTo/newComponent/faaca575.png diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index 9c172c9..7c8a8fd 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.9.4","generation_timestamp":"2023-11-15T14:16:48","documenter_version":"1.1.2"}} \ No newline at end of file +{"documenter":{"julia_version":"1.9.4","generation_timestamp":"2023-11-20T19:48:23","documenter_version":"1.1.2"}} \ No newline at end of file diff --git a/dev/api/index.html b/dev/api/index.html index 10448f9..68176f0 100644 --- a/dev/api/index.html +++ b/dev/api/index.html @@ -5,26 +5,26 @@ β = [1.,2.], contrasts=Dict(:cond=>EffectsCoding()) ) -source
UnfoldSim.MixedModelComponentType

A component that adds a hierarchical relation between parameters according to a LMM defined via MixedModels.jl

  • basis: an object, if accessed, provides a 'basis-function', e.g. hanning(40), this defines the response at a single event. It will be weighted by the model-prediction
  • formula: Formula-Object in the style of MixedModels.jl e.g. @formula 0~1+cond + (1|subject) - left-handside is ignored
  • β Vector of betas, must fit the formula
  • σs Dict of random effect variances, e.g. Dict(:subject=>[0.5,0.4]) or to specify correlationmatrix Dict(:subject=>[0.5,0.4,I(2,2)],...). Technically, this will be passed to MixedModels.jl create_re function, which creates the θ matrices.
  • contrasts: Dict in the style of MixedModels.jl. Default is empty.

All arguments can be named, in that case contrasts is optional

Works best with MultiSubjectDesign

MixedModelComponent(;
+
source
UnfoldSim.MixedModelComponentType

A component that adds a hierarchical relation between parameters according to a LMM defined via MixedModels.jl

  • basis: an object, if accessed, provides a 'basis-function', e.g. hanning(40), this defines the response at a single event. It will be weighted by the model-prediction
  • formula: Formula-Object in the style of MixedModels.jl e.g. @formula 0~1+cond + (1|subject) - left-handside is ignored
  • β Vector of betas, must fit the formula
  • σs Dict of random effect variances, e.g. Dict(:subject=>[0.5,0.4]) or to specify correlationmatrix Dict(:subject=>[0.5,0.4,I(2,2)],...). Technically, this will be passed to MixedModels.jl create_re function, which creates the θ matrices.
  • contrasts: Dict in the style of MixedModels.jl. Default is empty.

All arguments can be named, in that case contrasts is optional

Works best with MultiSubjectDesign

MixedModelComponent(;
     basis=hanning(40),
     formula=@formula(0~1+cond+(1+cond|subject)),
     β = [1.,2.],
     σs= Dict(:subject=>[0.5,0.4]),
     contrasts=Dict(:cond=>EffectsCoding())
 )
-
source
UnfoldSim.MultiSubjectDesignType
  • n_subjects::Int -> number of subjects
  • n_items::Int -> number of items (sometimes ≈trials)
  • subjects_between = nothing -> effects between subjects, e.g. young vs old
  • items_between = nothing -> effects between items, e.g. natural vs artificial images, but shown to all subjects
  • both_within = nothing -> effects completly crossed
  • tableModifyFun = x->x; # can be used to sort, or x->shuffle(MersenneTwister(42),x) - be sure to fix/update the rng accordingly!!

tipp: check the resulting dataframe using generate(design)

# declaring same condition both sub-between and item-between results in a full between subject/item design
+
source
UnfoldSim.MultiSubjectDesignType
  • n_subjects::Int -> number of subjects
  • n_items::Int -> number of items (sometimes ≈trials)
  • subjects_between = nothing -> effects between subjects, e.g. young vs old
  • items_between = nothing -> effects between items, e.g. natural vs artificial images, but shown to all subjects
  • both_within = nothing -> effects completly crossed
  • tableModifyFun = x->x; # can be used to sort, or x->shuffle(MersenneTwister(42),x) - be sure to fix/update the rng accordingly!!

tipp: check the resulting dataframe using generate(design)

# declaring same condition both sub-between and item-between results in a full between subject/item design
 design = MultiSubjectDesignjectDesign(;
         n_items=10,
 		n_subjects = 30,
         subjects_between=Dict(:cond=>["levelA","levelB"]),
 		items_between =Dict(:cond=>["levelA","levelB"]),
-        );
source
UnfoldSim.MultichannelComponentType

Wrapper for an AbstractComponent to project it to multiple target-channels via projection. optional adds noise to the source prior to projection.

source
UnfoldSim.RepeatDesignType

repeat a design DataFrame multiple times to mimick repeatedly recorded trials

designOnce = MultiSubjectDesign(;
+        );
source
UnfoldSim.MultichannelComponentType

Wrapper for an AbstractComponent to project it to multiple target-channels via projection. optional adds noise to the source prior to projection.

source
UnfoldSim.RepeatDesignType

repeat a design DataFrame multiple times to mimick repeatedly recorded trials

designOnce = MultiSubjectDesign(;
         n_items=2,
 		n_subjects = 2,
         subjects_between =Dict(:cond=>["levelA","levelB"]),
 		items_between =Dict(:cond=>["levelA","levelB"]),
         );
 
-design = RepeatDesign(designOnce,4);
source
UnfoldSim.SingleSubjectDesignType
  • conditions = Dict of conditions, e.g. Dict(:A=>["a_small","a_big"],:B=>["b_tiny","b_large"])
  • tableModifyFun = x->x; # can be used to sort, or x->shuffle(MersenneTwister(42),x) - be sure to fix/update the rng accordingly!!

Number of trials / rows in generate(design) depend on the full factorial of your conditions.

To increase the number of repetitions simply use RepeatDesign(SingleSubjectDesign(...),5)

tipp: check the resulting dataframe using generate(design)

source
Base.sizeMethod

Returns dimension of experiment design

source
DSP.Windows.hanningMethod

generate a hanning window

duration: in s offset: in s, defines hanning peak sfreq: sampling rate in Hz

source
UnfoldSim.adderp!Method

Helper function to add inplace the erps to the EEG, but for both 2D (1 channel) and 3D (X channel case)

source
UnfoldSim.closest_srcMethod
closest_src(coords_list::AbstractVector{<:AbstractVector}, pos)
-closest_src(coords::Vector{<:Real}, pos)

Takes an array of 'm' target coordinate vector (size 3) (or vector of vectors) and a matrix (n-by-3) of all available positions, and returns an array of size 'm' containing the indices of the respective items in 'pos' that are nearest to each of the target coordinates.

source
UnfoldSim.closest_srcMethod
closest_src(head::Hartmut,label::String)

Returns src-ix of the Headmodel Hartmut which is closest to the average of the label.

Important

We use the average in eucledean space, but the cortex is a curved surface. In most cases they will not overlap. Ideally we would calculate the average on the surface, but this is a bit more complex to do (you'd need to calculate the vertices etc.)

hartmut = headmodel()
-pos = closest_src(hartmut=>"Left Middle Temporal Gyrus, posterior division")
source
UnfoldSim.convertMethod

Function to convert output similar to unfold (data, evts)

source
UnfoldSim.gen_noiseMethod
gen_noise(t::RealisticNoise, n::Int)

Generate noise of a given type t and length n

source
UnfoldSim.gen_noiseMethod
gen_noise(t::Union{PinkNoise, RedNoise}, n::Int)

Generate noise of a given type t and length n

source
UnfoldSim.gen_noiseMethod
gen_noise(t::WhiteNoise, n::Int)

Generate noise of a given type t and length n

source
UnfoldSim.generateMethod

Generates full factorial Dataframe according to MixedModelsSim.jl 's simdatcrossed function Note: nitems = you can think of it as trials or better, as stimuli

Note: No condition can be named dv which is used internally in MixedModelsSim / MixedModels as a dummy left-side

Afterwards applies expdesign.tableModifyFun. Could be used to duplicate trials, sort, subselect etc.

Finally it sorts by :subject

julia> d = MultiSubjectDesign(;nsubjects = 10,nitems=20,both_within= Dict(:A=>nlevels(5),:B=>nlevels(2))) julia> generate(d)

source
UnfoldSim.generateMethod

Generates full-factorial DataFrame of expdesign.conditions

Afterwards applies expdesign.tableModifyFun.

julia> d = SingleSubjectDesign(;conditions= Dict(:A=>nlevels(5),:B=>nlevels(2))) julia> generate(d)

source
UnfoldSim.hartmut_citationMethod

Returns citation-string for HArtMuT

source
UnfoldSim.headmodelMethod

Load a headmodel, using Artifacts.jl automatically downloads the required files

Currently only type="hartmut" is implemented

source
UnfoldSim.hrfMethod

Generate a HRF kernel.

TR = 1/sfreq default parameters taken from SPM

Code adapted from Unfold.jl

source
UnfoldSim.leadfieldMethod

Returns the leadfield

source
UnfoldSim.magnitudeMethod

Extracts magnitude of the orientation-including leadfield.

By default uses the orientation specified in the headmodel

Fallback: along the third dimension using norm - the maximal projection

source
UnfoldSim.magnitudeMethod

Extract magnitude of 3-orientation-leadfield, type (default: "perpendicular") => uses the provided source-point orientations - otherwise falls back to norm

source
UnfoldSim.n_channelsMethod

Returns the number of channels. By default = 1

source
UnfoldSim.n_channelsMethod

for MultichannelComponent returns the length of the projection vector

source
UnfoldSim.padarrayMethod

Pads array with specified value, length padarray(arr, len, val)

source
UnfoldSim.predef_2x2Method

todo

Careful if you modify nitems with nsubjects = 1, n_items has to be a multiple of 4 (or your equivalent conditions factorial, e.g. all combinations length)

source
UnfoldSim.predef_eegMethod

Generates a P1/N1/P3 complex. predefeeg(;kwargs...) predefeeg(rng;kwargs...) predefeeg(rng,nsubjects;kwargs...)

In case of n_subjects, MixedModelComponents are generated

Default params: n_repeats=100 tableModifyFun = x->shuffle(deepcopy(rng),x # random trial order conditions = Dict(...),

component / signal

sfreq = 100, p1 = (p100(;sfreq=sfreq), @formula(0~1),[5],Dict()), # P1 amp 5, no effects n1 = (n170(;sfreq=sfreq), @formula(0~1+condition),[5,-3],Dict()), # N1 amp 5, dummycoded condition effect (levels "car", "face") of -3 p3 = (p300(;sfreq=sfreq), @formula(0~1+continuous),[5,1],Dict()), # P3 amp 5, continuous effect range [-5,5] with slope 1

noise

noiselevel = 0.2, noise = PinkNoise(;noiselevel=noiselevel),

onset

overlap = (0.5,0.2), # offset + width/length of Uniform noise. put offset to 1 for no overlap. put width to 0 for no jitter onset=UniformOnset(;offset=sfreq0.5overlap[1],width=sfreq0.5overlap[2]),

source
UnfoldSim.predef_eegMethod

predefeeg(rng,nsubjects;kwargs...) Runs predefeeg(rng;kwargs...) nsubject times and concatenates the results.

source
UnfoldSim.simulateMethod

by default call simulate with ::Abstractcomponent,::AbstractDesign`, but allow for custom types

making use of other information in simulation

source
UnfoldSim.simulateMethod

simulate a linearModel

julia> c = UnfoldSim.LinearModelComponent([0,1,1,0],@formula(0~1+cond),[1,2],Dict()) julia> design = MultiSubjectDesign(;nsubjects=2,nitems=50,item_between=(;:cond=>["A","B"])) julia> simulate(StableRNG(1),c,design)

source
UnfoldSim.simulateMethod

simulate MixedModelComponent

julia> design = MultiSubjectDesign(;nsubjects=2,nitems=50,item_between=(;:cond=>["A","B"])) julia> c = UnfoldSim.MixedModelComponent([0.,1,1,0],@formula(0~1+cond+(1|subject)),[1,2],Dict(:subject=>[2],),Dict()) julia> simulate(StableRNG(1),c,design)

source
UnfoldSim.simulateMethod

Simulates erp data given the specified parameters

source
UnfoldSim.weight_σsMethod

Weights a σs Dict for MixedModels.jl by a Float64

Finally sales it by σ_lmm, as a trick to simulate noise-free LMMs

I anticipate a function function weight_σs(σs::Dict,b_σs::Dict,σ_lmm::Float64) where each σs entry can be weighted individually

source
+design = RepeatDesign(designOnce,4);source
UnfoldSim.SingleSubjectDesignType
  • conditions = Dict of conditions, e.g. Dict(:A=>["a_small","a_big"],:B=>["b_tiny","b_large"])
  • tableModifyFun = x->x; # can be used to sort, or x->shuffle(MersenneTwister(42),x) - be sure to fix/update the rng accordingly!!

Number of trials / rows in generate(design) depend on the full factorial of your conditions.

To increase the number of repetitions simply use RepeatDesign(SingleSubjectDesign(...),5)

tipp: check the resulting dataframe using generate(design)

source
Base.sizeMethod

Returns dimension of experiment design

source
DSP.Windows.hanningMethod

generate a hanning window

duration: in s offset: in s, defines hanning peak sfreq: sampling rate in Hz

source
UnfoldSim.adderp!Method

Helper function to add inplace the erps to the EEG, but for both 2D (1 channel) and 3D (X channel case)

source
UnfoldSim.closest_srcMethod
closest_src(coords_list::AbstractVector{<:AbstractVector}, pos)
+closest_src(coords::Vector{<:Real}, pos)

Takes an array of 'm' target coordinate vector (size 3) (or vector of vectors) and a matrix (n-by-3) of all available positions, and returns an array of size 'm' containing the indices of the respective items in 'pos' that are nearest to each of the target coordinates.

source
UnfoldSim.closest_srcMethod
closest_src(head::Hartmut,label::String)

Returns src-ix of the Headmodel Hartmut which is closest to the average of the label.

Important

We use the average in eucledean space, but the cortex is a curved surface. In most cases they will not overlap. Ideally we would calculate the average on the surface, but this is a bit more complex to do (you'd need to calculate the vertices etc.)

hartmut = headmodel()
+pos = closest_src(hartmut=>"Left Middle Temporal Gyrus, posterior division")
source
UnfoldSim.convertMethod

Function to convert output similar to unfold (data, evts)

source
UnfoldSim.gen_noiseMethod
gen_noise(t::RealisticNoise, n::Int)

Generate noise of a given type t and length n

source
UnfoldSim.gen_noiseMethod
gen_noise(t::Union{PinkNoise, RedNoise}, n::Int)

Generate noise of a given type t and length n

source
UnfoldSim.gen_noiseMethod
gen_noise(t::WhiteNoise, n::Int)

Generate noise of a given type t and length n

source
UnfoldSim.generateMethod

Generates full factorial Dataframe according to MixedModelsSim.jl 's simdatcrossed function Note: nitems = you can think of it as trials or better, as stimuli

Note: No condition can be named dv which is used internally in MixedModelsSim / MixedModels as a dummy left-side

Afterwards applies expdesign.tableModifyFun. Could be used to duplicate trials, sort, subselect etc.

Finally it sorts by :subject

julia> d = MultiSubjectDesign(;nsubjects = 10,nitems=20,both_within= Dict(:A=>nlevels(5),:B=>nlevels(2))) julia> generate(d)

source
UnfoldSim.generateMethod

Generates full-factorial DataFrame of expdesign.conditions

Afterwards applies expdesign.tableModifyFun.

julia> d = SingleSubjectDesign(;conditions= Dict(:A=>nlevels(5),:B=>nlevels(2))) julia> generate(d)

source
UnfoldSim.hartmut_citationMethod

Returns citation-string for HArtMuT

source
UnfoldSim.headmodelMethod

Load a headmodel, using Artifacts.jl automatically downloads the required files

Currently only type="hartmut" is implemented

source
UnfoldSim.hrfMethod

Generate a HRF kernel.

TR = 1/sfreq default parameters taken from SPM

Code adapted from Unfold.jl

source
UnfoldSim.leadfieldMethod

Returns the leadfield

source
UnfoldSim.magnitudeMethod

Extracts magnitude of the orientation-including leadfield.

By default uses the orientation specified in the headmodel

Fallback: along the third dimension using norm - the maximal projection

source
UnfoldSim.magnitudeMethod

Extract magnitude of 3-orientation-leadfield, type (default: "perpendicular") => uses the provided source-point orientations - otherwise falls back to norm

source
UnfoldSim.n_channelsMethod

Returns the number of channels. By default = 1

source
UnfoldSim.n_channelsMethod

for MultichannelComponent returns the length of the projection vector

source
UnfoldSim.padarrayMethod

Pads array with specified value, length padarray(arr, len, val)

source
UnfoldSim.predef_2x2Method

todo

Careful if you modify nitems with nsubjects = 1, n_items has to be a multiple of 4 (or your equivalent conditions factorial, e.g. all combinations length)

source
UnfoldSim.predef_eegMethod

Generates a P1/N1/P3 complex. predefeeg(;kwargs...) predefeeg(rng;kwargs...) predefeeg(rng,nsubjects;kwargs...)

In case of n_subjects, MixedModelComponents are generated

Default params: n_repeats=100 tableModifyFun = x->shuffle(deepcopy(rng),x # random trial order conditions = Dict(...),

component / signal

sfreq = 100, p1 = (p100(;sfreq=sfreq), @formula(0~1),[5],Dict()), # P1 amp 5, no effects n1 = (n170(;sfreq=sfreq), @formula(0~1+condition),[5,-3],Dict()), # N1 amp 5, dummycoded condition effect (levels "car", "face") of -3 p3 = (p300(;sfreq=sfreq), @formula(0~1+continuous),[5,1],Dict()), # P3 amp 5, continuous effect range [-5,5] with slope 1

noise

noiselevel = 0.2, noise = PinkNoise(;noiselevel=noiselevel),

onset

overlap = (0.5,0.2), # offset + width/length of Uniform noise. put offset to 1 for no overlap. put width to 0 for no jitter onset=UniformOnset(;offset=sfreq0.5overlap[1],width=sfreq0.5overlap[2]),

source
UnfoldSim.predef_eegMethod

predefeeg(rng,nsubjects;kwargs...) Runs predefeeg(rng;kwargs...) nsubject times and concatenates the results.

source
UnfoldSim.simulateMethod

by default call simulate with ::Abstractcomponent,::AbstractDesign`, but allow for custom types

making use of other information in simulation

source
UnfoldSim.simulateMethod

simulate a linearModel

julia> c = UnfoldSim.LinearModelComponent([0,1,1,0],@formula(0~1+cond),[1,2],Dict()) julia> design = MultiSubjectDesign(;nsubjects=2,nitems=50,item_between=(;:cond=>["A","B"])) julia> simulate(StableRNG(1),c,design)

source
UnfoldSim.simulateMethod

simulate MixedModelComponent

julia> design = MultiSubjectDesign(;nsubjects=2,nitems=50,item_between=(;:cond=>["A","B"])) julia> c = UnfoldSim.MixedModelComponent([0.,1,1,0],@formula(0~1+cond+(1|subject)),[1,2],Dict(:subject=>[2],),Dict()) julia> simulate(StableRNG(1),c,design)

source
UnfoldSim.simulateMethod

Simulates erp data given the specified parameters

source
UnfoldSim.weight_σsMethod

Weights a σs Dict for MixedModels.jl by a Float64

Finally sales it by σ_lmm, as a trick to simulate noise-free LMMs

I anticipate a function function weight_σs(σs::Dict,b_σs::Dict,σ_lmm::Float64) where each σs entry can be weighted individually

source
diff --git a/dev/generated/HowTo/multichannel/index.html b/dev/generated/HowTo/multichannel/index.html index b10a70d..211f55b 100644 --- a/dev/generated/HowTo/multichannel/index.html +++ b/dev/generated/HowTo/multichannel/index.html @@ -1,15 +1,13 @@ -Multi Channel Data · UnfoldSim.jl
using UnfoldSim
+Multi Channel Data · UnfoldSim.jl
using UnfoldSim
 using UnfoldMakie
 using CairoMakie
 using DataFrames
-using Random
-
-# Specifcy design

We are using a one-level design for testing here.

design = SingleSubjectDesign(conditions=Dict(:condA=>["levelA"]))
SingleSubjectDesign
+using Random

Specify design

We are using a one-level design for testing here.

design = SingleSubjectDesign(conditions=Dict(:condA=>["levelA"]))
SingleSubjectDesign
   conditions: Dict{Symbol, Vector{String}}
   tableModifyFun: #10 (function of type UnfoldSim.var"#10#14")
 

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]);

next similar to the nested design above, we can nest the component in a MultichannelComponent. We could either provide the projection marix manually, e.g.:

mc = UnfoldSim.MultichannelComponent(c, [1,2,-1,3,5,2.3,1])
MultichannelComponent
+c2 = LinearModelComponent(;basis=p300(),formula = @formula(0~1),β = [1]);

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()
@@ -19,38 +17,13 @@
   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()
-
Hint

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 + Plotting
UniformOnset
-  width: Int64 20
-  offset: Int64 4
-

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, NoNoise())
-size(data)
(227, 62)

Let's plot using Butterfly & Topoplot first we convert the electrodes to positions usable in TopoPlots.jl

pos3d = hart.electrodes["pos"]
+
Info

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 + Plotting

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, NoNoise())
+size(data)
(227, 62)

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]
227-element Vector{GeometryBasics.Point{2, Float32}}:
- [0.40898308, 0.688676]
- [0.4295677, 0.6893123]
- [0.59101695, 0.688676]
- [0.5704323, 0.6893123]
- [0.38786337, 0.6868818]
- [0.6121366, 0.6868818]
- [0.3344946, 0.6886467]
- [0.6655054, 0.6886467]
- [0.4722479, 0.6547124]
- [0.5277521, 0.6547124]
- ⋮
- [0.25054252, 0.45768857]
- [0.7494575, 0.45768857]
- [0.25292397, 0.37572253]
- [0.74707603, 0.37572253]
- [0.28807896, 0.34849578]
- [0.71192104, 0.34849578]
- [0.27310896, 0.4239325]
- [0.72689104, 0.4239325]
- [0.5, 0.10441694]

let's plot!

f = Figure()
+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
Example block output

This page was generated using Literate.jl.

+f
Example block output

This page was generated using Literate.jl.

diff --git a/dev/generated/HowTo/newComponent/08f54f01.png b/dev/generated/HowTo/newComponent/08f54f01.png deleted file mode 100644 index ab07bf71d1f85bdbaae9cb92272e8bc191ab7dbc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52298 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�P^wq7xQ_%(0B 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 BC3pY; diff --git a/dev/generated/HowTo/newComponent/faaca575.png b/dev/generated/HowTo/newComponent/faaca575.png new file mode 100644 index 0000000000000000000000000000000000000000..ce6f4263f8a6506bffb62878ce9cd8a7ffea7cd5 GIT binary patch literal 48927 zcmb6BWmr_(|G*6sB1)>j&>-F2NC-#_NOyOabc2WtQX)f1*U%{`9V6W>-QC^rY|c6V z-+etV?iaUsE%sWo_MVv)-~22>l@z2g(TLCx5D+lIG9OeB5Rl&@ARuYIKmuCuaPjDX zU(ZeCq(2}$KK*1g<;5T%yhQ+i5L5F^*=w?nHMexjykkDj=Djlt+FUaXKwH48-x9dUiH3Q?cBWz^Q0XeqlOt8;~#pm*`SQr z_o}j1GV8F2{s;j|(=@7seR5;;>Gdy`d50Huo1OjvVhbfWbSr5t7cL9;em<5b*Nx(t zO7ysB!Q#QHyxTZ0g8!*9@}L;~dxeIlg{bvk4-^dqjeojkDh1+w#slsFUQjdo|Ik%m z#3)tB>*VTqHDHr`C`I73n<^^e`}c>aW0c4Lo}AC`cK!LhujQ`q@mln8lkefMx3~9p z@$sR_=W@YqN`HQS{_)1}(Y7V(zYiX!ZPBWurzhmT_m}N{luh_-Sidtx zJD|0_y`B7ig#5nK@vbunTV(4@`M|bm z?d+5?G+dMxIf=eKYzg4nyO?!!w6XC%YC}bTP!{vw|98+RL=#Di)2|cL6MsJa`&(^0 zMf}Lm&dx3*jsp)lql)A4 z{F{h&^SfJ3v#r}#o+n~1zvy!`RAYDRuv#NMFnZSwGJ#uHk4d1dS&Kw9YYQ?m)126g z!nz@LiyvE>+$uJ}u{hCB{FGmkEf<)KxYjidfx3ftBmXxfH zR>q}~dnSfdJlk-xq)LZSlOZGimZnlo`Ex2RxjuFk6@S9{poB?jMn()p3Ouy(nVA{t zfq{XhV>T`>uEdYIb#--1B=w`cH zl{{k~(tp;~&=N3MS670hSY1c+T1^s`L+j?i=|64ti`Xl&V=OM2bi0gTup(mC)^3Rj zZfC^~T+zT%=BCn}3hvj=ENfefhND*B({>VhbO=4Iz21oi;&0vP>a`ClqbeOu}koQ+Rb&8j@A;WK(W*f zzecA>LQ!6W(FJVYMiQl9k?LAsjfg>+nO6}1y#s0^y?w6|r47TYlyP@nlSaLd6w>+{ z^lcrf3MC{(ji7Y~SazlQ4d2Oxy*E=m93J{cvZWoH&NZT<#8F~H0(m86R8-NGR%ssn1H+$0kp72qVnaom{rV4g~&B6QL7 z0>xBdC~IaNn3$OQEGiAJ+VL08OFabq)Hr)JR@}bXLuxnm6p17Bw#q`HEAL$?V6bcd z9YxK{A$a$0?TC=8?&(VchHZPEO{29^=NtU|f#EVY4n@^)*woZ^{q`Zt{@3UzMe-%O zqyAS-FA`9*L_QIasD za~KS=nU`%g){!T-$MP2M0xGBX@LR4r7H>)x1zk4!+k#Mw3JdT4xA`sbJOk`ZO;?xU zA&*WXjEL(;whj(|%ipN-|K1SrmVu)&7j(5H67Od!JRsjc*WBfke~ZhFwFJRJ4z67{bK1B%WyD1*SOSD} z&r1^0d}Yw5y_*pV3ryWFEm>(_q?6W^M=t0s`&32@+?OoCQwwTWVKveMx*~Y?g99f8 zR=U5ZKk1|Jg?vP7MGKad4=vkIcF~&cBK-iKxOe5)8?e@ym9EA5IeF34D1}eeP_oY? zgyHmN1_G8*nMcsqUjJ3&C@e#7|8)0grWq#nwO!~DePges+%rpBnkE`b$>&ht@*enm zC^m0!pj!Js{U{#Z4o1nhcJCpjHd7WDryO_``OIf$BHPb1Y}b|6wsxDQy4t4wVzOI) zH~y}guc7*aIpR*~-itsxBw-gk`kF{9Yk$Bh;Lgf1{*zB-iZQ}?E>d|-Rn~|VH_X)kQJi7-j?RmO(fIBT3$5w*aKlD?HH6{nV4tLG(c zd|8zi0*fj7d7z|dM|XHPG(~hQk*qmd@v{5e3P#3=gknTf0JVd$%K5mGCGtmnuNk@6 zGCZ6E-hf)zB3VWK!aD$dX}mTsU)C<8JB?mMJ-|y90X&F()(B=&sOUtGQ1ooQjgsU; ztm|^w3yVTrf5u8*t*;Fxvh_z@Vjtq$`pHxitJf_Hj|%!v2>Vm6KE{p5Hi@U9wK9s; z5t!1Vvy8k9%02!9BFJI>X`P$LKI70mZMZvwP1Q0gAHYBy;Xb1GV_OFj?Ux#RRBA)` zB^p{nUaT=?`s%_+I)I=oJTv353MP`_RUQ6Q_ z5Jq;quif>^Bk2)RNnB^8U5^8`)&mzLOA2}#}r#W z0+~Mjzo4*>dp|;N`?lF8Y@ECqxpW0_PPv)4iOWXxzg{cf$Vt-Hn3)6xsb?Y*)DcVP z$}GFZl_QnsGuOQGuRoRvLxle6Llzn>&!w^^oDq|y!;l}+pUF;u35AXG3_$KGYEx;i z)>*?!(B~l@7!Tsk+cq;COhJm(Wn-;hG^>Y+rmNkmWCj+c)=tG&j(I8j(7}biVmGdv zj#L$mKd5hcDeuIgsl3d{^J-5W-dV;F1m#*bxtq>Lt6Z*f)Q>L;XvNt#M_GRnKVEXO5UMARyPU(j-bptu| zSXEMg?*-048`X_L*nc`alf3j(xwuU3JylPsIM<$%qBovS&5Ti)S;Ln9gQmLy3RTp8 zQ($`Q9vMk}!k|ZkHLBl`zhjXx&Z7Lpne()P!u=pty@o8bzHhL#JBgWx-C}a90L!4l zE=NDBY?^2F?1#K1=HX4;=9XFB9KF45MYG)zM1q12Fw|t_6~-&-D@mmk4s+;?-h&eJ zgDozj=;T@S@TV|~G}85Q-D;&-l?nV#d__eOQ*)6ch_^j*HpXy?E=2VF|Pqt*X%dO`>{Ax&En7J8Yt z$13#FAnG;A^DIu5-LRhXm#ne|E&2kY#Ryhhwhl6H#H@x zz*Bj+aT03NAkn*ikT=?Y#o1M$o!f`&#^_GXYTkAf+Pzdl)p(&u<>G*BwV0im?|2<*1LhUapvOdYWjBnU zH>dl(%III*-7wkFr;DnNDZBz0ChQO3i7+<4DOFxU%JH>{6&eL6I@c3$%^Lkp;#-g! zyBKk(_k7GGll^--i=hBt;YHIT7LIkisb@RJ;h%GVEw!Men6N}rn8ofbSB)qYPyt%h zE~%o~-g6I{aPm)G2xco$0{MEU83Ya~u1Hag2EVQk&b9k#@V8pxEKph0Y-wZ0_y$ic zVyP}N#+9THHb;Webyx3Yud0I!DBrlGD`Z>K-lISLOLP|3)ay05uLZy4yZ=4nu;?wMR=dhAUZ3)c;2~UrW?|{Lw)H05FjyppwJP)bE z3@Eu%(VnxQSDC^ZDP=vsvf!8xBAbgBv_fy48Fj7@pQjjituLIr2=TL^-BPPz0LrdW&yHeDhtLjvV%hXWh9>eS zn+G|d%hC=qJ8-}MB=cn;|KqAte2U|C_Kb*QrSGVu{w?jM-1TpHe5oxC0mEKYpkM^; zewEb(eA#xUe-<&{pN|@41eCW|#$4;%ZI-)RhD>9lqWtzfWqhk7pC=426Md`xvV z7LT#WDxP^)oJI9_3`=CqoklALx^f|Q2FhSmW(OuNf(1-0kpmLiy-ds?j_Vo5)CzCk zXsl^pf5@AQ0w;;n@SNA)*cws8HJsIu`{_dROkR`o?l%$4#Ca}Ur>fj4Dp(H6i|l$6 z$-rulOX6II6bWkME|I(?e=5+?_I5K!E3x=S3Pe(n*MgA9{`Z0q`yCcRA|=o(1E!qe zxcn?4Hr7y4J~5W9^y8IgFR_-dss$zbe_s6kr{ExUPbbt4xJYzVQ2Rd4H9tMsMJNm| zU-i@3e!r5vf|`lp#7RkL4@a8>7yI^u2v%5#A7ywDR5#;ZuC^@d1V<)XWwrWim;|C$ zF#e?lpT&{O4{6!PGBiUp-PE-DSy#S+X=w9g6RMOykFrf{$w6nqNY259f>6MkpPOf+ zPFEkdO44jTUg{DJQbDxW3>3_kJ4>0I6APIJxUO7oYB1G-uG z?(C}Fiilcj(`RNY)1+s%sf%J!iL)n(KW$A+UrVKFah-keb2cWAxVxZ6DaHwclsM6) zXEnSC864{l8Ct}mvSlNnowty(;$Y(Ak*Ct!zB1s$g#Q-iuOPrh{`#aTnHe8E2O>vh z!#dI-fABp$Wj{%nHCUy~xaVfqRur$HWY`}{YbsAj|BTA-?fFJlJDM9;HU^OG zGP#zv6_P5d^JCdEJImm16FE=M{1a`?pS0NLHuyvfD|mv}%!Fr0N`PZ09n9+XWbmUE z+tJK-|E%sm8a!8O*51ks{Zz9!JZs&Y?#`WmkT?A4z6PEBq{c#mc(-;UB`CU~*U9B5 z!Z={ZBuydF*Dv(|^X>+zBfKjQyHy4}gfZd-?n6GSFC=H@_OvC*STFViYQ0!vItExn ze6g{*Ifk}sTjIbQ+dFx+zM{(%_$Xe{`Xl)3R*F8D?_KK;)R*AG0j0{2nBhKK2#U+1 z?#xRn0SvDnKbYC8Fk_VFKW91`x)bQ~6F??8UIvT%KRwC%SaWoa-J$Xum2FyfNbH|3 zko&UCU;?z0!{h;st^fS;JwuXx#oG5cr~{tRGll+sc3m`_;_z)hfRHSh1U+zYNqfxZ4DM87@L4@x>!L^F*daV~mK| zU(3HxVZ{*Cg79$)uSM^g#prisI|!|g0l`*|NmVSwFqVhs1itZ3$9Qdr*ZXhXzN_V& zuj6wdkTauTSWWD+?VMxV05MvN&P@Q~D7%G2ZQb*DuE@%A@%UtzNxdWTzLry4Q#X4j z3jRo!*e&={WaRVDsVq@3Yu^t@$`YIhxfXwfOWss#U=hYoo=1(HFA;N{m0$BT$4zFG z2a5xmVlo~SfK{qUAh`PiHK9osyJz+#0TK4qw@b=9m+Dja$v+h0On{)ap`Bekd5j>6I~C!vNyzL*=XzqrrGelep?#6DvrBS&REs_qaAZa) z#w3Q723_*kdsJYyPDe+-1L?L`cm*HV0{2mk*EUHpguGagxLoNr$reJ06xzERl0O|H z%h6pH?7lI0wxQaeHO0txgxgW9)GZHaE$a1OFPKfe{K|;B6yUGcYn~f;l5*o2SYL)X z+IKk^vJD(SOZT&ef7mUngRl<;8+kr?pyWtt`skn9!lq^ z&fEbj%Jpk~#g$C}`Uh+F1HnSytB9JrJHpzHB^v$!NX!qDhmZ7YxqxfHq~m{E1q42* zRL4(}FK^=u*7**Q;pCr$N>sPvto7MEb-GO$HE}>0_P6X z4udBwMn!FaxfbIJ2eNmrTM$y)fLoL(>)mP?R+t!u7?3%t4lH^#qqTqQgHXZ;(Yul3U%U~^lmu%V0LyXMB(@*ry) z(fKaEhNz9H4ZpL%^E87XCKt=GaQ2fV+4}MV23xB%ftc(NKjS#Ei{->3}ZV_6OZR^{zwy+)~qC?xeZh;6|qD(wJ$zQ6`iki|1mGTlS=&kq|#(n>S$)l z=shk-BAC0Pp@LhztZqxD&NjZkEb&DOn`>{M6Ksn`o*+sqM7E3`<3_}epJ~W?0y3DG zU!~0a2G{MDNRg1ym4%LBSv@`Vb9CLZvOm%t#jAgZZVKdKz7}IBHy+2qyqhQ@d8oms z2?4$Mjy={g;UYJ6ghLOm-sT}mEXSCV1>)YVCraRYPFi6)oeTj3muFJ()DbhXUr}rM zBZxo_K0k%$wxgq_aTFBkV_5-r5jqYEQC7%;rm5QWlVRi3T6-Xi2G041$)_Y&ii#08 z$$IM^?y@_FC;yhMmm!)XIZ^#*Qp|g|?Bhzf>{k1htRw1p>&bkp$9{z33vMrUcd(*; z?@M)kwcwpV@!H+>JG?s?h{~=_U_wgELkij2yBD16Nt!W4OHZ=`)WMOS#lyNx)&X}5 zHKupZ7N%uTQ=6-7-$nQy)6V#;Ovt4Iv$1PhrJyS!dr7DkHIkg;K7Ld^83|ZRai+25 zl`#-ZL@%Kr`r7xexmuztINhEEyomqyq0`pQfHdyLxX-zP6be8rQ3y#0kH z1gA5u#5JxVhN-gSSRB;u$*$b^BbZ(2NL=LWNZ4_f-{^2St7PfNvwJqc3y#ZBS*tGQ z_hd==Y8FJ16v{&VArf@`Z#7fUUyEKr{Nz~Y_ed7338Wwn-O+CZ!ILE_4EmnaQp8nF zy*W`r(0KVz$o05hpNwI<{Vm4Ylp$Ac=j0kb??^XJDg-lX$gK%Q3KSR|pM*9)TeKB? zpO#d4_iS7|2*1X08!gptN#?K^s`nQcuYR22$swdf?K7?ZksQ>ai9SMSu`Q?-%(h8L z%1q>C7wn+Gtd80jB)P*<6znO$7T)JYRxPBhMa}3+0elyj>}cDHiwXvgx^HaH=(0oh z#Pe6zMK?{{72E6)b5=a#CmJ(_#%n6??$Md6SL5vCMUrjz&p`PO+1J%&Zb|8_4^m-+ zkGq41qoxL8mf4pH#O^+Bu_I*}h%PR57!rS`Btwn|jJwGeGno0y9XyRv^fG;r6`4o7 zV=80I+4O86(a?Ch?LL=|XMPx(W5*Dv8kbnhm!y~8h-&hqe#PbqAR`#{K*nBtDmgoe zD&U#WYde8@j6gk;V}Zrc@ei07gOp@%Dak?0PemPKPYC3W{S% z%Z~ocltJz28JWrX}x`pR-$y=tI;T_1W1H3i^J) zKAR8anWfhM{ngSD_Qy_F#6b(Wwg4~~=s9Qx`Q}<fZ{gBSS?gc{R6f8Wf-cv|h0u&d}cA9Ug)Fl^WBx-S^@?P-yHiEfPeK}ncT zIECZl1&_~=e_G912c2Bjf1>jk^CQ^`imk~3`@O2Yd%_;DC+6JY`0p0w>g=uG zHE9$1V4{4nZLsowH|qLasu}Y#^eRybnXlH4tM%2m+ThW~C-+GI$|2UnuhEJFdx6ni z(zR_e;L4yg=aM>^MVVbxh9zFTDp!2stOQ$&yEhSYE`X{^+mBJ!7!wSx$P#0+6PK7uMdmu=8ui0t$L(~K? zt4?Nvsb~m4j>GWA-*7S@Fp+XLH54l@sR(~(&PG0@3H7kGc4GJ8=aH@d9m$*EZFDO} zaP5U^6FacSAjH#)^qt%$L%p{l`H1}wDYw$-xEn5saK5t068}4yGIfQ>iqF}oJcTd% z)0Dcwh*~uCQE%cxb%bQq$P5yE|0$W>6GEnF(%-~M`^oa!F!=h(iqJ4d-NgFl)1;&+ z$slU=FdlaZGUfc%+I+sZNWa~l9{tsdo$Zbge|Ik=TI3;e8MMScZhYj3(eNUMX&J1^i7Tj82SWI!Y$J>uB;FGPWvyMU0hWqt6K+0{*u$PzjSGWR8 zpXpF^!4WnoNB4$50LQ5k*>-3mNOO!CUl+_g zAcgk2>L2$_IJca<3tuc@rR$}-=28+f?Z`wZcK!BFxZ{aLgu>gr+k-uin=0Nhz z;B5>xuagB3<@%Ed2HBm^|0sA-&VcI-2rTHzPDQ8 zVXWzd{Y_%vsv_&w7(0u%hQa9-d4TO!RznH|SmNJp8QT5|QcT_uHDn_FsrPOxZETR& z8|A=}Fi%upP)m{Ua$cSd-rX?U(xI4fiy)bji(x)@f+26e>jhsmp;aXGFbis4OVQ6( zRWQ^)2nIr;iFpw|&V|TQ-{pxwVOA*%LF%iq5L?@rfmDzpMJ=}RO3*5P}zr({f6 z9$(t58QAgKf~ao6g5V;>&YNqEmnLPw(;>#y{QPaioW7W5t7`(G!D5WOqfo7NUtaAm zoaY3--ghkQoYQBt1-{N?QWm+}&(`mlBLN>U*Wc2g>e#?&{J@6&gM+pC3Dx8yH3Ad( zIQf$);*dqcpP-O!yqIb&(sKe-;?X}}Txoa5_PI~qk!rTfFvx3!V57Ic2iIq%_?I{q zWC+6#ia9;mItv46@ zeoCqQox&J}WiD-O>wZj#yyU=$z=SIsB81i(s@j(&!J{oH#)6B2C4d_%P(g1#E?x#w zU@0phZl0{PJp&zOfW9-!Vpq$|K;-}IINek`qbmRXkdn+*W&W@`R{nqrq$FN@;l@^i zOgVMI1<&X+M8alf)lc3ZaPAbEw!Fzu)T-!TTm!-ue9ZlWnEkzkfd9abkaS(i zb(OK<=ow*BP5PXK2fJ`xf;e&r@AW6*kQ(d>;)OKF!Uw?!--+f zd#7Ie89C{gyccuwC+vE4vy9~M3AnxbXP@qTAoLUEoTll%ye<@?lSbJPyP|>zUdNga z4tkg8j`Ew14f(gUFo#+pBel`GbKPoJ6gDR2{$fkO<3r0kmbLywt~`ZQLEo!Snwpwo zA~(_+6*Wp`32Hs&1+o?2*sS;F$3aQ+a7@_yI~M!5J(gnL1TvW>N^JYG7dSuxBaiv*>qe|Dc(0sk}vQ z7&@fJ@I@+{X4-Y($IHc-Pua?Oxu;k4Cy51|>3LJ?`tezxt;G=nx&)>_za!pf=O#)O z{4@JZ=~G!^M)Xs@HS(sw=<_+-Lr062j?T@2M~lzZ(Q*j6|5f^ZWxz#p%XM(|KC)Ma#(@7lxd;o{xa|3mYz9cGjJ6J5@^3?&@ikIg3rpsB zP>6D#NIGKnE2*Ik!|ScGfUPv2gJo&fpF!2YJmDMO?V`}w+S<}7q76`^N(T5UCg`_ES=pM~O8QxRT|TG3uL$~; z+FxUV-<;{f7010uZxahed!f~G`mW`)4cq@{r>IQCd953cEjl+hcb7QiD>d5P{SC&) zrhIsCkd~67XK2{c7}eVs@M+L;Fh#&+!;ytDF^Q9}l1(@cHdUb(#-)Ds9?$+dk#Wu{ zI#P10Ac?k(G(3?}i_DSvTME*y={|6fZ>qWW?JB<{W!`qXDmb;Cy{4ze~Pps-*{c0Q^&ExxwHdsXB zwBsXFAO-#F5EF0EqF$oLjGj$X1&GVnX|JsKky-rX~e(=+UMY1z!q&yf$%@;#LB-+%Grj{24 zc-}}3fJM;-vh`q)aUWam3dTv6|UIPclYeda4A@- z*M4Y)E!+?RN_*^9{jILV{pZcVIUG_Jw^%}^5u>uj>y^*`SzS|;&f3Hz>(-HVGBorh zn1=`8&x9M?_XWMrwo*M7>aIurgN(ocdG|XaN<4=eyE5dR@Ck#m7r$^tqHu7TBDlWv z*oz>MpWE}NaH9xdu1=6g0u0VSkq3Ax*>!9_Kewv^5nwIMY+5h=#ubyWQQ!^+$fJ4R z(}DZ@dk;WT9X>uB9xgQ9_Ryu@@jY%V-u+R$`yeM54*P#clVBfdT?om8Wx=c#Q5~RB zZK9u9l13!DUCcba(mL4i?v|}aQYmkvqDGroT-~^{-a_Ff$h-k*2u_(YZCyneK0Qqn zw^-K?dnY?}U=xRn0c6+B%}tr#zHQ49ddsH?^#I|l8~^y7>>^kl&fFeiSqdclutwvR$MSTH%vKpB628XARAq~uYYU-$ z`m5y8$3i&sE?6AQ&?A+dJk#uVd$Awzcro7D*$Hq>4{zY>ez<7&O)F2{ChVJ||6Jgc z8_F`sSMe5WGr6>KyxnyrnVnsnA_ffufJ;|6n_g`W0{eZPzXG`okGm?UYRePTJR08z z11=mR`Ef9zLd)v$VJW?}+qP6bGPv_vMR!3Q{gHW%4sQSIKdzDz4K=*i)HpJP<}JDA zXI-QdZLL+~m)%`>GRhO7x=2vQ^o)T>!S5=Xtvzrbtf3d43R71p;x^;7Gl>Gn!L!QQ z7*CN%wK=dt%?@=stb;4Q#&>juut?a}O87@XuIX)sTa{a85{5m|NytTeceO@+_@{zY zkuUMH7@A9*7J+fR^<1UJ1MFgTMR5n%^Rpi;m-UsUr;PT?c;{Pp zUKFvoxMjp2r;Tls_z_2QH;O5qpYzk$x?eACP68`2+iNSZb0K`dLSBEfyKlF4!3Y{!szZDqcLC_|#_N%-X zR}j=OnL$3N6z^8bnAx=y=nwey;`qCxNEi?i!BlFF?#;Ej#2i?XVd$dWx6r`sd(Zcj zOy~JO&tr6FvL=|H%erq?oGk4}J4luYzyuj2Ykpw!idoF4`4b8Up9r`_3IgeaK6s1r zD092m2azW}vEtVYzuyKy|J4}LK$)6-+y~>wJ4Etd%gRRfSf2tcSukyYR%B)#4bg_3 zywT^fL3mwP{^D753)bLBh^|fifY)D5BX(DnBPhb6NV zaQsloM<}LV=iJeXx9-Bq{$A476V|f*7;GhnLYBzt)rjrch#^zhafmDsl7I+Sb<9~y z0xAup5ar%Yzt1fg-J5=`tLE=TxM0Et&)2vllK$6}#sx!uioS<&a(HY&tDk5;p-yq8 z?T`Iq?+ZrO`II$=uJ7OGDR?!Dj-s{yuRFT{;!&#;fYXoC?aI_6>w|r zLclx~K100q#P%EWE0`@0-ch<$AV|z|ws9ov$i-@WCOevjHWM57h+<&%XBg7Lpj{dW z_SI*Z=`Cw-nj0_PYgyOlz63Bl5550q@nb?zcRaMod_%{mHNujXnL5OdsA|Ikra& zsTAvlGAMCgj9lzAR*RZpN*Yy{bSnqoJ7x*}k@zN3zeGP)E#nbPHwRs8vs;@4xf==m zk}?cQ)5~~>ggGi6WYV$AV8Zr-?b$2Rnt5bYR=sY_ikjXpa&S^r;b=PaE)8sIU^W>@ zC;%ThE~$cmjoMr{pCkS-mo10PlLFn?b14|6JDV09O~QtyrUc;r%sfxcck$>31O}-5 zPp>n7{pA=IFM`t*9(!(!b`5+Vu+vi_pCt-ODsS1^8!6`-l7a%9yzvT)phx+wq(Tt0 zg@i*KXyJk&DU7%5V|ubC`1nF^wxGO{nX_D6i-QhWn3!&j|T>-=xAkV za&C*@iSDs6$oZ<7z`cyQ{}+Yo@s;$07fU(MU+U{j^2nK93}6<~imFIh(%Qx98-AbqG|K zn%_@wwpg+Q0xcS&Dkvq$r)<5D(G^zxVuOg!o+*@$CQFXW$@#IL+;X{Y-z3VN1%Gnr zFRv=KiFiOMJU*>(k7(wf_)f@6s`-6P`E#P2tB_qy9I=Z|7vF6ps%zTa@~0 zDC2Cooq7U!ie0`^`T6_q8IZY(bhde!^jldWnATxBYx(CBB|>ow@Q0^Nilrw#Wn@oO zVjP_&IK*bKt=mla<@K88|KW=9l-mhi4qu{$>KOlp5*eWNCF)_$jCgmI8>cvvRKyEC z{~e+b|FCQn{Np2=;uDdmWe+YB?^7d48cg6BBMfKKFJV7<*93X6z`mSEqS|y~FwKcN z@SLCY2_;jj|7D6G)!Y<#G5$Muba?YL4DIH?+ny%1>~hR)mEF1gngL+1gUM_@lFW)V zIQn`trl+?KzirafWF5XWFKI@T;$3o-RdEx+rs694ol?~(P9^72t6u=bIMi&Mv*Lwv z)AASN>fS&x9gn^UmF|U~ou-*n7{YL*PvW%ypoeWfW**nno@w*zrgBH~i;!9aK4C30Bm3yvU2!FCG=V!3`{^xovF&taHBh%laHN0|OO=rUlCu4iP}k zI*KEn(gEvd65}c3W8=I|ozAn{z z;$y0F%(v{+%)9>-woc*ca}`#Y-}DzJaWK0q=ELOlxPY7kfey^Y@4=*VSaYMJ#(==f z9EY^rGvDDydTStK{ZK|rO6lxALmqcyUONINd9e?0Si+q&C>lqTXc7;zcuf8^oraZM zZ~|xx#y1x<@i(=MG|!&MrjveI{iD_l_eX&$YppDOV>wUQ zM_|s1o|(7i63ukZOiDKd*UQ`PS6NP|(5)wTV980y2(5mq3lf=U{6xM_YVYb>=35RN zKY%?rCvna6(eqANY&o#SfKFqNXnE=XjeWZML$RC^s(7Wykw~5lk0%iaYt8+k41@-I zgT9*WEQErs@M3=W8g&D;z)WERNLcH8MxiH=gW!&r9{UY+;C)sp1|7xqKX^y`0J)xw zj0pQ5uoZI54ClCG9=*~pHr0@W$)p&q zE9F5gEC9afrtS*Yw~q;g1nU+!I#tS#5+a+7*4P`@1W#*Vgee^S2l%3B@IPhjSC$j! zL{PD#h&>?{MUvA%N||AHvK>2lH*|!z;vz^H&l5QZXyi}{xVWf(Aor}Is>SQ~@KeAA%m`$2-pt_!mz9kb zUpM6dO8R&k`RUSrmfelJ141_M}tz91d` z3i^B+;ivtnoAz%77Y#4aB--bH2gK4W2NyQaEGN>$Bv1eVkz6(B$7P(pv~A)BZE4MK3`8aT%dSF#P_U?mz2A>uZ%pgJvs`C;Bot z<4UO44rd-e-Z9&hNmI}UIKGp^i-yVMX>VNGqcm(^s{1wCK91X#H-5xlQa!%iw37ne z>_hEsk8{l(TkB)tT{4t=Tn&>-Q|D-D(963do{h*Ma7W#{&VplUuUWz}DJLBHOWa}WppvJd)o+13puiI}${ju1wm;$siz50t5T#sVaK0R}te$sKyL zPAsu~J&Bd1uhPNC4Lyy7;cHlno&ME(c`~5T&j&3;wdP>rl66V}EPDbL3cjawd~C=x z94s>mMScR4>?{a2xnjA0^<=rt`G?g3cFsjvW7ldpdx_<*80fAE-Mo1995~=cHqSWHOM-5_ zM!2L(sS7md1^6-cXZ4Cm({%P9`QTn61I=ePq4 zf9ud@0l$FfL+kN_dRcLcbsRUmDm+pF_d6T}0K#DEy8C{g0OhjRNiac2W#dhbFI;a& zvYskaW!xfHEG%Yp@Ev{*ZnahcrnhCcXi#d8TSqav94 z$I_vyW)vy1T~FeOkFRSOtdKagA-Q zfJ95CYd4DSx=M~biEgS<>})<$NtNgghu)Cz%rzE4!DnUk2=8&1dOjoEo~=w! zb6P`(F?XfOUzet*By@i3if>E-?^>pvu%DkM7ft3lFPyb~-CZ!up&<1EC4Of6W8C_M zP(*Cihyq^|Z}ph;MH>iCS=BRs3uj*gvR8D0W_ z_Y;i%aaVE$O8!%LER@cxXN53X=gaXD;ge6#KY{oxl*GQY3^cG@y(iZmUh;oy<2(~kgy@_ zhP{xVX9WYVV@`9Ryt8Iv{YAUmFUPq*4wEllQ%0VN_2VYmr?AlseB_3ASA=2_Xh%ME z1-$lHlu1Pb3w;3FXTlYi{kscA4`&7z0T>^^1Z8Y&@(%w1xnL;kol}`yd|jlToR>u4 z#+&bcPH_f-d787v)Tpno%fA6BVsenR+1rW&1v`&8MTM~-K~Md5cTKOsCF95Q0F!%c z@yrqiI2O^K0x|EHT;mv1OA)4AD|P4?0beJ3SkDiAiw`lAyLTC(eMabslSU&l-%V(( zR_TncnwUX46)T!=TMEq^41e?3UKL)gei046iWwNjCl(FiDzY#@12Fa8*fI3Cx|Y9d zxma95%TlP3dIz~uH@>yYg$94n+2b1+j3!*iR0f-|gj8h&=RWAV>+8x%lNcyl|HZE= zi4dtKV$Rmd*kJY(3E`!0Xs(g1eNAS_?dlxh4>?%AaeRth$x)q=6_asLx9iyTPed(s z2vR+7XCm&R!pgIvW*gg_!c3N6$@0+1qi`}4fkt^LB8#TLT%+mwqsrBrQrLpyfeFU@l2hc?b35aV>2Mkb{ z_dB!h z)uW4{8Uu5*uX+dRUU!06>UH5V(mb)cuuMqdUX|-)clE3 z#Z#IbY&ur6Aa|BA8j1B^b*Y}BuDM)R{`L2NQ72y~zQ(IfBL zXL2&@kh|Lqw-lEcJbV3EZ7}LUkXVpmpU;oh7XlGLW3t9REVZ?6p$9v*mp^9zUd%G z-Y<^g4uCmFQ}9uMCZd8?a7Sw&IH#)tY!#NX(a4C?*)6-W4g)xMd&PnzfmeR`L8gyx zxUua5rkkblS|C~yXG7nXVRbL(f&R!^Pxck zvOPRk|58TKHsxW3xx_L@B?Ns!!1r%s0fPno8u0C@*T3L%zJj0{08)(KKrirEeAJ$$ zthL+h33OZXdoRq2Z+o_LJWBo|9K(flJYQ2k4zOHW4>J7p-%{K63e}xo+29C+bH#D1IU06(DQUq zx#x>~($6n}B*||qbCu{}2+Y{Wd9ydv3pqnnIeWI;&{-1@@_?jJo!>0y!S!tY38w<> z89a_5kG;P3W!Y!&4XNqbbNJUU9R_m+u!Nd=mL3lb8vB9HcT$a}R}2=f|J!kRT7X9^ zuB({{>q4`4T|)dY?`xN{(Xf4}4mAxfE##vi33@h(doBMg zkG^O^z4n#3j^N+p>gVI`*FPd~vOcCt9@&?eHo1Vxub)e zm6+>lyOU~YjeAj+pBxf>SmZ+}HCQc- zP8D*4!}{786ssBfgIBJAlq1C^O!linQSd*;Y+Qzr7JGxu&%f&NIo1uup%;D2K7Jay zn2Eou1Arr}c?qSDzbm(a-GE6;0hZTB%jOcOXW6$1@F+A^kpiM~@KKj;{SJQhNqUpP6;KBtnO zibJhUJZMv*kNpk!{@}1p3@rO~eifB2Wuj?yiGegbZ1c9m{amhF>ic#7`fz)9^7Esl z%Mmh}lj9@*oFMVp$7#tZ2I~zS8%#j7`H49!_5A`+na!f1I^g!`?HSVP?nk+;LsM{b$1jk**_Z z1WOzl1N=NnL?($FB$4?5f6^XWStcQl?exEk!1g19o0Apmtr~mJ-aoGsql^!^f1UYz zeSJ8by@c#z33z^5FI-ijD2qKV0mafEIz@yE z9To+LUbhAnKHcYj24>O2Z7|vw4Zwf8{cxM10al;l@k1$Mc^tbRxnDmiG69`BZ&UX1#|N zSHc>l?CU>xABrCauE+by$(>`>9Eh8GT(P_%=K#rk}E1QbQ+8d_9iz| z624)^S2luaq{ki800>CI%*Fu?E5R&x#H+7b|}HKrqag4 z=~S8el0o3=$ADr&twJ4dd*HloA>gZFdtW-9QDdQpGq022>?7U<>YcOA{BVZnV-Nn4 zOioqa1z0%tT8uoB-#{{Sq!bm_`QMhAIc&e|(POY4=IV#WllU#%FOf=xKF_Gd^h1T= z_lx#W4qRu&DwHX}Kk<3P%JvkEdZ?ZDPo^9zX=0m$_#7; zFUHxUxz}Vc0QnH_?JN{mJ;JzKnz8=1B8HWUWHjV&SDHJQDgS|q&-;&@IrmF91l+v1 zG%j{i)*n^lG567Ahl&vpVU5=hy>}}W6&3sSy^^^P#-&OC)5hcPY}H^C&IhlD6_tNy zfMy2(G`Qg!_0yN>)Dm{W!!qts4Dfq;CYnV(j44Lsyoj_5`(kWIG)4RNb_BW(wmuGc zWK2MOG=MPoaJ}}#)_cYVKtli-kI<^mCS9WV^sis+cGlM0gH(?vaySw?CMG@I-S>+V zL04byj+XD$Igd+UZ)ugde^Re{p{(;3@KjI8w}H-}8WjPds4mr&X8JB{$Rn3O7>PS( zE#G_j@5QhZcld}=%r{S4wwo+hW)Cf9cP%g##<=eRP*}i`z=dz-S zyQQ?p;J5-Jxpc~$7aTrr*dX`+O7IB>6bZTLA9_8S55GJ|F@P+eaA0@9XUcaEKW@vX zn3g&YrpQPi-N72Z!0wjqaWm5_=oY3TT3FcK-@k(@NV-2g zT)PB$-W|3H0eXm=_3+H=DIBSI|D%qb@$1LEqi_9T_gck5K(4RU^l|j>iTNv>ms1&S zqQW1UYZ4%$(>7iaF=n>s^#K?2Ql1kM*Cznj2YYU7YYR_Z{B%z}*hq^r6Cj^Z-7Sm= zN|6#16H`)t*-W5nX6)Qd5DU8g5PaPzg-)PB`4|t-Tz;rNw`rdvrRS3Ab62RW3?1<>f;|y*~x~C63r)Z!F(i=13wjM57j#hen!Nm?AZ9$prS~)e-u& z2NA_>G1rvYwZHhBjY9gk<}HOxS1;0D4p?|{z@<5R8`P}t4YZ2k98#ksPovcX{>>CV z#;@JP?-T|5 zH_ht0jym6R3UPB!!gM?h?ScZlywg)tnwpw!zElKT5wWk!MPrl1JT@1y?|>O4B)K=< zNhCY37!@~HdHKp?lsrUhK2rMjT>~dw` z{{nQb#Nhi)*o9gk;qTnBopBtnBX+&=VQob6s^RZndnK4&XGik71K11u&}2Fs@smE_ z#CE&b)l|w>{lg1m<4C4FQBuQ$Bk)Q3J8!9uI6WG8F$z zyPq`P*&Yyr%~ol5eK>ZuyX`Wzw6V2?4d5s1{hvSgiVXoOtAG9k@gTWkM+OS~R3|=1 zi}A}zH64rxe!K}F^20iR5Fuu%>MV3$zFY>NQp-8&CwM-_-$jT!s`DO^?fHBj)e92d zrCt0JUAceyzlHV!lE2}}|1ql;-L;XTV)o$tMcMUAr>L-;bWhdhcmH|P0!FXwqc;C<&vJ9EB>deEY~rE~^;SPd!3)XV79=LNM=Kuj zaNtTuK-7blrECF$5ZiGvEO77VNSF7~-12Yas=wP6Nr*_?0J4y1RdV!nz%r4Nw!tFq zxJ%1H{`Q<)qO3~7Z$1&5#{qwEx3GnTeL`x_S19h+Z=2jQvm;+5&{3zGO0H8%0m%|7 zU7OFMiu^`fq%0?lP2)>!`HMjNSq}n~rrOyUpA9t_vA>QrZZ`jGuc-OWZu^y{(1p41RTSkp);8boNy{(A0U+|T zTksi)TUO^S7|{8M6Zme?NZRTf2hARN#qXGxu~)s1-J$V~Un+0?wWY`qm!=~S=a43h z<0WZJ%&x~57ex}MNp!oRLPC6d=_^X~Rz5XGt7ITin0@pX9P=tF1p(3y8xTno$#;@% z?TXl2lkBW@8-MGi+W9%hfDRdBZyGfu74FGF_pDT3>!nm*1q*=!44I`2zh*|BNxU?Z zbWV->Nl(=_I%uCVaNy&$ZdsXQ(DWEiO5pE1xfN0Sf4s7}` zsSjX0L!E$1VF2qA<+h{Y^P`dixjA%jKc%)r%`n2>Bn3richRVuQ&cr1H7Ui^@aKS2 zKw-KrU{VV$j-S5lgmo^X>%mvh#WTamKWR-8m~jrD3%8k)4p{lMf4rHp-{s1eK%iq% z2*Lyn+rL){9D~C$4zSi8+pTy3)IMZ+dqnBG(7!iBzF7kBn-tEGAFae0((ztE?dhrI z7@hdG4|@>LoDvaKmv|H>HmOo|njttC(3Si5{c$)V0YBc_+rWgvkQ}>{*IfDXKEJ60 zU3}d`y*E-W4wdY$@`%eG)vgd~MU_5FsfUwI%wTu65bcgj+e8Rnr=;Z$M6`8|;gCJ& z5dUq)1PBGPm>iT=KpoEl>8UAgB7m(=9x^nSs4L zG1FGFgaMT3$=!t49Ff){Vx)m1rIot3!UKrj<=mcK{^0Ky8RiLDzOj*4KG$nfHW$IO zgMn>>iV0bHTiUHAu`mr7m`oa zQ?g-i+kVII?q6upwa7l~hJDm@TtqY;YAP4FmC@?K&D$rjxcyF<^p`J(y5x5Er)^jp zTUlc?$LY*)o5IGp5x|J4xaPTEjaGX1L+cBIh2pkEjFq_h-NqL!1R`aD=sq17lb5%e z#+EIk+t{nSQ%!!?mUYoXQF&C{quT;r&HzsXzVygnz=@))1-RrfELu&<$Fo6>R83N${M$w9~Pm3l~Y^Y<#EPGDhpI<4$95iDm zLwM|6$~CuYCBlJAA-vWc?(qzfH270jGzoDjv@o2w>)ew+IsWjVPu$_(Yk`C}zbGK8 z`)4W=k^8Lp)nG!Bb|v7Y1_K`VCBK1p#rXpfLW=dFze@U#F(dPU&kCr4e7r~VdHE-5 zhWDKASjL=UnV6g7$#(%3P|zK^9|F!{Df>XN8sO@C(+61R)0Q7aD0>HkJIfThc-l64 zf3nBpirv$S(q=>VR1z1EBzS_?0r$DWGYCTcpkuw~)p__V|f06*^D zZPX@2&5o(B)AZz{Rw{TxrX|Vu%7|6SVOf-uorobtx^Vj)*+tfJfWgVcRSyy9@i7!A zO|ZP{4-Q%@u%_Jmyq(d_7! z73lojRbnK<&U^lM?$t_!Z7cwdO&ac*9ea#huNTJQ` z?qy6Dm&V_gFNjXkl~D&VU0VI2Uexe`5Cr6VNOXz(Q^xC^e?0&f@DY0FP8);6uGtu1 z>2p&1Q8!h8xws9qwyQ|w{F^pQm(8~h0NIHHXGTBtUq8@`nsrSMs!f->F!~;6_L~X9 zBZre~#^+S|28OdJXZ!)5GM2OJ0i=ix(WP)Za7yPrB*Y!f`9GcjC<_^SHwv8yx+``P z4)CBc&3rPH+SaG&TpnyNZPz;AM%ys4D^F_|ApU z5t-9ZWs043|CES3hMwUoHmJe4Aosi1g#X2~q?qM~CYzKbqt`Rwy90HLjUl$NeLNo= zqQtbZPw^b0l^}bi&LeNPWm90dyb1^Q!VX?-mz#as1a2OVK;9OU>j%8moTw3WH4}Xw z`!w~H`1;;telbNZ{lrePN)Um}N^L~udPFO%6s1@ZRRlmiG)l|n>^lXxmVctr>3^91 zImvY#h!K}|u|-MVo%x2i+inC->bZ79*_wmBux$FqKc~9|9YtBomISEPy#3R-ay&}# zPqtA~&6fSo*d8$Cu0W7jo7!d7%>kPf3ST5Z;P^epm=j%jP1?FUapuHRo)xXu+5YBy zKXrUI!*^?1m;3DgHi(!-snU+o`Cf%o#AgW*C64dpm~UmCtV4e_7OTIW=AB*f{wv>m z&RkPstq;=}u(KAR{cnMWn0b_U3UPcALD3H`^P`W|=hu90G1CzC?5y7SwCfp+kluLc z*?*~UqlCdQ#nzV6qwK&M2$N0lXF!fk=$Ho{cM49o`p;!WMVMv2(KM5`3$PWQ%$Dd_ zXSzon0NVmAmQx?t-&&3L#($zWQyd_|WPux(8}&0e8|BO+Wr(+Qg%yZ^aQ;vx-kl}I z-M;(7@;snHI27X+UR6aD5g0=yF9hmCk3Om}Z)X%!F@yma=>f;sSQ<(SM+%r}W21P? zZnP*d>R-kOhP~pk5fH~HdVWfUMZy3&DK-hv-b81pa4ffQc-vZw4Ep1@`)jx(NRBq| zuo4UT&z%Q%V`t&JnagF&^IR8)imn&+pGhNJJL)d2=z9}Yzib^1o9kctiz;%pOv=H^NI{DWtJ%BP6uhU)K+oBcuDBDTdXa49QocW3M?vh5yqzu=epKr7T9>PS*PFA&g5kV^@G;ws1jki7!44 zM8SWoshq!DRd4`etRj!!zrG!E9(g?@M6`##!*={fmeZfIls?5}`WP#70r;S?%ADa- zS;-&dBe8c;YZ!Ff+G?AC$=^0%Q+{oWg}92A8uQ1^hF9ctIb01#t*S^59IpUa5bJZv zc<8w`zn%5zMWVatgj@R`i`ALpB{;}NEKXct6`n2=w2P*b2oSka>>ET( zrDVIrbQ33ErKXGx+wJj5$7ggQ=~jAvFJ?>;pBh`7|Srz^okPJa6jWs!n~%A zDxTBgPZH!tV=@1)Bldo@k;igT`fE7Q78ul`P;vF2wpn&ZRXa)ann8MMIa)Tiiuoo8 z;J%J)c=)wkc73wJN9CcsjRAb?Y(j29o2~g)R-JZXS?hTkV5PJweggMzYZWmZP!D4m(W=Fizh!NE!bc)O$v z!gkKDiGb)PN|@h@eKM(bM4~=-_ar_91%rPwNIr^v(iJcU^*w$L0n-=DOqXKdBO{Q$ zX9lAv>83Vc_3WouM_pmAQ0L`V<}06#19R98W*eN2VL`%AKUm5zFpl`YY6k#v1fY$U z_STDhU@Rjm8X80_|Gr6`1Z@95^z>Ayk^dFY(F+JaZ^^pJ%`dk@>V~T|&8CTI#ZWu><*)%<0kGASD*#JDYez2CP-tiuk<7w6>DsOFxjTa*i+hZ7!(#}z zjCu$M*K}gi&xeX&DlpeU_G$fT@`SW99yuQ57;kI=6f$(Qy(t#eh01EIuAatAPm5Gk z3!rma50s5&VN?TuvulittOo{z?`b$zV!C`1;@zoDu=C<`w(`;-AF)qKQhvVBh3G1b z;;Ccq`?6Iv=}>y1(ba05!f*{;^$g39ga5iqj=41#*@ z@T#2`OlnQw3~I}4t5r#SC*TKo%d1aVmGp-pgiEwI#1f0GKqq2+CCc0??S)9(W5DtU z^R4<17Q_Z4IdT+BImEOJMq$s++{6zpp82S?E zi8HUhIfr;$ySBxp5cH-p2{fPRclVQr6f)c(m zkMIiwNJ~14+LV|>tiGb|wEQ%XU%VLoza8l5hCK-{Cg3!LX;|48W=}{TjEvPM>szkU ziY+wFwV!2=FL6gOZeN0a*L_nbIr+^DD6&kUvc$cCe|xy65rHZ-W&)8BzvHX5%-x_f z+*TynVtm+uty^M>@!ddui_GVLv;ZG0aZ@tqW$8rFMn@brK(XxHvo0q_C$A0O$WHtD zZ~$YT9W144Pc0OaH=fb^t*ErJ)|Y&fl}Fk;<&P-&`O%T;(nw3E`0l=-egL|H%1*eX zKGR40X>#g&F-zlQi7e4GNr>SSt{23IBk{^oXSzBvkG#fA^jzJdEH!+)!HC(x4RU2T z=1UnvYXXA^vC5>Eg%4dOVdiF2O)p~vApjtDW9xlPwq@S$o>m5}`GPFFBTk>_IXq|9 zPpkLL%x@(qxOr#fcbXOd?w2ih)WcZB^KT-ajQ%mG|mjGCTd%6jOGo?tC@=aa1!0xe>2j zWuV#OqmOfFfpk!*)tUmVy*d^SP+JtoEC>ycN|+#%YQa@QoN_n?+nh@gj5G2c-e#2+ z%*Sfj|BF;Vvq+AjkOn+VFx_1f)D)98r4et|b({S+5^c9-dU2aBVCb&VNco)@7jGzj zQJ|naQ1_Zal0K2YTkP&5VqKYbmYcfJ%q<^f2piS#b_g5UCZl8PgX6w7cjHscK(5o7 z#V?&~r)FO}U|usM780;A{xNCq1C!usyyHEMkBS<(1x;&}P!=S+WSEzst})x;OvJnF zT8OMElJ@n?n)S!eO2kA1-CW_dW}nmV+4xZy>;jLd0fL@#pmk>kBtdFno| zI2bJhXQO`MfG?Q_jhQi@orpBSA7Ew^0eq<3CL~Nv{>PV)w=&7U1-ZU z7?8A87i?4?;!{ETkkJV8j=#`tso{5IAvy?Y=2?;fwSpFsi+Gu{tS9(VEL@Hb&kyVZ zQ&ur>`@S@6TW$3R&B+gUEgsuX8TRvm?1YvXsl78KYLO+vwi`nGJ*=$W#|64OWMAu0 z8p5xM##VCo^SUP*JN?c_t81yM(d5RqdyjXl>RPMT%_mc$R?lb*Rb0(pH_G*~4NDL_ z{eeU+0kYNjgUt4bIVi$tuo|LInAHNU*~TZSvf$`$lpZ%iLZxoVVC2=Jn+X?(WY{km zk@^`BZPZ5U6DrmWrO<+D29U^& zMs=ghjeva#zz$@8@Ox(Ku|8#eSsCUTPODPr5BWU{- z@0qrjdU7&cj!0ulB;=pWZc1D%fb=a9?!}Y zN*VR&Y09h6qV~*(d%D1s>2vNbs`oZwCM5-GzN)TjkhcRkE;4?Oh%Z0R_^bSEz^SJs zUA<|*O4)GI-m~L%H=*e|`69yZGdWKTd%7;F4RKHfstAXNK4^TP+rLxz*MbcFzB^N3 z2QSc34nD{+ygo_@yZ_B#jr+zJ`igO+{Ya z7KmN+mV@7GVlAgCZEm~S-vW<~be^n<-LY~CBLZ*9b3~oPtch3qL>{@kvw}EUVv6DNmyAu+4tY93QkBSkt;QVKlp@wr=^n6mjfXX zlbhoR{F=xpC`CkY;XJbS>b45$X(GUcto5yPcO4^#u(ze~C*ix~*oAGUNPm2|4`&E# z6@~A-lmXuR)>v@O>(rV{^e%72Q!T@>CqkBV$gFzx8D#pg$HcWs1Vd*F zEuw^i9OXNJ{?Oeks;-Q9hS8+=wRtD<^ zy}YavT}6zs;PA6GyBu6~`XEdyO z+fx~~plt%janAOFhMi(0+_w`8n*>y%d{@8NzQ`h=0Sn^#w*vjV%m^vgGX1!%Jvv1x z6iqW|g1m-I1~0h{M;=XPJdcO1D7tV=G*|HND*DF0Qj>HP=TSu0vr6_+^;sj=@bgD3 z{K4SK2P5Ell9d(k-_`gByx|^wSijKXJ?q#=x7vCLpHEHZ3k-X7o|5#A>VMZ-imZHx zM5ouBNP!_2exWEn01%y!=FJ7u@6h(Xs8aV|MqG&ZBROaeI{eQN-roWnv$F-gQj)v* z{8|(W*c?7xK-IGknps;Nxrx>3H`9>Bt4N{{la}N8PTopf6jYm|fU;QX&Lui^L zbZ->wp}XQvC2*#SZ`DAO+Sk~;(^hGjQ%`!Qyuon6xWVahakbne+tz8dlebzRU%bfC z_d(`i);@?h49gWgX^waYByGy&puJ+N+@edkdf}0s#VS_Z-j2MuhvaGR z9Q&f#ouh{T*X!69o^K=XmUB-#2>)j*`i=}nnL2sM>{ln?5k9qE z$cu*`+GBosb+rl*HF{6UdfiMRK=1pweKSFUDiFZ;Fs6=91OOtOr~93?F_mGmQmq@j z^cP|a((waSW|!Jwg+Yx(VcQq}4Pgn^bm5T*w&-&ZDyPrUKVuv8<{%@+4An1;7bESe zGaGr`J-%x`!bvV)2$T34M$}HGPlt)Iu)Lxyv}L$k+#82NO%NS!5QXPPgG@>f51fi> z@(C)>y!$;HT#0hOe#eA#{j;(`W*R>s6d|l}6uj%|z3U1O?>*z_1{4Q{zJIcT+k!c- zcHbJW{hNc`j_A9<{I8(9!_4~>i!HwHmXk4rLL)%i-tx;J0NCY~HQTwCHL)ji?lsy; zLtf4t4xI}wu^tW$m>D#*N#A3%%3KCux|86?o!|se>Hd8E6L32&MC$xEfU;-BcPAgv zA7v80Yjs-I1E@*B&CA{{skj8d0rw}mzv{thLm_Wt=Z(uN-Eit|mKj;*FT+Z4#5?}) zik8~ssi5gn(WVn=6{A6`o!d-7;DcP@T|RJoqq8T5Op+RXQ{QbsM)3fXazzE-MC}AG zEG{lCFLwcajdQ6*0J9MY=qg6JNP-5+LgOM-Nxf5(8L`X`yQAYS>iHpRar&~8M++Po z_Pl4Id1x>J5vt@#DDY-Yxq?oeCp&?GfpDC8C8qlNLgM0i%H^Dg<&d(?^5mTp^$3zL znLTCufw!ijDZ$#Lquoy8@OQ#mf#bSQK@+pgzD|)pzOmx` z1Qz50P%V~AGM^z#I+V$7=ne>&-d>yo@)o!TgM)kZcMS%DxfCQM9nG1z(PpdLy=Uq5 z_k&XRgOR#o6{tFu_&>b&`J3s*SkRtJq=|5m@Ru!1bcjw zCK}#mTD*Y9n@ilE3?@okEfmW|h4sgx#`%b2o4@;=*#iQk5(uHv^72FFJ)Rr6Ppiid zs{nh&dqe%pX}n9`&1rVty`~Ey>n+&}En(+#p`n{rqM{b9qA&@xMfr?D3sNYtuHLZ z-0Sb#j1}`E@*X>LTV;so<>ev@@7cNzAIa^~i@O37#Dls>xWH`;nU<8Z0P0l+_D z)E@y0`*XE$FkaFIgQeSm)OOhj^2%sG>dY-h(73-OE}WE=NahsY&uS=iuth&P*)(

GNw#XFdSN0=~8f%I!L6-Zl2ptrcCMarU+@W|HIEw1YK&q?|4=7vs&Lw_||Uc zM?P@Byq)xY7>tSN%Sq(*m+KzmR!86UP}Hk~#U_V&PPkC%bzIXkydS8t@imzAmc>{R zR`h4IY)w#9SsA@Q-yANjwxp%RERG@{9iWOhSP>@%9ML^UwC2|)@5||WkPC^=-OvvY z4U#_qr?aQ0=P#elc%aZij2U$3VCFyHwR)*%>K+Ehw99%KLK2Bf;3Ate`m$@G(y%`Q z2=7T5k&37_ltE{VvzY~3IMTFo2XY-?G#Oes(n}U-Bf`PxG&hZ+74snj9xvwgR{PX0 z8Mmb5>fBtb_u*IMYk3wl*L@ZWJYEw>EjX-5`_*4_8)=bcZ@I zZ0M9zWg``uZ9mF~()0Jjmz>oM;UALm{JQaty_5#J!*6PhO}-s*w|~}v5N%9zJNQJu z^hbYpQK~{5ra~>xHJ)9R+8%D_{o+U!ZH_cQ%i819NW~%)Z!@eT&}4^oRs02961fdg zlLB}lPFiOY4O?DrmStD+W>#UY{3BxCq7~bH7Qk==Sm%bgd>zA_jv^rQZ=d zUm{3r6NvTIYfoY*Vh+~Ao+I^xH%~HExkq2omXqV6=D}?9gO9dI@`EpWuQ=IJ@0+Ez z74>6kM0A*w7pjWxe!fV5=|%Cwb5r8J3&Ew*oTOk_i-Hum&C%;5)T#uvH`)~4ANwq; zu&*?3bw=2l5c(T+P;riFTbg6>(O+A|#3AXtjlfTSLjM6N46z*>J##Nan=1j)1tZQc zeHFud``-ZIi#U*}1M8m6(H|Jw>s|tJ!*i%m*8;37Y6-J0z5efumhgBr@>s$BR+S9Z zrh9CH(rd`FmNm@;g?O_o=uw6)Y@}LHFDwksKC8Tkt~Yv3bb9)4xnCB1dN(q!YkiP) zIBq8a#~OYhn`)5MgMvrn{kM~Oc_bLZ6tnl1=m9xo%YP`?gz3GV$}sZ$x~c%#TmG~= zGK}D+u!J_d*_Cw%7ruUXCF%h|S@fxWEO;vi;0m&$6RWf+QGyf%H%7v7oc(;VWFkQIZ(|#!kiR8C1-^1CudQCH>#3v6z&!tS_}7=R6$FM z-&{P55cG`UYtoRqhT3$6)Pc&w7G?amw;^Awi7qk5sxz#R>w6gViaHN9=^tO*0^@&PjRcK4gyQx3rg5ephw{^TVQG(G7M1FF^ zVm)ND4DEFLssNNzgUI!l33Er(bOL{_PDx(B|9+^QJf;4%5I8jRi@zj|Km_)_t`cpD zLYi4|e^40>Kt~NgTwbL|D-Hq~MQc>Oz6lR^^tZ~b8N@jW5Sg&|1xW(`V2d){F;(_6 zns1WR?~SLLLR3RLfHHrIYJT_~X_DdxLS_>N!spe!!7Cfr6@s~?Ttk1p#va3e5$nc1 zA!r#JLN$Bc>eusl^vnE$eWx09b>a(ViBAs+LVXPt=K#5dju9DFDe7{HF0 z$EV!EF^5E$*iITMk(rttVOUM_hzC`YLNOD2FqFJMzq0CHAuTx6*%VQQ1bp-u91Jl} z;6UrmFoOIg{q*8=4>Wgzv^R_{<8h*smv0-KCD=_=UH=D1I?1`l29J2p-n+-xx1}si zdsNc0Ci=1=Szh8EzJ}S{LCJxZ&T31ni_=bw@I zAaGZU960MH2LwoaP=<@Hvt-kO)0r1YIP|3Iba1p6h)U4|GtgOk83YzpsU&tyAJR~I z0jN~cd+~VbozSnDzA}nh;m(C1X{jVtajvYoHsG|fAjZ%5x{YH##pI?tRs~z^)85u` zp?4dcDX~riHjo*>sdJV1au&|!IjkSwPawLIQzT-St)3!l{a__d;D|;@=c@NK^TwZwtD5HRu~6kQkFs9sK?acA&k9Z5_*yDf8E%0>VIQW{g zrUl9P`ASA0wJk%o^R=*w{sY{_l;zw+ieviyHp`rmDe0>NmB8-8mJClJ2acf?1P7Kw z3#`(P+}bXJi>cf^>;@-ZxU#(hT|*0QRxPG3f8j}jgGOuw?Ql?BGAM26B z%xyzRf^l>2vBRp*Zbg+n-T8# zO;Q=;_vz?QF%MbFw_m;9Yl=YTHeUawg*g9q4!^cT- z+v_l-lib<^(}?}LT}C>6qDGJP3@~yw`0vZR10C|0;+-d|wuXab%1s}Nf8hIDQ{@uy zPl)sWR0L5{SQ;DUk=T6j$)kFC(I11|c%8v4VN&ap6bMDq;O~I>KCN5rR8rdmJhSAMj?j*-%NFXNZQwLGG$Cz3epc= z(NZuCInPmI+MySuoI)ycB@Bz&PJDOFljpF8{`n%V_vEe+`XmoORV*;vf`bjic zcwu^tYKqV9H8)S$qD$jEP-K<$)HcVOP_TC*m8>|vW1pOmv@F{b zgQNdILuDD(@<~(K%gSMKS)&mkoZRpNLw9ykoZNkqA-x@gl-zaA-#1fL2_k;bvUBpJ zGEuPIAX{3QsLgfbs&@#bS)w-(dfn3CrD)D*%Q5DrvxJ(*j;5!Ww;AZMQgQbi%`%sZ zN~0d zH;dJB>tSIONy=RdPy-?{lQ{K4X{PYsEJXYPv1ocnQ;Ha;lyE8-L#Fg}8=@RvcOsHd z?TWeD)>J`feC9XJFOT6THQ~#fHAcmA@f$9pEqpNWYJQcb5T?*40k66^A@t6KKIXb2 z9W;8tB_RXdC=Do)wx0Ztlw{x*LMUyJ(SlJ%86)T=A2_{$BD3I-WN> zhOBn*s(y%6!^oJ4s$96#lOH^gop#ii?(@!|5V#v&_ztF0g7n~+er(#8fUo&%MYTGY z(HM(2|9bDJO4=b}rK^eaYh8O+Pu2?NX&}QLP!6tI%7j82#up_i0`0C6?JGYlAi467 zdawBera1j|d-r-$-^M(v&JVMZNM(cCzl9L1p_g3xl=?DHBv^-gBp4o#eGtln@G82o z`U}lpB)xu6#qY2`*?eeU@QClwzD?F1*y17dBdll?G5&aiV5|VEx0R)@W_NMYFU*wY z9U&27LpJ4E+1@jAgN~&Kx}dARSGeo)NTQs)+^Y3hWw0gL7$`z*_<;a1rA_ZbsHUHp ztL6m6Dg`ap3TZ_hs8#dcqZ|t*F(-KRDv4f_eZ*&yS%JXKeqB66D%`Ln()}MT!23{# z-q6=>^g-IHAoK_DkCsUSQHwfj0g}B1m!cGhs>##bUFzlc{NmqQiMJk5aEE9fP=5HA zESW}t+a?vSpsi!Y*6#~4V3)mCW7LleX$A1?=QkbdS0})AZ#CfxcEbK)>j&37(PYLR z{~7NslfMsM*qcTsR_MhG!HA!VS7mm1VICx7II0j#be!@?;MM)+B%{UgC_5UL^d5dI z#leX6ndT&|MHM-4+L&oKW;WU$FiUEDl=yAsf+e z^i7UQKQPMuAVj%|W3F&(!RsCfFV>xox(GBOcxgxk3dy@tfcNdnj@yb-RRpO8L2I5G z%p24y+6v=-Nevnu1wpz9|D_8j%p@lyA4J#^gV6}Fp0d*Ij;ZMeJ+0mmk-UHlD{tmU zjYUd4j@ox&q7%SRy;j*}{0ZtLJwoHilt;Do!|^VJB5WX53>6q%b_Tf9Ob>DavbmHB={G;-L1jW7`X`S|GATa%K5(luqUy=q54jefHD1f(3qB8)F>gksRrF^s&6b0}8- z{9V9$tRO}1$!8=p7|eCHYGIj${*{+gg{aFbeWYlzZLGmautAF5Duus-^C4J%3;nV) zeM8BM+t`T9L)Zwd!Dp=JM)W`)Ve(z0BqF{4R^V3Vi? zrl^aovI*OmmDtHiVD2%P{RB-4d#;=?B2mvmuVxL5#iT)|#-q;W-Bl4Q_N0CGGX5M_ z+8mfK9DOoY``Y=25^_d%uXefA(RdBG;vu~BJT(2{(BIC2D?{TNN=K5JE}AOTa^#^-!cjB50bfj%a?g5Vs$y+EN<0?u^67`ceD&RZFCTO5-K z3^#=dP4d$N>5i?5*D7#UH(3>V zylRMS_5+R;3Ucs=&Eu@F!zehJYQvLsg;0LDgA+k4Z>TtSzhaFn8Wro(L3aF>Zd|bX zRI#!oJi*N|aBsTbz10IMAw7*Z)#*~zf~eyjuoefxD@iuGg}3avpJ?fJ{?mD1ZDb@v zHq6duI4B^rjz2&N)}J!W=GbNeI;~`-6joLa5v%v~&|^4ybNa!$Z}x?@>igBBgpY7n z@f=KaPxE^~DdN<*pFzVjgF;B9Yw@kh0lU8uhTp<5Ye*lp@zk5d2AAA?TcK8jNdy^) zKBPGbR!Mo|T~H9pPw&fE5iWO;VNGzswL_P_TTT}YC-Y<=xzKGmnA>K`vA|(JuI2>f zoYyvrv)R{w!XFM;WzTJ*g^cx70bznlfXGW(Ucyc_YWM{$XGEIVUGQnAs zEi&6C<)Ov6X(E;>2(%>q0-{}HkDOf4FPL;i4&=BS$Km%q1SutwQk}!p#H;)^)NwPY zu1Sw8_MS;@wXbASf=ekmVl-sbeEBt zd52~8jGARymv!sp28lAH35yL1Ma`&TpnKA!2}P|{GIvD4Ng-c7{<_d%O~rBiE;x{n z?1i~isV!ruQjFmNz+hVMk1+%n1K(%^H(tpcest4+gF4@C*P@4Gn8>tCK2pE6a?nF% z*U^kltP`>Qb4EH!Tcp0CyARo(fK~0QbON;cjJBlYyWI&hPb1X5rG5I9$I@nF?hbD6 zj9!#Q6=S(2^HrM{fNG#yhMUxy&HEDLw2W4CTvLYdDlMQ*7iddnfx6n)F-{%?@ha z@gEaov?4}Ph42F<#1n(MZQm;i-3!?e;~uz^4!*X=Vstx-q3Yh_YOVlJ_ zi?DQm-ivUXiqv#4H3eGHUqGZ2tc?~x_X%~kYCNFKgC5MWaL~~a*<+-zI68?th%y|U z`V+jLvnYSorqh6zdsdXgvJgAzhCLznQ&o{Zx3`7#x!6K10vd$SBs=baSJc`5v^ls? z(;Z(s;bwB+tu3D*k#uaKOwha0Xn)f^$o1u(yLksFX2q6-WLoo?b>) zrCV(sS;%xA-wJx8Zudxa<;Ag=J9Qt#6}>k6HFV@kv}>t+t#ro_&Mv06=y@7->kN_ zPHW^Hfenb}M9bjMwiCR(XQO;-fP}!dOE4wb-jD<*zWyL@bUSP@J)r@N4cnFJ{sGzP z8+7s~PIp1dLijf-b zFX8`_?B8IGo0AOxW_U^X3qGve@sV0%=uf$pfMaP)wx7(?!l<7&-FMI!KEYJ{bxNHJH2P1-shY>T}8Ng7X4z zzaQcAn{eIe#O5x}I@)ph!^!uAS;uX_dmqYPkxMi=rF?I-(e`cl)(1!}n0<(*UcTCG zf58Qe-cU!0ecDq|Z>DyypRjVPU`9j#{@#^Q*c62#(xP@>7xZn$gUbfu= znsjN>dsjN42`C`Fqk{BKC`v~}I?@Gcp(D}-EGSLsEucc^MS;+(fP`M8GbeoSyx+{4 zwPyYr7Ha`FH@W2I-gEYO_HREMUo0%N!Zla0G&s+#W&4jW$lR+%Inl1!J<6}hI5&93 zz1OW7VDdd~MChTch`Q=7N7x+?coR9(Sf=%HlFDc7CVT}gym1o=#-4F*7Z8!p;LMrg z`I13AUK)mm(2s)z0}Et~uPp$=^_VdJ4epCS8a{3&cjSjD>%T{bB<~QrTn91@5KUAQ zDoJQwO@1NIqrw*1DE>lz`Zl1LO%SLa?axfr#UH68)7Z{Foh_S#fau#uD)ZzC(}TQMW{(e~4ff zzc=WSFW{JPjoAMb*>!Sg3a#3Wur`{5KXS1}&!>&S;d|~ma##(EUu93(iN_aGwJ+Hnt6?>nuZfBn0K!g1~`|5 zydW*+BqrNwwYq=w@sau=%*h#d8#W>d-7RqN=^d?5RmTj4Sb=q6)6zR5o<23?hLxdB z5{jwl2NkN2&IU$gm-QGChULS_0hzt{3d*#3D~z118WTJ4>|UvIv4dc}@Po%o4f!dX z!&Nj5%Ej>Yu(!&^FhK@xzGCwX?f?=cLMnlb+j(VT%f(2U`hnTDpn1JHZN8LeC6e3t zb3RqNS^0?#86|Y3f+2Qva^G=#xX;2%^8etcwdXn2v$8S?2yk&$|0x%DIQRXs zYymDGXieXu0de+?cP4yv)wQG53w44%sc~obzXxAHF0>1_$MSMHILZ2n7$VPHY)R>l z&4r!gadN^mtht%dZYuVrC|&N#>!TG))5($sO5XE&RaRhl0P}|yZmo+yowb@IM1kb2 zqR%mVT!}8pTfjeojJoXu-HVSzD4Rw}gXBH+3y_ICx8N!qu<~*^78W=qO%43WJ@)E- zrj0u0&7b%4dB%d7L$xSxZ>6bwifxe=VXOIZYO09dZ7 zmf*`EbGzAn8dzTJqVxkj`jMJppKiUgxrsscEdPZsL4uC9>t7fX4_Y@0xE;139{wcy zsDcO(1s<9Clr*Ysw|IN3X`#)CCb-wyYGZsq-5-22?OHRSG(luN`Jof3-gBr?;&UuA z+3C#RYfgG;i_va}&P}a92mc)Hut6|`J{XPuV2%;;*ZGwS7^H*!n%*`T80SY0&;k9U z1AF-7<&3^rHn5 zGF(_z@rrSMuDxG=1?_|JLg`6v-5l!p6O`&WiUG1p3E9< zMqDlcS-^It=i+=+rn0-gc^j3P7WfHk$*^L0ZQr@P88oi?H9YKfyqtj_-dY&o>gvj? zmpfHqqvYzkp>nx1Yzd81i3HrF$gNc>3I-ciRy!pi?8$T`7>|S}o4im0Yb!Z(6;Lli zE8vTMXD1(ec)M{2Q8GLNTWV1B2~mRd*%)SbJGp<%5V$T#SkXhrABMKX-XWK5FjB?M zXbTPFhQW-mPMco~SZ5!=IKr5o@bo&Yyz0P$LMd;&^$5c{bQPm&iY5FV_Zvr=s%)fG zuXQVEOV(o!1Fx*Eu6`Y}JRMXb`}*YzBr_+c8JPUA1nq;iYOl3<Vzt!YV;O&6DjhF(>++7ZYc;&K4Y?MK;|9+Y;&3b2gVW=F(#^c? z2un{o+x{UY^gi*8M&5rNhJp3@Xr*}}{2hSTx znbO;@JkCaM6VMY{k-0O4eNktZ>FT2Pej8nZdh*bV$2jk7iC0ZcO)p30YvA#2ky_%6 z|N1A^lfh`8tr^leKr{vY&T>55oNn~n`|TdIZU!)|F^Mtrr@%1DYFDczwAH3q4flC3 zB&d^wta`?9hBvcJe@ZLBjLbsoi8W^c1Mc^?pZ$Hf?IzJ z(tB}PB$-w7EG&Hgeyv}2>uflq<;ym( zc%SU<>0$L-f_uvZ5ehf&z|m9ndy{|VrWlM*M=x)7hzKcjUV)oVl$nY()PU-2jg=+K z^Z3<;Av!0DlU(ucPjv)i7vDwyEVM-~+^=2uY_qX}8k66zpGDO_<-N4>N=Z>?F-%8CltYfB@mZ$Dqd&pubpOXayie;-r3L6g$pDu<_rmwgVcg-%rhf zvadJOawT9lORNp=<>ZDZINtWnhM>eCdM`mWH2jwB>XYvEUqzCx%OnY>Y0=pp4 zLdt^R_T>``i|Orc55BZN>+6jX8SS)X#V0cus0Lh??VN;zRO=)iW_3DjfTi@i848Gr zrGN0y970N0(jiVN7H5ToBhoUv%=_LUS>?Ky`hs z=5&2r0ix0H;JcGULL6GBI@;eqcUp?7Erv^$`rznBBirInW2z9y`O5f8<-Q2ehp$(! z-z*B_hYZ>L8L~gD$QRznuD?-MQ&R&hjGSj%f-sIo{W-F<6MmhGo)9Xk)+UL>_;@_* z(Ad~mK<>29)&Y*^t8<8b!!J0en%5ESajrtrdCRrJu-%Y~2+Ow?{R!o3n#J(UX53`Y zL%6txd$1@Yt#}PG9G=NcAfdW&k~9}u$I+$Rm8j{{x45&cem1@hh_auV);M&R@!ue^ zQRCdt@m9v044jmc6x!(+-(=Y`K1$`0J)X5nrkI+#gSdYvFvEW;42BmR{Oc~0H5(1< zpJhRlwkrEp?IkckVj15&791QK%TVXyfOi!)r+&og#}8bSf;c#R_gn``v@Xk0{Gv8a z`EAP9f()Lp#2ZICRrJp`f9WWrg*1q@)Jz<>kUj-GeCzkkdk%jLoUHxPHwpC$18dA&;IfoLYSq>bEY(&(MrQv`_LBvS#^3($ zb9h2uRp`|-U?e|^n3vDERX*dckAqGr7Z;#$U18oV7#~$sR6q|wJvFt?c&QmWqR24B z+*r%u*XPaa7Q|R!Y~T6e#PtK$MD!ik5AiBn2=3yG=X15N4PrvR*2W^B#Dqe9^rl?! z7_(xDuS;ZEySm6A5>R4cema>tgHdVLBZ?B9l9RO`9(rJX7TjRxoHaU!(xVzolU&{t zSH|kISU(bAvW~7XFtosm1VAH2huHbr)jHI>V$~YV@S1BKIV*57p zGW>z$SfS|kd(az?H^I>+NkW)Q z)}{oGx-9SAiY|v$OonQ50Dup@hG)igPH1Q`foKwS0~|Yw$BIu*8C60p^iR@y6_zc2 zX+d*bh;j8M+(+29)*gy`if3$Yfkfe;%&(P_<)*Qp(-Ph z%QMzh^}iWIJQhWpNN^3&)9ymFak9z|trGUhjB_bZ=K}WMcD_Z}9d>#9ucC0JLl^FS zK%vnOcKATbv}2nQsP(G`UskBK31q}R;?@h@&Nca-P43M%(L{(i=>0GsH%G7ZaLA$6 z80@GHQ|BDR8x#9@WKO&4ZG<3iJRKCCH9298`%WnD&~YFjPSzK7IG9Na3||=O>$^H# z>T)A_UXz})Dgg{2O_WjY4JoNcjHVqC!;31eMiYIPp#eqC%L^C7JIHC=o_um0yl1k+ zI$s!ze+Y=*!+u|v8Eh02XIR-WiTXx#0nzZ46`?XvgY zEBM-Bh}l;iT=t2S<$x5U6b2wPn^A9faLH3mOluR8g8xU!&DK{{$k3(8`#a6clAbv zz{)*XDcz-rTCKzVH)YtS?TCa6PV8em%wS|Ifo^^l|NN{(luM+E$iHz}{YCtnQhg_u zT}JgAY70^>42>QNA_Tj!`oi0clBhNI)_XHI6U=~W*UGL{qcb>g5(ty}h>cB!dn7$$ zKci8g!^CUv>+M-O=-V2hEb7i@%ip>sNU_fv!!=F{yGrcT=@#q+6I$B> zk=^(E{(YkOzgmN_^qJJ!_skS3{tSzdbbxQ`3ZT9G0A_Bf5UxmGkW}-o!e+RIwF<-A z&HwYwax732B4dI@_9(|;?E}?|dQNKC(i)azRmh9J`Hf_H^a%M!Z)H(Q<0Htw_s z-dqrbgT@SEV{O0zkDm$z&9)TY>|UnN!rpFwGs?le`_h~fHG(fZoHzQ)sbDrOo+JW~ zmaUZ;mY+P6EQwIXRv)dTI_F&jZcP@K}>&rmyi~fM(FeVD9YZ&krQG>{X7|- zL0$1IBZ~YqiK@uL{2Q+8_}#~d-SNezXdDz`4N!xjqBROWUiSwlAD1LHUn>ei#}{XA zvRh2>`ZC2HR^G)^47Y>9d?Ml_oW#DRy8jgvM?h&Lnig*V&3;@Oj1#kP0g3scuGzM)m~0v|E&Sz61qQc=SA`Q3 zAfbjes1&d$=51zdshq#aaWzaoE>yu)a7vn{Fz8TG=4k)hAkipl9rm?r-d$)-^q!B5 z!t8X|K65oJ=~1Oq-kzTdIyR!?8Mf={b7uIJ>JxhB;+N^iOkNxf0+iCplIX;bZBW?H z0}G_4oUA!T>EGAr$M5CZplb$4#d?)EcgXat?x_mx!s+}f^>sEUosX+(X>haej+ni? z#KyoLPO#tD`+eLuAA1@5CLGj+-5Z^Z#rG0+g_Jmtf0QukIQ)Fan3IlR^2s>w^w@`S zQCYI?_hp1J-S1<_PZ5T=ChpE8m9oEHQmU`VI=Syr~#S&fMV*_Uz^ z9V$mzs7Yr!1UNh^8K0rzb-+u>Ptd7G@cuxpG<|-Gp2C43ZE8+cVJ{_JPW&pIUT&F0 z6|(=4O_F0jG6Y3m;F7l#hTuAN5d2`tB!NI!GBuR%8Ke)BEiaeZ?qD!#r&T>n8uTZrXk8a+E%!o|=0gtc`5nudWOf!*O?q&R!(5CEQ1DN(+MLx=Xl_{vs zZr{67LsBYFhp3?eCiat;yt|5fcaMOEh4DY+9Z5QX&;qZia0g+hk;Fz5i3gtGn&L6k z+?C0b3*`zggv*7lW0U5|-DA1o#KK(vI>;;%^JnODB5?@O8j0pAldK7!cBvjjAP07u zxJ`cp8k9^%p!>m<2Dc4Y3+EyXIlOMC!hBzZZ8Q3?T z{zBhayxi~q!lLeQ=mc7KM&T_JS~8h&q7{zZki$&|D){u$4_3TP$pX!u+m(;5iZpsd zWS&WpI^*8af4(PmrByt6K%s``9HGoKZORE-`!Qw4>5UNs3&dE>1*f|b%CE8^PmDRu zOBwx~Bi6k+-Q$y97DY)YYA{ikQ8e9lz&*YIwcN%`v=?7g7p4ZXqt@H=hSi%Micqq0 za^4kpXZTDUawZaYhc1S7!%R=LAxDZdr#;U~Z9yT~qF^eOVo5fLFr?nUBr}uxmpaf} zSvn)-bIwR-rF4GU=-kiSzRrsaf8;H|y2kCoz;UTSS&*kXEHgr`lu=2>^-Ly+!<(pE zlGO9T-uZKK^SbMA{U`|2Pa@3+^oTxi|KIZl9dT;Iu4KgsnRI(+pbd&wl zv@3z_ZeO3|Op)4EQpFLyWjF4b!Cx``fb1Hg4#T0d(vALXoflK?yvfDs{@y?S;-F3l zlopv&(zrbDdn)-|PP!?{g@-dFaFYnSD?cgB$%%Eg@;)90qo2ejqAmphZ`gI8SIye; z{0Uia=OBTxvwYtD^%1(8phmMW%7}@yReEK{Js4!T%5VG4Nx3X_q@a-t=hxbZ9n-4{ zO2=(!wxs%?tv06%e8=&QG;sPTa)382`t(L|2^NStDDeuy@$G7Q-r#!wK!b&ZbO>a+ zbiBN$_B<O2EB;Ai#`leUBUbcnZhbA|lbC zyo}V- zO1w&Ut(EFk?)8rVOSM<3&n1A@rfrzv}M@ zPe*H}b-i*&1RETKEnOv)U+`07NFz3Qf$@F&hU6%cWq>aK<-v_1d2}oqnp&bmz@rou za#f??sFA2)`ep~g?2`Xh;U>XnACOq$8OOTF0@dvyWt*zdVI{D1H0;fe1;xL%gbnAo zX8BY{+~)ixrd9D97Hb6z#oUu5cX9EsF~OV&NjL%u6mtVF-$EBFe8#TQ+e<&q? zRW`qze*fDSbq{bd)t)sQr=LGaz?Ld0HQXc>++T=w@cbZ(Ub|sxT180L8d<$uOahh; z{M*Cisp?7}Vm>9teXXhZsb+tXWR7FibIt50E+iBAtu6Tv6FroPVgO6^K!*3_O5wZvDtm>4cQcDjaP#o z6o}s2>ji*gTeKr_SJxUlLVe2f^@hp8FWkxMKa3JmN$|d%2jJws_8P?-ORHFDX-JxW zMA&v}3#!S`zY%dm%D~S- zp=9fG9*qG(Z64g7HBO1Lc!^HbMX}KcCN4E$$YtaFs6+v!R1lr!4vnUzfGF+91U*aF zso4=~$6$uaN|*?>@6AgAa(Y`qc@e0tszWDAi%pEvm!-$+lMFV>^5QN=IsXqn*w677 zEP8k!ifO3s*3-xhVUJL}(~!+J z+W=cB>s{%e>b~&LDT}+Rj1$zT(FlE7meFkU5~R%7U)>pN&nBN*F~#kF3wNH^A^7}M zunC;*vE(Y{S2lE%m~l1FYrwl|nEWj#mcd;uwc#zK*c=Un_ z_()Jx66Ij%UIEsH9uk_wcaxh}Qx}Qy_$XNc5yCAAWqLTGDOn{b$nzTa<3^Z{%aRLK z<9@TpqBL~3JEASS{GU3r*^D0|0j5|t@W$uBgY^GMd=Ns=41OAQ;&>B8mi z%J5j4Vt+OwPi=hE5TZz(jZ|MjN|JUi7+sC=b_--#bVw`jGp??7GWLU z+Kb!hp8q%nLojc%`c=>uk_ANhz1wJs_BjYWAQ3xz1ybqTgSrwP)6?C4C83?ix)Q=~ z-p`RiO2@ZeSYOVSuCnhj7NN7T&(Eu2OE~+b-GA4%8C*m^;?;aiv>_m9kc0eZ+H1AJ zmbF|-xMe?YoupUyu+m+Y5;FG+7X}N#&ygNYCgk!sgvlD} zC7D;AIUX8py}>x)#psvR?=#u_@1X?YnL})l{+L8}>T&!SxHrrx1#F)hBTh`AP!yl> zfP7V~#gKeU@~41DWiUtM*2Wz+dO~Y8>B--oL0FA*pYPl>-jqtWnLQk2I<3)5u+UHq z{uo3IcG-t<)0_AtZ{QjGDOMb7e9775nwcC>?I)0Y25-|;GACct)AqpRnT3WhmBg6B zVX-(n*iyo-8UStosUn`!FQ2`q`s8W}TtK^%wFpYRtzXo_$3;j$TwmQ)4NZDHQrO>p zXS(@yT!g(n2;=TV0^WMQGfq1IpPs&i6N}4d2!58WIi3h%uD4tvZ@R~H(c8T}KsP^1 z10Bv44Lpv+LfS>$wWFJ9KjaX<()GGTK`C0?cP%ynV|v=SA%3AnmlB(n1PeDwvs-m0 z_N(&zw>uro)cVu5@wXMRCSP!Bu&$%7a>(s&V8RzWub&|Y%P=WrypK{BcMv|^x2qHG zny5Jd{3L@H@6*HUB*N^RjVGf?@B&Mp=%@vj!vXyJ6CJr_whem9Z}kTGxMh1C0>!8T z^9T?hRxktoKZu&Tx$@xZ*Lz7Fxo%y_@U;^YXw~~oCi?nYAPJ0Hp@C3&-GR;n0O#gK zo0zVY_w5wQxNq{WZHq&ngB%CM`=HV+`N=<&+aI|ao$#v>*JTY8ez8_{fwCFM>btgU zrg$Lly62u>gw4PLVWysYN}2SoO-gnaL>~Wz(k$@`slskd#2VnUqN#gTNXD5!1P9Ju z+#BFkMpOHkU5*#mx?Afpd(HA+ov<(DW-MWy!-{Au^A4 zyl@qIL}%?T@Exm$nBsCpX&14}%b!iD{f>a!#4~P?)(~;KvDMPoVA%9=+;pv|(kyOo z774&NU%gR?V1_DDmx*C@jaE&hy1>3ZE{yqAg5-gcLmaZlv04h?eb%nJ-pTN>PTgcU zWR$jI;wPPh9{-j$#Gwl9_$$YjldP|jQGYAPSVI-t*VE$`J!+&c*yz7S-L@876MZGG zvCq-ZsTKTFXWD!%2UA8cw+AR`2UXnHiDojcCotK}CJPS`5J^+|(%&^iu(S_NFrQ&% z#(`tXQG_JKxtKdbpFq29p-({U0&NQAp5zMd5MmzJJSF9dO~FS>N*XUnh4sbye&0!& z{X%f&kSPzb^z_9{igbRUB9v_CUj;T)V5}-H?&lRxndcO$`*}I!`pT6W+w@W)+4)w{ zQ^Do)GSWQ`Yykesn)HeU#)IcSxOJwX8NtGLwlf$)m5`s7u*~_0fQG=I6?ZM|ZI-_z zO6+T@MW;-?xUwoXfsj4zRODyt^WVF(H|R#=snHGtV>Hi_;63f2h?j}{&FGpyW745_ zEOlvygXs~DuTSbU`Z$f{P?R5q3@+D{#dD_ZmO<&|zfG}Fqep`~mEND4_eC#LfGO!+ zAc}ByZex}@-evaYwt^{NzA9%_74=Avmc0avp+pnn(_Av03V*PQx??Y>gYCS=#+g=xvnKiA8Hsu{!KTbV{&JCZf3XJ-#VYR@R0h^NeAq|G5) z{Gm>g@8mMv_T(-~Ueo)X@S`tD@Bzue_dH1C9|~W_>C4Dqy&-OL!!Aja4@gY;rO=T2 z;dU0wDp!Cw^lp*{08bujFdV@WRfC9a4+9JdK;fHif;U?V!Y-E*A z6afe&xcQJQ8m8}F=F}%}fP=EkC>|Ye&+NHMV{QA& zDBGo8J3*9_^@!!egWU-tkgO6ITL$8hxoyfb@oS2}+XWdOdcVSTfq(y62vXOdU!g%q zx#MZr=fB)JF5A|^&i%8DZu4Yw_xzcQ^(X}FTrg$(4uWbRN3THg?*fneI}ra~7kI>|QD+m~cauQMn( zs8&@b+W6vw^TBvDjPXR--uwZ|Kdel9LRhC1RonGPA8MIt75*isfB#k>EQp`919Mkk z%R9g(WRGe|M-VX<=m4H0D`6Y?@rFqR$EeB~f00X;Z>K41_su-{@KTroXQacim=YOa z1&^ToI7s80bOh&_X*=r*#+QpM(5geMw9urCp>yMb)=$DQi?^BA=(GyxXWHGvehm$Y zh9M5o>(U`B3cvsjyBOWOW56v-IbcAV1cx{%p-E;}31poF<$uV@VjFwDW|qKV_fdm8 zVxvoB(&@_Rh$rRe78Zwl|MnRXy@`0QZgd_4Mc3*QS0e|zth-Wy z4PA>ZZa6bDbK&KJ6r$nJhmVjiU%u4IkuMtg0?10Z^=x;+79l>brCK3$-RZ&0CX@Gi z9g~VYDX4140omDO-7$RICTTl6yHdS8%epmszkcul_{tEr+B{K+lUi9FDth$j(buo7 zUi@3Dt4Ws5p)Q6MM<@}YcnRmxK+w|4syfPb(zVXk-@gGgC-XN=LP5Scfm_eU>>mX)qQ|ZaZ!*GbdqwL{&R9OP6}){Ffec# zXB)4a^iW|VcY5-~w9+;b{0}!r%S@gQ@wIEU8B#D@9c+n?|KH!hg>*IdF@YS`|8e+F z9O7SYdoCy&85sc>M@8P<**RdXI0JAqx0?BD3e;qBNJ#-^13R?sI0LP{ zOf4oRR^D>*2N8~s^=)zImF3rzv^*xT>;Bq^R=Pm_cC=5`ZZv4~hCm>Y#9c6GWMt$D z&de~fn<45trqNm6#af=_Q3?#|H|bi7^-{G60=l3L)X7NNg&rOrMygzZ)`O7G&4&|! zQghtUUS<_;IkJ8F^y$qTrnmKLXM;~c6U25caFF!e++1Z2&y}3#xtB3HWr2$Y(JT$> zv7qPI=g5qi6C@N^ZuWu(Zh$}9Y&ms<&=QVXT`XJuu&o3RUcl)nz_V-lA7{R8tpg8g(G2x<(5GsI#Tg*ajQx1lF}U5#<;7nzQp}g2Sq-{9?>T5jc6jJE+j4%U2e6rz=UX$i=aBeI z+rYpxPS)eDNJrR{CqxQrYSG}p95k;mK3Kr?$mGb8CxxDg$;H(*(!%h#o4>rtYpeNh z@ZZK+!CSYaFO_6X{JM}Syhfi)4}R+FU3D-7?bk%b#HzRV-n=RE5K8@myg`}Jgr zIp&6394~ezMk^LXdKcxC-C5df+_J2KA@2gRW@umow#21;k4u|!&_v+wq zu@<=a1)ooOc4GY0#2ou?(9i^P)`z@n>d0sE-p%(KvzmKJ3WiWuc7=AX7JZJ{_u zRzU~t7QeSHHn(UPn#Z%crLHsG-tKb`-aC`m($thYLXd4v)(U57PawOcfa8@I=qETl zOnE)JV}AP;xIw44f~AvW`MyI*Nl9Az>g_6nGhSui1Nby>%{GD`4bMJy zD48z7pjon;b#~yx)RQ}2isYvRg9~F$P7bE&B(NjwzkFL-TKe`a7ehSem~t08iI=Nt z^8RPE0{dgW;N!1#LD_=LoqT+Ja>qzLp(b#y^H5EGdyzpEXxK6}66S?W=4O;~3p_+Qc6N4R zm`%{4u=VIwtFiCFr#x`pm0n$1U`Sz1e?n^!x)^05Vq#%*8?5O(aFKEL=qs5;b91v# z$!>h!#eS()hLFYWd%#T$G~ol+sJSmv*HG>$9CK4k9d6CGf-5(YpP!E8Vu$t86Pji2 zyY?xsOhGa`t+O&{8R%K<<)*&nZ@$ z2h%zhT#Fc+v+-J&&|0sm|ISZ3UQ$<6RhK9bxUQxu=^tRphuOx7iTVDSZvL;6%)fIS gs8{*_@nV -New Duration/Shift-dependent Component · UnfoldSim.jl

New component: Duration + Shift

We want a new component that changes it's duration and shift depending on a column in the event-design. This is somewhat already implemented in the HRF + Pupil bases

begin
-	using UnfoldSim
-    using Unfold
-	using Random
-	using DSP
-    using CairoMakie
-end
-
-
-sfreq = 100
+New Duration/Shift-dependent Component · UnfoldSim.jl

New component: Duration + Shift

We want a new component that changes its duration and shift depending on a column in the event-design. This is somewhat already implemented in the HRF + Pupil bases

using UnfoldSim
+using Unfold
+using Random
+using DSP
+using CairoMakie
 
-# Design
100

Let's genrate a design with two columns, shift + duration

design = UnfoldSim.SingleSubjectDesign(;conditions= Dict(
+sfreq = 100;

Design

Let's generate a design with two columns, shift + duration

design = UnfoldSim.SingleSubjectDesign(;conditions= Dict(
             :shift => rand(100).*sfreq/5,
             :duration=>20 .+rand(100).*sfreq/5))
SingleSubjectDesign
   conditions: Dict{Symbol, Vector{Float64}}
@@ -18,7 +13,7 @@
 

We also need a new AbstractComponent

struct TimeVaryingComponent <: AbstractComponent
     basisfunction
     maxlength
-end

we have to define the length of a component

Base.length(c::TimeVaryingComponent) = length(c.maxlength)

While we could have put the TimeVaryingComponent.basisfunction directly into the simulate function, I thought this is a bit more modular

function UnfoldSim.simulate(rng,c::TimeVaryingComponent,design::AbstractDesign)
+end

We have to define the length of a component

Base.length(c::TimeVaryingComponent) = length(c.maxlength)

While we could have put the TimeVaryingComponent.basisfunction directly into the simulate function, I thought this is a bit more modular

function UnfoldSim.simulate(rng,c::TimeVaryingComponent,design::AbstractDesign)
     evts = generate(design)
     return c.basisfunction(evts,c.maxlength)
 end

finally, the actual function that does the shifting + duration

function basis_shiftduration(evts,maxlength)
@@ -39,4 +34,4 @@
 
 
 erp = UnfoldSim.simulate(MersenneTwister(1),TimeVaryingComponent(basis_shiftduration,50),design)
-heatmap(hcat(erp...))
Example block output

This page was generated using Literate.jl.

+heatmap(hcat(erp...))
Example block output

This page was generated using Literate.jl.

diff --git a/dev/generated/HowTo/newDesign/index.html b/dev/generated/HowTo/newDesign/index.html index 2bbef0b..a00b436 100644 --- a/dev/generated/HowTo/newDesign/index.html +++ b/dev/generated/HowTo/newDesign/index.html @@ -2,10 +2,10 @@ New Experimental Design · UnfoldSim.jl
using UnfoldSim
 using StableRNGs
 using DataFrames
-using Parameters

Define a new Design

A design specifies how much data is generated, and how the event-table(s) should be generated. Already implemented examples are MultiSubjectDesign and SingleSubjectdesign

We need 3 things for a new design: a struct<:AbstractDesign, a size and a generate function

1) type

We need a ImbalanceSubjectDesign struct. You are free to implement it as you wish, as long as the other two functions are implemented

@with_kw struct ImbalanceSubjectDesign <: UnfoldSim.AbstractDesign
+using Parameters

Define a new Design

A design specifies how much data is generated, and how the event-table(s) should be generated. Already implemented examples are MultiSubjectDesign and SingleSubjectDesign

We need 3 things for a new design: a struct<:AbstractDesign, a size and a generate function

1) type

We need a ImbalanceSubjectDesign struct. You are free to implement it as you wish, as long as the other two functions are implemented

@with_kw struct ImbalanceSubjectDesign <: UnfoldSim.AbstractDesign
     nTrials::Int
     balance::Float64 = 0.5 # default balanced
-end;

2) size

we need a size(design::ImbalanceSubjectDesign) function to tell how many events we will have. This is used at different places, e.g. in the Default onset implementation

# note the trailling , to make it a Tuple
+end;

2) size

we need a size(design::ImbalanceSubjectDesign) function to tell how many events we will have. This is used at different places, e.g. in the Default onset implementation

# note the trailing , to make it a Tuple
 size(design::ImbalanceSubjectDesign) = (design.nTrials,);

3) generate

We need a type generate(design::ImbalanceSubjectDesign) function. This function should return the actual table as a DataFrame

function generate(design::ImbalanceSubjectDesign)
     nA = Int(round.(design.nTrials .* design.balance))
     nB = Int(round.(design.nTrials .* (1-design.balance)))
@@ -13,4 +13,4 @@
     levels = vcat(repeat(["levelA"],nA),repeat(["levelB"],nB))
     return DataFrame(Dict(:condition=>levels))
 end;

Finally, we can test the function and see whether it returns a Design-DataFrame as we requested

design = ImbalanceSubjectDesign(;nTrials=6,balance=0.2)
-generate(design)
6×1 DataFrame
Rowcondition
String
1levelA
2levelB
3levelB
4levelB
5levelB
6levelB
Important

it is the users task to ensure that each run is reproducible. So if you have a random process (e.g. shuffling), be sure to safe a RNG object in your struct and use it in your generate function.


This page was generated using Literate.jl.

+generate(design)
6×1 DataFrame
Rowcondition
String
1levelA
2levelB
3levelB
4levelB
5levelB
6levelB
Important

It is the users task to ensure that each run is reproducible. So if you have a random process (e.g. shuffling), be sure to safe a RNG object in your struct and use it in your generate function.


This page was generated using Literate.jl.

diff --git a/dev/generated/HowTo/repeatTrials/index.html b/dev/generated/HowTo/repeatTrials/index.html index 876db26..a9caa02 100644 --- a/dev/generated/HowTo/repeatTrials/index.html +++ b/dev/generated/HowTo/repeatTrials/index.html @@ -1,10 +1,10 @@ -Repeating Trials within a Design · UnfoldSim.jl
using UnfoldSim

Repeating Design entries

Sometimes we want to repeat a design, that is, have multiple trials with identical values, but it is not always straight forward to implement For instance, there is no way to easily modify MultiSubjectDesign to have multiple identical subject/item combinations, without doing awkward repetitions of condition-levels or something.

If you struggle with this problem RepeatDesign is an easy tool for you

designOnce = MultiSubjectDesign(;
-        n_items=2,
-		n_subjects = 2,
-        subjects_between =Dict(:cond=>["levelA","levelB"]),
-		items_between =Dict(:cond=>["levelA","levelB"]),
-        );
+Repeating Trials within a Design · UnfoldSim.jl
using UnfoldSim

Repeating Design entries

Sometimes we want to repeat a design, that is, have multiple trials with identical values, but it is not always straight forward to implement. For instance, there is no way to easily modify MultiSubjectDesign to have multiple identical subject/item combinations, without doing awkward repetitions of condition-levels or something.

If you struggle with this problem RepeatDesign is an easy tool for you:

designOnce = MultiSubjectDesign(;
+    n_items=2,
+    n_subjects = 2,
+    subjects_between =Dict(:cond=>["levelA","levelB"]),
+    items_between =Dict(:cond=>["levelA","levelB"]),
+);
 
 design = RepeatDesign(designOnce,4);
-generate(design)
16×3 DataFrame
Rowsubjectconditem
StringStringString
1S1levelAI1
2S1levelBI2
3S1levelAI1
4S1levelBI2
5S1levelAI1
6S1levelBI2
7S1levelAI1
8S1levelBI2
9S2levelAI1
10S2levelBI2
11S2levelAI1
12S2levelBI2
13S2levelAI1
14S2levelBI2
15S2levelAI1
16S2levelBI2

As you can see, the design was simply repeated. As always, you can ignore the dv column, it is for internal consistency with MixedModelsSim.jl

Note

if you implemented your own AbstractDesign, you need to define the size function accordingly. E.g.: Base.size(design::RepeatDesign{SingleSubjectDesign}) = size(design.design).*design.repeat


This page was generated using Literate.jl.

+generate(design)
16×3 DataFrame
Rowsubjectconditem
StringStringString
1S1levelAI1
2S1levelBI2
3S1levelAI1
4S1levelBI2
5S1levelAI1
6S1levelBI2
7S1levelAI1
8S1levelBI2
9S2levelAI1
10S2levelBI2
11S2levelAI1
12S2levelBI2
13S2levelAI1
14S2levelBI2
15S2levelAI1
16S2levelBI2

As you can see, the design was simply repeated. As always, you can ignore the dv column, it is for internal consistency with MixedModelsSim.jl

Note

If you implemented your own AbstractDesign, you need to define the size function accordingly. E.g.: Base.size(design::RepeatDesign{SingleSubjectDesign}) = size(design.design).*design.repeat


This page was generated using Literate.jl.

diff --git a/dev/generated/reference/basistypes/index.html b/dev/generated/reference/basistypes/index.html index 15694f3..89373c3 100644 --- a/dev/generated/reference/basistypes/index.html +++ b/dev/generated/reference/basistypes/index.html @@ -1,9 +1,8 @@ -ComponentBasisTypes · UnfoldSim.jl
using UnfoldSim
+ComponentBasisTypes · UnfoldSim.jl
using UnfoldSim
 using CairoMakie
 using DSP
-using StableRNGs
-#

Basistypes

There are several bases types directly implemented. They can be easily used for the components.

Note

You can use any arbitrary shape defined by yourself! We often make use of hanning(50) from the DSP.jl package

EEG

by default, the EEG bases assume a sampling rate of 100, which can easily be changed by e.g. p100(;sfreq=300)

f = Figure()
+using StableRNGs

Basistypes

There are several basis types directly implemented. They can be easily used for the components.

Note

You can use any arbitrary shape defined by yourself! We often make use of hanning(50) from the DSP.jl package.

EEG

By default, the EEG bases assume a sampling rate of 100, which can easily be changed by e.g. p100(;sfreq=300)

f = Figure()
 ax = f[1,1] = Axis(f)
 for b in [p100,n170,p300,n400]
     lines!(ax,b(),label=string(b))
@@ -32,9 +31,7 @@
 
     axislegend(string(cfg[1]);merge=true,)
 end
-f
-
-# pupil
Example block output

we use the simplified PuRF from Hoeks & Levelt, 1993. Note that https://www.science.org/doi/10.1126/sciadv.abi9979 show some evidence in their supplementary material, that the convolution model is not fully applicable.

f = Figure()
+f
Example block output

Pupil

We use the simplified PuRF from Hoeks & Levelt, 1993. Note that https://www.science.org/doi/10.1126/sciadv.abi9979 show some evidence in their supplementary material, that the convolution model is not fully applicable.

f = Figure()
 plotConfig = (:n=>5:3:15,
              :tmax=>0.5:0.2:1.1,
              )
@@ -48,4 +45,4 @@
 
     axislegend(string(cfg[1]);merge=true,)
 end
-f
Example block output

This page was generated using Literate.jl.

+f
Example block output

This page was generated using Literate.jl.

diff --git a/dev/generated/reference/noisetypes/index.html b/dev/generated/reference/noisetypes/index.html index 3906fc1..c877c2f 100644 --- a/dev/generated/reference/noisetypes/index.html +++ b/dev/generated/reference/noisetypes/index.html @@ -3,7 +3,7 @@ using CairoMakie using DSP using StableRNGs -import StatsBase.autocor

What's the noise?

There are several noise-types directly implemented. Here is a comparison

f = Figure()
+import StatsBase.autocor

What's the noise?

There are several noise-types directly implemented. Here is a comparison:

f = Figure()
 ax_sig = f[1,1:2] = Axis(f;title="1.000 samples of noise")
 ax_spec = f[2,1] = Axis(f;title="Welch Periodigram")
 ax_auto = f[2,2] = Axis(f;title="Autocorrelogram (every 10th lag)")
@@ -27,4 +27,4 @@
 
 end
 f[1:2,3] = Legend(f,ax_sig,"NoiseType")
-f
Example block output

!!! Recommendation We recommed for smaller signals the ExponentialNoise, maybe with a removed DC offset or a HighPass filter. For long signals, this Noise requires lot's of memory though. maybe Pinknoise is a better choice


This page was generated using Literate.jl.

+fExample block output
Hint

We recommend for smaller signals the ExponentialNoise, maybe with a removed DC offset or a HighPass filter. For long signals, this Noise requires lots of memory though. Maybe Pinknoise is a better choice then.


This page was generated using Literate.jl.

diff --git a/dev/generated/reference/overview/index.html b/dev/generated/reference/overview/index.html index b05c0c2..f1aaf0b 100644 --- a/dev/generated/reference/overview/index.html +++ b/dev/generated/reference/overview/index.html @@ -1,12 +1,12 @@ -Toolbox Overview · UnfoldSim.jl

Overview of functionality

UnfoldSim has many modules, here we try to collect them to provide you with an overview

using UnfoldSim
+Toolbox Overview · UnfoldSim.jl

Overview of functionality

UnfoldSim has many modules, here we try to collect them to provide you with an overview.

using UnfoldSim
 using InteractiveUtils

Design

Designs define the experimental design. They can be nested, e.g. RepeatDesign(SingleSubjectDesign,10) would repeat the generated design-dataframe 10x.

subtypes(AbstractDesign)
3-element Vector{Any}:
  MultiSubjectDesign
  RepeatDesign
- SingleSubjectDesign

Component

components define a signal. Some components can be nested, e.g. LinearModelComponent|>MultichannelComponent, see the multi-channel tutorial for more information

subtypes(AbstractComponent)
3-element Vector{Any}:
+ SingleSubjectDesign

Component

Components define a signal. Some components can be nested, e.g. LinearModelComponent|>MultichannelComponent, see the multi-channel tutorial for more information.

subtypes(AbstractComponent)
3-element Vector{Any}:
  LinearModelComponent
  MixedModelComponent
- MultichannelComponent

Onsets

Onsets define the distance between events in the continuous signal

subtypes(AbstractOnset)
2-element Vector{Any}:
+ MultichannelComponent

Onsets

Onsets define the distance between events in the continuous signal.

subtypes(AbstractOnset)
2-element Vector{Any}:
  LogNormalOnset
  UniformOnset

Noise

Choose the noise you need!

subtypes(AbstractNoise)
7-element Vector{Any}:
  ExponentialNoise
@@ -15,4 +15,4 @@
  RedNoise
  UnfoldSim.AutoRegressiveNoise
  UnfoldSim.RealisticNoise
- WhiteNoise

This page was generated using Literate.jl.

+ WhiteNoise

This page was generated using Literate.jl.

diff --git a/dev/generated/tutorials/poweranalysis/index.html b/dev/generated/tutorials/poweranalysis/index.html index 20f4c56..42f4099 100644 --- a/dev/generated/tutorials/poweranalysis/index.html +++ b/dev/generated/tutorials/poweranalysis/index.html @@ -25,4 +25,4 @@ # calculate a one-sided t-test pvals[seed] = pvalue(OneSampleTTest(y_big,y_small)) -end
 15.129196 seconds (25.38 M allocations: 3.362 GiB, 4.58% gc time, 94.41% compilation time: 9% of which was recompilation)

let's calculate the power

power = mean(pvals .<0.05)*100
60.0

This page was generated using Literate.jl.

+end
 15.980139 seconds (26.11 M allocations: 3.399 GiB, 4.51% gc time, 94.10% compilation time: 9% of which was recompilation)

let's calculate the power

power = mean(pvals .<0.05)*100
60.0

This page was generated using Literate.jl.

diff --git a/dev/generated/tutorials/quickstart/index.html b/dev/generated/tutorials/quickstart/index.html index c0c7c57..6f1e547 100644 --- a/dev/generated/tutorials/quickstart/index.html +++ b/dev/generated/tutorials/quickstart/index.html @@ -1,12 +1,12 @@ -Quickstart · UnfoldSim.jl
using UnfoldSim
+Quickstart · UnfoldSim.jl
using UnfoldSim
 using Random
-using CairoMakie
Tipp

Use subtypes(AbstractNoise) (or subtypes(AbstractComponent) etc.) to find already implemented building blocks

"Experimental" Design

Define a 1 x 2 design with 20 trials. That is, one condition (condaA) with two levels.

design = SingleSubjectDesign(;
+using CairoMakie
Tip

Use subtypes(AbstractNoise) (or subtypes(AbstractComponent) etc.) to find already implemented building blocks.

"Experimental" Design

Define a 1 x 2 design with 20 trials. That is, one condition (condaA) with two levels.

design = SingleSubjectDesign(;
         conditions=Dict(:condA=>["levelA","levelB"])
-        ) |> x->RepeatDesign(x,10);

Component / Signal

Define a simple component and ground truth simulation formula. Akin to ERP components, we call one simulation signal a component.

Highlight

You could easily specify multiple components by providing a vector of components, which are automatically added at the same onsets. This procedure simplifies to generate some response that is independent of simulated condition, whereas other depends on it.

signal = LinearModelComponent(;
+        ) |> x->RepeatDesign(x,10);

Component / Signal

Define a simple component and ground truth simulation formula. Akin to ERP components, we call one simulation signal a component.

Note

You could easily specify multiple components by providing a vector of components, which are automatically added at the same onsets. This procedure simplifies to generate some response that is independent of simulated condition, whereas other depends on it.

signal = LinearModelComponent(;
         basis=[0,0,0,0.5,1,1,0.5,0,0],
         formula = @formula(0~1+condA),
         β = [1,0.5]
-        );

Onsets and Noise

We will start with a uniform (but overlapping, offset < length(signal.basis)) onset-distribution

onset = UniformOnset(;width=20,offset=4);

And we will use some noise

noise = PinkNoise(;noiselevel=0.2);

Combine & Generate

We will put it all together in one Simulation type

simulation = Simulation(design, signal,  onset, noise);

finally, we will simulate some data

data,events = simulate(MersenneTwister(1),simulation);

Data is a n-sample Vector (but could be a Matrix for e.g. MultiSubjectDesign).

events is a DataFrame that contains a column latency with the onsets of events.

Plot them!

lines(data;color="black")
+        );

Onsets and Noise

We will start with a uniform (but overlapping, offset < length(signal.basis)) onset-distribution

onset = UniformOnset(;width=20,offset=4);

And we will use some noise

noise = PinkNoise(;noiselevel=0.2);

Combine & Generate

We will put it all together in one Simulation type

simulation = Simulation(design, signal,  onset, noise);

finally, we will simulate some data

data,events = simulate(MersenneTwister(1),simulation);

Data is a n-sample Vector (but could be a Matrix for e.g. MultiSubjectDesign).

events is a DataFrame that contains a column latency with the onsets of events.

Plot them!

lines(data;color="black")
 vlines!(events.latency;color=["orange","teal"][1 .+ (events.condA.=="levelB")])
-current_figure()
Example block output

This page was generated using Literate.jl.

+current_figure()
Example block output

This page was generated using Literate.jl.

diff --git a/dev/generated/tutorials/simulateERP/index.html b/dev/generated/tutorials/simulateERP/index.html index cbdcf4d..ecfccd9 100644 --- a/dev/generated/tutorials/simulateERP/index.html +++ b/dev/generated/tutorials/simulateERP/index.html @@ -1,11 +1,11 @@ -Simulate ERPs · UnfoldSim.jl
using UnfoldSim
+Simulate ERPs · UnfoldSim.jl
using UnfoldSim
 using CairoMakie
 using Random
 using Unfold
-using UnfoldMakie

ERP Complex

here we will learn how to simulate a typical ERP complex with P100, N170, P300

let's grab a SingleSubjectDesign and add a continuous predictor

design = SingleSubjectDesign(;
+using UnfoldMakie

ERP Complex

Here we will learn how to simulate a typical ERP complex with P100, N170, P300.

Let's grab a SingleSubjectDesign and add a continuous predictor

design = SingleSubjectDesign(;
         conditions=Dict(:condition=>["car","face"],:continuous=>range(-5,5,length=10))
-        ) |> x->RepeatDesign(x,100);

let's make use of the prespecified basis functions, but use different formulas + parameters for each!

p100 is unaffected by our design and has amplitude of 5

p1 =  LinearModelComponent(;
+        ) |> x->RepeatDesign(x,100);

Let's make use of the prespecified basis functions, but use different formulas + parameters for each!

p100 is unaffected by our design and has amplitude of 5

p1 =  LinearModelComponent(;
         basis = p100(),
         formula = @formula(0~1),
         β = [5]
@@ -17,29 +17,7 @@
         basis = p300(),
         formula = @formula(0~1+continuous),
         β = [5,1]
-        );

now we can simply combine the components and simulate

components = [p1,n1,p3]
-data,evts = simulate(MersenneTwister(1),design,[p1,n1,p3],UniformOnset(;width=0,offset=1000),PinkNoise());
-
-# Analysis
([-0.23791263767079127, 0.7795891463181703, 0.05361693547332502, 0.12380920288278903, 0.7569649245743058, 0.3111863187121404, -0.847739657112498, -0.40995674870938037, 0.0009473713863165388, -1.2529893062381732  …  6.40407333484387, 4.969979336030171, 4.478563726264574, 3.6967811153769046, 3.232807745307163, 1.6266788619810815, 0.8313055493944903, 0.9393347800539115, 1.0530779209290557, 1.3268954815109233], 2000×3 DataFrame
-  Row  continuous  condition  latency 
-      │ Float64     String     Int64   
-──────┼────────────────────────────────
-    1 │  -5.0       car           1000
-    2 │  -3.88889   car           2000
-    3 │  -2.77778   car           3000
-    4 │  -1.66667   car           4000
-    5 │  -0.555556  car           5000
-    6 │   0.555556  car           6000
-    7 │   1.66667   car           7000
-    8 │   2.77778   car           8000
-  ⋮   │     ⋮           ⋮         ⋮
- 1994 │  -1.66667   face       1994000
- 1995 │  -0.555556  face       1995000
- 1996 │   0.555556  face       1996000
- 1997 │   1.66667   face       1997000
- 1998 │   2.77778   face       1998000
- 1999 │   3.88889   face       1999000
- 2000 │   5.0       face       2000000
-                      1985 rows omitted)

Let's check that everything worked out well, by using Unfold

m = fit(UnfoldModel,Dict(Any=>(@formula(0~1+condition+continuous),firbasis(τ=[-0.1,1],sfreq=100,name="basis"))),evts,data);

first the "pure" beta/linear regression parameters

plot_erp(coeftable(m))
Example block output

and now beautifully visualized as marginal betas / predicted ERPs

plot_erp(effects(Dict(:condition=>["car","face"],:continuous=>-5:5),m);
+        );

Now we can simply combine the components and simulate

components = [p1,n1,p3]
+data,evts = simulate(MersenneTwister(1),design,[p1,n1,p3],UniformOnset(;width=0,offset=1000),PinkNoise());

Analysis

Let's check that everything worked out well, by using Unfold

m = fit(UnfoldModel,Dict(Any=>(@formula(0~1+condition+continuous),firbasis(τ=[-0.1,1],sfreq=100,name="basis"))),evts,data);

first the "pure" beta/linear regression parameters

plot_erp(coeftable(m))
Example block output

and now beautifully visualized as marginal betas / predicted ERPs

plot_erp(effects(Dict(:condition=>["car","face"],:continuous=>-5:5),m);
         mapping=(:color=>:continuous,linestyle=:condition,group=:continuous),
-        extra=(;categoricalColor=false))
Example block output

This page was generated using Literate.jl.

+ extra=(;categoricalColor=false))
Example block output

This page was generated using Literate.jl.

diff --git a/dev/index.html b/dev/index.html index 63e7e0d..f0ce09f 100644 --- a/dev/index.html +++ b/dev/index.html @@ -7,4 +7,4 @@ vlines!(evts.latency;color=["orange","teal"][1 .+ (evts.condition .=="car")]) current_figure()Example block output

Or simulate epoched data directly

data,evts = UnfoldSim.predef_eeg(;n_repeats=20,noiselevel=0.8,return_epoched=true)
-heatmap(data[:,sortperm(evts,[:condition,:continuous])])
Example block output +heatmap(data[:,sortperm(evts,[:condition,:continuous])])Example block output diff --git a/dev/search_index.js b/dev/search_index.js index c3009f1..4f78321 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"EditURL = \"../../../literate/HowTo/newComponent.jl\"","category":"page"},{"location":"generated/HowTo/newComponent/#New-component:-Duration-Shift","page":"New Duration/Shift-dependent Component","title":"New component: Duration + Shift","text":"","category":"section"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"We want a new component that changes it's duration and shift depending on a column in the event-design. This is somewhat already implemented in the HRF + Pupil bases","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"begin\n\tusing UnfoldSim\n using Unfold\n\tusing Random\n\tusing DSP\n using CairoMakie\nend\n\n\nsfreq = 100\n\n# Design","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"Let's genrate a design with two columns, shift + duration","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"design = UnfoldSim.SingleSubjectDesign(;conditions= Dict(\n :shift => rand(100).*sfreq/5,\n :duration=>20 .+rand(100).*sfreq/5))","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"We also need a new AbstractComponent","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"struct TimeVaryingComponent <: AbstractComponent\n basisfunction\n maxlength\nend","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"we have to define the length of a component","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"Base.length(c::TimeVaryingComponent) = length(c.maxlength)","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"While we could have put the TimeVaryingComponent.basisfunction directly into the simulate function, I thought this is a bit more modular","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"function UnfoldSim.simulate(rng,c::TimeVaryingComponent,design::AbstractDesign)\n evts = generate(design)\n return c.basisfunction(evts,c.maxlength)\nend","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"finally, the actual function that does the shifting + duration","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"function basis_shiftduration(evts,maxlength)\n basis = hanning.(Int.(round.(evts.duration))) ## hanning as long as duration\n if \"shift\" ∈ names(evts)\n basis = padarray.(basis,Int.(round.(.-evts.shift)),0) ## shift by adding 0 in front\n end\n # we should make sure that all bases have maxlength by appending / truncating\n difftomax = maxlength .- length.(basis)\n if any(difftomax.<0)\n @warn \"basis longer than max length in at least one case. either increase maxlength or redefine function. Trying to truncate the basis\"\n basis[difftomax .>0] = padarray.(basis[difftomax .> 0],difftomax[difftomax .> 0],0)\n return [b[1:maxlength] for b in basis]\n else\n return padarray.(basis,difftomax,0)\n end\nend\n\n\nerp = UnfoldSim.simulate(MersenneTwister(1),TimeVaryingComponent(basis_shiftduration,50),design)\nheatmap(hcat(erp...))","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"EditURL = \"../../../literate/tutorials/simulateERP.jl\"","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"using UnfoldSim\nusing CairoMakie\nusing Random\nusing Unfold\nusing UnfoldMakie","category":"page"},{"location":"generated/tutorials/simulateERP/#ERP-Complex","page":"Simulate ERPs","title":"ERP Complex","text":"","category":"section"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"here we will learn how to simulate a typical ERP complex with P100, N170, P300","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"let's grab a SingleSubjectDesign and add a continuous predictor","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"design = SingleSubjectDesign(;\n conditions=Dict(:condition=>[\"car\",\"face\"],:continuous=>range(-5,5,length=10))\n ) |> x->RepeatDesign(x,100);\nnothing #hide","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"let's make use of the prespecified basis functions, but use different formulas + parameters for each!","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"p100 is unaffected by our design and has amplitude of 5","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"p1 = LinearModelComponent(;\n basis = p100(),\n formula = @formula(0~1),\n β = [5]\n );\nnothing #hide","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"n170 has a condition effect, faces are more negative than cars","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"n1 = LinearModelComponent(;\n basis = n170(),\n formula = @formula(0~1+condition),\n β = [5,-3]\n );\nnothing #hide","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"p300 has a continuous effect, higher continuous values will result in larger P300's","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"p3 = LinearModelComponent(;\n basis = p300(),\n formula = @formula(0~1+continuous),\n β = [5,1]\n );\nnothing #hide","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"now we can simply combine the components and simulate","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"components = [p1,n1,p3]\ndata,evts = simulate(MersenneTwister(1),design,[p1,n1,p3],UniformOnset(;width=0,offset=1000),PinkNoise());\n\n# Analysis","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"Let's check that everything worked out well, by using Unfold","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"m = fit(UnfoldModel,Dict(Any=>(@formula(0~1+condition+continuous),firbasis(τ=[-0.1,1],sfreq=100,name=\"basis\"))),evts,data);\nnothing #hide","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"first the \"pure\" beta/linear regression parameters","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"plot_erp(coeftable(m))","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"and now beautifully visualized as marginal betas / predicted ERPs","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"plot_erp(effects(Dict(:condition=>[\"car\",\"face\"],:continuous=>-5:5),m);\n mapping=(:color=>:continuous,linestyle=:condition,group=:continuous),\n extra=(;categoricalColor=false))","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"EditURL = \"../../../literate/HowTo/newDesign.jl\"","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"using UnfoldSim\nusing StableRNGs\nusing DataFrames\nusing Parameters","category":"page"},{"location":"generated/HowTo/newDesign/#Define-a-new-Design","page":"New Experimental Design","title":"Define a new Design","text":"","category":"section"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"A design specifies how much data is generated, and how the event-table(s) should be generated. Already implemented examples are MultiSubjectDesign and SingleSubjectdesign","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"We need 3 things for a new design: a struct<:AbstractDesign, a size and a generate function","category":"page"},{"location":"generated/HowTo/newDesign/#)-type","page":"New Experimental Design","title":"1) type","text":"","category":"section"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"We need a ImbalanceSubjectDesign struct. You are free to implement it as you wish, as long as the other two functions are implemented","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"@with_kw struct ImbalanceSubjectDesign <: UnfoldSim.AbstractDesign\n nTrials::Int\n balance::Float64 = 0.5 # default balanced\nend;\nnothing #hide","category":"page"},{"location":"generated/HowTo/newDesign/#)-size","page":"New Experimental Design","title":"2) size","text":"","category":"section"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"we need a size(design::ImbalanceSubjectDesign) function to tell how many events we will have. This is used at different places, e.g. in the Default onset implementation","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"# note the trailling , to make it a Tuple\nsize(design::ImbalanceSubjectDesign) = (design.nTrials,);\nnothing #hide","category":"page"},{"location":"generated/HowTo/newDesign/#)-generate","page":"New Experimental Design","title":"3) generate","text":"","category":"section"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"We need a type generate(design::ImbalanceSubjectDesign) function. This function should return the actual table as a DataFrame","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"function generate(design::ImbalanceSubjectDesign)\n nA = Int(round.(design.nTrials .* design.balance))\n nB = Int(round.(design.nTrials .* (1-design.balance)))\n @assert nA + nB ≈ design.nTrials\n levels = vcat(repeat([\"levelA\"],nA),repeat([\"levelB\"],nB))\n return DataFrame(Dict(:condition=>levels))\nend;\nnothing #hide","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"Finally, we can test the function and see whether it returns a Design-DataFrame as we requested","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"design = ImbalanceSubjectDesign(;nTrials=6,balance=0.2)\ngenerate(design)","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"important: Important\nit is the users task to ensure that each run is reproducible. So if you have a random process (e.g. shuffling), be sure to safe a RNG object in your struct and use it in your generate function.","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"EditURL = \"../../../literate/HowTo/multichannel.jl\"","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"using UnfoldSim\nusing UnfoldMakie\nusing CairoMakie\nusing DataFrames\nusing Random\n\n# Specifcy design","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"We are using a one-level design for testing here.","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"design = SingleSubjectDesign(conditions=Dict(:condA=>[\"levelA\"]))","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"Next we generate two simple components at two different times without any formula attached (we have a single condition anyway)","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"c = LinearModelComponent(;basis=p100(),formula = @formula(0~1),β = [1]);\nc2 = LinearModelComponent(;basis=p300(),formula = @formula(0~1),β = [1]);\nnothing #hide","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"next similar to the nested design above, we can nest the component in a MultichannelComponent. We could either provide the projection marix manually, e.g.:","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"mc = UnfoldSim.MultichannelComponent(c, [1,2,-1,3,5,2.3,1])","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"or maybe more convenient: use the pair-syntax: Headmodel=>Label which makes use of a headmodel (HaRTmuT is currently easily available in UnfoldSim)","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"hart = headmodel(type=\"hartmut\")\nmc = UnfoldSim.MultichannelComponent(c, hart=>\"Left Postcentral Gyrus\")\nmc2 = UnfoldSim.MultichannelComponent(c2, hart=>\"Right Occipital Pole\")","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"hint: Hint\n","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"You could also specify a noise-specific component which is applied prior to projection & summing with other components","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"finally we need to define the onsets of the signal","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"onset = UniformOnset(;width=20,offset=4);\n\n# Simulation + Plotting","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"Now as usual we simulate data. Inspecting data shows our result is now indeed ~230 Electrodes large! Nice!","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"data,events = simulate(MersenneTwister(1),design, [mc,mc2], onset, NoNoise())\nsize(data)","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"Let's plot using Butterfly & Topoplot first we convert the electrodes to positions usable in TopoPlots.jl","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"pos3d = hart.electrodes[\"pos\"]\n\npos2d = to_positions(pos3d')\npos2d = [Point2f(p[1]+0.5,p[2]+0.5) for p in pos2d]","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"let's plot!","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"f = Figure()\ndf = DataFrame(:estimate => data[:],:channel => repeat(1:size(data,1),outer=size(data,2)),:time => repeat(1:size(data,2),inner=size(data,1)))\nplot_butterfly!(f[1,1:2],df;positions=pos2d)\nplot_topoplot!(f[2,1],df[df.time .== 28,:];positions=pos2d,visual=(;enlarge=0.5,label_scatter=false),axis=(;limits=((0,1),(0,0.9))))\nplot_topoplot!(f[2,2],df[df.time .== 48,:];positions=pos2d,visual=(;enlarge=0.5,label_scatter=false),axis=(;limits=((0,1),(0,0.9))))\nf","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"This page was generated using Literate.jl.","category":"page"},{"location":"api/","page":"DocStrings","title":"DocStrings","text":"Modules = [UnfoldSim]","category":"page"},{"location":"api/#UnfoldSim.LinearModelComponent","page":"DocStrings","title":"UnfoldSim.LinearModelComponent","text":"A multiple regression component for one subject\n\nbasis: an object, if accessed, provides a 'basis-function', e.g. hanning(40), this defines the response at a single event. It will be weighted by the model-prediction\nformula: StatsModels Formula-Object @formula 0~1+cond (left side must be 0)\nβ Vector of betas, must fit the formula\ncontrasts: Dict. Default is empty, e.g. Dict(:condA=>EffectsCoding())\n\nAll arguments can be named, in that case contrasts is optional\n\nWorks best with SingleSubjectDesign\n\nLinearModelComponent(;\n basis=hanning(40),\n formula=@formula(0~1+cond),\n β = [1.,2.],\n contrasts=Dict(:cond=>EffectsCoding())\n)\n\n\n\n\n\n\n","category":"type"},{"location":"api/#UnfoldSim.MixedModelComponent","page":"DocStrings","title":"UnfoldSim.MixedModelComponent","text":"A component that adds a hierarchical relation between parameters according to a LMM defined via MixedModels.jl\n\nbasis: an object, if accessed, provides a 'basis-function', e.g. hanning(40), this defines the response at a single event. It will be weighted by the model-prediction\nformula: Formula-Object in the style of MixedModels.jl e.g. @formula 0~1+cond + (1|subject) - left-handside is ignored\nβ Vector of betas, must fit the formula\nσs Dict of random effect variances, e.g. Dict(:subject=>[0.5,0.4]) or to specify correlationmatrix Dict(:subject=>[0.5,0.4,I(2,2)],...). Technically, this will be passed to MixedModels.jl create_re function, which creates the θ matrices.\ncontrasts: Dict in the style of MixedModels.jl. Default is empty.\n\nAll arguments can be named, in that case contrasts is optional\n\nWorks best with MultiSubjectDesign\n\nMixedModelComponent(;\n basis=hanning(40),\n formula=@formula(0~1+cond+(1+cond|subject)),\n β = [1.,2.],\n σs= Dict(:subject=>[0.5,0.4]),\n contrasts=Dict(:cond=>EffectsCoding())\n)\n\n\n\n\n\n\n","category":"type"},{"location":"api/#UnfoldSim.MultiSubjectDesign","page":"DocStrings","title":"UnfoldSim.MultiSubjectDesign","text":"n_subjects::Int -> number of subjects\nn_items::Int -> number of items (sometimes ≈trials)\nsubjects_between = nothing -> effects between subjects, e.g. young vs old \nitems_between = nothing -> effects between items, e.g. natural vs artificial images, but shown to all subjects\nboth_within = nothing\t-> effects completly crossed\ntableModifyFun = x->x; # can be used to sort, or x->shuffle(MersenneTwister(42),x) - be sure to fix/update the rng accordingly!!\n\ntipp: check the resulting dataframe using generate(design)\n\n# declaring same condition both sub-between and item-between results in a full between subject/item design\ndesign = MultiSubjectDesignjectDesign(;\n n_items=10,\n\t\tn_subjects = 30,\n subjects_between=Dict(:cond=>[\"levelA\",\"levelB\"]),\n\t\titems_between =Dict(:cond=>[\"levelA\",\"levelB\"]),\n );\n\n\n\n\n\n","category":"type"},{"location":"api/#UnfoldSim.MultichannelComponent","page":"DocStrings","title":"UnfoldSim.MultichannelComponent","text":"Wrapper for an AbstractComponent to project it to multiple target-channels via projection. optional adds noise to the source prior to projection.\n\n\n\n\n\n","category":"type"},{"location":"api/#UnfoldSim.RepeatDesign","page":"DocStrings","title":"UnfoldSim.RepeatDesign","text":"repeat a design DataFrame multiple times to mimick repeatedly recorded trials\n\ndesignOnce = MultiSubjectDesign(;\n n_items=2,\n\t\tn_subjects = 2,\n subjects_between =Dict(:cond=>[\"levelA\",\"levelB\"]),\n\t\titems_between =Dict(:cond=>[\"levelA\",\"levelB\"]),\n );\n\ndesign = RepeatDesign(designOnce,4);\n\n\n\n\n\n","category":"type"},{"location":"api/#UnfoldSim.SingleSubjectDesign","page":"DocStrings","title":"UnfoldSim.SingleSubjectDesign","text":"conditions = Dict of conditions, e.g. Dict(:A=>[\"a_small\",\"a_big\"],:B=>[\"b_tiny\",\"b_large\"])\ntableModifyFun = x->x; # can be used to sort, or x->shuffle(MersenneTwister(42),x) - be sure to fix/update the rng accordingly!!\n\nNumber of trials / rows in generate(design) depend on the full factorial of your conditions.\n\nTo increase the number of repetitions simply use RepeatDesign(SingleSubjectDesign(...),5)\n\ntipp: check the resulting dataframe using generate(design)\n\n\n\n\n\n","category":"type"},{"location":"api/#Base.size-Tuple{MultiSubjectDesign}","page":"DocStrings","title":"Base.size","text":"Returns dimension of experiment design\n\n\n\n\n\n","category":"method"},{"location":"api/#DSP.Windows.hanning-Tuple{Any, Any, Any}","page":"DocStrings","title":"DSP.Windows.hanning","text":"generate a hanning window\n\nduration: in s offset: in s, defines hanning peak sfreq: sampling rate in Hz\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.adderp!-Tuple{Any, Vector, Vararg{Any, 4}}","page":"DocStrings","title":"UnfoldSim.adderp!","text":"Helper function to add inplace the erps to the EEG, but for both 2D (1 channel) and 3D (X channel case)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.closest_src-Tuple{AbstractVector{<:AbstractVector}, Any}","page":"DocStrings","title":"UnfoldSim.closest_src","text":"closest_src(coords_list::AbstractVector{<:AbstractVector}, pos)\nclosest_src(coords::Vector{<:Real}, pos)\n\nTakes an array of 'm' target coordinate vector (size 3) (or vector of vectors) and a matrix (n-by-3) of all available positions, and returns an array of size 'm' containing the indices of the respective items in 'pos' that are nearest to each of the target coordinates.\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.closest_src-Tuple{Hartmut, String}","page":"DocStrings","title":"UnfoldSim.closest_src","text":"closest_src(head::Hartmut,label::String)\n\nReturns src-ix of the Headmodel Hartmut which is closest to the average of the label.\n\nimportant: Important\nWe use the average in eucledean space, but the cortex is a curved surface. In most cases they will not overlap. Ideally we would calculate the average on the surface, but this is a bit more complex to do (you'd need to calculate the vertices etc.)\n\nhartmut = headmodel()\npos = closest_src(hartmut=>\"Left Middle Temporal Gyrus, posterior division\")\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.convert-NTuple{4, Any}","page":"DocStrings","title":"UnfoldSim.convert","text":"Function to convert output similar to unfold (data, evts)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.gen_noise-Tuple{Any, UnfoldSim.RealisticNoise, Int64}","page":"DocStrings","title":"UnfoldSim.gen_noise","text":"gen_noise(t::RealisticNoise, n::Int)\n\nGenerate noise of a given type t and length n\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.gen_noise-Tuple{Any, Union{PinkNoise, RedNoise}, Int64}","page":"DocStrings","title":"UnfoldSim.gen_noise","text":"gen_noise(t::Union{PinkNoise, RedNoise}, n::Int)\n\nGenerate noise of a given type t and length n\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.gen_noise-Tuple{Any, WhiteNoise, Int64}","page":"DocStrings","title":"UnfoldSim.gen_noise","text":"gen_noise(t::WhiteNoise, n::Int)\n\nGenerate noise of a given type t and length n\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.generate-Tuple{MultiSubjectDesign}","page":"DocStrings","title":"UnfoldSim.generate","text":"Generates full factorial Dataframe according to MixedModelsSim.jl 's simdatcrossed function Note: nitems = you can think of it as trials or better, as stimuli\n\nNote: No condition can be named dv which is used internally in MixedModelsSim / MixedModels as a dummy left-side\n\nAfterwards applies expdesign.tableModifyFun. Could be used to duplicate trials, sort, subselect etc.\n\nFinally it sorts by :subject\n\njulia> d = MultiSubjectDesign(;nsubjects = 10,nitems=20,both_within= Dict(:A=>nlevels(5),:B=>nlevels(2))) julia> generate(d)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.generate-Tuple{SingleSubjectDesign}","page":"DocStrings","title":"UnfoldSim.generate","text":"Generates full-factorial DataFrame of expdesign.conditions\n\nAfterwards applies expdesign.tableModifyFun.\n\njulia> d = SingleSubjectDesign(;conditions= Dict(:A=>nlevels(5),:B=>nlevels(2))) julia> generate(d)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.hartmut_citation-Tuple{}","page":"DocStrings","title":"UnfoldSim.hartmut_citation","text":"Returns citation-string for HArtMuT\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.headmodel-Tuple{}","page":"DocStrings","title":"UnfoldSim.headmodel","text":"Load a headmodel, using Artifacts.jl automatically downloads the required files\n\nCurrently only type=\"hartmut\" is implemented\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.hrf-Tuple{}","page":"DocStrings","title":"UnfoldSim.hrf","text":"Generate a HRF kernel. \n\nTR = 1/sfreq default parameters taken from SPM\n\nCode adapted from Unfold.jl\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.leadfield-Tuple{Hartmut}","page":"DocStrings","title":"UnfoldSim.leadfield","text":"Returns the leadfield\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.magnitude-Tuple{AbstractHeadmodel}","page":"DocStrings","title":"UnfoldSim.magnitude","text":"Extracts magnitude of the orientation-including leadfield.\n\nBy default uses the orientation specified in the headmodel\n\nFallback: along the third dimension using norm - the maximal projection\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.magnitude-Tuple{Hartmut}","page":"DocStrings","title":"UnfoldSim.magnitude","text":"Extract magnitude of 3-orientation-leadfield, type (default: \"perpendicular\") => uses the provided source-point orientations - otherwise falls back to norm\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.n_channels-Tuple{AbstractComponent}","page":"DocStrings","title":"UnfoldSim.n_channels","text":"Returns the number of channels. By default = 1\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.n_channels-Tuple{MultichannelComponent}","page":"DocStrings","title":"UnfoldSim.n_channels","text":"for MultichannelComponent returns the length of the projection vector\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.padarray-Tuple{Vector, Tuple, Any}","page":"DocStrings","title":"UnfoldSim.padarray","text":"Pads array with specified value, length padarray(arr, len, val)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.predef_2x2-Tuple{Random.AbstractRNG}","page":"DocStrings","title":"UnfoldSim.predef_2x2","text":"todo\n\nCareful if you modify nitems with nsubjects = 1, n_items has to be a multiple of 4 (or your equivalent conditions factorial, e.g. all combinations length)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.predef_eeg-Tuple{Any}","page":"DocStrings","title":"UnfoldSim.predef_eeg","text":"Generates a P1/N1/P3 complex. predefeeg(;kwargs...) predefeeg(rng;kwargs...) predefeeg(rng,nsubjects;kwargs...)\n\nIn case of n_subjects, MixedModelComponents are generated\n\nDefault params: n_repeats=100 tableModifyFun = x->shuffle(deepcopy(rng),x # random trial order conditions = Dict(...),\n\ncomponent / signal\n\nsfreq = 100, p1 = (p100(;sfreq=sfreq), @formula(0~1),[5],Dict()), # P1 amp 5, no effects n1 = (n170(;sfreq=sfreq), @formula(0~1+condition),[5,-3],Dict()), # N1 amp 5, dummycoded condition effect (levels \"car\", \"face\") of -3 p3 = (p300(;sfreq=sfreq), @formula(0~1+continuous),[5,1],Dict()), # P3 amp 5, continuous effect range [-5,5] with slope 1\n\nnoise\n\nnoiselevel = 0.2, noise = PinkNoise(;noiselevel=noiselevel),\n\nonset\n\noverlap = (0.5,0.2), # offset + width/length of Uniform noise. put offset to 1 for no overlap. put width to 0 for no jitter onset=UniformOnset(;offset=sfreq0.5overlap[1],width=sfreq0.5overlap[2]), \n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.predef_eeg-Tuple{Random.AbstractRNG, Any}","page":"DocStrings","title":"UnfoldSim.predef_eeg","text":"predefeeg(rng,nsubjects;kwargs...) Runs predefeeg(rng;kwargs...) nsubject times and concatenates the results.\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.simulate-Tuple{Any, AbstractComponent, Simulation}","page":"DocStrings","title":"UnfoldSim.simulate","text":"by default call simulate with ::Abstractcomponent,::AbstractDesign`, but allow for custom types\n\nmaking use of other information in simulation\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.simulate-Tuple{Any, LinearModelComponent, AbstractDesign}","page":"DocStrings","title":"UnfoldSim.simulate","text":"simulate a linearModel\n\njulia> c = UnfoldSim.LinearModelComponent([0,1,1,0],@formula(0~1+cond),[1,2],Dict()) julia> design = MultiSubjectDesign(;nsubjects=2,nitems=50,item_between=(;:cond=>[\"A\",\"B\"])) julia> simulate(StableRNG(1),c,design)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.simulate-Tuple{Any, MixedModelComponent, AbstractDesign}","page":"DocStrings","title":"UnfoldSim.simulate","text":"simulate MixedModelComponent\n\njulia> design = MultiSubjectDesign(;nsubjects=2,nitems=50,item_between=(;:cond=>[\"A\",\"B\"])) julia> c = UnfoldSim.MixedModelComponent([0.,1,1,0],@formula(0~1+cond+(1|subject)),[1,2],Dict(:subject=>[2],),Dict()) julia> simulate(StableRNG(1),c,design)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.simulate-Tuple{Any, Vector{<:AbstractComponent}, Simulation}","page":"DocStrings","title":"UnfoldSim.simulate","text":"Simulates erp data given the specified parameters \n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.weight_σs-Tuple{Dict, Float64, Float64}","page":"DocStrings","title":"UnfoldSim.weight_σs","text":"Weights a σs Dict for MixedModels.jl by a Float64\n\nFinally sales it by σ_lmm, as a trick to simulate noise-free LMMs\n\nI anticipate a function function weight_σs(σs::Dict,b_σs::Dict,σ_lmm::Float64) where each σs entry can be weighted individually\n\n\n\n\n\n","category":"method"},{"location":"generated/reference/noisetypes/","page":"NoiseTypes","title":"NoiseTypes","text":"EditURL = \"../../../literate/reference/noisetypes.jl\"","category":"page"},{"location":"generated/reference/noisetypes/","page":"NoiseTypes","title":"NoiseTypes","text":"using UnfoldSim\nusing CairoMakie\nusing DSP\nusing StableRNGs\nimport StatsBase.autocor","category":"page"},{"location":"generated/reference/noisetypes/#What's-the-noise?","page":"NoiseTypes","title":"What's the noise?","text":"","category":"section"},{"location":"generated/reference/noisetypes/","page":"NoiseTypes","title":"NoiseTypes","text":"There are several noise-types directly implemented. Here is a comparison","category":"page"},{"location":"generated/reference/noisetypes/","page":"NoiseTypes","title":"NoiseTypes","text":"f = Figure()\nax_sig = f[1,1:2] = Axis(f;title=\"1.000 samples of noise\")\nax_spec = f[2,1] = Axis(f;title=\"Welch Periodigram\")\nax_auto = f[2,2] = Axis(f;title=\"Autocorrelogram (every 10th lag)\")\nfor n = [PinkNoise RedNoise WhiteNoise NoNoise ExponentialNoise]\n\n # generate\n noisevec = gen_noise(StableRNG(1),n(),10000)\n\n # plot 1000 samples\n lines!(ax_sig,noisevec[1:1000];label=string(n))\n\n # calc spectrum\n perio = welch_pgram(noisevec)\n\n # plot spectrum\n lines!(ax_spec,freq(perio),log10.(power(perio)))\n\n lags = 0:10:500\n autocor_vec = autocor(noisevec,lags)\n lines!(ax_auto,lags,autocor_vec)\n\nend\nf[1:2,3] = Legend(f,ax_sig,\"NoiseType\")\nf","category":"page"},{"location":"generated/reference/noisetypes/","page":"NoiseTypes","title":"NoiseTypes","text":"!!! Recommendation We recommed for smaller signals the ExponentialNoise, maybe with a removed DC offset or a HighPass filter. For long signals, this Noise requires lot's of memory though. maybe Pinknoise is a better choice","category":"page"},{"location":"generated/reference/noisetypes/","page":"NoiseTypes","title":"NoiseTypes","text":"","category":"page"},{"location":"generated/reference/noisetypes/","page":"NoiseTypes","title":"NoiseTypes","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"EditURL = \"../../../literate/reference/basistypes.jl\"","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"using UnfoldSim\nusing CairoMakie\nusing DSP\nusing StableRNGs\n#","category":"page"},{"location":"generated/reference/basistypes/#Basistypes","page":"ComponentBasisTypes","title":"Basistypes","text":"","category":"section"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"There are several bases types directly implemented. They can be easily used for the components.","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"note: Note\nYou can use any arbitrary shape defined by yourself! We often make use of hanning(50) from the DSP.jl package","category":"page"},{"location":"generated/reference/basistypes/#EEG","page":"ComponentBasisTypes","title":"EEG","text":"","category":"section"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"by default, the EEG bases assume a sampling rate of 100, which can easily be changed by e.g. p100(;sfreq=300)","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"f = Figure()\nax = f[1,1] = Axis(f)\nfor b in [p100,n170,p300,n400]\n lines!(ax,b(),label=string(b))\n scatter!(ax,b(),label=string(b))\nend\naxislegend(ax,merge=true)\nf","category":"page"},{"location":"generated/reference/basistypes/#fMRI","page":"ComponentBasisTypes","title":"fMRI","text":"","category":"section"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"default hrf TR is 1. Get to know all your favourite shapes!","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"##--\nf = Figure()\nplotConfig = (:peak=>1:3:10,\n :psUnder=>10:5:30,\n :amplitude=>2:5,\n :shift=>0:3:10,\n :peak_width => 0.1:0.5:1.5,\n :psUnder_width => 0.1:0.5:1.5,\n )\n\nfor (ix,pl) = enumerate(plotConfig)\n col = (ix-1)%3 +1\n row = Int(ceil(ix/3))\n\n ax = f[row,col] = Axis(f)\n cfg = collect(pl)\n for k = cfg[2]\n lines!(ax,UnfoldSim.hrf(;TR=0.1,(cfg[1]=>k,)...),label=string(k))\n end\n\n axislegend(string(cfg[1]);merge=true,)\nend\nf\n\n# pupil","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"we use the simplified PuRF from Hoeks & Levelt, 1993. Note that https://www.science.org/doi/10.1126/sciadv.abi9979 show some evidence in their supplementary material, that the convolution model is not fully applicable.","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"f = Figure()\nplotConfig = (:n=>5:3:15,\n :tmax=>0.5:0.2:1.1,\n )\n\nfor (ix,pl) = enumerate(plotConfig)\n ax = f[1,ix] = Axis(f)\n cfg = collect(pl)\n for k = cfg[2]\n lines!(ax,UnfoldSim.PuRF(;(cfg[1]=>k,)...),label=string(k))\n end\n\n axislegend(string(cfg[1]);merge=true,)\nend\nf","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"EditURL = \"../../../literate/tutorials/poweranalysis.jl\"","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"using UnfoldSim\nusing Unfold\nusing Statistics\nusing HypothesisTests\nusing DataFrames\nusing Random","category":"page"},{"location":"generated/tutorials/poweranalysis/#Simple-Poweranalysis-Script","page":"Poweranalysis","title":"Simple Poweranalysis Script","text":"","category":"section"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"For a power analysis, we will repeatedly simulate data, and check whether we can find a significant effect.","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"We perform the power analysis on epoched data.","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"pvals = fill(NaN,100)\n@time for seed = eachindex(pvals)\n # Simulate data of 30 subjects\n data,evts = UnfoldSim.predef_2x2(MersenneTwister(seed);\n n_subjects=20, ## 30 subjects\n overlap=(1,0), ## deactivate overlap\n noiselevel=10, ## add more noise to make it more challenging\n return_epoched=true, ## saves us the epoching step\n )\n\n\n # take the mean over a pre-specified timewindow\n evts.y = dropdims(mean(data[40:60,:],dims=1),dims=(1))\n\n # extract the two levels of condition A\n evts_reduced = combine(groupby(evts,[:subject,:A]),:y=>mean)\n y_big = evts_reduced[evts_reduced.A .==\"a_big\",:y_mean]\n y_small = evts_reduced[evts_reduced.A .==\"a_small\",:y_mean]\n\n # calculate a one-sided t-test\n pvals[seed] = pvalue(OneSampleTTest(y_big,y_small))\nend","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"let's calculate the power","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"power = mean(pvals .<0.05)*100","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"EditURL = \"../../../literate/reference/overview.jl\"","category":"page"},{"location":"generated/reference/overview/#Overview-of-functionality","page":"Toolbox Overview","title":"Overview of functionality","text":"","category":"section"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"UnfoldSim has many modules, here we try to collect them to provide you with an overview","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"using UnfoldSim\nusing InteractiveUtils","category":"page"},{"location":"generated/reference/overview/#Design","page":"Toolbox Overview","title":"Design","text":"","category":"section"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"Designs define the experimental design. They can be nested, e.g. RepeatDesign(SingleSubjectDesign,10) would repeat the generated design-dataframe 10x.","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"subtypes(AbstractDesign)","category":"page"},{"location":"generated/reference/overview/#Component","page":"Toolbox Overview","title":"Component","text":"","category":"section"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"components define a signal. Some components can be nested, e.g. LinearModelComponent|>MultichannelComponent, see the multi-channel tutorial for more information","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"subtypes(AbstractComponent)","category":"page"},{"location":"generated/reference/overview/#Onsets","page":"Toolbox Overview","title":"Onsets","text":"","category":"section"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"Onsets define the distance between events in the continuous signal","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"subtypes(AbstractOnset)","category":"page"},{"location":"generated/reference/overview/#Noise","page":"Toolbox Overview","title":"Noise","text":"","category":"section"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"Choose the noise you need!","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"subtypes(AbstractNoise)","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"EditURL = \"../../../literate/tutorials/quickstart.jl\"","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"using UnfoldSim\nusing Random\nusing CairoMakie","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"tipp: Tipp\nUse subtypes(AbstractNoise) (or subtypes(AbstractComponent) etc.) to find already implemented building blocks","category":"page"},{"location":"generated/tutorials/quickstart/#\"Experimental\"-Design","page":"Quickstart","title":"\"Experimental\" Design","text":"","category":"section"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"Define a 1 x 2 design with 20 trials. That is, one condition (condaA) with two levels.","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"design = SingleSubjectDesign(;\n conditions=Dict(:condA=>[\"levelA\",\"levelB\"])\n ) |> x->RepeatDesign(x,10);\nnothing #hide","category":"page"},{"location":"generated/tutorials/quickstart/#Component-/-Signal","page":"Quickstart","title":"Component / Signal","text":"","category":"section"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"Define a simple component and ground truth simulation formula. Akin to ERP components, we call one simulation signal a component.","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"highlight: Highlight\nYou could easily specify multiple components by providing a vector of components, which are automatically added at the same onsets. This procedure simplifies to generate some response that is independent of simulated condition, whereas other depends on it.","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"signal = LinearModelComponent(;\n basis=[0,0,0,0.5,1,1,0.5,0,0],\n formula = @formula(0~1+condA),\n β = [1,0.5]\n );\nnothing #hide","category":"page"},{"location":"generated/tutorials/quickstart/#Onsets-and-Noise","page":"Quickstart","title":"Onsets and Noise","text":"","category":"section"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"We will start with a uniform (but overlapping, offset < length(signal.basis)) onset-distribution","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"onset = UniformOnset(;width=20,offset=4);\nnothing #hide","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"And we will use some noise","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"noise = PinkNoise(;noiselevel=0.2);\nnothing #hide","category":"page"},{"location":"generated/tutorials/quickstart/#Combine-and-Generate","page":"Quickstart","title":"Combine & Generate","text":"","category":"section"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"We will put it all together in one Simulation type","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"simulation = Simulation(design, signal, onset, noise);\nnothing #hide","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"finally, we will simulate some data","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"data,events = simulate(MersenneTwister(1),simulation);\nnothing #hide","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"Data is a n-sample Vector (but could be a Matrix for e.g. MultiSubjectDesign).","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"events is a DataFrame that contains a column latency with the onsets of events.","category":"page"},{"location":"generated/tutorials/quickstart/#Plot-them!","page":"Quickstart","title":"Plot them!","text":"","category":"section"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"lines(data;color=\"black\")\nvlines!(events.latency;color=[\"orange\",\"teal\"][1 .+ (events.condA.==\"levelB\")])\ncurrent_figure()","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"EditURL = \"../../../literate/HowTo/repeatTrials.jl\"","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"using UnfoldSim","category":"page"},{"location":"generated/HowTo/repeatTrials/#Repeating-Design-entries","page":"Repeating Trials within a Design","title":"Repeating Design entries","text":"","category":"section"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"Sometimes we want to repeat a design, that is, have multiple trials with identical values, but it is not always straight forward to implement For instance, there is no way to easily modify MultiSubjectDesign to have multiple identical subject/item combinations, without doing awkward repetitions of condition-levels or something.","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"If you struggle with this problem RepeatDesign is an easy tool for you","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"designOnce = MultiSubjectDesign(;\n n_items=2,\n\t\tn_subjects = 2,\n subjects_between =Dict(:cond=>[\"levelA\",\"levelB\"]),\n\t\titems_between =Dict(:cond=>[\"levelA\",\"levelB\"]),\n );\n\ndesign = RepeatDesign(designOnce,4);\ngenerate(design)","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"As you can see, the design was simply repeated. As always, you can ignore the dv column, it is for internal consistency with MixedModelsSim.jl","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"note: Note\nif you implemented your own AbstractDesign, you need to define the size function accordingly. E.g.: Base.size(design::RepeatDesign{SingleSubjectDesign}) = size(design.design).*design.repeat","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"This page was generated using Literate.jl.","category":"page"},{"location":"","page":"Home","title":"Home","text":"CurrentModule = UnfoldSim","category":"page"},{"location":"#UnfoldSim","page":"Home","title":"UnfoldSim","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Documentation for UnfoldSim.","category":"page"},{"location":"#Start-simulating-timeseries","page":"Home","title":"Start simulating timeseries","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"We offer some predefined signals, check them out!","category":"page"},{"location":"","page":"Home","title":"Home","text":"For instance an P1/N170/P300 complex.","category":"page"},{"location":"","page":"Home","title":"Home","text":"using UnfoldSim\nusing CairoMakie\ndata,evts = UnfoldSim.predef_eeg(;n_repeats=1,noiselevel=0.8)\n\nlines(data;color=\"black\")\nvlines!(evts.latency;color=[\"orange\",\"teal\"][1 .+ (evts.condition .==\"car\")])\n\ncurrent_figure()","category":"page"},{"location":"#Or-simulate-epoched-data-directly","page":"Home","title":"Or simulate epoched data directly","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"\ndata,evts = UnfoldSim.predef_eeg(;n_repeats=20,noiselevel=0.8,return_epoched=true)\nheatmap(data[:,sortperm(evts,[:condition,:continuous])])\n","category":"page"}] +[{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"EditURL = \"../../../literate/HowTo/newComponent.jl\"","category":"page"},{"location":"generated/HowTo/newComponent/#New-component:-Duration-Shift","page":"New Duration/Shift-dependent Component","title":"New component: Duration + Shift","text":"","category":"section"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"We want a new component that changes its duration and shift depending on a column in the event-design. This is somewhat already implemented in the HRF + Pupil bases","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"using UnfoldSim\nusing Unfold\nusing Random\nusing DSP\nusing CairoMakie\n\nsfreq = 100;\nnothing #hide","category":"page"},{"location":"generated/HowTo/newComponent/#Design","page":"New Duration/Shift-dependent Component","title":"Design","text":"","category":"section"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"Let's generate a design with two columns, shift + duration","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"design = UnfoldSim.SingleSubjectDesign(;conditions= Dict(\n :shift => rand(100).*sfreq/5,\n :duration=>20 .+rand(100).*sfreq/5))","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"We also need a new AbstractComponent","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"struct TimeVaryingComponent <: AbstractComponent\n basisfunction\n maxlength\nend","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"We have to define the length of a component","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"Base.length(c::TimeVaryingComponent) = length(c.maxlength)","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"While we could have put the TimeVaryingComponent.basisfunction directly into the simulate function, I thought this is a bit more modular","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"function UnfoldSim.simulate(rng,c::TimeVaryingComponent,design::AbstractDesign)\n evts = generate(design)\n return c.basisfunction(evts,c.maxlength)\nend","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"finally, the actual function that does the shifting + duration","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"function basis_shiftduration(evts,maxlength)\n basis = hanning.(Int.(round.(evts.duration))) ## hanning as long as duration\n if \"shift\" ∈ names(evts)\n basis = padarray.(basis,Int.(round.(.-evts.shift)),0) ## shift by adding 0 in front\n end\n # we should make sure that all bases have maxlength by appending / truncating\n difftomax = maxlength .- length.(basis)\n if any(difftomax.<0)\n @warn \"basis longer than max length in at least one case. either increase maxlength or redefine function. Trying to truncate the basis\"\n basis[difftomax .>0] = padarray.(basis[difftomax .> 0],difftomax[difftomax .> 0],0)\n return [b[1:maxlength] for b in basis]\n else\n return padarray.(basis,difftomax,0)\n end\nend\n\n\nerp = UnfoldSim.simulate(MersenneTwister(1),TimeVaryingComponent(basis_shiftduration,50),design)\nheatmap(hcat(erp...))","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"","category":"page"},{"location":"generated/HowTo/newComponent/","page":"New Duration/Shift-dependent Component","title":"New Duration/Shift-dependent Component","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"EditURL = \"../../../literate/tutorials/simulateERP.jl\"","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"using UnfoldSim\nusing CairoMakie\nusing Random\nusing Unfold\nusing UnfoldMakie","category":"page"},{"location":"generated/tutorials/simulateERP/#ERP-Complex","page":"Simulate ERPs","title":"ERP Complex","text":"","category":"section"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"Here we will learn how to simulate a typical ERP complex with P100, N170, P300.","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"Let's grab a SingleSubjectDesign and add a continuous predictor","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"design = SingleSubjectDesign(;\n conditions=Dict(:condition=>[\"car\",\"face\"],:continuous=>range(-5,5,length=10))\n ) |> x->RepeatDesign(x,100);\nnothing #hide","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"Let's make use of the prespecified basis functions, but use different formulas + parameters for each!","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"p100 is unaffected by our design and has amplitude of 5","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"p1 = LinearModelComponent(;\n basis = p100(),\n formula = @formula(0~1),\n β = [5]\n );\nnothing #hide","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"n170 has a condition effect, faces are more negative than cars","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"n1 = LinearModelComponent(;\n basis = n170(),\n formula = @formula(0~1+condition),\n β = [5,-3]\n );\nnothing #hide","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"p300 has a continuous effect, higher continuous values will result in larger P300's","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"p3 = LinearModelComponent(;\n basis = p300(),\n formula = @formula(0~1+continuous),\n β = [5,1]\n );\nnothing #hide","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"Now we can simply combine the components and simulate","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"components = [p1,n1,p3]\ndata,evts = simulate(MersenneTwister(1),design,[p1,n1,p3],UniformOnset(;width=0,offset=1000),PinkNoise());\nnothing #hide","category":"page"},{"location":"generated/tutorials/simulateERP/#Analysis","page":"Simulate ERPs","title":"Analysis","text":"","category":"section"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"Let's check that everything worked out well, by using Unfold","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"m = fit(UnfoldModel,Dict(Any=>(@formula(0~1+condition+continuous),firbasis(τ=[-0.1,1],sfreq=100,name=\"basis\"))),evts,data);\nnothing #hide","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"first the \"pure\" beta/linear regression parameters","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"plot_erp(coeftable(m))","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"and now beautifully visualized as marginal betas / predicted ERPs","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"plot_erp(effects(Dict(:condition=>[\"car\",\"face\"],:continuous=>-5:5),m);\n mapping=(:color=>:continuous,linestyle=:condition,group=:continuous),\n extra=(;categoricalColor=false))","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"","category":"page"},{"location":"generated/tutorials/simulateERP/","page":"Simulate ERPs","title":"Simulate ERPs","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"EditURL = \"../../../literate/HowTo/newDesign.jl\"","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"using UnfoldSim\nusing StableRNGs\nusing DataFrames\nusing Parameters","category":"page"},{"location":"generated/HowTo/newDesign/#Define-a-new-Design","page":"New Experimental Design","title":"Define a new Design","text":"","category":"section"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"A design specifies how much data is generated, and how the event-table(s) should be generated. Already implemented examples are MultiSubjectDesign and SingleSubjectDesign","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"We need 3 things for a new design: a struct<:AbstractDesign, a size and a generate function","category":"page"},{"location":"generated/HowTo/newDesign/#)-type","page":"New Experimental Design","title":"1) type","text":"","category":"section"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"We need a ImbalanceSubjectDesign struct. You are free to implement it as you wish, as long as the other two functions are implemented","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"@with_kw struct ImbalanceSubjectDesign <: UnfoldSim.AbstractDesign\n nTrials::Int\n balance::Float64 = 0.5 # default balanced\nend;\nnothing #hide","category":"page"},{"location":"generated/HowTo/newDesign/#)-size","page":"New Experimental Design","title":"2) size","text":"","category":"section"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"we need a size(design::ImbalanceSubjectDesign) function to tell how many events we will have. This is used at different places, e.g. in the Default onset implementation","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"# note the trailing , to make it a Tuple\nsize(design::ImbalanceSubjectDesign) = (design.nTrials,);\nnothing #hide","category":"page"},{"location":"generated/HowTo/newDesign/#)-generate","page":"New Experimental Design","title":"3) generate","text":"","category":"section"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"We need a type generate(design::ImbalanceSubjectDesign) function. This function should return the actual table as a DataFrame","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"function generate(design::ImbalanceSubjectDesign)\n nA = Int(round.(design.nTrials .* design.balance))\n nB = Int(round.(design.nTrials .* (1-design.balance)))\n @assert nA + nB ≈ design.nTrials\n levels = vcat(repeat([\"levelA\"],nA),repeat([\"levelB\"],nB))\n return DataFrame(Dict(:condition=>levels))\nend;\nnothing #hide","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"Finally, we can test the function and see whether it returns a Design-DataFrame as we requested","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"design = ImbalanceSubjectDesign(;nTrials=6,balance=0.2)\ngenerate(design)","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"warning: Important\nIt is the users task to ensure that each run is reproducible. So if you have a random process (e.g. shuffling), be sure to safe a RNG object in your struct and use it in your generate function.","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"","category":"page"},{"location":"generated/HowTo/newDesign/","page":"New Experimental Design","title":"New Experimental Design","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"EditURL = \"../../../literate/HowTo/multichannel.jl\"","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"using UnfoldSim\nusing UnfoldMakie\nusing CairoMakie\nusing DataFrames\nusing Random","category":"page"},{"location":"generated/HowTo/multichannel/#Specify-design","page":"Multi Channel Data","title":"Specify design","text":"","category":"section"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"We are using a one-level design for testing here.","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"design = SingleSubjectDesign(conditions=Dict(:condA=>[\"levelA\"]))","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"Next we generate two simple components at two different times without any formula attached (we have a single condition anyway)","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"c = LinearModelComponent(;basis=p100(),formula = @formula(0~1),β = [1]);\nc2 = LinearModelComponent(;basis=p300(),formula = @formula(0~1),β = [1]);\nnothing #hide","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"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.:","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"mc = UnfoldSim.MultichannelComponent(c, [1,2,-1,3,5,2.3,1])","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"or maybe more convenient: use the pair-syntax: Headmodel=>Label which makes use of a headmodel (HaRTmuT is currently easily available in UnfoldSim)","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"hart = headmodel(type=\"hartmut\")\nmc = UnfoldSim.MultichannelComponent(c, hart=>\"Left Postcentral Gyrus\")\nmc2 = UnfoldSim.MultichannelComponent(c2, hart=>\"Right Occipital Pole\")","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"info: Info\nYou could also specify a noise-specific component which is applied prior to projection & summing with other components.","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"finally we need to define the onsets of the signal","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"onset = UniformOnset(;width=20,offset=4);\nnothing #hide","category":"page"},{"location":"generated/HowTo/multichannel/#Simulation-Plotting","page":"Multi Channel Data","title":"Simulation + Plotting","text":"","category":"section"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"Now as usual we simulate data. Inspecting data shows our result is now indeed ~230 Electrodes large! Nice!","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"data,events = simulate(MersenneTwister(1),design, [mc,mc2], onset, NoNoise())\nsize(data)","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"Let's plot using Butterfly & Topoplot: first we convert the electrodes to positions usable in TopoPlots.jl","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"pos3d = hart.electrodes[\"pos\"];\n\npos2d = to_positions(pos3d');\npos2d = [Point2f(p[1]+0.5,p[2]+0.5) for p in pos2d];\nnothing #hide","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"now plot!","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"f = Figure()\ndf = DataFrame(:estimate => data[:],:channel => repeat(1:size(data,1),outer=size(data,2)),:time => repeat(1:size(data,2),inner=size(data,1)))\nplot_butterfly!(f[1,1:2],df;positions=pos2d)\nplot_topoplot!(f[2,1],df[df.time .== 28,:];positions=pos2d,visual=(;enlarge=0.5,label_scatter=false),axis=(;limits=((0,1),(0,0.9))))\nplot_topoplot!(f[2,2],df[df.time .== 48,:];positions=pos2d,visual=(;enlarge=0.5,label_scatter=false),axis=(;limits=((0,1),(0,0.9))))\nf","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"","category":"page"},{"location":"generated/HowTo/multichannel/","page":"Multi Channel Data","title":"Multi Channel Data","text":"This page was generated using Literate.jl.","category":"page"},{"location":"api/","page":"DocStrings","title":"DocStrings","text":"Modules = [UnfoldSim]","category":"page"},{"location":"api/#UnfoldSim.LinearModelComponent","page":"DocStrings","title":"UnfoldSim.LinearModelComponent","text":"A multiple regression component for one subject\n\nbasis: an object, if accessed, provides a 'basis-function', e.g. hanning(40), this defines the response at a single event. It will be weighted by the model-prediction\nformula: StatsModels Formula-Object @formula 0~1+cond (left side must be 0)\nβ Vector of betas, must fit the formula\ncontrasts: Dict. Default is empty, e.g. Dict(:condA=>EffectsCoding())\n\nAll arguments can be named, in that case contrasts is optional\n\nWorks best with SingleSubjectDesign\n\nLinearModelComponent(;\n basis=hanning(40),\n formula=@formula(0~1+cond),\n β = [1.,2.],\n contrasts=Dict(:cond=>EffectsCoding())\n)\n\n\n\n\n\n\n","category":"type"},{"location":"api/#UnfoldSim.MixedModelComponent","page":"DocStrings","title":"UnfoldSim.MixedModelComponent","text":"A component that adds a hierarchical relation between parameters according to a LMM defined via MixedModels.jl\n\nbasis: an object, if accessed, provides a 'basis-function', e.g. hanning(40), this defines the response at a single event. It will be weighted by the model-prediction\nformula: Formula-Object in the style of MixedModels.jl e.g. @formula 0~1+cond + (1|subject) - left-handside is ignored\nβ Vector of betas, must fit the formula\nσs Dict of random effect variances, e.g. Dict(:subject=>[0.5,0.4]) or to specify correlationmatrix Dict(:subject=>[0.5,0.4,I(2,2)],...). Technically, this will be passed to MixedModels.jl create_re function, which creates the θ matrices.\ncontrasts: Dict in the style of MixedModels.jl. Default is empty.\n\nAll arguments can be named, in that case contrasts is optional\n\nWorks best with MultiSubjectDesign\n\nMixedModelComponent(;\n basis=hanning(40),\n formula=@formula(0~1+cond+(1+cond|subject)),\n β = [1.,2.],\n σs= Dict(:subject=>[0.5,0.4]),\n contrasts=Dict(:cond=>EffectsCoding())\n)\n\n\n\n\n\n\n","category":"type"},{"location":"api/#UnfoldSim.MultiSubjectDesign","page":"DocStrings","title":"UnfoldSim.MultiSubjectDesign","text":"n_subjects::Int -> number of subjects\nn_items::Int -> number of items (sometimes ≈trials)\nsubjects_between = nothing -> effects between subjects, e.g. young vs old \nitems_between = nothing -> effects between items, e.g. natural vs artificial images, but shown to all subjects\nboth_within = nothing\t-> effects completly crossed\ntableModifyFun = x->x; # can be used to sort, or x->shuffle(MersenneTwister(42),x) - be sure to fix/update the rng accordingly!!\n\ntipp: check the resulting dataframe using generate(design)\n\n# declaring same condition both sub-between and item-between results in a full between subject/item design\ndesign = MultiSubjectDesignjectDesign(;\n n_items=10,\n\t\tn_subjects = 30,\n subjects_between=Dict(:cond=>[\"levelA\",\"levelB\"]),\n\t\titems_between =Dict(:cond=>[\"levelA\",\"levelB\"]),\n );\n\n\n\n\n\n","category":"type"},{"location":"api/#UnfoldSim.MultichannelComponent","page":"DocStrings","title":"UnfoldSim.MultichannelComponent","text":"Wrapper for an AbstractComponent to project it to multiple target-channels via projection. optional adds noise to the source prior to projection.\n\n\n\n\n\n","category":"type"},{"location":"api/#UnfoldSim.RepeatDesign","page":"DocStrings","title":"UnfoldSim.RepeatDesign","text":"repeat a design DataFrame multiple times to mimick repeatedly recorded trials\n\ndesignOnce = MultiSubjectDesign(;\n n_items=2,\n\t\tn_subjects = 2,\n subjects_between =Dict(:cond=>[\"levelA\",\"levelB\"]),\n\t\titems_between =Dict(:cond=>[\"levelA\",\"levelB\"]),\n );\n\ndesign = RepeatDesign(designOnce,4);\n\n\n\n\n\n","category":"type"},{"location":"api/#UnfoldSim.SingleSubjectDesign","page":"DocStrings","title":"UnfoldSim.SingleSubjectDesign","text":"conditions = Dict of conditions, e.g. Dict(:A=>[\"a_small\",\"a_big\"],:B=>[\"b_tiny\",\"b_large\"])\ntableModifyFun = x->x; # can be used to sort, or x->shuffle(MersenneTwister(42),x) - be sure to fix/update the rng accordingly!!\n\nNumber of trials / rows in generate(design) depend on the full factorial of your conditions.\n\nTo increase the number of repetitions simply use RepeatDesign(SingleSubjectDesign(...),5)\n\ntipp: check the resulting dataframe using generate(design)\n\n\n\n\n\n","category":"type"},{"location":"api/#Base.size-Tuple{MultiSubjectDesign}","page":"DocStrings","title":"Base.size","text":"Returns dimension of experiment design\n\n\n\n\n\n","category":"method"},{"location":"api/#DSP.Windows.hanning-Tuple{Any, Any, Any}","page":"DocStrings","title":"DSP.Windows.hanning","text":"generate a hanning window\n\nduration: in s offset: in s, defines hanning peak sfreq: sampling rate in Hz\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.adderp!-Tuple{Any, Vector, Vararg{Any, 4}}","page":"DocStrings","title":"UnfoldSim.adderp!","text":"Helper function to add inplace the erps to the EEG, but for both 2D (1 channel) and 3D (X channel case)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.closest_src-Tuple{AbstractVector{<:AbstractVector}, Any}","page":"DocStrings","title":"UnfoldSim.closest_src","text":"closest_src(coords_list::AbstractVector{<:AbstractVector}, pos)\nclosest_src(coords::Vector{<:Real}, pos)\n\nTakes an array of 'm' target coordinate vector (size 3) (or vector of vectors) and a matrix (n-by-3) of all available positions, and returns an array of size 'm' containing the indices of the respective items in 'pos' that are nearest to each of the target coordinates.\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.closest_src-Tuple{Hartmut, String}","page":"DocStrings","title":"UnfoldSim.closest_src","text":"closest_src(head::Hartmut,label::String)\n\nReturns src-ix of the Headmodel Hartmut which is closest to the average of the label.\n\nimportant: Important\nWe use the average in eucledean space, but the cortex is a curved surface. In most cases they will not overlap. Ideally we would calculate the average on the surface, but this is a bit more complex to do (you'd need to calculate the vertices etc.)\n\nhartmut = headmodel()\npos = closest_src(hartmut=>\"Left Middle Temporal Gyrus, posterior division\")\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.convert-NTuple{4, Any}","page":"DocStrings","title":"UnfoldSim.convert","text":"Function to convert output similar to unfold (data, evts)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.gen_noise-Tuple{Any, UnfoldSim.RealisticNoise, Int64}","page":"DocStrings","title":"UnfoldSim.gen_noise","text":"gen_noise(t::RealisticNoise, n::Int)\n\nGenerate noise of a given type t and length n\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.gen_noise-Tuple{Any, Union{PinkNoise, RedNoise}, Int64}","page":"DocStrings","title":"UnfoldSim.gen_noise","text":"gen_noise(t::Union{PinkNoise, RedNoise}, n::Int)\n\nGenerate noise of a given type t and length n\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.gen_noise-Tuple{Any, WhiteNoise, Int64}","page":"DocStrings","title":"UnfoldSim.gen_noise","text":"gen_noise(t::WhiteNoise, n::Int)\n\nGenerate noise of a given type t and length n\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.generate-Tuple{MultiSubjectDesign}","page":"DocStrings","title":"UnfoldSim.generate","text":"Generates full factorial Dataframe according to MixedModelsSim.jl 's simdatcrossed function Note: nitems = you can think of it as trials or better, as stimuli\n\nNote: No condition can be named dv which is used internally in MixedModelsSim / MixedModels as a dummy left-side\n\nAfterwards applies expdesign.tableModifyFun. Could be used to duplicate trials, sort, subselect etc.\n\nFinally it sorts by :subject\n\njulia> d = MultiSubjectDesign(;nsubjects = 10,nitems=20,both_within= Dict(:A=>nlevels(5),:B=>nlevels(2))) julia> generate(d)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.generate-Tuple{SingleSubjectDesign}","page":"DocStrings","title":"UnfoldSim.generate","text":"Generates full-factorial DataFrame of expdesign.conditions\n\nAfterwards applies expdesign.tableModifyFun.\n\njulia> d = SingleSubjectDesign(;conditions= Dict(:A=>nlevels(5),:B=>nlevels(2))) julia> generate(d)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.hartmut_citation-Tuple{}","page":"DocStrings","title":"UnfoldSim.hartmut_citation","text":"Returns citation-string for HArtMuT\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.headmodel-Tuple{}","page":"DocStrings","title":"UnfoldSim.headmodel","text":"Load a headmodel, using Artifacts.jl automatically downloads the required files\n\nCurrently only type=\"hartmut\" is implemented\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.hrf-Tuple{}","page":"DocStrings","title":"UnfoldSim.hrf","text":"Generate a HRF kernel. \n\nTR = 1/sfreq default parameters taken from SPM\n\nCode adapted from Unfold.jl\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.leadfield-Tuple{Hartmut}","page":"DocStrings","title":"UnfoldSim.leadfield","text":"Returns the leadfield\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.magnitude-Tuple{AbstractHeadmodel}","page":"DocStrings","title":"UnfoldSim.magnitude","text":"Extracts magnitude of the orientation-including leadfield.\n\nBy default uses the orientation specified in the headmodel\n\nFallback: along the third dimension using norm - the maximal projection\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.magnitude-Tuple{Hartmut}","page":"DocStrings","title":"UnfoldSim.magnitude","text":"Extract magnitude of 3-orientation-leadfield, type (default: \"perpendicular\") => uses the provided source-point orientations - otherwise falls back to norm\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.n_channels-Tuple{AbstractComponent}","page":"DocStrings","title":"UnfoldSim.n_channels","text":"Returns the number of channels. By default = 1\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.n_channels-Tuple{MultichannelComponent}","page":"DocStrings","title":"UnfoldSim.n_channels","text":"for MultichannelComponent returns the length of the projection vector\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.padarray-Tuple{Vector, Tuple, Any}","page":"DocStrings","title":"UnfoldSim.padarray","text":"Pads array with specified value, length padarray(arr, len, val)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.predef_2x2-Tuple{Random.AbstractRNG}","page":"DocStrings","title":"UnfoldSim.predef_2x2","text":"todo\n\nCareful if you modify nitems with nsubjects = 1, n_items has to be a multiple of 4 (or your equivalent conditions factorial, e.g. all combinations length)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.predef_eeg-Tuple{Any}","page":"DocStrings","title":"UnfoldSim.predef_eeg","text":"Generates a P1/N1/P3 complex. predefeeg(;kwargs...) predefeeg(rng;kwargs...) predefeeg(rng,nsubjects;kwargs...)\n\nIn case of n_subjects, MixedModelComponents are generated\n\nDefault params: n_repeats=100 tableModifyFun = x->shuffle(deepcopy(rng),x # random trial order conditions = Dict(...),\n\ncomponent / signal\n\nsfreq = 100, p1 = (p100(;sfreq=sfreq), @formula(0~1),[5],Dict()), # P1 amp 5, no effects n1 = (n170(;sfreq=sfreq), @formula(0~1+condition),[5,-3],Dict()), # N1 amp 5, dummycoded condition effect (levels \"car\", \"face\") of -3 p3 = (p300(;sfreq=sfreq), @formula(0~1+continuous),[5,1],Dict()), # P3 amp 5, continuous effect range [-5,5] with slope 1\n\nnoise\n\nnoiselevel = 0.2, noise = PinkNoise(;noiselevel=noiselevel),\n\nonset\n\noverlap = (0.5,0.2), # offset + width/length of Uniform noise. put offset to 1 for no overlap. put width to 0 for no jitter onset=UniformOnset(;offset=sfreq0.5overlap[1],width=sfreq0.5overlap[2]), \n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.predef_eeg-Tuple{Random.AbstractRNG, Any}","page":"DocStrings","title":"UnfoldSim.predef_eeg","text":"predefeeg(rng,nsubjects;kwargs...) Runs predefeeg(rng;kwargs...) nsubject times and concatenates the results.\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.simulate-Tuple{Any, AbstractComponent, Simulation}","page":"DocStrings","title":"UnfoldSim.simulate","text":"by default call simulate with ::Abstractcomponent,::AbstractDesign`, but allow for custom types\n\nmaking use of other information in simulation\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.simulate-Tuple{Any, LinearModelComponent, AbstractDesign}","page":"DocStrings","title":"UnfoldSim.simulate","text":"simulate a linearModel\n\njulia> c = UnfoldSim.LinearModelComponent([0,1,1,0],@formula(0~1+cond),[1,2],Dict()) julia> design = MultiSubjectDesign(;nsubjects=2,nitems=50,item_between=(;:cond=>[\"A\",\"B\"])) julia> simulate(StableRNG(1),c,design)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.simulate-Tuple{Any, MixedModelComponent, AbstractDesign}","page":"DocStrings","title":"UnfoldSim.simulate","text":"simulate MixedModelComponent\n\njulia> design = MultiSubjectDesign(;nsubjects=2,nitems=50,item_between=(;:cond=>[\"A\",\"B\"])) julia> c = UnfoldSim.MixedModelComponent([0.,1,1,0],@formula(0~1+cond+(1|subject)),[1,2],Dict(:subject=>[2],),Dict()) julia> simulate(StableRNG(1),c,design)\n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.simulate-Tuple{Any, Vector{<:AbstractComponent}, Simulation}","page":"DocStrings","title":"UnfoldSim.simulate","text":"Simulates erp data given the specified parameters \n\n\n\n\n\n","category":"method"},{"location":"api/#UnfoldSim.weight_σs-Tuple{Dict, Float64, Float64}","page":"DocStrings","title":"UnfoldSim.weight_σs","text":"Weights a σs Dict for MixedModels.jl by a Float64\n\nFinally sales it by σ_lmm, as a trick to simulate noise-free LMMs\n\nI anticipate a function function weight_σs(σs::Dict,b_σs::Dict,σ_lmm::Float64) where each σs entry can be weighted individually\n\n\n\n\n\n","category":"method"},{"location":"generated/reference/noisetypes/","page":"NoiseTypes","title":"NoiseTypes","text":"EditURL = \"../../../literate/reference/noisetypes.jl\"","category":"page"},{"location":"generated/reference/noisetypes/","page":"NoiseTypes","title":"NoiseTypes","text":"using UnfoldSim\nusing CairoMakie\nusing DSP\nusing StableRNGs\nimport StatsBase.autocor","category":"page"},{"location":"generated/reference/noisetypes/#What's-the-noise?","page":"NoiseTypes","title":"What's the noise?","text":"","category":"section"},{"location":"generated/reference/noisetypes/","page":"NoiseTypes","title":"NoiseTypes","text":"There are several noise-types directly implemented. Here is a comparison:","category":"page"},{"location":"generated/reference/noisetypes/","page":"NoiseTypes","title":"NoiseTypes","text":"f = Figure()\nax_sig = f[1,1:2] = Axis(f;title=\"1.000 samples of noise\")\nax_spec = f[2,1] = Axis(f;title=\"Welch Periodigram\")\nax_auto = f[2,2] = Axis(f;title=\"Autocorrelogram (every 10th lag)\")\nfor n = [PinkNoise RedNoise WhiteNoise NoNoise ExponentialNoise]\n\n # generate\n noisevec = gen_noise(StableRNG(1),n(),10000)\n\n # plot 1000 samples\n lines!(ax_sig,noisevec[1:1000];label=string(n))\n\n # calc spectrum\n perio = welch_pgram(noisevec)\n\n # plot spectrum\n lines!(ax_spec,freq(perio),log10.(power(perio)))\n\n lags = 0:10:500\n autocor_vec = autocor(noisevec,lags)\n lines!(ax_auto,lags,autocor_vec)\n\nend\nf[1:2,3] = Legend(f,ax_sig,\"NoiseType\")\nf","category":"page"},{"location":"generated/reference/noisetypes/","page":"NoiseTypes","title":"NoiseTypes","text":"hint: Hint\nWe recommend for smaller signals the ExponentialNoise, maybe with a removed DC offset or a HighPass filter. For long signals, this Noise requires lots of memory though. Maybe Pinknoise is a better choice then.","category":"page"},{"location":"generated/reference/noisetypes/","page":"NoiseTypes","title":"NoiseTypes","text":"","category":"page"},{"location":"generated/reference/noisetypes/","page":"NoiseTypes","title":"NoiseTypes","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"EditURL = \"../../../literate/reference/basistypes.jl\"","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"using UnfoldSim\nusing CairoMakie\nusing DSP\nusing StableRNGs","category":"page"},{"location":"generated/reference/basistypes/#Basistypes","page":"ComponentBasisTypes","title":"Basistypes","text":"","category":"section"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"There are several basis types directly implemented. They can be easily used for the components.","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"note: Note\nYou can use any arbitrary shape defined by yourself! We often make use of hanning(50) from the DSP.jl package.","category":"page"},{"location":"generated/reference/basistypes/#EEG","page":"ComponentBasisTypes","title":"EEG","text":"","category":"section"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"By default, the EEG bases assume a sampling rate of 100, which can easily be changed by e.g. p100(;sfreq=300)","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"f = Figure()\nax = f[1,1] = Axis(f)\nfor b in [p100,n170,p300,n400]\n lines!(ax,b(),label=string(b))\n scatter!(ax,b(),label=string(b))\nend\naxislegend(ax,merge=true)\nf","category":"page"},{"location":"generated/reference/basistypes/#fMRI","page":"ComponentBasisTypes","title":"fMRI","text":"","category":"section"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"default hrf TR is 1. Get to know all your favourite shapes!","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"##--\nf = Figure()\nplotConfig = (:peak=>1:3:10,\n :psUnder=>10:5:30,\n :amplitude=>2:5,\n :shift=>0:3:10,\n :peak_width => 0.1:0.5:1.5,\n :psUnder_width => 0.1:0.5:1.5,\n )\n\nfor (ix,pl) = enumerate(plotConfig)\n col = (ix-1)%3 +1\n row = Int(ceil(ix/3))\n\n ax = f[row,col] = Axis(f)\n cfg = collect(pl)\n for k = cfg[2]\n lines!(ax,UnfoldSim.hrf(;TR=0.1,(cfg[1]=>k,)...),label=string(k))\n end\n\n axislegend(string(cfg[1]);merge=true,)\nend\nf","category":"page"},{"location":"generated/reference/basistypes/#Pupil","page":"ComponentBasisTypes","title":"Pupil","text":"","category":"section"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"We use the simplified PuRF from Hoeks & Levelt, 1993. Note that https://www.science.org/doi/10.1126/sciadv.abi9979 show some evidence in their supplementary material, that the convolution model is not fully applicable.","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"f = Figure()\nplotConfig = (:n=>5:3:15,\n :tmax=>0.5:0.2:1.1,\n )\n\nfor (ix,pl) = enumerate(plotConfig)\n ax = f[1,ix] = Axis(f)\n cfg = collect(pl)\n for k = cfg[2]\n lines!(ax,UnfoldSim.PuRF(;(cfg[1]=>k,)...),label=string(k))\n end\n\n axislegend(string(cfg[1]);merge=true,)\nend\nf","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"","category":"page"},{"location":"generated/reference/basistypes/","page":"ComponentBasisTypes","title":"ComponentBasisTypes","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"EditURL = \"../../../literate/tutorials/poweranalysis.jl\"","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"using UnfoldSim\nusing Unfold\nusing Statistics\nusing HypothesisTests\nusing DataFrames\nusing Random","category":"page"},{"location":"generated/tutorials/poweranalysis/#Simple-Poweranalysis-Script","page":"Poweranalysis","title":"Simple Poweranalysis Script","text":"","category":"section"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"For a power analysis, we will repeatedly simulate data, and check whether we can find a significant effect.","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"We perform the power analysis on epoched data.","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"pvals = fill(NaN,100)\n@time for seed = eachindex(pvals)\n # Simulate data of 30 subjects\n data,evts = UnfoldSim.predef_2x2(MersenneTwister(seed);\n n_subjects=20, ## 30 subjects\n overlap=(1,0), ## deactivate overlap\n noiselevel=10, ## add more noise to make it more challenging\n return_epoched=true, ## saves us the epoching step\n )\n\n\n # take the mean over a pre-specified timewindow\n evts.y = dropdims(mean(data[40:60,:],dims=1),dims=(1))\n\n # extract the two levels of condition A\n evts_reduced = combine(groupby(evts,[:subject,:A]),:y=>mean)\n y_big = evts_reduced[evts_reduced.A .==\"a_big\",:y_mean]\n y_small = evts_reduced[evts_reduced.A .==\"a_small\",:y_mean]\n\n # calculate a one-sided t-test\n pvals[seed] = pvalue(OneSampleTTest(y_big,y_small))\nend","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"let's calculate the power","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"power = mean(pvals .<0.05)*100","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"","category":"page"},{"location":"generated/tutorials/poweranalysis/","page":"Poweranalysis","title":"Poweranalysis","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"EditURL = \"../../../literate/reference/overview.jl\"","category":"page"},{"location":"generated/reference/overview/#Overview-of-functionality","page":"Toolbox Overview","title":"Overview of functionality","text":"","category":"section"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"UnfoldSim has many modules, here we try to collect them to provide you with an overview.","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"using UnfoldSim\nusing InteractiveUtils","category":"page"},{"location":"generated/reference/overview/#Design","page":"Toolbox Overview","title":"Design","text":"","category":"section"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"Designs define the experimental design. They can be nested, e.g. RepeatDesign(SingleSubjectDesign,10) would repeat the generated design-dataframe 10x.","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"subtypes(AbstractDesign)","category":"page"},{"location":"generated/reference/overview/#Component","page":"Toolbox Overview","title":"Component","text":"","category":"section"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"Components define a signal. Some components can be nested, e.g. LinearModelComponent|>MultichannelComponent, see the multi-channel tutorial for more information.","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"subtypes(AbstractComponent)","category":"page"},{"location":"generated/reference/overview/#Onsets","page":"Toolbox Overview","title":"Onsets","text":"","category":"section"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"Onsets define the distance between events in the continuous signal.","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"subtypes(AbstractOnset)","category":"page"},{"location":"generated/reference/overview/#Noise","page":"Toolbox Overview","title":"Noise","text":"","category":"section"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"Choose the noise you need!","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"subtypes(AbstractNoise)","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"","category":"page"},{"location":"generated/reference/overview/","page":"Toolbox Overview","title":"Toolbox Overview","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"EditURL = \"../../../literate/tutorials/quickstart.jl\"","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"using UnfoldSim\nusing Random\nusing CairoMakie","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"tip: Tip\nUse subtypes(AbstractNoise) (or subtypes(AbstractComponent) etc.) to find already implemented building blocks.","category":"page"},{"location":"generated/tutorials/quickstart/#\"Experimental\"-Design","page":"Quickstart","title":"\"Experimental\" Design","text":"","category":"section"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"Define a 1 x 2 design with 20 trials. That is, one condition (condaA) with two levels.","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"design = SingleSubjectDesign(;\n conditions=Dict(:condA=>[\"levelA\",\"levelB\"])\n ) |> x->RepeatDesign(x,10);\nnothing #hide","category":"page"},{"location":"generated/tutorials/quickstart/#Component-/-Signal","page":"Quickstart","title":"Component / Signal","text":"","category":"section"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"Define a simple component and ground truth simulation formula. Akin to ERP components, we call one simulation signal a component.","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"note: Note\nYou could easily specify multiple components by providing a vector of components, which are automatically added at the same onsets. This procedure simplifies to generate some response that is independent of simulated condition, whereas other depends on it.","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"signal = LinearModelComponent(;\n basis=[0,0,0,0.5,1,1,0.5,0,0],\n formula = @formula(0~1+condA),\n β = [1,0.5]\n );\nnothing #hide","category":"page"},{"location":"generated/tutorials/quickstart/#Onsets-and-Noise","page":"Quickstart","title":"Onsets and Noise","text":"","category":"section"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"We will start with a uniform (but overlapping, offset < length(signal.basis)) onset-distribution","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"onset = UniformOnset(;width=20,offset=4);\nnothing #hide","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"And we will use some noise","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"noise = PinkNoise(;noiselevel=0.2);\nnothing #hide","category":"page"},{"location":"generated/tutorials/quickstart/#Combine-and-Generate","page":"Quickstart","title":"Combine & Generate","text":"","category":"section"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"We will put it all together in one Simulation type","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"simulation = Simulation(design, signal, onset, noise);\nnothing #hide","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"finally, we will simulate some data","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"data,events = simulate(MersenneTwister(1),simulation);\nnothing #hide","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"Data is a n-sample Vector (but could be a Matrix for e.g. MultiSubjectDesign).","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"events is a DataFrame that contains a column latency with the onsets of events.","category":"page"},{"location":"generated/tutorials/quickstart/#Plot-them!","page":"Quickstart","title":"Plot them!","text":"","category":"section"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"lines(data;color=\"black\")\nvlines!(events.latency;color=[\"orange\",\"teal\"][1 .+ (events.condA.==\"levelB\")])\ncurrent_figure()","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"","category":"page"},{"location":"generated/tutorials/quickstart/","page":"Quickstart","title":"Quickstart","text":"This page was generated using Literate.jl.","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"EditURL = \"../../../literate/HowTo/repeatTrials.jl\"","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"using UnfoldSim","category":"page"},{"location":"generated/HowTo/repeatTrials/#Repeating-Design-entries","page":"Repeating Trials within a Design","title":"Repeating Design entries","text":"","category":"section"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"Sometimes we want to repeat a design, that is, have multiple trials with identical values, but it is not always straight forward to implement. For instance, there is no way to easily modify MultiSubjectDesign to have multiple identical subject/item combinations, without doing awkward repetitions of condition-levels or something.","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"If you struggle with this problem RepeatDesign is an easy tool for you:","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"designOnce = MultiSubjectDesign(;\n n_items=2,\n n_subjects = 2,\n subjects_between =Dict(:cond=>[\"levelA\",\"levelB\"]),\n items_between =Dict(:cond=>[\"levelA\",\"levelB\"]),\n);\n\ndesign = RepeatDesign(designOnce,4);\ngenerate(design)","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"As you can see, the design was simply repeated. As always, you can ignore the dv column, it is for internal consistency with MixedModelsSim.jl","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"note: Note\nIf you implemented your own AbstractDesign, you need to define the size function accordingly. E.g.: Base.size(design::RepeatDesign{SingleSubjectDesign}) = size(design.design).*design.repeat","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"","category":"page"},{"location":"generated/HowTo/repeatTrials/","page":"Repeating Trials within a Design","title":"Repeating Trials within a Design","text":"This page was generated using Literate.jl.","category":"page"},{"location":"","page":"Home","title":"Home","text":"CurrentModule = UnfoldSim","category":"page"},{"location":"#UnfoldSim","page":"Home","title":"UnfoldSim","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Documentation for UnfoldSim.","category":"page"},{"location":"#Start-simulating-timeseries","page":"Home","title":"Start simulating timeseries","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"We offer some predefined signals, check them out!","category":"page"},{"location":"","page":"Home","title":"Home","text":"For instance an P1/N170/P300 complex.","category":"page"},{"location":"","page":"Home","title":"Home","text":"using UnfoldSim\nusing CairoMakie\ndata,evts = UnfoldSim.predef_eeg(;n_repeats=1,noiselevel=0.8)\n\nlines(data;color=\"black\")\nvlines!(evts.latency;color=[\"orange\",\"teal\"][1 .+ (evts.condition .==\"car\")])\n\ncurrent_figure()","category":"page"},{"location":"#Or-simulate-epoched-data-directly","page":"Home","title":"Or simulate epoched data directly","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"\ndata,evts = UnfoldSim.predef_eeg(;n_repeats=20,noiselevel=0.8,return_epoched=true)\nheatmap(data[:,sortperm(evts,[:condition,:continuous])])\n","category":"page"}] }