Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ticks and toposeries animations #298

Merged
merged 44 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
349f45b
fix joss bug?
vladdez Dec 17, 2024
b1f3f27
Merge branch 'main' of https://github.com/unfoldtoolbox/UnfoldMakie.jl
vladdez Dec 17, 2024
ca4b023
ticks on x, y cand colorbar are corrected
vladdez Jan 14, 2025
14565d6
Update src/plot_erpimage.jl
vladdez Jan 14, 2025
b90c879
Update src/plot_erpimage.jl
vladdez Jan 14, 2025
03659de
up Unfold and tests format
vladdez Jan 14, 2025
35742b1
bug in significance
vladdez Jan 14, 2025
d38bd5d
Merge branch 'main' into uncertain
vladdez Jan 14, 2025
981fa63
switch from colorrange to limits in toposeries in colorbar
vladdez Jan 14, 2025
76e13e2
extracting cutting_management into separate function
vladdez Jan 14, 2025
e79ff61
more lines into add function + rounding of lables
vladdez Jan 14, 2025
d3bdf6c
Merge branch 'main' into uncertain
vladdez Jan 14, 2025
ada6eb7
Update src/plot_topoplotseries.jl
vladdez Jan 14, 2025
4755863
Update src/plot_topoplotseries.jl
vladdez Jan 14, 2025
5a95e53
bug
vladdez Jan 15, 2025
ee22e6a
solved jumping colorbar in plot_topoplot
vladdez Jan 15, 2025
338931b
further lintering
vladdez Jan 15, 2025
7520999
issue 223 workaround
vladdez Jan 15, 2025
9200775
adding in docs info about uncertainty visualisation for toposeries
vladdez Jan 17, 2025
4d5a569
bugs in docs
vladdez Jan 17, 2025
187593a
gif added
vladdez Jan 17, 2025
72c6753
gif added 2
vladdez Jan 17, 2025
7e0dc61
bootstrapped animation
vladdez Jan 20, 2025
c37ed6a
moved data generation into example_data
vladdez Jan 20, 2025
81496e9
explaining text
vladdez Jan 20, 2025
25af12d
noice twiggle
vladdez Jan 20, 2025
e3e1baa
bug
vladdez Jan 21, 2025
4dae9f4
no contours
vladdez Jan 21, 2025
02a555f
adding Animations
vladdez Jan 21, 2025
46bb126
just for ease of local testing
vladdez Jan 22, 2025
ad4d2ac
update Makie Project and solve the bug
vladdez Jan 22, 2025
6187904
issue 223 finally solved
vladdez Jan 23, 2025
169726e
Update docs/literate/how_to/uncertain_topo.jl
vladdez Jan 24, 2025
5c019fd
Update docs/literate/intro/speed.jl
vladdez Jan 24, 2025
8c6072a
comments: easy
vladdez Jan 24, 2025
31c091f
correcting bootstrapping data and implementation of easing
vladdez Jan 24, 2025
679e248
del Revise
vladdez Jan 24, 2025
958e502
bug
vladdez Jan 24, 2025
04e8a16
Update src/plot_erp.jl
vladdez Jan 24, 2025
d5ab508
renaming xlables to topoplot_xlables
vladdez Jan 24, 2025
069f159
bug
vladdez Jan 27, 2025
6343b65
topolabels_rounding
vladdez Jan 27, 2025
616885b
colorrange is now adjustable in plot_erpplot
vladdez Jan 27, 2025
a4d2497
after final revision
vladdez Jan 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[deps]
AlgebraOfGraphics = "cbdf2221-f076-402e-a563-3d30da359d67"
Animations = "27a7e980-b3e6-11e9-2bcd-0b925532e340"
BSplineKit = "093aae92-e908-43d7-9660-e50ee39d5a0a"
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
Expand All @@ -11,9 +12,9 @@ DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
DataFramesMeta = "1313f7d8-7da2-5740-9ea0-a2ca25f37964"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
FFMPEG_jll = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
Glob = "c27321d9-0574-5035-807b-f59d2c89b15c"
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
MakieThemes = "e296ed71-da82-5faf-88ab-0034a9761098"
Observables = "510215fc-4207-5dde-b226-833fc4488ee2"
PyMNE = "6c5003b2-cbe8-491c-a0d1-70088e6a0fd6"
Expand All @@ -28,6 +29,8 @@ UnfoldSim = "ed8ae6d2-84d3-44c6-ab46-0baf21700804"
XML2_jll = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"

