From b0b4b8405ef6d868033eb53a78e85a082e279cab Mon Sep 17 00:00:00 2001 From: jschepers Date: Tue, 9 Jan 2024 10:45:55 +0000 Subject: [PATCH 001/113] Formatting --- src/UnfoldSim.jl | 124 +++++++++++++++++++++++------------------------ src/design.jl | 79 +++++++++++++++--------------- 2 files changed, 102 insertions(+), 101 deletions(-) diff --git a/src/UnfoldSim.jl b/src/UnfoldSim.jl index e8756fe8..7f165369 100644 --- a/src/UnfoldSim.jl +++ b/src/UnfoldSim.jl @@ -1,74 +1,74 @@ module UnfoldSim - using DSP - using Random - using DataFrames - using Distributions # for LogNormal Onset - using Parameters - using StatsModels - using MixedModels - using ImageFiltering # for Noise-filter (can be replaced maybe?) - using MixedModelsSim - using SignalAnalysis - using LinearAlgebra - using ToeplitzMatrices # for AR Expo. Noise "Circulant" - using StatsModels - using HDF5,Artifacts,FileIO +using DSP +using Random +using DataFrames +using Distributions # for LogNormal Onset +using Parameters +using StatsModels +using MixedModels +using ImageFiltering # for Noise-filter (can be replaced maybe?) +using MixedModelsSim +using SignalAnalysis +using LinearAlgebra +using ToeplitzMatrices # for AR Expo. Noise "Circulant" +using StatsModels +using HDF5, Artifacts, FileIO - using LinearAlgebra # headmodel +using LinearAlgebra # headmodel - import DSP.hanning - import Base.length - import Base.size - import Base.show - include("types.jl") - include("design.jl") - include("component.jl") - include("noise.jl") - include("simulation.jl") - include("onset.jl") - include("predefinedSimulations.jl") - include("headmodel.jl") - include("helper.jl") - include("bases.jl") +import DSP.hanning +import Base.length +import Base.size +import Base.show +include("types.jl") +include("design.jl") +include("component.jl") +include("noise.jl") +include("simulation.jl") +include("onset.jl") +include("predefinedSimulations.jl") +include("headmodel.jl") +include("helper.jl") +include("bases.jl") - export size,length - export AbstractComponent,AbstractNoise,AbstractOnset,AbstractDesign - # statsmodels re-export - export @formula,DummyCoding,EffectsCoding - # mixedModels re-export - export create_re - - # main types - export Simulation - - # component types - export MixedModelComponent,LinearModelComponent +export size, length +export AbstractComponent, AbstractNoise, AbstractOnset, AbstractDesign +# statsmodels re-export +export @formula, DummyCoding, EffectsCoding +# mixedModels re-export +export create_re - # export designs - export MultiSubjectDesign,SingleSubjectDesign, RepeatDesign +# main types +export Simulation - # noise functions - export PinkNoise,RedNoise,WhiteNoise,NoNoise,ExponentialNoise #,RealNoise (not implemented yet) - - # UnfoldSim functions - export simulate, gen_noise,generate - - # utilities - export padarray,convert +# component types +export MixedModelComponent, LinearModelComponent - # export Offsets - export UniformOnset,LogNormalOnset - - # re-export StatsModels - export DummyCoding,EffectsCoding +# export designs +export MultiSubjectDesign, SingleSubjectDesign, RepeatDesign - # export bases - export p100,n170,p300,n400,hrf +# noise functions +export PinkNoise, RedNoise, WhiteNoise, NoNoise, ExponentialNoise #,RealNoise (not implemented yet) - # headmodel - export AbstractHeadmodel,Hartmut,headmodel,leadfield,orientation,magnitude +# UnfoldSim functions +export simulate, gen_noise, generate - # multichannel - export MultichannelComponent +# utilities +export padarray, convert + +# export Offsets +export UniformOnset, LogNormalOnset, NoOnset + +# re-export StatsModels +export DummyCoding, EffectsCoding + +# export bases +export p100, n170, p300, n400, hrf + +# headmodel +export AbstractHeadmodel, Hartmut, headmodel, leadfield, orientation, magnitude + +# multichannel +export MultichannelComponent end diff --git a/src/design.jl b/src/design.jl index a22c05f7..67730d43 100644 --- a/src/design.jl +++ b/src/design.jl @@ -11,20 +11,20 @@ tipp: check the resulting dataframe using `generate(design)` ```julia # declaring same condition both sub-between and item-between results in a full between subject/item design design = MultiSubjectDesignjectDesign(; - n_items=10, + n_items=10, n_subjects = 30, - subjects_between=Dict(:cond=>["levelA","levelB"]), + subjects_between=Dict(:cond=>["levelA","levelB"]), items_between =Dict(:cond=>["levelA","levelB"]), - ); + ); ``` """ @with_kw struct MultiSubjectDesign <: AbstractDesign - n_subjects::Int - n_items::Int - subjects_between = nothing - items_between = nothing - both_within = nothing - tableModifyFun = x->x; # can be used to sort, or x->shuffle(rng,x) + n_subjects::Int + n_items::Int + subjects_between = nothing + items_between = nothing + both_within = nothing + tableModifyFun = x -> x # can be used to sort, or x->shuffle(rng,x) end @@ -40,7 +40,7 @@ tipp: check the resulting dataframe using `generate(design)` """ @with_kw struct SingleSubjectDesign <: AbstractDesign conditions = nothing - tableModifyFun = x->x; + tableModifyFun = x -> x end @@ -60,8 +60,8 @@ function generate(expdesign::SingleSubjectDesign) # we get a Dict(:A=>["1","2"],:B=>["3","4"]), but needed a list # of named tuples for MixedModelsSim.factorproduct function. evts = factorproduct( - ((;k=>v) for (k,v) in pairs(expdesign.conditions))...) |> DataFrame - + ((; k => v) for (k, v) in pairs(expdesign.conditions))...) |> DataFrame + # by default does nothing return expdesign.tableModifyFun(evts) end @@ -81,31 +81,32 @@ julia> generate(d) """ function generate(expdesign::MultiSubjectDesign) #generate(expdesign::AbstractDesign) = generate(MersenneTwister(1),expdesign) - + # check that :dv is not in any condition - allconditions = [expdesign.subjects_between,expdesign.items_between,expdesign.both_within] - @assert :dv ∉ keys(merge(allconditions[.!isnothing.(allconditions)]...)) "due to technical limitations in MixedModelsSim.jl, `:dv` cannot be used as a factorname" + allconditions = [expdesign.subjects_between, expdesign.items_between, expdesign.both_within] + @assert all(isnothing.(allconditions)) || :dv ∉ keys(merge(allconditions[.!isnothing.(allconditions)]...)) "due to technical limitations in MixedModelsSim.jl, `:dv` cannot be used as a factorname" + + - data = DataFrame( MixedModelsSim.simdat_crossed( - expdesign.n_subjects, - expdesign.n_items, - subj_btwn=expdesign.subjects_between, - item_btwn=expdesign.items_between, - both_win=expdesign.both_within - ) + expdesign.n_subjects, + expdesign.n_items, + subj_btwn = expdesign.subjects_between, + item_btwn = expdesign.items_between, + both_win = expdesign.both_within, + ), ) - rename!(data,:subj => :subject) - select!(data,Not(:dv)) # remove the default column from MixedModelsSim.jl - we don't need it in UnfoldSim.jl + rename!(data, :subj => :subject) + select!(data, Not(:dv)) # remove the default column from MixedModelsSim.jl - we don't need it in UnfoldSim.jl # by default does nothing data = expdesign.tableModifyFun(data) - + # sort by subject - data = sort!(data,(order(:subject))) + data = sort!(data, (order(:subject))) return data - + end @@ -121,11 +122,11 @@ repeat a design DataFrame multiple times to mimick repeatedly recorded trials ```julia designOnce = MultiSubjectDesign(; - n_items=2, + n_items=2, n_subjects = 2, - subjects_between =Dict(:cond=>["levelA","levelB"]), + subjects_between =Dict(:cond=>["levelA","levelB"]), items_between =Dict(:cond=>["levelA","levelB"]), - ); + ); design = RepeatDesign(designOnce,4); ``` @@ -135,13 +136,13 @@ design = RepeatDesign(designOnce,4); repeat::Int = 1 end -function UnfoldSim.generate(design::RepeatDesign) - df = map(x->generate(design.design),1:design.repeat) |>x->vcat(x...) - if isa(design.design,MultiSubjectDesign) - sort!(df,[:subject]) - end - return df - +function UnfoldSim.generate(design::RepeatDesign) + df = map(x -> generate(design.design), 1:design.repeat) |> x -> vcat(x...) + if isa(design.design, MultiSubjectDesign) + sort!(df, [:subject]) + end + return df + end -Base.size(design::RepeatDesign{MultiSubjectDesign}) = size(design.design).*(design.repeat,1) -Base.size(design::RepeatDesign{SingleSubjectDesign}) = size(design.design).*design.repeat +Base.size(design::RepeatDesign{MultiSubjectDesign}) = size(design.design) .* (design.repeat, 1) +Base.size(design::RepeatDesign{SingleSubjectDesign}) = size(design.design) .* design.repeat From 0cf94ad3e73346339cd1baab9588300df76e541e Mon Sep 17 00:00:00 2001 From: jschepers Date: Wed, 10 Jan 2024 14:33:16 +0000 Subject: [PATCH 002/113] Formatting --- test/runtests.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 723fc891..541b5a48 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,10 +2,10 @@ using UnfoldSim include("setup.jl") @testset "UnfoldSim.jl" begin - include("component.jl") - include("design.jl") - include("noise.jl") - include("onset.jl") - include("simulation.jl") - include("helper.jl") + include("component.jl") + include("design.jl") + include("noise.jl") + include("onset.jl") + include("simulation.jl") + include("helper.jl") end From e24e95817d954ebaa37da831ba8d137baedb5b50 Mon Sep 17 00:00:00 2001 From: jschepers Date: Wed, 10 Jan 2024 14:34:08 +0000 Subject: [PATCH 003/113] adapted Unfold epoch function for Unfoldsim --- src/helper.jl | 122 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 93 insertions(+), 29 deletions(-) diff --git a/src/helper.jl b/src/helper.jl index bab57049..c0169031 100755 --- a/src/helper.jl +++ b/src/helper.jl @@ -2,10 +2,10 @@ Pads array with specified value, length padarray(arr, len, val) """ -padarray(arr::Vector,len::Tuple,val) = padarray(padarray(arr,len[1],val),len[2],val) +padarray(arr::Vector, len::Tuple, val) = padarray(padarray(arr, len[1], val), len[2], val) function padarray(arr::Vector, len::Int, val) - pad = fill(val, abs(len)) - arr = len > 0 ? vcat(arr, pad) : vcat(pad, arr) + pad = fill(val, abs(len)) + arr = len > 0 ? vcat(arr, pad) : vcat(pad, arr) return arr end @@ -14,30 +14,30 @@ end """ Function to convert output similar to unfold (data, evts) """ -function convert(eeg, onsets, design,n_ch,;reshape=true) +function convert(eeg, onsets, design, n_ch, ; reshape = true) evt = UnfoldSim.generate(design) @debug size(eeg) if reshape - n_subj = length(size(design))==1 ? 1 : size(design)[2] + n_subj = length(size(design)) == 1 ? 1 : size(design)[2] if n_ch == 1 - data = eeg[:,] - - evt.latency = (onsets' .+ range(0,size(eeg,2)-1).*size(eeg,1) )'[:,] + data = eeg[:,] + + evt.latency = (onsets' .+ range(0, size(eeg, 2) - 1) .* size(eeg, 1))'[:,] elseif n_subj == 1 data = eeg @debug size(onsets) evt.latency = onsets else # multi subject + multi channel - data = eeg[:,:,] - evt.latency = (onsets' .+ range(0,size(eeg,3)-1).*size(eeg,2) )'[:,] + data = eeg[:, :] + evt.latency = (onsets' .+ range(0, size(eeg, 3) - 1) .* size(eeg, 2))'[:,] end else data = eeg end - return data,evt - + return data, evt + end @@ -47,21 +47,21 @@ end 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. """ -closest_src(coords_list::AbstractVector{<:AbstractVector}, pos) = closest_src.(coords_list, Ref(pos)) - -function closest_src(coords::Vector{<:Real}, pos) - s = size(pos); - dist = zeros(s[1]); - diff = zeros(s[2]); - for i=1:s[1] - for j=1:s[2] - diff[j] = pos[i,j] - coords[j]; - end - dist[i] = norm(diff); +closest_src(coords_list::AbstractVector{<:AbstractVector}, pos) = closest_src.(coords_list, Ref(pos)) + +function closest_src(coords::Vector{<:Real}, pos) + s = size(pos) + dist = zeros(s[1]) + diff = zeros(s[2]) + for i ∈ 1:s[1] + for j ∈ 1:s[2] + diff[j] = pos[i, j] - coords[j] end + dist[i] = norm(diff) + end return findmin(dist)[2] - - + + end """ @@ -76,12 +76,76 @@ hartmut = headmodel() pos = closest_src(hartmut=>"Left Middle Temporal Gyrus, posterior division") ``` """ -function closest_src(head::Hartmut,label::String) +function closest_src(head::Hartmut, label::String) pos = head.cortical["pos"] ix = findall(head.cortical["label"] .== label) - @assert sum(ix)>0 """could not find label $label in hartmut.cortical["label"] - try unique(hartmut.cortical["label"]) for a list""" + @assert sum(ix) > 0 """could not find label $label in hartmut.cortical["label"] - try unique(hartmut.cortical["label"]) for a list""" - ix = UnfoldSim.closest_src(mean(pos[ix,:],dims=1)[1,:],pos) + ix = UnfoldSim.closest_src(mean(pos[ix, :], dims = 1)[1, :], pos) return ix -end \ No newline at end of file +end + + +# Adapted from Unfold.jl: https://github.com/unfoldtoolbox/Unfold.jl/blob/b3a21c2bb7e93d2f45ec64b0197f4663a6d7939a/src/utilities.jl#L40 + +# One channel case +function epoch(data::AbstractVector, args...; kwargs...) + data_r = reshape(data, (1, :)) + ep = epoch(data_r, args...; kwargs...) + return dropdims(ep; dims = 1) +end + +function epoch( + data::AbstractArray{T, 2}, + events, + τ::Tuple{Number, Number}, + sfreq; + eventtime::Symbol = :latency, +) where {T <: Union{Missing, Number}} + # data: channels x times + + # partial taken from EEG.jl + + numEpochs = size(events, 1) + + times = range(τ[1], stop = τ[2], step = 1 ./ sfreq) + lenEpochs = length(times) + numChans = size(data, 1) + epochs = Array{T}( + undef, + Int(numChans), + Int(lenEpochs), + Int(numEpochs), + ) + + + # User feedback + @debug "Creating epochs: $numChans x $lenEpochs x $numEpochs" + + for si ∈ 1:size(events, 1) + # d_start and d_end are the start and end of the epoch (in samples) in the data + d_start = Int(round(events[si, eventtime]) + times[1] .* sfreq) + d_end = Int(round(events[si, eventtime]) + times[end] .* sfreq) + + # e_start and e_end are the start and end within the epoch (in samples) + e_start = 1 + e_end = lenEpochs + #println("d: $(size(data)),e: $(size(epochs)) | $d_start,$d_end,$e_start,$e_end | $(events[si,eventtime])") + + # Case that the start of the epoch is before the start of the data/recording (e.g. if the start is before i.e. negative relative to the event) + if d_start < 1 + #@warn "d_start $d_start" + e_start = e_start + (-d_start + 1) + d_start = 1 + end + # Case that the end of the epoch is after the end of the data/recording + if d_end > size(data, 2) + e_end = e_end - (d_end - size(data, 2)) + d_end = size(data, 2) + end + #println("d: $(size(data)),e: $(size(epochs)) | $d_start,$d_end,$e_start,$e_end | $(events[si,eventtime])") + epochs[:, e_start:e_end, si] = data[:, d_start:d_end] + end + return epochs +end From 2674d4ee2b42e444dd5af5feca8b11d8a789f5db Mon Sep 17 00:00:00 2001 From: jschepers Date: Wed, 10 Jan 2024 14:35:17 +0000 Subject: [PATCH 004/113] adapted simulate function --- src/onset.jl | 51 +++++++------- src/simulation.jl | 166 ++++++++++++++++++++++++++++------------------ 2 files changed, 127 insertions(+), 90 deletions(-) diff --git a/src/onset.jl b/src/onset.jl index 64061975..55d2befe 100644 --- a/src/onset.jl +++ b/src/onset.jl @@ -2,41 +2,44 @@ # Types #--------------- -@with_kw struct UniformOnset<:AbstractOnset - width=50 # how many samples jitter? - offset=0 # minimal offset? +@with_kw struct UniformOnset <: AbstractOnset + width = 50 # how many samples jitter? + offset = 0 # minimal offset? end -@with_kw struct LogNormalOnset<:AbstractOnset - μ # mean - σ # variance - offset = 0 # additional offset - truncate_upper = nothing # truncate at some sample? +@with_kw struct LogNormalOnset <: AbstractOnset + μ::Any # mean + σ::Any # variance + offset = 0 # additional offset + truncate_upper = nothing # truncate at some sample? end +# In the case that the user directly wants the erps/epoched data (no overlap) +struct NoOnset <: AbstractOnset end + #------------- -function rand_onsets(rng,onset::UniformOnset,design::AbstractDesign) - return Int.(round.(rand(deepcopy(rng), onset.offset:(onset.offset + onset.width), size(design)))) +function rand_onsets(rng, onset::UniformOnset, design::AbstractDesign) + return Int.(round.(rand(deepcopy(rng), onset.offset:(onset.offset+onset.width), size(design)))) end -function rand_onsets(rng,onset::LogNormalOnset,design::AbstractDesign) - s = size(design) - fun = LogNormal(onset.μ,onset.σ) - if !isnothing(onset.truncate_upper) - fun = truncated(fun;upper=onset.truncate_upper) - end - return Int.(round.(onset.offset .+ rand(deepcopy(rng), fun, s))) +function rand_onsets(rng, onset::LogNormalOnset, design::AbstractDesign) + s = size(design) + fun = LogNormal(onset.μ, onset.σ) + if !isnothing(onset.truncate_upper) + fun = truncated(fun; upper = onset.truncate_upper) + end + return Int.(round.(onset.offset .+ rand(deepcopy(rng), fun, s))) end # main call from `simulation` -function generate(rng,onset::AbstractOnset,simulation::Simulation) - +function generate(rng, onset::AbstractOnset, simulation::Simulation) + # sample different onsets - onsets = rand_onsets(rng,onset,simulation.design) - - # accumulate them - onsets_accum = accumulate(+, onsets, dims=1) - + onsets = rand_onsets(rng, onset, simulation.design) + + # accumulate them + onsets_accum = accumulate(+, onsets, dims = 1) + return onsets_accum end diff --git a/src/simulation.jl b/src/simulation.jl index f0d0ff4e..057a5c6c 100755 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -1,9 +1,9 @@ # helper to move input ::Component to ::Vector{Component} -Simulation(design::AbstractDesign,component::AbstractComponent,onset::AbstractOnset,noisetype::AbstractNoise) = Simulation(design,[component],onset,noisetype) +Simulation(design::AbstractDesign, component::AbstractComponent, onset::AbstractOnset, noisetype::AbstractNoise) = Simulation(design, [component], onset, noisetype) # by default no noise -Simulation(design::AbstractDesign,component,onset::AbstractOnset) = Simulation(design,component,onset,NoNoise()) +# Simulation(design::AbstractDesign,component,onset::AbstractOnset) = Simulation(design,component,onset,NoNoise()) """ Simulate eeg data given a simulation design, effect sizes and variances @@ -11,79 +11,113 @@ Simulate eeg data given a simulation design, effect sizes and variances make use of `return_epoched=true` to skip the Onset-calculation + conversion to continuous data and get the epoched data directly """ -simulate(rng, design::AbstractDesign, signal, onset::AbstractOnset, noise::AbstractNoise=NoNoise(); kwargs...) = simulate(rng, Simulation(design, signal, onset, noise); kwargs...) +simulate(rng, design::AbstractDesign, signal, onset::AbstractOnset, noise::AbstractNoise = NoNoise(); kwargs...) = simulate(rng, Simulation(design, signal, onset, noise); kwargs...) -function simulate(rng, simulation::Simulation;return_epoched::Bool=false) - - # unpacking fields - (; design, components, onset, noisetype) = simulation +function simulate(rng, simulation::Simulation; return_epoched::Bool = false) + (; design, components, onset, noisetype) = simulation + + # equivalent to !(isa(onset,NoOnset) && return_epoched == false) + @assert !isa(onset, NoOnset) || return_epoched == true "It is not possible to get continuous data without specifying a specific onset distribution. Please either specify an onset distribution (other than `NoOnset`) or set `return_epoched = true` to get epoched data without overlap." # create epoch data / erps - erps = simulate(deepcopy(rng), components,simulation) -# @debug size(erps) + erps = simulate(deepcopy(rng), components, simulation) + + # create events data frame + events = UnfoldSim.generate(design) + + if isa(onset, NoOnset) + # reshape the erps such that the last dimension is split in two dimensions (trials per subject and subject) + # such that the resulting dimensions are dimensions: channels x times x trials x subjects + # TODO: This assumes a balanced design, but create_continuous_eeg also assumes this, so we should be fine ;) + size_erps = size(erps) + eeg = reshape(erps, size_erps[1:end-1]..., size(design)...) + else # if there is an onset distribution given the next step is to create a continuous EEG + eeg, latencies = create_continuous_eeg(rng, erps, simulation) + events.latency = latencies + end - n_subj = length(size(design))==1 ? 1 : size(design)[2] - n_trial = size(design)[1] - n_ch = n_channels(components) + add_noise!(deepcopy(rng), noisetype, eeg) - # create events data frame - events = UnfoldSim.generate(design) - - if !return_epoched - # we only need to simulate onsets & pull everything together, if we - # want a continuous EEG - - onsets = generate(deepcopy(rng),onset,simulation) - - # save the onsets in the events df - events.latency = onsets[:,] - - # combine erps with onsets - maxlen = maxlength(components) - max_length = Int(ceil(maximum(onsets))) .+ maxlen - - - eeg = zeros(n_ch,max_length,n_subj) - - # not all designs have multiple subjects - if n_subj == 1; eeg = dropdims(eeg,dims=3); end - - # not all designs have multiple channels - if n_ch == 1; eeg = dropdims(eeg,dims=1); end - - - for e in 1:n_ch - for s in 1:n_subj - for i in 1:n_trial - one_onset = onsets[CartesianIndex(i, s)] - adderp!(eeg,erps,e,s,one_onset:one_onset+maxlen-1,(s-1)*n_trial+i) - end - end + # In case the data should be epoched & onset distribution is given i.e. the signals might be overlapping + if return_epoched && !isa(onset, NoOnset) + + # use epoch function to epoch the continuous (possibly overlapping) signal + if length(size(design)) == 1 # if there is only one subject + eeg = epoch(eeg, events, (0, maxlength(components) - 1), 1) + else # multi-subject case + evt_epoch = groupby(events, :subject) |> collect + # Epoch data per subject + # Note: Ref() is needed to prevent broadcasting of τ and sfreq (due to applying epoch elementwise) + eeg = epoch.(eachslice(eeg, dims = length(size(eeg))), evt_epoch, Ref((0, maxlength(components) - 1)), Ref(1)) + + # TODO: This assumes a balanced design, but create_continuous_eeg also assumes this, so we should be fine ;) + # Concatenate the epoched data of all subjects again. + eeg = reshape(reduce(hcat, vec.(eeg)), size(eeg[1])..., length(eeg)) + #cat(eeg..., dims = length(size(erps)) + 1) #TODO: find a way to use always the right dims end - else - eeg = erps + end + return eeg, events + +end +function create_continuous_eeg(rng, erps, simulation) - add_noise!(deepcopy(rng),noisetype,eeg) + (; design, components, onset, noisetype) = simulation - return eeg, events + n_subj = length(size(design)) == 1 ? 1 : size(design)[2] + n_trial = size(design)[1] + n_ch = n_channels(components) + + # we only need to simulate onsets & pull everything together, if we + # want a continuous EEG + onsets = generate(deepcopy(rng), onset, simulation) + # flatten onsets (since subjects are concatenated in the events df) + latencies = onsets[:,] + + # combine erps with onsets + max_length_component = maxlength(components) + max_length_continuoustime = Int(ceil(maximum(onsets))) .+ max_length_component + + + eeg = zeros(n_ch, max_length_continuoustime, n_subj) + + for e in 1:n_ch + for s in 1:n_subj + for i in 1:n_trial + one_onset = onsets[CartesianIndex(i, s)] + adderp!(eeg, erps, e, s, one_onset:one_onset+max_length_component-1, (s - 1) * n_trial + i) + end + end + end + + # not all designs have multiple subjects + if n_subj == 1 + eeg = dropdims(eeg, dims = 3) + end + + # not all designs have multiple channels + if n_ch == 1 + eeg = dropdims(eeg, dims = 1) + end + + return eeg, latencies end """ Helper function to add inplace the erps to the EEG, but for both 2D (1 channel) and 3D (X channel case) """ -function adderp!(eeg,erps::Vector,e,s,tvec,erpvec) - @views eeg[tvec] .+= erps[:, erpvec] +function adderp!(eeg, erps::Vector, e, s, tvec, erpvec) + @views eeg[e, tvec, s] .+= erps[:, erpvec] end -function adderp!(eeg,erps::Matrix,e,s,tvec,erpvec) - @views eeg[tvec,s] .+= erps[:, erpvec] +function adderp!(eeg, erps::Matrix, e, s, tvec, erpvec)# + @views eeg[e, tvec, s] .+= erps[:, erpvec] end -function adderp!(eeg,erps::AbstractArray,e,s,tvec,erpvec) - @views eeg[e,tvec,s] .+= erps[e,:, erpvec] +function adderp!(eeg, erps::AbstractArray, e, s, tvec, erpvec) + @views eeg[e, tvec, s] .+= erps[e, :, erpvec] end @@ -91,39 +125,39 @@ end """ Simulates erp data given the specified parameters """ -function simulate(rng, components::Vector{<:AbstractComponent},simulation::Simulation) +function simulate(rng, components::Vector{<:AbstractComponent}, simulation::Simulation) if n_channels(components) > 1 - epoch_data = zeros(n_channels(components),maxlength(components), length(simulation.design)) + epoch_data = zeros(n_channels(components), maxlength(components), length(simulation.design)) else epoch_data = zeros(maxlength(components), length(simulation.design)) end for c in components - simulateandadd!(epoch_data,c,simulation,rng) + simulateandadd!(epoch_data, c, simulation, rng) end return epoch_data end -function simulateandadd!(epoch_data::AbstractMatrix,c,simulation,rng) +function simulateandadd!(epoch_data::AbstractMatrix, c, simulation, rng) @debug "matrix" - @views epoch_data[1:length(c),:] .+= simulate(rng,c,simulation) + @views epoch_data[1:length(c), :] .+= simulate(rng, c, simulation) end -function simulateandadd!(epoch_data::AbstractArray,c,simulation,rng) +function simulateandadd!(epoch_data::AbstractArray, c, simulation, rng) @debug "3D Array" - @views epoch_data[:,1:length(c),:] .+= simulate(rng,c,simulation) + @views epoch_data[:, 1:length(c), :] .+= simulate(rng, c, simulation) end -function add_noise!(rng,noisetype::AbstractNoise,eeg) +function add_noise!(rng, noisetype::AbstractNoise, eeg) # generate noise noise = gen_noise(deepcopy(rng), noisetype, length(eeg)) - + noise = reshape(noise, size(eeg)) - + # add noise to data eeg .+= noise - + end From 529877bc2b8a48b19d4e69b202d46bfb828ba069 Mon Sep 17 00:00:00 2001 From: jschepers Date: Wed, 10 Jan 2024 14:35:46 +0000 Subject: [PATCH 005/113] added new tests for the dimensions of the simulate output --- test/simulation.jl | 271 +++++++++++++++++++++++++++++---------------- 1 file changed, 173 insertions(+), 98 deletions(-) diff --git a/test/simulation.jl b/test/simulation.jl index 70248e86..e7b5a1c4 100644 --- a/test/simulation.jl +++ b/test/simulation.jl @@ -1,102 +1,177 @@ @testset "simulation" begin - ## Define elements for the simulation - - # Define experimental factors - conditions = Dict(:cond => ["A", "B"]) - - # Create design for one subject with 20 trials (10 in each of the two factor levels) - repetitions = 10 - design_single_subject = SingleSubjectDesign(; - conditions = conditions) |> x -> RepeatDesign(x, repetitions) - - # Create design for multiple subjects with conditions as a between-items factor - n_subjects = 5 - n_items = 4 - design_multiple_subjects = MultiSubjectDesign(;n_subjects=n_subjects, n_items=n_items, items_between = conditions) - - # Linear component for the single-subject simulation - signal_linear = LinearModelComponent(; - basis = p100(), - formula = @formula(0 ~ 1 + cond), - β = [1, 0.5]) - - # Mixed-model component for the multi-subject simulation - signal_mixed = MixedModelComponent(; - basis = p100(), - formula = @formula(0 ~ 1 + cond + (1 + cond|subject)), - β = [1, 0.5], - σs = Dict(:subject => [0.2,0.1])) - - # Define headmodel and MultichannelComponent for multi-channel cases - hartmut_model = headmodel(type="hartmut") - signal_linear_multichannel = MultichannelComponent(signal_linear, hartmut_model => "Left Central Opercular Cortex") - signal_mixed_multichannel = MultichannelComponent(signal_mixed, hartmut_model => "Left Central Opercular Cortex") - - # Overlap since offset ["A", "B"]) + + # Create design for one subject with 20 trials (10 in each of the two factor levels) + repetitions = 10 + design_single_subject = SingleSubjectDesign(; + conditions = conditions) |> x -> RepeatDesign(x, repetitions) + + # Create design for multiple subjects with conditions as a between-items factor + n_subjects = 5 + n_items = 4 + design_multiple_subjects = MultiSubjectDesign(; n_subjects = n_subjects, n_items = n_items, items_between = conditions) + + # Linear component for the single-subject simulation + signal_linear = LinearModelComponent(; + basis = p100(), + formula = @formula(0 ~ 1 + cond), + β = [1, 0.5]) + + # Mixed-model component for the multi-subject simulation + signal_mixed = MixedModelComponent(; + basis = p100(), + formula = @formula(0 ~ 1 + cond + (1 + cond | subject)), + β = [1, 0.5], + σs = Dict(:subject => [0.2, 0.1])) + + # Define headmodel and MultichannelComponent for multi-channel cases + hartmut_model = headmodel(type = "hartmut") + signal_linear_multichannel = MultichannelComponent(signal_linear, hartmut_model => "Left Central Opercular Cortex") + signal_mixed_multichannel = MultichannelComponent(signal_mixed, hartmut_model => "Left Central Opercular Cortex") + + # Overlap since offset [1]), + ) + else + design = SingleSubjectDesign(; conditions = Dict(:a => ["a"])) |> x -> RepeatDesign(x, 7) + comp = LinearModelComponent(; + basis = [1, 2, 3, 4], + formula = @formula(0 ~ 1), + β = [1]) + + end + for channel ∈ ["single", "multi"] + if channel == "multi" + comp = comp |> x -> MultichannelComponent(x, [1, 2, -3, 4, 5]) + end + + for sim_onset ∈ ["noonset", "yesonset"] + if sim_onset == "yesonset" + onset = UniformOnset(; width = 20, offset = 4) + else + onset = NoOnset() + end + + for return_epoched ∈ [false, true] + simulation = Simulation(design, comp, onset, NoNoise()) + + try + data, events = simulate(MersenneTwister(1), simulation; return_epoched = return_epoched) + + sz = size(data) + + if channel == "multi" + @test sz[1] == 5 + offset = 1 + else + @test all(sz .!= 5) + offset = 0 + end + if return_epoched == true + @test sz[1+offset] == 4 + @test sz[2+offset] == 7 + if subject == "multi" + @test sz[3+offset] == 3 + end + else + @test sz[1+offset] > 4 + if subject == "multi" + @test sz[2+offset] == 3 + end + end + + catch AssertionError + # The AssertionError in the simulate function should be elicited only in the case below + @test (sim_onset == "noonset") & (return_epoched == false) + end + end + end + + end + end + end end From f4c20e61a472f7d16717b020ed61cfb2bf32474d Mon Sep 17 00:00:00 2001 From: jschepers Date: Tue, 9 Jan 2024 10:45:55 +0000 Subject: [PATCH 006/113] Formatting --- src/UnfoldSim.jl | 124 +++++++++++++++++++++++------------------------ src/design.jl | 102 +++++++++++++++++++------------------- 2 files changed, 113 insertions(+), 113 deletions(-) diff --git a/src/UnfoldSim.jl b/src/UnfoldSim.jl index e8756fe8..7f165369 100644 --- a/src/UnfoldSim.jl +++ b/src/UnfoldSim.jl @@ -1,74 +1,74 @@ module UnfoldSim - using DSP - using Random - using DataFrames - using Distributions # for LogNormal Onset - using Parameters - using StatsModels - using MixedModels - using ImageFiltering # for Noise-filter (can be replaced maybe?) - using MixedModelsSim - using SignalAnalysis - using LinearAlgebra - using ToeplitzMatrices # for AR Expo. Noise "Circulant" - using StatsModels - using HDF5,Artifacts,FileIO +using DSP +using Random +using DataFrames +using Distributions # for LogNormal Onset +using Parameters +using StatsModels +using MixedModels +using ImageFiltering # for Noise-filter (can be replaced maybe?) +using MixedModelsSim +using SignalAnalysis +using LinearAlgebra +using ToeplitzMatrices # for AR Expo. Noise "Circulant" +using StatsModels +using HDF5, Artifacts, FileIO - using LinearAlgebra # headmodel +using LinearAlgebra # headmodel - import DSP.hanning - import Base.length - import Base.size - import Base.show - include("types.jl") - include("design.jl") - include("component.jl") - include("noise.jl") - include("simulation.jl") - include("onset.jl") - include("predefinedSimulations.jl") - include("headmodel.jl") - include("helper.jl") - include("bases.jl") +import DSP.hanning +import Base.length +import Base.size +import Base.show +include("types.jl") +include("design.jl") +include("component.jl") +include("noise.jl") +include("simulation.jl") +include("onset.jl") +include("predefinedSimulations.jl") +include("headmodel.jl") +include("helper.jl") +include("bases.jl") - export size,length - export AbstractComponent,AbstractNoise,AbstractOnset,AbstractDesign - # statsmodels re-export - export @formula,DummyCoding,EffectsCoding - # mixedModels re-export - export create_re - - # main types - export Simulation - - # component types - export MixedModelComponent,LinearModelComponent +export size, length +export AbstractComponent, AbstractNoise, AbstractOnset, AbstractDesign +# statsmodels re-export +export @formula, DummyCoding, EffectsCoding +# mixedModels re-export +export create_re - # export designs - export MultiSubjectDesign,SingleSubjectDesign, RepeatDesign +# main types +export Simulation - # noise functions - export PinkNoise,RedNoise,WhiteNoise,NoNoise,ExponentialNoise #,RealNoise (not implemented yet) - - # UnfoldSim functions - export simulate, gen_noise,generate - - # utilities - export padarray,convert +# component types +export MixedModelComponent, LinearModelComponent - # export Offsets - export UniformOnset,LogNormalOnset - - # re-export StatsModels - export DummyCoding,EffectsCoding +# export designs +export MultiSubjectDesign, SingleSubjectDesign, RepeatDesign - # export bases - export p100,n170,p300,n400,hrf +# noise functions +export PinkNoise, RedNoise, WhiteNoise, NoNoise, ExponentialNoise #,RealNoise (not implemented yet) - # headmodel - export AbstractHeadmodel,Hartmut,headmodel,leadfield,orientation,magnitude +# UnfoldSim functions +export simulate, gen_noise, generate - # multichannel - export MultichannelComponent +# utilities +export padarray, convert + +# export Offsets +export UniformOnset, LogNormalOnset, NoOnset + +# re-export StatsModels +export DummyCoding, EffectsCoding + +# export bases +export p100, n170, p300, n400, hrf + +# headmodel +export AbstractHeadmodel, Hartmut, headmodel, leadfield, orientation, magnitude + +# multichannel +export MultichannelComponent end diff --git a/src/design.jl b/src/design.jl index 1b8f0bf8..2e375971 100644 --- a/src/design.jl +++ b/src/design.jl @@ -11,20 +11,20 @@ tipp: check the resulting dataframe using `generate(design)` ```julia # declaring same condition both sub-between and item-between results in a full between subject/item design design = MultiSubjectDesign(; - n_items = 10, + n_items = 10, n_subjects = 30, - subjects_between = Dict(:cond => ["levelA", "levelB"]), + subjects_between = Dict(:cond => ["levelA", "levelB"]), items_between = Dict(:cond => ["levelA", "levelB"]), - ); + ); ``` """ @with_kw struct MultiSubjectDesign <: AbstractDesign - n_subjects::Int - n_items::Int - subjects_between = nothing - items_between = nothing - both_within = nothing - tableModifyFun = x -> x # can be used to sort, or x->shuffle(rng,x) + n_subjects::Int + n_items::Int + subjects_between = nothing + items_between = nothing + both_within = nothing + tableModifyFun = x -> x # can be used to sort, or x->shuffle(rng,x) end @@ -39,8 +39,8 @@ To increase the number of repetitions simply use `RepeatDesign(SingleSubjectDesi tipp: check the resulting dataframe using `generate(design)` """ @with_kw struct SingleSubjectDesign <: AbstractDesign - conditions = nothing - tableModifyFun = x -> x + conditions = nothing + tableModifyFun = x -> x end @@ -57,14 +57,14 @@ julia> d = SingleSubjectDesign(;conditions= Dict(:A=>nlevels(5),:B=>nlevels(2))) julia> generate(d) """ function generate(expdesign::SingleSubjectDesign) - # we get a Dict(:A=>["1","2"],:B=>["3","4"]), but needed a list - # of named tuples for MixedModelsSim.factorproduct function. - evts = - factorproduct(((; k => v) for (k, v) in pairs(expdesign.conditions))...) |> - DataFrame - - # by default does nothing - return expdesign.tableModifyFun(evts) + # we get a Dict(:A=>["1","2"],:B=>["3","4"]), but needed a list + # of named tuples for MixedModelsSim.factorproduct function. + evts = + factorproduct(((; k => v) for (k, v) in pairs(expdesign.conditions))...) |> + DataFrame + + # by default does nothing + return expdesign.tableModifyFun(evts) end """ @@ -81,32 +81,32 @@ julia> d = MultiSubjectDesign(;n_subjects = 10,n_items=20,both_within= Dict(:A=> julia> generate(d) """ function generate(expdesign::MultiSubjectDesign) - #generate(expdesign::AbstractDesign) = generate(MersenneTwister(1),expdesign) + #generate(expdesign::AbstractDesign) = generate(MersenneTwister(1),expdesign) - # check that :dv is not in any condition - allconditions = - [expdesign.subjects_between, expdesign.items_between, expdesign.both_within] - @assert :dv ∉ keys(merge(allconditions[.!isnothing.(allconditions)]...)) "due to technical limitations in MixedModelsSim.jl, `:dv` cannot be used as a factorname" + # check that :dv is not in any condition + allconditions = + [expdesign.subjects_between, expdesign.items_between, expdesign.both_within] + @assert :dv ∉ keys(merge(allconditions[.!isnothing.(allconditions)]...)) "due to technical limitations in MixedModelsSim.jl, `:dv` cannot be used as a factorname" - data = DataFrame( - MixedModelsSim.simdat_crossed( - expdesign.n_subjects, - expdesign.n_items, - subj_btwn = expdesign.subjects_between, - item_btwn = expdesign.items_between, - both_win = expdesign.both_within, - ), - ) - rename!(data, :subj => :subject) - select!(data, Not(:dv)) # remove the default column from MixedModelsSim.jl - we don't need it in UnfoldSim.jl - # by default does nothing - data = expdesign.tableModifyFun(data) + data = DataFrame( + MixedModelsSim.simdat_crossed( + expdesign.n_subjects, + expdesign.n_items, + subj_btwn = expdesign.subjects_between, + item_btwn = expdesign.items_between, + both_win = expdesign.both_within, + ), + ) + rename!(data, :subj => :subject) + select!(data, Not(:dv)) # remove the default column from MixedModelsSim.jl - we don't need it in UnfoldSim.jl + # by default does nothing + data = expdesign.tableModifyFun(data) - # sort by subject - data = sort!(data, (order(:subject))) + # sort by subject + data = sort!(data, (order(:subject))) - return data + return data end @@ -123,28 +123,28 @@ repeat a design DataFrame multiple times to mimick repeatedly recorded trials ```julia designOnce = MultiSubjectDesign(; - n_items=2, + n_items=2, n_subjects = 2, - subjects_between =Dict(:cond=>["levelA","levelB"]), + subjects_between =Dict(:cond=>["levelA","levelB"]), items_between =Dict(:cond=>["levelA","levelB"]), - ); + ); design = RepeatDesign(designOnce,4); ``` """ @with_kw struct RepeatDesign{T} <: AbstractDesign - design::T - repeat::Int = 1 + design::T + repeat::Int = 1 end function UnfoldSim.generate(design::RepeatDesign) - df = map(x -> generate(design.design), 1:design.repeat) |> x -> vcat(x...) - if isa(design.design, MultiSubjectDesign) - sort!(df, [:subject]) - end - return df + df = map(x -> generate(design.design), 1:design.repeat) |> x -> vcat(x...) + if isa(design.design, MultiSubjectDesign) + sort!(df, [:subject]) + end + return df end Base.size(design::RepeatDesign{MultiSubjectDesign}) = - size(design.design) .* (design.repeat, 1) + size(design.design) .* (design.repeat, 1) Base.size(design::RepeatDesign{SingleSubjectDesign}) = size(design.design) .* design.repeat From 79992acdbbcac5b911584dfd6326ee8411629525 Mon Sep 17 00:00:00 2001 From: jschepers Date: Wed, 10 Jan 2024 14:33:16 +0000 Subject: [PATCH 007/113] Formatting --- test/runtests.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 723fc891..541b5a48 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,10 +2,10 @@ using UnfoldSim include("setup.jl") @testset "UnfoldSim.jl" begin - include("component.jl") - include("design.jl") - include("noise.jl") - include("onset.jl") - include("simulation.jl") - include("helper.jl") + include("component.jl") + include("design.jl") + include("noise.jl") + include("onset.jl") + include("simulation.jl") + include("helper.jl") end From d5788dafb5166e5ea823391e5adc2cd1684b9a67 Mon Sep 17 00:00:00 2001 From: jschepers Date: Wed, 10 Jan 2024 14:34:08 +0000 Subject: [PATCH 008/113] adapted Unfold epoch function for Unfoldsim --- src/helper.jl | 122 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 93 insertions(+), 29 deletions(-) diff --git a/src/helper.jl b/src/helper.jl index bab57049..c0169031 100755 --- a/src/helper.jl +++ b/src/helper.jl @@ -2,10 +2,10 @@ Pads array with specified value, length padarray(arr, len, val) """ -padarray(arr::Vector,len::Tuple,val) = padarray(padarray(arr,len[1],val),len[2],val) +padarray(arr::Vector, len::Tuple, val) = padarray(padarray(arr, len[1], val), len[2], val) function padarray(arr::Vector, len::Int, val) - pad = fill(val, abs(len)) - arr = len > 0 ? vcat(arr, pad) : vcat(pad, arr) + pad = fill(val, abs(len)) + arr = len > 0 ? vcat(arr, pad) : vcat(pad, arr) return arr end @@ -14,30 +14,30 @@ end """ Function to convert output similar to unfold (data, evts) """ -function convert(eeg, onsets, design,n_ch,;reshape=true) +function convert(eeg, onsets, design, n_ch, ; reshape = true) evt = UnfoldSim.generate(design) @debug size(eeg) if reshape - n_subj = length(size(design))==1 ? 1 : size(design)[2] + n_subj = length(size(design)) == 1 ? 1 : size(design)[2] if n_ch == 1 - data = eeg[:,] - - evt.latency = (onsets' .+ range(0,size(eeg,2)-1).*size(eeg,1) )'[:,] + data = eeg[:,] + + evt.latency = (onsets' .+ range(0, size(eeg, 2) - 1) .* size(eeg, 1))'[:,] elseif n_subj == 1 data = eeg @debug size(onsets) evt.latency = onsets else # multi subject + multi channel - data = eeg[:,:,] - evt.latency = (onsets' .+ range(0,size(eeg,3)-1).*size(eeg,2) )'[:,] + data = eeg[:, :] + evt.latency = (onsets' .+ range(0, size(eeg, 3) - 1) .* size(eeg, 2))'[:,] end else data = eeg end - return data,evt - + return data, evt + end @@ -47,21 +47,21 @@ end 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. """ -closest_src(coords_list::AbstractVector{<:AbstractVector}, pos) = closest_src.(coords_list, Ref(pos)) - -function closest_src(coords::Vector{<:Real}, pos) - s = size(pos); - dist = zeros(s[1]); - diff = zeros(s[2]); - for i=1:s[1] - for j=1:s[2] - diff[j] = pos[i,j] - coords[j]; - end - dist[i] = norm(diff); +closest_src(coords_list::AbstractVector{<:AbstractVector}, pos) = closest_src.(coords_list, Ref(pos)) + +function closest_src(coords::Vector{<:Real}, pos) + s = size(pos) + dist = zeros(s[1]) + diff = zeros(s[2]) + for i ∈ 1:s[1] + for j ∈ 1:s[2] + diff[j] = pos[i, j] - coords[j] end + dist[i] = norm(diff) + end return findmin(dist)[2] - - + + end """ @@ -76,12 +76,76 @@ hartmut = headmodel() pos = closest_src(hartmut=>"Left Middle Temporal Gyrus, posterior division") ``` """ -function closest_src(head::Hartmut,label::String) +function closest_src(head::Hartmut, label::String) pos = head.cortical["pos"] ix = findall(head.cortical["label"] .== label) - @assert sum(ix)>0 """could not find label $label in hartmut.cortical["label"] - try unique(hartmut.cortical["label"]) for a list""" + @assert sum(ix) > 0 """could not find label $label in hartmut.cortical["label"] - try unique(hartmut.cortical["label"]) for a list""" - ix = UnfoldSim.closest_src(mean(pos[ix,:],dims=1)[1,:],pos) + ix = UnfoldSim.closest_src(mean(pos[ix, :], dims = 1)[1, :], pos) return ix -end \ No newline at end of file +end + + +# Adapted from Unfold.jl: https://github.com/unfoldtoolbox/Unfold.jl/blob/b3a21c2bb7e93d2f45ec64b0197f4663a6d7939a/src/utilities.jl#L40 + +# One channel case +function epoch(data::AbstractVector, args...; kwargs...) + data_r = reshape(data, (1, :)) + ep = epoch(data_r, args...; kwargs...) + return dropdims(ep; dims = 1) +end + +function epoch( + data::AbstractArray{T, 2}, + events, + τ::Tuple{Number, Number}, + sfreq; + eventtime::Symbol = :latency, +) where {T <: Union{Missing, Number}} + # data: channels x times + + # partial taken from EEG.jl + + numEpochs = size(events, 1) + + times = range(τ[1], stop = τ[2], step = 1 ./ sfreq) + lenEpochs = length(times) + numChans = size(data, 1) + epochs = Array{T}( + undef, + Int(numChans), + Int(lenEpochs), + Int(numEpochs), + ) + + + # User feedback + @debug "Creating epochs: $numChans x $lenEpochs x $numEpochs" + + for si ∈ 1:size(events, 1) + # d_start and d_end are the start and end of the epoch (in samples) in the data + d_start = Int(round(events[si, eventtime]) + times[1] .* sfreq) + d_end = Int(round(events[si, eventtime]) + times[end] .* sfreq) + + # e_start and e_end are the start and end within the epoch (in samples) + e_start = 1 + e_end = lenEpochs + #println("d: $(size(data)),e: $(size(epochs)) | $d_start,$d_end,$e_start,$e_end | $(events[si,eventtime])") + + # Case that the start of the epoch is before the start of the data/recording (e.g. if the start is before i.e. negative relative to the event) + if d_start < 1 + #@warn "d_start $d_start" + e_start = e_start + (-d_start + 1) + d_start = 1 + end + # Case that the end of the epoch is after the end of the data/recording + if d_end > size(data, 2) + e_end = e_end - (d_end - size(data, 2)) + d_end = size(data, 2) + end + #println("d: $(size(data)),e: $(size(epochs)) | $d_start,$d_end,$e_start,$e_end | $(events[si,eventtime])") + epochs[:, e_start:e_end, si] = data[:, d_start:d_end] + end + return epochs +end From 2d1f07c0f1004b432c43be4fac985471284bed59 Mon Sep 17 00:00:00 2001 From: jschepers Date: Wed, 10 Jan 2024 14:35:17 +0000 Subject: [PATCH 009/113] adapted simulate function --- src/onset.jl | 51 +++++++------- src/simulation.jl | 166 ++++++++++++++++++++++++++++------------------ 2 files changed, 127 insertions(+), 90 deletions(-) diff --git a/src/onset.jl b/src/onset.jl index 64061975..55d2befe 100644 --- a/src/onset.jl +++ b/src/onset.jl @@ -2,41 +2,44 @@ # Types #--------------- -@with_kw struct UniformOnset<:AbstractOnset - width=50 # how many samples jitter? - offset=0 # minimal offset? +@with_kw struct UniformOnset <: AbstractOnset + width = 50 # how many samples jitter? + offset = 0 # minimal offset? end -@with_kw struct LogNormalOnset<:AbstractOnset - μ # mean - σ # variance - offset = 0 # additional offset - truncate_upper = nothing # truncate at some sample? +@with_kw struct LogNormalOnset <: AbstractOnset + μ::Any # mean + σ::Any # variance + offset = 0 # additional offset + truncate_upper = nothing # truncate at some sample? end +# In the case that the user directly wants the erps/epoched data (no overlap) +struct NoOnset <: AbstractOnset end + #------------- -function rand_onsets(rng,onset::UniformOnset,design::AbstractDesign) - return Int.(round.(rand(deepcopy(rng), onset.offset:(onset.offset + onset.width), size(design)))) +function rand_onsets(rng, onset::UniformOnset, design::AbstractDesign) + return Int.(round.(rand(deepcopy(rng), onset.offset:(onset.offset+onset.width), size(design)))) end -function rand_onsets(rng,onset::LogNormalOnset,design::AbstractDesign) - s = size(design) - fun = LogNormal(onset.μ,onset.σ) - if !isnothing(onset.truncate_upper) - fun = truncated(fun;upper=onset.truncate_upper) - end - return Int.(round.(onset.offset .+ rand(deepcopy(rng), fun, s))) +function rand_onsets(rng, onset::LogNormalOnset, design::AbstractDesign) + s = size(design) + fun = LogNormal(onset.μ, onset.σ) + if !isnothing(onset.truncate_upper) + fun = truncated(fun; upper = onset.truncate_upper) + end + return Int.(round.(onset.offset .+ rand(deepcopy(rng), fun, s))) end # main call from `simulation` -function generate(rng,onset::AbstractOnset,simulation::Simulation) - +function generate(rng, onset::AbstractOnset, simulation::Simulation) + # sample different onsets - onsets = rand_onsets(rng,onset,simulation.design) - - # accumulate them - onsets_accum = accumulate(+, onsets, dims=1) - + onsets = rand_onsets(rng, onset, simulation.design) + + # accumulate them + onsets_accum = accumulate(+, onsets, dims = 1) + return onsets_accum end diff --git a/src/simulation.jl b/src/simulation.jl index f0d0ff4e..057a5c6c 100755 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -1,9 +1,9 @@ # helper to move input ::Component to ::Vector{Component} -Simulation(design::AbstractDesign,component::AbstractComponent,onset::AbstractOnset,noisetype::AbstractNoise) = Simulation(design,[component],onset,noisetype) +Simulation(design::AbstractDesign, component::AbstractComponent, onset::AbstractOnset, noisetype::AbstractNoise) = Simulation(design, [component], onset, noisetype) # by default no noise -Simulation(design::AbstractDesign,component,onset::AbstractOnset) = Simulation(design,component,onset,NoNoise()) +# Simulation(design::AbstractDesign,component,onset::AbstractOnset) = Simulation(design,component,onset,NoNoise()) """ Simulate eeg data given a simulation design, effect sizes and variances @@ -11,79 +11,113 @@ Simulate eeg data given a simulation design, effect sizes and variances make use of `return_epoched=true` to skip the Onset-calculation + conversion to continuous data and get the epoched data directly """ -simulate(rng, design::AbstractDesign, signal, onset::AbstractOnset, noise::AbstractNoise=NoNoise(); kwargs...) = simulate(rng, Simulation(design, signal, onset, noise); kwargs...) +simulate(rng, design::AbstractDesign, signal, onset::AbstractOnset, noise::AbstractNoise = NoNoise(); kwargs...) = simulate(rng, Simulation(design, signal, onset, noise); kwargs...) -function simulate(rng, simulation::Simulation;return_epoched::Bool=false) - - # unpacking fields - (; design, components, onset, noisetype) = simulation +function simulate(rng, simulation::Simulation; return_epoched::Bool = false) + (; design, components, onset, noisetype) = simulation + + # equivalent to !(isa(onset,NoOnset) && return_epoched == false) + @assert !isa(onset, NoOnset) || return_epoched == true "It is not possible to get continuous data without specifying a specific onset distribution. Please either specify an onset distribution (other than `NoOnset`) or set `return_epoched = true` to get epoched data without overlap." # create epoch data / erps - erps = simulate(deepcopy(rng), components,simulation) -# @debug size(erps) + erps = simulate(deepcopy(rng), components, simulation) + + # create events data frame + events = UnfoldSim.generate(design) + + if isa(onset, NoOnset) + # reshape the erps such that the last dimension is split in two dimensions (trials per subject and subject) + # such that the resulting dimensions are dimensions: channels x times x trials x subjects + # TODO: This assumes a balanced design, but create_continuous_eeg also assumes this, so we should be fine ;) + size_erps = size(erps) + eeg = reshape(erps, size_erps[1:end-1]..., size(design)...) + else # if there is an onset distribution given the next step is to create a continuous EEG + eeg, latencies = create_continuous_eeg(rng, erps, simulation) + events.latency = latencies + end - n_subj = length(size(design))==1 ? 1 : size(design)[2] - n_trial = size(design)[1] - n_ch = n_channels(components) + add_noise!(deepcopy(rng), noisetype, eeg) - # create events data frame - events = UnfoldSim.generate(design) - - if !return_epoched - # we only need to simulate onsets & pull everything together, if we - # want a continuous EEG - - onsets = generate(deepcopy(rng),onset,simulation) - - # save the onsets in the events df - events.latency = onsets[:,] - - # combine erps with onsets - maxlen = maxlength(components) - max_length = Int(ceil(maximum(onsets))) .+ maxlen - - - eeg = zeros(n_ch,max_length,n_subj) - - # not all designs have multiple subjects - if n_subj == 1; eeg = dropdims(eeg,dims=3); end - - # not all designs have multiple channels - if n_ch == 1; eeg = dropdims(eeg,dims=1); end - - - for e in 1:n_ch - for s in 1:n_subj - for i in 1:n_trial - one_onset = onsets[CartesianIndex(i, s)] - adderp!(eeg,erps,e,s,one_onset:one_onset+maxlen-1,(s-1)*n_trial+i) - end - end + # In case the data should be epoched & onset distribution is given i.e. the signals might be overlapping + if return_epoched && !isa(onset, NoOnset) + + # use epoch function to epoch the continuous (possibly overlapping) signal + if length(size(design)) == 1 # if there is only one subject + eeg = epoch(eeg, events, (0, maxlength(components) - 1), 1) + else # multi-subject case + evt_epoch = groupby(events, :subject) |> collect + # Epoch data per subject + # Note: Ref() is needed to prevent broadcasting of τ and sfreq (due to applying epoch elementwise) + eeg = epoch.(eachslice(eeg, dims = length(size(eeg))), evt_epoch, Ref((0, maxlength(components) - 1)), Ref(1)) + + # TODO: This assumes a balanced design, but create_continuous_eeg also assumes this, so we should be fine ;) + # Concatenate the epoched data of all subjects again. + eeg = reshape(reduce(hcat, vec.(eeg)), size(eeg[1])..., length(eeg)) + #cat(eeg..., dims = length(size(erps)) + 1) #TODO: find a way to use always the right dims end - else - eeg = erps + end + return eeg, events + +end +function create_continuous_eeg(rng, erps, simulation) - add_noise!(deepcopy(rng),noisetype,eeg) + (; design, components, onset, noisetype) = simulation - return eeg, events + n_subj = length(size(design)) == 1 ? 1 : size(design)[2] + n_trial = size(design)[1] + n_ch = n_channels(components) + + # we only need to simulate onsets & pull everything together, if we + # want a continuous EEG + onsets = generate(deepcopy(rng), onset, simulation) + # flatten onsets (since subjects are concatenated in the events df) + latencies = onsets[:,] + + # combine erps with onsets + max_length_component = maxlength(components) + max_length_continuoustime = Int(ceil(maximum(onsets))) .+ max_length_component + + + eeg = zeros(n_ch, max_length_continuoustime, n_subj) + + for e in 1:n_ch + for s in 1:n_subj + for i in 1:n_trial + one_onset = onsets[CartesianIndex(i, s)] + adderp!(eeg, erps, e, s, one_onset:one_onset+max_length_component-1, (s - 1) * n_trial + i) + end + end + end + + # not all designs have multiple subjects + if n_subj == 1 + eeg = dropdims(eeg, dims = 3) + end + + # not all designs have multiple channels + if n_ch == 1 + eeg = dropdims(eeg, dims = 1) + end + + return eeg, latencies end """ Helper function to add inplace the erps to the EEG, but for both 2D (1 channel) and 3D (X channel case) """ -function adderp!(eeg,erps::Vector,e,s,tvec,erpvec) - @views eeg[tvec] .+= erps[:, erpvec] +function adderp!(eeg, erps::Vector, e, s, tvec, erpvec) + @views eeg[e, tvec, s] .+= erps[:, erpvec] end -function adderp!(eeg,erps::Matrix,e,s,tvec,erpvec) - @views eeg[tvec,s] .+= erps[:, erpvec] +function adderp!(eeg, erps::Matrix, e, s, tvec, erpvec)# + @views eeg[e, tvec, s] .+= erps[:, erpvec] end -function adderp!(eeg,erps::AbstractArray,e,s,tvec,erpvec) - @views eeg[e,tvec,s] .+= erps[e,:, erpvec] +function adderp!(eeg, erps::AbstractArray, e, s, tvec, erpvec) + @views eeg[e, tvec, s] .+= erps[e, :, erpvec] end @@ -91,39 +125,39 @@ end """ Simulates erp data given the specified parameters """ -function simulate(rng, components::Vector{<:AbstractComponent},simulation::Simulation) +function simulate(rng, components::Vector{<:AbstractComponent}, simulation::Simulation) if n_channels(components) > 1 - epoch_data = zeros(n_channels(components),maxlength(components), length(simulation.design)) + epoch_data = zeros(n_channels(components), maxlength(components), length(simulation.design)) else epoch_data = zeros(maxlength(components), length(simulation.design)) end for c in components - simulateandadd!(epoch_data,c,simulation,rng) + simulateandadd!(epoch_data, c, simulation, rng) end return epoch_data end -function simulateandadd!(epoch_data::AbstractMatrix,c,simulation,rng) +function simulateandadd!(epoch_data::AbstractMatrix, c, simulation, rng) @debug "matrix" - @views epoch_data[1:length(c),:] .+= simulate(rng,c,simulation) + @views epoch_data[1:length(c), :] .+= simulate(rng, c, simulation) end -function simulateandadd!(epoch_data::AbstractArray,c,simulation,rng) +function simulateandadd!(epoch_data::AbstractArray, c, simulation, rng) @debug "3D Array" - @views epoch_data[:,1:length(c),:] .+= simulate(rng,c,simulation) + @views epoch_data[:, 1:length(c), :] .+= simulate(rng, c, simulation) end -function add_noise!(rng,noisetype::AbstractNoise,eeg) +function add_noise!(rng, noisetype::AbstractNoise, eeg) # generate noise noise = gen_noise(deepcopy(rng), noisetype, length(eeg)) - + noise = reshape(noise, size(eeg)) - + # add noise to data eeg .+= noise - + end From bdc0f5dcfaf3cf093f7d9af7f04e0d7089dd28a9 Mon Sep 17 00:00:00 2001 From: jschepers Date: Wed, 10 Jan 2024 14:35:46 +0000 Subject: [PATCH 010/113] added new tests for the dimensions of the simulate output --- test/simulation.jl | 271 +++++++++++++++++++++++++++++---------------- 1 file changed, 173 insertions(+), 98 deletions(-) diff --git a/test/simulation.jl b/test/simulation.jl index 70248e86..e7b5a1c4 100644 --- a/test/simulation.jl +++ b/test/simulation.jl @@ -1,102 +1,177 @@ @testset "simulation" begin - ## Define elements for the simulation - - # Define experimental factors - conditions = Dict(:cond => ["A", "B"]) - - # Create design for one subject with 20 trials (10 in each of the two factor levels) - repetitions = 10 - design_single_subject = SingleSubjectDesign(; - conditions = conditions) |> x -> RepeatDesign(x, repetitions) - - # Create design for multiple subjects with conditions as a between-items factor - n_subjects = 5 - n_items = 4 - design_multiple_subjects = MultiSubjectDesign(;n_subjects=n_subjects, n_items=n_items, items_between = conditions) - - # Linear component for the single-subject simulation - signal_linear = LinearModelComponent(; - basis = p100(), - formula = @formula(0 ~ 1 + cond), - β = [1, 0.5]) - - # Mixed-model component for the multi-subject simulation - signal_mixed = MixedModelComponent(; - basis = p100(), - formula = @formula(0 ~ 1 + cond + (1 + cond|subject)), - β = [1, 0.5], - σs = Dict(:subject => [0.2,0.1])) - - # Define headmodel and MultichannelComponent for multi-channel cases - hartmut_model = headmodel(type="hartmut") - signal_linear_multichannel = MultichannelComponent(signal_linear, hartmut_model => "Left Central Opercular Cortex") - signal_mixed_multichannel = MultichannelComponent(signal_mixed, hartmut_model => "Left Central Opercular Cortex") - - # Overlap since offset ["A", "B"]) + + # Create design for one subject with 20 trials (10 in each of the two factor levels) + repetitions = 10 + design_single_subject = SingleSubjectDesign(; + conditions = conditions) |> x -> RepeatDesign(x, repetitions) + + # Create design for multiple subjects with conditions as a between-items factor + n_subjects = 5 + n_items = 4 + design_multiple_subjects = MultiSubjectDesign(; n_subjects = n_subjects, n_items = n_items, items_between = conditions) + + # Linear component for the single-subject simulation + signal_linear = LinearModelComponent(; + basis = p100(), + formula = @formula(0 ~ 1 + cond), + β = [1, 0.5]) + + # Mixed-model component for the multi-subject simulation + signal_mixed = MixedModelComponent(; + basis = p100(), + formula = @formula(0 ~ 1 + cond + (1 + cond | subject)), + β = [1, 0.5], + σs = Dict(:subject => [0.2, 0.1])) + + # Define headmodel and MultichannelComponent for multi-channel cases + hartmut_model = headmodel(type = "hartmut") + signal_linear_multichannel = MultichannelComponent(signal_linear, hartmut_model => "Left Central Opercular Cortex") + signal_mixed_multichannel = MultichannelComponent(signal_mixed, hartmut_model => "Left Central Opercular Cortex") + + # Overlap since offset [1]), + ) + else + design = SingleSubjectDesign(; conditions = Dict(:a => ["a"])) |> x -> RepeatDesign(x, 7) + comp = LinearModelComponent(; + basis = [1, 2, 3, 4], + formula = @formula(0 ~ 1), + β = [1]) + + end + for channel ∈ ["single", "multi"] + if channel == "multi" + comp = comp |> x -> MultichannelComponent(x, [1, 2, -3, 4, 5]) + end + + for sim_onset ∈ ["noonset", "yesonset"] + if sim_onset == "yesonset" + onset = UniformOnset(; width = 20, offset = 4) + else + onset = NoOnset() + end + + for return_epoched ∈ [false, true] + simulation = Simulation(design, comp, onset, NoNoise()) + + try + data, events = simulate(MersenneTwister(1), simulation; return_epoched = return_epoched) + + sz = size(data) + + if channel == "multi" + @test sz[1] == 5 + offset = 1 + else + @test all(sz .!= 5) + offset = 0 + end + if return_epoched == true + @test sz[1+offset] == 4 + @test sz[2+offset] == 7 + if subject == "multi" + @test sz[3+offset] == 3 + end + else + @test sz[1+offset] > 4 + if subject == "multi" + @test sz[2+offset] == 3 + end + end + + catch AssertionError + # The AssertionError in the simulate function should be elicited only in the case below + @test (sim_onset == "noonset") & (return_epoched == false) + end + end + end + + end + end + end end From 6ee6ce1db10dc7db61a6ea105eded04f34638be0 Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 15:55:23 +0100 Subject: [PATCH 011/113] Apply suggestions from code review (JuliaFormatter) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/design.jl | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/design.jl b/src/design.jl index 2e375971..a2eb3ef5 100644 --- a/src/design.jl +++ b/src/design.jl @@ -19,12 +19,12 @@ design = MultiSubjectDesign(; ``` """ @with_kw struct MultiSubjectDesign <: AbstractDesign - n_subjects::Int - n_items::Int - subjects_between = nothing - items_between = nothing - both_within = nothing - tableModifyFun = x -> x # can be used to sort, or x->shuffle(rng,x) + n_subjects::Int + n_items::Int + subjects_between = nothing + items_between = nothing + both_within = nothing + tableModifyFun = x -> x # can be used to sort, or x->shuffle(rng,x) end @@ -39,8 +39,8 @@ To increase the number of repetitions simply use `RepeatDesign(SingleSubjectDesi tipp: check the resulting dataframe using `generate(design)` """ @with_kw struct SingleSubjectDesign <: AbstractDesign - conditions = nothing - tableModifyFun = x -> x + conditions = nothing + tableModifyFun = x -> x end @@ -57,14 +57,14 @@ julia> d = SingleSubjectDesign(;conditions= Dict(:A=>nlevels(5),:B=>nlevels(2))) julia> generate(d) """ function generate(expdesign::SingleSubjectDesign) - # we get a Dict(:A=>["1","2"],:B=>["3","4"]), but needed a list - # of named tuples for MixedModelsSim.factorproduct function. - evts = - factorproduct(((; k => v) for (k, v) in pairs(expdesign.conditions))...) |> - DataFrame - - # by default does nothing - return expdesign.tableModifyFun(evts) + # we get a Dict(:A=>["1","2"],:B=>["3","4"]), but needed a list + # of named tuples for MixedModelsSim.factorproduct function. + evts = + factorproduct(((; k => v) for (k, v) in pairs(expdesign.conditions))...) |> + DataFrame + + # by default does nothing + return expdesign.tableModifyFun(evts) end """ @@ -81,12 +81,12 @@ julia> d = MultiSubjectDesign(;n_subjects = 10,n_items=20,both_within= Dict(:A=> julia> generate(d) """ function generate(expdesign::MultiSubjectDesign) - #generate(expdesign::AbstractDesign) = generate(MersenneTwister(1),expdesign) + #generate(expdesign::AbstractDesign) = generate(MersenneTwister(1),expdesign) - # check that :dv is not in any condition - allconditions = - [expdesign.subjects_between, expdesign.items_between, expdesign.both_within] - @assert :dv ∉ keys(merge(allconditions[.!isnothing.(allconditions)]...)) "due to technical limitations in MixedModelsSim.jl, `:dv` cannot be used as a factorname" + # check that :dv is not in any condition + allconditions = + [expdesign.subjects_between, expdesign.items_between, expdesign.both_within] + @assert :dv ∉ keys(merge(allconditions[.!isnothing.(allconditions)]...)) "due to technical limitations in MixedModelsSim.jl, `:dv` cannot be used as a factorname" data = DataFrame( From 663c7bc09c38760a45f08e1d77732e52ba205f4f Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 15:56:16 +0100 Subject: [PATCH 012/113] Apply suggestions from code review (JuliaFormatter) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/design.jl | 52 +++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/design.jl b/src/design.jl index a2eb3ef5..f464b1ec 100644 --- a/src/design.jl +++ b/src/design.jl @@ -89,24 +89,24 @@ function generate(expdesign::MultiSubjectDesign) @assert :dv ∉ keys(merge(allconditions[.!isnothing.(allconditions)]...)) "due to technical limitations in MixedModelsSim.jl, `:dv` cannot be used as a factorname" - data = DataFrame( - MixedModelsSim.simdat_crossed( - expdesign.n_subjects, - expdesign.n_items, - subj_btwn = expdesign.subjects_between, - item_btwn = expdesign.items_between, - both_win = expdesign.both_within, - ), - ) - rename!(data, :subj => :subject) - select!(data, Not(:dv)) # remove the default column from MixedModelsSim.jl - we don't need it in UnfoldSim.jl - # by default does nothing - data = expdesign.tableModifyFun(data) - - # sort by subject - data = sort!(data, (order(:subject))) - - return data + data = DataFrame( + MixedModelsSim.simdat_crossed( + expdesign.n_subjects, + expdesign.n_items, + subj_btwn = expdesign.subjects_between, + item_btwn = expdesign.items_between, + both_win = expdesign.both_within, + ), + ) + rename!(data, :subj => :subject) + select!(data, Not(:dv)) # remove the default column from MixedModelsSim.jl - we don't need it in UnfoldSim.jl + # by default does nothing + data = expdesign.tableModifyFun(data) + + # sort by subject + data = sort!(data, (order(:subject))) + + return data end @@ -133,18 +133,18 @@ design = RepeatDesign(designOnce,4); ``` """ @with_kw struct RepeatDesign{T} <: AbstractDesign - design::T - repeat::Int = 1 + design::T + repeat::Int = 1 end function UnfoldSim.generate(design::RepeatDesign) - df = map(x -> generate(design.design), 1:design.repeat) |> x -> vcat(x...) - if isa(design.design, MultiSubjectDesign) - sort!(df, [:subject]) - end - return df + df = map(x -> generate(design.design), 1:design.repeat) |> x -> vcat(x...) + if isa(design.design, MultiSubjectDesign) + sort!(df, [:subject]) + end + return df end Base.size(design::RepeatDesign{MultiSubjectDesign}) = - size(design.design) .* (design.repeat, 1) + size(design.design) .* (design.repeat, 1) Base.size(design::RepeatDesign{SingleSubjectDesign}) = size(design.design) .* design.repeat From 14e914370fb44b93a95d1c094f970e33afb7e39b Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 15:59:21 +0100 Subject: [PATCH 013/113] Apply suggestions from code review (JuliaFormatter) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/helper.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/helper.jl b/src/helper.jl index c0169031..46f08005 100755 --- a/src/helper.jl +++ b/src/helper.jl @@ -4,9 +4,9 @@ padarray(arr, len, val) """ padarray(arr::Vector, len::Tuple, val) = padarray(padarray(arr, len[1], val), len[2], val) function padarray(arr::Vector, len::Int, val) - pad = fill(val, abs(len)) - arr = len > 0 ? vcat(arr, pad) : vcat(pad, arr) - return arr + pad = fill(val, abs(len)) + arr = len > 0 ? vcat(arr, pad) : vcat(pad, arr) + return arr end @@ -47,7 +47,8 @@ end 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. """ -closest_src(coords_list::AbstractVector{<:AbstractVector}, pos) = closest_src.(coords_list, Ref(pos)) +closest_src(coords_list::AbstractVector{<:AbstractVector}, pos) = + closest_src.(coords_list, Ref(pos)) function closest_src(coords::Vector{<:Real}, pos) s = size(pos) From a5d2e66e9796594c8d8abf487065daf69154d344 Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 16:02:30 +0100 Subject: [PATCH 014/113] Apply suggestions from code review (JuliaFormatter) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/helper.jl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/helper.jl b/src/helper.jl index 46f08005..5c859c53 100755 --- a/src/helper.jl +++ b/src/helper.jl @@ -51,16 +51,16 @@ closest_src(coords_list::AbstractVector{<:AbstractVector}, pos) = closest_src.(coords_list, Ref(pos)) function closest_src(coords::Vector{<:Real}, pos) - s = size(pos) - dist = zeros(s[1]) - diff = zeros(s[2]) - for i ∈ 1:s[1] - for j ∈ 1:s[2] - diff[j] = pos[i, j] - coords[j] - end - dist[i] = norm(diff) - end - return findmin(dist)[2] + s = size(pos) + dist = zeros(s[1]) + diff = zeros(s[2]) + for i ∈ 1:s[1] + for j ∈ 1:s[2] + diff[j] = pos[i, j] - coords[j] + end + dist[i] = norm(diff) + end + return findmin(dist)[2] end From 4b0fd2b088448bab457b4315ea4e627d11c742be Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 16:48:43 +0100 Subject: [PATCH 015/113] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- test/simulation.jl | 382 +++++++++++++++++++++++++-------------------- 1 file changed, 209 insertions(+), 173 deletions(-) diff --git a/test/simulation.jl b/test/simulation.jl index e7b5a1c4..82defa40 100644 --- a/test/simulation.jl +++ b/test/simulation.jl @@ -1,177 +1,213 @@ @testset "simulation" begin - @testset "general_test_simulate" begin - ## Define elements for the simulation - - # Define experimental factors - conditions = Dict(:cond => ["A", "B"]) - - # Create design for one subject with 20 trials (10 in each of the two factor levels) - repetitions = 10 - design_single_subject = SingleSubjectDesign(; - conditions = conditions) |> x -> RepeatDesign(x, repetitions) - - # Create design for multiple subjects with conditions as a between-items factor - n_subjects = 5 - n_items = 4 - design_multiple_subjects = MultiSubjectDesign(; n_subjects = n_subjects, n_items = n_items, items_between = conditions) - - # Linear component for the single-subject simulation - signal_linear = LinearModelComponent(; - basis = p100(), - formula = @formula(0 ~ 1 + cond), - β = [1, 0.5]) - - # Mixed-model component for the multi-subject simulation - signal_mixed = MixedModelComponent(; - basis = p100(), - formula = @formula(0 ~ 1 + cond + (1 + cond | subject)), - β = [1, 0.5], - σs = Dict(:subject => [0.2, 0.1])) - - # Define headmodel and MultichannelComponent for multi-channel cases - hartmut_model = headmodel(type = "hartmut") - signal_linear_multichannel = MultichannelComponent(signal_linear, hartmut_model => "Left Central Opercular Cortex") - signal_mixed_multichannel = MultichannelComponent(signal_mixed, hartmut_model => "Left Central Opercular Cortex") - - # Overlap since offset [1]), - ) - else - design = SingleSubjectDesign(; conditions = Dict(:a => ["a"])) |> x -> RepeatDesign(x, 7) - comp = LinearModelComponent(; - basis = [1, 2, 3, 4], - formula = @formula(0 ~ 1), - β = [1]) - - end - for channel ∈ ["single", "multi"] - if channel == "multi" - comp = comp |> x -> MultichannelComponent(x, [1, 2, -3, 4, 5]) - end - - for sim_onset ∈ ["noonset", "yesonset"] - if sim_onset == "yesonset" - onset = UniformOnset(; width = 20, offset = 4) - else - onset = NoOnset() - end - - for return_epoched ∈ [false, true] - simulation = Simulation(design, comp, onset, NoNoise()) - - try - data, events = simulate(MersenneTwister(1), simulation; return_epoched = return_epoched) - - sz = size(data) - - if channel == "multi" - @test sz[1] == 5 - offset = 1 - else - @test all(sz .!= 5) - offset = 0 - end - if return_epoched == true - @test sz[1+offset] == 4 - @test sz[2+offset] == 7 - if subject == "multi" - @test sz[3+offset] == 3 - end - else - @test sz[1+offset] > 4 - if subject == "multi" - @test sz[2+offset] == 3 - end - end - - catch AssertionError - # The AssertionError in the simulate function should be elicited only in the case below - @test (sim_onset == "noonset") & (return_epoched == false) - end - end - end - - end - end - end + @testset "general_test_simulate" begin + ## Define elements for the simulation + + # Define experimental factors + conditions = Dict(:cond => ["A", "B"]) + + # Create design for one subject with 20 trials (10 in each of the two factor levels) + repetitions = 10 + design_single_subject = + SingleSubjectDesign(; conditions = conditions) |> + x -> RepeatDesign(x, repetitions) + + # Create design for multiple subjects with conditions as a between-items factor + n_subjects = 5 + n_items = 4 + design_multiple_subjects = MultiSubjectDesign(; + n_subjects = n_subjects, + n_items = n_items, + items_between = conditions, + ) + + # Linear component for the single-subject simulation + signal_linear = LinearModelComponent(; + basis = p100(), + formula = @formula(0 ~ 1 + cond), + β = [1, 0.5], + ) + + # Mixed-model component for the multi-subject simulation + signal_mixed = MixedModelComponent(; + basis = p100(), + formula = @formula(0 ~ 1 + cond + (1 + cond | subject)), + β = [1, 0.5], + σs = Dict(:subject => [0.2, 0.1]), + ) + + # Define headmodel and MultichannelComponent for multi-channel cases + hartmut_model = headmodel(type = "hartmut") + signal_linear_multichannel = MultichannelComponent( + signal_linear, + hartmut_model => "Left Central Opercular Cortex", + ) + signal_mixed_multichannel = MultichannelComponent( + signal_mixed, + hartmut_model => "Left Central Opercular Cortex", + ) + + # Overlap since offset [1]), + ) + else + design = + SingleSubjectDesign(; conditions = Dict(:a => ["a"])) |> + x -> RepeatDesign(x, 7) + comp = LinearModelComponent(; + basis = [1, 2, 3, 4], + formula = @formula(0 ~ 1), + β = [1], + ) + + end + for channel ∈ ["single", "multi"] + if channel == "multi" + comp = comp |> x -> MultichannelComponent(x, [1, 2, -3, 4, 5]) + end + + for sim_onset ∈ ["noonset", "yesonset"] + if sim_onset == "yesonset" + onset = UniformOnset(; width = 20, offset = 4) + else + onset = NoOnset() + end + + for return_epoched ∈ [false, true] + simulation = Simulation(design, comp, onset, NoNoise()) + + try + data, events = simulate( + MersenneTwister(1), + simulation; + return_epoched = return_epoched, + ) + + sz = size(data) + + if channel == "multi" + @test sz[1] == 5 + offset = 1 + else + @test all(sz .!= 5) + offset = 0 + end + if return_epoched == true + @test sz[1+offset] == 4 + @test sz[2+offset] == 7 + if subject == "multi" + @test sz[3+offset] == 3 + end + else + @test sz[1+offset] > 4 + if subject == "multi" + @test sz[2+offset] == 3 + end + end + + catch AssertionError + # The AssertionError in the simulate function should be elicited only in the case below + @test (sim_onset == "noonset") & (return_epoched == false) + end + end + end + + end + end + end end From d5d48f36d51e4613b40da9fad6321eec014ab752 Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 16:49:34 +0100 Subject: [PATCH 016/113] Apply suggestions from code review (JuliaFormatter) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- test/runtests.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 541b5a48..723fc891 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,10 +2,10 @@ using UnfoldSim include("setup.jl") @testset "UnfoldSim.jl" begin - include("component.jl") - include("design.jl") - include("noise.jl") - include("onset.jl") - include("simulation.jl") - include("helper.jl") + include("component.jl") + include("design.jl") + include("noise.jl") + include("onset.jl") + include("simulation.jl") + include("helper.jl") end From 490c7e6c3e426b29e3b6c3bc98e8b4bfad98e253 Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 16:50:51 +0100 Subject: [PATCH 017/113] Apply suggestions from code review (JuliaFormatter) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/simulation.jl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/simulation.jl b/src/simulation.jl index 057a5c6c..c5c4f20b 100755 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -151,13 +151,12 @@ end function add_noise!(rng, noisetype::AbstractNoise, eeg) - # generate noise - noise = gen_noise(deepcopy(rng), noisetype, length(eeg)) + # generate noise + noise = gen_noise(deepcopy(rng), noisetype, length(eeg)) - noise = reshape(noise, size(eeg)) + noise = reshape(noise, size(eeg)) - # add noise to data - eeg .+= noise + # add noise to data + eeg .+= noise end - From 7e65e7ec81fa3302a84f734b5ab0ca65a9b7f53e Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 17:00:03 +0100 Subject: [PATCH 018/113] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/helper.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/helper.jl b/src/helper.jl index 5c859c53..ce17c3e6 100755 --- a/src/helper.jl +++ b/src/helper.jl @@ -83,8 +83,8 @@ function closest_src(head::Hartmut, label::String) ix = findall(head.cortical["label"] .== label) @assert sum(ix) > 0 """could not find label $label in hartmut.cortical["label"] - try unique(hartmut.cortical["label"]) for a list""" - ix = UnfoldSim.closest_src(mean(pos[ix, :], dims = 1)[1, :], pos) - return ix + ix = UnfoldSim.closest_src(mean(pos[ix, :], dims = 1)[1, :], pos) + return ix end @@ -92,9 +92,9 @@ end # One channel case function epoch(data::AbstractVector, args...; kwargs...) - data_r = reshape(data, (1, :)) - ep = epoch(data_r, args...; kwargs...) - return dropdims(ep; dims = 1) + data_r = reshape(data, (1, :)) + ep = epoch(data_r, args...; kwargs...) + return dropdims(ep; dims = 1) end function epoch( From d396dea187439568434e634893ccfa3d6c5397e3 Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 17:00:29 +0100 Subject: [PATCH 019/113] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/helper.jl | 97 ++++++++++++++++++++++++--------------------------- src/onset.jl | 12 +++---- 2 files changed, 52 insertions(+), 57 deletions(-) diff --git a/src/helper.jl b/src/helper.jl index ce17c3e6..8ed27e74 100755 --- a/src/helper.jl +++ b/src/helper.jl @@ -98,55 +98,50 @@ function epoch(data::AbstractVector, args...; kwargs...) end function epoch( - data::AbstractArray{T, 2}, - events, - τ::Tuple{Number, Number}, - sfreq; - eventtime::Symbol = :latency, -) where {T <: Union{Missing, Number}} - # data: channels x times - - # partial taken from EEG.jl - - numEpochs = size(events, 1) - - times = range(τ[1], stop = τ[2], step = 1 ./ sfreq) - lenEpochs = length(times) - numChans = size(data, 1) - epochs = Array{T}( - undef, - Int(numChans), - Int(lenEpochs), - Int(numEpochs), - ) - - - # User feedback - @debug "Creating epochs: $numChans x $lenEpochs x $numEpochs" - - for si ∈ 1:size(events, 1) - # d_start and d_end are the start and end of the epoch (in samples) in the data - d_start = Int(round(events[si, eventtime]) + times[1] .* sfreq) - d_end = Int(round(events[si, eventtime]) + times[end] .* sfreq) - - # e_start and e_end are the start and end within the epoch (in samples) - e_start = 1 - e_end = lenEpochs - #println("d: $(size(data)),e: $(size(epochs)) | $d_start,$d_end,$e_start,$e_end | $(events[si,eventtime])") - - # Case that the start of the epoch is before the start of the data/recording (e.g. if the start is before i.e. negative relative to the event) - if d_start < 1 - #@warn "d_start $d_start" - e_start = e_start + (-d_start + 1) - d_start = 1 - end - # Case that the end of the epoch is after the end of the data/recording - if d_end > size(data, 2) - e_end = e_end - (d_end - size(data, 2)) - d_end = size(data, 2) - end - #println("d: $(size(data)),e: $(size(epochs)) | $d_start,$d_end,$e_start,$e_end | $(events[si,eventtime])") - epochs[:, e_start:e_end, si] = data[:, d_start:d_end] - end - return epochs + data::AbstractArray{T,2}, + events, + τ::Tuple{Number,Number}, + sfreq; + eventtime::Symbol = :latency, +) where {T<:Union{Missing,Number}} + # data: channels x times + + # partial taken from EEG.jl + + numEpochs = size(events, 1) + + times = range(τ[1], stop = τ[2], step = 1 ./ sfreq) + lenEpochs = length(times) + numChans = size(data, 1) + epochs = Array{T}(undef, Int(numChans), Int(lenEpochs), Int(numEpochs)) + + + # User feedback + @debug "Creating epochs: $numChans x $lenEpochs x $numEpochs" + + for si ∈ 1:size(events, 1) + # d_start and d_end are the start and end of the epoch (in samples) in the data + d_start = Int(round(events[si, eventtime]) + times[1] .* sfreq) + d_end = Int(round(events[si, eventtime]) + times[end] .* sfreq) + + # e_start and e_end are the start and end within the epoch (in samples) + e_start = 1 + e_end = lenEpochs + #println("d: $(size(data)),e: $(size(epochs)) | $d_start,$d_end,$e_start,$e_end | $(events[si,eventtime])") + + # Case that the start of the epoch is before the start of the data/recording (e.g. if the start is before i.e. negative relative to the event) + if d_start < 1 + #@warn "d_start $d_start" + e_start = e_start + (-d_start + 1) + d_start = 1 + end + # Case that the end of the epoch is after the end of the data/recording + if d_end > size(data, 2) + e_end = e_end - (d_end - size(data, 2)) + d_end = size(data, 2) + end + #println("d: $(size(data)),e: $(size(epochs)) | $d_start,$d_end,$e_start,$e_end | $(events[si,eventtime])") + epochs[:, e_start:e_end, si] = data[:, d_start:d_end] + end + return epochs end diff --git a/src/onset.jl b/src/onset.jl index 55d2befe..190c47dc 100644 --- a/src/onset.jl +++ b/src/onset.jl @@ -3,14 +3,14 @@ #--------------- @with_kw struct UniformOnset <: AbstractOnset - width = 50 # how many samples jitter? - offset = 0 # minimal offset? + width = 50 # how many samples jitter? + offset = 0 # minimal offset? end @with_kw struct LogNormalOnset <: AbstractOnset - μ::Any # mean - σ::Any # variance - offset = 0 # additional offset - truncate_upper = nothing # truncate at some sample? + μ::Any # mean + σ::Any # variance + offset = 0 # additional offset + truncate_upper = nothing # truncate at some sample? end # In the case that the user directly wants the erps/epoched data (no overlap) From aa1a4700a8b8406ea6e5af371a9ee8a8ce4446ed Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 17:01:18 +0100 Subject: [PATCH 020/113] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/onset.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/onset.jl b/src/onset.jl index 190c47dc..e43f6d25 100644 --- a/src/onset.jl +++ b/src/onset.jl @@ -37,9 +37,8 @@ function generate(rng, onset::AbstractOnset, simulation::Simulation) # sample different onsets onsets = rand_onsets(rng, onset, simulation.design) - # accumulate them - onsets_accum = accumulate(+, onsets, dims = 1) + # accumulate them + onsets_accum = accumulate(+, onsets, dims = 1) return onsets_accum end - From e2341fbaded15a606a1cfcf213e3b4e4902556ce Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 17:01:44 +0100 Subject: [PATCH 021/113] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/onset.jl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/onset.jl b/src/onset.jl index e43f6d25..399cd534 100644 --- a/src/onset.jl +++ b/src/onset.jl @@ -18,16 +18,18 @@ struct NoOnset <: AbstractOnset end #------------- function rand_onsets(rng, onset::UniformOnset, design::AbstractDesign) - return Int.(round.(rand(deepcopy(rng), onset.offset:(onset.offset+onset.width), size(design)))) + return Int.( + round.(rand(deepcopy(rng), onset.offset:(onset.offset+onset.width), size(design))) + ) end function rand_onsets(rng, onset::LogNormalOnset, design::AbstractDesign) - s = size(design) - fun = LogNormal(onset.μ, onset.σ) - if !isnothing(onset.truncate_upper) - fun = truncated(fun; upper = onset.truncate_upper) - end - return Int.(round.(onset.offset .+ rand(deepcopy(rng), fun, s))) + s = size(design) + fun = LogNormal(onset.μ, onset.σ) + if !isnothing(onset.truncate_upper) + fun = truncated(fun; upper = onset.truncate_upper) + end + return Int.(round.(onset.offset .+ rand(deepcopy(rng), fun, s))) end From 6539abf7126db71a4b1407f332fb6a04e88cfe70 Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 17:05:26 +0100 Subject: [PATCH 022/113] Apply suggestions from code review (JuliaFormatter) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/onset.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/onset.jl b/src/onset.jl index 399cd534..8e6a5f93 100644 --- a/src/onset.jl +++ b/src/onset.jl @@ -42,5 +42,5 @@ function generate(rng, onset::AbstractOnset, simulation::Simulation) # accumulate them onsets_accum = accumulate(+, onsets, dims = 1) - return onsets_accum + return onsets_accum end From 92cb0b6d805731c56f765d1ec36024ebb7db1e12 Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 17:07:25 +0100 Subject: [PATCH 023/113] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/simulation.jl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/simulation.jl b/src/simulation.jl index c5c4f20b..04ec6a1a 100755 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -1,6 +1,11 @@ # helper to move input ::Component to ::Vector{Component} -Simulation(design::AbstractDesign, component::AbstractComponent, onset::AbstractOnset, noisetype::AbstractNoise) = Simulation(design, [component], onset, noisetype) +Simulation( + design::AbstractDesign, + component::AbstractComponent, + onset::AbstractOnset, + noisetype::AbstractNoise, +) = Simulation(design, [component], onset, noisetype) # by default no noise # Simulation(design::AbstractDesign,component,onset::AbstractOnset) = Simulation(design,component,onset,NoNoise()) From 350bd065be372a3fb2b1912c13ff7d2faead0c22 Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 17:08:20 +0100 Subject: [PATCH 024/113] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/simulation.jl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/simulation.jl b/src/simulation.jl index 04ec6a1a..74a584b4 100755 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -16,7 +16,14 @@ Simulate eeg data given a simulation design, effect sizes and variances make use of `return_epoched=true` to skip the Onset-calculation + conversion to continuous data and get the epoched data directly """ -simulate(rng, design::AbstractDesign, signal, onset::AbstractOnset, noise::AbstractNoise = NoNoise(); kwargs...) = simulate(rng, Simulation(design, signal, onset, noise); kwargs...) +simulate( + rng, + design::AbstractDesign, + signal, + onset::AbstractOnset, + noise::AbstractNoise = NoNoise(); + kwargs..., +) = simulate(rng, Simulation(design, signal, onset, noise); kwargs...) function simulate(rng, simulation::Simulation; return_epoched::Bool = false) (; design, components, onset, noisetype) = simulation From b70d1fd17eb9a5a950e84cf0030998f0367d4e4d Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 17:12:54 +0100 Subject: [PATCH 025/113] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/simulation.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simulation.jl b/src/simulation.jl index 74a584b4..837addbc 100755 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -129,7 +129,7 @@ function adderp!(eeg, erps::Matrix, e, s, tvec, erpvec)# @views eeg[e, tvec, s] .+= erps[:, erpvec] end function adderp!(eeg, erps::AbstractArray, e, s, tvec, erpvec) - @views eeg[e, tvec, s] .+= erps[e, :, erpvec] + @views eeg[e, tvec, s] .+= erps[e, :, erpvec] end From 4938c0f8819dc30bc80b41b7635eaf25295668ce Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Wed, 10 Jan 2024 17:13:22 +0100 Subject: [PATCH 026/113] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/simulation.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simulation.jl b/src/simulation.jl index 837addbc..295e9217 100755 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -123,7 +123,7 @@ end Helper function to add inplace the erps to the EEG, but for both 2D (1 channel) and 3D (X channel case) """ function adderp!(eeg, erps::Vector, e, s, tvec, erpvec) - @views eeg[e, tvec, s] .+= erps[:, erpvec] + @views eeg[e, tvec, s] .+= erps[:, erpvec] end function adderp!(eeg, erps::Matrix, e, s, tvec, erpvec)# @views eeg[e, tvec, s] .+= erps[:, erpvec] From 54999a9d80bdb13c38c67254d00edc86744f7796 Mon Sep 17 00:00:00 2001 From: jschepers Date: Thu, 11 Jan 2024 14:20:08 +0000 Subject: [PATCH 027/113] test manual formatting with JuliaFormatter --- src/simulation.jl | 218 ++++++++++++++++++++++++---------------------- 1 file changed, 114 insertions(+), 104 deletions(-) diff --git a/src/simulation.jl b/src/simulation.jl index 295e9217..a6455c0f 100755 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -1,4 +1,3 @@ - # helper to move input ::Component to ::Vector{Component} Simulation( design::AbstractDesign, @@ -7,9 +6,6 @@ Simulation( noisetype::AbstractNoise, ) = Simulation(design, [component], onset, noisetype) -# by default no noise -# Simulation(design::AbstractDesign,component,onset::AbstractOnset) = Simulation(design,component,onset,NoNoise()) - """ Simulate eeg data given a simulation design, effect sizes and variances @@ -26,96 +22,109 @@ simulate( ) = simulate(rng, Simulation(design, signal, onset, noise); kwargs...) function simulate(rng, simulation::Simulation; return_epoched::Bool = false) - (; design, components, onset, noisetype) = simulation - - # equivalent to !(isa(onset,NoOnset) && return_epoched == false) - @assert !isa(onset, NoOnset) || return_epoched == true "It is not possible to get continuous data without specifying a specific onset distribution. Please either specify an onset distribution (other than `NoOnset`) or set `return_epoched = true` to get epoched data without overlap." - - # create epoch data / erps - erps = simulate(deepcopy(rng), components, simulation) - - # create events data frame - events = UnfoldSim.generate(design) - - if isa(onset, NoOnset) - # reshape the erps such that the last dimension is split in two dimensions (trials per subject and subject) - # such that the resulting dimensions are dimensions: channels x times x trials x subjects - # TODO: This assumes a balanced design, but create_continuous_eeg also assumes this, so we should be fine ;) - size_erps = size(erps) - eeg = reshape(erps, size_erps[1:end-1]..., size(design)...) - else # if there is an onset distribution given the next step is to create a continuous EEG - eeg, latencies = create_continuous_eeg(rng, erps, simulation) - events.latency = latencies - end - - add_noise!(deepcopy(rng), noisetype, eeg) - - # In case the data should be epoched & onset distribution is given i.e. the signals might be overlapping - if return_epoched && !isa(onset, NoOnset) - - # use epoch function to epoch the continuous (possibly overlapping) signal - if length(size(design)) == 1 # if there is only one subject - eeg = epoch(eeg, events, (0, maxlength(components) - 1), 1) - else # multi-subject case - evt_epoch = groupby(events, :subject) |> collect - # Epoch data per subject - # Note: Ref() is needed to prevent broadcasting of τ and sfreq (due to applying epoch elementwise) - eeg = epoch.(eachslice(eeg, dims = length(size(eeg))), evt_epoch, Ref((0, maxlength(components) - 1)), Ref(1)) - - # TODO: This assumes a balanced design, but create_continuous_eeg also assumes this, so we should be fine ;) - # Concatenate the epoched data of all subjects again. - eeg = reshape(reduce(hcat, vec.(eeg)), size(eeg[1])..., length(eeg)) - #cat(eeg..., dims = length(size(erps)) + 1) #TODO: find a way to use always the right dims - end - - - end - return eeg, events + (; design, components, onset, noisetype) = simulation + + # equivalent to !(isa(onset,NoOnset) && return_epoched == false) + @assert !isa(onset, NoOnset) || return_epoched == true "It is not possible to get continuous data without specifying a specific onset distribution. Please either specify an onset distribution (other than `NoOnset`) or set `return_epoched = true` to get epoched data without overlap." + + # create epoch data / erps + erps = simulate(deepcopy(rng), components, simulation) + + # create events data frame + events = UnfoldSim.generate(design) + + if isa(onset, NoOnset) + # reshape the erps such that the last dimension is split in two dimensions (trials per subject and subject) + # such that the resulting dimensions are dimensions: channels x times x trials x subjects + # TODO: This assumes a balanced design, but create_continuous_eeg also assumes this, so we should be fine ;) + size_erps = size(erps) + eeg = reshape(erps, size_erps[1:end-1]..., size(design)...) + else # if there is an onset distribution given the next step is to create a continuous EEG + eeg, latencies = create_continuous_eeg(rng, erps, simulation) + events.latency = latencies + end + + add_noise!(deepcopy(rng), noisetype, eeg) + + # In case the data should be epoched & onset distribution is given i.e. the signals might be overlapping + if return_epoched && !isa(onset, NoOnset) + + # use epoch function to epoch the continuous (possibly overlapping) signal + if length(size(design)) == 1 # if there is only one subject + eeg = epoch(eeg, events, (0, maxlength(components) - 1), 1) + else # multi-subject case + evt_epoch = groupby(events, :subject) |> collect + # Epoch data per subject + # Note: Ref() is needed to prevent broadcasting of τ and sfreq (due to applying epoch elementwise) + eeg = + epoch.( + eachslice(eeg, dims = length(size(eeg))), + evt_epoch, + Ref((0, maxlength(components) - 1)), + Ref(1), + ) + + # TODO: This assumes a balanced design, but create_continuous_eeg also assumes this, so we should be fine ;) + # Concatenate the epoched data of all subjects again. + eeg = reshape(reduce(hcat, vec.(eeg)), size(eeg[1])..., length(eeg)) + #cat(eeg..., dims = length(size(erps)) + 1) #TODO: find a way to use always the right dims + end + + + end + return eeg, events end function create_continuous_eeg(rng, erps, simulation) - (; design, components, onset, noisetype) = simulation - - n_subj = length(size(design)) == 1 ? 1 : size(design)[2] - n_trial = size(design)[1] - n_ch = n_channels(components) - - # we only need to simulate onsets & pull everything together, if we - # want a continuous EEG - onsets = generate(deepcopy(rng), onset, simulation) - - # flatten onsets (since subjects are concatenated in the events df) - latencies = onsets[:,] - - # combine erps with onsets - max_length_component = maxlength(components) - max_length_continuoustime = Int(ceil(maximum(onsets))) .+ max_length_component - - - eeg = zeros(n_ch, max_length_continuoustime, n_subj) - - for e in 1:n_ch - for s in 1:n_subj - for i in 1:n_trial - one_onset = onsets[CartesianIndex(i, s)] - adderp!(eeg, erps, e, s, one_onset:one_onset+max_length_component-1, (s - 1) * n_trial + i) - end - end - end - - # not all designs have multiple subjects - if n_subj == 1 - eeg = dropdims(eeg, dims = 3) - end - - # not all designs have multiple channels - if n_ch == 1 - eeg = dropdims(eeg, dims = 1) - end - - return eeg, latencies + (; design, components, onset, noisetype) = simulation + + n_subj = length(size(design)) == 1 ? 1 : size(design)[2] + n_trial = size(design)[1] + n_ch = n_channels(components) + + # we only need to simulate onsets & pull everything together, if we + # want a continuous EEG + onsets = generate(deepcopy(rng), onset, simulation) + + # flatten onsets (since subjects are concatenated in the events df) + latencies = onsets[:,] + + # combine erps with onsets + max_length_component = maxlength(components) + max_length_continuoustime = Int(ceil(maximum(onsets))) .+ max_length_component + + + eeg = zeros(n_ch, max_length_continuoustime, n_subj) + + for e = 1:n_ch + for s = 1:n_subj + for i = 1:n_trial + one_onset = onsets[CartesianIndex(i, s)] + adderp!( + eeg, + erps, + e, + s, + one_onset:one_onset+max_length_component-1, + (s - 1) * n_trial + i, + ) + end + end + end + + # not all designs have multiple subjects + if n_subj == 1 + eeg = dropdims(eeg, dims = 3) + end + + # not all designs have multiple channels + if n_ch == 1 + eeg = dropdims(eeg, dims = 1) + end + + return eeg, latencies end @@ -126,7 +135,7 @@ function adderp!(eeg, erps::Vector, e, s, tvec, erpvec) @views eeg[e, tvec, s] .+= erps[:, erpvec] end function adderp!(eeg, erps::Matrix, e, s, tvec, erpvec)# - @views eeg[e, tvec, s] .+= erps[:, erpvec] + @views eeg[e, tvec, s] .+= erps[:, erpvec] end function adderp!(eeg, erps::AbstractArray, e, s, tvec, erpvec) @views eeg[e, tvec, s] .+= erps[e, :, erpvec] @@ -138,24 +147,25 @@ end Simulates erp data given the specified parameters """ function simulate(rng, components::Vector{<:AbstractComponent}, simulation::Simulation) - if n_channels(components) > 1 - epoch_data = zeros(n_channels(components), maxlength(components), length(simulation.design)) - else - epoch_data = zeros(maxlength(components), length(simulation.design)) - end - - for c in components - simulateandadd!(epoch_data, c, simulation, rng) - end - return epoch_data + if n_channels(components) > 1 + epoch_data = + zeros(n_channels(components), maxlength(components), length(simulation.design)) + else + epoch_data = zeros(maxlength(components), length(simulation.design)) + end + + for c in components + simulateandadd!(epoch_data, c, simulation, rng) + end + return epoch_data end function simulateandadd!(epoch_data::AbstractMatrix, c, simulation, rng) - @debug "matrix" - @views epoch_data[1:length(c), :] .+= simulate(rng, c, simulation) + @debug "matrix" + @views epoch_data[1:length(c), :] .+= simulate(rng, c, simulation) end function simulateandadd!(epoch_data::AbstractArray, c, simulation, rng) - @debug "3D Array" - @views epoch_data[:, 1:length(c), :] .+= simulate(rng, c, simulation) + @debug "3D Array" + @views epoch_data[:, 1:length(c), :] .+= simulate(rng, c, simulation) end From 5ec880f209e503ebab66d93349afb089432671a5 Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Tue, 16 Jan 2024 18:38:12 +0000 Subject: [PATCH 028/113] added new HowTo --- docs/literate/HowTo/predefinedData.jl | 48 +++++++++++++++++++++++++++ docs/literate/tutorials/quickstart.jl | 27 +++++++-------- docs/make.jl | 29 ++++++++-------- 3 files changed, 75 insertions(+), 29 deletions(-) create mode 100644 docs/literate/HowTo/predefinedData.jl diff --git a/docs/literate/HowTo/predefinedData.jl b/docs/literate/HowTo/predefinedData.jl new file mode 100644 index 00000000..baf23883 --- /dev/null +++ b/docs/literate/HowTo/predefinedData.jl @@ -0,0 +1,48 @@ +# # Make use +# Let's say you want to + +using UnfoldSim +using DataFrames +using CairoMakie ## for plotting + +# From a previous study, we (somehow, e.g. [pyMNE.jl]()) imported event-table like this: +my_events = DataFrame(:condition => [:A, :B, :B, :A, :A], :latency => [7, 13, 22, 35, 41]) + +# To use exactly these values, we can generate a new `AbstractDesign`, which will always return this event dataframe +struct MyManualDesign <: AbstractDesign + my_events +end +UnfoldSim.generate(d::MyManualDesign) = deepcopy(d.my_events) ## generate function which is called internally in UnfoldSim +UnfoldSim.size(d::MyManualDesign) = size(d.my_events, 1); ## necessary function to tell what the dimensionality of the experimental design is +# !!! note +# Note the `UnfoldSim.generate` which tells Julia to "overload" the `generate` function as defined in UnfoldSim + + +# Next we generate a `MyManualDesign` +mydesign = MyManualDesign(my_events); + +# We could already use this "solo" and simulate some data, for example: +signal = LinearModelComponent(; + basis=[1, 1, 0.5, 0, 0], + formula=@formula(0 ~ 1 + condition), + β=[1, 0.5]); + +data, events = simulate(MersenneTwister(1), mydesign, signal, UniformOnset(; width=10, offset=5)) +lines(data) ## plotting +vlines!(my_events.latency; linestyle=:dash) +current_figure() +# Looks good, but the events don't match our custom onsets yet + +# ## Custom Timings +# Finally, we want to use our custom timings as well. For this we define a new `AbstractOnset`. Again, it simply returns our manually provided latencies +struct MyManualOnset <: AbstractOnset end +UnfoldSim.generate(rng, onset::MyManualOnset, simulation::Simulation) = generate(simulation.design).latency +# !!! hint +# This is a bit of a trick, it relies that `MyManualOnset`` is always used in combination with `MyManualDesign`. You could of course repeat the structure from `MyManualDesign` also for `MyManualOnset` and have an explicit field in the structure containing the onsets. + +# And that's it +data, events = simulate(MersenneTwister(1), mydesign, signal, MyManualOnset()) +lines(data) ## plotting +vlines!(my_events.latency, linestyle=:dash) +current_figure() +# now everything matches, lovely! \ No newline at end of file diff --git a/docs/literate/tutorials/quickstart.jl b/docs/literate/tutorials/quickstart.jl index 04a9d622..9c2075d1 100644 --- a/docs/literate/tutorials/quickstart.jl +++ b/docs/literate/tutorials/quickstart.jl @@ -9,8 +9,8 @@ using CairoMakie # ## "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); + 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. @@ -18,29 +18,26 @@ design = SingleSubjectDesign(; # !!! 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] - ); + 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); +onset = UniformOnset(; width=20, offset=4); # And we will use some noise -noise = PinkNoise(;noiselevel=0.2); +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`). +data, events = simulate(MersenneTwister(1), design, signal, onset, noise); +# Data is a `n-sample` Vector (but could be a 2D-Array 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")]) +lines(data; color="black") +vlines!(events.latency; color=["orange", "teal"][1 .+ (events.condA.=="levelB")]) current_figure() \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 74d8e128..e759d6f0 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -7,19 +7,19 @@ using Literate GENERATED = joinpath(@__DIR__, "src", "generated") SOURCE = joinpath(@__DIR__, "literate") -for subfolder ∈ ["explanations","HowTo","tutorials","reference"] - local SOURCE_FILES = Glob.glob(subfolder*"/*.jl", SOURCE) +for subfolder ∈ ["explanations", "HowTo", "tutorials", "reference"] + local SOURCE_FILES = Glob.glob(subfolder * "/*.jl", SOURCE) #config=Dict(:repo_root_path=>"https://github.com/unfoldtoolbox/UnfoldSim") - foreach(fn -> Literate.markdown(fn, GENERATED*"/"*subfolder), SOURCE_FILES) + foreach(fn -> Literate.markdown(fn, GENERATED * "/" * subfolder), SOURCE_FILES) end -DocMeta.setdocmeta!(UnfoldSim, :DocTestSetup, :(using UnfoldSim); recursive = true) +DocMeta.setdocmeta!(UnfoldSim, :DocTestSetup, :(using UnfoldSim); recursive=true) makedocs(; - modules = [UnfoldSim], - authors = "Luis Lips, Benedikt Ehinger, Judith Schepers", + modules=[UnfoldSim], + authors="Luis Lips, Benedikt Ehinger, Judith Schepers", #repo="https://github.com/unfoldtoolbox/UnfoldSim.jl/blob/{commit}{path}#{line}", repo=Documenter.Remotes.GitHub("unfoldtoolbox", "UnfoldSim.jl"), sitename="UnfoldSim.jl", @@ -31,10 +31,10 @@ makedocs(; ), pages=[ "Home" => "index.md", - "Tutorials"=>[ - "Quickstart" => "generated/tutorials/quickstart.md", - "Simulate ERPs" => "generated/tutorials/simulateERP.md", - "Poweranalysis" => "generated/tutorials/poweranalysis.md", + "Tutorials" => [ + "Quickstart" => "generated/tutorials/quickstart.md", + "Simulate ERPs" => "generated/tutorials/simulateERP.md", + "Poweranalysis" => "generated/tutorials/poweranalysis.md", ], "Reference" => [ "Overview: Toolbox Functions" => "./generated/reference/overview.md", @@ -43,10 +43,11 @@ makedocs(; "Overview: Components (EEG, fMRI, Pupil)" => "./generated/reference/basistypes.md", ], "HowTo" => [ - "Define a new, (imbalanced) design" => "./generated/HowTo/newDesign.md", - "Repeating a design" => "./generated/HowTo/repeatTrials.md", - "Define a new duration & jitter component" => "./generated/HowTo/newComponent.md", - "Generate multi channel data" =>"./generated/HowTo/multichannel.md", + "Define a new, (imbalanced) design" => "./generated/HowTo/newDesign.md", + "Repeating a design" => "./generated/HowTo/repeatTrials.md", + "Define a new duration & jitter component" => "./generated/HowTo/newComponent.md", + "Generate multi channel data" => "./generated/HowTo/multichannel.md", + "Use predefined design / onsets data" => "./generated/HowTo/predefinedData.md", ], "DocStrings" => "api.md", ], From 8880ec50d793b84daf01f8d5362e3edc8e626ba6 Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Thu, 18 Jan 2024 19:25:33 +0000 Subject: [PATCH 029/113] added using Random --- docs/literate/HowTo/predefinedData.jl | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/literate/HowTo/predefinedData.jl b/docs/literate/HowTo/predefinedData.jl index baf23883..02fd68bd 100644 --- a/docs/literate/HowTo/predefinedData.jl +++ b/docs/literate/HowTo/predefinedData.jl @@ -3,6 +3,7 @@ using UnfoldSim using DataFrames +using Random using CairoMakie ## for plotting # From a previous study, we (somehow, e.g. [pyMNE.jl]()) imported event-table like this: @@ -10,7 +11,7 @@ my_events = DataFrame(:condition => [:A, :B, :B, :A, :A], :latency => [7, 13, 22 # To use exactly these values, we can generate a new `AbstractDesign`, which will always return this event dataframe struct MyManualDesign <: AbstractDesign - my_events + my_events::Any end UnfoldSim.generate(d::MyManualDesign) = deepcopy(d.my_events) ## generate function which is called internally in UnfoldSim UnfoldSim.size(d::MyManualDesign) = size(d.my_events, 1); ## necessary function to tell what the dimensionality of the experimental design is @@ -23,26 +24,29 @@ mydesign = MyManualDesign(my_events); # We could already use this "solo" and simulate some data, for example: signal = LinearModelComponent(; - basis=[1, 1, 0.5, 0, 0], - formula=@formula(0 ~ 1 + condition), - β=[1, 0.5]); + basis = [1, 1, 0.5, 0, 0], + formula = @formula(0 ~ 1 + condition), + β = [1, 0.5], +); -data, events = simulate(MersenneTwister(1), mydesign, signal, UniformOnset(; width=10, offset=5)) +data, events = + simulate(MersenneTwister(1), mydesign, signal, UniformOnset(; width = 10, offset = 5)) lines(data) ## plotting -vlines!(my_events.latency; linestyle=:dash) +vlines!(my_events.latency; linestyle = :dash) current_figure() # Looks good, but the events don't match our custom onsets yet # ## Custom Timings # Finally, we want to use our custom timings as well. For this we define a new `AbstractOnset`. Again, it simply returns our manually provided latencies struct MyManualOnset <: AbstractOnset end -UnfoldSim.generate(rng, onset::MyManualOnset, simulation::Simulation) = generate(simulation.design).latency +UnfoldSim.generate(rng, onset::MyManualOnset, simulation::Simulation) = + generate(simulation.design).latency # !!! hint # This is a bit of a trick, it relies that `MyManualOnset`` is always used in combination with `MyManualDesign`. You could of course repeat the structure from `MyManualDesign` also for `MyManualOnset` and have an explicit field in the structure containing the onsets. # And that's it data, events = simulate(MersenneTwister(1), mydesign, signal, MyManualOnset()) lines(data) ## plotting -vlines!(my_events.latency, linestyle=:dash) +vlines!(my_events.latency, linestyle = :dash) current_figure() # now everything matches, lovely! \ No newline at end of file From e00d05adf9b860c532a3d759f1a66464599bac48 Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Tue, 23 Jan 2024 10:18:10 +0100 Subject: [PATCH 030/113] Update design.jl --- src/design.jl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/design.jl b/src/design.jl index f464b1ec..9d965849 100644 --- a/src/design.jl +++ b/src/design.jl @@ -36,6 +36,8 @@ Number of trials / rows in `generate(design)` depend on the full factorial of yo To increase the number of repetitions simply use `RepeatDesign(SingleSubjectDesign(...),5)` +If conditions are omitted (or set to `nothing`), a single trial is simulated with a column `:dummy` and content `:dummy` - this is for convenience. + tipp: check the resulting dataframe using `generate(design)` """ @with_kw struct SingleSubjectDesign <: AbstractDesign @@ -53,16 +55,22 @@ Generates full-factorial DataFrame of expdesign.conditions Afterwards applies expdesign.tableModifyFun. +If conditions is `nothing`, a single trial is simulated with a column `:dummy` and content `:dummy` - this is for convenience. + + julia> d = SingleSubjectDesign(;conditions= Dict(:A=>nlevels(5),:B=>nlevels(2))) julia> generate(d) """ function generate(expdesign::SingleSubjectDesign) + if isnothing(expdesign.conditions) + evts = DataFrame(:dummy=>[:dummy]) + else # we get a Dict(:A=>["1","2"],:B=>["3","4"]), but needed a list # of named tuples for MixedModelsSim.factorproduct function. evts = factorproduct(((; k => v) for (k, v) in pairs(expdesign.conditions))...) |> DataFrame - + end # by default does nothing return expdesign.tableModifyFun(evts) end From 7cfb6a99a8c14a78ebea8c1203b539662e610b2f Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 22 Jan 2024 14:44:21 +0000 Subject: [PATCH 031/113] adapted generate function for MultiSubjectDesigns without conditions + added tests --- src/design.jl | 5 ++++- test/design.jl | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/design.jl b/src/design.jl index f464b1ec..6940449c 100644 --- a/src/design.jl +++ b/src/design.jl @@ -86,7 +86,10 @@ function generate(expdesign::MultiSubjectDesign) # check that :dv is not in any condition allconditions = [expdesign.subjects_between, expdesign.items_between, expdesign.both_within] - @assert :dv ∉ keys(merge(allconditions[.!isnothing.(allconditions)]...)) "due to technical limitations in MixedModelsSim.jl, `:dv` cannot be used as a factorname" + + @assert all(isnothing.(allconditions)) || + :dv ∉ keys(merge(allconditions[.!isnothing.(allconditions)]...)) "due to technical limitations in MixedModelsSim.jl, `:dv` cannot be used as a factorname" + #@assert :dv ∉ keys(merge(allconditions[.!isnothing.(allconditions)]...)) "due to technical limitations in MixedModelsSim.jl, `:dv` cannot be used as a factorname" data = DataFrame( diff --git a/test/design.jl b/test/design.jl index a7c99be8..67811f1b 100644 --- a/test/design.jl +++ b/test/design.jl @@ -77,4 +77,32 @@ @test size(design) == (3 * 5 * 2,) end + @testset "MultiSubjectDesign without conditions" begin + # Define number of subjects and items + n_subjects = 3 + n_items = 5 + + # Create a multi-subject design without any conditions + design_multi = MultiSubjectDesign(; n_subjects = n_subjects, n_items = n_items) + + # Create the events data frame based on the design defined above + events_multi = generate(design_multi) + + # Test that there are only subject and item columns (no condition columns) in the events df + @test names(events_multi) == ["subject", "item"] + + # Test that the total length of the events df is the number of subjects times + # the number of items since every item occurs in every subject + @test size(events_multi, 1) == n_subjects * n_items + + # Test that all subjects and items appear in the events df (and also not more) + @test length(unique(events_multi.subject)) == n_subjects + @test length(unique(events_multi.item)) == n_items + + # Compute number of items per subject + n_items_per_subject = combine(groupby(events_multi, :subject), :item => length) + # Check that all subjects have the same number of items (and this number equals n_items) + @test all(.==(n_items, n_items_per_subject.item_length)) + end + end From 83821b6c8a01bb6f2914093a106970ee8fb3e558 Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 22 Jan 2024 15:15:15 +0000 Subject: [PATCH 032/113] remove commented code --- src/design.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/design.jl b/src/design.jl index 6940449c..67a0dcc8 100644 --- a/src/design.jl +++ b/src/design.jl @@ -89,8 +89,6 @@ function generate(expdesign::MultiSubjectDesign) @assert all(isnothing.(allconditions)) || :dv ∉ keys(merge(allconditions[.!isnothing.(allconditions)]...)) "due to technical limitations in MixedModelsSim.jl, `:dv` cannot be used as a factorname" - #@assert :dv ∉ keys(merge(allconditions[.!isnothing.(allconditions)]...)) "due to technical limitations in MixedModelsSim.jl, `:dv` cannot be used as a factorname" - data = DataFrame( MixedModelsSim.simdat_crossed( From d3cab54ae03b46ceade1d276215848d816395174 Mon Sep 17 00:00:00 2001 From: jschepers Date: Thu, 25 Jan 2024 14:04:20 +0000 Subject: [PATCH 033/113] Added more tests for simulated data with a multi-subject design without conditions --- test/design.jl | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/test/design.jl b/test/design.jl index 67811f1b..28f73884 100644 --- a/test/design.jl +++ b/test/design.jl @@ -103,6 +103,30 @@ n_items_per_subject = combine(groupby(events_multi, :subject), :item => length) # Check that all subjects have the same number of items (and this number equals n_items) @test all(.==(n_items, n_items_per_subject.item_length)) + + # Simulate some data with a multi-subject design without conditions + + # Define a component that only has a 1 as its basis to facilitate counting the peaks + component = MixedModelComponent(; + basis = [1], + formula = @formula(0 ~ 1 + (1 | subject)), + β = [5], + σs = Dict(:subject => [1]), + ) + + # Define an onset without Overlap + onset = UniformOnset(; width = 10, offset = 3) + + # Simulate data without noise + data, events = simulate(StableRNG(1), design_multi, component, onset, NoNoise()) + + # Check that the simulated data has as many columns as subjects + @test size(data, 2) == n_subjects + + # Check that (for each subject) the data has as many peaks (i.e. events) as set in n_items + for s = 1:n_subjects + @test count(x -> .!iszero(x), data[:, s]) == n_items + end end -end +end \ No newline at end of file From e792c5016d4a93f293c79d45a75482ab9706d797 Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Mon, 5 Feb 2024 13:21:30 +0100 Subject: [PATCH 034/113] simulate docstring --- src/simulation.jl | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/simulation.jl b/src/simulation.jl index a6455c0f..42a85b16 100755 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -7,9 +7,29 @@ Simulation( ) = Simulation(design, [component], onset, noisetype) """ -Simulate eeg data given a simulation design, effect sizes and variances + simulate( + rng, + design::AbstractDesign, + signal, + onset::AbstractOnset, + noise::AbstractNoise = NoNoise(); + return_epoched=false, + ) + +Main simulation function, given `Design`, [Array of] `Component`, `Onset` and optional [`Noise`], returns continuous or epoched EEG data. + +## optional +- `return_epoched` (Bool, default: `false`): Skip the Onset-calculation and conversion to continuous data and return the epoched data directly (see also remarks below). + +## Return +Depending on the design, the components and on `return_epoched`, the output can be a 1-D, 2-D, 3-D or 4-D Array. For example, a 4-D Array would have the dimensions `channels x time x trials x subjects` + +## Notes +Some remarks to how the noise is added: + - If `return_epoched = true` and `onset =NoOnset()` the noise is added to the epoched data matrix + - If `onset` is not `NoOnset`, a continuous eeg signal is created and the noise is added to this i.e. this means that the noise won't be the same as in the `onset = NoOnset()` case even if `return_epoched = true`. + - The case `return_epoched = false` and `onset = NoOnset()` is not possible and therefore covered by an assert statement -make use of `return_epoched=true` to skip the Onset-calculation + conversion to continuous data and get the epoched data directly """ simulate( From f114405f1c9ae002f21f829f5339f9c2cd35fb19 Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Mon, 5 Feb 2024 13:22:29 +0100 Subject: [PATCH 035/113] v0.2.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 922df7e1..34b4f9c0 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnfoldSim" uuid = "ed8ae6d2-84d3-44c6-ab46-0baf21700804" authors = ["Benedikt Ehinger", "Luis Lips", "Judith Schepers", "Maanik Marathe"] -version = "0.1.7" +version = "0.2.0" [deps] Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" From 8e063c19d9b16c80712558fe0a35ba729d3d25df Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Tue, 6 Feb 2024 09:29:58 +0000 Subject: [PATCH 036/113] rename tableModifyFun to event_order_function --- src/design.jl | 34 +++++++++++++++++----------------- src/predefinedSimulations.jl | 18 +++++++++--------- test/design.jl | 8 ++++---- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/design.jl b/src/design.jl index f27b803f..4666c800 100644 --- a/src/design.jl +++ b/src/design.jl @@ -4,7 +4,7 @@ - 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!! +- event_order_function = 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)` @@ -24,13 +24,13 @@ design = MultiSubjectDesign(; subjects_between = nothing items_between = nothing both_within = nothing - tableModifyFun = x -> x # can be used to sort, or x->shuffle(rng,x) + event_order_function = x -> x # can be used to sort, or x->shuffle(rng,x) end """ - 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!! +- event_order_function = 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`. @@ -42,7 +42,7 @@ tipp: check the resulting dataframe using `generate(design)` """ @with_kw struct SingleSubjectDesign <: AbstractDesign conditions = nothing - tableModifyFun = x -> x + event_order_function = x -> x end @@ -53,7 +53,7 @@ size(expdesign::SingleSubjectDesign) = (*(length.(values(expdesign.conditions)). """ Generates full-factorial DataFrame of expdesign.conditions -Afterwards applies expdesign.tableModifyFun. +Afterwards applies expdesign.event_order_function. If conditions is `nothing`, a single trial is simulated with a column `:dummy` and content `:dummy` - this is for convenience. @@ -62,17 +62,17 @@ julia> d = SingleSubjectDesign(;conditions= Dict(:A=>nlevels(5),:B=>nlevels(2))) julia> generate(d) """ function generate(expdesign::SingleSubjectDesign) - if isnothing(expdesign.conditions) - evts = DataFrame(:dummy=>[:dummy]) - else - # we get a Dict(:A=>["1","2"],:B=>["3","4"]), but needed a list - # of named tuples for MixedModelsSim.factorproduct function. - evts = - factorproduct(((; k => v) for (k, v) in pairs(expdesign.conditions))...) |> - DataFrame - end + if isnothing(expdesign.conditions) + evts = DataFrame(:dummy => [:dummy]) + else + # we get a Dict(:A=>["1","2"],:B=>["3","4"]), but needed a list + # of named tuples for MixedModelsSim.factorproduct function. + evts = + factorproduct(((; k => v) for (k, v) in pairs(expdesign.conditions))...) |> + DataFrame + end # by default does nothing - return expdesign.tableModifyFun(evts) + return expdesign.event_order_function(evts) end """ @@ -81,7 +81,7 @@ Note: n_items = 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. +Afterwards applies expdesign.event_order_function. Could be used to duplicate trials, sort, subselect etc. Finally it sorts by `:subject` @@ -110,7 +110,7 @@ function generate(expdesign::MultiSubjectDesign) rename!(data, :subj => :subject) select!(data, Not(:dv)) # remove the default column from MixedModelsSim.jl - we don't need it in UnfoldSim.jl # by default does nothing - data = expdesign.tableModifyFun(data) + data = expdesign.event_order_function(data) # sort by subject data = sort!(data, (order(:subject))) diff --git a/src/predefinedSimulations.jl b/src/predefinedSimulations.jl index 6b7e2df4..56fe3b6d 100644 --- a/src/predefinedSimulations.jl +++ b/src/predefinedSimulations.jl @@ -20,7 +20,7 @@ Most used kwargs is: `return_epoched=true` to ignore the overlap/onset bits and ## Default params: . n_repeats=100 -- tableModifyFun = x->shuffle(deepcopy(rng),x # random trial order +- event_order_function = x->shuffle(deepcopy(rng),x # random trial order - conditions = Dict(...), #### component / signal @@ -41,7 +41,7 @@ function predef_eeg( rng; # design n_repeats = 100, - tableModifyFun = x -> shuffle(deepcopy(rng), x), + event_order_function = x -> shuffle(deepcopy(rng), x), # component / signal sfreq = 100, @@ -57,7 +57,7 @@ function predef_eeg( :condition => ["car", "face"], :continuous => range(-5, 5, length = 10), ), - tableModifyFun = tableModifyFun, + event_order_function = event_order_function, ) |> x -> RepeatDesign(x, n_repeats) return predef_eeg(rng, design, LinearModelComponent, [p1, n1, p3]; sfreq, kwargs...) end @@ -90,7 +90,7 @@ function predef_eeg( n_subjects; # design n_items = 100, - tableModifyFun = x -> shuffle(deepcopy(rng), x), + event_order_function = x -> shuffle(deepcopy(rng), x), conditions = Dict( :condition => ["car", "face"], :continuous => range(-5, 5, length = 10), @@ -130,7 +130,7 @@ function predef_eeg( n_subjects = n_subjects, n_items = n_items, items_between = conditions, - tableModifyFun = tableModifyFun, + event_order_function = event_order_function, ) return predef_eeg(rng, design, MixedModelComponent, [p1, n1, p3]; sfreq, kwargs...) @@ -146,7 +146,7 @@ Most used kwargs is: `return_epoched=true` to ignore the overlap/onset bits and - `n_items`=100, - `n_subjects`=1, - `conditions` = Dict(:A=>["a_small","a_big"],:B=>["b_tiny","b_large"]), -- `tableModifyFun` = x->shuffle(deepcopy(rng),x), +- `event_order_function` = x->shuffle(deepcopy(rng),x), #### component / signal - `signalsize` = 100, length of simulated hanning window @@ -173,7 +173,7 @@ function predef_2x2( n_items = 100, n_subjects = 1, conditions = Dict(:A => ["a_small", "a_big"], :B => ["b_tiny", "b_large"]), - tableModifyFun = x -> shuffle(deepcopy(rng), x), + event_order_function = x -> shuffle(deepcopy(rng), x), # component / signal signalsize = 100, @@ -201,7 +201,7 @@ function predef_2x2( design = SingleSubjectDesign(; conditions = conditions, - tableModifyFun = tableModifyFun, + event_order_function = event_order_function, ) |> x -> RepeatDesign(x, n_items ./ length(x)) signal = LinearModelComponent(; @@ -215,7 +215,7 @@ function predef_2x2( n_subjects = n_subjects, n_items = n_items, items_between = conditions, - tableModifyFun = tableModifyFun, + event_order_function = event_order_function, ) signal = MixedModelComponent(; basis = basis, diff --git a/test/design.jl b/test/design.jl index 28f73884..338f22e3 100644 --- a/test/design.jl +++ b/test/design.jl @@ -10,7 +10,7 @@ @test sum((generate(des).B .== "S2") .&& (generate(des).A .== "S2")) == 1 des = SingleSubjectDesign(; conditions = Dict(:A => nlevels(5), :B => nlevels(2)), - tableModifyFun = x -> sort(x, order(:B, rev = true)), + event_order_function = x -> sort(x, order(:B, rev = true)), ) @test generate(des).B[1] == "S2" end @@ -33,16 +33,16 @@ n_subjects = 10, n_items = 100, both_within = Dict(:A => nlevels(5), :B => nlevels(2)), - tableModifyFun = x -> sort(x, order(:item, rev = true)), + event_order_function = x -> sort(x, order(:item, rev = true)), ) @test generate(des).subject[1] == "S01" - # check tableModifyFun + # check event_order_function des = MultiSubjectDesign(; n_subjects = 10, n_items = 100, both_within = Dict(:A => nlevels(5), :B => nlevels(2)), - tableModifyFun = x -> sort(x, order(:B, rev = true)), + event_order_function = x -> sort(x, order(:B, rev = true)), ) @test generate(des).B[1] == "S2" From 8969c068f94766a2e9b85eb207f267389cb98d6b Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Wed, 7 Feb 2024 15:51:50 +0100 Subject: [PATCH 037/113] renaming (#73) * rename tableModifyFun to event_order_function * renaming 1: generate design, onsets * rename 2: noise * rename 3: renamed everything again ;-D * renamed create_continuous_eeg and eeg * renamed 4: all of judiths good finds * Apply suggestions from code review (JuliaFormatter) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Apply suggestions from code review (JuliaFormatter) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * renamed some more internal variables --------- Co-authored-by: Judith Schepers Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- docs/literate/HowTo/newComponent.jl | 10 +- docs/literate/HowTo/newDesign.jl | 6 +- docs/literate/HowTo/predefinedData.jl | 6 +- docs/literate/HowTo/repeatTrials.jl | 2 +- docs/literate/reference/basistypes.jl | 4 +- docs/literate/reference/noisetypes.jl | 2 +- docs/literate/reference/onsettypes.jl | 15 ++- src/UnfoldSim.jl | 12 ++- src/bases.jl | 8 +- src/component.jl | 93 +++++++++++------ src/design.jl | 57 ++++++----- src/helper.jl | 43 ++++---- src/noise.jl | 40 ++++++-- src/onset.jl | 8 +- src/simulation.jl | 138 +++++++++----------------- test/Project.toml | 1 - test/design.jl | 36 +++---- test/helper.jl | 10 +- test/noise.jl | 8 +- test/onset.jl | 46 ++++++--- test/setup.jl | 4 +- 21 files changed, 295 insertions(+), 254 deletions(-) diff --git a/docs/literate/HowTo/newComponent.jl b/docs/literate/HowTo/newComponent.jl index 6eea979b..bb8bee72 100644 --- a/docs/literate/HowTo/newComponent.jl +++ b/docs/literate/HowTo/newComponent.jl @@ -30,7 +30,7 @@ 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) + evts = generate_events(design) return c.basisfunction(evts, c.maxlength) end @@ -38,16 +38,16 @@ end function basis_shiftduration(evts, maxlength) basis = hanning.(Int.(round.(evts.duration))) ## hanning as long as duration if "shift" ∈ names(evts) - basis = padarray.(basis, Int.(round.(.-evts.shift)), 0) ## shift by adding 0 in front + basis = pad_array.(basis, Int.(round.(.-evts.shift)), 0) ## shift by adding 0 in front end ## we should make sure that all bases have maxlength by appending / truncating difftomax = maxlength .- length.(basis) if any(difftomax .< 0) @warn "basis longer than max length in at least one case. either increase maxlength or redefine function. Trying to truncate the basis" - basis[difftomax.>0] = padarray.(basis[difftomax.>0], difftomax[difftomax.>0], 0) + basis[difftomax.>0] = pad_array.(basis[difftomax.>0], difftomax[difftomax.>0], 0) return [b[1:maxlength] for b in basis] else - return padarray.(basis, difftomax, 0) + return pad_array.(basis, difftomax, 0) end end @@ -57,4 +57,4 @@ erp = UnfoldSim.simulate( TimeVaryingComponent(basis_shiftduration, 50), design, ) -plot_erpimage(hcat(erp...), sortvalues = generate(design).shift) +plot_erpimage(hcat(erp...), sortvalues = generate_events(design).shift) diff --git a/docs/literate/HowTo/newDesign.jl b/docs/literate/HowTo/newDesign.jl index 4931bc08..955044d2 100644 --- a/docs/literate/HowTo/newDesign.jl +++ b/docs/literate/HowTo/newDesign.jl @@ -24,8 +24,8 @@ end; 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) +# We need a type `generate_events(design::ImbalanceSubjectDesign)` function. This function should return the actual table as a `DataFrame` +function generate_events(design::ImbalanceSubjectDesign) nA = Int(round.(design.nTrials .* design.balance)) nB = Int(round.(design.nTrials .* (1 - design.balance))) @assert nA + nB ≈ design.nTrials @@ -35,7 +35,7 @@ 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) +generate_events(design) # !!! warning "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 diff --git a/docs/literate/HowTo/predefinedData.jl b/docs/literate/HowTo/predefinedData.jl index 02fd68bd..fc5b9d9e 100644 --- a/docs/literate/HowTo/predefinedData.jl +++ b/docs/literate/HowTo/predefinedData.jl @@ -13,7 +13,7 @@ my_events = DataFrame(:condition => [:A, :B, :B, :A, :A], :latency => [7, 13, 22 struct MyManualDesign <: AbstractDesign my_events::Any end -UnfoldSim.generate(d::MyManualDesign) = deepcopy(d.my_events) ## generate function which is called internally in UnfoldSim +UnfoldSim.generate_events(d::MyManualDesign) = deepcopy(d.my_events) ## generate function which is called internally in UnfoldSim UnfoldSim.size(d::MyManualDesign) = size(d.my_events, 1); ## necessary function to tell what the dimensionality of the experimental design is # !!! note # Note the `UnfoldSim.generate` which tells Julia to "overload" the `generate` function as defined in UnfoldSim @@ -39,8 +39,8 @@ current_figure() # ## Custom Timings # Finally, we want to use our custom timings as well. For this we define a new `AbstractOnset`. Again, it simply returns our manually provided latencies struct MyManualOnset <: AbstractOnset end -UnfoldSim.generate(rng, onset::MyManualOnset, simulation::Simulation) = - generate(simulation.design).latency +UnfoldSim.simulate_onsets(rng, onset::MyManualOnset, simulation::Simulation) = + generate_events(simulation.design).latency # !!! hint # This is a bit of a trick, it relies that `MyManualOnset`` is always used in combination with `MyManualDesign`. You could of course repeat the structure from `MyManualDesign` also for `MyManualOnset` and have an explicit field in the structure containing the onsets. diff --git a/docs/literate/HowTo/repeatTrials.jl b/docs/literate/HowTo/repeatTrials.jl index e544e167..046bfb75 100644 --- a/docs/literate/HowTo/repeatTrials.jl +++ b/docs/literate/HowTo/repeatTrials.jl @@ -16,7 +16,7 @@ designOnce = MultiSubjectDesign(; ); design = RepeatDesign(designOnce, 4); -generate(design) +generate_events(design) # As you can see, the design was simply repeated. diff --git a/docs/literate/reference/basistypes.jl b/docs/literate/reference/basistypes.jl index 7e0f3aac..c04d73cc 100644 --- a/docs/literate/reference/basistypes.jl +++ b/docs/literate/reference/basistypes.jl @@ -26,11 +26,11 @@ f f = Figure() plotConfig = ( :peak => 1:3:10, - :psUnder => 10:5:30, + :post_undershoot => 10:5:30, :amplitude => 2:5, :shift => 0:3:10, :peak_width => 0.1:0.5:1.5, - :psUnder_width => 0.1:0.5:1.5, + :post_undershoot_width => 0.1:0.5:1.5, ) for (ix, pl) in enumerate(plotConfig) diff --git a/docs/literate/reference/noisetypes.jl b/docs/literate/reference/noisetypes.jl index ef943925..2057857d 100644 --- a/docs/literate/reference/noisetypes.jl +++ b/docs/literate/reference/noisetypes.jl @@ -13,7 +13,7 @@ ax_auto = f[2, 2] = Axis(f; title = "Autocorrelogram (every 10th lag)") for n in [PinkNoise RedNoise WhiteNoise NoNoise ExponentialNoise] ## generate - noisevec = gen_noise(StableRNG(1), n(), 10000) + noisevec = simulate_noise(StableRNG(1), n(), 10000) ## plot 1000 samples lines!(ax_sig, noisevec[1:1000]; label = string(n)) diff --git a/docs/literate/reference/onsettypes.jl b/docs/literate/reference/onsettypes.jl index 554126f2..279a2226 100644 --- a/docs/literate/reference/onsettypes.jl +++ b/docs/literate/reference/onsettypes.jl @@ -52,13 +52,18 @@ let # hide ## Go through all parameter combinations and plot a histogram of the sampled onsets # hide for (width, offset) in combinations # hide - onsets = UnfoldSim.rand_onsets( # hide + distances = UnfoldSim.simulate_interonset_distances( # hide MersenneTwister(42), # hide UniformOnset(; width = width, offset = offset), # hide design, # hide ) # hide - hist!(ax, onsets, bins = range(0, 100, step = 1), label = "($width, $offset)") # hide + hist!( + ax, + distances, + bins = range(0, 100, step = 1), + label = "($width, $offset)", + ) # hide if label == "offset" && offset != 0 # hide vlines!(offset, color = "black") # hide @@ -99,7 +104,7 @@ let ## Go through all parameter combinations and plot a histogram of the sampled onsets for (width, offset) in combinations - onsets = UnfoldSim.rand_onsets( + onsets = UnfoldSim.simulate_interonset_distances( MersenneTwister(42), UniformOnset(; width = width, offset = offset), design, @@ -163,7 +168,7 @@ let # hide ## Go through all parameter combinations and plot a histogram of the sampled onsets # hide for (μ, σ, offset, truncate_upper) in combinations # hide - onsets = UnfoldSim.rand_onsets( # hide + onsets = UnfoldSim.simulate_interonset_distances( # hide MersenneTwister(42), # hide LogNormalOnset(; # hide μ = μ, # hide @@ -228,7 +233,7 @@ let ## Go through all parameter combinations and plot a histogram of the sampled onsets for (μ, σ, offset, truncate_upper) in combinations - onsets = UnfoldSim.rand_onsets( + onsets = UnfoldSim.simulate_interonset_distances( MersenneTwister(42), LogNormalOnset(; μ = μ, diff --git a/src/UnfoldSim.jl b/src/UnfoldSim.jl index 7f165369..cd1b0287 100644 --- a/src/UnfoldSim.jl +++ b/src/UnfoldSim.jl @@ -52,10 +52,16 @@ export MultiSubjectDesign, SingleSubjectDesign, RepeatDesign export PinkNoise, RedNoise, WhiteNoise, NoNoise, ExponentialNoise #,RealNoise (not implemented yet) # UnfoldSim functions -export simulate, gen_noise, generate +export simulate, + simulate_responses, + simulate_interonset_distances, + simulate_component, + simulate_onsets, + simulate_noise, + generate_events # utilities -export padarray, convert +export pad_array, convert # export Offsets export UniformOnset, LogNormalOnset, NoOnset @@ -64,7 +70,7 @@ export UniformOnset, LogNormalOnset, NoOnset export DummyCoding, EffectsCoding # export bases -export p100, n170, p300, n400, hrf +export p100, n170, p300, n400, hrf, PuRF # headmodel export AbstractHeadmodel, Hartmut, headmodel, leadfield, orientation, magnitude diff --git a/src/bases.jl b/src/bases.jl index 0d7b4a1e..d496f5a4 100644 --- a/src/bases.jl +++ b/src/bases.jl @@ -16,7 +16,7 @@ sfreq: sampling rate in Hz """ function DSP.hanning(duration, offset, sfreq) signal = hanning(Int(round(duration * sfreq))) - return padarray(signal, -Int(round(offset * sfreq / 2)), 0) + return pad_array(signal, -Int(round(offset * sfreq / 2)), 0) end ## pupil @@ -41,10 +41,10 @@ Code adapted from Unfold.jl function hrf(; TR = 1, peak = 6.0, - psUnder = 16, + post_undershoot = 16, length = 32.0, peak_width = 1.0, - psUnder_width = 1, + post_undershoot_width = 1, amplitude = 6, shift = 0, ) @@ -63,7 +63,7 @@ function hrf(; # Note the inverted scale parameter compared to SPM12. g1 = Gamma(peak ./ peak_width, peak_width ./ dt) #spm_Gpdf(u,p(2)/p(4),dt/p(4)) - g2 = Gamma(psUnder ./ psUnder_width, psUnder_width ./ dt) + g2 = Gamma(post_undershoot ./ post_undershoot_width, post_undershoot_width ./ dt) # g1 - g2/p(5); hrf = pdf.(g1, u) .- pdf.(g2, u) ./ amplitude diff --git a/src/component.jl b/src/component.jl index fa2617e6..5d571de4 100644 --- a/src/component.jl +++ b/src/component.jl @@ -71,13 +71,13 @@ MultichannelComponent(c::AbstractComponent, p) = MultichannelComponent(c::AbstractComponent, p, NoNoise()) function MultichannelComponent( - c::AbstractComponent, - p::Pair{<:AbstractHeadmodel,String}, - n::AbstractNoise, + component::AbstractComponent, + projection::Pair{<:AbstractHeadmodel,String}, + noise::AbstractNoise, ) - ix = closest_src(p[1], p[2]) - mg = magnitude(p[1]) - return MultichannelComponent(c, mg[:, ix], n) + ix = closest_src(projection[1], projection[2]) + mg = magnitude(projection[1]) + return MultichannelComponent(component, mg[:, ix], noise) end Base.length(c::MultichannelComponent) = length(c.component) @@ -98,11 +98,11 @@ function n_channels(c::Vector{<:AbstractComponent}) return all_channels[1] end -function simulate(rng, c::MultichannelComponent, design::AbstractDesign) - y = simulate(rng, c.component, design) +function simulate_component(rng, c::MultichannelComponent, design::AbstractDesign) + y = simulate_component(rng, c.component, design) - for tr = 1:size(y, 2) - y[:, tr] .= y[:, tr] .+ gen_noise(rng, c.noise, size(y, 1)) + for trial = 1:size(y, 2) + y[:, trial] .= y[:, trial] .+ simulate_noise(rng, c.noise, size(y, 1)) end y_proj = kron(y, c.projection) @@ -118,28 +118,28 @@ maxlength(c::Vector{AbstractComponent}) = maximum(length.(c)) # by default call simulate with `::Abstractcomponent,::AbstractDesign``, but allow for custom types # making use of other information in simulation """ -simulate(rng, c::AbstractComponent, simulation::Simulation) = - simulate(rng, c, simulation.design) +simulate_component(rng, c::AbstractComponent, simulation::Simulation) = + simulate_component(rng, c, simulation.design) """ simulate a linearModel julia> c = UnfoldSim.LinearModelComponent([0,1,1,0],@formula(0~1+cond),[1,2],Dict()) julia> design = MultiSubjectDesign(;n_subjects=2,n_items=50,item_between=(;:cond=>["A","B"])) -julia> simulate(StableRNG(1),c,design) +julia> simulate_component(StableRNG(1),c,design) """ -function simulate(rng, c::LinearModelComponent, design::AbstractDesign) - evts = generate(design) +function simulate_component(rng, c::LinearModelComponent, design::AbstractDesign) + events = generate_events(design) # special case, intercept only # https://github.com/JuliaStats/StatsModels.jl/issues/269 if c.formula.rhs == ConstantTerm(1) - X = ones(nrow(evts), 1) + X = ones(nrow(events), 1) else if isempty(c.contrasts) - m = StatsModels.ModelFrame(c.formula, evts) + m = StatsModels.ModelFrame(c.formula, events) else - m = StatsModels.ModelFrame(c.formula, evts; contrasts = c.contrasts) + m = StatsModels.ModelFrame(c.formula, events; contrasts = c.contrasts) end X = StatsModels.modelmatrix(m) end @@ -154,20 +154,20 @@ julia> c = UnfoldSim.MixedModelComponent([0.,1,1,0],@formula(0~1+cond+(1|subject julia> simulate(StableRNG(1),c,design) """ -function simulate(rng, c::MixedModelComponent, design::AbstractDesign) - evts = generate(design) +function simulate_component(rng, c::MixedModelComponent, design::AbstractDesign) + events = generate_events(design) # add the mixed models lefthandside lhs_column = :tmp_dv - @assert string(lhs_column) ∉ names(evts) "Error: Wow you are unlucky, we have to introduce a temporary lhs-symbol which we name ``:tmp_dv` - you seem to have a condition called `:tmp_dv` in your dataset as well. Please rename it!" + @assert string(lhs_column) ∉ names(events) "Error: Wow you are unlucky, we have to introduce a temporary lhs-symbol which we name ``:tmp_dv` - you seem to have a condition called `:tmp_dv` in your dataset as well. Please rename it!" f = FormulaTerm(Term(:tmp_dv), c.formula.rhs) - evts[!, lhs_column] .= 0 + events[!, lhs_column] .= 0 # create dummy if isempty(c.contrasts) - m = MixedModels.MixedModel(f, evts) + m = MixedModels.MixedModel(f, events) else - m = MixedModels.MixedModel(f, evts; contrasts = c.contrasts) + m = MixedModels.MixedModel(f, events; contrasts = c.contrasts) end @@ -177,8 +177,8 @@ function simulate(rng, c::MixedModelComponent, design::AbstractDesign) # residual variance for lmm σ_lmm = 0.0001 if 1 == 1 - namedre = weight_σs(c.σs, 1.0, σ_lmm) - θ = createθ(m; namedre...) + named_random_effects = weight_σs(c.σs, 1.0, σ_lmm) + θ = createθ(m; named_random_effects...) simulate!(deepcopy(rng), m.y, m; β = c.β, σ = σ_lmm, θ = θ) # save data to array @@ -200,9 +200,9 @@ function simulate(rng, c::MixedModelComponent, design::AbstractDesign) # weight random effects by the basis function - namedre = weight_σs(c.σs, basis_σs, σ_lmm) + named_random_effects = weight_σs(c.σs, basis_σs, σ_lmm) - θ = createθ(m; namedre...) + θ = createθ(m; named_random_effects...) # simulate with new parameters; will update m.y @@ -247,7 +247,40 @@ function weight_σs(σs::Dict, b_σs::Float64, σ_lmm::Float64) push!(vals, v) end - namedre = NamedTuple(keys .=> vals) + named_random_effects = NamedTuple(keys .=> vals) + + return named_random_effects +end + +#---- - return namedre +""" +Simulates multiple component responses and accumulates them on a per-event basis +""" +function simulate_responses( + rng, + components::Vector{<:AbstractComponent}, + simulation::Simulation, +) + if n_channels(components) > 1 + epoch_data = + zeros(n_channels(components), maxlength(components), length(simulation.design)) + else + epoch_data = zeros(maxlength(components), length(simulation.design)) + end + + for c in components + simulate_and_add!(epoch_data, c, simulation, rng) + end + return epoch_data +end + + +function simulate_and_add!(epoch_data::AbstractMatrix, c, simulation, rng) + @debug "matrix" + @views epoch_data[1:length(c), :] .+= simulate_component(rng, c, simulation) +end +function simulate_and_add!(epoch_data::AbstractArray, c, simulation, rng) + @debug "3D Array" + @views epoch_data[:, 1:length(c), :] .+= simulate_component(rng, c, simulation) end diff --git a/src/design.jl b/src/design.jl index 4666c800..46dc989b 100644 --- a/src/design.jl +++ b/src/design.jl @@ -6,7 +6,7 @@ - both_within = nothing -> effects completly crossed - event_order_function = 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)` +tipp: check the resulting dataframe using `generate_events(design)` ```julia # declaring same condition both sub-between and item-between results in a full between subject/item design @@ -32,13 +32,13 @@ end - conditions = Dict of conditions, e.g. `Dict(:A=>["a_small","a_big"],:B=>["b_tiny","b_large"])` - event_order_function = 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`. +Number of trials / rows in `generate_events(design)` depend on the full factorial of your `conditions`. To increase the number of repetitions simply use `RepeatDesign(SingleSubjectDesign(...),5)` If conditions are omitted (or set to `nothing`), a single trial is simulated with a column `:dummy` and content `:dummy` - this is for convenience. -tipp: check the resulting dataframe using `generate(design)` +tipp: check the resulting dataframe using `generate_events(design)` """ @with_kw struct SingleSubjectDesign <: AbstractDesign conditions = nothing @@ -47,32 +47,33 @@ end """ Returns dimension of experiment design""" -size(expdesign::MultiSubjectDesign) = (expdesign.n_items, expdesign.n_subjects) -size(expdesign::SingleSubjectDesign) = (*(length.(values(expdesign.conditions))...),) +size(design::MultiSubjectDesign) = (design.n_items, design.n_subjects) +size(design::SingleSubjectDesign) = (*(length.(values(design.conditions))...),) """ -Generates full-factorial DataFrame of expdesign.conditions +Generates full-factorial DataFrame of design.conditions -Afterwards applies expdesign.event_order_function. + +Afterwards applies design.event_order_function. If conditions is `nothing`, a single trial is simulated with a column `:dummy` and content `:dummy` - this is for convenience. julia> d = SingleSubjectDesign(;conditions= Dict(:A=>nlevels(5),:B=>nlevels(2))) -julia> generate(d) +julia> generate_events(d) """ -function generate(expdesign::SingleSubjectDesign) - if isnothing(expdesign.conditions) - evts = DataFrame(:dummy => [:dummy]) +function generate_events(design::SingleSubjectDesign) + if isnothing(design.conditions) + events = DataFrame(:dummy => [:dummy]) else # we get a Dict(:A=>["1","2"],:B=>["3","4"]), but needed a list # of named tuples for MixedModelsSim.factorproduct function. - evts = - factorproduct(((; k => v) for (k, v) in pairs(expdesign.conditions))...) |> + events = + factorproduct(((; k => v) for (k, v) in pairs(design.conditions))...) |> DataFrame end # by default does nothing - return expdesign.event_order_function(evts) + return design.event_order_function(events) end """ @@ -81,36 +82,34 @@ Note: n_items = 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.event_order_function. Could be used to duplicate trials, sort, subselect etc. +Afterwards applies design.event_order_function. Could be used to duplicate trials, sort, subselect etc. Finally it sorts by `:subject` julia> d = MultiSubjectDesign(;n_subjects = 10,n_items=20,both_within= Dict(:A=>nlevels(5),:B=>nlevels(2))) -julia> generate(d) +julia> generate_events(d) """ -function generate(expdesign::MultiSubjectDesign) - #generate(expdesign::AbstractDesign) = generate(MersenneTwister(1),expdesign) +function generate_events(design::MultiSubjectDesign) # check that :dv is not in any condition - allconditions = - [expdesign.subjects_between, expdesign.items_between, expdesign.both_within] + allconditions = [design.subjects_between, design.items_between, design.both_within] @assert all(isnothing.(allconditions)) || :dv ∉ keys(merge(allconditions[.!isnothing.(allconditions)]...)) "due to technical limitations in MixedModelsSim.jl, `:dv` cannot be used as a factorname" data = DataFrame( MixedModelsSim.simdat_crossed( - expdesign.n_subjects, - expdesign.n_items, - subj_btwn = expdesign.subjects_between, - item_btwn = expdesign.items_between, - both_win = expdesign.both_within, + design.n_subjects, + design.n_items, + subj_btwn = design.subjects_between, + item_btwn = design.items_between, + both_win = design.both_within, ), ) rename!(data, :subj => :subject) select!(data, Not(:dv)) # remove the default column from MixedModelsSim.jl - we don't need it in UnfoldSim.jl # by default does nothing - data = expdesign.event_order_function(data) + data = design.event_order_function(data) # sort by subject data = sort!(data, (order(:subject))) @@ -121,7 +120,7 @@ end # length is the same of all dimensions -length(expdesign::AbstractDesign) = *(size(expdesign)...) +length(design::AbstractDesign) = *(size(design)...) @@ -146,8 +145,8 @@ design = RepeatDesign(designOnce,4); repeat::Int = 1 end -function UnfoldSim.generate(design::RepeatDesign) - df = map(x -> generate(design.design), 1:design.repeat) |> x -> vcat(x...) +function UnfoldSim.generate_events(design::RepeatDesign) + df = map(x -> generate_events(design.design), 1:design.repeat) |> x -> vcat(x...) if isa(design.design, MultiSubjectDesign) sort!(df, [:subject]) end diff --git a/src/helper.jl b/src/helper.jl index c2609498..3c4ef541 100755 --- a/src/helper.jl +++ b/src/helper.jl @@ -1,42 +1,45 @@ """ Pads array with specified value, length -padarray(arr, len, val) +pad_array(arr, len, val) """ -padarray(arr::Vector, len::Tuple, val) = padarray(padarray(arr, len[1], val), len[2], val) -function padarray(arr::Vector, len::Int, val) +pad_array(arr::Vector, len::Tuple, val) = + pad_array(pad_array(arr, len[1], val), len[2], val) +function pad_array(arr::Vector, len::Int, val) pad = fill(val, abs(len)) arr = len > 0 ? vcat(arr, pad) : vcat(pad, arr) return arr end -# TODO: Transfer function to Unfold.jl + """ -Function to convert output similar to unfold (data, evts) +Obsolete - # TODO: Transfer function to Unfold.jl + +Function to convert output similar to unfold (data, events) """ -function convert(eeg, onsets, design, n_ch, ; reshape = true) - evt = UnfoldSim.generate(design) +function convert(eeg, onsets, design, n_chan; reshape = true) + events = UnfoldSim.generate_events(design) @debug size(eeg) if reshape - n_subj = length(size(design)) == 1 ? 1 : size(design)[2] + n_subjects = length(size(design)) == 1 ? 1 : size(design)[2] - if n_ch == 1 + if n_chan == 1 data = eeg[:,] - evt.latency = (onsets' .+ range(0, size(eeg, 2) - 1) .* size(eeg, 1))'[:,] - elseif n_subj == 1 + events.latency = (onsets' .+ range(0, size(eeg, 2) - 1) .* size(eeg, 1))'[:,] + elseif n_subjects == 1 data = eeg @debug size(onsets) - evt.latency = onsets + events.latency = onsets else # multi subject + multi channel data = eeg[:, :] - evt.latency = (onsets' .+ range(0, size(eeg, 3) - 1) .* size(eeg, 2))'[:,] + events.latency = (onsets' .+ range(0, size(eeg, 3) - 1) .* size(eeg, 2))'[:,] end else data = eeg end - return data, evt + return data, events end @@ -108,16 +111,16 @@ function epoch( # partial taken from EEG.jl - numEpochs = size(events, 1) + n_epochs = size(events, 1) times = range(τ[1], stop = τ[2], step = 1 ./ sfreq) - lenEpochs = length(times) - numChans = size(data, 1) - epochs = Array{T}(undef, Int(numChans), Int(lenEpochs), Int(numEpochs)) + length_epochs = length(times) + n_chan = size(data, 1) + epochs = Array{T}(undef, Int(n_chan), Int(length_epochs), Int(n_epochs)) # User feedback - @debug "Creating epochs: $numChans x $lenEpochs x $numEpochs" + @debug "Creating epochs: $n_chan x $length_epochs x $n_epochs" for si ∈ 1:size(events, 1) # d_start and d_end are the start and end of the epoch (in samples) in the data @@ -126,7 +129,7 @@ function epoch( # e_start and e_end are the start and end within the epoch (in samples) e_start = 1 - e_end = lenEpochs + e_end = length_epochs #println("d: $(size(data)),e: $(size(epochs)) | $d_start,$d_end,$e_start,$e_end | $(events[si,eventtime])") # Case that the start of the epoch is before the start of the data/recording (e.g. if the start is before i.e. negative relative to the event) diff --git a/src/noise.jl b/src/noise.jl index 69c9b327..3bae512b 100644 --- a/src/noise.jl +++ b/src/noise.jl @@ -44,24 +44,24 @@ end """ - gen_noise(t::Union{PinkNoise, RedNoise}, n::Int) + simulate_noise(t::Union{PinkNoise, RedNoise}, n::Int) Generate noise of a given type t and length n """ -function gen_noise(rng, t::Union{PinkNoise,RedNoise}, n::Int) +function simulate_noise(rng, t::Union{PinkNoise,RedNoise}, n::Int) return t.noiselevel .* rand(rng, t.func(n, 1.0)) end -function gen_noise(rng, t::NoNoise, n::Int) +function simulate_noise(rng, t::NoNoise, n::Int) return zeros(n) end """ - gen_noise(t::WhiteNoise, n::Int) + simulate_noise(t::WhiteNoise, n::Int) Generate noise of a given type t and length n """ -function gen_noise(rng, t::WhiteNoise, n::Int) +function simulate_noise(rng, t::WhiteNoise, n::Int) noisevector = randn(rng, n) if !isnothing(t.imfilter) noisevector = imfilter(noisevector, Kernel.gaussian((t.imfilter,))) @@ -71,27 +71,47 @@ end """ - gen_noise(t::RealisticNoise, n::Int) + simulate_noise(t::RealisticNoise, n::Int) Generate noise of a given type t and length n """ -function gen_noise(rng, t::RealisticNoise, n::Int) +function simulate_noise(rng, t::RealisticNoise, n::Int) error("not implemented") return 0 end -function gen_noise(rng, t::ExponentialNoise, n::Int) +function simulate_noise(rng, t::ExponentialNoise, n::Int) - function exponentialCorrelation(x; nu = 1, length_ratio = 1) + function exponential_correlation(x; nu = 1, length_ratio = 1) # Author: Jaromil Frossard # generate exponential function R = length(x) * length_ratio return exp.(-3 * (x / R) .^ nu) end - Σ = Symmetric(Circulant(exponentialCorrelation([0:1:(n-1);], nu = t.ν)), :L) + Σ = Symmetric(Circulant(exponential_correlation([0:1:(n-1);], nu = t.ν)), :L) # cholesky(Σ) is n x n diagonal, lots of RAM :S return t.noiselevel .* 10 .* (randn(rng, n)'*cholesky(Σ).U)[1, :] end + + + + +""" +Generate and add noise to the data-matrix + +Assumes that the signal can be linearized, that is, that the noise is stationary +""" +function add_noise!(rng, noisetype::AbstractNoise, signal) + + # generate noise + noise = simulate_noise(deepcopy(rng), noisetype, length(signal)) + + noise = reshape(noise, size(signal)) + + # add noise to data + signal .+= noise + +end diff --git a/src/onset.jl b/src/onset.jl index c0188ca6..f3e4fa2b 100644 --- a/src/onset.jl +++ b/src/onset.jl @@ -17,13 +17,13 @@ end struct NoOnset <: AbstractOnset end #------------- -function rand_onsets(rng, onset::UniformOnset, design::AbstractDesign) +function simulate_interonset_distances(rng, onset::UniformOnset, design::AbstractDesign) return Int.( round.(rand(deepcopy(rng), onset.offset:(onset.offset+onset.width), size(design))) ) end -function rand_onsets(rng, onset::LogNormalOnset, design::AbstractDesign) +function simulate_interonset_distances(rng, onset::LogNormalOnset, design::AbstractDesign) s = size(design) fun = LogNormal(onset.μ, onset.σ) if !isnothing(onset.truncate_upper) @@ -34,10 +34,10 @@ end # main call from `simulation` -function generate(rng, onset::AbstractOnset, simulation::Simulation) +function simulate_onsets(rng, onset::AbstractOnset, simulation::Simulation) # sample different onsets - onsets = rand_onsets(rng, onset, simulation.design) + onsets = simulate_interonset_distances(rng, onset, simulation.design) # accumulate them onsets_accum = accumulate(+, onsets, dims = 1) diff --git a/src/simulation.jl b/src/simulation.jl index 42a85b16..fbbe7309 100755 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -16,7 +16,7 @@ Simulation( return_epoched=false, ) -Main simulation function, given `Design`, [Array of] `Component`, `Onset` and optional [`Noise`], returns continuous or epoched EEG data. +Main simulation function, given `Design`, [Array of] `Component`, `Onset` and optional [`Noise`], returns continuous or epoched signal. ## optional - `return_epoched` (Bool, default: `false`): Skip the Onset-calculation and conversion to continuous data and return the epoched data directly (see also remarks below). @@ -27,7 +27,7 @@ Depending on the design, the components and on `return_epoched`, the output can ## Notes Some remarks to how the noise is added: - If `return_epoched = true` and `onset =NoOnset()` the noise is added to the epoched data matrix - - If `onset` is not `NoOnset`, a continuous eeg signal is created and the noise is added to this i.e. this means that the noise won't be the same as in the `onset = NoOnset()` case even if `return_epoched = true`. + - If `onset` is not `NoOnset`, a continuous signal is created and the noise is added to this i.e. this means that the noise won't be the same as in the `onset = NoOnset()` case even if `return_epoched = true`. - The case `return_epoched = false` and `onset = NoOnset()` is not possible and therefore covered by an assert statement """ @@ -47,158 +47,116 @@ function simulate(rng, simulation::Simulation; return_epoched::Bool = false) # equivalent to !(isa(onset,NoOnset) && return_epoched == false) @assert !isa(onset, NoOnset) || return_epoched == true "It is not possible to get continuous data without specifying a specific onset distribution. Please either specify an onset distribution (other than `NoOnset`) or set `return_epoched = true` to get epoched data without overlap." - # create epoch data / erps - erps = simulate(deepcopy(rng), components, simulation) + # create epoch data / responses + responses = simulate_responses(deepcopy(rng), components, simulation) # create events data frame - events = UnfoldSim.generate(design) + events = UnfoldSim.generate_events(design) if isa(onset, NoOnset) - # reshape the erps such that the last dimension is split in two dimensions (trials per subject and subject) + # reshape the responses such that the last dimension is split in two dimensions (trials per subject and subject) # such that the resulting dimensions are dimensions: channels x times x trials x subjects - # TODO: This assumes a balanced design, but create_continuous_eeg also assumes this, so we should be fine ;) - size_erps = size(erps) - eeg = reshape(erps, size_erps[1:end-1]..., size(design)...) - else # if there is an onset distribution given the next step is to create a continuous EEG - eeg, latencies = create_continuous_eeg(rng, erps, simulation) + # TODO: This assumes a balanced design, but create_continuous_signal also assumes this, so we should be fine ;) + size_responses = size(responses) + signal = reshape(responses, size_responses[1:end-1]..., size(design)...) + else # if there is an onset distribution given the next step is to create a continuous signal + signal, latencies = create_continuous_signal(rng, responses, simulation) events.latency = latencies end - add_noise!(deepcopy(rng), noisetype, eeg) + add_noise!(deepcopy(rng), noisetype, signal) # In case the data should be epoched & onset distribution is given i.e. the signals might be overlapping if return_epoched && !isa(onset, NoOnset) # use epoch function to epoch the continuous (possibly overlapping) signal if length(size(design)) == 1 # if there is only one subject - eeg = epoch(eeg, events, (0, maxlength(components) - 1), 1) + signal = epoch(signal, events, (0, maxlength(components) - 1), 1) else # multi-subject case - evt_epoch = groupby(events, :subject) |> collect + events_epoch = groupby(events, :subject) |> collect # Epoch data per subject # Note: Ref() is needed to prevent broadcasting of τ and sfreq (due to applying epoch elementwise) - eeg = + signal = epoch.( - eachslice(eeg, dims = length(size(eeg))), - evt_epoch, + eachslice(signal, dims = length(size(signal))), + events_epoch, Ref((0, maxlength(components) - 1)), Ref(1), ) - # TODO: This assumes a balanced design, but create_continuous_eeg also assumes this, so we should be fine ;) + # TODO: This assumes a balanced design, but create_continuous_signal also assumes this, so we should be fine ;) # Concatenate the epoched data of all subjects again. - eeg = reshape(reduce(hcat, vec.(eeg)), size(eeg[1])..., length(eeg)) - #cat(eeg..., dims = length(size(erps)) + 1) #TODO: find a way to use always the right dims + signal = reshape(reduce(hcat, vec.(signal)), size(signal[1])..., length(signal)) + #cat(signal..., dims = length(size(responses)) + 1) #TODO: find a way to use always the right dims end end - return eeg, events + return signal, events end -function create_continuous_eeg(rng, erps, simulation) +function create_continuous_signal(rng, responses, simulation) (; design, components, onset, noisetype) = simulation - n_subj = length(size(design)) == 1 ? 1 : size(design)[2] - n_trial = size(design)[1] - n_ch = n_channels(components) + n_subjects = length(size(design)) == 1 ? 1 : size(design)[2] + n_trials = size(design)[1] + n_chan = n_channels(components) # we only need to simulate onsets & pull everything together, if we - # want a continuous EEG - onsets = generate(deepcopy(rng), onset, simulation) + # want a continuous signal + onsets = simulate_onsets(deepcopy(rng), onset, simulation) # flatten onsets (since subjects are concatenated in the events df) latencies = onsets[:,] - # combine erps with onsets + # combine responses with onsets max_length_component = maxlength(components) max_length_continuoustime = Int(ceil(maximum(onsets))) .+ max_length_component - eeg = zeros(n_ch, max_length_continuoustime, n_subj) + signal = zeros(n_chan, max_length_continuoustime, n_subjects) - for e = 1:n_ch - for s = 1:n_subj - for i = 1:n_trial + for e = 1:n_chan + for s = 1:n_subjects + for i = 1:n_trials one_onset = onsets[CartesianIndex(i, s)] - adderp!( - eeg, - erps, + add_responses!( + signal, + responses, e, s, one_onset:one_onset+max_length_component-1, - (s - 1) * n_trial + i, + (s - 1) * n_trials + i, ) end end end # not all designs have multiple subjects - if n_subj == 1 - eeg = dropdims(eeg, dims = 3) + if n_subjects == 1 + signal = dropdims(signal, dims = 3) end # not all designs have multiple channels - if n_ch == 1 - eeg = dropdims(eeg, dims = 1) + if n_chan == 1 + signal = dropdims(signal, dims = 1) end - return eeg, latencies + return signal, latencies end """ -Helper function to add inplace the erps to the EEG, but for both 2D (1 channel) and 3D (X channel case) +Helper function to add inplace the responses to the signal, but for both 2D (1 channel) and 3D (X channel case) """ -function adderp!(eeg, erps::Vector, e, s, tvec, erpvec) - @views eeg[e, tvec, s] .+= erps[:, erpvec] +function add_responses!(signal, responses::Vector, e, s, tvec, erpvec) + @views signal[e, tvec, s] .+= responses[:, erpvec] end -function adderp!(eeg, erps::Matrix, e, s, tvec, erpvec)# - @views eeg[e, tvec, s] .+= erps[:, erpvec] +function add_responses!(signal, responses::Matrix, e, s, tvec, erpvec)# + @views signal[e, tvec, s] .+= responses[:, erpvec] end -function adderp!(eeg, erps::AbstractArray, e, s, tvec, erpvec) - @views eeg[e, tvec, s] .+= erps[e, :, erpvec] -end - - - -""" -Simulates erp data given the specified parameters -""" -function simulate(rng, components::Vector{<:AbstractComponent}, simulation::Simulation) - if n_channels(components) > 1 - epoch_data = - zeros(n_channels(components), maxlength(components), length(simulation.design)) - else - epoch_data = zeros(maxlength(components), length(simulation.design)) - end - - for c in components - simulateandadd!(epoch_data, c, simulation, rng) - end - return epoch_data -end -function simulateandadd!(epoch_data::AbstractMatrix, c, simulation, rng) - @debug "matrix" - @views epoch_data[1:length(c), :] .+= simulate(rng, c, simulation) -end -function simulateandadd!(epoch_data::AbstractArray, c, simulation, rng) - @debug "3D Array" - @views epoch_data[:, 1:length(c), :] .+= simulate(rng, c, simulation) -end - - - - -function add_noise!(rng, noisetype::AbstractNoise, eeg) - - # generate noise - noise = gen_noise(deepcopy(rng), noisetype, length(eeg)) - - noise = reshape(noise, size(eeg)) - - # add noise to data - eeg .+= noise - +function add_responses!(signal, responses::AbstractArray, e, s, tvec, erpvec) + @views signal[e, tvec, s] .+= responses[e, :, erpvec] end diff --git a/test/Project.toml b/test/Project.toml index a12cea71..e6b8ec6d 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -6,4 +6,3 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -UnfoldSim = "ed8ae6d2-84d3-44c6-ab46-0baf21700804" diff --git a/test/design.jl b/test/design.jl index 338f22e3..62dcd678 100644 --- a/test/design.jl +++ b/test/design.jl @@ -2,17 +2,19 @@ @testset "SingleSubjectDesign" begin des = SingleSubjectDesign(; conditions = Dict(:A => nlevels(5), :B => nlevels(2))) - @test generate(des) == generate(des) - @test size(generate(des)) == (5 * 2, 2) - @test names(generate(des)) == ["A", "B"] - @test sum(generate(des).A .== "S2") == 2 - @test sum(generate(des).B .== "S2") == 5 - @test sum((generate(des).B .== "S2") .&& (generate(des).A .== "S2")) == 1 + @test generate_events(des) == generate_events(des) + @test size(generate_events(des)) == (5 * 2, 2) + @test names(generate_events(des)) == ["A", "B"] + @test sum(generate_events(des).A .== "S2") == 2 + @test sum(generate_events(des).B .== "S2") == 5 + @test sum( + (generate_events(des).B .== "S2") .&& (generate_events(des).A .== "S2"), + ) == 1 des = SingleSubjectDesign(; conditions = Dict(:A => nlevels(5), :B => nlevels(2)), event_order_function = x -> sort(x, order(:B, rev = true)), ) - @test generate(des).B[1] == "S2" + @test generate_events(des).B[1] == "S2" end @testset "MultiSubjectDesign" begin @@ -23,10 +25,10 @@ ) - @test size(generate(des)) == (10 * 100 * 5 * 2, 4) - @test names(generate(des)) == ["subject", "item", "A", "B"] - @test sum(generate(des).A .== "S2") == 10 * 100 * 2 - @test sum(generate(des).B .== "S2") == 10 * 100 * 5 + @test size(generate_events(des)) == (10 * 100 * 5 * 2, 4) + @test names(generate_events(des)) == ["subject", "item", "A", "B"] + @test sum(generate_events(des).A .== "S2") == 10 * 100 * 2 + @test sum(generate_events(des).B .== "S2") == 10 * 100 * 5 # check that finally everything is sorted by subject des = MultiSubjectDesign(; @@ -35,7 +37,7 @@ both_within = Dict(:A => nlevels(5), :B => nlevels(2)), event_order_function = x -> sort(x, order(:item, rev = true)), ) - @test generate(des).subject[1] == "S01" + @test generate_events(des).subject[1] == "S01" # check event_order_function des = MultiSubjectDesign(; @@ -44,7 +46,7 @@ both_within = Dict(:A => nlevels(5), :B => nlevels(2)), event_order_function = x -> sort(x, order(:B, rev = true)), ) - @test generate(des).B[1] == "S2" + @test generate_events(des).B[1] == "S2" # check that this throws an error because of `dv` as condition name des = MultiSubjectDesign(; @@ -52,7 +54,7 @@ n_items = 100, both_within = Dict(:dv => nlevels(5), :B => nlevels(2)), ) - @test_throws AssertionError generate(des) + @test_throws AssertionError generate_events(des) end @testset "RepeatDesign" begin @@ -65,7 +67,7 @@ ) design = RepeatDesign(designOnce, 3) - @test size(generate(design)) == (8 * 12 * 3, 3) + @test size(generate_events(design)) == (8 * 12 * 3, 3) @test size(design) == (8 * 3, 12) #--- single sub @@ -73,7 +75,7 @@ SingleSubjectDesign(; conditions = Dict(:A => nlevels(5), :B => nlevels(2))) design = RepeatDesign(designOnce, 3) - @test size(generate(design)) == (5 * 2 * 3, 2) + @test size(generate_events(design)) == (5 * 2 * 3, 2) @test size(design) == (3 * 5 * 2,) end @@ -86,7 +88,7 @@ design_multi = MultiSubjectDesign(; n_subjects = n_subjects, n_items = n_items) # Create the events data frame based on the design defined above - events_multi = generate(design_multi) + events_multi = generate_events(design_multi) # Test that there are only subject and item columns (no condition columns) in the events df @test names(events_multi) == ["subject", "item"] diff --git a/test/helper.jl b/test/helper.jl index de7732c5..053941d0 100644 --- a/test/helper.jl +++ b/test/helper.jl @@ -1,11 +1,11 @@ @testset "helper" begin - @testset "padarray" begin - @test padarray([2, 2], (-3, 2), -1) == [-1, -1, -1, 2, 2, -1, -1] - @test padarray([2, 2], -2, -1) == [-1, -1, 2, 2] - @test padarray([2, 2], 2, -1) == [2, 2, -1, -1] - @test padarray([2, 2], 2, -1) == [2, 2, -1, -1] + @testset "pad_array" begin + @test pad_array([2, 2], (-3, 2), -1) == [-1, -1, -1, 2, 2, -1, -1] + @test pad_array([2, 2], -2, -1) == [-1, -1, 2, 2] + @test pad_array([2, 2], 2, -1) == [2, 2, -1, -1] + @test pad_array([2, 2], 2, -1) == [2, 2, -1, -1] end diff --git a/test/noise.jl b/test/noise.jl index cf4a76e3..7526efc0 100644 --- a/test/noise.jl +++ b/test/noise.jl @@ -2,13 +2,13 @@ for n in [PinkNoise RedNoise WhiteNoise ExponentialNoise] - noisevec_1 = gen_noise(StableRNG(1), n(; noiselevel = 1), 123) + noisevec_1 = simulate_noise(StableRNG(1), n(; noiselevel = 1), 123) @test size(noisevec_1) == (123,) - noisevec_0 = gen_noise(StableRNG(1), n(; noiselevel = 0), 123) + noisevec_0 = simulate_noise(StableRNG(1), n(; noiselevel = 0), 123) @test all(noisevec_0 .== 0) - noisevec_2 = gen_noise(StableRNG(1), n(; noiselevel = 2), 123) + noisevec_2 = simulate_noise(StableRNG(1), n(; noiselevel = 2), 123) @test all(2 .* noisevec_1 .≈ noisevec_2) end - noisevec = gen_noise(StableRNG(1), NoNoise(), 123) + noisevec = simulate_noise(StableRNG(1), NoNoise(), 123) @test size(noisevec) == (123,) end diff --git a/test/onset.jl b/test/onset.jl index cb578e8a..7d46908f 100644 --- a/test/onset.jl +++ b/test/onset.jl @@ -1,44 +1,60 @@ @testset "onset" begin - dummydesign = gen_debug_design(; n_subj = 300, n_item = 1000) + dummydesign = gen_debug_design(; n_subjects = 300, n_item = 1000) @testset "UniformOnset" begin - unifOnset = UniformOnset(; offset = 100, width = 50) + uniform_onset = UniformOnset(; offset = 100, width = 50) # test random numbers are UniformOnset - rand_vec = UnfoldSim.rand_onsets(StableRNG(1), unifOnset, dummydesign) + rand_vec = UnfoldSim.simulate_interonset_distances( + StableRNG(1), + uniform_onset, + dummydesign, + ) @test size(rand_vec) == (1000, 300) @test minimum(rand_vec) ≈ 100 @test maximum(rand_vec) ≈ 150 end @testset "LogNormalOnset" begin # test basics - logNormalOnset = LogNormalOnset(; μ = 4, σ = 1) - rand_vec = UnfoldSim.rand_onsets(StableRNG(1), logNormalOnset, dummydesign) + lognormal_onset = LogNormalOnset(; μ = 4, σ = 1) + rand_vec = UnfoldSim.simulate_interonset_distances( + StableRNG(1), + lognormal_onset, + dummydesign, + ) @test size(rand_vec) == (1000, 300) @test isapprox(mean(log.(rand_vec)), 4; atol = 0.01) @test minimum(rand_vec) > 0 @test isapprox(std(log.(rand_vec)), 1; atol = 0.01) # test offset - logNormalOnset = LogNormalOnset(; μ = 4, σ = 1, offset = 100) - rand_vec = UnfoldSim.rand_onsets(StableRNG(1), logNormalOnset, dummydesign) + lognormal_onset = LogNormalOnset(; μ = 4, σ = 1, offset = 100) + rand_vec = UnfoldSim.simulate_interonset_distances( + StableRNG(1), + lognormal_onset, + dummydesign, + ) @test minimum(rand_vec) > 100 # test Truncated - logNormalOnset = LogNormalOnset(; μ = 4, σ = 1, truncate_upper = 100) - rand_vec = UnfoldSim.rand_onsets(StableRNG(1), logNormalOnset, dummydesign) + lognormal_onset = LogNormalOnset(; μ = 4, σ = 1, truncate_upper = 100) + rand_vec = UnfoldSim.simulate_interonset_distances( + StableRNG(1), + lognormal_onset, + dummydesign, + ) @test maximum(rand_vec) <= 100 @test minimum(rand_vec) >= 0 end - @testset "gen_onsets" begin + @testset "sim_onsets" begin # test accumulate always increasing - unifOnset = UniformOnset(; offset = 0, width = 50) + uniform_onset = UniformOnset(; offset = 0, width = 50) - accumOnset = UnfoldSim.generate( + accumulated_onset = UnfoldSim.simulate_onsets( StableRNG(1), - unifOnset, - gen_debug_simulation(onset = unifOnset), + uniform_onset, + gen_debug_simulation(onset = uniform_onset), ) - @test all(diff(accumOnset, dims = 1) .>= 0) + @test all(diff(accumulated_onset, dims = 1) .>= 0) end end diff --git a/test/setup.jl b/test/setup.jl index 484d7b1f..4838bb6a 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -7,13 +7,13 @@ using LinearAlgebra using MixedModelsSim using DataFrames -function gen_debug_design(; n_subj = 20, n_item = 100) +function gen_debug_design(; n_subjects = 20, n_item = 100) # define design parameters item_btwn = Dict("stimType" => ["A", "B"]) # instantiate the design return MultiSubjectDesign(; - n_subjects = n_subj, + n_subjects = n_subjects, n_items = n_item, items_between = item_btwn, ) From 23976cf5a8f5ce6a280660ea45fc7db11362ef31 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 17:11:20 +0000 Subject: [PATCH 038/113] docs: update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d80d0006..c41870af 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ You are very welcome to raise issues and start pull requests! Maanik Marathe
Maanik Marathe

📖 💻 Benedikt Ehinger
Benedikt Ehinger

🐛 💻 📖 🤔 🚇 🚧 👀 ⚠️ Luis
Luis

🐛 💻 📖 🤔 - Judith Schepers
Judith Schepers

🤔 🐛 📖 💻 + Judith Schepers
Judith Schepers

🤔 🐛 📖 💻 ⚠️ Vladimir Mikheev
Vladimir Mikheev

🐛 Manpa Barman
Manpa Barman

🚇 René Skukies
René Skukies

📖 From 5cd330099be80d9ff5ffdbf5c15fd708ad262252 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 17:11:21 +0000 Subject: [PATCH 039/113] docs: update .all-contributorsrc --- .all-contributorsrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 853e1aaa..d37c8153 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -57,7 +57,8 @@ "bug", "doc", "tutorial", - "code" + "code", + "test" ] }, { From 4af682fa4ec673442156b810a47568755b5550b3 Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Thu, 8 Feb 2024 11:38:26 +0100 Subject: [PATCH 040/113] v0.3.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 34b4f9c0..721992b1 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnfoldSim" uuid = "ed8ae6d2-84d3-44c6-ab46-0baf21700804" authors = ["Benedikt Ehinger", "Luis Lips", "Judith Schepers", "Maanik Marathe"] -version = "0.2.0" +version = "0.3.0" [deps] Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" From 1f773b198255731c529a1c054f114520a5e677d8 Mon Sep 17 00:00:00 2001 From: jschepers Date: Fri, 9 Feb 2024 15:24:00 +0000 Subject: [PATCH 041/113] added axes labels to the noise type plots --- docs/literate/reference/noisetypes.jl | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/literate/reference/noisetypes.jl b/docs/literate/reference/noisetypes.jl index 2057857d..fe3efc4a 100644 --- a/docs/literate/reference/noisetypes.jl +++ b/docs/literate/reference/noisetypes.jl @@ -7,9 +7,23 @@ import StatsBase.autocor # 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)") +ax_sig = + f[1, 1:3] = + Axis(f; title = "1.000 samples of noise", xlabel = "Time", ylabel = "Amplitude") +ax_spec = + f[2, 1:2] = Axis( + f; + title = "Welch Periodogram", + xlabel = "Normalized frequency", + ylabel = "log(Power)", + ) +ax_auto = + f[2, 3:4] = Axis( + f; + title = "Autocorrelogram (every 10th lag)", + xlabel = "Lag", + ylabel = "Autocorrelation", + ) for n in [PinkNoise RedNoise WhiteNoise NoNoise ExponentialNoise] ## generate @@ -29,7 +43,7 @@ for n in [PinkNoise RedNoise WhiteNoise NoNoise ExponentialNoise] lines!(ax_auto, lags, autocor_vec) end -f[1:2, 3] = Legend(f, ax_sig, "NoiseType") +f[1, 4] = Legend(f, ax_sig, "Noise type", tellheight = true) f From 5c9a1a07c83f1dda9d2e0f95cdf43991bca2fe3b Mon Sep 17 00:00:00 2001 From: jschepers Date: Fri, 9 Feb 2024 16:20:32 +0000 Subject: [PATCH 042/113] adapted variable range + added axes labels --- docs/literate/tutorials/simulateERP.jl | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/literate/tutorials/simulateERP.jl b/docs/literate/tutorials/simulateERP.jl index eea6f995..b011978d 100644 --- a/docs/literate/tutorials/simulateERP.jl +++ b/docs/literate/tutorials/simulateERP.jl @@ -11,7 +11,7 @@ design = SingleSubjectDesign(; conditions = Dict( :condition => ["car", "face"], - :continuous => range(-5, 5, length = 10), + :continuous => range(0, 5, length = 10), ), ) |> x -> RepeatDesign(x, 100); @@ -60,11 +60,19 @@ m = fit( ); # first the "pure" beta/linear regression parameters -plot_erp(coeftable(m)) +plot_erp( + coeftable(m); + axis = ( + title = "Estimated regression parameters", + xlabel = "Time [s]", + ylabel = "Amplitude [μV]", + ), +) # and now beautifully visualized as marginal betas / predicted ERPs f = plot_erp( - effects(Dict(:condition => ["car", "face"], :continuous => -5:5), m); + effects(Dict(:condition => ["car", "face"], :continuous => 0:0.5:5), m); + axis = (title = "Predicted event-related potential (ERP)", xlabel = "Time [s]", ylabel = "Amplitude [μV]"), mapping = (:color => :continuous, linestyle = :condition, group = :continuous), legend = (; valign = :top, halign = :right, tellwidth = false), categorical_color = false, From c6b37040a1671408fd470ac19ef8e765895e0bc1 Mon Sep 17 00:00:00 2001 From: jschepers Date: Fri, 9 Feb 2024 16:38:39 +0000 Subject: [PATCH 043/113] trying to fix predefined onsets howto --- docs/literate/HowTo/predefinedData.jl | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/literate/HowTo/predefinedData.jl b/docs/literate/HowTo/predefinedData.jl index fc5b9d9e..7c52ee31 100644 --- a/docs/literate/HowTo/predefinedData.jl +++ b/docs/literate/HowTo/predefinedData.jl @@ -1,12 +1,13 @@ -# # Make use -# Let's say you want to +# # Use existing experimental designs & onsets in the simulation + +# Let's say you want to use the events data frame (containing the levels of the experimental variables and the event onsets (latencies)) from a previous study in your simulation. using UnfoldSim using DataFrames using Random -using CairoMakie ## for plotting +using CairoMakie # for plotting -# From a previous study, we (somehow, e.g. [pyMNE.jl]()) imported event-table like this: +# From a previous study, we (somehow, e.g. by using [pyMNE.jl](https://github.com/beacon-biosignals/PyMNE.jl)) imported an event data frame like this: my_events = DataFrame(:condition => [:A, :B, :B, :A, :A], :latency => [7, 13, 22, 35, 41]) # To use exactly these values, we can generate a new `AbstractDesign`, which will always return this event dataframe @@ -16,7 +17,7 @@ end UnfoldSim.generate_events(d::MyManualDesign) = deepcopy(d.my_events) ## generate function which is called internally in UnfoldSim UnfoldSim.size(d::MyManualDesign) = size(d.my_events, 1); ## necessary function to tell what the dimensionality of the experimental design is # !!! note -# Note the `UnfoldSim.generate` which tells Julia to "overload" the `generate` function as defined in UnfoldSim +# Note the `UnfoldSim.generate_events` which tells Julia to "overload" the `generate_events` function as defined in UnfoldSim. # Next we generate a `MyManualDesign` @@ -31,10 +32,10 @@ signal = LinearModelComponent(; data, events = simulate(MersenneTwister(1), mydesign, signal, UniformOnset(; width = 10, offset = 5)) -lines(data) ## plotting +lines(data) # plotting vlines!(my_events.latency; linestyle = :dash) current_figure() -# Looks good, but the events don't match our custom onsets yet +# Looks good, but the events don't match our custom onsets yet. # ## Custom Timings # Finally, we want to use our custom timings as well. For this we define a new `AbstractOnset`. Again, it simply returns our manually provided latencies @@ -42,11 +43,11 @@ struct MyManualOnset <: AbstractOnset end UnfoldSim.simulate_onsets(rng, onset::MyManualOnset, simulation::Simulation) = generate_events(simulation.design).latency # !!! hint -# This is a bit of a trick, it relies that `MyManualOnset`` is always used in combination with `MyManualDesign`. You could of course repeat the structure from `MyManualDesign` also for `MyManualOnset` and have an explicit field in the structure containing the onsets. +# This is a bit of a trick, it relies that `MyManualOnset` is always used in combination with `MyManualDesign`. You could of course repeat the structure from `MyManualDesign` also for `MyManualOnset` and have an explicit field in the structure containing the onsets. # And that's it data, events = simulate(MersenneTwister(1), mydesign, signal, MyManualOnset()) -lines(data) ## plotting +lines(data) # plotting vlines!(my_events.latency, linestyle = :dash) current_figure() # now everything matches, lovely! \ No newline at end of file From b790f414a9f798bfbf84d8c178b6cdd9e54a80a0 Mon Sep 17 00:00:00 2001 From: jschepers Date: Fri, 9 Feb 2024 16:39:27 +0000 Subject: [PATCH 044/113] removed outdated information --- README.md | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c41870af..f41e1136 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A Julia package to simulate multivariate time series, e.g. model-based ERPs, fMRI activity, pupil dilation etc. UnfoldSim.jl provides multi-channel support via EEG-forward models. Moreover, it is possible to simulate overlapping event-related activity and to add noise of a certain type e.g. Pink noise. -Many Tutorials, Guides, How-Tos and References available in the [documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/)! +Many tutorials, guides, how-tos and references are available in the [documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/)! ![grafik](https://user-images.githubusercontent.com/10183650/213565922-90feec23-3b51-4602-b50c-31561dbfc261.png) @@ -41,36 +41,42 @@ AppStore -> JuliaUp, or `winget install julia -s msstore` in CMD ```julia using Pkg -Pkg.add("https://github.com/unfoldtoolbox/UnfoldSim.jl/tree/main") +Pkg.add("UnfoldSim") ``` -Once the toolbox is registered this will translate to ```Pkg.add("UnfoldSim")``` - - # Quickstart ```julia -data,evts = UnfoldSim.predef_eeg(;n_repeats=1,noiselevel=0.8) +data, evts = UnfoldSim.predef_eeg(; n_repeats = 1, noiselevel = 0.8) ``` -Produces continuous "EEG" with PinkNoise and some Overlap between 20 events (2 conditions * 10 levels of continuous variable). +Produces continuous "EEG" with PinkNoise and some overlap between 20 events (2 conditions * 10 levels of the continuous variable). ## Slightly longer ```julia -# start by defining the design / event-table -design = SingleSubjectDesign(;conditions=Dict(:condA=>["levelA","levelB"])) |> d->RepeatDesign(d,10); -# next define a ground-truth signal + relation to events/design with Wilkinson Formulas +# Start by defining the design / events data frame +design = + SingleSubjectDesign(; conditions = Dict(:condA => ["levelA", "levelB"])) |> + d -> RepeatDesign(d, 10); + +# Next define a ground truth signal + relation to events/design with Wilkinson formulas signal = LinearModelComponent(; - basis=[0,0,0,0.5,1,1,0.5,0,0], - formula = @formula(0~1+condA), - β = [1,0.5] - ); + basis = [0, 0, 0, 0.5, 1, 1, 0.5, 0, 0], + formula = @formula(0 ~ 1 + condA), + β = [1, 0.5], +); # finally, define some Onset Distribution and Noise, and simulate! -data,events = simulate(Random.MersenneTwister(1),design, signal, UniformOnset(;offset=5,width=4), PinkNoise()); +data, events = simulate( + Random.MersenneTwister(1), + design, + signal, + UniformOnset(; offset = 5, width = 4), + PinkNoise(), +); ``` All components (design, components, onsets, noise) can be easily modified and you can simply plugin your own! ## Contributions -Contributions are very welcome. These could be typos, bugreports, feature-requests, speed-optimization, new solvers, better code, better documentation. +Contributions are very welcome. These could be typos, bug reports, feature requests, speed-optimization, better code, better documentation. ### How-to Contribute From d0d1b89682c67efcbb13634cc54594b3f8266cf2 Mon Sep 17 00:00:00 2001 From: jschepers Date: Fri, 9 Feb 2024 16:51:27 +0000 Subject: [PATCH 045/113] deleted paper.md in root directory --- paper.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 paper.md diff --git a/paper.md b/paper.md deleted file mode 100644 index 8b137891..00000000 --- a/paper.md +++ /dev/null @@ -1 +0,0 @@ - From 4516f806663b3f74275ef5edb8223768747a0857 Mon Sep 17 00:00:00 2001 From: jschepers Date: Fri, 9 Feb 2024 17:01:05 +0000 Subject: [PATCH 046/113] updated link --- docs/literate/HowTo/predefinedData.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/literate/HowTo/predefinedData.jl b/docs/literate/HowTo/predefinedData.jl index 7c52ee31..9a394e9b 100644 --- a/docs/literate/HowTo/predefinedData.jl +++ b/docs/literate/HowTo/predefinedData.jl @@ -7,7 +7,7 @@ using DataFrames using Random using CairoMakie # for plotting -# From a previous study, we (somehow, e.g. by using [pyMNE.jl](https://github.com/beacon-biosignals/PyMNE.jl)) imported an event data frame like this: +# From a previous study, we (somehow, e.g. by using [pyMNE.jl](https://unfoldtoolbox.github.io/Unfold.jl/dev/HowTo/pymne/)) imported an event data frame like this: my_events = DataFrame(:condition => [:A, :B, :B, :A, :A], :latency => [7, 13, 22, 35, 41]) # To use exactly these values, we can generate a new `AbstractDesign`, which will always return this event dataframe From 42509a2427689b7b7c215c2e49a7e92a51b53dd9 Mon Sep 17 00:00:00 2001 From: jschepers Date: Fri, 9 Feb 2024 17:31:41 +0000 Subject: [PATCH 047/113] added test_broken for MixedModelsSim bug --- test/design.jl | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/design.jl b/test/design.jl index 62dcd678..3a6866b1 100644 --- a/test/design.jl +++ b/test/design.jl @@ -131,4 +131,30 @@ end end + @testset "MultiSubjectDesign with between subject and item factors" begin + + # Define number of subjects and items + n_subjects = 2 + n_items = 2 + + # Create a multi-subject design with the same factor between subject and item + design = MultiSubjectDesign(; + n_subjects = n_subjects, + n_items = n_items, + subjects_between = Dict(:cond => ["levelA", "levelB"]), + items_between = Dict(:cond => ["levelA", "levelB"]), + ) + + # Create events data frame based on the design + events_df = generate_events(design) + + # Extract events for subject 2 + s2 = subset(events_df, :subject => x -> x .== "S2") + + # Since cond is a between_subject factor, each subject should only be in one condition + # Currently, this is not the case due to a bug in MixedModelsSim.jl (https://github.com/RePsychLing/MixedModelsSim.jl/pull/66) + + @test_broken length(unique(s2.cond)) == 1 + end + end \ No newline at end of file From 3142fda01f450da1aea9cf405bc06566619ca27e Mon Sep 17 00:00:00 2001 From: jschepers Date: Fri, 9 Feb 2024 17:35:44 +0000 Subject: [PATCH 048/113] replace image --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f41e1136..2b3a89b5 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ UnfoldSim.jl provides multi-channel support via EEG-forward models. Moreover, it Many tutorials, guides, how-tos and references are available in the [documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/)! -![grafik](https://user-images.githubusercontent.com/10183650/213565922-90feec23-3b51-4602-b50c-31561dbfc261.png) +![grafik](image-1.png) ## Install From 3c256e92e1c5825a11056066da3242c851f8553a Mon Sep 17 00:00:00 2001 From: jschepers Date: Fri, 9 Feb 2024 17:46:52 +0000 Subject: [PATCH 049/113] fix image --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2b3a89b5..f5db3332 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ UnfoldSim.jl provides multi-channel support via EEG-forward models. Moreover, it Many tutorials, guides, how-tos and references are available in the [documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/)! -![grafik](image-1.png) +![readme_image](https://github.com/unfoldtoolbox/UnfoldSim.jl/assets/22366977/e6093d1e-2de7-4da2-86c6-a0d31ecc6420) ## Install From 347d7ab0e1c0d4bcd6c2c35067135adaf368e271 Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 12 Feb 2024 13:02:13 +0000 Subject: [PATCH 050/113] adapt README image --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5db3332..b1d0e413 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ UnfoldSim.jl provides multi-channel support via EEG-forward models. Moreover, it Many tutorials, guides, how-tos and references are available in the [documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/)! -![readme_image](https://github.com/unfoldtoolbox/UnfoldSim.jl/assets/22366977/e6093d1e-2de7-4da2-86c6-a0d31ecc6420) +![readme_image](https://github.com/unfoldtoolbox/UnfoldSim.jl/assets/22366977/a52dc5f8-54aa-43c1-8235-6d6fb34a79b2) ## Install From 17f3fe8a6be2c9b37426af4f5124d302eb8487c1 Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Mon, 12 Feb 2024 14:11:56 +0100 Subject: [PATCH 051/113] JuliaFormatter: Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- docs/literate/tutorials/simulateERP.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/literate/tutorials/simulateERP.jl b/docs/literate/tutorials/simulateERP.jl index b011978d..32e0a260 100644 --- a/docs/literate/tutorials/simulateERP.jl +++ b/docs/literate/tutorials/simulateERP.jl @@ -72,7 +72,11 @@ plot_erp( # and now beautifully visualized as marginal betas / predicted ERPs f = plot_erp( effects(Dict(:condition => ["car", "face"], :continuous => 0:0.5:5), m); - axis = (title = "Predicted event-related potential (ERP)", xlabel = "Time [s]", ylabel = "Amplitude [μV]"), + axis = ( + title = "Predicted event-related potential (ERP)", + xlabel = "Time [s]", + ylabel = "Amplitude [μV]", + ), mapping = (:color => :continuous, linestyle = :condition, group = :continuous), legend = (; valign = :top, halign = :right, tellwidth = false), categorical_color = false, From 0913018193ea0b0f601dbca438e3d84ac723635f Mon Sep 17 00:00:00 2001 From: jschepers Date: Fri, 16 Feb 2024 14:58:38 +0000 Subject: [PATCH 052/113] Adapted SimulateERP tutorial and updated README --- README.md | 12 ++++++++---- docs/literate/tutorials/simulateERP.jl | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b1d0e413..8506143e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ UnfoldSim.jl provides multi-channel support via EEG-forward models. Moreover, it Many tutorials, guides, how-tos and references are available in the [documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/)! -![readme_image](https://github.com/unfoldtoolbox/UnfoldSim.jl/assets/22366977/a52dc5f8-54aa-43c1-8235-6d6fb34a79b2) +![readme_figure](https://github.com/unfoldtoolbox/UnfoldSim.jl/assets/22366977/b69d186c-fd3d-4449-9f2e-03d7e01b8cb3) ## Install @@ -46,12 +46,16 @@ Pkg.add("UnfoldSim") # Quickstart ```julia -data, evts = UnfoldSim.predef_eeg(; n_repeats = 1, noiselevel = 0.8) +using UnfoldSim +data, events = UnfoldSim.predef_eeg(; n_repeats = 1, noiselevel = 0.8) ``` Produces continuous "EEG" with PinkNoise and some overlap between 20 events (2 conditions * 10 levels of the continuous variable). ## Slightly longer ```julia +using UnfoldSim +using Random + # Start by defining the design / events data frame design = SingleSubjectDesign(; conditions = Dict(:condA => ["levelA", "levelB"])) |> @@ -63,7 +67,7 @@ signal = LinearModelComponent(; formula = @formula(0 ~ 1 + condA), β = [1, 0.5], ); -# finally, define some Onset Distribution and Noise, and simulate! +# finally, define some inter-onset distribution and noise, and simulate data! data, events = simulate( Random.MersenneTwister(1), design, @@ -123,4 +127,4 @@ TBA ## Acknowledgements -Funded by Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) under Germany´s Excellence Strategy – EXC 2075 – 390740016 +Funded by Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) under Germany´s Excellence Strategy – EXC 2075 – 390740016. Furthermore, the authors thank the International Max Planck Research School for Intelligent Systems (IMPRS-IS) for supporting Judith Schepers. diff --git a/docs/literate/tutorials/simulateERP.jl b/docs/literate/tutorials/simulateERP.jl index 32e0a260..b3798096 100644 --- a/docs/literate/tutorials/simulateERP.jl +++ b/docs/literate/tutorials/simulateERP.jl @@ -24,7 +24,7 @@ p1 = LinearModelComponent(; basis = p100(), formula = @formula(0 ~ 1), β = [5]) n1 = LinearModelComponent(; basis = n170(), formula = @formula(0 ~ 1 + condition), - β = [5, -3], + β = [5, 3], ); # **p300** has a continuous effect, higher continuous values will result in larger P300's. # We include both a linear and a quadratic effect of the continuous variable. From 83ec6aaba47ab7443625e150e7e0f5d9233f8221 Mon Sep 17 00:00:00 2001 From: jschepers Date: Fri, 16 Feb 2024 15:52:33 +0000 Subject: [PATCH 053/113] adapted tests for design since MixedModelsSim bug has been fixed --- test/design.jl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/design.jl b/test/design.jl index 3a6866b1..02efd8dc 100644 --- a/test/design.jl +++ b/test/design.jl @@ -67,7 +67,8 @@ ) design = RepeatDesign(designOnce, 3) - @test size(generate_events(design)) == (8 * 12 * 3, 3) + # Note: the number of items has to be divided by the number of cond levels because cond is both between item and subject + @test size(generate_events(design)) == ((8 / 2) * 12 * 3, 3) @test size(design) == (8 * 3, 12) #--- single sub @@ -149,12 +150,16 @@ events_df = generate_events(design) # Extract events for subject 2 - s2 = subset(events_df, :subject => x -> x .== "S2") + s2 = subset(events_df, :subject => ByRow(==("S2"))) # Since cond is a between_subject factor, each subject should only be in one condition - # Currently, this is not the case due to a bug in MixedModelsSim.jl (https://github.com/RePsychLing/MixedModelsSim.jl/pull/66) + @test length(unique(s2.cond)) == 1 - @test_broken length(unique(s2.cond)) == 1 + # Extract events for item 1 + i1 = subset(events_df, :item => ByRow(==("I1"))) + + # Since cond is a between_item factor, each item should only be in one condition + @test length(unique(i1.cond)) == 1 end end \ No newline at end of file From 9521959a7da9e0703ebdd76c6107563e03ceff35 Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Mon, 19 Feb 2024 14:50:22 +0100 Subject: [PATCH 054/113] Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index fa0b2ff1..33371dc3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Luis Lips, Benedikt Ehinger, Judith Schepers +Copyright (c) 2022-2024: Benedikt Ehinger, Judith Schepers, Luis Lips, Maanik Marathe and [other contributors](https://github.com/unfoldtoolbox/UnfoldSim.jl?tab=readme-ov-file#contributors). Permission is hereby granted, free of charge, to any person obtaining a copy From e07566787cd7204d05c6365365f601ba0b99f305 Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Mon, 19 Feb 2024 15:12:01 +0100 Subject: [PATCH 055/113] Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 33371dc3..878b1c02 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2024: Benedikt Ehinger, Judith Schepers, Luis Lips, Maanik Marathe and [other contributors](https://github.com/unfoldtoolbox/UnfoldSim.jl?tab=readme-ov-file#contributors). +Copyright (c) 2022-2024: Benedikt Ehinger, Judith Schepers, Luis Lips, Maanik Marathe and other contributors: https://github.com/unfoldtoolbox/UnfoldSim.jl?tab=readme-ov-file#contributors. Permission is hereby granted, free of charge, to any person obtaining a copy From 4b898feddc92fe364473fd3b00b860824ff6ff3a Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Fri, 16 Feb 2024 20:22:49 +0000 Subject: [PATCH 056/113] improved and added docstrings --- src/bases.jl | 22 ++++++++++++-- src/component.jl | 40 +++++++++++++++++++++---- src/design.jl | 7 +++++ src/headmodel.jl | 11 +++++-- src/helper.jl | 17 ++++++++++- src/noise.jl | 57 ++++++++++++++++++++++++++---------- src/onset.jl | 29 ++++++++++++++++-- src/predefinedSimulations.jl | 11 +++---- src/simulation.jl | 10 +++++++ 9 files changed, 171 insertions(+), 33 deletions(-) diff --git a/src/bases.jl b/src/bases.jl index d496f5a4..70b779d8 100644 --- a/src/bases.jl +++ b/src/bases.jl @@ -1,10 +1,25 @@ # here we define some commonly used basis for simulation ## EEG -# P100, N170, P300, N400 +""" + p100(;sfreq=100) +Generator for Hanning window, peak at 100ms, width 100ms, at kwargs `sfreq` (default 100) +""" p100(; sfreq = 100) = hanning(0.1, 0.1, sfreq) +""" + p300(;sfreq=100) +Generator for Hanning window, peak at 300ms, width 300ms, at kwargs `sfreq` (default 100) +""" p300(; sfreq = 100) = hanning(0.3, 0.3, sfreq) +""" + n170(;sfreq=100) +Generator for Hanning window, negative (!) peak at 170ms, width 150ms, at kwargs `sfreq` (default 100) +""" n170(; sfreq = 100) = -hanning(0.15, 0.17, sfreq) +""" + n400(;sfreq=100) +Generator for Hanning window, negative (!) peak at 400ms, width 400ms, at kwargs `sfreq` (default 100) +""" n400(; sfreq = 100) = -hanning(0.4, 0.4, sfreq) """ @@ -20,7 +35,10 @@ function DSP.hanning(duration, offset, sfreq) end ## pupil - +""" + PuRF() +Default generator for PuRF Pupil Response Function +""" function PuRF(; n = 10.1, tmax = 0.93, sfreq = 100) t = (0:1/sfreq:3*tmax) return PuRF(t, n, tmax) ./ PuRF(tmax, n, tmax) diff --git a/src/component.jl b/src/component.jl index 5d571de4..6d77815e 100644 --- a/src/component.jl +++ b/src/component.jl @@ -82,22 +82,31 @@ end Base.length(c::MultichannelComponent) = length(c.component) """ + n_channels(c::AbstractComponent) Returns the number of channels. By default = 1 """ n_channels(c::AbstractComponent) = 1 """ -for `MultichannelComponent` returns the length of the projection vector + n_channels(c::MultichannelComponent) +for `MultichannelComponent` returns the length of the projection vector. + """ n_channels(c::MultichannelComponent) = length(c.projection) - +""" +for a vector of multichanelcomponents, returns the first but asserts all are of equal length +""" function n_channels(c::Vector{<:AbstractComponent}) all_channels = n_channels.(c) @assert length(unique(all_channels)) == 1 "Error - projections of different channels cannot be different from eachother" return all_channels[1] end +""" + simulate_component(rng,c::MultichannelComponent,design::AbstractDesign) +Returns the projection of a component from source to "sensor" space +""" function simulate_component(rng, c::MultichannelComponent, design::AbstractDesign) y = simulate_component(rng, c.component, design) @@ -112,17 +121,22 @@ end Base.length(c::AbstractComponent) = length(c.basis) + +""" + maxlength(c::Vector{AbstractComponent}) = maximum(length.(c)) +""" maxlength(c::Vector{AbstractComponent}) = maximum(length.(c)) """ -# by default call simulate with `::Abstractcomponent,::AbstractDesign``, but allow for custom types -# making use of other information in simulation + simulate_component(rng, c::AbstractComponent, simulation::Simulation) +by default call simulate_component with `(::Abstractcomponent,::AbstractDesign)` instead of whole simulation. This allows users to provide a hook to do something completly different :) """ simulate_component(rng, c::AbstractComponent, simulation::Simulation) = simulate_component(rng, c, simulation.design) """ -simulate a linearModel + simulate_component(rng, c::AbstractComponent, simulation::Simulation) +Generates a linear model design matrix and weights it by c.β julia> c = UnfoldSim.LinearModelComponent([0,1,1,0],@formula(0~1+cond),[1,2],Dict()) julia> design = MultiSubjectDesign(;n_subjects=2,n_items=50,item_between=(;:cond=>["A","B"])) @@ -147,7 +161,12 @@ function simulate_component(rng, c::LinearModelComponent, design::AbstractDesign return y' .* c.basis end """ -simulate MixedModelComponent + simulate_component(rng, c::MixedModelComponent, design::AbstractDesign) +Generates a MixedModel and simulates data according to c.β and c.σs + +A trick is used to remove the Normal-Noise from the MixedModel which might lead to rare numerical instabilities. Practically, we upscale the σs by factor 10000, and provide a σ=0.0001. Internally this results in a normalization where the response scale is 10000 times larger than the noise. + +Currently it is not possible to use a different basis for fixed and random effects, but a code-stub exists (it is slow though) julia> design = MultiSubjectDesign(;n_subjects=2,n_items=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()) @@ -255,6 +274,10 @@ end #---- """ + simulate_responses( + rng, + components::Vector{<:AbstractComponent}, + simulation::Simulation) Simulates multiple component responses and accumulates them on a per-event basis """ function simulate_responses( @@ -276,6 +299,11 @@ function simulate_responses( end +""" + simulate_and_add!(epoch_data::AbstractMatrix, c, simulation, rng) + simulate_and_add!(epoch_data::AbstractArray, c, simulation, rng) +Helper function to call `simulate_component` and add it to a provided Array` +""" function simulate_and_add!(epoch_data::AbstractMatrix, c, simulation, rng) @debug "matrix" @views epoch_data[1:length(c), :] .+= simulate_component(rng, c, simulation) diff --git a/src/design.jl b/src/design.jl index 46dc989b..2207889a 100644 --- a/src/design.jl +++ b/src/design.jl @@ -77,6 +77,7 @@ function generate_events(design::SingleSubjectDesign) end """ + generate_events(design::MultiSubjectDesign) Generates full factorial Dataframe according to MixedModelsSim.jl 's simdat_crossed function Note: n_items = you can think of it as `trials` or better, as stimuli @@ -127,6 +128,7 @@ length(design::AbstractDesign) = *(size(design)...) # ---- """ + RepeatDesign{T} repeat a design DataFrame multiple times to mimick repeatedly recorded trials ```julia @@ -145,6 +147,11 @@ design = RepeatDesign(designOnce,4); repeat::Int = 1 end +""" + UnfoldSim.generate_events(design::RepeatDesign{T}) + +In a repeated design, iteratively calls the underlying {T} Design and concatenates. In case of MultiSubjectDesign, sorts by subject +""" function UnfoldSim.generate_events(design::RepeatDesign) df = map(x -> generate_events(design.design), 1:design.repeat) |> x -> vcat(x...) if isa(design.design, MultiSubjectDesign) diff --git a/src/headmodel.jl b/src/headmodel.jl index 4dc9fe08..7983559f 100644 --- a/src/headmodel.jl +++ b/src/headmodel.jl @@ -86,6 +86,7 @@ Fallback: along the third dimension using `norm` - the maximal projection magnitude(headmodel::AbstractHeadmodel) = magnitude(leadfield(headmodel)) """ +magnitude(headmodel::Hartmut; type = "perpendicular") = Extract magnitude of 3-orientation-leadfield, `type` (default: "perpendicular") => uses the provided source-point orientations - otherwise falls back to `norm` """ @@ -93,7 +94,10 @@ magnitude(headmodel::Hartmut; type = "perpendicular") = type == "perpendicular" ? magnitude(leadfield(headmodel), orientation(headmodel)) : magnitude(leadfield(headmodel)) - +""" + magnitude(lf::AbstractArray{T,3}, orientation::AbstractArray{T,2}) where {T<:Real} +Returns the magnitude along an orientation of the leadfield +""" function magnitude(lf::AbstractArray{T,3}, orientation::AbstractArray{T,2}) where {T<:Real} si = size(lf) magnitude = fill(NaN, si[1:2]) @@ -105,7 +109,10 @@ function magnitude(lf::AbstractArray{T,3}, orientation::AbstractArray{T,2}) wher return magnitude end - +""" + magnitude(lf::AbstractArray{T,3}) where {T<:Real} +If orientation is not specified, returns the maximal magnitude (norm of leadfield) +""" function magnitude(lf::AbstractArray{T,3}) where {T<:Real} si = size(lf) magnitude = fill(NaN, si[1:2]) diff --git a/src/helper.jl b/src/helper.jl index 3c4ef541..b248f3cd 100755 --- a/src/helper.jl +++ b/src/helper.jl @@ -91,9 +91,24 @@ function closest_src(head::Hartmut, label::String) end -# Adapted from Unfold.jl: https://github.com/unfoldtoolbox/Unfold.jl/blob/b3a21c2bb7e93d2f45ec64b0197f4663a6d7939a/src/utilities.jl#L40 + # One channel case + +""" + epoch(data::AbstractVector, args...; kwargs...) + epoch( + data::AbstractArray{T,2}, + events, + τ::Tuple{Number,Number}, + sfreq; + eventtime::Symbol = :latency, + ) where {T<:Union{Missing,Number}} +Helper function to epoch data + +Adapted from Unfold.jl: https://github.com/unfoldtoolbox/Unfold.jl/blob/b3a21c2bb7e93d2f45ec64b0197f4663a6d7939a/src/utilities.jl#L40 + +""" function epoch(data::AbstractVector, args...; kwargs...) data_r = reshape(data, (1, :)) ep = epoch(data_r, args...; kwargs...) diff --git a/src/noise.jl b/src/noise.jl index 3bae512b..ba4a1d29 100644 --- a/src/noise.jl +++ b/src/noise.jl @@ -2,38 +2,68 @@ # Types #------------- + +""" + PinkNoise <: AbstractNoise +Generates Pink Noise using the SignalAnalysis.jl implementation +""" @with_kw struct PinkNoise <: AbstractNoise noiselevel = 1 func = SignalAnalysis.PinkGaussian end +""" + RedNoise <: AbstractNoise +Generates Red Noise using the SignalAnalysis.jl implementation +""" @with_kw struct RedNoise <: AbstractNoise noiselevel = 1 func = SignalAnalysis.RedGaussian end +""" + WhiteNoise <: WhiteNoise + noiselevel = 1 + imfilter = 0 +Generates White Noise using `randn` - thus Gaussian noise. +Using imfilter > 0 it is possible to smooth the noise using Image.imfilter +""" @with_kw struct WhiteNoise <: AbstractNoise noiselevel = 1 imfilter = 0 end - +""" + RealisticNoise <: AbstractNoise +Not implemented- planned to use Artefacts.jl to provide real EEG data to add +""" @with_kw struct RealisticNoise <: AbstractNoise noiselevel = 1 end +""" + NoNoise <: AbstractNoise + +Returns zeros instead of noise +""" struct NoNoise <: AbstractNoise end +""" + AutoRegressiveNoise <: AbstractNoise +Not implemented +""" struct AutoRegressiveNoise <: AbstractNoise end """ - Noise with exponential decay in AR spectrum - !!! warning - Current implementation: cholesky of NxN matrix needs to be calculated, might need lot's of RAM + ExponentialNoise <: AbstractNoise + +Noise with exponential decay in AR spectrum +!!! warning + Current implementation: cholesky of NxN matrix needs to be calculated, might need lot's of RAM """ @@ -44,23 +74,23 @@ end """ - simulate_noise(t::Union{PinkNoise, RedNoise}, n::Int) + simulate_noise(rng, t::Union{PinkNoise,RedNoise}, n::Int) -Generate noise of a given type t and length n +Generate Pink or Red Noise using the SignalAnalysis.jl implementation """ function simulate_noise(rng, t::Union{PinkNoise,RedNoise}, n::Int) return t.noiselevel .* rand(rng, t.func(n, 1.0)) end +""" + simulate_noise(rng, t::NoNoise, n::Int) +Returns zeros instead of noise +""" function simulate_noise(rng, t::NoNoise, n::Int) return zeros(n) end -""" - simulate_noise(t::WhiteNoise, n::Int) -Generate noise of a given type t and length n -""" function simulate_noise(rng, t::WhiteNoise, n::Int) noisevector = randn(rng, n) if !isnothing(t.imfilter) @@ -70,11 +100,7 @@ function simulate_noise(rng, t::WhiteNoise, n::Int) end -""" - simulate_noise(t::RealisticNoise, n::Int) -Generate noise of a given type t and length n -""" function simulate_noise(rng, t::RealisticNoise, n::Int) error("not implemented") return 0 @@ -100,8 +126,9 @@ end """ -Generate and add noise to the data-matrix + add_noise!(rng, noisetype::AbstractNoise, signal) +Generate and add noise to a data matrix. Assumes that the signal can be linearized, that is, that the noise is stationary """ function add_noise!(rng, noisetype::AbstractNoise, signal) diff --git a/src/onset.jl b/src/onset.jl index f3e4fa2b..6b74c8e0 100644 --- a/src/onset.jl +++ b/src/onset.jl @@ -2,10 +2,22 @@ # Types #--------------- +""" + struct UniformOnset <: AbstractOnset +Provides a Uniform Distribution in the inter-event-distances. +`width` is the width of the uniform distribution (=> the jitter) +`offset` is the minimal distance. The maximal distance is `offset + width` +""" @with_kw struct UniformOnset <: AbstractOnset width = 50 # how many samples jitter? offset = 0 # minimal offset? end +""" + @with_kw struct LogNormalOnset <: AbstractOnset +log-normal inter-event-distances using the Distributions.jl truncated LogNormal distribution + +Be careful with large `μ` and `σ` values, as they are on logscale. σ>8 can quickly give you out-of-memory sized signals! +""" @with_kw struct LogNormalOnset <: AbstractOnset μ::Any # mean σ::Any # variance @@ -13,10 +25,18 @@ end truncate_upper = nothing # truncate at some sample? end -# In the case that the user directly wants the erps/epoched data (no overlap) +""" + struct NoOnset <: AbstractOnset end +In the case that the user directly wants no overlap to be simulated (=> epoched data) +""" struct NoOnset <: AbstractOnset end -#------------- +""" + simulate_interonset_distances(rng, onset::UniformOnset, design::AbstractDesign) + simulate_interonset_distances(rng, onset::LogNormalOnset, design::AbstractDesign) +Generate the inter-event-onset vector in samples (returns Int) +""" + function simulate_interonset_distances(rng, onset::UniformOnset, design::AbstractDesign) return Int.( round.(rand(deepcopy(rng), onset.offset:(onset.offset+onset.width), size(design))) @@ -33,7 +53,12 @@ function simulate_interonset_distances(rng, onset::LogNormalOnset, design::Abstr end + +""" + simulate_onsets(rng, onset::AbstractOnset, simulation::Simulation) +Calls `simulate_interonset_distances` to generate distances between events and then adds them up to generate the actual latencies in samples # main call from `simulation` +""" function simulate_onsets(rng, onset::AbstractOnset, simulation::Simulation) # sample different onsets diff --git a/src/predefinedSimulations.jl b/src/predefinedSimulations.jl index 56fe3b6d..0ace3d93 100644 --- a/src/predefinedSimulations.jl +++ b/src/predefinedSimulations.jl @@ -11,15 +11,16 @@ predef_eeg(;kwargs...) predef_eeg(rng;kwargs...) predef_eeg(rng,n_subjects;kwargs...) -Gene -rates a P1/N1/P3 complex. -In case `n_subjects` is defined - `MixedModelComponents`` are generated, else `LinearModelComponents` +Generates a P1/N1/P3 complex. +In case `n_subjects` is defined - `MixedModelComponents` are generated, else `LinearModelComponents` + +Most used `kwargs` is: `return_epoched=true` to ignore the overlap/onset bits and return already epoched data + -Most used kwargs is: `return_epoched=true` to ignore the overlap/onset bits and return already epoched data ## Default params: -. n_repeats=100 +- n_repeats=100 - event_order_function = x->shuffle(deepcopy(rng),x # random trial order - conditions = Dict(...), diff --git a/src/simulation.jl b/src/simulation.jl index fbbe7309..8aa50e4a 100755 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -96,6 +96,13 @@ function simulate(rng, simulation::Simulation; return_epoched::Bool = false) end + +""" + create_continuous_signal(rng, responses, simulation) +Based on the responses and simulation parameters, simulates onset-latencies and adds together a continuous signal + + +""" function create_continuous_signal(rng, responses, simulation) (; design, components, onset, noisetype) = simulation @@ -149,6 +156,9 @@ end """ + add_responses!(signal, responses::Vector, e, s, tvec, erpvec) + add_responses!(signal, responses::Matrix, e, s, tvec, erpvec) + add_responses!(signal, responses::AbstractArray, e, s, tvec, erpvec) Helper function to add inplace the responses to the signal, but for both 2D (1 channel) and 3D (X channel case) """ function add_responses!(signal, responses::Vector, e, s, tvec, erpvec) From 9c085443bd3c82c8c03e6708a8af650b67547eee Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Mon, 19 Feb 2024 14:35:42 +0100 Subject: [PATCH 057/113] Update src/component.jl Co-authored-by: Judith Schepers --- src/component.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/component.jl b/src/component.jl index 6d77815e..ace8f01c 100644 --- a/src/component.jl +++ b/src/component.jl @@ -89,7 +89,7 @@ n_channels(c::AbstractComponent) = 1 """ n_channels(c::MultichannelComponent) -for `MultichannelComponent` returns the length of the projection vector. +For `MultichannelComponent` return the length of the projection vector. """ n_channels(c::MultichannelComponent) = length(c.projection) From 4f44dce954dbaf661b45cadb1afecffbe09f4007 Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Mon, 19 Feb 2024 14:43:23 +0100 Subject: [PATCH 058/113] Apply suggestions from code review Co-authored-by: Judith Schepers --- src/bases.jl | 13 ++++++++----- src/component.jl | 18 +++++++++--------- src/design.jl | 8 ++++---- src/headmodel.jl | 6 +++--- src/helper.jl | 2 +- src/noise.jl | 20 ++++++++++---------- src/onset.jl | 14 +++++++------- src/predefinedSimulations.jl | 8 ++++---- src/simulation.jl | 4 ++-- 9 files changed, 48 insertions(+), 45 deletions(-) diff --git a/src/bases.jl b/src/bases.jl index 70b779d8..32eb2f73 100644 --- a/src/bases.jl +++ b/src/bases.jl @@ -3,22 +3,25 @@ ## EEG """ p100(;sfreq=100) -Generator for Hanning window, peak at 100ms, width 100ms, at kwargs `sfreq` (default 100) +Generator for Hanning window, peak at 100ms, width 100ms, at kwargs `sfreq` (default 100). """ p100(; sfreq = 100) = hanning(0.1, 0.1, sfreq) + """ p300(;sfreq=100) -Generator for Hanning window, peak at 300ms, width 300ms, at kwargs `sfreq` (default 100) +Generator for Hanning window, peak at 300ms, width 300ms, at kwargs `sfreq` (default 100). """ p300(; sfreq = 100) = hanning(0.3, 0.3, sfreq) + """ n170(;sfreq=100) -Generator for Hanning window, negative (!) peak at 170ms, width 150ms, at kwargs `sfreq` (default 100) +Generator for Hanning window, negative (!) peak at 170ms, width 150ms, at kwargs `sfreq` (default 100). """ n170(; sfreq = 100) = -hanning(0.15, 0.17, sfreq) + """ n400(;sfreq=100) -Generator for Hanning window, negative (!) peak at 400ms, width 400ms, at kwargs `sfreq` (default 100) +Generator for Hanning window, negative (!) peak at 400ms, width 400ms, at kwargs `sfreq` (default 100). """ n400(; sfreq = 100) = -hanning(0.4, 0.4, sfreq) @@ -37,7 +40,7 @@ end ## pupil """ PuRF() -Default generator for PuRF Pupil Response Function +Default generator for PuRF Pupil Response Function. """ function PuRF(; n = 10.1, tmax = 0.93, sfreq = 100) t = (0:1/sfreq:3*tmax) diff --git a/src/component.jl b/src/component.jl index ace8f01c..87b0309e 100644 --- a/src/component.jl +++ b/src/component.jl @@ -83,7 +83,7 @@ Base.length(c::MultichannelComponent) = length(c.component) """ n_channels(c::AbstractComponent) -Returns the number of channels. By default = 1 +Return the number of channels. By default = 1. """ n_channels(c::AbstractComponent) = 1 @@ -95,7 +95,7 @@ For `MultichannelComponent` return the length of the projection vector. n_channels(c::MultichannelComponent) = length(c.projection) """ -for a vector of multichanelcomponents, returns the first but asserts all are of equal length +For a vector of `MultichannelComponent`s, return the first but asserts all are of equal length. """ function n_channels(c::Vector{<:AbstractComponent}) all_channels = n_channels.(c) @@ -105,7 +105,7 @@ end """ simulate_component(rng,c::MultichannelComponent,design::AbstractDesign) -Returns the projection of a component from source to "sensor" space +Return the projection of a component from source to "sensor" space. """ function simulate_component(rng, c::MultichannelComponent, design::AbstractDesign) y = simulate_component(rng, c.component, design) @@ -129,14 +129,14 @@ maxlength(c::Vector{AbstractComponent}) = maximum(length.(c)) """ simulate_component(rng, c::AbstractComponent, simulation::Simulation) -by default call simulate_component with `(::Abstractcomponent,::AbstractDesign)` instead of whole simulation. This allows users to provide a hook to do something completly different :) +By default call `simulate_component` with `(::Abstractcomponent,::AbstractDesign)` instead of the whole simulation. This allows users to provide a hook to do something completely different :) """ simulate_component(rng, c::AbstractComponent, simulation::Simulation) = simulate_component(rng, c, simulation.design) """ simulate_component(rng, c::AbstractComponent, simulation::Simulation) -Generates a linear model design matrix and weights it by c.β +Generate a linear model design matrix, weight it by c.β and multiply the result with the given basis vector. julia> c = UnfoldSim.LinearModelComponent([0,1,1,0],@formula(0~1+cond),[1,2],Dict()) julia> design = MultiSubjectDesign(;n_subjects=2,n_items=50,item_between=(;:cond=>["A","B"])) @@ -162,11 +162,11 @@ function simulate_component(rng, c::LinearModelComponent, design::AbstractDesign end """ simulate_component(rng, c::MixedModelComponent, design::AbstractDesign) -Generates a MixedModel and simulates data according to c.β and c.σs +Generates a MixedModel and simulates data according to c.β and c.σs. A trick is used to remove the Normal-Noise from the MixedModel which might lead to rare numerical instabilities. Practically, we upscale the σs by factor 10000, and provide a σ=0.0001. Internally this results in a normalization where the response scale is 10000 times larger than the noise. -Currently it is not possible to use a different basis for fixed and random effects, but a code-stub exists (it is slow though) +Currently, it is not possible to use a different basis for fixed and random effects, but a code-stub exists (it is slow though). julia> design = MultiSubjectDesign(;n_subjects=2,n_items=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()) @@ -278,7 +278,7 @@ end rng, components::Vector{<:AbstractComponent}, simulation::Simulation) -Simulates multiple component responses and accumulates them on a per-event basis +Simulate multiple component responses and accumulates them on a per-event basis. """ function simulate_responses( rng, @@ -302,7 +302,7 @@ end """ simulate_and_add!(epoch_data::AbstractMatrix, c, simulation, rng) simulate_and_add!(epoch_data::AbstractArray, c, simulation, rng) -Helper function to call `simulate_component` and add it to a provided Array` +Helper function to call `simulate_component` and add it to a provided Array. """ function simulate_and_add!(epoch_data::AbstractMatrix, c, simulation, rng) @debug "matrix" diff --git a/src/design.jl b/src/design.jl index 2207889a..fffff39a 100644 --- a/src/design.jl +++ b/src/design.jl @@ -78,8 +78,8 @@ end """ generate_events(design::MultiSubjectDesign) -Generates full factorial Dataframe according to MixedModelsSim.jl 's simdat_crossed function -Note: n_items = you can think of it as `trials` or better, as stimuli +Generate full factorial Dataframe according to MixedModelsSim.jl 's `simdat_crossed` function. +Note: n_items = 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 @@ -129,7 +129,7 @@ length(design::AbstractDesign) = *(size(design)...) """ RepeatDesign{T} -repeat a design DataFrame multiple times to mimick repeatedly recorded trials +Repeat a design DataFrame multiple times to mimick repeatedly recorded trials. ```julia designOnce = MultiSubjectDesign(; @@ -150,7 +150,7 @@ end """ UnfoldSim.generate_events(design::RepeatDesign{T}) -In a repeated design, iteratively calls the underlying {T} Design and concatenates. In case of MultiSubjectDesign, sorts by subject +In a repeated design, iteratively calls the underlying {T} Design and concatenates. In case of MultiSubjectDesign, sorts by subject. """ function UnfoldSim.generate_events(design::RepeatDesign) df = map(x -> generate_events(design.design), 1:design.repeat) |> x -> vcat(x...) diff --git a/src/headmodel.jl b/src/headmodel.jl index 7983559f..6e330f4d 100644 --- a/src/headmodel.jl +++ b/src/headmodel.jl @@ -88,7 +88,7 @@ magnitude(headmodel::AbstractHeadmodel) = magnitude(leadfield(headmodel)) """ magnitude(headmodel::Hartmut; type = "perpendicular") = Extract magnitude of 3-orientation-leadfield, -`type` (default: "perpendicular") => uses the provided source-point orientations - otherwise falls back to `norm` +`type` (default: "perpendicular") => uses the provided source-point orientations - otherwise falls back to `norm`. """ magnitude(headmodel::Hartmut; type = "perpendicular") = type == "perpendicular" ? magnitude(leadfield(headmodel), orientation(headmodel)) : @@ -96,7 +96,7 @@ magnitude(headmodel::Hartmut; type = "perpendicular") = """ magnitude(lf::AbstractArray{T,3}, orientation::AbstractArray{T,2}) where {T<:Real} -Returns the magnitude along an orientation of the leadfield +Return the magnitude along an orientation of the leadfield. """ function magnitude(lf::AbstractArray{T,3}, orientation::AbstractArray{T,2}) where {T<:Real} si = size(lf) @@ -111,7 +111,7 @@ end """ magnitude(lf::AbstractArray{T,3}) where {T<:Real} -If orientation is not specified, returns the maximal magnitude (norm of leadfield) +If orientation is not specified, returns the maximal magnitude (norm of leadfield). """ function magnitude(lf::AbstractArray{T,3}) where {T<:Real} si = size(lf) diff --git a/src/helper.jl b/src/helper.jl index b248f3cd..c78ca098 100755 --- a/src/helper.jl +++ b/src/helper.jl @@ -104,7 +104,7 @@ end sfreq; eventtime::Symbol = :latency, ) where {T<:Union{Missing,Number}} -Helper function to epoch data +Helper function to epoch data. Adapted from Unfold.jl: https://github.com/unfoldtoolbox/Unfold.jl/blob/b3a21c2bb7e93d2f45ec64b0197f4663a6d7939a/src/utilities.jl#L40 diff --git a/src/noise.jl b/src/noise.jl index ba4a1d29..0f205673 100644 --- a/src/noise.jl +++ b/src/noise.jl @@ -5,7 +5,7 @@ """ PinkNoise <: AbstractNoise -Generates Pink Noise using the SignalAnalysis.jl implementation +Generate Pink Noise using the SignalAnalysis.jl implementation. """ @with_kw struct PinkNoise <: AbstractNoise noiselevel = 1 @@ -15,7 +15,7 @@ end """ RedNoise <: AbstractNoise -Generates Red Noise using the SignalAnalysis.jl implementation +Generate Red Noise using the SignalAnalysis.jl implementation. """ @with_kw struct RedNoise <: AbstractNoise noiselevel = 1 @@ -27,8 +27,8 @@ end WhiteNoise <: WhiteNoise noiselevel = 1 imfilter = 0 -Generates White Noise using `randn` - thus Gaussian noise. -Using imfilter > 0 it is possible to smooth the noise using Image.imfilter +Generate White Noise using `randn` - thus Gaussian noise. +Using imfilter > 0 it is possible to smooth the noise using Image.imfilter. """ @with_kw struct WhiteNoise <: AbstractNoise noiselevel = 1 @@ -37,7 +37,7 @@ end """ RealisticNoise <: AbstractNoise -Not implemented- planned to use Artefacts.jl to provide real EEG data to add +Not implemented - planned to use Artefacts.jl to provide real EEG data to add. """ @with_kw struct RealisticNoise <: AbstractNoise noiselevel = 1 @@ -46,7 +46,7 @@ end """ NoNoise <: AbstractNoise -Returns zeros instead of noise +Return zeros instead of noise. """ struct NoNoise <: AbstractNoise end @@ -61,9 +61,9 @@ struct AutoRegressiveNoise <: AbstractNoise end """ ExponentialNoise <: AbstractNoise -Noise with exponential decay in AR spectrum +Noise with exponential decay in AR spectrum. !!! warning - Current implementation: cholesky of NxN matrix needs to be calculated, might need lot's of RAM + Current implementation: Cholesky of NxN matrix needs to be calculated, which might need lots of RAM. """ @@ -76,7 +76,7 @@ end """ simulate_noise(rng, t::Union{PinkNoise,RedNoise}, n::Int) -Generate Pink or Red Noise using the SignalAnalysis.jl implementation +Generate Pink or Red Noise using the `SignalAnalysis.jl` implementation. """ function simulate_noise(rng, t::Union{PinkNoise,RedNoise}, n::Int) return t.noiselevel .* rand(rng, t.func(n, 1.0)) @@ -84,7 +84,7 @@ end """ simulate_noise(rng, t::NoNoise, n::Int) -Returns zeros instead of noise +Return zeros instead of noise. """ function simulate_noise(rng, t::NoNoise, n::Int) return zeros(n) diff --git a/src/onset.jl b/src/onset.jl index 6b74c8e0..5e7d364a 100644 --- a/src/onset.jl +++ b/src/onset.jl @@ -4,9 +4,9 @@ """ struct UniformOnset <: AbstractOnset -Provides a Uniform Distribution in the inter-event-distances. -`width` is the width of the uniform distribution (=> the jitter) -`offset` is the minimal distance. The maximal distance is `offset + width` +Provide a Uniform Distribution of the inter-event-distances. +`width` is the width of the uniform distribution (=> the jitter). Since the lower bound is 0, `width` is also the upper bound. +`offset` is the minimal distance. The maximal distance is `offset + width`. """ @with_kw struct UniformOnset <: AbstractOnset width = 50 # how many samples jitter? @@ -14,7 +14,7 @@ Provides a Uniform Distribution in the inter-event-distances. end """ @with_kw struct LogNormalOnset <: AbstractOnset -log-normal inter-event-distances using the Distributions.jl truncated LogNormal distribution +Log-normal inter-event distances using the `Distributions.jl` truncated LogNormal distribution. Be careful with large `μ` and `σ` values, as they are on logscale. σ>8 can quickly give you out-of-memory sized signals! """ @@ -27,14 +27,14 @@ end """ struct NoOnset <: AbstractOnset end -In the case that the user directly wants no overlap to be simulated (=> epoched data) +In the case that the user directly wants no overlap to be simulated (=> epoched data). """ struct NoOnset <: AbstractOnset end """ simulate_interonset_distances(rng, onset::UniformOnset, design::AbstractDesign) simulate_interonset_distances(rng, onset::LogNormalOnset, design::AbstractDesign) -Generate the inter-event-onset vector in samples (returns Int) +Generate the inter-event-onset vector in samples (returns Int). """ function simulate_interonset_distances(rng, onset::UniformOnset, design::AbstractDesign) @@ -56,7 +56,7 @@ end """ simulate_onsets(rng, onset::AbstractOnset, simulation::Simulation) -Calls `simulate_interonset_distances` to generate distances between events and then adds them up to generate the actual latencies in samples +Call `simulate_interonset_distances` to generate distances between events and then add them up to generate the actual latencies in samples. # main call from `simulation` """ function simulate_onsets(rng, onset::AbstractOnset, simulation::Simulation) diff --git a/src/predefinedSimulations.jl b/src/predefinedSimulations.jl index 0ace3d93..01928159 100644 --- a/src/predefinedSimulations.jl +++ b/src/predefinedSimulations.jl @@ -11,16 +11,16 @@ predef_eeg(;kwargs...) predef_eeg(rng;kwargs...) predef_eeg(rng,n_subjects;kwargs...) -Generates a P1/N1/P3 complex. -In case `n_subjects` is defined - `MixedModelComponents` are generated, else `LinearModelComponents` +Generate a P1/N1/P3 complex. +In case `n_subjects` is defined - `MixedModelComponents` are generated, else `LinearModelComponents`. -Most used `kwargs` is: `return_epoched=true` to ignore the overlap/onset bits and return already epoched data +The most used `kwargs` is: `return_epoched=true` to ignore the overlap/onset bits and return already epoched data. ## Default params: -- n_repeats=100 +- n_repeats = 100 - event_order_function = x->shuffle(deepcopy(rng),x # random trial order - conditions = Dict(...), diff --git a/src/simulation.jl b/src/simulation.jl index 8aa50e4a..a3e92dd2 100755 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -99,7 +99,7 @@ end """ create_continuous_signal(rng, responses, simulation) -Based on the responses and simulation parameters, simulates onset-latencies and adds together a continuous signal +Based on the responses and simulation parameters, simulate onset latencies and add together a continuous signal. """ @@ -159,7 +159,7 @@ end add_responses!(signal, responses::Vector, e, s, tvec, erpvec) add_responses!(signal, responses::Matrix, e, s, tvec, erpvec) add_responses!(signal, responses::AbstractArray, e, s, tvec, erpvec) -Helper function to add inplace the responses to the signal, but for both 2D (1 channel) and 3D (X channel case) +Helper function to add inplace the responses to the signal, but for both 2D (1 channel) and 3D (X channel case). """ function add_responses!(signal, responses::Vector, e, s, tvec, erpvec) @views signal[e, tvec, s] .+= responses[:, erpvec] From 0489ec5107b2f22af07d4259c1cc6612987ceace Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Mon, 19 Feb 2024 14:44:45 +0100 Subject: [PATCH 059/113] Update component.jl --- src/component.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/component.jl b/src/component.jl index 87b0309e..ee50eede 100644 --- a/src/component.jl +++ b/src/component.jl @@ -124,6 +124,7 @@ Base.length(c::AbstractComponent) = length(c.basis) """ maxlength(c::Vector{AbstractComponent}) = maximum(length.(c)) +maximum of individual component lengths """ maxlength(c::Vector{AbstractComponent}) = maximum(length.(c)) From 36089b377e1dbd55db105d334a7adfcfbf88bc81 Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Mon, 19 Feb 2024 14:46:30 +0100 Subject: [PATCH 060/113] Update noise.jl --- src/noise.jl | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/noise.jl b/src/noise.jl index 0f205673..6b7b8592 100644 --- a/src/noise.jl +++ b/src/noise.jl @@ -6,6 +6,8 @@ """ PinkNoise <: AbstractNoise Generate Pink Noise using the SignalAnalysis.jl implementation. + +`noiselevel` is used to scale the noise """ @with_kw struct PinkNoise <: AbstractNoise noiselevel = 1 @@ -16,6 +18,8 @@ end """ RedNoise <: AbstractNoise Generate Red Noise using the SignalAnalysis.jl implementation. + +`noiselevel` is used to scale the noise """ @with_kw struct RedNoise <: AbstractNoise noiselevel = 1 @@ -28,7 +32,9 @@ end noiselevel = 1 imfilter = 0 Generate White Noise using `randn` - thus Gaussian noise. -Using imfilter > 0 it is possible to smooth the noise using Image.imfilter. +`noiselevel` is used to scale the noise + +Using `imfilter` > 0 it is possible to smooth the noise using Image.imfilter. """ @with_kw struct WhiteNoise <: AbstractNoise noiselevel = 1 @@ -62,11 +68,12 @@ struct AutoRegressiveNoise <: AbstractNoise end ExponentialNoise <: AbstractNoise Noise with exponential decay in AR spectrum. + +`noiselevel` is used to scale the noise + !!! warning Current implementation: Cholesky of NxN matrix needs to be calculated, which might need lots of RAM. - """ - @with_kw struct ExponentialNoise <: AbstractNoise noiselevel = 1 ν = 1.5 # exponential factor of AR decay "nu" From 70e49f5214f1aa0422d405505b8e80692c668e59 Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Mon, 19 Feb 2024 14:48:15 +0100 Subject: [PATCH 061/113] Update bases.jl --- src/bases.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bases.jl b/src/bases.jl index 32eb2f73..2dbeffc9 100644 --- a/src/bases.jl +++ b/src/bases.jl @@ -3,25 +3,25 @@ ## EEG """ p100(;sfreq=100) -Generator for Hanning window, peak at 100ms, width 100ms, at kwargs `sfreq` (default 100). +Generator for Hanning window, peak at 100ms, width 100ms, at kwargs `sfreq` (default 100). Returns a vector. """ p100(; sfreq = 100) = hanning(0.1, 0.1, sfreq) """ p300(;sfreq=100) -Generator for Hanning window, peak at 300ms, width 300ms, at kwargs `sfreq` (default 100). +Generator for Hanning window, peak at 300ms, width 300ms, at kwargs `sfreq` (default 100). Returns a vector. """ p300(; sfreq = 100) = hanning(0.3, 0.3, sfreq) """ n170(;sfreq=100) -Generator for Hanning window, negative (!) peak at 170ms, width 150ms, at kwargs `sfreq` (default 100). +Generator for Hanning window, negative (!) peak at 170ms, width 150ms, at kwargs `sfreq` (default 100). Returns a vector. """ n170(; sfreq = 100) = -hanning(0.15, 0.17, sfreq) """ n400(;sfreq=100) -Generator for Hanning window, negative (!) peak at 400ms, width 400ms, at kwargs `sfreq` (default 100). +Generator for Hanning window, negative (!) peak at 400ms, width 400ms, at kwargs `sfreq` (default 100). Returns a vector. """ n400(; sfreq = 100) = -hanning(0.4, 0.4, sfreq) From abc9f178766b7e855aba94c36854171a1249d55b Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Mon, 19 Feb 2024 14:57:16 +0100 Subject: [PATCH 062/113] Update component.jl --- src/component.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/component.jl b/src/component.jl index ee50eede..a2d87fc1 100644 --- a/src/component.jl +++ b/src/component.jl @@ -140,7 +140,7 @@ simulate_component(rng, c::AbstractComponent, simulation::Simulation) = Generate a linear model design matrix, weight it by c.β and multiply the result with the given basis vector. julia> c = UnfoldSim.LinearModelComponent([0,1,1,0],@formula(0~1+cond),[1,2],Dict()) -julia> design = MultiSubjectDesign(;n_subjects=2,n_items=50,item_between=(;:cond=>["A","B"])) +julia> design = MultiSubjectDesign(;n_subjects=2,n_items=50,items_between=(;:cond=>["A","B"])) julia> simulate_component(StableRNG(1),c,design) """ function simulate_component(rng, c::LinearModelComponent, design::AbstractDesign) @@ -169,7 +169,7 @@ A trick is used to remove the Normal-Noise from the MixedModel which might lead Currently, it is not possible to use a different basis for fixed and random effects, but a code-stub exists (it is slow though). -julia> design = MultiSubjectDesign(;n_subjects=2,n_items=50,item_between=(;:cond=>["A","B"])) +julia> design = MultiSubjectDesign(;n_subjects=2,n_items=50,items_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) From 162f6d8960d1db830ac4582dc869391f2e525ec6 Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Mon, 19 Feb 2024 15:49:18 +0100 Subject: [PATCH 063/113] Update predefinedSimulations.jl --- src/predefinedSimulations.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/predefinedSimulations.jl b/src/predefinedSimulations.jl index 01928159..874bb870 100644 --- a/src/predefinedSimulations.jl +++ b/src/predefinedSimulations.jl @@ -14,7 +14,7 @@ predef_eeg(rng,n_subjects;kwargs...) Generate a P1/N1/P3 complex. In case `n_subjects` is defined - `MixedModelComponents` are generated, else `LinearModelComponents`. -The most used `kwargs` is: `return_epoched=true` to ignore the overlap/onset bits and return already epoched data. +The most used `kwargs` is: `return_epoched=true` which returns already epoched data. If you want epoched data without overlap, specify `onset=NoOnset()` and `return_epoched=true` @@ -141,7 +141,7 @@ end predef_2x2(rng::AbstractRNG;kwargs...) -Most used kwargs is: `return_epoched=true` to ignore the overlap/onset bits and return already epoched data +The most used `kwargs` is: `return_epoched=true` which returns already epoched data. If you want epoched data without overlap, specify `onset=NoOnset()` and `return_epoched=true` #### design - `n_items`=100, From 5b65d9863abace81c370bc1c63f0a7dbe4d0403b Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Mon, 19 Feb 2024 16:37:41 +0100 Subject: [PATCH 064/113] v0.3.1 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 721992b1..8a8913b0 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnfoldSim" uuid = "ed8ae6d2-84d3-44c6-ab46-0baf21700804" authors = ["Benedikt Ehinger", "Luis Lips", "Judith Schepers", "Maanik Marathe"] -version = "0.3.0" +version = "0.3.1" [deps] Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" From 5128e3f6d9335ddf81058405ca2bf34cd629915a Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 26 Feb 2024 12:50:14 +0000 Subject: [PATCH 065/113] Set minimum for first onset to 1 (not 0) --- src/onset.jl | 2 +- test/onset.jl | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/onset.jl b/src/onset.jl index 5e7d364a..29530fce 100644 --- a/src/onset.jl +++ b/src/onset.jl @@ -65,7 +65,7 @@ function simulate_onsets(rng, onset::AbstractOnset, simulation::Simulation) onsets = simulate_interonset_distances(rng, onset, simulation.design) # accumulate them - onsets_accum = accumulate(+, onsets, dims = 1) + onsets_accum = accumulate(+, onsets, dims = 1, init = 1) return onsets_accum end diff --git a/test/onset.jl b/test/onset.jl index 7d46908f..9865a32c 100644 --- a/test/onset.jl +++ b/test/onset.jl @@ -46,7 +46,7 @@ @test minimum(rand_vec) >= 0 end @testset "sim_onsets" begin - # test accumulate always increasing + uniform_onset = UniformOnset(; offset = 0, width = 50) accumulated_onset = UnfoldSim.simulate_onsets( @@ -54,7 +54,11 @@ uniform_onset, gen_debug_simulation(onset = uniform_onset), ) + # test accumulate always increasing @test all(diff(accumulated_onset, dims = 1) .>= 0) + + # test that the first onset is at >=1 (not 0) + @test accumulated_onset[1] >= 1 end end From 5af1d2dc7017ae88a36592a4e9a3d7547a23f89c Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Mon, 26 Feb 2024 13:55:17 +0100 Subject: [PATCH 066/113] Update test/onset.jl Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- test/onset.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/test/onset.jl b/test/onset.jl index 9865a32c..0e6ec715 100644 --- a/test/onset.jl +++ b/test/onset.jl @@ -46,7 +46,6 @@ @test minimum(rand_vec) >= 0 end @testset "sim_onsets" begin - uniform_onset = UniformOnset(; offset = 0, width = 50) accumulated_onset = UnfoldSim.simulate_onsets( From 0dff1739c5fff985c54a09eaa82afbb2e8fbb2ec Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Mon, 26 Feb 2024 10:18:49 +0000 Subject: [PATCH 067/113] formatted nicer, #77 --- docs/literate/tutorials/multisubject.jl | 115 ++++++++++++++++++++++++ docs/src/index.md | 13 +-- 2 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 docs/literate/tutorials/multisubject.jl diff --git a/docs/literate/tutorials/multisubject.jl b/docs/literate/tutorials/multisubject.jl new file mode 100644 index 00000000..4b9540c4 --- /dev/null +++ b/docs/literate/tutorials/multisubject.jl @@ -0,0 +1,115 @@ +using UnfoldSim +using Unfold +using CairoMakie +using UnfoldMakie +using DataFrames + +# # Multi-Subject simulation +# Similar to the single subject case, multi-subject simulation depends on: +# - `Design` (typically a `MultiSubjectDesign`) +# - `Components` (typically a `MixedModelComponent`) +# - `Onset` (any) +# - `Noise` (any) + +# ## Design + +# Our first design should be 20 subjects, with 3 items each. Any individual image is shown only either as large or small, thus we choose `items_between`. +design = MultiSubjectDesign( + n_subjects = 20, + n_items = 4, + items_between = Dict(:condition => ["large", "small"]), +) + + +# ## Between, within? +# In the beginning, the distinction between `between-items`, `between-subjects` and `within-subjects`, `within-items` and `both-between`, `both-within` feels daunting. +# +# We base our terminology on `MixedModelsSim` which uses the following definitions: +# - `subjects_between` -> effects between subjects, e.g. young vs old +# - `items_between` -> effects between items, e.g. natural vs artificial images, (but shown to all subjects if not specified in subjects_between as well) +# - `both_within` -> effects completly crossed, e.g. word vs. scramble, where the "original" word is the item, and shown to all subjects + + +# ## Components +# For multi-subject, similar to the `LinearModelComponent` specified before, we have to define the fixed effect `β`, the model parameters that are applied to all subjects. +β = [1, 2] # 1 = intercept, 2 = condition_is_large effect + +# In addition, we have to provide random effects `σs`, which define the spread (and correlation) of the subjects around the fixed effects, foreach parameter +σs = Dict( + :subject => [0.5, 1], # we have more spread in the condition-effect + :item => [1], # the item-variability is higher than the subject-variability +) + +# now we are ready to build it together +signal = MixedModelComponent(; + basis = UnfoldSim.hanning(50), + formula = @formula(0 ~ 1 + condition + (1 + condition | subject) + (1 | item)), + β = β, + σs = σs, + contrasts = Dict(:condition => EffectsCoding()), # we highly recommend specifying your contrasts, by Default its Dummy/ReferenceCoding with alphabetically sorted levels (relying 100% on StatsModels.jl) +) + +# and simulate! +data, evts = simulate(design, signal, NoOnset(), NoNoise(), return_epoched = true); + +# We get data with 50 samples (our `basis` from above), with `2` trials/items and 20 subjects. We get items and subjects separately because we chose no-overlap (via `NoOnset`) and `return_epoched=true``. +@info size(data) +first(evts, 5) + +# Finally, let's plot the data +f = Figure() + +for k = 1:4 + series( + f[1, k], + data[:, k, :]', + solid_color = :black, + axis = (; limits = ((0, 50), (-4, 5))), + ) + Label(f[1, k, Top()], text = "Item:" * evts[k, :item] * ", c:" * evts[k, :condition]) +end +f + +# Some remarks on interpreting the plot: +# - The β main-effect of small / Large (#1 and #3 vs. #2 and #4) is clearly visible. +# - The variability between subjects, is the variability between the individual curves. +# - The item effect shows up e.g. that #2 vs. #4 column show different values. + + +# # Continuous Signals / Overlap +# Let's continue our tutorial and simulate overlapping signals instead. +# +# We replace the `NoOnset` with an `UniformOnset` between 20 and 70 sampples after each event.` We further remove the `return_epoched`, because we want to have continuous data for now. +data, evts = simulate(design, signal, UniformOnset(offset = 20, width = 50), NoNoise()); +size(data) + +# The data is now $size(data,1) x $size(data,2), with the first dimension being continuous data, and the latter still the subjects. +series(data', solid_color = :black) + +# Each line is one subject, and it looks a bit unstructured, because the event-onsets are of course random or each subject. +# !!! note +# All subjects have the same sequence of trials, if you need to change this, specify a `event_order_function` in the `MultiSubjectDesign` + + +# # Analyzing these data with Unfold.jl +# We will analyze these data using the `Unfold.jl` toolbox. While preliminary support for deconvolution (overlap correction) for mixed models is available, here we will simply apply it separately to each timepoint, following the MassUnivariate approach. +data, evts = simulate( + design, + signal, + UniformOnset(offset = 20, width = 50), + NoNoise(); + return_epoched = true, +); +size(data) + +# For Unfold.jl, we have to reshape the data, so that all subjects are concatenated. +data = reshape(data, size(data, 1), :) +times = range(0, 1, length = size(data, 1)) +m = fit( + UnfoldModel, + @formula(0 ~ 1 + condition + (1 | item) + (1 + condition | subject)), + evts, + data, + times, +) +plot_erp(coeftable(m))#, mapping = (; col = :group)) # FIXME facetting by col currently broken, waiting for new UnfoldMakie release! diff --git a/docs/src/index.md b/docs/src/index.md index a9c184f3..415199d4 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -12,11 +12,12 @@ We offer some predefined signals, check them out! For instance an P1/N170/P300 complex. ```@example main using UnfoldSim -using CairoMakie -data,evts = UnfoldSim.predef_eeg(;n_repeats=1,noiselevel=0.8) +using CairoMakie # plotting -lines(data;color="black") -vlines!(evts.latency;color=["orange","teal"][1 .+ (evts.condition .=="car")]) +data, evts = UnfoldSim.predef_eeg(; n_repeats = 1, noiselevel = 0.8) + +lines(data; color = "black") +vlines!(evts.latency; color = ["orange", "teal"][1 .+ (evts.condition.=="car")]) current_figure() ``` @@ -24,7 +25,7 @@ current_figure() ## Or simulate epoched data directly ```@example main -data,evts = UnfoldSim.predef_eeg(;n_repeats=20,noiselevel=0.8,return_epoched=true) -heatmap(data[:,sortperm(evts,[:condition,:continuous])]) +data, evts = UnfoldSim.predef_eeg(; n_repeats = 20, noiselevel = 0.8, return_epoched = true) +heatmap(data[:, sortperm(evts, [:condition, :continuous])]) ``` From 3d10aed4fbd3eb21a6949117c5709d8eaf47930c Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Mon, 26 Feb 2024 10:19:05 +0000 Subject: [PATCH 068/113] removed unused file --- scripts/sketch.jl | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 scripts/sketch.jl diff --git a/scripts/sketch.jl b/scripts/sketch.jl deleted file mode 100644 index 671e7128..00000000 --- a/scripts/sketch.jl +++ /dev/null @@ -1,14 +0,0 @@ -using MixedModelsSim -using MixedModels -using Random - - - - -dfA = get_df(1) -dfB = get_df(2) - - -df = dfA # for debug - -signal_coef(dfA) From 5d34f6eb49de077f80dc536a92d61dc15ae71bfc Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Mon, 26 Feb 2024 10:20:45 +0000 Subject: [PATCH 069/113] fix methods error, fix #6 hard to parse error --- src/component.jl | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/component.jl b/src/component.jl index a2d87fc1..335c6254 100644 --- a/src/component.jl +++ b/src/component.jl @@ -123,10 +123,10 @@ end Base.length(c::AbstractComponent) = length(c.basis) """ - maxlength(c::Vector{AbstractComponent}) = maximum(length.(c)) + maxlength(c::Vector{<:AbstractComponent}) = maximum(length.(c)) maximum of individual component lengths """ -maxlength(c::Vector{AbstractComponent}) = maximum(length.(c)) +maxlength(c::Vector{<:AbstractComponent}) = maximum(length.(c)) """ simulate_component(rng, c::AbstractComponent, simulation::Simulation) @@ -199,7 +199,17 @@ function simulate_component(rng, c::MixedModelComponent, design::AbstractDesign) if 1 == 1 named_random_effects = weight_σs(c.σs, 1.0, σ_lmm) θ = createθ(m; named_random_effects...) - simulate!(deepcopy(rng), m.y, m; β = c.β, σ = σ_lmm, θ = θ) + @debug named_random_effects, θ, m.θ + try + simulate!(deepcopy(rng), m.y, m; β = c.β, σ = σ_lmm, θ = θ) + catch e + if isa(e, DimensionMismatch) + @warn "Most likely your σs's do not match the formula!" + elseif isa(e, ArgumentError) + @warn "Most likely your β's do not match the formula!" + end + rethrow(e) + end # save data to array #@show size(m.y) From 6cc1fd59bb9d09520f280ad2e9440b8a5b002530 Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Mon, 26 Feb 2024 10:21:04 +0000 Subject: [PATCH 070/113] fix italic-issue in docstring --- src/design.jl | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/design.jl b/src/design.jl index fffff39a..c4b72bf1 100644 --- a/src/design.jl +++ b/src/design.jl @@ -1,10 +1,12 @@ """ -- 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 -- event_order_function = x->x; # can be used to sort, or x->shuffle(MersenneTwister(42),x) - be sure to fix/update the rng accordingly!! + MultiSubjectDesign + +- `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 if not specified also in `subjects_between`) +- `both_within` = nothing -> effects completly crossed +- `event_order_function` = `x->x`; # can be used to sort, or e.g. `x->shuffle(MersenneTwister(42),x)` - be sure to fix/update the rng accordingly!! tipp: check the resulting dataframe using `generate_events(design)` @@ -30,7 +32,7 @@ end """ - conditions = Dict of conditions, e.g. `Dict(:A=>["a_small","a_big"],:B=>["b_tiny","b_large"])` -- event_order_function = x->x; # can be used to sort, or x->shuffle(MersenneTwister(42),x) - be sure to fix/update the rng accordingly!! +- `event_order_function` = 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_events(design)` depend on the full factorial of your `conditions`. @@ -83,7 +85,7 @@ Note: n_items = 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 design.event_order_function. Could be used to duplicate trials, sort, subselect etc. +Afterwards applies `design.event_order_function``. Could be used to duplicate trials, sort, subselect etc. Finally it sorts by `:subject` From 8f57729b7ff42044f329630cb2c5cfd96cca671f Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Mon, 26 Feb 2024 10:21:30 +0000 Subject: [PATCH 071/113] add possibility to call simulation without specifying random generator --- src/simulation.jl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/simulation.jl b/src/simulation.jl index a3e92dd2..64652a49 100755 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -32,8 +32,14 @@ Some remarks to how the noise is added: """ + +function simulate(args...; kwargs...) + @warn "No random generator defined, used the default (`Random.MersenneTwister(1)`) with a fixed seed. This will always return the same results and the user is strongly encouraged to provide their own random generator!" + simulate(MersenneTwister(1), args...; kwargs...) +end + simulate( - rng, + rng::AbstractRNG, design::AbstractDesign, signal, onset::AbstractOnset, @@ -41,7 +47,8 @@ simulate( kwargs..., ) = simulate(rng, Simulation(design, signal, onset, noise); kwargs...) -function simulate(rng, simulation::Simulation; return_epoched::Bool = false) + +function simulate(rng::AbstractRNG, simulation::Simulation; return_epoched::Bool = false) (; design, components, onset, noisetype) = simulation # equivalent to !(isa(onset,NoOnset) && return_epoched == false) From a28ed8137b8bbb236c20d0a22f1ca0c885999ec6 Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Mon, 26 Feb 2024 10:23:22 +0000 Subject: [PATCH 072/113] add multisubject to index --- docs/make.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/make.jl b/docs/make.jl index e97757f1..36d61d32 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -35,6 +35,7 @@ makedocs(; "Quickstart" => "generated/tutorials/quickstart.md", "Simulate ERPs" => "generated/tutorials/simulateERP.md", "Poweranalysis" => "generated/tutorials/poweranalysis.md", + "Multi-Subjects" => "generated/tutorials/multisubject.md", ], "Reference" => [ "Overview: Toolbox Functions" => "./generated/reference/overview.md", From 37b46c89d38878596ad79e388329065b181413e3 Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Mon, 26 Feb 2024 10:53:54 +0000 Subject: [PATCH 073/113] wadded warning for extreme inter-onset-distances --- src/onset.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/onset.jl b/src/onset.jl index 29530fce..4ad97aa7 100644 --- a/src/onset.jl +++ b/src/onset.jl @@ -64,6 +64,9 @@ function simulate_onsets(rng, onset::AbstractOnset, simulation::Simulation) # sample different onsets onsets = simulate_interonset_distances(rng, onset, simulation.design) + if maximum(onsets) > 10000 + @warn "Maximum of inter-event-distances was $(maximum(onsets)) - are you sure this is what you want?" + end # accumulate them onsets_accum = accumulate(+, onsets, dims = 1, init = 1) From 32d14be3b70aa2a9f9269ff6a9547390274ef0d5 Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Mon, 26 Feb 2024 11:22:17 +0000 Subject: [PATCH 074/113] better docstring + enforce Dict{Symbol} --- src/design.jl | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/design.jl b/src/design.jl index c4b72bf1..42e02328 100644 --- a/src/design.jl +++ b/src/design.jl @@ -3,13 +3,15 @@ - `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 if not specified also in `subjects_between`) -- `both_within` = nothing -> effects completly crossed +- `subjects_between` = Dict{Symbol,Vector} -> effects between subjects, e.g. young vs old +- `items_between` = Dict{Symbol,Vector} -> effects between items, e.g. natural vs artificial images, (but shown to all subjects if not specified also in `subjects_between`) +- `both_within` = Dict{Symbol,Vector} -> effects completly crossed - `event_order_function` = `x->x`; # can be used to sort, or e.g. `x->shuffle(MersenneTwister(42),x)` - be sure to fix/update the rng accordingly!! tipp: check the resulting dataframe using `generate_events(design)` + + ```julia # declaring same condition both sub-between and item-between results in a full between subject/item design design = MultiSubjectDesign(; @@ -23,15 +25,15 @@ design = MultiSubjectDesign(; @with_kw struct MultiSubjectDesign <: AbstractDesign n_subjects::Int n_items::Int - subjects_between = nothing - items_between = nothing - both_within = nothing + subjects_between::Dict{Symbol,Vector} = Dict() + items_between::Dict{Symbol,Vector} = Dict() + both_within::Dict{Symbol,Vector} = Dict() event_order_function = x -> x # can be used to sort, or x->shuffle(rng,x) end """ -- conditions = Dict of conditions, e.g. `Dict(:A=>["a_small","a_big"],:B=>["b_tiny","b_large"])` +- conditions = Dict{Symbol,Vector} of conditions, e.g. `Dict(:A=>["a_small","a_big"],:B=>["b_tiny","b_large"])` - `event_order_function` = 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_events(design)` depend on the full factorial of your `conditions`. @@ -43,7 +45,7 @@ If conditions are omitted (or set to `nothing`), a single trial is simulated wit tipp: check the resulting dataframe using `generate_events(design)` """ @with_kw struct SingleSubjectDesign <: AbstractDesign - conditions = nothing + conditions::Dict{Symbol,Vector} = Dict() event_order_function = x -> x end @@ -65,7 +67,7 @@ julia> d = SingleSubjectDesign(;conditions= Dict(:A=>nlevels(5),:B=>nlevels(2))) julia> generate_events(d) """ function generate_events(design::SingleSubjectDesign) - if isnothing(design.conditions) + if isempty(design.conditions) events = DataFrame(:dummy => [:dummy]) else # we get a Dict(:A=>["1","2"],:B=>["3","4"]), but needed a list @@ -97,16 +99,17 @@ function generate_events(design::MultiSubjectDesign) # check that :dv is not in any condition allconditions = [design.subjects_between, design.items_between, design.both_within] - @assert all(isnothing.(allconditions)) || - :dv ∉ keys(merge(allconditions[.!isnothing.(allconditions)]...)) "due to technical limitations in MixedModelsSim.jl, `:dv` cannot be used as a factorname" + @assert all(isempty.(allconditions)) || + :dv ∉ keys(merge(allconditions[.!isempty.(allconditions)]...)) "due to technical limitations in MixedModelsSim.jl, `:dv` cannot be used as a factorname" data = DataFrame( MixedModelsSim.simdat_crossed( design.n_subjects, design.n_items, - subj_btwn = design.subjects_between, - item_btwn = design.items_between, - both_win = design.both_within, + subj_btwn = isempty(design.subjects_between) ? nothing : + design.subjects_between, + item_btwn = isempty(design.items_between) ? nothing : design.items_between, + both_win = isempty(design.both_within) ? nothing : design.both_within, ), ) rename!(data, :subj => :subject) From 35fb5c850529c3aa438ee1b05c52b569cf5d3dfe Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Mon, 26 Feb 2024 11:22:29 +0000 Subject: [PATCH 075/113] enforce docstring --- test/setup.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/setup.jl b/test/setup.jl index 4838bb6a..8cad84ed 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -9,7 +9,7 @@ using DataFrames function gen_debug_design(; n_subjects = 20, n_item = 100) # define design parameters - item_btwn = Dict("stimType" => ["A", "B"]) + item_btwn = Dict(:stimType => ["A", "B"]) # instantiate the design return MultiSubjectDesign(; From 7de7e979fbeec84e21938f1fa6b17684655400fc Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Mon, 26 Feb 2024 11:23:02 +0000 Subject: [PATCH 076/113] remove creation for Simulation object in tutorial --- docs/literate/tutorials/quickstart.jl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/literate/tutorials/quickstart.jl b/docs/literate/tutorials/quickstart.jl index be13e05b..56ddedb0 100644 --- a/docs/literate/tutorials/quickstart.jl +++ b/docs/literate/tutorials/quickstart.jl @@ -31,11 +31,8 @@ onset = UniformOnset(; width = 20, offset = 4); 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, events = simulate(MersenneTwister(1), design, signal, onset, noise); # 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. From 65278370d612dde518ea587d260fe56bb39f6d19 Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Mon, 26 Feb 2024 11:42:57 +0000 Subject: [PATCH 077/113] added return_parameter flag --- src/component.jl | 82 ++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/src/component.jl b/src/component.jl index 335c6254..3cb92379 100644 --- a/src/component.jl +++ b/src/component.jl @@ -169,12 +169,19 @@ A trick is used to remove the Normal-Noise from the MixedModel which might lead Currently, it is not possible to use a different basis for fixed and random effects, but a code-stub exists (it is slow though). +- `return_parameters` (Bool,false) - can be used to return the per-event parameters used to weight the basis function. Sometimes useful to see what is simulated + julia> design = MultiSubjectDesign(;n_subjects=2,n_items=50,items_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) """ -function simulate_component(rng, c::MixedModelComponent, design::AbstractDesign) +function simulate_component( + rng, + c::MixedModelComponent, + design::AbstractDesign; + return_parameters = false, +) events = generate_events(design) # add the mixed models lefthandside @@ -192,58 +199,57 @@ function simulate_component(rng, c::MixedModelComponent, design::AbstractDesign) # empty epoch data - epoch_data_component = zeros(Int(length(c.basis)), length(design)) + #epoch_data_component = zeros(Int(length(c.basis)), length(design)) # residual variance for lmm σ_lmm = 0.0001 - if 1 == 1 - named_random_effects = weight_σs(c.σs, 1.0, σ_lmm) - θ = createθ(m; named_random_effects...) - @debug named_random_effects, θ, m.θ - try - simulate!(deepcopy(rng), m.y, m; β = c.β, σ = σ_lmm, θ = θ) - catch e - if isa(e, DimensionMismatch) - @warn "Most likely your σs's do not match the formula!" - elseif isa(e, ArgumentError) - @warn "Most likely your β's do not match the formula!" - end - rethrow(e) - end - - # save data to array - #@show size(m.y) - #@show size(c.basis) + named_random_effects = weight_σs(c.σs, 1.0, σ_lmm) + θ = createθ(m; named_random_effects...) + @debug named_random_effects, θ, m.θ + try + simulate!(deepcopy(rng), m.y, m; β = c.β, σ = σ_lmm, θ = θ) + catch e + if isa(e, DimensionMismatch) + @warn "Most likely your σs's do not match the formula!" + elseif isa(e, ArgumentError) + @warn "Most likely your β's do not match the formula!" + end + rethrow(e) + end - epoch_data_component = kron(c.basis, m.y') + # save data to array - else - # iterate over each timepoint - for t in eachindex(c.basis) + # in case the parameters are of interest, we will return those, not them weighted by basis + epoch_data_component = kron(return_parameters ? [1.0] : c.basis, m.y') + return epoch_data_component + #= + else + # iterate over each timepoint + for t in eachindex(c.basis) - # select weight from basis - # right now, it is the same, but maybe changein thefuture? - basis_β = c.basis[t] - basis_σs = c.basis[t] + # select weight from basis + # right now, it is the same, but maybe changein thefuture? + basis_β = c.basis[t] + basis_σs = c.basis[t] - # weight random effects by the basis function - named_random_effects = weight_σs(c.σs, basis_σs, σ_lmm) + # weight random effects by the basis function + named_random_effects = weight_σs(c.σs, basis_σs, σ_lmm) - θ = createθ(m; named_random_effects...) + θ = createθ(m; named_random_effects...) - # simulate with new parameters; will update m.y - simulate!(deepcopy(rng), m.y, m; β = basis_β .* c.β, σ = σ_lmm, θ = θ) + # simulate with new parameters; will update m.y + simulate!(deepcopy(rng), m.y, m; β = basis_β .* c.β, σ = σ_lmm, θ = θ) - # save data to array - epoch_data_component[t, :] = m.y + # save data to array + epoch_data_component[t, :] = m.y + end end - end - return epoch_data_component - + return epoch_data_component + =# end From 7a24e59bf66ed09f76b2d20872b22c1ca320ddc6 Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Mon, 26 Feb 2024 11:44:23 +0000 Subject: [PATCH 078/113] small reaming --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index 36d61d32..57b83b6e 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -50,7 +50,7 @@ makedocs(; "Generate multi channel data" => "./generated/HowTo/multichannel.md", "Use predefined design / onsets data" => "./generated/HowTo/predefinedData.md", ], - "DocStrings" => "api.md", + "API / DocStrings" => "api.md", ], ) From f33d7d3fe5bd75e58f7f945e8fc5cbe8a45c931b Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Mon, 26 Feb 2024 20:49:56 +0100 Subject: [PATCH 079/113] Update multisubject.jl --- docs/literate/tutorials/multisubject.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/literate/tutorials/multisubject.jl b/docs/literate/tutorials/multisubject.jl index 4b9540c4..e8f81631 100644 --- a/docs/literate/tutorials/multisubject.jl +++ b/docs/literate/tutorials/multisubject.jl @@ -32,7 +32,7 @@ design = MultiSubjectDesign( # ## Components # For multi-subject, similar to the `LinearModelComponent` specified before, we have to define the fixed effect `β`, the model parameters that are applied to all subjects. -β = [1, 2] # 1 = intercept, 2 = condition_is_large effect +β = [1, 2] # 1 = intercept, 2 = difference between large and small # In addition, we have to provide random effects `σs`, which define the spread (and correlation) of the subjects around the fixed effects, foreach parameter σs = Dict( From ef09c34bb3c2afa4d8e198a2a69c185c6e244ed0 Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Mon, 26 Feb 2024 20:51:28 +0100 Subject: [PATCH 080/113] Update multisubject.jl --- docs/literate/tutorials/multisubject.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/literate/tutorials/multisubject.jl b/docs/literate/tutorials/multisubject.jl index e8f81631..8c3e9ae9 100644 --- a/docs/literate/tutorials/multisubject.jl +++ b/docs/literate/tutorials/multisubject.jl @@ -53,7 +53,8 @@ signal = MixedModelComponent(; data, evts = simulate(design, signal, NoOnset(), NoNoise(), return_epoched = true); # We get data with 50 samples (our `basis` from above), with `2` trials/items and 20 subjects. We get items and subjects separately because we chose no-overlap (via `NoOnset`) and `return_epoched=true``. -@info size(data) +size(data) + first(evts, 5) # Finally, let's plot the data From 93cde2aa2e29bc54486f86488a2f774d9ffb0660 Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Mon, 26 Feb 2024 20:54:59 +0100 Subject: [PATCH 081/113] Apply suggestions from code review Co-authored-by: Judith Schepers --- docs/literate/tutorials/multisubject.jl | 14 +++++++------- docs/literate/tutorials/quickstart.jl | 6 +++--- docs/make.jl | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/literate/tutorials/multisubject.jl b/docs/literate/tutorials/multisubject.jl index 8c3e9ae9..afde853b 100644 --- a/docs/literate/tutorials/multisubject.jl +++ b/docs/literate/tutorials/multisubject.jl @@ -4,7 +4,7 @@ using CairoMakie using UnfoldMakie using DataFrames -# # Multi-Subject simulation +# # Multi-subject simulation # Similar to the single subject case, multi-subject simulation depends on: # - `Design` (typically a `MultiSubjectDesign`) # - `Components` (typically a `MixedModelComponent`) @@ -40,7 +40,7 @@ design = MultiSubjectDesign( :item => [1], # the item-variability is higher than the subject-variability ) -# now we are ready to build it together +# now we are ready to assemble the parts signal = MixedModelComponent(; basis = UnfoldSim.hanning(50), formula = @formula(0 ~ 1 + condition + (1 + condition | subject) + (1 | item)), @@ -52,7 +52,7 @@ signal = MixedModelComponent(; # and simulate! data, evts = simulate(design, signal, NoOnset(), NoNoise(), return_epoched = true); -# We get data with 50 samples (our `basis` from above), with `2` trials/items and 20 subjects. We get items and subjects separately because we chose no-overlap (via `NoOnset`) and `return_epoched=true``. +# We get data with 50 samples (our `basis` from above), with `4` items and 20 subjects. We get items and subjects separately because we chose no-overlap (via `NoOnset`) and `return_epoched = true``. size(data) first(evts, 5) @@ -72,7 +72,7 @@ end f # Some remarks on interpreting the plot: -# - The β main-effect of small / Large (#1 and #3 vs. #2 and #4) is clearly visible. +# - The β main-effect of small (#2 and #4) vs. large (#1 and #3) is clearly visible. # - The variability between subjects, is the variability between the individual curves. # - The item effect shows up e.g. that #2 vs. #4 column show different values. @@ -80,16 +80,16 @@ f # # Continuous Signals / Overlap # Let's continue our tutorial and simulate overlapping signals instead. # -# We replace the `NoOnset` with an `UniformOnset` between 20 and 70 sampples after each event.` We further remove the `return_epoched`, because we want to have continuous data for now. +# We replace the `NoOnset` with an `UniformOnset` with 20 to 70 samples between subsequent events. We further remove the `return_epoched`, because we want to have continuous data for now. data, evts = simulate(design, signal, UniformOnset(offset = 20, width = 50), NoNoise()); size(data) # The data is now $size(data,1) x $size(data,2), with the first dimension being continuous data, and the latter still the subjects. series(data', solid_color = :black) -# Each line is one subject, and it looks a bit unstructured, because the event-onsets are of course random or each subject. +# Each line is one subject, and it looks a bit unstructured, because the event-onsets are of course random for each subject. # !!! note -# All subjects have the same sequence of trials, if you need to change this, specify a `event_order_function` in the `MultiSubjectDesign` +# All subjects have the same sequence of trials, if you need to change this, specify a `event_order_function` in the `MultiSubjectDesign`. # # Analyzing these data with Unfold.jl diff --git a/docs/literate/tutorials/quickstart.jl b/docs/literate/tutorials/quickstart.jl index 56ddedb0..86896185 100644 --- a/docs/literate/tutorials/quickstart.jl +++ b/docs/literate/tutorials/quickstart.jl @@ -31,11 +31,11 @@ onset = UniformOnset(; width = 20, offset = 4); noise = PinkNoise(; noiselevel = 0.2); # ## Combine & Generate -# finally, we will simulate some data +# Finally, we will simulate some data data, events = simulate(MersenneTwister(1), design, signal, onset, noise); -# Data is a `n-sample` Vector (but could be a Matrix for e.g. `MultiSubjectDesign`). +# `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. +# `Events` is a DataFrame that contains a column `latency` with the onsets of events. # ## Plot them! lines(data; color = "black") diff --git a/docs/make.jl b/docs/make.jl index 57b83b6e..fb18d297 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -35,7 +35,7 @@ makedocs(; "Quickstart" => "generated/tutorials/quickstart.md", "Simulate ERPs" => "generated/tutorials/simulateERP.md", "Poweranalysis" => "generated/tutorials/poweranalysis.md", - "Multi-Subjects" => "generated/tutorials/multisubject.md", + "Multi-subject simulation" => "generated/tutorials/multisubject.md", ], "Reference" => [ "Overview: Toolbox Functions" => "./generated/reference/overview.md", From f29f579dd0d7e25295fe36f0836618e786dff4a9 Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Mon, 26 Feb 2024 20:55:46 +0100 Subject: [PATCH 082/113] remove old comment --- src/component.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/component.jl b/src/component.jl index 3cb92379..6c94e8b1 100644 --- a/src/component.jl +++ b/src/component.jl @@ -218,9 +218,6 @@ function simulate_component( rethrow(e) end - # save data to array - - # in case the parameters are of interest, we will return those, not them weighted by basis epoch_data_component = kron(return_parameters ? [1.0] : c.basis, m.y') return epoch_data_component From d8672d5b77163bba0576a1e7c92d4c891e5375ad Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Mon, 26 Feb 2024 20:57:34 +0100 Subject: [PATCH 083/113] Update multisubject.jl --- docs/literate/tutorials/multisubject.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/literate/tutorials/multisubject.jl b/docs/literate/tutorials/multisubject.jl index afde853b..44d944ba 100644 --- a/docs/literate/tutorials/multisubject.jl +++ b/docs/literate/tutorials/multisubject.jl @@ -65,7 +65,7 @@ for k = 1:4 f[1, k], data[:, k, :]', solid_color = :black, - axis = (; limits = ((0, 50), (-4, 5))), + axis = (; limits = ((0, 50), (-5, 6))), ) Label(f[1, k, Top()], text = "Item:" * evts[k, :item] * ", c:" * evts[k, :condition]) end @@ -93,7 +93,7 @@ series(data', solid_color = :black) # # Analyzing these data with Unfold.jl -# We will analyze these data using the `Unfold.jl` toolbox. While preliminary support for deconvolution (overlap correction) for mixed models is available, here we will simply apply it separately to each timepoint, following the MassUnivariate approach. +# We will analyze these data using the `Unfold.jl` toolbox. While preliminary support for deconvolution (overlap correction) for mixed models is available, here we will not make use of it, but rather apply a MixedModel to each timepoint, following the Mass-univariate approach. data, evts = simulate( design, signal, @@ -114,3 +114,5 @@ m = fit( times, ) plot_erp(coeftable(m))#, mapping = (; col = :group)) # FIXME facetting by col currently broken, waiting for new UnfoldMakie release! + +# The first column shows the fixed effects, the latter the item and subject random effects as they evolve across time From ef4d921fa5f43873c4c159519e522c02e57a556a Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Mon, 26 Feb 2024 21:01:32 +0100 Subject: [PATCH 084/113] Update multisubject.jl --- docs/literate/tutorials/multisubject.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/literate/tutorials/multisubject.jl b/docs/literate/tutorials/multisubject.jl index 44d944ba..84bcdf6e 100644 --- a/docs/literate/tutorials/multisubject.jl +++ b/docs/literate/tutorials/multisubject.jl @@ -84,7 +84,7 @@ f data, evts = simulate(design, signal, UniformOnset(offset = 20, width = 50), NoNoise()); size(data) -# The data is now $size(data,1) x $size(data,2), with the first dimension being continuous data, and the latter still the subjects. +# with the first dimension being continuous data, and the latter still the subjects. series(data', solid_color = :black) # Each line is one subject, and it looks a bit unstructured, because the event-onsets are of course random for each subject. From 1bab9dd33f299f6890de7e532117162f26ab4e9f Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Tue, 27 Feb 2024 07:47:31 +0100 Subject: [PATCH 085/113] Update docs/literate/tutorials/multisubject.jl Co-authored-by: Judith Schepers --- docs/literate/tutorials/multisubject.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/literate/tutorials/multisubject.jl b/docs/literate/tutorials/multisubject.jl index 84bcdf6e..68566b0d 100644 --- a/docs/literate/tutorials/multisubject.jl +++ b/docs/literate/tutorials/multisubject.jl @@ -13,7 +13,7 @@ using DataFrames # ## Design -# Our first design should be 20 subjects, with 3 items each. Any individual image is shown only either as large or small, thus we choose `items_between`. +# Our first design should be 20 subjects, with 4 items each. Any individual image is shown only either as large or small, thus we choose `items_between`. design = MultiSubjectDesign( n_subjects = 20, n_items = 4, From 75e86a157f9c46bce3acbd14511ed5660dec6d43 Mon Sep 17 00:00:00 2001 From: Benedikt Ehinger Date: Tue, 27 Feb 2024 07:48:40 +0100 Subject: [PATCH 086/113] v0.3.2 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 8a8913b0..d877ed66 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnfoldSim" uuid = "ed8ae6d2-84d3-44c6-ab46-0baf21700804" authors = ["Benedikt Ehinger", "Luis Lips", "Judith Schepers", "Maanik Marathe"] -version = "0.3.1" +version = "0.3.2" [deps] Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" From 3546e29a42bcd7d3629e08c7f5b90707ac1b4439 Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Fri, 1 Mar 2024 19:05:07 +0000 Subject: [PATCH 087/113] update to new unfoldmakie --- docs/literate/tutorials/multisubject.jl | 2 +- docs/make.jl | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/literate/tutorials/multisubject.jl b/docs/literate/tutorials/multisubject.jl index 68566b0d..b63e58d4 100644 --- a/docs/literate/tutorials/multisubject.jl +++ b/docs/literate/tutorials/multisubject.jl @@ -113,6 +113,6 @@ m = fit( data, times, ) -plot_erp(coeftable(m))#, mapping = (; col = :group)) # FIXME facetting by col currently broken, waiting for new UnfoldMakie release! +plot_erp(coeftable(m), mapping = (; col = :group)) # The first column shows the fixed effects, the latter the item and subject random effects as they evolve across time diff --git a/docs/make.jl b/docs/make.jl index fb18d297..ffba630f 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -54,4 +54,9 @@ makedocs(; ], ) -deploydocs(; repo = "github.com/unfoldtoolbox/UnfoldSim.jl", devbranch = "main") +deploydocs(; + repo = "github.com/unfoldtoolbox/UnfoldSim.jl", + devbranch = "main", + versions = "v#.#", + push_preview = true, +) From d2a3c8ae490812f060638585eca0e3f8616ffafd Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Mon, 15 Apr 2024 11:43:24 +0000 Subject: [PATCH 088/113] first test of artefacts simulation --- docs/nb_muscle.jl | 2326 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2326 insertions(+) create mode 100644 docs/nb_muscle.jl diff --git a/docs/nb_muscle.jl b/docs/nb_muscle.jl new file mode 100644 index 00000000..623b4725 --- /dev/null +++ b/docs/nb_muscle.jl @@ -0,0 +1,2326 @@ +### A Pluto.jl notebook ### +# v0.19.37 + +using Markdown +using InteractiveUtils + +# ╔═╡ 080f23f4-f900-11ee-1e5d-cd486a86ccff +begin + using UnfoldSim + using DSP + using CairoMakie + using Random + using Parameters + using UnfoldMakie + using DataFrames +end + + +# ╔═╡ 5d925343-5d02-4887-9337-11cdf810c698 +hart = headmodel() + +# ╔═╡ bf4d7bdf-77df-402e-8c4e-098e85fcc34a +T = 10; # total time + +# ╔═╡ b4beaaa3-7f98-4f12-a8d9-38994974e462 +srate = 100; #sampling rate + +# ╔═╡ b53f298d-e420-44b1-b1ce-cd5f8fe514d0 +musclelabels = unique(hart.artefactual["label"]) + +# ╔═╡ f56aa7db-4dbd-404f-8704-3328b190b0ca +what_muscle = rand(musclelabels,1) + +# ╔═╡ 6352a797-eb24-4964-9c34-0d410310df4e +# get a "random" muscle & a random muscle-sourcepoint +lead_ix = rand(findall(hart.artefactual["label"].==what_muscle),1)[1] + +# ╔═╡ 17c5239c-04a0-47cd-9393-115763e7aaad +lead = magnitude(hart.artefactual["leadfield"][:,lead_ix:lead_ix,:],hart.artefactual["orientation"][lead_ix:lead_ix,:]); + +# ╔═╡ 76575e4c-df59-49a3-8799-0aa0db4d35db +design = SingleSubjectDesign(conditions=Dict(:a=>["b"])) + +# ╔═╡ d01bbf66-1f7e-4646-b8e4-4d6ec6539533 +@with_kw struct MuscleNoise <: AbstractNoise + srate + noiselevel = 1 +end + +# ╔═╡ a005fef0-b145-492b-88d7-198ba1a751c9 +function UnfoldSim.simulate_noise(rng, t::MuscleNoise, n::Int) + x = rand(rng,n).-0.5 + + fi = digitalfilter(Bandstop(5,20; fs=t.srate),Butterworth(2)) # this kind of gives us the sqrt-shape in FFT space + return t.noiselevel .* filt(fi,x) +end + + +# ╔═╡ 796eb40e-2e70-492e-bca1-af197519684d +begin +c = LinearModelComponent(; basis = zeros(T*srate), formula = @formula(0 ~ 1), β = [1]); # dummy LinearModelComponent +mc = UnfoldSim.MultichannelComponent(c, lead[:,1],MuscleNoise(srate=srate)) # Multichannelcomponent with MuscleNoise + Leadfield + +end + +# ╔═╡ 9248e9d0-2fc6-4e82-8608-3c259b8a3c86 +data, events = simulate(MersenneTwister(1),design, mc, NoOnset(), NoNoise();return_epoched=true); + + +# ╔═╡ c7991ae2-27f3-4d96-a667-d1e6130d01a3 +begin + pos3d = hart.electrodes["pos"]; + pos2d = to_positions(pos3d') + pos2d = [Point2f(p[1] + 0.5, p[2] + 0.5) for p in pos2d]; +end + +# ╔═╡ 9821d37d-007d-497e-83e7-4f324696e8a2 +let + # select only every 20th channel + n_th = 30 +data2 = reshape(data[1:n_th:end,1:500,:],length(1:n_th:size(data,1)),:) +df = DataFrame( + :estimate => data2[:], + :channel => repeat(1:size(data2, 1), outer = size(data2, 2)), + :time => repeat(1:size(data2, 2), inner = size(data2, 1)), +) + f = Figure() +plot_butterfly!(f[1, 1:2], df; positions = pos2d[1:n_th:end]) +plot_topoplot!( + f[2, 1], + df[df.time .== 34, :]; + positions = pos2d[1:n_th:end], + visual = (; enlarge = 0.5, label_scatter = false), + axis = (; limits = ((0, 1), (0, 0.9))), +) +f +end + +# ╔═╡ 00000000-0000-0000-0000-000000000001 +PLUTO_PROJECT_TOML_CONTENTS = """ +[deps] +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +DSP = "717857b8-e6f2-59f4-9121-6e50c889abd2" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +UnfoldMakie = "69a5ce3b-64fb-4f22-ae69-36dd4416af2a" +UnfoldSim = "ed8ae6d2-84d3-44c6-ab46-0baf21700804" + +[compat] +CairoMakie = "~0.11.10" +DSP = "~0.7.9" +DataFrames = "~1.6.1" +Parameters = "~0.12.3" +UnfoldMakie = "~0.5.1" +UnfoldSim = "~0.3.2" +""" + +# ╔═╡ 00000000-0000-0000-0000-000000000002 +PLUTO_MANIFEST_TOML_CONTENTS = """ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.10.0" +manifest_format = "2.0" +project_hash = "e44ecb3641345b4f099e108ed95cccfef816bc7d" + +[[deps.AbstractFFTs]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "d92ad398961a3ed262d8bf04a1a2b8340f915fef" +uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c" +version = "1.5.0" +weakdeps = ["ChainRulesCore", "Test"] + + [deps.AbstractFFTs.extensions] + AbstractFFTsChainRulesCoreExt = "ChainRulesCore" + AbstractFFTsTestExt = "Test" + +[[deps.AbstractLattices]] +git-tree-sha1 = "222ee9e50b98f51b5d78feb93dd928880df35f06" +uuid = "398f06c4-4d28-53ec-89ca-5b2656b7603d" +version = "0.3.0" + +[[deps.AbstractTrees]] +git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" +uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +version = "0.4.5" + +[[deps.Adapt]] +deps = ["LinearAlgebra", "Requires"] +git-tree-sha1 = "6a55b747d1812e699320963ffde36f1ebdda4099" +uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" +version = "4.0.4" +weakdeps = ["StaticArrays"] + + [deps.Adapt.extensions] + AdaptStaticArraysExt = "StaticArrays" + +[[deps.AlgebraOfGraphics]] +deps = ["Colors", "Dates", "Dictionaries", "FileIO", "GLM", "GeoInterface", "GeometryBasics", "GridLayoutBase", "KernelDensity", "Loess", "Makie", "PlotUtils", "PooledArrays", "PrecompileTools", "RelocatableFolders", "StatsBase", "StructArrays", "Tables"] +git-tree-sha1 = "3fbdee81b0cdc2b106b681dd2b9d4bdc60ca35a2" +uuid = "cbdf2221-f076-402e-a563-3d30da359d67" +version = "0.6.18" + +[[deps.Animations]] +deps = ["Colors"] +git-tree-sha1 = "e81c509d2c8e49592413bfb0bb3b08150056c79d" +uuid = "27a7e980-b3e6-11e9-2bcd-0b925532e340" +version = "0.4.1" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.1" + +[[deps.ArrayInterface]] +deps = ["Adapt", "LinearAlgebra", "SparseArrays", "SuiteSparse"] +git-tree-sha1 = "44691067188f6bd1b2289552a23e4b7572f4528d" +uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" +version = "7.9.0" + + [deps.ArrayInterface.extensions] + ArrayInterfaceBandedMatricesExt = "BandedMatrices" + ArrayInterfaceBlockBandedMatricesExt = "BlockBandedMatrices" + ArrayInterfaceCUDAExt = "CUDA" + ArrayInterfaceChainRulesExt = "ChainRules" + ArrayInterfaceGPUArraysCoreExt = "GPUArraysCore" + ArrayInterfaceReverseDiffExt = "ReverseDiff" + ArrayInterfaceStaticArraysCoreExt = "StaticArraysCore" + ArrayInterfaceTrackerExt = "Tracker" + + [deps.ArrayInterface.weakdeps] + BandedMatrices = "aae01518-5342-5314-be14-df237901396f" + BlockBandedMatrices = "ffab5731-97b5-5995-9138-79e8c1846df0" + CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" + ChainRules = "082447d4-558c-5d27-93f4-14fc19e9eca2" + GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527" + ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" + StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" + Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" + +[[deps.ArrayLayouts]] +deps = ["FillArrays", "LinearAlgebra"] +git-tree-sha1 = "0330bc3e828a05d1073553fb56f9695d73077370" +uuid = "4c555306-a7a7-4459-81d9-ec55ddd5c99a" +version = "1.9.1" +weakdeps = ["SparseArrays"] + + [deps.ArrayLayouts.extensions] + ArrayLayoutsSparseArraysExt = "SparseArrays" + +[[deps.Arrow]] +deps = ["ArrowTypes", "BitIntegers", "CodecLz4", "CodecZstd", "ConcurrentUtilities", "DataAPI", "Dates", "EnumX", "LoggingExtras", "Mmap", "PooledArrays", "SentinelArrays", "Tables", "TimeZones", "TranscodingStreams", "UUIDs"] +git-tree-sha1 = "29faa9835f77dee04b2833b2c9ee415223b3ebbd" +uuid = "69666777-d1a9-59fb-9406-91d4454c9d45" +version = "2.7.1" + +[[deps.ArrowTypes]] +deps = ["Sockets", "UUIDs"] +git-tree-sha1 = "404265cd8128a2515a81d5eae16de90fdef05101" +uuid = "31f734f8-188a-4ce0-8406-c8a06bd891cd" +version = "2.3.0" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[[deps.Automa]] +deps = ["PrecompileTools", "TranscodingStreams"] +git-tree-sha1 = "588e0d680ad1d7201d4c6a804dcb1cd9cba79fbb" +uuid = "67c07d97-cdcb-5c2c-af73-a7f9c32a568b" +version = "1.0.3" + +[[deps.AxisAlgorithms]] +deps = ["LinearAlgebra", "Random", "SparseArrays", "WoodburyMatrices"] +git-tree-sha1 = "01b8ccb13d68535d73d2b0c23e39bd23155fb712" +uuid = "13072b0f-2c55-5437-9ae7-d433b7a33950" +version = "1.1.0" + +[[deps.AxisArrays]] +deps = ["Dates", "IntervalSets", "IterTools", "RangeArrays"] +git-tree-sha1 = "16351be62963a67ac4083f748fdb3cca58bfd52f" +uuid = "39de3d68-74b9-583c-8d2d-e117c070f3a9" +version = "0.4.7" + +[[deps.BSplineKit]] +deps = ["ArrayLayouts", "BandedMatrices", "FastGaussQuadrature", "LinearAlgebra", "PrecompileTools", "Random", "Reexport", "SparseArrays", "Static", "StaticArrays", "StaticArraysCore"] +git-tree-sha1 = "40946927f4799ea625086a14796087959d3c4fcc" +uuid = "093aae92-e908-43d7-9660-e50ee39d5a0a" +version = "0.17.1" + +[[deps.BandedMatrices]] +deps = ["ArrayLayouts", "FillArrays", "LinearAlgebra", "PrecompileTools"] +git-tree-sha1 = "c946c5014cf4cdbfacacb363b110e7bffba3e742" +uuid = "aae01518-5342-5314-be14-df237901396f" +version = "1.6.1" +weakdeps = ["SparseArrays"] + + [deps.BandedMatrices.extensions] + BandedMatricesSparseArraysExt = "SparseArrays" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[deps.BitIntegers]] +deps = ["Random"] +git-tree-sha1 = "a55462dfddabc34bc97d3a7403a2ca2802179ae6" +uuid = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1" +version = "0.3.1" + +[[deps.Bzip2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "9e2a6b69137e6969bab0152632dcb3bc108c8bdd" +uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" +version = "1.0.8+1" + +[[deps.CEnum]] +git-tree-sha1 = "389ad5c84de1ae7cf0e28e381131c98ea87d54fc" +uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82" +version = "0.5.0" + +[[deps.CRC32c]] +uuid = "8bf52ea8-c179-5cab-976a-9e18b702a9bc" + +[[deps.CRlibm_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "e329286945d0cfc04456972ea732551869af1cfc" +uuid = "4e9b3aee-d8a1-5a3d-ad8b-7d824db253f0" +version = "1.0.1+0" + +[[deps.Cairo]] +deps = ["Cairo_jll", "Colors", "Glib_jll", "Graphics", "Libdl", "Pango_jll"] +git-tree-sha1 = "d0b3f8b4ad16cb0a2988c6788646a5e6a17b6b1b" +uuid = "159f3aea-2a34-519c-b102-8c37f9878175" +version = "1.0.5" + +[[deps.CairoMakie]] +deps = ["CRC32c", "Cairo", "Colors", "FFTW", "FileIO", "FreeType", "GeometryBasics", "LinearAlgebra", "Makie", "PrecompileTools"] +git-tree-sha1 = "e64af6bea1f0dcde6ebecc581768074f992ad39b" +uuid = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +version = "0.11.10" + +[[deps.Cairo_jll]] +deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "a4c43f59baa34011e303e76f5c8c91bf58415aaf" +uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" +version = "1.18.0+1" + +[[deps.Calculus]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "f641eb0a4f00c343bbc32346e1217b86f3ce9dad" +uuid = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9" +version = "0.5.1" + +[[deps.CatIndices]] +deps = ["CustomUnitRanges", "OffsetArrays"] +git-tree-sha1 = "a0f80a09780eed9b1d106a1bf62041c2efc995bc" +uuid = "aafaddc9-749c-510e-ac4f-586e18779b91" +version = "0.2.2" + +[[deps.CategoricalArrays]] +deps = ["DataAPI", "Future", "Missings", "Printf", "Requires", "Statistics", "Unicode"] +git-tree-sha1 = "1568b28f91293458345dabba6a5ea3f183250a61" +uuid = "324d7699-5711-5eae-9e2f-1d82baa6b597" +version = "0.10.8" +weakdeps = ["JSON", "RecipesBase", "SentinelArrays", "StructTypes"] + + [deps.CategoricalArrays.extensions] + CategoricalArraysJSONExt = "JSON" + CategoricalArraysRecipesBaseExt = "RecipesBase" + CategoricalArraysSentinelArraysExt = "SentinelArrays" + CategoricalArraysStructTypesExt = "StructTypes" + +[[deps.ChainRulesCore]] +deps = ["Compat", "LinearAlgebra"] +git-tree-sha1 = "575cd02e080939a33b6df6c5853d14924c08e35b" +uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" +version = "1.23.0" +weakdeps = ["SparseArrays"] + + [deps.ChainRulesCore.extensions] + ChainRulesCoreSparseArraysExt = "SparseArrays" + +[[deps.CloughTocher2DInterpolation]] +deps = ["LinearAlgebra", "MiniQhull", "PrecompileTools", "StaticArrays"] +git-tree-sha1 = "7b250c72cb6ec97bd77fa025e350ba848f623ce9" +uuid = "b70b374f-000b-463f-88dc-37030f004bd0" +version = "0.1.1" + +[[deps.CodecLz4]] +deps = ["Lz4_jll", "TranscodingStreams"] +git-tree-sha1 = "b8aecef9f90530cf322a8386630ec18485c17991" +uuid = "5ba52731-8f18-5e0d-9241-30f10d1ec561" +version = "0.4.3" + +[[deps.CodecZstd]] +deps = ["TranscodingStreams", "Zstd_jll"] +git-tree-sha1 = "23373fecba848397b1705f6183188a0c0bc86917" +uuid = "6b39b394-51ab-5f42-8807-6242bab2b4c2" +version = "0.8.2" + +[[deps.ColorBrewer]] +deps = ["Colors", "JSON", "Test"] +git-tree-sha1 = "61c5334f33d91e570e1d0c3eb5465835242582c4" +uuid = "a2cac450-b92f-5266-8821-25eda20663c8" +version = "0.4.0" + +[[deps.ColorSchemes]] +deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "PrecompileTools", "Random"] +git-tree-sha1 = "67c1f244b991cad9b0aa4b7540fb758c2488b129" +uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4" +version = "3.24.0" + +[[deps.ColorTypes]] +deps = ["FixedPointNumbers", "Random"] +git-tree-sha1 = "b10d0b65641d57b8b4d5e234446582de5047050d" +uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" +version = "0.11.5" + +[[deps.ColorVectorSpace]] +deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Requires", "Statistics", "TensorCore"] +git-tree-sha1 = "a1f44953f2382ebb937d60dafbe2deea4bd23249" +uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4" +version = "0.10.0" +weakdeps = ["SpecialFunctions"] + + [deps.ColorVectorSpace.extensions] + SpecialFunctionsExt = "SpecialFunctions" + +[[deps.Colors]] +deps = ["ColorTypes", "FixedPointNumbers", "Reexport"] +git-tree-sha1 = "fc08e5930ee9a4e03f84bfb5211cb54e7769758a" +uuid = "5ae59095-9a9b-59fe-a467-6f913c188581" +version = "0.12.10" + +[[deps.Combinatorics]] +git-tree-sha1 = "08c8b6831dc00bfea825826be0bc8336fc369860" +uuid = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" +version = "1.0.2" + +[[deps.CommonSubexpressions]] +deps = ["MacroTools", "Test"] +git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7" +uuid = "bbf7d656-a473-5ed7-a52c-81e309532950" +version = "0.3.0" + +[[deps.Compat]] +deps = ["TOML", "UUIDs"] +git-tree-sha1 = "c955881e3c981181362ae4088b35995446298b80" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "4.14.0" +weakdeps = ["Dates", "LinearAlgebra"] + + [deps.Compat.extensions] + CompatLinearAlgebraExt = "LinearAlgebra" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.0.5+1" + +[[deps.ComputationalResources]] +git-tree-sha1 = "52cb3ec90e8a8bea0e62e275ba577ad0f74821f7" +uuid = "ed09eef8-17a6-5b46-8889-db040fac31e3" +version = "0.3.2" + +[[deps.ConcurrentUtilities]] +deps = ["Serialization", "Sockets"] +git-tree-sha1 = "6cbbd4d241d7e6579ab354737f4dd95ca43946e1" +uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb" +version = "2.4.1" + +[[deps.ConstructionBase]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "260fd2400ed2dab602a7c15cf10c1933c59930a2" +uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" +version = "1.5.5" +weakdeps = ["IntervalSets", "StaticArrays"] + + [deps.ConstructionBase.extensions] + ConstructionBaseIntervalSetsExt = "IntervalSets" + ConstructionBaseStaticArraysExt = "StaticArrays" + +[[deps.Contour]] +git-tree-sha1 = "439e35b0b36e2e5881738abc8857bd92ad6ff9a8" +uuid = "d38c429a-6771-53c6-b99e-75d170b6e991" +version = "0.6.3" + +[[deps.CoordinateTransformations]] +deps = ["LinearAlgebra", "StaticArrays"] +git-tree-sha1 = "f9d7112bfff8a19a3a4ea4e03a8e6a91fe8456bf" +uuid = "150eb455-5306-5404-9cee-2592286d6298" +version = "0.6.3" + +[[deps.Crayons]] +git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15" +uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f" +version = "4.1.1" + +[[deps.CustomUnitRanges]] +git-tree-sha1 = "1a3f97f907e6dd8983b744d2642651bb162a3f7a" +uuid = "dc8bdbbb-1ca9-579f-8c36-e416f6a65cce" +version = "1.0.2" + +[[deps.DSP]] +deps = ["Compat", "FFTW", "IterTools", "LinearAlgebra", "Polynomials", "Random", "Reexport", "SpecialFunctions", "Statistics"] +git-tree-sha1 = "f7f4319567fe769debfcf7f8c03d8da1dd4e2fb0" +uuid = "717857b8-e6f2-59f4-9121-6e50c889abd2" +version = "0.7.9" + +[[deps.DataAPI]] +git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" +uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +version = "1.16.0" + +[[deps.DataFrames]] +deps = ["Compat", "DataAPI", "DataStructures", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrecompileTools", "PrettyTables", "Printf", "REPL", "Random", "Reexport", "SentinelArrays", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] +git-tree-sha1 = "04c738083f29f86e62c8afc341f0967d8717bdb8" +uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +version = "1.6.1" + +[[deps.DataStructures]] +deps = ["Compat", "InteractiveUtils", "OrderedCollections"] +git-tree-sha1 = "0f4b5d62a88d8f59003e43c25a8a90de9eb76317" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.18.18" + +[[deps.DataValueInterfaces]] +git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" +uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" +version = "1.0.0" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.Delaunator]] +git-tree-sha1 = "204ec3e7ce391774b8261b8cb29e4b68e745d8b6" +uuid = "466f8f70-d5e3-4806-ac0b-a54b75a91218" +version = "0.1.1" + +[[deps.DelaunayTriangulation]] +deps = ["DataStructures", "EnumX", "ExactPredicates", "Random", "SimpleGraphs"] +git-tree-sha1 = "d4e9dc4c6106b8d44e40cd4faf8261a678552c7c" +uuid = "927a84f5-c5f4-47a5-9785-b46e178433df" +version = "0.8.12" + +[[deps.Dictionaries]] +deps = ["Indexing", "Random", "Serialization"] +git-tree-sha1 = "1f3b7b0d321641c1f2e519f7aed77f8e1f6cb133" +uuid = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" +version = "0.3.29" + +[[deps.Dierckx]] +deps = ["Dierckx_jll"] +git-tree-sha1 = "d1ea9f433781bb6ff504f7d3cb70c4782c504a3a" +uuid = "39dd38d3-220a-591b-8e3c-4c3a8c710a94" +version = "0.5.3" + +[[deps.Dierckx_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "6596b96fe1caff3db36415eeb6e9d3b50bfe40ee" +uuid = "cd4c43a9-7502-52ba-aa6d-59fb2a88580b" +version = "0.1.0+0" + +[[deps.DiffResults]] +deps = ["StaticArraysCore"] +git-tree-sha1 = "782dd5f4561f5d267313f23853baaaa4c52ea621" +uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5" +version = "1.1.0" + +[[deps.DiffRules]] +deps = ["IrrationalConstants", "LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"] +git-tree-sha1 = "23163d55f885173722d1e4cf0f6110cdbaf7e272" +uuid = "b552c78f-8df3-52c6-915a-8e097449b14b" +version = "1.15.1" + +[[deps.Distances]] +deps = ["LinearAlgebra", "Statistics", "StatsAPI"] +git-tree-sha1 = "66c4c81f259586e8f002eacebc177e1fb06363b0" +uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7" +version = "0.10.11" +weakdeps = ["ChainRulesCore", "SparseArrays"] + + [deps.Distances.extensions] + DistancesChainRulesCoreExt = "ChainRulesCore" + DistancesSparseArraysExt = "SparseArrays" + +[[deps.Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" + +[[deps.Distributions]] +deps = ["FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"] +git-tree-sha1 = "7c302d7a5fec5214eb8a5a4c466dcf7a51fcf169" +uuid = "31c24e10-a181-5473-b8eb-7969acd0382f" +version = "0.25.107" + + [deps.Distributions.extensions] + DistributionsChainRulesCoreExt = "ChainRulesCore" + DistributionsDensityInterfaceExt = "DensityInterface" + DistributionsTestExt = "Test" + + [deps.Distributions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + DensityInterface = "b429d917-457f-4dbc-8f4c-0cc954292b1d" + Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.DocStringExtensions]] +deps = ["LibGit2"] +git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" +uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +version = "0.9.3" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.DualNumbers]] +deps = ["Calculus", "NaNMath", "SpecialFunctions"] +git-tree-sha1 = "5837a837389fccf076445fce071c8ddaea35a566" +uuid = "fa6b7ba4-c1ee-5f82-b5fc-ecf0adba8f74" +version = "0.6.8" + +[[deps.EarCut_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "e3290f2d49e661fbd94046d7e3726ffcb2d41053" +uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5" +version = "2.2.4+0" + +[[deps.Effects]] +deps = ["Combinatorics", "DataFrames", "Distributions", "ForwardDiff", "LinearAlgebra", "Statistics", "StatsAPI", "StatsBase", "StatsModels", "Tables"] +git-tree-sha1 = "60e6b029a1cf31cc6176c4da4af641794404177a" +uuid = "8f03c58b-bd97-4933-a826-f71b64d2cca2" +version = "1.2.0" +weakdeps = ["GLM", "MixedModels"] + + [deps.Effects.extensions] + EffectsGLMExt = "GLM" + EffectsMixedModelsExt = ["GLM", "MixedModels"] + +[[deps.EnumX]] +git-tree-sha1 = "bdb1942cd4c45e3c678fd11569d5cccd80976237" +uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56" +version = "1.0.4" + +[[deps.ExactPredicates]] +deps = ["IntervalArithmetic", "Random", "StaticArrays"] +git-tree-sha1 = "b3f2ff58735b5f024c392fde763f29b057e4b025" +uuid = "429591f6-91af-11e9-00e2-59fbe8cec110" +version = "2.2.8" + +[[deps.Expat_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "4558ab818dcceaab612d1bb8c19cee87eda2b83c" +uuid = "2e619515-83b5-522b-bb60-26c02a35a201" +version = "2.5.0+0" + +[[deps.ExprTools]] +git-tree-sha1 = "27415f162e6028e81c72b82ef756bf321213b6ec" +uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04" +version = "0.1.10" + +[[deps.Extents]] +git-tree-sha1 = "2140cd04483da90b2da7f99b2add0750504fc39c" +uuid = "411431e0-e8b7-467b-b5e0-f676ba4f2910" +version = "0.1.2" + +[[deps.FFMPEG_jll]] +deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "PCRE2_jll", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"] +git-tree-sha1 = "ab3f7e1819dba9434a3a5126510c8fda3a4e7000" +uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5" +version = "6.1.1+0" + +[[deps.FFTViews]] +deps = ["CustomUnitRanges", "FFTW"] +git-tree-sha1 = "cbdf14d1e8c7c8aacbe8b19862e0179fd08321c2" +uuid = "4f61f5a4-77b1-5117-aa51-3ab5ef4ef0cd" +version = "0.3.2" + +[[deps.FFTW]] +deps = ["AbstractFFTs", "FFTW_jll", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"] +git-tree-sha1 = "4820348781ae578893311153d69049a93d05f39d" +uuid = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" +version = "1.8.0" + +[[deps.FFTW_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "c6033cc3892d0ef5bb9cd29b7f2f0331ea5184ea" +uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a" +version = "3.3.10+0" + +[[deps.FastGaussQuadrature]] +deps = ["LinearAlgebra", "SpecialFunctions", "StaticArrays"] +git-tree-sha1 = "fd923962364b645f3719855c88f7074413a6ad92" +uuid = "442a2c76-b920-505d-bb47-c5924d526838" +version = "1.0.2" + +[[deps.FileIO]] +deps = ["Pkg", "Requires", "UUIDs"] +git-tree-sha1 = "82d8afa92ecf4b52d78d869f038ebfb881267322" +uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" +version = "1.16.3" + +[[deps.FilePaths]] +deps = ["FilePathsBase", "MacroTools", "Reexport", "Requires"] +git-tree-sha1 = "919d9412dbf53a2e6fe74af62a73ceed0bce0629" +uuid = "8fc22ac5-c921-52a6-82fd-178b2807b824" +version = "0.8.3" + +[[deps.FilePathsBase]] +deps = ["Compat", "Dates", "Mmap", "Printf", "Test", "UUIDs"] +git-tree-sha1 = "9f00e42f8d99fdde64d40c8ea5d14269a2e2c1aa" +uuid = "48062228-2e41-5def-b9a4-89aafe57970f" +version = "0.9.21" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + +[[deps.FillArrays]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "bfe82a708416cf00b73a3198db0859c82f741558" +uuid = "1a297f60-69ca-5386-bcde-b61e274b549b" +version = "1.10.0" +weakdeps = ["PDMats", "SparseArrays", "Statistics"] + + [deps.FillArrays.extensions] + FillArraysPDMatsExt = "PDMats" + FillArraysSparseArraysExt = "SparseArrays" + FillArraysStatisticsExt = "Statistics" + +[[deps.FiniteDiff]] +deps = ["ArrayInterface", "LinearAlgebra", "Requires", "Setfield", "SparseArrays"] +git-tree-sha1 = "bc0c5092d6caaea112d3c8e3b238d61563c58d5f" +uuid = "6a86dc24-6348-571c-b903-95158fe2bd41" +version = "2.23.0" + + [deps.FiniteDiff.extensions] + FiniteDiffBandedMatricesExt = "BandedMatrices" + FiniteDiffBlockBandedMatricesExt = "BlockBandedMatrices" + FiniteDiffStaticArraysExt = "StaticArrays" + + [deps.FiniteDiff.weakdeps] + BandedMatrices = "aae01518-5342-5314-be14-df237901396f" + BlockBandedMatrices = "ffab5731-97b5-5995-9138-79e8c1846df0" + StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + +[[deps.FixedPointNumbers]] +deps = ["Statistics"] +git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc" +uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" +version = "0.8.4" + +[[deps.Fontconfig_jll]] +deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Pkg", "Zlib_jll"] +git-tree-sha1 = "21efd19106a55620a188615da6d3d06cd7f6ee03" +uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" +version = "2.13.93+0" + +[[deps.Format]] +git-tree-sha1 = "9c68794ef81b08086aeb32eeaf33531668d5f5fc" +uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8" +version = "1.3.7" + +[[deps.ForwardDiff]] +deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions"] +git-tree-sha1 = "cf0fe81336da9fb90944683b8c41984b08793dad" +uuid = "f6369f11-7733-5829-9624-2563aa707210" +version = "0.10.36" +weakdeps = ["StaticArrays"] + + [deps.ForwardDiff.extensions] + ForwardDiffStaticArraysExt = "StaticArrays" + +[[deps.FreeType]] +deps = ["CEnum", "FreeType2_jll"] +git-tree-sha1 = "907369da0f8e80728ab49c1c7e09327bf0d6d999" +uuid = "b38be410-82b0-50bf-ab77-7b57e271db43" +version = "4.1.1" + +[[deps.FreeType2_jll]] +deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "d8db6a5a2fe1381c1ea4ef2cab7c69c2de7f9ea0" +uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" +version = "2.13.1+0" + +[[deps.FreeTypeAbstraction]] +deps = ["ColorVectorSpace", "Colors", "FreeType", "GeometryBasics"] +git-tree-sha1 = "2493cdfd0740015955a8e46de4ef28f49460d8bc" +uuid = "663a7486-cb36-511b-a19d-713bb74d65c9" +version = "0.10.3" + +[[deps.FriBidi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "aa31987c2ba8704e23c6c8ba8a4f769d5d7e4f91" +uuid = "559328eb-81f9-559d-9380-de523a88c83c" +version = "1.0.10+0" + +[[deps.Future]] +deps = ["Random"] +uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" + +[[deps.GLM]] +deps = ["Distributions", "LinearAlgebra", "Printf", "Reexport", "SparseArrays", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns", "StatsModels"] +git-tree-sha1 = "273bd1cd30768a2fddfa3fd63bbc746ed7249e5f" +uuid = "38e38edf-8417-5370-95a0-9cbb8c7f171a" +version = "1.9.0" + +[[deps.GeoInterface]] +deps = ["Extents"] +git-tree-sha1 = "d4f85701f569584f2cff7ba67a137d03f0cfb7d0" +uuid = "cf35fbd7-0cd7-5166-be24-54bfbe79505f" +version = "1.3.3" + +[[deps.GeometryBasics]] +deps = ["EarCut_jll", "Extents", "GeoInterface", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"] +git-tree-sha1 = "5694b56ccf9d15addedc35e9a4ba9c317721b788" +uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326" +version = "0.4.10" + +[[deps.Gettext_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"] +git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046" +uuid = "78b55507-aeef-58d4-861c-77aaff3498b1" +version = "0.21.0+0" + +[[deps.Glib_jll]] +deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] +git-tree-sha1 = "359a1ba2e320790ddbe4ee8b4d54a305c0ea2aff" +uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" +version = "2.80.0+0" + +[[deps.Graphics]] +deps = ["Colors", "LinearAlgebra", "NaNMath"] +git-tree-sha1 = "d61890399bc535850c4bf08e4e0d3a7ad0f21cbd" +uuid = "a2bd30eb-e257-5431-a919-1863eab51364" +version = "1.1.2" + +[[deps.Graphite2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011" +uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" +version = "1.3.14+0" + +[[deps.GridLayoutBase]] +deps = ["GeometryBasics", "InteractiveUtils", "Observables"] +git-tree-sha1 = "6f93a83ca11346771a93bbde2bdad2f65b61498f" +uuid = "3955a311-db13-416c-9275-1d80ed98e5e9" +version = "0.10.2" + +[[deps.Grisu]] +git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2" +uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe" +version = "1.0.2" + +[[deps.HDF5]] +deps = ["Compat", "HDF5_jll", "Libdl", "MPIPreferences", "Mmap", "Preferences", "Printf", "Random", "Requires", "UUIDs"] +git-tree-sha1 = "e856eef26cf5bf2b0f95f8f4fc37553c72c8641c" +uuid = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" +version = "0.17.2" + + [deps.HDF5.extensions] + MPIExt = "MPI" + + [deps.HDF5.weakdeps] + MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195" + +[[deps.HDF5_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"] +git-tree-sha1 = "6384c847ff5056c5624e30e75b3ca48902cae0ac" +uuid = "0234f1f7-429e-5d53-9886-15a909be8d59" +version = "1.14.3+2" + +[[deps.HarfBuzz_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"] +git-tree-sha1 = "129acf094d168394e80ee1dc4bc06ec835e510a3" +uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" +version = "2.8.1+1" + +[[deps.Hwloc_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "ca0f6bf568b4bfc807e7537f081c81e35ceca114" +uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8" +version = "2.10.0+0" + +[[deps.HypergeometricFunctions]] +deps = ["DualNumbers", "LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"] +git-tree-sha1 = "f218fe3736ddf977e0e772bc9a586b2383da2685" +uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a" +version = "0.3.23" + +[[deps.IfElse]] +git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1" +uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173" +version = "0.1.1" + +[[deps.ImageAxes]] +deps = ["AxisArrays", "ImageBase", "ImageCore", "Reexport", "SimpleTraits"] +git-tree-sha1 = "2e4520d67b0cef90865b3ef727594d2a58e0e1f8" +uuid = "2803e5a7-5153-5ecf-9a86-9b4c37f5f5ac" +version = "0.6.11" + +[[deps.ImageBase]] +deps = ["ImageCore", "Reexport"] +git-tree-sha1 = "eb49b82c172811fd2c86759fa0553a2221feb909" +uuid = "c817782e-172a-44cc-b673-b171935fbb9e" +version = "0.1.7" + +[[deps.ImageCore]] +deps = ["ColorVectorSpace", "Colors", "FixedPointNumbers", "MappedArrays", "MosaicViews", "OffsetArrays", "PaddedViews", "PrecompileTools", "Reexport"] +git-tree-sha1 = "b2a7eaa169c13f5bcae8131a83bc30eff8f71be0" +uuid = "a09fc81d-aa75-5fe9-8630-4744c3626534" +version = "0.10.2" + +[[deps.ImageFiltering]] +deps = ["CatIndices", "ComputationalResources", "DataStructures", "FFTViews", "FFTW", "ImageBase", "ImageCore", "LinearAlgebra", "OffsetArrays", "PrecompileTools", "Reexport", "SparseArrays", "StaticArrays", "Statistics", "TiledIteration"] +git-tree-sha1 = "432ae2b430a18c58eb7eca9ef8d0f2db90bc749c" +uuid = "6a3955dd-da59-5b1f-98d4-e7296123deb5" +version = "0.7.8" + +[[deps.ImageIO]] +deps = ["FileIO", "IndirectArrays", "JpegTurbo", "LazyModules", "Netpbm", "OpenEXR", "PNGFiles", "QOI", "Sixel", "TiffImages", "UUIDs"] +git-tree-sha1 = "bca20b2f5d00c4fbc192c3212da8fa79f4688009" +uuid = "82e4d734-157c-48bb-816b-45c225c6df19" +version = "0.6.7" + +[[deps.ImageMetadata]] +deps = ["AxisArrays", "ImageAxes", "ImageBase", "ImageCore"] +git-tree-sha1 = "355e2b974f2e3212a75dfb60519de21361ad3cb7" +uuid = "bc367c6b-8a6b-528e-b4bd-a4b897500b49" +version = "0.9.9" + +[[deps.Imath_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "3d09a9f60edf77f8a4d99f9e015e8fbf9989605d" +uuid = "905a6f67-0a94-5f89-b386-d35d92009cd1" +version = "3.1.7+0" + +[[deps.Indexing]] +git-tree-sha1 = "ce1566720fd6b19ff3411404d4b977acd4814f9f" +uuid = "313cdc1a-70c2-5d6a-ae34-0150d3930a38" +version = "1.1.1" + +[[deps.IndirectArrays]] +git-tree-sha1 = "012e604e1c7458645cb8b436f8fba789a51b257f" +uuid = "9b13fd28-a010-5f03-acff-a1bbcff69959" +version = "1.0.0" + +[[deps.Inflate]] +git-tree-sha1 = "ea8031dea4aff6bd41f1df8f2fdfb25b33626381" +uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" +version = "0.1.4" + +[[deps.InlineStrings]] +deps = ["Parsers"] +git-tree-sha1 = "9cc2baf75c6d09f9da536ddf58eb2f29dedaf461" +uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" +version = "1.4.0" + +[[deps.IntegerMathUtils]] +git-tree-sha1 = "b8ffb903da9f7b8cf695a8bead8e01814aa24b30" +uuid = "18e54dd8-cb9d-406c-a71d-865a43cbb235" +version = "0.1.2" + +[[deps.IntelOpenMP_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "5fdf2fe6724d8caabf43b557b84ce53f3b7e2f6b" +uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0" +version = "2024.0.2+0" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[deps.Interpolations]] +deps = ["Adapt", "AxisAlgorithms", "ChainRulesCore", "LinearAlgebra", "OffsetArrays", "Random", "Ratios", "Requires", "SharedArrays", "SparseArrays", "StaticArrays", "WoodburyMatrices"] +git-tree-sha1 = "88a101217d7cb38a7b481ccd50d21876e1d1b0e0" +uuid = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" +version = "0.15.1" +weakdeps = ["Unitful"] + + [deps.Interpolations.extensions] + InterpolationsUnitfulExt = "Unitful" + +[[deps.IntervalArithmetic]] +deps = ["CRlibm_jll", "MacroTools", "RoundingEmulator"] +git-tree-sha1 = "a066535c05e21f8e18b18e759a5d790b7a706399" +uuid = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253" +version = "0.22.10" +weakdeps = ["DiffRules", "ForwardDiff", "RecipesBase"] + + [deps.IntervalArithmetic.extensions] + IntervalArithmeticDiffRulesExt = "DiffRules" + IntervalArithmeticForwardDiffExt = "ForwardDiff" + IntervalArithmeticRecipesBaseExt = "RecipesBase" + +[[deps.IntervalSets]] +git-tree-sha1 = "dba9ddf07f77f60450fe5d2e2beb9854d9a49bd0" +uuid = "8197267c-284f-5f27-9208-e0e47529a953" +version = "0.7.10" +weakdeps = ["Random", "RecipesBase", "Statistics"] + + [deps.IntervalSets.extensions] + IntervalSetsRandomExt = "Random" + IntervalSetsRecipesBaseExt = "RecipesBase" + IntervalSetsStatisticsExt = "Statistics" + +[[deps.InvertedIndices]] +git-tree-sha1 = "0dc7b50b8d436461be01300fd8cd45aa0274b038" +uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" +version = "1.3.0" + +[[deps.IrrationalConstants]] +git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2" +uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" +version = "0.2.2" + +[[deps.Isoband]] +deps = ["isoband_jll"] +git-tree-sha1 = "f9b6d97355599074dc867318950adaa6f9946137" +uuid = "f1662d9f-8043-43de-a69a-05efc1cc6ff4" +version = "0.1.1" + +[[deps.IterTools]] +git-tree-sha1 = "42d5f897009e7ff2cf88db414a389e5ed1bdd023" +uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e" +version = "1.10.0" + +[[deps.IterativeSolvers]] +deps = ["LinearAlgebra", "Printf", "Random", "RecipesBase", "SparseArrays"] +git-tree-sha1 = "59545b0a2b27208b0650df0a46b8e3019f85055b" +uuid = "42fd0dbc-a981-5370-80f2-aaf504508153" +version = "0.9.4" + +[[deps.IteratorInterfaceExtensions]] +git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" +uuid = "82899510-4779-5014-852e-03e436cf321d" +version = "1.0.0" + +[[deps.JLD2]] +deps = ["FileIO", "MacroTools", "Mmap", "OrderedCollections", "Pkg", "PrecompileTools", "Printf", "Reexport", "Requires", "TranscodingStreams", "UUIDs"] +git-tree-sha1 = "5ea6acdd53a51d897672edb694e3cc2912f3f8a7" +uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +version = "0.4.46" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.5.0" + +[[deps.JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[deps.JSON3]] +deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"] +git-tree-sha1 = "eb3edce0ed4fa32f75a0a11217433c31d56bd48b" +uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +version = "1.14.0" +weakdeps = ["ArrowTypes"] + + [deps.JSON3.extensions] + JSON3ArrowExt = ["ArrowTypes"] + +[[deps.JpegTurbo]] +deps = ["CEnum", "FileIO", "ImageCore", "JpegTurbo_jll", "TOML"] +git-tree-sha1 = "fa6d0bcff8583bac20f1ffa708c3913ca605c611" +uuid = "b835a17e-a41a-41e7-81f0-2f016b05efe0" +version = "0.1.5" + +[[deps.JpegTurbo_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "3336abae9a713d2210bb57ab484b1e065edd7d23" +uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" +version = "3.0.2+0" + +[[deps.KernelDensity]] +deps = ["Distributions", "DocStringExtensions", "FFTW", "Interpolations", "StatsBase"] +git-tree-sha1 = "fee018a29b60733876eb557804b5b109dd3dd8a7" +uuid = "5ab0869b-81aa-558d-bb23-cbf5423bbe9b" +version = "0.6.8" + +[[deps.LAME_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "f6250b16881adf048549549fba48b1161acdac8c" +uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d" +version = "3.100.1+0" + +[[deps.LLVMOpenMP_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "d986ce2d884d49126836ea94ed5bfb0f12679713" +uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" +version = "15.0.7+0" + +[[deps.LZO_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "e5b909bcf985c5e2605737d2ce278ed791b89be6" +uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac" +version = "2.10.1+0" + +[[deps.LaTeXStrings]] +git-tree-sha1 = "50901ebc375ed41dbf8058da26f9de442febbbec" +uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" +version = "1.3.1" + +[[deps.LazyArtifacts]] +deps = ["Artifacts", "Pkg"] +uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" + +[[deps.LazyModules]] +git-tree-sha1 = "a560dd966b386ac9ae60bdd3a3d3a326062d3c3e" +uuid = "8cdb02fc-e678-4876-92c5-9defec4f444e" +version = "0.3.1" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.4" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.4.0+0" + +[[deps.LibGit2]] +deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.6.4+0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.0+1" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[deps.Libffi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "0b4a5d71f3e5200a7dff793393e09dfc2d874290" +uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" +version = "3.2.2+1" + +[[deps.Libgcrypt_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll", "Pkg"] +git-tree-sha1 = "64613c82a59c120435c067c2b809fc61cf5166ae" +uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4" +version = "1.8.7+0" + +[[deps.Libgpg_error_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "c333716e46366857753e273ce6a69ee0945a6db9" +uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8" +version = "1.42.0+0" + +[[deps.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "f9557a255370125b405568f9767d6d195822a175" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.17.0+0" + +[[deps.Libmount_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "dae976433497a2f841baadea93d27e68f1a12a97" +uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" +version = "2.39.3+0" + +[[deps.Libuuid_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "0a04a1318df1bf510beb2562cf90fb0c386f58c4" +uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" +version = "2.39.3+1" + +[[deps.LightXML]] +deps = ["Libdl", "XML2_jll"] +git-tree-sha1 = "3a994404d3f6709610701c7dabfc03fed87a81f8" +uuid = "9c8b4983-aa76-5018-a973-4c85ecc9e179" +version = "0.9.1" + +[[deps.LineSearches]] +deps = ["LinearAlgebra", "NLSolversBase", "NaNMath", "Parameters", "Printf"] +git-tree-sha1 = "7bbea35cec17305fc70a0e5b4641477dc0789d9d" +uuid = "d3d80556-e9d4-5f37-9878-2ab0fcc64255" +version = "7.2.0" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[deps.LinearAlgebraX]] +deps = ["LinearAlgebra", "Mods", "Primes", "SimplePolynomials"] +git-tree-sha1 = "d76cec8007ec123c2b681269d40f94b053473fcf" +uuid = "9b3f67b0-2d00-526e-9884-9e4938f8fb88" +version = "0.2.7" + +[[deps.Loess]] +deps = ["Distances", "LinearAlgebra", "Statistics", "StatsAPI"] +git-tree-sha1 = "a113a8be4c6d0c64e217b472fb6e61c760eb4022" +uuid = "4345ca2d-374a-55d4-8d30-97f9976e7612" +version = "0.6.3" + +[[deps.LogExpFunctions]] +deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] +git-tree-sha1 = "18144f3e9cbe9b15b070288eef858f71b291ce37" +uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" +version = "0.3.27" + + [deps.LogExpFunctions.extensions] + LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" + LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables" + LogExpFunctionsInverseFunctionsExt = "InverseFunctions" + + [deps.LogExpFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[deps.LoggingExtras]] +deps = ["Dates", "Logging"] +git-tree-sha1 = "c1dd6d7978c12545b4179fb6153b9250c96b0075" +uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" +version = "1.0.3" + +[[deps.Lz4_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "6c26c5e8a4203d43b5497be3ec5d4e0c3cde240a" +uuid = "5ced341a-0733-55b8-9ab6-a4889d929147" +version = "1.9.4+0" + +[[deps.MKL_jll]] +deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl"] +git-tree-sha1 = "72dc3cf284559eb8f53aa593fe62cb33f83ed0c0" +uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7" +version = "2024.0.0+0" + +[[deps.MLBase]] +deps = ["IterTools", "Random", "Reexport", "StatsBase"] +git-tree-sha1 = "ac79beff4257e6e80004d5aee25ffeee79d91263" +uuid = "f0e99cf1-93fa-52ec-9ecc-5026115318e0" +version = "0.9.2" + +[[deps.MPICH_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] +git-tree-sha1 = "656036b9ed6f942d35e536e249600bc31d0f9df8" +uuid = "7cb0a576-ebde-5e09-9194-50597f1243b4" +version = "4.2.0+0" + +[[deps.MPIPreferences]] +deps = ["Libdl", "Preferences"] +git-tree-sha1 = "8f6af051b9e8ec597fa09d8885ed79fd582f33c9" +uuid = "3da0fdf6-3ccc-4f1b-acd9-58baa6c99267" +version = "0.1.10" + +[[deps.MPItrampoline_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] +git-tree-sha1 = "77c3bd69fdb024d75af38713e883d0f249ce19c2" +uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748" +version = "5.3.2+0" + +[[deps.MacroTools]] +deps = ["Markdown", "Random"] +git-tree-sha1 = "2fa9ee3e63fd3a4f7a9a4f4744a52f4856de82df" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.13" + +[[deps.Makie]] +deps = ["Animations", "Base64", "CRC32c", "ColorBrewer", "ColorSchemes", "ColorTypes", "Colors", "Contour", "DelaunayTriangulation", "Distributions", "DocStringExtensions", "Downloads", "FFMPEG_jll", "FileIO", "FilePaths", "FixedPointNumbers", "Format", "FreeType", "FreeTypeAbstraction", "GeometryBasics", "GridLayoutBase", "ImageIO", "InteractiveUtils", "IntervalSets", "Isoband", "KernelDensity", "LaTeXStrings", "LinearAlgebra", "MacroTools", "MakieCore", "Markdown", "MathTeXEngine", "Observables", "OffsetArrays", "Packing", "PlotUtils", "PolygonOps", "PrecompileTools", "Printf", "REPL", "Random", "RelocatableFolders", "Scratch", "ShaderAbstractions", "Showoff", "SignedDistanceFields", "SparseArrays", "Statistics", "StatsBase", "StatsFuns", "StructArrays", "TriplotBase", "UnicodeFun"] +git-tree-sha1 = "46ca613be7a1358fb93529726ea2fc28050d3ae0" +uuid = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" +version = "0.20.9" + +[[deps.MakieCore]] +deps = ["Observables", "REPL"] +git-tree-sha1 = "248b7a4be0f92b497f7a331aed02c1e9a878f46b" +uuid = "20f20a25-4f0e-4fdf-b5d1-57303727442b" +version = "0.7.3" + +[[deps.MappedArrays]] +git-tree-sha1 = "2dab0221fe2b0f2cb6754eaa743cc266339f527e" +uuid = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900" +version = "0.4.2" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[deps.MathTeXEngine]] +deps = ["AbstractTrees", "Automa", "DataStructures", "FreeTypeAbstraction", "GeometryBasics", "LaTeXStrings", "REPL", "RelocatableFolders", "UnicodeFun"] +git-tree-sha1 = "96ca8a313eb6437db5ffe946c457a401bbb8ce1d" +uuid = "0a4f8689-d25c-4efe-a92b-7142dfc1aa53" +version = "0.5.7" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.2+1" + +[[deps.MetaArrays]] +deps = ["Requires"] +git-tree-sha1 = "6647f7d45a9153162d6561957405c12088caf537" +uuid = "36b8f3f0-b776-11e8-061f-1f20094e1fc8" +version = "0.2.10" + +[[deps.MicrosoftMPI_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "f12a29c4400ba812841c6ace3f4efbb6dbb3ba01" +uuid = "9237b28f-5490-5468-be7b-bb81f5f5e6cf" +version = "10.1.4+2" + +[[deps.MiniQhull]] +deps = ["QhullMiniWrapper_jll"] +git-tree-sha1 = "9dc837d180ee49eeb7c8b77bb1c860452634b0d1" +uuid = "978d7f02-9e05-4691-894f-ae31a51d76ca" +version = "0.4.0" + +[[deps.Missings]] +deps = ["DataAPI"] +git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" +uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" +version = "1.2.0" + +[[deps.MixedModels]] +deps = ["Arrow", "BSplineKit", "DataAPI", "Distributions", "GLM", "JSON3", "LinearAlgebra", "Markdown", "MixedModelsDatasets", "NLopt", "PooledArrays", "PrecompileTools", "ProgressMeter", "Random", "SparseArrays", "StaticArrays", "Statistics", "StatsAPI", "StatsBase", "StatsFuns", "StatsModels", "StructTypes", "Tables", "TypedTables"] +git-tree-sha1 = "7859d505f4760c4e7d16fc000f441569a752a09d" +uuid = "ff71e718-51f3-5ec2-a782-8ffcbfa3c316" +version = "4.23.1" + +[[deps.MixedModelsDatasets]] +deps = ["Arrow", "Artifacts", "LazyArtifacts"] +git-tree-sha1 = "5f508af97ecf39645febed8ba2fabf5cfdc682e0" +uuid = "7e9fb7ac-9f67-43bf-b2c8-96ba0796cbb6" +version = "0.1.1" + +[[deps.MixedModelsSim]] +deps = ["LinearAlgebra", "MixedModels", "PooledArrays", "PrettyTables", "Random", "Statistics", "Tables"] +git-tree-sha1 = "492d8041f5ef9dffad4b91b6cd5075adf70cc4d2" +uuid = "d5ae56c5-23ca-4a1f-b505-9fc4796fc1fe" +version = "0.2.9" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.Mocking]] +deps = ["Compat", "ExprTools"] +git-tree-sha1 = "4cc0c5a83933648b615c36c2b956d94fda70641e" +uuid = "78c3b35d-d492-501b-9361-3d52fe80e533" +version = "0.7.7" + +[[deps.Mods]] +git-tree-sha1 = "924f962b524a71eef7a21dae1e6853817f9b658f" +uuid = "7475f97c-0381-53b1-977b-4c60186c8d62" +version = "2.2.4" + +[[deps.MosaicViews]] +deps = ["MappedArrays", "OffsetArrays", "PaddedViews", "StackViews"] +git-tree-sha1 = "7b86a5d4d70a9f5cdf2dacb3cbe6d251d1a61dbe" +uuid = "e94cdb99-869f-56ef-bcf0-1ae2bcbe0389" +version = "0.3.4" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2023.1.10" + +[[deps.Multisets]] +git-tree-sha1 = "8d852646862c96e226367ad10c8af56099b4047e" +uuid = "3b2b4ff1-bcff-5658-a3ee-dbcf1ce5ac09" +version = "0.4.4" + +[[deps.NLSolversBase]] +deps = ["DiffResults", "Distributed", "FiniteDiff", "ForwardDiff"] +git-tree-sha1 = "a0b464d183da839699f4c79e7606d9d186ec172c" +uuid = "d41bc354-129a-5804-8e4c-c37616107c6c" +version = "7.8.3" + +[[deps.NLopt]] +deps = ["NLopt_jll"] +git-tree-sha1 = "3b887e2ef56be3309e68d2546c6178e2d2fa9a60" +uuid = "76087f3c-5699-56af-9a33-bf431cd00edd" +version = "1.0.2" + + [deps.NLopt.extensions] + NLoptMathOptInterfaceExt = ["MathOptInterface"] + + [deps.NLopt.weakdeps] + MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" + +[[deps.NLopt_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "9b1f15a08f9d00cdb2761dcfa6f453f5d0d6f973" +uuid = "079eb43e-fd8e-5478-9966-2cf3e3edb778" +version = "2.7.1+0" + +[[deps.NaNMath]] +deps = ["OpenLibm_jll"] +git-tree-sha1 = "0877504529a3e5c3343c6f8b4c0381e57e4387e4" +uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" +version = "1.0.2" + +[[deps.NearestNeighbors]] +deps = ["Distances", "StaticArrays"] +git-tree-sha1 = "ded64ff6d4fdd1cb68dfcbb818c69e144a5b2e4c" +uuid = "b8a86587-4115-5ab1-83bc-aa920d37bbce" +version = "0.4.16" + +[[deps.Netpbm]] +deps = ["FileIO", "ImageCore", "ImageMetadata"] +git-tree-sha1 = "d92b107dbb887293622df7697a2223f9f8176fcd" +uuid = "f09324ee-3d7c-5217-9330-fc30815ba969" +version = "1.1.1" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.Observables]] +git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225" +uuid = "510215fc-4207-5dde-b226-833fc4488ee2" +version = "0.5.5" + +[[deps.OffsetArrays]] +git-tree-sha1 = "6a731f2b5c03157418a20c12195eb4b74c8f8621" +uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" +version = "1.13.0" +weakdeps = ["Adapt"] + + [deps.OffsetArrays.extensions] + OffsetArraysAdaptExt = "Adapt" + +[[deps.Ogg_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "887579a3eb005446d514ab7aeac5d1d027658b8f" +uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051" +version = "1.3.5+1" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.23+2" + +[[deps.OpenEXR]] +deps = ["Colors", "FileIO", "OpenEXR_jll"] +git-tree-sha1 = "327f53360fdb54df7ecd01e96ef1983536d1e633" +uuid = "52e1d378-f018-4a11-a4be-720524705ac7" +version = "0.3.2" + +[[deps.OpenEXR_jll]] +deps = ["Artifacts", "Imath_jll", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "a4ca623df1ae99d09bc9868b008262d0c0ac1e4f" +uuid = "18a262bb-aa17-5467-a713-aee519bc75cb" +version = "3.1.4+0" + +[[deps.OpenLibm_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "05823500-19ac-5b8b-9628-191a04bc5112" +version = "0.8.1+2" + +[[deps.OpenMPI_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] +git-tree-sha1 = "e25c1778a98e34219a00455d6e4384e017ea9762" +uuid = "fe0851c0-eecd-5654-98d4-656369965a5c" +version = "4.1.6+0" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "3da7367955dcc5c54c1ba4d402ccdc09a1a3e046" +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "3.0.13+1" + +[[deps.OpenSpecFun_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1" +uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" +version = "0.5.5+0" + +[[deps.Optim]] +deps = ["Compat", "FillArrays", "ForwardDiff", "LineSearches", "LinearAlgebra", "NLSolversBase", "NaNMath", "Parameters", "PositiveFactorizations", "Printf", "SparseArrays", "StatsBase"] +git-tree-sha1 = "d9b79c4eed437421ac4285148fcadf42e0700e89" +uuid = "429524aa-4258-5aef-a3af-852621145aeb" +version = "1.9.4" + + [deps.Optim.extensions] + OptimMOIExt = "MathOptInterface" + + [deps.Optim.weakdeps] + MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" + +[[deps.Opus_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "51a08fb14ec28da2ec7a927c4337e4332c2a4720" +uuid = "91d4177d-7536-5919-b921-800302f37372" +version = "1.3.2+0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.6.3" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.42.0+1" + +[[deps.PDMats]] +deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"] +git-tree-sha1 = "949347156c25054de2db3b166c52ac4728cbad65" +uuid = "90014a1f-27ba-587c-ab20-58faa44d9150" +version = "0.11.31" + +[[deps.PNGFiles]] +deps = ["Base64", "CEnum", "ImageCore", "IndirectArrays", "OffsetArrays", "libpng_jll"] +git-tree-sha1 = "67186a2bc9a90f9f85ff3cc8277868961fb57cbd" +uuid = "f57f5aa1-a3ce-4bc8-8ab9-96f992907883" +version = "0.4.3" + +[[deps.Packing]] +deps = ["GeometryBasics"] +git-tree-sha1 = "ec3edfe723df33528e085e632414499f26650501" +uuid = "19eb6ba3-879d-56ad-ad62-d5c202156566" +version = "0.5.0" + +[[deps.PaddedViews]] +deps = ["OffsetArrays"] +git-tree-sha1 = "0fac6313486baae819364c52b4f483450a9d793f" +uuid = "5432bcbf-9aad-5242-b902-cca2824c8663" +version = "0.5.12" + +[[deps.Pango_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "FriBidi_jll", "Glib_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl"] +git-tree-sha1 = "526f5a03792669e4187e584e8ec9d534248ca765" +uuid = "36c8627f-9965-5494-a995-c6b170f724f3" +version = "1.52.1+0" + +[[deps.Parameters]] +deps = ["OrderedCollections", "UnPack"] +git-tree-sha1 = "34c0e9ad262e5f7fc75b10a9952ca7692cfc5fbe" +uuid = "d96e819e-fc66-5662-9728-84c9c7592b0a" +version = "0.12.3" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.8.1" + +[[deps.Peaks]] +deps = ["Compat", "RecipesBase"] +git-tree-sha1 = "1627365757c8b87ad01c2c13e55a5120cbe5b548" +uuid = "18e31ff7-3703-566c-8e60-38913d67486b" +version = "0.4.4" + +[[deps.Permutations]] +deps = ["Combinatorics", "LinearAlgebra", "Random"] +git-tree-sha1 = "eb3f9df2457819bf0a9019bd93cc451697a0751e" +uuid = "2ae35dd2-176d-5d53-8349-f30d82d94d4f" +version = "0.4.20" + +[[deps.Pixman_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] +git-tree-sha1 = "64779bc4c9784fee475689a1752ef4d5747c5e87" +uuid = "30392449-352a-5448-841d-b1acce4e97dc" +version = "0.42.2+0" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.10.0" + +[[deps.PkgVersion]] +deps = ["Pkg"] +git-tree-sha1 = "f9501cc0430a26bc3d156ae1b5b0c1b47af4d6da" +uuid = "eebad327-c553-4316-9ea0-9fa01ccd7688" +version = "0.3.3" + +[[deps.PlotUtils]] +deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Random", "Reexport", "Statistics"] +git-tree-sha1 = "7b1a9df27f072ac4c9c7cbe5efb198489258d1f5" +uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043" +version = "1.4.1" + +[[deps.PolygonOps]] +git-tree-sha1 = "77b3d3605fc1cd0b42d95eba87dfcd2bf67d5ff6" +uuid = "647866c9-e3ac-4575-94e7-e3d426903924" +version = "0.1.2" + +[[deps.Polynomials]] +deps = ["LinearAlgebra", "RecipesBase", "Setfield", "SparseArrays"] +git-tree-sha1 = "a9c7a523d5ed375be3983db190f6a5874ae9286d" +uuid = "f27b6e38-b328-58d1-80ce-0feddd5e7a45" +version = "4.0.6" + + [deps.Polynomials.extensions] + PolynomialsChainRulesCoreExt = "ChainRulesCore" + PolynomialsFFTWExt = "FFTW" + PolynomialsMakieCoreExt = "MakieCore" + PolynomialsMutableArithmeticsExt = "MutableArithmetics" + + [deps.Polynomials.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" + MakieCore = "20f20a25-4f0e-4fdf-b5d1-57303727442b" + MutableArithmetics = "d8a4904e-b15c-11e9-3269-09a3773c0cb0" + +[[deps.PooledArrays]] +deps = ["DataAPI", "Future"] +git-tree-sha1 = "36d8b4b899628fb92c2749eb488d884a926614d3" +uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" +version = "1.4.3" + +[[deps.PositiveFactorizations]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "17275485f373e6673f7e7f97051f703ed5b15b20" +uuid = "85a6dd25-e78a-55b7-8502-1745935b8125" +version = "0.2.4" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.2.1" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.3" + +[[deps.PrettyTables]] +deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "Reexport", "StringManipulation", "Tables"] +git-tree-sha1 = "88b895d13d53b5577fd53379d913b9ab9ac82660" +uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" +version = "2.3.1" + +[[deps.Primes]] +deps = ["IntegerMathUtils"] +git-tree-sha1 = "cb420f77dc474d23ee47ca8d14c90810cafe69e7" +uuid = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae" +version = "0.5.6" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.ProgressMeter]] +deps = ["Distributed", "Printf"] +git-tree-sha1 = "763a8ceb07833dd51bb9e3bbca372de32c0605ad" +uuid = "92933f4c-e287-5a05-a399-4b506db050ca" +version = "1.10.0" + +[[deps.QOI]] +deps = ["ColorTypes", "FileIO", "FixedPointNumbers"] +git-tree-sha1 = "18e8f4d1426e965c7b532ddd260599e1510d26ce" +uuid = "4b34888f-f399-49d4-9bb3-47ed5cae4e65" +version = "1.0.0" + +[[deps.QhullMiniWrapper_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Qhull_jll"] +git-tree-sha1 = "607cf73c03f8a9f83b36db0b86a3a9c14179621f" +uuid = "460c41e3-6112-5d7f-b78c-b6823adb3f2d" +version = "1.0.0+1" + +[[deps.Qhull_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "be2449911f4d6cfddacdf7efc895eceda3eee5c1" +uuid = "784f63db-0788-585a-bace-daefebcd302b" +version = "8.0.1003+0" + +[[deps.QuadGK]] +deps = ["DataStructures", "LinearAlgebra"] +git-tree-sha1 = "9b23c31e76e333e6fb4c1595ae6afa74966a729e" +uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" +version = "2.9.4" + +[[deps.REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.RangeArrays]] +git-tree-sha1 = "b9039e93773ddcfc828f12aadf7115b4b4d225f5" +uuid = "b3c3ace0-ae52-54e7-9d0b-2c1406fd6b9d" +version = "0.3.2" + +[[deps.Ratios]] +deps = ["Requires"] +git-tree-sha1 = "1342a47bf3260ee108163042310d26f2be5ec90b" +uuid = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439" +version = "0.4.5" +weakdeps = ["FixedPointNumbers"] + + [deps.Ratios.extensions] + RatiosFixedPointNumbersExt = "FixedPointNumbers" + +[[deps.RecipesBase]] +deps = ["PrecompileTools"] +git-tree-sha1 = "5c3d09cc4f31f5fc6af001c250bf1278733100ff" +uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +version = "1.3.4" + +[[deps.Reexport]] +git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "1.2.2" + +[[deps.RelocatableFolders]] +deps = ["SHA", "Scratch"] +git-tree-sha1 = "ffdaf70d81cf6ff22c2b6e733c900c3321cab864" +uuid = "05181044-ff0b-4ac5-8273-598c1e38db00" +version = "1.0.1" + +[[deps.Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.0" + +[[deps.RingLists]] +deps = ["Random"] +git-tree-sha1 = "f39da63aa6d2d88e0c1bd20ed6a3ff9ea7171ada" +uuid = "286e9d63-9694-5540-9e3c-4e6708fa07b2" +version = "0.2.8" + +[[deps.Rmath]] +deps = ["Random", "Rmath_jll"] +git-tree-sha1 = "f65dcb5fa46aee0cf9ed6274ccbd597adc49aa7b" +uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa" +version = "0.7.1" + +[[deps.Rmath_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "6ed52fdd3382cf21947b15e8870ac0ddbff736da" +uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f" +version = "0.4.0+0" + +[[deps.RoundingEmulator]] +git-tree-sha1 = "40b9edad2e5287e05bd413a38f61a8ff55b9557b" +uuid = "5eaf0fd0-dfba-4ccb-bf02-d820a40db705" +version = "0.2.1" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.ScatteredInterpolation]] +deps = ["Combinatorics", "Distances", "LinearAlgebra", "NearestNeighbors"] +git-tree-sha1 = "0d642a08199bbeccd874b33fe3a1b699d345ca79" +uuid = "3f865c0f-6dca-5f4d-999b-29fe1e7e3c92" +version = "0.3.6" + +[[deps.Scratch]] +deps = ["Dates"] +git-tree-sha1 = "3bac05bc7e74a75fd9cba4295cde4045d9fe2386" +uuid = "6c6a2e73-6563-6170-7368-637461726353" +version = "1.2.1" + +[[deps.SentinelArrays]] +deps = ["Dates", "Random"] +git-tree-sha1 = "0e7508ff27ba32f26cd459474ca2ede1bc10991f" +uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" +version = "1.4.1" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.Setfield]] +deps = ["ConstructionBase", "Future", "MacroTools", "StaticArraysCore"] +git-tree-sha1 = "e2cc6d8c88613c05e1defb55170bf5ff211fbeac" +uuid = "efcf1570-3423-57d1-acb7-fd33fddbac46" +version = "1.1.1" + +[[deps.ShaderAbstractions]] +deps = ["ColorTypes", "FixedPointNumbers", "GeometryBasics", "LinearAlgebra", "Observables", "StaticArrays", "StructArrays", "Tables"] +git-tree-sha1 = "79123bc60c5507f035e6d1d9e563bb2971954ec8" +uuid = "65257c39-d410-5151-9873-9b3e5be5013e" +version = "0.4.1" + +[[deps.SharedArrays]] +deps = ["Distributed", "Mmap", "Random", "Serialization"] +uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" + +[[deps.ShiftedArrays]] +git-tree-sha1 = "503688b59397b3307443af35cd953a13e8005c16" +uuid = "1277b4bf-5013-50f5-be3d-901d8477a67a" +version = "2.0.0" + +[[deps.Showoff]] +deps = ["Dates", "Grisu"] +git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de" +uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f" +version = "1.0.3" + +[[deps.SignalAnalysis]] +deps = ["DSP", "Distributions", "DocStringExtensions", "FFTW", "LinearAlgebra", "MetaArrays", "Optim", "PaddedViews", "Peaks", "Random", "Requires", "SignalBase", "Statistics", "WAV"] +git-tree-sha1 = "d57f40bf531ae2b82549aae0cb20e84331d40333" +uuid = "df1fea92-c066-49dd-8b36-eace3378ea47" +version = "0.6.0" + +[[deps.SignalBase]] +deps = ["Unitful"] +git-tree-sha1 = "14cb05cba5cc89d15e6098e7bb41dcef2606a10a" +uuid = "00c44e92-20f5-44bc-8f45-a1dcef76ba38" +version = "0.1.2" + +[[deps.SignedDistanceFields]] +deps = ["Random", "Statistics", "Test"] +git-tree-sha1 = "d263a08ec505853a5ff1c1ebde2070419e3f28e9" +uuid = "73760f76-fbc4-59ce-8f25-708e95d2df96" +version = "0.4.0" + +[[deps.SimpleGraphs]] +deps = ["AbstractLattices", "Combinatorics", "DataStructures", "IterTools", "LightXML", "LinearAlgebra", "LinearAlgebraX", "Optim", "Primes", "Random", "RingLists", "SimplePartitions", "SimplePolynomials", "SimpleRandom", "SparseArrays", "Statistics"] +git-tree-sha1 = "f65caa24a622f985cc341de81d3f9744435d0d0f" +uuid = "55797a34-41de-5266-9ec1-32ac4eb504d3" +version = "0.8.6" + +[[deps.SimplePartitions]] +deps = ["AbstractLattices", "DataStructures", "Permutations"] +git-tree-sha1 = "e182b9e5afb194142d4668536345a365ea19363a" +uuid = "ec83eff0-a5b5-5643-ae32-5cbf6eedec9d" +version = "0.3.2" + +[[deps.SimplePolynomials]] +deps = ["Mods", "Multisets", "Polynomials", "Primes"] +git-tree-sha1 = "7063828369cafa93f3187b3d0159f05582011405" +uuid = "cc47b68c-3164-5771-a705-2bc0097375a0" +version = "0.2.17" + +[[deps.SimpleRandom]] +deps = ["Distributions", "LinearAlgebra", "Random"] +git-tree-sha1 = "3a6fb395e37afab81aeea85bae48a4db5cd7244a" +uuid = "a6525b86-64cd-54fa-8f65-62fc48bdc0e8" +version = "0.3.1" + +[[deps.SimpleTraits]] +deps = ["InteractiveUtils", "MacroTools"] +git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231" +uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" +version = "0.9.4" + +[[deps.Sixel]] +deps = ["Dates", "FileIO", "ImageCore", "IndirectArrays", "OffsetArrays", "REPL", "libsixel_jll"] +git-tree-sha1 = "2da10356e31327c7096832eb9cd86307a50b1eb6" +uuid = "45858cf5-a6b0-47a3-bbea-62219f50df47" +version = "0.1.3" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[deps.SortingAlgorithms]] +deps = ["DataStructures"] +git-tree-sha1 = "66e0a8e672a0bdfca2c3f5937efb8538b9ddc085" +uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" +version = "1.2.1" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.10.0" + +[[deps.SpecialFunctions]] +deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] +git-tree-sha1 = "e2cfc4012a19088254b3950b85c3c1d8882d864d" +uuid = "276daf66-3868-5448-9aa4-cd146d93841b" +version = "2.3.1" +weakdeps = ["ChainRulesCore"] + + [deps.SpecialFunctions.extensions] + SpecialFunctionsChainRulesCoreExt = "ChainRulesCore" + +[[deps.SplitApplyCombine]] +deps = ["Dictionaries", "Indexing"] +git-tree-sha1 = "c06d695d51cfb2187e6848e98d6252df9101c588" +uuid = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66" +version = "1.2.3" + +[[deps.StackViews]] +deps = ["OffsetArrays"] +git-tree-sha1 = "46e589465204cd0c08b4bd97385e4fa79a0c770c" +uuid = "cae243ae-269e-4f55-b966-ac2d0dc13c15" +version = "0.1.1" + +[[deps.Static]] +deps = ["IfElse"] +git-tree-sha1 = "d2fdac9ff3906e27f7a618d47b676941baa6c80c" +uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3" +version = "0.8.10" + +[[deps.StaticArrayInterface]] +deps = ["ArrayInterface", "Compat", "IfElse", "LinearAlgebra", "PrecompileTools", "Requires", "SparseArrays", "Static", "SuiteSparse"] +git-tree-sha1 = "5d66818a39bb04bf328e92bc933ec5b4ee88e436" +uuid = "0d7ed370-da01-4f52-bd93-41d350b8b718" +version = "1.5.0" +weakdeps = ["OffsetArrays", "StaticArrays"] + + [deps.StaticArrayInterface.extensions] + StaticArrayInterfaceOffsetArraysExt = "OffsetArrays" + StaticArrayInterfaceStaticArraysExt = "StaticArrays" + +[[deps.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "bf074c045d3d5ffd956fa0a461da38a44685d6b2" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.3" +weakdeps = ["ChainRulesCore", "Statistics"] + + [deps.StaticArrays.extensions] + StaticArraysChainRulesCoreExt = "ChainRulesCore" + StaticArraysStatisticsExt = "Statistics" + +[[deps.StaticArraysCore]] +git-tree-sha1 = "36b3d696ce6366023a0ea192b4cd442268995a0d" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.2" + +[[deps.Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +version = "1.10.0" + +[[deps.StatsAPI]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "1ff449ad350c9c4cbc756624d6f8a8c3ef56d3ed" +uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" +version = "1.7.0" + +[[deps.StatsBase]] +deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] +git-tree-sha1 = "5cf7606d6cef84b543b483848d4ae08ad9832b21" +uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" +version = "0.34.3" + +[[deps.StatsFuns]] +deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"] +git-tree-sha1 = "cef0472124fab0695b58ca35a77c6fb942fdab8a" +uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c" +version = "1.3.1" + + [deps.StatsFuns.extensions] + StatsFunsChainRulesCoreExt = "ChainRulesCore" + StatsFunsInverseFunctionsExt = "InverseFunctions" + + [deps.StatsFuns.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.StatsModels]] +deps = ["DataAPI", "DataStructures", "LinearAlgebra", "Printf", "REPL", "ShiftedArrays", "SparseArrays", "StatsAPI", "StatsBase", "StatsFuns", "Tables"] +git-tree-sha1 = "5cf6c4583533ee38639f73b880f35fc85f2941e0" +uuid = "3eaba693-59b7-5ba5-a881-562e759f1c8d" +version = "0.7.3" + +[[deps.StringManipulation]] +deps = ["PrecompileTools"] +git-tree-sha1 = "a04cabe79c5f01f4d723cc6704070ada0b9d46d5" +uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e" +version = "0.3.4" + +[[deps.StructArrays]] +deps = ["ConstructionBase", "DataAPI", "Tables"] +git-tree-sha1 = "f4dc295e983502292c4c3f951dbb4e985e35b3be" +uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" +version = "0.6.18" + + [deps.StructArrays.extensions] + StructArraysAdaptExt = "Adapt" + StructArraysGPUArraysCoreExt = "GPUArraysCore" + StructArraysSparseArraysExt = "SparseArrays" + StructArraysStaticArraysExt = "StaticArrays" + + [deps.StructArrays.weakdeps] + Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" + GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527" + SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + +[[deps.StructTypes]] +deps = ["Dates", "UUIDs"] +git-tree-sha1 = "ca4bccb03acf9faaf4137a9abc1881ed1841aa70" +uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" +version = "1.10.0" + +[[deps.SuiteSparse]] +deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"] +uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "7.2.1+1" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.TZJData]] +deps = ["Artifacts"] +git-tree-sha1 = "b69f8338df046774bd975b13be9d297eca5efacb" +uuid = "dc5dba14-91b3-4cab-a142-028a31da12f7" +version = "1.1.0+2023d" + +[[deps.TableTraits]] +deps = ["IteratorInterfaceExtensions"] +git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" +uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" +version = "1.0.1" + +[[deps.Tables]] +deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "OrderedCollections", "TableTraits"] +git-tree-sha1 = "cb76cf677714c095e535e3501ac7954732aeea2d" +uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" +version = "1.11.1" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.TensorCore]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6" +uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50" +version = "0.1.1" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.TiffImages]] +deps = ["ColorTypes", "DataStructures", "DocStringExtensions", "FileIO", "FixedPointNumbers", "IndirectArrays", "Inflate", "Mmap", "OffsetArrays", "PkgVersion", "ProgressMeter", "UUIDs"] +git-tree-sha1 = "34cc045dd0aaa59b8bbe86c644679bc57f1d5bd0" +uuid = "731e570b-9d59-4bfa-96dc-6df516fadf69" +version = "0.6.8" + +[[deps.TiledIteration]] +deps = ["OffsetArrays", "StaticArrayInterface"] +git-tree-sha1 = "1176cc31e867217b06928e2f140c90bd1bc88283" +uuid = "06e1c1a7-607b-532d-9fad-de7d9aa2abac" +version = "0.5.0" + +[[deps.TimeZones]] +deps = ["Artifacts", "Dates", "Downloads", "InlineStrings", "LazyArtifacts", "Mocking", "Printf", "Scratch", "TZJData", "Unicode", "p7zip_jll"] +git-tree-sha1 = "cc54d5c9803309474014a8955a96e4adcd11bcf4" +uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53" +version = "1.14.0" +weakdeps = ["RecipesBase"] + + [deps.TimeZones.extensions] + TimeZonesRecipesBaseExt = "RecipesBase" + +[[deps.TimerOutputs]] +deps = ["ExprTools", "Printf"] +git-tree-sha1 = "f548a9e9c490030e545f72074a41edfd0e5bcdd7" +uuid = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" +version = "0.5.23" + +[[deps.ToeplitzMatrices]] +deps = ["AbstractFFTs", "DSP", "FillArrays", "LinearAlgebra"] +git-tree-sha1 = "df4e499f6321e72f801aab45336ba76ed06e97db" +uuid = "c751599d-da0a-543b-9d20-d0a503d91d24" +version = "0.8.3" +weakdeps = ["StatsBase"] + + [deps.ToeplitzMatrices.extensions] + ToeplitzMatricesStatsBaseExt = "StatsBase" + +[[deps.TopoPlots]] +deps = ["CloughTocher2DInterpolation", "Delaunator", "Dierckx", "GeometryBasics", "InteractiveUtils", "LinearAlgebra", "Makie", "Parameters", "PrecompileTools", "ScatteredInterpolation", "Statistics"] +git-tree-sha1 = "b29e9b44575df854abe6952065bb6064b9d42263" +uuid = "2bdbdf9c-dbd8-403f-947b-1a4e0dd41a7a" +version = "0.1.7" + +[[deps.TranscodingStreams]] +git-tree-sha1 = "71509f04d045ec714c4748c785a59045c3736349" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.10.7" +weakdeps = ["Random", "Test"] + + [deps.TranscodingStreams.extensions] + TestExt = ["Test", "Random"] + +[[deps.TriplotBase]] +git-tree-sha1 = "4d4ed7f294cda19382ff7de4c137d24d16adc89b" +uuid = "981d1d27-644d-49a2-9326-4793e63143c3" +version = "0.1.0" + +[[deps.Tullio]] +deps = ["DiffRules", "LinearAlgebra", "Requires"] +git-tree-sha1 = "6d476962ba4e435d7f4101a403b1d3d72afe72f3" +uuid = "bc48ee85-29a4-5162-ae0b-a64e1601d4bc" +version = "0.3.7" + + [deps.Tullio.extensions] + TullioCUDAExt = "CUDA" + TullioChainRulesCoreExt = "ChainRulesCore" + TullioFillArraysExt = "FillArrays" + TullioTrackerExt = "Tracker" + + [deps.Tullio.weakdeps] + CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b" + Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" + +[[deps.TypedTables]] +deps = ["Adapt", "Dictionaries", "Indexing", "SplitApplyCombine", "Tables", "Unicode"] +git-tree-sha1 = "84fd7dadde577e01eb4323b7e7b9cb51c62c60d4" +uuid = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9" +version = "1.4.6" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.UnPack]] +git-tree-sha1 = "387c1f73762231e86e0c9c5443ce3b4a0a9a0c2b" +uuid = "3a884ed6-31ef-47d7-9d2a-63182c4928ed" +version = "1.0.2" + +[[deps.Unfold]] +deps = ["DSP", "DataFrames", "Distributions", "DocStringExtensions", "Effects", "FileIO", "GLM", "IterativeSolvers", "JLD2", "LinearAlgebra", "Logging", "MLBase", "Missings", "ProgressMeter", "Random", "SparseArrays", "StaticArrays", "Statistics", "StatsBase", "StatsFuns", "StatsModels", "Tables", "Test", "TimerOutputs", "Tullio"] +git-tree-sha1 = "62d17ffc411e3fb4f971524c59e3155b6b2f5819" +uuid = "181c99d8-e21b-4ff3-b70b-c233eddec679" +version = "0.6.9" + + [deps.Unfold.extensions] + UnfoldBSplineKitExt = "BSplineKit" + UnfoldKrylovExt = ["Krylov", "CUDA"] + UnfoldMixedModelsExt = "MixedModels" + UnfoldRobustModelsExt = "RobustModels" + + [deps.Unfold.weakdeps] + BSplineKit = "093aae92-e908-43d7-9660-e50ee39d5a0a" + CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" + Krylov = "ba0b0d4f-ebba-5204-a429-3ac8c609bfb7" + MixedModels = "ff71e718-51f3-5ec2-a782-8ffcbfa3c316" + RobustModels = "d6ea1423-9682-4bbd-952f-b1577cbf8c98" + +[[deps.UnfoldMakie]] +deps = ["AlgebraOfGraphics", "CategoricalArrays", "ColorSchemes", "ColorTypes", "Colors", "CoordinateTransformations", "DataFrames", "DataStructures", "DocStringExtensions", "GeometryBasics", "GridLayoutBase", "ImageFiltering", "Interpolations", "LinearAlgebra", "Makie", "SparseArrays", "StaticArrays", "Statistics", "TopoPlots", "Unfold"] +git-tree-sha1 = "c6367a66390a105e7f0cfbc1d4f1686e239ea5e7" +uuid = "69a5ce3b-64fb-4f22-ae69-36dd4416af2a" +version = "0.5.1" + + [deps.UnfoldMakie.extensions] + UnfoldMakiePyMNEExt = "PyMNE" + + [deps.UnfoldMakie.weakdeps] + PyMNE = "6c5003b2-cbe8-491c-a0d1-70088e6a0fd6" + +[[deps.UnfoldSim]] +deps = ["Artifacts", "DSP", "DataFrames", "Distributions", "FileIO", "HDF5", "ImageFiltering", "LinearAlgebra", "MixedModels", "MixedModelsSim", "Parameters", "Random", "SignalAnalysis", "Statistics", "StatsModels", "ToeplitzMatrices"] +git-tree-sha1 = "28b31c77e4b2267a51d1bfe95055127dbda01aa8" +uuid = "ed8ae6d2-84d3-44c6-ab46-0baf21700804" +version = "0.3.2" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[deps.UnicodeFun]] +deps = ["REPL"] +git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf" +uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1" +version = "0.4.1" + +[[deps.Unitful]] +deps = ["Dates", "LinearAlgebra", "Random"] +git-tree-sha1 = "3c793be6df9dd77a0cf49d80984ef9ff996948fa" +uuid = "1986cc42-f94f-5a68-af5c-568840ba703d" +version = "1.19.0" + + [deps.Unitful.extensions] + ConstructionBaseUnitfulExt = "ConstructionBase" + InverseFunctionsUnitfulExt = "InverseFunctions" + + [deps.Unitful.weakdeps] + ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.WAV]] +deps = ["Base64", "FileIO", "Libdl", "Logging"] +git-tree-sha1 = "7e7e1b4686995aaf4ecaaf52f6cd824fa6bd6aa5" +uuid = "8149f6b0-98f6-5db9-b78f-408fbbb8ef88" +version = "1.2.0" + +[[deps.WoodburyMatrices]] +deps = ["LinearAlgebra", "SparseArrays"] +git-tree-sha1 = "c1a7aa6219628fcd757dede0ca95e245c5cd9511" +uuid = "efce3f68-66dc-5838-9240-27a6d6f5f9b6" +version = "1.0.0" + +[[deps.XML2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] +git-tree-sha1 = "532e22cf7be8462035d092ff21fada7527e2c488" +uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" +version = "2.12.6+0" + +[[deps.XSLT_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"] +git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a" +uuid = "aed1982a-8fda-507f-9586-7b0439959a61" +version = "1.1.34+0" + +[[deps.Xorg_libX11_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] +git-tree-sha1 = "afead5aba5aa507ad5a3bf01f58f82c8d1403495" +uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" +version = "1.8.6+0" + +[[deps.Xorg_libXau_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "6035850dcc70518ca32f012e46015b9beeda49d8" +uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" +version = "1.0.11+0" + +[[deps.Xorg_libXdmcp_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "34d526d318358a859d7de23da945578e8e8727b7" +uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" +version = "1.1.4+0" + +[[deps.Xorg_libXext_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"] +git-tree-sha1 = "b7c0aa8c376b31e4852b360222848637f481f8c3" +uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" +version = "1.3.4+4" + +[[deps.Xorg_libXrender_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"] +git-tree-sha1 = "19560f30fd49f4d4efbe7002a1037f8c43d43b96" +uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" +version = "0.9.10+4" + +[[deps.Xorg_libpthread_stubs_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "8fdda4c692503d44d04a0603d9ac0982054635f9" +uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74" +version = "0.1.1+0" + +[[deps.Xorg_libxcb_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"] +git-tree-sha1 = "b4bfde5d5b652e22b9c790ad00af08b6d042b97d" +uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" +version = "1.15.0+0" + +[[deps.Xorg_xtrans_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e92a1a012a10506618f10b7047e478403a046c77" +uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" +version = "1.5.0+0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+1" + +[[deps.Zstd_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b" +uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" +version = "1.5.6+0" + +[[deps.isoband_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "51b5eeb3f98367157a7a12a1fb0aa5328946c03c" +uuid = "9a68df92-36a6-505f-a73e-abb412b6bfb4" +version = "0.2.3+0" + +[[deps.libaec_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "46bf7be2917b59b761247be3f317ddf75e50e997" +uuid = "477f73a3-ac25-53e9-8cc3-50b2fa2566f0" +version = "1.1.2+0" + +[[deps.libaom_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "3a2ea60308f0996d26f1e5354e10c24e9ef905d4" +uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b" +version = "3.4.0+0" + +[[deps.libass_jll]] +deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"] +git-tree-sha1 = "5982a94fcba20f02f42ace44b9894ee2b140fe47" +uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0" +version = "0.15.1+0" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.8.0+1" + +[[deps.libfdk_aac_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "daacc84a041563f965be61859a36e17c4e4fcd55" +uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280" +version = "2.0.2+0" + +[[deps.libpng_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "d7015d2e18a5fd9a4f47de711837e980519781a4" +uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" +version = "1.6.43+1" + +[[deps.libsixel_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Pkg", "libpng_jll"] +git-tree-sha1 = "d4f63314c8aa1e48cd22aa0c17ed76cd1ae48c3c" +uuid = "075b6546-f08a-558a-be8f-8157d0f608a5" +version = "1.10.3+0" + +[[deps.libvorbis_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"] +git-tree-sha1 = "b910cb81ef3fe6e78bf6acee440bda86fd6ae00c" +uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a" +version = "1.3.7+1" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.52.0+1" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+2" + +[[deps.x264_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "4fea590b89e6ec504593146bf8b988b2c00922b2" +uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a" +version = "2021.5.5+0" + +[[deps.x265_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "ee567a171cce03570d77ad3a43e90218e38937a9" +uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76" +version = "3.5.0+0" +""" + +# ╔═╡ Cell order: +# ╠═080f23f4-f900-11ee-1e5d-cd486a86ccff +# ╠═5d925343-5d02-4887-9337-11cdf810c698 +# ╠═bf4d7bdf-77df-402e-8c4e-098e85fcc34a +# ╠═b4beaaa3-7f98-4f12-a8d9-38994974e462 +# ╠═b53f298d-e420-44b1-b1ce-cd5f8fe514d0 +# ╠═f56aa7db-4dbd-404f-8704-3328b190b0ca +# ╠═6352a797-eb24-4964-9c34-0d410310df4e +# ╠═17c5239c-04a0-47cd-9393-115763e7aaad +# ╠═76575e4c-df59-49a3-8799-0aa0db4d35db +# ╠═d01bbf66-1f7e-4646-b8e4-4d6ec6539533 +# ╠═a005fef0-b145-492b-88d7-198ba1a751c9 +# ╠═796eb40e-2e70-492e-bca1-af197519684d +# ╠═9248e9d0-2fc6-4e82-8608-3c259b8a3c86 +# ╠═c7991ae2-27f3-4d96-a667-d1e6130d01a3 +# ╠═9821d37d-007d-497e-83e7-4f324696e8a2 +# ╟─00000000-0000-0000-0000-000000000001 +# ╟─00000000-0000-0000-0000-000000000002 From 6fbbc6940cb8438e2cd6906f4bac405590307bee Mon Sep 17 00:00:00 2001 From: "behinger (s-ccs 001)" Date: Wed, 1 May 2024 11:23:06 +0000 Subject: [PATCH 089/113] fix #92 --- src/simulation.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/simulation.jl b/src/simulation.jl index 64652a49..2376e8ff 100755 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -33,9 +33,9 @@ Some remarks to how the noise is added: """ -function simulate(args...; kwargs...) +function simulate(design::AbstractDesign, signal, onset::AbstractOnset, args...; kwargs...) @warn "No random generator defined, used the default (`Random.MersenneTwister(1)`) with a fixed seed. This will always return the same results and the user is strongly encouraged to provide their own random generator!" - simulate(MersenneTwister(1), args...; kwargs...) + simulate(MersenneTwister(1), design, signal, onset, args...; kwargs...) end simulate( From 88f3e59531966d99360eed325002f77d31bf8f4f Mon Sep 17 00:00:00 2001 From: Judith Schepers Date: Tue, 18 Jun 2024 15:52:17 +0200 Subject: [PATCH 090/113] Create CONTRIBUTING.md --- CONTRIBUTING.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a784977c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,18 @@ +# Contribution guide +Contributions are very welcome. These could be typos, bug reports, feature requests, speed optimization, better code, and better documentation. +You are very welcome to raise issues and start pull requests. + +## Bug reports +If you notice any bugs, such as crashing code, incorrect results or speed issues, please raise a GitHub issue. The issue should contain a short description of the problem, (optimally) a minimal working example to reproduce the bug and which UnfoldSim.jl version you are using. + +## Code contributions (Pull requests) +When opening a pull request, please add a short but meaningful description of the changes/features you implemented. Moreover, please add tests (where appropriate) to ensure that your code is working as expected. + +## Adding documentation +1. We recommend to write a Literate.jl document and place it in `docs/literate/FOLDER/FILENAME.jl` with `FOLDER` being `HowTo`, `Explanation`, `Tutorial` or `Reference` ([recommended reading on the 4 categories](https://documentation.divio.com/)). +2. Literate.jl converts the `.jl` file to a `.md` automatically and places it in `docs/src/generated/FOLDER/FILENAME.md`. +3. Edit [make.jl](https://github.com/unfoldtoolbox/Unfold.jl/blob/main/docs/make.jl) with a reference to `docs/src/generated/FOLDER/FILENAME.md`. + +## Formatting (Beware of reviewdog :dog:) +We use the [julia-format](https://github.com/julia-actions/julia-format) Github action to ensure that the code follows the formatting rules defined by [JuliaFormatter.jl](https://github.com/domluna/JuliaFormatter.jl). +When opening a pull request [reviewdog](https://github.com/reviewdog/reviewdog) will automatically make formatting suggestions for your code. From 60bd9f9a08f06ea7ce5bea079f91767685685245 Mon Sep 17 00:00:00 2001 From: jschepers Date: Tue, 18 Jun 2024 14:07:26 +0000 Subject: [PATCH 091/113] adapt contributions and contributors sections of the readme --- README.md | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8506143e..cdfb3b45 100644 --- a/README.md +++ b/README.md @@ -79,18 +79,7 @@ data, events = simulate( All components (design, components, onsets, noise) can be easily modified and you can simply plugin your own! ## Contributions - -Contributions are very welcome. These could be typos, bug reports, feature requests, speed-optimization, better code, better documentation. - -### How-to Contribute - -You are very welcome to raise issues and start pull requests! - -### Adding Documentation - -1. We recommend to write a Literate.jl document and place it in `docs/literate/FOLDER/FILENAME.jl` with `FOLDER` being `HowTo`, `Explanation`, `Tutorial` or `Reference` ([recommended reading on the 4 categories](https://documentation.divio.com/)). -2. Literate.jl converts the `.jl` file to a `.md` automatically and places it in `docs/src/generated/FOLDER/FILENAME.md`. -3. Edit [make.jl](https://github.com/unfoldtoolbox/Unfold.jl/blob/main/docs/make.jl) with a reference to `docs/src/generated/FOLDER/FILENAME.md`. +Contributions of any kind are very welcome. Please have a look at [CONTRIBUTING.md](https://github.com/unfoldtoolbox/UnfoldSim.jl/blob/main/CONTRIBUTING.md) for guidance on contributing to UnfoldSim.jl. ## Contributors @@ -118,8 +107,7 @@ You are very welcome to raise issues and start pull requests! This project follows the [all-contributors](https://allcontributors.org/docs/en/specification) specification. - -Contributions of any kind welcome! +Please reach out, if you have contributed to UnfoldSim.jl but we have not listed you as a contributor yet. ## Citation From fb0540695c875cb06fef1fb0854a0d4917049b67 Mon Sep 17 00:00:00 2001 From: jschepers Date: Wed, 31 Jul 2024 15:26:39 +0000 Subject: [PATCH 092/113] removed NoNoise from the noise comparison plot --- joss_paper/create_paper_plots.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joss_paper/create_paper_plots.jl b/joss_paper/create_paper_plots.jl index 53b9b4b7..58dddc48 100644 --- a/joss_paper/create_paper_plots.jl +++ b/joss_paper/create_paper_plots.jl @@ -274,7 +274,7 @@ let ygridvisible = false, ) - for n in [PinkNoise RedNoise WhiteNoise NoNoise ExponentialNoise] + for n in [PinkNoise RedNoise WhiteNoise ExponentialNoise] # Generate noise samples noisevec = simulate_noise(StableRNG(1), n(), 10000) From 3e697ff958463d74153f032cffcb9038bca71f96 Mon Sep 17 00:00:00 2001 From: jschepers Date: Wed, 31 Jul 2024 15:37:46 +0000 Subject: [PATCH 093/113] (auto) add joss paper plots --- .../plots/example_coefficients_effects.svg | 2159 +++++++-------- joss_paper/plots/example_simulated_data.svg | 1008 ++++--- joss_paper/plots/noise_types.svg | 1529 +++++------ joss_paper/plots/onset_distributions.svg | 2405 ++++++++--------- 4 files changed, 3299 insertions(+), 3802 deletions(-) diff --git a/joss_paper/plots/example_coefficients_effects.svg b/joss_paper/plots/example_coefficients_effects.svg index 23470f78..def4d0b9 100644 --- a/joss_paper/plots/example_coefficients_effects.svg +++ b/joss_paper/plots/example_coefficients_effects.svg @@ -1,1402 +1,1253 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - + + - - + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - + + - - + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - + + - - + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - + + + + + + + + + + + + diff --git a/joss_paper/plots/example_simulated_data.svg b/joss_paper/plots/example_simulated_data.svg index 1ef8a42d..8fb582da 100644 --- a/joss_paper/plots/example_simulated_data.svg +++ b/joss_paper/plots/example_simulated_data.svg @@ -1,664 +1,576 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - + + - - + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - + + + + + + diff --git a/joss_paper/plots/noise_types.svg b/joss_paper/plots/noise_types.svg index 2d2fd669..6ce113d1 100644 --- a/joss_paper/plots/noise_types.svg +++ b/joss_paper/plots/noise_types.svg @@ -1,990 +1,869 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - + + - - + + - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + - - + + - - + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - + + + + + + + + + + diff --git a/joss_paper/plots/onset_distributions.svg b/joss_paper/plots/onset_distributions.svg index 39cf0afc..a4fa1ee7 100644 --- a/joss_paper/plots/onset_distributions.svg +++ b/joss_paper/plots/onset_distributions.svg @@ -1,1406 +1,1261 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - + + + + + + From b6a9d887396749c1b86e6fbf58df028e39cddddc Mon Sep 17 00:00:00 2001 From: jschepers Date: Wed, 31 Jul 2024 16:08:02 +0000 Subject: [PATCH 094/113] Start addressing JOSS review comments --- joss_paper/paper.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index bc5fad0f..21ff5e5f 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -1,5 +1,5 @@ --- -title: 'UnfoldSim.jl: A toolbox for simulating continuous event-based time series data for EEG and beyond' +title: 'UnfoldSim.jl: Simulating continuous event-based time series data for EEG and beyond' tags: - Julia - EEG @@ -38,9 +38,9 @@ bibliography: paper.bib `UnfoldSim.jl` is a Julia package used to simulate multivariate time series, with a focus on EEG, especially event-related potentials (ERPs). The user provides four ingredients: 1) an experimental design, with both categorical and continuous variables, 2) event basis functions specified via linear or hierarchical models, 3) an inter-event onset distribution, and 4) a noise specification. `UnfoldSim.jl` then simulates continuous EEG signals with potentially overlapping events. Multi-channel support via EEG-forward models is available as well. `UnfoldSim.jl` is modular, providing intuitive entrance points for individual customizations. The user can implement custom designs, components, onset distributions or noise types to tailor the toolbox to their needs. This allows support even for other modalities, e.g. single-voxel fMRI or pupil dilation signals. # Statement of Need -In our work (e.g. @ehinger2019unfold, @dimigen2021regression), we often analyze data containing (temporally) overlapping events (e.g. stimulus onset and button press, or consecutive eye-fixations), non-linear effects, and complex experimental designs. For a multitude of reasons, we need to simulate such kind of data: Simulated EEG data is necessary to test preprocessing and analysis tools, validate statistical methods, illustrate conceptual issues, test toolbox functionalities, and find limitations of traditional analysis workflows. For instance, such simulation tools allow for testing the assumptions of new analysis algorithms and testing their robustness against any violation of these assumptions. +In our work (e.g. @ehinger2019unfold, @dimigen2021regression), we often analyze data containing (temporally) overlapping events (e.g. stimulus onset and button press, or consecutive eye-fixations), non-linear effects, and complex experimental designs. For a multitude of reasons, we often need to simulate such kind of data: Simulated EEG data is useful to test preprocessing and analysis tools, validate statistical methods, illustrate conceptual issues, test toolbox functionalities, and find limitations of traditional analysis workflows. For instance, such simulation tools allow for testing the assumptions of new analysis algorithms and testing their robustness against any violation of these assumptions. -While other EEG simulation toolboxes exist, they each have limitations: they are dominantly MATLAB-based, they do not simulate continuous EEG, and they offer little support for designs more complex than two conditions or with non-linear effects. +While other EEG simulation toolboxes exist, they each have limitations: they are dominantly based on the proprietary MATLAB software, they do not simulate continuous EEG, and they offer little support for designs more complex than two conditions or with non-linear effects. In contrast, UnfoldSim.jl is free and open-source and it allows to simulate continuous EEG signals even for complex designs. # Functionality The toolbox provides four abstract types: `AbstractDesign`, `AbstractComponent`, `AbstractOnset` and `AbstractNoise`. In the following, we present the concrete types that are currently implemented. In addition, users can also implement their own concrete types fitting their individual needs. @@ -64,14 +64,14 @@ The inter-onset distribution defines the distance between events in the case of ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. -![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Panel **B** displays its $log_{10}(power)$ at normalized frequencies.\label{fig_noise_types}](plots/noise_types.svg) +![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Panel **B** displays its log10(power) at normalized frequencies.\label{fig_noise_types}](plots/noise_types.svg) # Simulation example In this section, one can find an example of how to use `UnfoldSim.jl` to simulate continuous EEG data. Additional examples can be found in the [`UnfoldSim.jl` documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/). Moreover, to get started, the `UnfoldSim.jl` toolbox offers the function `predef_eeg` which, depending on the input, simulates continuous EEG data either for a single subject or multiple subjects. In the following, we will first provide examples for the four simulation “ingredients” mentioned above which will then be used to simulate data. -1\. We specify an **experimental design** with one subject in two experimental conditions including a continuous variable with 10 levels. To mimic randomization in an experiment, we shuffle the trials using the `event_order_function` argument. To generate more trials we repeat the design 100 times which results in 2000 trials in total. +1\. We specify an **experimental design** with one subject in two experimental conditions including a continuous variable with 10 values. To mimic randomization in an experiment, we shuffle the trials using the `event_order_function` argument. To generate more trials we repeat the design 100 times which results in 2000 trials in total. ```julia design = @@ -81,7 +81,7 @@ design = :continuous => range(0, 5, length = 10), ), event_order_function = x -> shuffle(deepcopy(StableRNG(1)), x), - ) |> x -> RepeatDesign(x, 100); + ) |> x -> RepeatDesign(x, 100) ``` \autoref{events_df} shows the first rows of the events data frame resulting from the experimental design that we specified. @@ -103,13 +103,13 @@ n1 = LinearModelComponent(; basis = n170(), formula = @formula(0 ~ 1 + condition), β = [5, 3], -); +) p3 = LinearModelComponent(; basis = p300(), formula = @formula(0 ~ 1 + continuous + continuous^2), β = [5, 1, 0.2], -); +) components = [n1, p3] ``` @@ -129,7 +129,7 @@ noise = PinkNoise(; noiselevel = 2) Finally, we combine all the ingredients and simulate data (see \autoref{fig_example_simulated_data}). To make the simulation reproducible, one can specify a random generator. ```julia -eeg_data, events_df = simulate(StableRNG(1), design, components, onset, noise); +eeg_data, events_df = simulate(StableRNG(1), design, components, onset, noise) ``` ![First 1400 samples from the simulated continuous EEG data. The vertical lines denote the event onsets and their colour represents the respective condition i.e. car or face.\label{fig_example_simulated_data}](plots/example_simulated_data.svg) @@ -145,7 +145,7 @@ m = fit( )), events_df, eeg_data, -); +) ``` In subplot A of \autoref{fig_example_coefficients_effects}, one can see the model estimates for the different coefficients and as intended there is a condition effect in the first negative component and an effect of the continuous variable on the second (positive) component. The relation between the levels of the continuous variable and the scaling of the second component is even clearer visible in subplot B of \autoref{fig_example_coefficients_effects} which depicts the estimated marginal effects of the predictors. Instead of showing the regression coefficients, we can evaluate the estimated function at specific values of the continuous variable. @@ -164,7 +164,7 @@ In Python, `MNE-Python` [@GramfortEtAl2013a] provides some tutorials to simulate In contrast to these tools, `UnfoldSim.jl` has a higher-level perspective, uniquely focusing on the regression-ERP aspect. `UnfoldSim.jl` provides functions to simulate multi-condition experiments, uniquely allows for modeling hierarchical, that is, multi-subject EEG datasets, and offers support to model continuous EEG data with overlapping events. Further, the implementation in Julia offers a platform that is free, that actively encourages research software engineering methods, that makes it easy to add custom expansions via the `AbstractTypes`, and finally, if one is not convinced about the elegancy and speed of Julia, it allows for easy and transparent access from Python and R. # Acknowledgements -Funded by Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) under Germany´s Excellence Strategy – EXC 2075 – 390740016. The authors further thank the International Max Planck Research School for Intelligent Systems (IMPRS-IS) for supporting Judith Schepers. Moreover, the authors would like to thank Tanja Bien for her valuable feedback on the paper manuscript. +Funded by Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) under Germany's Excellence Strategy – EXC 2075 – 390740016. The authors further thank the International Max Planck Research School for Intelligent Systems (IMPRS-IS) for supporting Judith Schepers. Moreover, the authors would like to thank Tanja Bien for her valuable feedback on the paper manuscript. # Package references Please note that we only mention the main dependencies of the toolbox here, but the dependencies of the dependencies can be found in the respective `Manifest.toml` files. Furthermore, please note that we only list rather than cite the packages for which we could not find any citation file or instruction. From d581cee1f9574fade020baa34437ce3268cb9c7f Mon Sep 17 00:00:00 2001 From: jschepers Date: Thu, 1 Aug 2024 13:15:20 +0000 Subject: [PATCH 095/113] Revise paper e.g. adapt figure captions# --- joss_paper/paper.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index 21ff5e5f..b41580f4 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -35,7 +35,7 @@ bibliography: paper.bib # Summary -`UnfoldSim.jl` is a Julia package used to simulate multivariate time series, with a focus on EEG, especially event-related potentials (ERPs). The user provides four ingredients: 1) an experimental design, with both categorical and continuous variables, 2) event basis functions specified via linear or hierarchical models, 3) an inter-event onset distribution, and 4) a noise specification. `UnfoldSim.jl` then simulates continuous EEG signals with potentially overlapping events. Multi-channel support via EEG-forward models is available as well. `UnfoldSim.jl` is modular, providing intuitive entrance points for individual customizations. The user can implement custom designs, components, onset distributions or noise types to tailor the toolbox to their needs. This allows support even for other modalities, e.g. single-voxel fMRI or pupil dilation signals. +`UnfoldSim.jl` is a Julia package used to simulate multivariate time series, with a focus on EEG, especially event-related potentials (ERPs). The user provides four ingredients: 1) an experimental design, with both categorical and continuous variables, 2) event basis functions specified via linear or hierarchical models, 3) an inter-event onset distribution, and 4) a noise specification. `UnfoldSim.jl` then simulates continuous EEG signals with potentially overlapping events. Multi-channel support via EEG-forward models is available as well. `UnfoldSim.jl` is modular, providing intuitive entrance points for individual customizations. The user can implement custom designs, components, onset distributions or noise types to tailor the package to their needs. This allows support even for other modalities, e.g. single-voxel fMRI or pupil dilation signals. # Statement of Need In our work (e.g. @ehinger2019unfold, @dimigen2021regression), we often analyze data containing (temporally) overlapping events (e.g. stimulus onset and button press, or consecutive eye-fixations), non-linear effects, and complex experimental designs. For a multitude of reasons, we often need to simulate such kind of data: Simulated EEG data is useful to test preprocessing and analysis tools, validate statistical methods, illustrate conceptual issues, test toolbox functionalities, and find limitations of traditional analysis workflows. For instance, such simulation tools allow for testing the assumptions of new analysis algorithms and testing their robustness against any violation of these assumptions. @@ -43,13 +43,13 @@ In our work (e.g. @ehinger2019unfold, @dimigen2021regression), we often analyze While other EEG simulation toolboxes exist, they each have limitations: they are dominantly based on the proprietary MATLAB software, they do not simulate continuous EEG, and they offer little support for designs more complex than two conditions or with non-linear effects. In contrast, UnfoldSim.jl is free and open-source and it allows to simulate continuous EEG signals even for complex designs. # Functionality -The toolbox provides four abstract types: `AbstractDesign`, `AbstractComponent`, `AbstractOnset` and `AbstractNoise`. In the following, we present the concrete types that are currently implemented. In addition, users can also implement their own concrete types fitting their individual needs. +The package provides four abstract types: `AbstractDesign`, `AbstractComponent`, `AbstractOnset` and `AbstractNoise`. In the following, we present the concrete types that are currently implemented. In addition, users can also implement their own concrete types fitting their individual needs. ## Experimental designs -Currently, we support a single and a multi-subject design. They are used to generate an experimental design containing the conditions and levels of all predictors. The multi-subject design uses the `MixedModelsSim.jl` toolbox [@phillip_alday_2024_10669002] and allows a flexible specification of the random-effects structure by indicating which predictors are within- or between-subject (or item). Tailored randomisation is possible via a user-specified function, which is applied after design generation. Designs can be encapsulated, for instance, the `RepeatDesign` type which repeats the generated event table multiple times, thus generating new trials. Currently, only balanced designs are implemented, i.e. all possible combinations of predictor levels have the same number of trials. However, [a tutorial on how to implement a new design](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/generated/HowTo/newDesign) for imbalanced datasets is provided. +Currently, we support a single and a multi-subject design. They are used to generate an experimental design containing the conditions and levels of all predictors. The multi-subject design uses the `MixedModelsSim.jl` package [@phillip_alday_2024_10669002] and allows a flexible specification of the random-effects structure by indicating which predictors are within- or between-subject (or item). Tailored randomisation is possible via a user-specified function, which is applied after design generation. Designs can be encapsulated, for instance, the `RepeatDesign` type which repeats the generated event table multiple times, thus generating new trials. Currently, only balanced designs are implemented, i.e. all possible combinations of predictor levels have the same number of trials. However, [a tutorial on how to implement a new design](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/generated/HowTo/newDesign) for imbalanced datasets is provided. ## Event basis functions (Components) -`UnfoldSim.jl` provides a `LinearModelComponent` and a `MixedModelComponent` for single- and multi-subject simulation respectively. These components determine the shape of the response to an event. They consist of a basis function which is weighted by the user-defined regression model. The user specifies a basis function for the component by either providing a custom vector or choosing one of the prespecified bases. For example, the toolbox provides simplified versions of typical EEG components e.g. N170 which are implemented as temporally shifted Hanning windows. Further, in the components’ model formulae, fixed-effects ($\beta s$) and random effects (`MultiSubjectDesign`s only) need to be specified. +`UnfoldSim.jl` provides a `LinearModelComponent` and a `MixedModelComponent` for single- and multi-subject simulation respectively. These components determine the shape of the response to an event. They consist of a basis function which is weighted by the user-defined regression model. The user specifies a basis function for the component by either providing a custom vector or choosing one of the prespecified bases. For example, the package provides simplified versions of typical EEG components e.g. N170 which are implemented as temporally shifted Hanning windows. Further, in the components’ model formulae, fixed-effects ($\beta s$) and random effects (`MultiSubjectDesign`s only) need to be specified. Each component can be nested in a `MultichannelComponent`, which, using a forward headmodel, projects the simulated source component to the multi-channel electrode space. Using `Artifacts.jl` we provide on-demand access to the HArtMuT [@harmening2022hartmut] model. @@ -59,7 +59,7 @@ To generate complex activations, it is possible to specify a vector of `<:Abstra The inter-onset distribution defines the distance between events in the case of a continuous EEG. Currently, `UniformOnset` and `LogNormalOnset` are implemented. By specifying the parameters of the inter-onset distribution, one indirectly controls the amount of overlap between two or more event-related responses. \autoref{fig_onset_distributions} illustrates the parameterization of the two implemented onset distributions. -![Illustration of the inter-onset distributions. The colour indicates different sets of parameter values.\label{fig_onset_distributions}](plots/onset_distributions.svg) +![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg) ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. @@ -67,7 +67,7 @@ UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` ![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Panel **B** displays its log10(power) at normalized frequencies.\label{fig_noise_types}](plots/noise_types.svg) # Simulation example -In this section, one can find an example of how to use `UnfoldSim.jl` to simulate continuous EEG data. Additional examples can be found in the [`UnfoldSim.jl` documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/). Moreover, to get started, the `UnfoldSim.jl` toolbox offers the function `predef_eeg` which, depending on the input, simulates continuous EEG data either for a single subject or multiple subjects. +In this section, one can find an example of how to use `UnfoldSim.jl` to simulate continuous EEG data. Additional examples can be found in the [`UnfoldSim.jl` documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/). Moreover, to get started, the `UnfoldSim.jl` package offers the function `predef_eeg` which, depending on the input, simulates continuous EEG data either for a single subject or multiple subjects. In the following, we will first provide examples for the four simulation “ingredients” mentioned above which will then be used to simulate data. @@ -86,17 +86,17 @@ design = \autoref{events_df} shows the first rows of the events data frame resulting from the experimental design that we specified. -: First five rows extracted from the events data frame representing the experimental design. Each row corresponds to one event. The columns *continuous* and *condition* display the levels of the predictor variables for the specific event and the *latency* column denotes the event onset (in samples).\label{events_df} +: First five rows extracted from the events data frame representing the experimental design. Each row corresponds to one event. The columns *continuous* and *condition* display the levels of the predictor variables for the specific event.\label{events_df} -| **continuous** | **condition** | **latency** | -|:---------------|:--------------|:------------| -| 2.22222 | face | 200 | -| 4.44444 | car | 400 | -| 3.88889 | car | 600 | -| 1.11111 | car | 800 | -| 0.555556 | car | 1000 | +| **continuous** | **condition** | +|:---------------|:--------------| +| 2.22222 | face | +| 4.44444 | car | +| 3.88889 | car | +| 1.11111 | car | +| 0.555556 | car | -2\. Next, we create a signal consisting of two different **components**. For the first component, we use the prespecified N170 base and include a condition effect of the “face/car” condition i.e. faces will have a more negative signal than cars. For the second component, we use the prespecified P300 base and include a linear and a quadratic effect of the continuous variable: the larger the value of the continuous variable, the larger the simulated potential. +2\. Next, we create a signal consisting of two different **components**. For the first component, we use the prespecified N170 base with an intercept of 5 µV and a condition effect of 3 µV for the “face/car” condition i.e. faces will have a more negative signal than cars. For the second component, we use the prespecified P300 base and include a linear and a quadratic effect of the continuous variable: the larger the value of the continuous variable, the larger the simulated potential. ```julia n1 = LinearModelComponent(; @@ -134,15 +134,15 @@ eeg_data, events_df = simulate(StableRNG(1), design, components, onset, noise) ![First 1400 samples from the simulated continuous EEG data. The vertical lines denote the event onsets and their colour represents the respective condition i.e. car or face.\label{fig_example_simulated_data}](plots/example_simulated_data.svg) -To validate the simulation results, we use the `Unfold.jl` toolbox [@ehinger2019unfold] to fit a regression model to the simulated data and examine the estimated regression parameters and marginal effects. For the formula, we include a categorical predictor for *condition* and a non-linear predictor (based on splines) for *continuous*. +To validate the simulation results, we use the `Unfold.jl` package [@ehinger2019unfold] to fit an Unfold regression model to the simulated data and examine the estimated regression parameters and marginal effects. For the formula, we include a categorical predictor for *condition* and a non-linear predictor (based on splines) for *continuous*. ```julia m = fit( UnfoldModel, - Dict( Any => ( + [Any => ( @formula(0 ~ 1 + condition + spl(continuous, 4)), firbasis(τ = [-0.1, 1], sfreq = 100, name = "basis"), - )), + )], events_df, eeg_data, ) From e17ed15f548c3b3ce841b305d2ef85a037e38ef4 Mon Sep 17 00:00:00 2001 From: jschepers Date: Thu, 1 Aug 2024 13:38:08 +0000 Subject: [PATCH 096/113] Try to fix subscript --- joss_paper/paper.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index b41580f4..e8135bf5 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -64,7 +64,7 @@ The inter-onset distribution defines the distance between events in the case of ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. -![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Panel **B** displays its log10(power) at normalized frequencies.\label{fig_noise_types}](plots/noise_types.svg) +![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Panel **B** displays its log10(power) $\text{log_{10}(power)}$ at normalized frequencies.\label{fig_noise_types}](plots/noise_types.svg) # Simulation example In this section, one can find an example of how to use `UnfoldSim.jl` to simulate continuous EEG data. Additional examples can be found in the [`UnfoldSim.jl` documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/). Moreover, to get started, the `UnfoldSim.jl` package offers the function `predef_eeg` which, depending on the input, simulates continuous EEG data either for a single subject or multiple subjects. @@ -96,7 +96,7 @@ design = | 1.11111 | car | | 0.555556 | car | -2\. Next, we create a signal consisting of two different **components**. For the first component, we use the prespecified N170 base with an intercept of 5 µV and a condition effect of 3 µV for the “face/car” condition i.e. faces will have a more negative signal than cars. For the second component, we use the prespecified P300 base and include a linear and a quadratic effect of the continuous variable: the larger the value of the continuous variable, the larger the simulated potential. +2\. Next, we create a signal consisting of two different **components**. For the first component, we use the prespecified N170 base with an intercept of 5 µV and a condition effect of 3 µV for the “face/car” condition i.e. faces will have a more negative signal than cars. For the second component, we use the prespecified P300 base and include a linear and a quadratic effect of the continuous variable: the larger the value of the continuous variable, the larger the simulated potential. ```julia n1 = LinearModelComponent(; From c603dcaffbca6ab5b4d895734e952f24e8b12262 Mon Sep 17 00:00:00 2001 From: jschepers Date: Thu, 1 Aug 2024 13:44:44 +0000 Subject: [PATCH 097/113] Try to fix subscript --- joss_paper/paper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index e8135bf5..cb79eb40 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -64,7 +64,7 @@ The inter-onset distribution defines the distance between events in the case of ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. -![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Panel **B** displays its log10(power) $\text{log_{10}(power)}$ at normalized frequencies.\label{fig_noise_types}](plots/noise_types.svg) +![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Panel **B** displays its log10(power) at normalized frequencies.\label{fig_noise_types}](plots/noise_types.svg) # Simulation example In this section, one can find an example of how to use `UnfoldSim.jl` to simulate continuous EEG data. Additional examples can be found in the [`UnfoldSim.jl` documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/). Moreover, to get started, the `UnfoldSim.jl` package offers the function `predef_eeg` which, depending on the input, simulates continuous EEG data either for a single subject or multiple subjects. From 0d8dd629aa1ef6860a9e95c2f750fcdff5481dda Mon Sep 17 00:00:00 2001 From: jschepers Date: Thu, 1 Aug 2024 13:51:29 +0000 Subject: [PATCH 098/113] Try to fix subscript --- joss_paper/paper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index cb79eb40..e9860844 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -64,7 +64,7 @@ The inter-onset distribution defines the distance between events in the case of ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. -![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Panel **B** displays its log10(power) at normalized frequencies.\label{fig_noise_types}](plots/noise_types.svg) +![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Panel **B** displays its log10(power) $\log_{10}(power)$ $\text{\log_{10}(power)}$ $\text{log}_{\text{10}}\text{(power)}$ at normalized frequencies.\label{fig_noise_types}](plots/noise_types.svg) # Simulation example In this section, one can find an example of how to use `UnfoldSim.jl` to simulate continuous EEG data. Additional examples can be found in the [`UnfoldSim.jl` documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/). Moreover, to get started, the `UnfoldSim.jl` package offers the function `predef_eeg` which, depending on the input, simulates continuous EEG data either for a single subject or multiple subjects. From 1ee776de2cfc03038a2db57c9b63f16fb4f4af23 Mon Sep 17 00:00:00 2001 From: jschepers Date: Thu, 1 Aug 2024 13:54:58 +0000 Subject: [PATCH 099/113] Try to fix subscript --- joss_paper/paper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index e9860844..0cfe459a 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -64,7 +64,7 @@ The inter-onset distribution defines the distance between events in the case of ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. -![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Panel **B** displays its log10(power) $\log_{10}(power)$ $\text{\log_{10}(power)}$ $\text{log}_{\text{10}}\text{(power)}$ at normalized frequencies.\label{fig_noise_types}](plots/noise_types.svg) +![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Panel **B** displays its $\log_{10}(power)$ $\text{\log_{10}(power)}$ $\text{log}_{\text{10}}\text{(power)}$ at normalized frequencies. \label{fig_noise_types}](plots/noise_types.svg) # Simulation example In this section, one can find an example of how to use `UnfoldSim.jl` to simulate continuous EEG data. Additional examples can be found in the [`UnfoldSim.jl` documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/). Moreover, to get started, the `UnfoldSim.jl` package offers the function `predef_eeg` which, depending on the input, simulates continuous EEG data either for a single subject or multiple subjects. From 80817752afc60568f0d4016774ea7f041ef1b391 Mon Sep 17 00:00:00 2001 From: jschepers Date: Thu, 1 Aug 2024 13:56:29 +0000 Subject: [PATCH 100/113] Try to fix subscript --- joss_paper/paper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index 0cfe459a..9802b5b9 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -64,7 +64,7 @@ The inter-onset distribution defines the distance between events in the case of ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. -![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Panel **B** displays its $\log_{10}(power)$ $\text{\log_{10}(power)}$ $\text{log}_{\text{10}}\text{(power)}$ at normalized frequencies. \label{fig_noise_types}](plots/noise_types.svg) +![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Panel **B** displays its $\text{log}_{\text{10}}\text{(power)}$ at normalized frequencies. \label{fig_noise_types}](plots/noise_types.svg) # Simulation example In this section, one can find an example of how to use `UnfoldSim.jl` to simulate continuous EEG data. Additional examples can be found in the [`UnfoldSim.jl` documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/). Moreover, to get started, the `UnfoldSim.jl` package offers the function `predef_eeg` which, depending on the input, simulates continuous EEG data either for a single subject or multiple subjects. From a6c049ea0da8408c5faea7010dcf56ee691b994f Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 5 Aug 2024 08:51:46 +0000 Subject: [PATCH 101/113] Adapt noise figure and caption --- joss_paper/create_paper_plots.jl | 19 ++++++++++--------- joss_paper/paper.md | 4 ++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/joss_paper/create_paper_plots.jl b/joss_paper/create_paper_plots.jl index 58dddc48..a5687822 100644 --- a/joss_paper/create_paper_plots.jl +++ b/joss_paper/create_paper_plots.jl @@ -57,22 +57,23 @@ let # Analysing data using Unfold m = fit( UnfoldModel, - Dict( + [ Any => ( @formula(0 ~ 1 + condition + spl(continuous, 4)), firbasis(τ = [-0.1, 1], sfreq = 100, name = "basis"), ), - ), + ], events_df, eeg_data, ) #---- - # Visualise event data frame + # Visualise design (event data frame) + events_design = generate_events(design) pretty_table( - events_df[1:5, :], + events_design[1:5, :], alignment = :l, backend = Val(:markdown), - header = names(events_df), + header = names(events_design), ) #---- # Visualise simulated data @@ -254,7 +255,7 @@ let title = "Noise samples", titlesize = 18, xlabel = "Time", - ylabel = "Amplitude", + ylabel = "Amplitude (including offset)", xlabelsize = 16, ylabelsize = 16, xgridvisible = false, @@ -274,13 +275,13 @@ let ygridvisible = false, ) - for n in [PinkNoise RedNoise WhiteNoise ExponentialNoise] + for (ix, n) in enumerate([PinkNoise RedNoise WhiteNoise ExponentialNoise]) # Generate noise samples - noisevec = simulate_noise(StableRNG(1), n(), 10000) + noisevec = simulate_noise(StableRNG(10), n(), 10000) # Plot 1000 samples - lines!(ax_A, noisevec[1:1000]; label = string(n)) + lines!(ax_A, noisevec[1:1000] .- (ix - 1) * 5; label = string(n)) # Calculate Welch periodogram perio = welch_pgram(noisevec) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index 9802b5b9..a98640e2 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -64,7 +64,7 @@ The inter-onset distribution defines the distance between events in the case of ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. -![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Panel **B** displays its $\text{log}_{\text{10}}\text{(power)}$ at normalized frequencies. \label{fig_noise_types}](plots/noise_types.svg) +![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Please note that the noise signals are shifted by 5 µV for visualisation purposes. Panel **B** displays its $\text{log}_{\text{10}}\text{(power)}$ at normalized frequencies. \label{fig_noise_types}](plots/noise_types.svg) # Simulation example In this section, one can find an example of how to use `UnfoldSim.jl` to simulate continuous EEG data. Additional examples can be found in the [`UnfoldSim.jl` documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/). Moreover, to get started, the `UnfoldSim.jl` package offers the function `predef_eeg` which, depending on the input, simulates continuous EEG data either for a single subject or multiple subjects. @@ -167,7 +167,7 @@ In contrast to these tools, `UnfoldSim.jl` has a higher-level perspective, uniqu Funded by Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) under Germany's Excellence Strategy – EXC 2075 – 390740016. The authors further thank the International Max Planck Research School for Intelligent Systems (IMPRS-IS) for supporting Judith Schepers. Moreover, the authors would like to thank Tanja Bien for her valuable feedback on the paper manuscript. # Package references -Please note that we only mention the main dependencies of the toolbox here, but the dependencies of the dependencies can be found in the respective `Manifest.toml` files. Furthermore, please note that we only list rather than cite the packages for which we could not find any citation file or instruction. +Please note that we only mention the main dependencies of the package here, but the dependencies of the dependencies can be found in the respective `Manifest.toml` files. Furthermore, please note that we only list rather than cite the packages for which we could not find any citation file or instruction. **Julia** [@Julia-2017] **DataFrames.jl** [@JSSv107i04] From d8dcb9d81231476845fcb057893aa5f8925eba6a Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 5 Aug 2024 08:56:26 +0000 Subject: [PATCH 102/113] (auto) add joss paper plots --- joss_paper/plots/noise_types.svg | 815 +++++++++++++++++-------------- 1 file changed, 447 insertions(+), 368 deletions(-) diff --git a/joss_paper/plots/noise_types.svg b/joss_paper/plots/noise_types.svg index 6ce113d1..df63878e 100644 --- a/joss_paper/plots/noise_types.svg +++ b/joss_paper/plots/noise_types.svg @@ -2,868 +2,947 @@ - + - + - + - - - - - - - - - - + - - - - + + - + - - + + - - + + - - + + - - + + + + + - + - - + + - - + + - - + + - + - - + + - + + + + - + - + - + - + - + - + - + - + - + - - - - + - + - - - - + - + - + - - + + + + + + + + + + + + + + + + - + - + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + - + + + + - + + + + + + + - + - + - - - - + - + - + - + + + + - + - + - + + + + + - + - + + + + + + + + + - + - + - - - - - - - - + + - + - + - + - + + + + - - + + + + + + + + + + - + + + + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - + - - - - - - - + - + - + - + - + - + - + - + - + - + - + - - + + - - + + - - + + - - + + + + + + + + - + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - - + - + - + - + - + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - + + + + - + - + - - - - - - - - - - - - - - + + + + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - - - - + + + + + + + From ae032b506ec49e0f9c64ecfc046c0945c930fbcc Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 5 Aug 2024 12:02:13 +0000 Subject: [PATCH 103/113] adapted figure size --- joss_paper/paper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index a98640e2..303cfb97 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -59,7 +59,7 @@ To generate complex activations, it is possible to specify a vector of `<:Abstra The inter-onset distribution defines the distance between events in the case of a continuous EEG. Currently, `UniformOnset` and `LogNormalOnset` are implemented. By specifying the parameters of the inter-onset distribution, one indirectly controls the amount of overlap between two or more event-related responses. \autoref{fig_onset_distributions} illustrates the parameterization of the two implemented onset distributions. -![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg) +![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="10pt"} ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. From c966897586ffb24b76f703b0723693b6db8b3522 Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 5 Aug 2024 12:14:50 +0000 Subject: [PATCH 104/113] adapted figure size --- joss_paper/paper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index 303cfb97..13128114 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -59,7 +59,7 @@ To generate complex activations, it is possible to specify a vector of `<:Abstra The inter-onset distribution defines the distance between events in the case of a continuous EEG. Currently, `UniformOnset` and `LogNormalOnset` are implemented. By specifying the parameters of the inter-onset distribution, one indirectly controls the amount of overlap between two or more event-related responses. \autoref{fig_onset_distributions} illustrates the parameterization of the two implemented onset distributions. -![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="10pt"} +![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="100pt"} ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. From 783be2f0d33a6489ed16f68b7b8c54d5d69197a4 Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 5 Aug 2024 12:17:56 +0000 Subject: [PATCH 105/113] adapted figure size --- joss_paper/paper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index 13128114..9a67532b 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -59,7 +59,7 @@ To generate complex activations, it is possible to specify a vector of `<:Abstra The inter-onset distribution defines the distance between events in the case of a continuous EEG. Currently, `UniformOnset` and `LogNormalOnset` are implemented. By specifying the parameters of the inter-onset distribution, one indirectly controls the amount of overlap between two or more event-related responses. \autoref{fig_onset_distributions} illustrates the parameterization of the two implemented onset distributions. -![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="100pt"} +![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="300pt"} ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. From bc57c9b5d3f6483897f4e12c2f012e9810576bd3 Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 5 Aug 2024 12:20:10 +0000 Subject: [PATCH 106/113] adapted figure size --- joss_paper/paper.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index 9a67532b..3b03bf76 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -59,12 +59,12 @@ To generate complex activations, it is possible to specify a vector of `<:Abstra The inter-onset distribution defines the distance between events in the case of a continuous EEG. Currently, `UniformOnset` and `LogNormalOnset` are implemented. By specifying the parameters of the inter-onset distribution, one indirectly controls the amount of overlap between two or more event-related responses. \autoref{fig_onset_distributions} illustrates the parameterization of the two implemented onset distributions. -![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="300pt"} +![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="250pt"} ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. -![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Please note that the noise signals are shifted by 5 µV for visualisation purposes. Panel **B** displays its $\text{log}_{\text{10}}\text{(power)}$ at normalized frequencies. \label{fig_noise_types}](plots/noise_types.svg) +![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Please note that the noise signals are shifted by 5 µV for visualisation purposes. Panel **B** displays its $\text{log}_{\text{10}}\text{(power)}$ at normalized frequencies. \label{fig_noise_types}](plots/noise_types.svg){height="250pt"} # Simulation example In this section, one can find an example of how to use `UnfoldSim.jl` to simulate continuous EEG data. Additional examples can be found in the [`UnfoldSim.jl` documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/). Moreover, to get started, the `UnfoldSim.jl` package offers the function `predef_eeg` which, depending on the input, simulates continuous EEG data either for a single subject or multiple subjects. From 9bf8d6b7412c4f1cd3db82956c6a28822b9c7965 Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 5 Aug 2024 12:23:48 +0000 Subject: [PATCH 107/113] adapted figure size --- joss_paper/paper.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index 3b03bf76..41875664 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -64,7 +64,7 @@ The inter-onset distribution defines the distance between events in the case of ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. -![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Please note that the noise signals are shifted by 5 µV for visualisation purposes. Panel **B** displays its $\text{log}_{\text{10}}\text{(power)}$ at normalized frequencies. \label{fig_noise_types}](plots/noise_types.svg){height="250pt"} +![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Please note that the noise signals are shifted by 5 µV for visualisation purposes. Panel **B** displays its $\text{log}_{\text{10}}\text{(power)}$ at normalized frequencies. \label{fig_noise_types}](plots/noise_types.svg){height="230pt"} # Simulation example In this section, one can find an example of how to use `UnfoldSim.jl` to simulate continuous EEG data. Additional examples can be found in the [`UnfoldSim.jl` documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/). Moreover, to get started, the `UnfoldSim.jl` package offers the function `predef_eeg` which, depending on the input, simulates continuous EEG data either for a single subject or multiple subjects. @@ -132,7 +132,7 @@ Finally, we combine all the ingredients and simulate data (see \autoref{fig_exam eeg_data, events_df = simulate(StableRNG(1), design, components, onset, noise) ``` -![First 1400 samples from the simulated continuous EEG data. The vertical lines denote the event onsets and their colour represents the respective condition i.e. car or face.\label{fig_example_simulated_data}](plots/example_simulated_data.svg) +![First 1400 samples from the simulated continuous EEG data. The vertical lines denote the event onsets and their colour represents the respective condition i.e. car or face.\label{fig_example_simulated_data}](plots/example_simulated_data.svg){height="250pt"} To validate the simulation results, we use the `Unfold.jl` package [@ehinger2019unfold] to fit an Unfold regression model to the simulated data and examine the estimated regression parameters and marginal effects. For the formula, we include a categorical predictor for *condition* and a non-linear predictor (based on splines) for *continuous*. @@ -150,7 +150,7 @@ m = fit( In subplot A of \autoref{fig_example_coefficients_effects}, one can see the model estimates for the different coefficients and as intended there is a condition effect in the first negative component and an effect of the continuous variable on the second (positive) component. The relation between the levels of the continuous variable and the scaling of the second component is even clearer visible in subplot B of \autoref{fig_example_coefficients_effects} which depicts the estimated marginal effects of the predictors. Instead of showing the regression coefficients, we can evaluate the estimated function at specific values of the continuous variable. -![Regression results for the simulated data. Panel **A** displays the estimated regression coefficients over time. Panel **B** shows the estimated marginal effects i.e. the estimated event-related potential at different predictor levels.\label{fig_example_coefficients_effects}](plots/example_coefficients_effects.svg) +![Regression results for the simulated data. Panel **A** displays the estimated regression coefficients over time. Panel **B** shows the estimated marginal effects i.e. the estimated event-related potential at different predictor levels.\label{fig_example_coefficients_effects}](plots/example_coefficients_effects.svg){height="250pt"} As shown in this example, `UnfoldSim.jl` and `Unfold.jl` can be easily combined to investigate the effects of certain features, e.g. the type of noise or its intensity on the analysis result and thereby assess the robustness of the analysis. From ff86cda9c171767e77a98899c9acdae25cc950ca Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 5 Aug 2024 12:27:03 +0000 Subject: [PATCH 108/113] adapted figure size --- joss_paper/paper.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index 41875664..c233e0e8 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -59,12 +59,12 @@ To generate complex activations, it is possible to specify a vector of `<:Abstra The inter-onset distribution defines the distance between events in the case of a continuous EEG. Currently, `UniformOnset` and `LogNormalOnset` are implemented. By specifying the parameters of the inter-onset distribution, one indirectly controls the amount of overlap between two or more event-related responses. \autoref{fig_onset_distributions} illustrates the parameterization of the two implemented onset distributions. -![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="250pt"} +![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="230pt"} ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. -![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Please note that the noise signals are shifted by 5 µV for visualisation purposes. Panel **B** displays its $\text{log}_{\text{10}}\text{(power)}$ at normalized frequencies. \label{fig_noise_types}](plots/noise_types.svg){height="230pt"} +![Illustration of the different noise types (indicated by colour). Panel **A** shows the noise over time. Please note that the noise signals are shifted by 5 µV for visualisation purposes. Panel **B** displays its $\text{log}_{\text{10}}\text{(power)}$ at normalized frequencies. \label{fig_noise_types}](plots/noise_types.svg){height="250pt"} # Simulation example In this section, one can find an example of how to use `UnfoldSim.jl` to simulate continuous EEG data. Additional examples can be found in the [`UnfoldSim.jl` documentation](https://unfoldtoolbox.github.io/UnfoldSim.jl/dev/). Moreover, to get started, the `UnfoldSim.jl` package offers the function `predef_eeg` which, depending on the input, simulates continuous EEG data either for a single subject or multiple subjects. From 1f808327194d8f69255d941b6b8aea4676422cc6 Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 5 Aug 2024 12:32:34 +0000 Subject: [PATCH 109/113] adapted figure size --- joss_paper/paper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index c233e0e8..9e2f6206 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -59,7 +59,7 @@ To generate complex activations, it is possible to specify a vector of `<:Abstra The inter-onset distribution defines the distance between events in the case of a continuous EEG. Currently, `UniformOnset` and `LogNormalOnset` are implemented. By specifying the parameters of the inter-onset distribution, one indirectly controls the amount of overlap between two or more event-related responses. \autoref{fig_onset_distributions} illustrates the parameterization of the two implemented onset distributions. -![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="230pt"} +![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="220pt"} ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. From e96ef9cf7b1e82ea392adc64c1efb0e03e608116 Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 5 Aug 2024 12:34:56 +0000 Subject: [PATCH 110/113] adapted figure size --- joss_paper/paper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index 9e2f6206..b9108747 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -59,7 +59,7 @@ To generate complex activations, it is possible to specify a vector of `<:Abstra The inter-onset distribution defines the distance between events in the case of a continuous EEG. Currently, `UniformOnset` and `LogNormalOnset` are implemented. By specifying the parameters of the inter-onset distribution, one indirectly controls the amount of overlap between two or more event-related responses. \autoref{fig_onset_distributions} illustrates the parameterization of the two implemented onset distributions. -![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="220pt"} +![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="200pt"} ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. From c46f58f9f0d67e490616be2288cfd4ce50fa9562 Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 5 Aug 2024 12:37:36 +0000 Subject: [PATCH 111/113] adapted figure size --- joss_paper/paper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index b9108747..592ed424 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -59,7 +59,7 @@ To generate complex activations, it is possible to specify a vector of `<:Abstra The inter-onset distribution defines the distance between events in the case of a continuous EEG. Currently, `UniformOnset` and `LogNormalOnset` are implemented. By specifying the parameters of the inter-onset distribution, one indirectly controls the amount of overlap between two or more event-related responses. \autoref{fig_onset_distributions} illustrates the parameterization of the two implemented onset distributions. -![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="200pt"} +![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="150pt"} ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. From 74985f1be1cf2b032bb91097e0ca8cafd37504ce Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 5 Aug 2024 12:42:10 +0000 Subject: [PATCH 112/113] adapted figure size --- joss_paper/paper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index 592ed424..d5f3b0dd 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -59,7 +59,7 @@ To generate complex activations, it is possible to specify a vector of `<:Abstra The inter-onset distribution defines the distance between events in the case of a continuous EEG. Currently, `UniformOnset` and `LogNormalOnset` are implemented. By specifying the parameters of the inter-onset distribution, one indirectly controls the amount of overlap between two or more event-related responses. \autoref{fig_onset_distributions} illustrates the parameterization of the two implemented onset distributions. -![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="150pt"} +![Illustration of the inter-onset distributions. The colours indicate different sets of parameter values. Please note that for the lognormal distribution, the parameters are defined on a logarithmic scale, while the distribution is shown on a linear scale. \label{fig_onset_distributions}](plots/onset_distributions.svg){height="180pt"} ## Noise types UnfoldSim.jl offers different noise types: `WhiteNoise`, `RedNoise`, `PinkNoise` and exponentially decaying autoregressive noise (`ExponentialNoise`) (see \autoref{fig_noise_types}). In the future, we will add simple autoregressive noise and noise based on actual EEG data. From 075c086d54c716157a3e788c4b435eb43d68ec2e Mon Sep 17 00:00:00 2001 From: jschepers Date: Mon, 5 Aug 2024 15:19:58 +0000 Subject: [PATCH 113/113] adapt date --- joss_paper/paper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joss_paper/paper.md b/joss_paper/paper.md index d5f3b0dd..5183ae1a 100644 --- a/joss_paper/paper.md +++ b/joss_paper/paper.md @@ -29,7 +29,7 @@ affiliations: index: 1 - name: Stuttgart Center for Simulation Science, University of Stuttgart, Germany index: 2 -date: 19 February 2024 +date: 5 August 2024 bibliography: paper.bib ---