Speed measurement

Here we will compare the speed of plotting UnfoldMakie with MNE (Python) and EEGLAB (MATLAB).

Three cases are measured:

  • Single topoplot
  • Topoplot series with 50 topoplots
  • Topoplott animation with 50 timestamps

Note that the results of benchmarking on your computer and on Github may differ.

using UnfoldMakie
using TopoPlots
using PyMNE
using PythonPlot
using BenchmarkTools
using Observables
using CairoMakie

Data input

dat, positions = TopoPlots.example_data()
df = UnfoldMakie.eeg_array_to_dataframe(dat[:, :, 1], string.(1:length(positions)));

Topoplots

UnfoldMakie.jl

@benchmark plot_topoplot(dat[:, 320, 1]; positions = positions)
BenchmarkTools.Trial: 127 samples with 1 evaluation.
 Range (minmax):  35.070 ms342.629 ms   GC (min … max): 0.00% … 88.62%
 Time  (median):     35.954 ms                GC (median):    0.00%
 Time  (mean ± σ):   39.293 ms ±  27.339 ms   GC (mean ± σ):  6.08% ±  7.86%

  ▁█                                                           
  ██▇▃▃▃▂▃▄▁▁▁▁▁▁▁▁▁▂▁▁▁▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▂▁▂ ▂
  35.1 ms         Histogram: frequency by time         60.3 ms <

 Memory estimate: 9.22 MiB, allocs estimate: 134210.

UnfoldMakie.jl with DelaunayMesh

@benchmark plot_topoplot(
    dat[:, 320, 1];
    positions = positions,
    topo_interpolation = (; interpolation = DelaunayMesh()),
)
BenchmarkTools.Trial: 134 samples with 1 evaluation.
 Range (minmax):  35.645 ms72.701 ms   GC (min … max): 0.00% … 0.00%
 Time  (median):     36.892 ms               GC (median):    0.00%
 Time  (mean ± σ):   37.530 ms ±  3.602 ms   GC (mean ± σ):  0.00% ± 0.00%

   █▁▂                                                       
  █████▇▅▇▆▃▃▄▄▃▁▁▁▁▁▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▃▁▁▁▁▁▁▁▁▁▁▃ ▃
  35.6 ms         Histogram: frequency by time        51.6 ms <

 Memory estimate: 9.22 MiB, allocs estimate: 134217.

MNE

posmat = collect(reduce(hcat, [[p[1], p[2]] for p in positions])')
pypos = Py(posmat).to_numpy()
pydat = Py(dat[:, 320, 1])

@benchmark begin
    f = PythonPlot.figure()
    PyMNE.viz.plot_topomap(
        pydat,
        pypos,
        sphere = 1.1,
        extrapolate = "box",
        cmap = "RdBu_r",
        sensors = false,
        contours = 6,
    )
    f.show()
end
BenchmarkTools.Trial: 252 samples with 1 evaluation.
 Range (minmax):  13.847 ms536.151 ms   GC (min … max): 0.00% … 0.00%
 Time  (median):     14.146 ms                GC (median):    0.00%
 Time  (mean ± σ):   19.871 ms ±  49.661 ms   GC (mean ± σ):  0.00% ± 0.00%

  ██▆▆                                                         
  ████▇▄▁▆▄▆▄▁▁▄▁▁▁▁▁▄▁▁▁▄▁▁▆▁▆▁▁▁▁▁▁▁▁▁▄▁▁▁▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▄ ▆
  13.8 ms       Histogram: log(frequency) by time      24.3 ms <

 Memory estimate: 4.27 KiB, allocs estimate: 134.

Topoplot series

Note that UnfoldMakie and MNE have different defaults for displaying topoplot series. UnfoldMakie in plot_topoplot averages over time samples. MNE in plot_topopmap displays single samples without averaging.

UnfoldMakie.jl

@benchmark begin
    plot_topoplotseries(
        df;
        bin_num = 50,
        positions = positions,
        axis = (; xlabel = "Time windows [s]"),
    )
end
BenchmarkTools.Trial: 3 samples with 1 evaluation.
 Range (minmax):  1.877 s  2.036 s   GC (min … max): 0.00% … 0.00%
 Time  (median):     1.924 s               GC (median):    0.00%
 Time  (mean ± σ):   1.946 s ± 81.456 ms   GC (mean ± σ):  0.00% ± 0.00%

   ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ ▁
  1.88 s         Histogram: frequency by time        2.04 s <

 Memory estimate: 414.11 MiB, allocs estimate: 5851615.

MNE

easycap_montage = PyMNE.channels.make_standard_montage("standard_1020")
ch_names = pyconvert(Vector{String}, easycap_montage.ch_names)[1:64]
info = PyMNE.create_info(PyList(ch_names), ch_types = "eeg", sfreq = 1)
info.set_montage(easycap_montage)
simulated_epochs = PyMNE.EvokedArray(Py(dat[:, :, 1]), info)

@benchmark simulated_epochs.plot_topomap(1:50)
BenchmarkTools.Trial: 5 samples with 1 evaluation.
 Range (minmax):  813.506 ms   2.025 s   GC (min … max): 0.00% … 0.00%
 Time  (median):     821.387 ms                GC (median):    0.00%
 Time  (mean ± σ):      1.061 s ± 538.959 ms   GC (mean ± σ):  0.00% ± 0.00%

                                                                
  ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▆ ▁
  814 ms           Histogram: frequency by time          2.03 s <

 Memory estimate: 3.25 KiB, allocs estimate: 111.

MATLAB

Running MATLAB on a GitHub Action is not easy. So we benchmarked three consecutive executions (on a screenshot) on a server with an AMD EPYC 7452 32-core processor. Note that Github and the server we used for MATLAB benchmarking are two different computers, which can give different timing results.

Animation

The main advantage of Julia is the speed with which the figures are updated.

timestamps = range(1, 50, step = 1)
framerate = 50
50

UnfoldMakie with .gif

@benchmark begin
    f = Makie.Figure()
    dat_obs = Observable(dat[:, 1, 1])
    plot_topoplot!(f[1, 1], dat_obs, positions = positions)
    record(f, "topoplot_animation_UM.gif", timestamps; framerate = framerate) do t
        dat_obs[] = @view(dat[:, t, 1])
    end
end
BenchmarkTools.Trial: 1 sample with 1 evaluation.
 Single result which took 5.564 s (0.00% GC) to evaluate,
 with a memory estimate of 1.66 GiB, over 17695024 allocations.

MNE with .gif

@benchmark begin
    fig, anim = simulated_epochs.animate_topomap(
        times = Py(timestamps),
        frame_rate = framerate,
        blit = false,
        image_interp = "cubic", # same as CloughTocher
    )
    anim.save("topomap_animation_mne.gif", writer = "ffmpeg", fps = framerate)
end
BenchmarkTools.Trial: 1 sample with 1 evaluation.
 Single result which took 8.987 s (0.00% GC) to evaluate,
 with a memory estimate of 4.18 KiB, over 158 allocations.


This page was generated using Literate.jl.