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 BenchmarkTools
using Observables
using CairoMakie
using PythonPlot;
using PyMNE;
    CondaPkg Found dependencies: /home/runner/.julia/packages/CondaPkg/0UqYV/CondaPkg.toml
    CondaPkg Found dependencies: /home/runner/.julia/packages/PyMNE/cNGDN/CondaPkg.toml
    CondaPkg Found dependencies: /home/runner/.julia/packages/PythonCall/83z4q/CondaPkg.toml
    CondaPkg Found dependencies: /home/runner/.julia/packages/PythonPlot/oS8x4/CondaPkg.toml
    CondaPkg Resolving changes
             + libstdcxx
             + libstdcxx-ng
             + matplotlib
             + mne (pip)
             + openssl
             + python
             + uv
    CondaPkg Creating environment
             │ /home/runner/.julia/artifacts/7c4ad8f2d391c6f8127c0c0f65c9ec85b80e5837/bin/micromamba
             │ -r /home/runner/.julia/scratchspaces/0b3b1443-0f03-428d-bdfb-f27f9c1191ea/root
             │ create
             │ -y
             │ -p /home/runner/work/UnfoldMakie.jl/UnfoldMakie.jl/docs/.CondaPkg/env
             │ --override-channels
             │ libstdcxx[version='>=3.4,<15.0']
             │ libstdcxx-ng[version='>=3.4,<15.0']
             │ matplotlib[version='>=1']
             │ openssl[version='>=3, <3.6']
             │ python[version='>=3.10,!=3.14.0,!=3.14.1,<4, >=3.4,<4',channel='conda-forge',build='*cp*']
             │ uv[version='>=0.4']
             │ -c conda-forge
             └ -c anaconda

Transaction

  Prefix: /home/runner/work/UnfoldMakie.jl/UnfoldMakie.jl/docs/.CondaPkg/env

  Updating specs:

   - libstdcxx[version='>=3.4,<15.0']
   - libstdcxx-ng[version='>=3.4,<15.0']
   - matplotlib[version='>=1']
   - openssl[version='>=3, <3.6']
   - conda-forge::python[version='>=3.10,!=3.14.0,!=3.14.1,<4, >=3.4,<4',build=*cp*]
   - uv[version='>=0.4']


  Package                           Version  Build                 Channel           Size
───────────────────────────────────────────────────────────────────────────────────────────
  Install:
