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.3Data 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 (min … max): 46.187 ms … 301.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 (min … max): 48.656 ms … 345.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()
endBenchmarkTools.Trial: 372 samples with 1 evaluation per sample.
Range (min … max): 12.105 ms … 184.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]"),
)
endBenchmarkTools.Trial: 3 samples with 1 evaluation per sample.
Range (min … max): 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 (min … max): 676.587 ms … 682.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 = 5050UnfoldMakie 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
endBenchmarkTools.Trial: 2 samples with 1 evaluation per sample.
Range (min … max): 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)
endBenchmarkTools.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.