[compat]
AlgebraOfGraphics = "0.7, 0.8"
AlgebraOfGraphics = "0.8"
BSplineKit = "0.16, 0.17"
CairoMakie = "0.11, 0.12, 0.13"
Colors = "0.12, 0.13"
Makie = "0.21, 0.22"
47 changes: 47 additions & 0 deletions docs/example_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,53 @@ function example_data(example = "TopoPlots.jl")
dat_e = dat_e[1, :, :]
#evts = filter(row -> row.Δlatency > 0, evts)
return dat_e, evts, times
elseif example == "bootstrap_toposeries"
trials = 50
time_padding = 100
component = n400()
design = UnfoldSim.SingleSubjectDesign(conditions = Dict(:condA => ["levelA"])) # design with one condition
design = UnfoldSim.RepeatDesign(design, trials)
generate_events(design)

time1 = vcat(rand(time_padding), component) # 500 msec = randiom 100 msec and 400 msec of n400
c = UnfoldSim.LinearModelComponent(;
basis = time1,
formula = @formula(0 ~ 1),
β = [1],
)

hart = headmodel(type = "hartmut") # 227 electrodes
less_hart = magnitude(hart)[:, 1] # extract 1 lead field and 64 electrodes

mc = UnfoldSim.MultichannelComponent(c, less_hart)

# simulation of 3d matrix
onset = UniformOnset(; width = 20, offset = 4)
dat, events = simulate(
MersenneTwister(1),
design,
mc,
onset,
PinkNoise(noiselevel = 10.5),
return_epoched = true,
)