───────────────────────────────────────────────────────────────────────────────────────────

  + tzdata                            2025c  hc9c84f9_1            conda-forge     Cached
  + python_abi                         3.14  8_cp314               conda-forge     Cached
  + ca-certificates                2026.1.4  hbd8a1cb_0            conda-forge     Cached
  + font-ttf-dejavu-sans-mono          2.37  hab24e00_0            conda-forge     Cached
  + font-ttf-inconsolata              3.000  h77eed37_0            conda-forge     Cached
  + font-ttf-source-code-pro          2.038  h77eed37_0            conda-forge     Cached
  + font-ttf-ubuntu                    0.83  h77eed37_3            conda-forge     Cached
  + fonts-conda-forge                     1  hc364b38_1            conda-forge     Cached
  + fonts-conda-ecosystem                 1  0                     conda-forge     Cached
  + _libgcc_mutex                       0.1  conda_forge           conda-forge     Cached
  + libgomp                          14.3.0  he0feb66_16           conda-forge     Cached
  + libglvnd                          1.7.0  ha4b6fd6_2            conda-forge     Cached
  + _openmp_mutex                       4.5  2_gnu                 conda-forge     Cached
  + libopengl                         1.7.0  ha4b6fd6_2            conda-forge     Cached
  + libegl                            1.7.0  ha4b6fd6_2            conda-forge     Cached
  + libgcc                           14.3.0  he0feb66_16           conda-forge     Cached
  + libpciaccess                       0.18  hb9d3cd8_0            conda-forge     Cached
  + libmpdec                          4.0.0  hb9d3cd8_0            conda-forge     Cached
  + libntlm                             1.8  hb9d3cd8_0            conda-forge     Cached
  + libgfortran5                     14.3.0  hda2acac_16           conda-forge     Cached
  + keyutils                          1.6.3  hb9d3cd8_0            conda-forge     Cached
  + libwebp-base                      1.6.0  hd42ef1d_0            conda-forge     Cached
  + libdeflate                         1.25  h17f619e_0            conda-forge     Cached
  + libjpeg-turbo                     3.1.2  hb03c661_0            conda-forge     Cached
  + alsa-lib                       1.2.15.3  hb03c661_0            conda-forge     Cached
  + xorg-libice                       1.1.2  hb9d3cd8_0            conda-forge     Cached
  + libbrotlicommon                   1.2.0  hb03c661_1            conda-forge     Cached
  + libiconv                           1.18  h3b78370_2            conda-forge     Cached
  + xorg-libxdmcp                     1.1.5  hb03c661_1            conda-forge     Cached
  + pthread-stubs                       0.4  hb9d3cd8_1002         conda-forge     Cached
  + xorg-libxau                      1.0.12  hb03c661_1            conda-forge     Cached
  + libgcc-ng                        14.3.0  h69a702a_16           conda-forge     Cached
  + ncurses                             6.5  h2d0b736_3            conda-forge     Cached
  + libzlib                           1.3.1  hb9d3cd8_2            conda-forge     Cached
  + libuuid                          2.41.3  h5347b49_0            conda-forge     Cached
  + liblzma                           5.8.2  hb03c661_0            conda-forge     Cached
  + libexpat                          2.7.3  hecca717_0            conda-forge     Cached
  + bzip2                             1.0.8  hda65f42_8            conda-forge     Cached
  + libffi                            3.5.2  h9ec8514_0            conda-forge     Cached
  + openssl                           3.5.4  h26f9b46_0            conda-forge     Cached
  + libstdcxx                        14.3.0  h934c35e_16           conda-forge     Cached
  + libdrm                          2.4.125  hb03c661_1            conda-forge     Cached
  + libgfortran                      14.3.0  h69a702a_16           conda-forge     Cached
  + libbrotlienc                      1.2.0  hb03c661_1            conda-forge     Cached
  + libbrotlidec                      1.2.0  hb03c661_1            conda-forge     Cached
  + libxcb                           1.17.0  h8a09558_0            conda-forge     Cached
  + libxcrypt                        4.4.36  hd590300_1            conda-forge     Cached
  + libedit                    3.1.20250104  pl5321h7949ede_0      conda-forge     Cached
  + readline                            8.3  h853b02a_0            conda-forge     Cached
  + libpng                           1.6.54  h421ea60_0            conda-forge     Cached
  + zstd                              1.5.7  hb78ec9c_6            conda-forge     Cached
  + tk                               8.6.13  noxft_ha0e22de_103    conda-forge     Cached
  + xorg-libsm                        1.2.6  he73a12e_0            conda-forge     Cached
  + pcre2                             10.47  haa7fec5_0            conda-forge     Cached
  + lerc                              4.0.0  h0aef613_1            conda-forge     Cached
  + pixman                           0.46.4  h54a6638_1            conda-forge     Cached
  + graphite2                        1.3.14  hecca717_2            conda-forge     Cached
  + zlib-ng                           2.3.2  hceb46e0_1            conda-forge     Cached
  + wayland                          1.24.0  hd6090a7_1            conda-forge     Cached
  + icu                                78.2  h33c6efd_0            conda-forge     Cached
  + double-conversion                 3.4.0  hecca717_0            conda-forge     Cached
  + uv                               0.9.26  h76e24b7_0            conda-forge     Cached
  + libstdcxx-ng                     14.3.0  hdf11a46_16           conda-forge     Cached
  + libopenblas                      0.3.30  pthreads_h94d23a6_4   conda-forge     Cached
  + brotli-bin                        1.2.0  hb03c661_1            conda-forge     Cached
  + xcb-util                          0.4.1  h4f16b4b_2            conda-forge     Cached
  + xcb-util-wm                       0.4.2  hb711507_0            conda-forge     Cached
  + xcb-util-keysyms                  0.4.1  hb711507_0            conda-forge     Cached
  + xcb-util-renderutil              0.3.10  hb711507_0            conda-forge     Cached
  + xorg-libx11                      1.8.12  h4f16b4b_0            conda-forge     Cached
  + libfreetype6                     2.14.1  h73754d4_0            conda-forge     Cached
  + ld_impl_linux-64                   2.45  default_hbd61a6d_105  conda-forge     Cached
  + libglib                          2.86.3  h6548e54_0            conda-forge     Cached
  + libtiff                           4.7.1  h9d88235_1            conda-forge     Cached
  + libsqlite                        3.51.2  hf4e2dac_0            conda-forge     Cached
  + libxml2-16                       2.15.1  hca6bf5a_1            conda-forge     Cached
  + krb5                             1.21.3  h659f571_0            conda-forge     Cached
  + qhull                            2020.2  h434a139_5            conda-forge     Cached
  + libblas                          3.11.0  5_h4a7cf45_openblas   conda-forge     Cached
  + brotli                            1.2.0  hed03a55_1            conda-forge     Cached
  + xcb-util-image                    0.4.0  hb711507_2            conda-forge     Cached
  + xorg-libxfixes                    6.0.2  hb03c661_0            conda-forge     Cached
  + xkeyboard-config                   2.46  hb03c661_0            conda-forge     Cached
  + libglx                            1.7.0  ha4b6fd6_2            conda-forge     Cached
  + xorg-libxrender                  0.9.12  hb9d3cd8_0            conda-forge     Cached
  + xorg-libxext                      1.3.6  hb9d3cd8_0            conda-forge     Cached
  + libfreetype                      2.14.1  ha770c72_0            conda-forge     Cached
  + dbus                             1.16.2  h24cb091_1            conda-forge     Cached
  + openjpeg                          2.5.4  h55fea9a_0            conda-forge     Cached
  + lcms2                              2.18  h0c24ade_0            conda-forge     Cached
  + python                           3.14.2  h32b2ec7_100_cp314    conda-forge     Cached
  + libxml2                          2.15.1  he237659_1            conda-forge     Cached
  + cyrus-sasl                       2.1.28  hd9c7081_0            conda-forge     Cached
  + libcups                           2.3.3  hb8b1518_5            conda-forge     Cached
  + libcblas                         3.11.0  5_h0358290_openblas   conda-forge     Cached
  + liblapack                        3.11.0  5_h47877c9_openblas   conda-forge     Cached
  + xcb-util-cursor                   0.1.6  hb03c661_0            conda-forge     Cached
  + xorg-libxcomposite                0.4.6  hb9d3cd8_2            conda-forge     Cached
  + libgl                             1.7.0  ha4b6fd6_2            conda-forge     Cached
  + xorg-libxcursor                   1.2.3  hb9d3cd8_0            conda-forge     Cached
  + xorg-libxi                        1.8.2  hb9d3cd8_0            conda-forge     Cached
  + xorg-libxxf86vm                   1.1.6  hb9d3cd8_0            conda-forge     Cached
  + xorg-libxdamage                   1.1.6  hb9d3cd8_0            conda-forge     Cached
  + xorg-libxrandr                    1.5.4  hb9d3cd8_0            conda-forge     Cached
  + freetype                         2.14.1  ha770c72_0            conda-forge     Cached
  + libxkbcommon                     1.13.1  hca5e8e5_0            conda-forge     Cached
  + libxslt                          1.1.43  h711ed8c_1            conda-forge     Cached
  + libllvm21                        21.1.8  hf7376ad_0            conda-forge     Cached
  + openldap                         2.6.10  he970967_0            conda-forge     Cached
  + xorg-libxtst                      1.2.5  hb9d3cd8_3            conda-forge     Cached
  + libvulkan-loader              1.4.328.1  h5279c79_0            conda-forge     Cached
  + fontconfig                       2.15.0  h7e30c49_1            conda-forge     Cached
  + libclang-cpp21.1                 21.1.8  default_h99862b1_1    conda-forge     Cached
  + libclang13                       21.1.8  default_h746c552_1    conda-forge     Cached
  + libpq                              18.1  hb80d175_3            conda-forge     Cached
  + cairo                            1.18.4  he90730b_1            conda-forge     Cached
  + harfbuzz                         12.3.0  h6083320_0            conda-forge     Cached
  + qt6-main                         6.10.1  hb82b983_4            conda-forge     Cached
  + pip                                25.3  pyh145f28c_0          conda-forge     Cached
  + munkres                           1.1.4  pyhd8ed1ab_1          conda-forge     Cached
  + six                              1.17.0  pyhe01879c_1          conda-forge     Cached
  + pyparsing                         3.3.1  pyhcf101f3_0          conda-forge     Cached
  + packaging                          25.0  pyh29332c3_1          conda-forge     Cached
  + cycler                           0.12.1  pyhcf101f3_2          conda-forge     Cached
  + python-dateutil             2.9.0.post0  pyhe01879c_2          conda-forge     Cached
  + unicodedata2                     17.0.0  py314h5bd0f2a_1       conda-forge     Cached
  + pillow                           12.1.0  py314h8ec4b1a_0       conda-forge     Cached
  + numpy                             2.4.1  py314h2b28147_0       conda-forge     Cached
  + kiwisolver                        1.4.9  py314h97ea11e_2       conda-forge     Cached
  + pyside6                          6.10.1  py314hf36963e_0       conda-forge     Cached
  + tornado                           6.5.4  py314h7b0bd38_0       conda-forge     Cached
  + contourpy                         1.3.3  py314h9891dd4_3       conda-forge     Cached
  + fonttools                        4.61.1  pyh7db6752_0          conda-forge     Cached
  + matplotlib-base                  3.10.8  py314h1194b4b_0       conda-forge     Cached
  + matplotlib                       3.10.8  py314hdafbbf9_0       conda-forge     Cached

  Summary:

  Install: 135 packages

  Total download: 0 B

───────────────────────────────────────────────────────────────────────────────────────────



Transaction starting

Transaction finished

To activate this environment, use:

    micromamba activate /home/runner/work/UnfoldMakie.jl/UnfoldMakie.jl/docs/.CondaPkg/env

Or to execute a single command in this environment, use:

    micromamba run -p /home/runner/work/UnfoldMakie.jl/UnfoldMakie.jl/docs/.CondaPkg/env mycommand

    CondaPkg Installing Pip packages
             │ /home/runner/work/UnfoldMakie.jl/UnfoldMakie.jl/docs/.CondaPkg/env/bin/uv
             │ pip
             │ install
             └ mne >=1.4
Using Python 3.14.2 environment at: /home/runner/work/UnfoldMakie.jl/UnfoldMakie.jl/docs/.CondaPkg/env
Resolved 25 packages in 128ms
Downloading mne (7.1MiB)
Downloading scipy (33.4MiB)
 Downloaded mne
 Downloaded scipy
Prepared 14 packages in 570ms
Installed 14 packages in 29ms
 + certifi==2026.1.4
 + charset-normalizer==3.4.4
 + decorator==5.2.1
 + idna==3.11
 + jinja2==3.1.6
 + lazy-loader==0.4
 + markupsafe==3.0.3
 + mne==1.11.0
 + platformdirs==4.5.1
 + pooch==1.8.2
 + requests==2.32.5
 + scipy==1.17.0
 + tqdm==4.67.1
 + urllib3==2.6.3

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: 93 samples with 1 evaluation per sample.
 Range (minmax):  46.187 ms301.105 ms   GC (min … max): 0.00% … 82.25%
 Time  (median):     50.824 ms                GC (median):    0.00%
 Time  (mean ± σ):   53.772 ms ±  26.038 ms   GC (mean ± σ):  4.95% ±  8.53%

                 █▄     ▆▄ ▆ ▂  ▂                               
  ▆▄▁▁▁▁▄▁▆▁▆▄▄█▄███▆▆▆▄█████▆▁█▆▄▄▆█▄▁▄▄▁▁▁▁▄▆▁▁▆▁▁▁▄▄▆▁▁▁▄ ▁
  46.2 ms         Histogram: frequency by time         57.8 ms <

 Memory estimate: 11.59 MiB, allocs estimate: 190700.