# Create the DataFrame
#= Unfold.result_to_table(eff::Vector{<:AbstractArray}, events::Vector{<:DataFrame},
times::Vector, eventnames::Vector)
Unfold.result_to_table(rand(5,11,13), [DataFrame(:trial=>1:13)], [1:11], ["myevent"]) =#
df_toposeries = Unfold.result_to_table(
dat,
[DataFrame(:trial => 1:size(dat, 3))],
[1:size(dat, 2)],
["myevent"],
)
rename!(df_toposeries, :yhat => :estimate)
# chosing positions
pos3d = hart.electrodes["pos"]
pos2d = to_positions(pos3d')
pos_toposeries = [Point2f(p[1] + 0.5, p[2] + 0.5) for p in pos2d]
return df_toposeries, pos_toposeries
elseif example == "raw_ch_names"
return [
"FP1",
Expand Down
6 changes: 1 addition & 5 deletions docs/literate/how_to/mult_vis_in_fig.jl
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,7 @@ plot_erp!(
legend = (; nbanks = 2),
)

plot_parallelcoordinates(
f[3, 2:3],
uf_5chan;
mapping = (; color = :coefname),
)
plot_parallelcoordinates(f[3, 2:3], uf_5chan; mapping = (; color = :coefname))

plot_erpimage!(f[1, 4:5], times, d_singletrial)
plot_circular_topoplots!(
Expand Down
165 changes: 165 additions & 0 deletions docs/literate/how_to/uncertain_topo.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# ```@raw html
# <details>
# <summary>Click to expand</summary>
# ```
using Unfold
using UnfoldMakie
using UnfoldSim
using DataFrames
using CairoMakie
using TopoPlots
using Statistics
using Random
using Animations

# ```@raw html
# </details >
# ```
# Representing uncertainty is one of the most difficult tasks in visualization. It is especially difficult for heatmaps and topoplots.
# Here we will present new ways to show uncertainty for topoplots series.

# Uncertainty in EEG data usually comes from subjects and trials:
# 1) Subjects may vary by phsiological or behavioral features;
# 2) Something can change between trials (electrode connection can got worse etc.).

# Data input
include("../../../example_data.jl")
dat, positions = TopoPlots.example_data()
df = UnfoldMakie.eeg_array_to_dataframe(dat[:, :, 1], string.(1:length(positions)));
df_uncert = UnfoldMakie.eeg_array_to_dataframe(dat[:, :, 2], string.(1:length(positions)));

# Generate data with 227 channels, 50 trials, 500 mseconds for bootstrapping
df_toposeries, pos_toposeries = example_data("bootstrap_toposeries");
df_toposeries = df_toposeries[df_toposeries.trial.<=15, :];


# # Uncertainty via additional row
# In this case we alread have two datasets: `df` with mean estimates and `df_uncert` with variability estimation.

f = Figure()
plot_topoplotseries!(
f[1, 1],
df;
bin_num = 5,
positions = positions,
axis = (; xlabel = ""),
colorbar = (; label = "Voltage estimate"),
)
plot_topoplotseries!(
f[2, 1],
df_uncert;
bin_num = 5,
positions = positions,
visual = (; colormap = :viridis),
axis = (; xlabel = "50 ms"),
colorbar = (; label = "Voltage uncertainty"),
)
f

# # Uncertainty via animation

# In this case, we need to boostrap the data, so we'll use raw data with single trials.

# To show the uncertainty of the estimate, we will compute 10 different means of the boostrapped data.
# More specifically: 1) create N boostrapped data sets using random sampling with replacement across trials; 2) compute their means; 3) do a toposeries animation iterating over these means.

# ```@raw html
# <details>
# <summary>Click to expand for supportive functions</summary>
# ```
# With this function we will bootstrap the data.
# `rng` - random number generated. Be sure to send the same rng from outside the function.
bootstrap_toposeries(df; kwargs...) = bootstrap_toposeries(MersenneTwister(), df; kwargs...)
function bootstrap_toposeries(rng::AbstractRNG, df)
df1 = groupby(df, [:time, :channel])
len_estimate = length(df1[1].estimate)
bootstrap_ix = rand(rng, 1:len_estimate, len_estimate) # random sample with replacement
tmp = vcat([d.estimate[bootstrap_ix] for d in df1]...)
df1 = DataFrame(df1)

df1.estimate .= tmp
return df1
end

# function for easing - smooth transition between frames in animation.
# `update_ratio` - transition ratio between time1 and time2.
# `at` - create animation object: 0 and 1 are time points, old and new are data vectors.

function ease_between(new, old, update_ratio; easing_function = sineio())

anim = Animation(0, old, 1, new; defaulteasing = easing_function)
return at(anim, update_ratio)
end
# ```@raw html
# </details >
# ```

dat_obs = Observable(df_toposeries)
f = Figure()
plot_topoplotseries!(
f[1, 1],
dat_obs;
bin_num = 5,
nrows = 2,
positions = pos_toposeries,
axis = (; xlabel = "Time [msec]"),
)

# Basic toposeries
record(f, "bootstrap_toposeries.mp4"; framerate = 2) do io
for i = 1:10
dat_obs[] = bootstrap_toposeries(df_toposeries)
recordframe!(io)
end
end;
# ![](bootstrap_toposeries.mp4)

# Toposeries without contour
dat_obs = Observable(df_toposeries)
f = Figure()
plot_topoplotseries!(
f[1, 1],
dat_obs;
bin_num = 5,
nrows = 2,
positions = pos_toposeries,
visual = (; contours = false),
axis = (; xlabel = "Time [msec]"),
)
record(f, "bootstrap_toposeries_nocontours.mp4"; framerate = 2) do io
for i = 1:10
dat_obs[] = bootstrap_toposeries(df_toposeries)
recordframe!(io)
end
end;
# ![](bootstrap_toposeries_nocontours.mp4)

# Toposeries with easing (smooth transition between frames)
dat_obs = Observable(df_toposeries)
f = Figure()
plot_topoplotseries!(
f[1, 1],
dat_obs;
bin_num = 5,
nrows = 2,
positions = pos_toposeries,
visual = (; contours = false),
axis = (; xlabel = "Time [msec]"),
)
record(f, "bootstrap_toposeries_easing.mp4"; framerate = 10) do io
rng = MersenneTwister(1)
for n_bootstrapping = 1:10
recordframe!(io)
new_df = bootstrap_toposeries(rng, df_toposeries)
old_estimate = dat_obs.val.estimate
for update_ratio in range(0, 1, length = 8)
#@show n_bootstrapping update_ratio
dat_obs.val.estimate .=
ease_between(new_df.estimate, old_estimate, update_ratio)
notify(dat_obs)
recordframe!(io)
end
end
end;

# ![](bootstrap_toposeries_easing.mp4)
12 changes: 7 additions & 5 deletions docs/literate/intro/code_principles.jl
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# # Code principles


# Here we will write about principles which we developed through our publication.
# Here we will write about coding principles which we developed through our [publication](https://apertureneuro.org/article/116386-the-art-of-brainwaves-a-survey-on-event-related-potential-visualization-practices):

# - Code should be clear and concise.
# - Variables inside the code should have meaningful names.
# - Every function exposed to the user should have documentation that specifies all parameters, types, input and output arguments.
# - Most people will not look at the defaults, so it is very important to nudge users to label important details of the plot.
# - Variables in the code should have meaningful names.
# - Every function exposed to the user should have documentation that specifies all parameters, types, and input and output arguments.
# - Most people will not look at the defaults, so it is very important to encourage users to label important details of the representation.
# - Function naming should be based on some theory and naming conventions.
# - You should avoid functions longer 50 lines.
# - You should avoid functions longer than 50 lines.
# - You should avoid putting more than 5 functions in a file.
# - You should avoid pull requests with more than 10 affected files.
4 changes: 3 additions & 1 deletion docs/literate/intro/speed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ end
# ![](topoplot_animation_UM.gif)

# MNE with .gif
# Note that due to some bugs in (probably) `CondaPkg` topoplot is blac and white.

@benchmark begin
fig, anim = simulated_epochs.animate_topomap(
times = Py(timestamps),
Expand All @@ -117,4 +117,6 @@ end
anim.save("topomap_animation_mne.gif", writer = "ffmpeg", fps = framerate)
end

# Note, that due to some bugs in (probably) `PythonCall` topoplot is black and white.

# ![](topomap_animation_mne.gif)
35 changes: 32 additions & 3 deletions docs/literate/tutorials/channel_image.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,38 @@ include("../../../example_data.jl")
data, pos = TopoPlots.example_data()
data = data[:, :, 1]
pos = pos[1:30]
raw_ch_names = ["FP1", "F3", "F7", "FC3", "C3", "C5", "P3", "P7", "P9", "PO7",
"PO3", "O1", "Oz", "Pz", "CPz", "FP2", "Fz", "F4", "F8", "FC4", "FCz", "Cz",
"C4", "C6", "P4", "P8", "P10", "PO8", "PO4", "O2"]
raw_ch_names = [
"FP1",
"F3",
"F7",
"FC3",
"C3",
"C5",
"P3",
"P7",
"P9",
"PO7",
"PO3",
"O1",
"Oz",
"Pz",
"CPz",
"FP2",
"Fz",
"F4",
"F8",
"FC4",
"FCz",
"Cz",
"C4",
"C6",
"P4",
"P8",
"P10",
"PO8",
"PO4",
"O2",
]


plot_channelimage(data[1:30, :], pos, raw_ch_names;)
Expand Down
Loading
Loading