UnfoldMakie.jl with DelaunayMesh

@benchmark plot_topoplot(
    dat[:, 320, 1];
    positions = positions,
    topo_interpolation = (; interpolation = DelaunayMesh()),
)
BenchmarkTools.Trial: 89 samples with 1 evaluation per sample.
 Range (minmax):  48.656 ms345.221 ms   GC (min … max): 0.00% … 84.35%
 Time  (median):     52.468 ms                GC (median):    0.00%
 Time  (mean ± σ):   56.263 ms ±  31.079 ms   GC (mean ± σ):  5.82% ±  8.94%

               █  ▃▁▃ ▆ ▁  ▁▁   ▆            ▁                 
  ▇▁▁▁▇▇▇▄▄▄▇▇▇█▁▄███▄█▇█▁▁██▇▄▁█▄▇▁▇▇▁▁▁▄▁▄▁█▁▄▁▄▇▄▄▁▁▇▁▄▁▇ ▁
  48.7 ms         Histogram: frequency by time         58.8 ms <

 Memory estimate: 11.59 MiB, allocs estimate: 190707.

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: 372 samples with 1 evaluation per sample.
 Range (minmax):  12.105 ms184.206 ms   GC (min … max): 0.00% … 0.00%
 Time  (median):     12.813 ms                GC (median):    0.00%
 Time  (mean ± σ):   13.434 ms ±   9.161 ms   GC (mean ± σ):  0.00% ± 0.00%

   ▁ ▅▃▁▅▄▅█▃▆▃█▅▁█▃▂▅▁▇                                       
  ▅█▄███████████████████▄▇▆▄▃▃▃▃▁▃▁▃▁▁▃▃▃▃▁▁▁▁▃▁▁▁▁▁▁▁▁▁▁▁▁▃ ▄
  12.1 ms         Histogram: frequency by time         15.2 ms <

 Memory estimate: 3.30 KiB, allocs estimate: 98.

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 per sample.
 Range (minmax):  2.197 s   2.447 s   GC (min … max): 0.00% … 12.14%
 Time  (median):     2.302 s                GC (median):    0.00%
 Time  (mean ± σ):   2.315 s ± 125.596 ms   GC (mean ± σ):  4.28% ±  7.01%

   ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ ▁
  2.2 s          Histogram: frequency by time         2.45 s <

 Memory estimate: 342.81 MiB, allocs estimate: 4928119.

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: 8 samples with 1 evaluation per sample.
 Range (minmax):  676.587 ms682.412 ms   GC (min … max): 0.00% … 0.00%
 Time  (median):     679.970 ms                GC (median):    0.00%
 Time  (mean ± σ):   679.858 ms ±   2.171 ms   GC (mean ± σ):  0.00% ± 0.00%

  █     █                 █        █  █                 █ █   █  
  █▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█▁█▁▁▁█ ▁
  677 ms           Histogram: frequency by time          682 ms <

 Memory estimate: 2.39 KiB, allocs estimate: 69.

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: 2 samples with 1 evaluation per sample.
 Range (minmax):  2.731 s  2.762 s   GC (min … max): 0.00% … 0.00%
 Time  (median):     2.746 s               GC (median):    0.00%
 Time  (mean ± σ):   2.746 s ± 21.387 ms   GC (mean ± σ):  0.00% ± 0.00%

                              ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ ▁
  2.73 s         Histogram: frequency by time        2.76 s <

 Memory estimate: 178.42 MiB, allocs estimate: 602398.

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 per sample.
 Single result which took 9.375 s (0.00% GC) to evaluate,
 with a memory estimate of 3.03 KiB, over 96 allocations.

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


This page was generated using Literate.jl.