Ultrafast EEG template matching under Dynamic Time Warping — a Python port of the algorithm at the core of NeuroBrowser.
J. Jing, J. Dauwels, T. Rakthanmanon, E. Keogh, S. S. Cash, M. B. Westover. Rapid annotation of interictal epileptiform discharges via template matching under Dynamic Time Warping. Journal of Neuroscience Methods 274 (2016) 179–190. doi:10.1016/j.jneumeth.2016.02.025
NeuroBrowser lets an expert mark one example of a waveform (e.g. an interictal epileptiform discharge / "spike") and then rapidly finds every similar waveform in a long EEG recording. The similarity measure is Dynamic Time Warping (DTW), made fast enough for interactive use by the UCR Suite / "Trillion" lower-bounding algorithm (Rakthanmanon et al., KDD 2012 — E. Keogh and T. Rakthanmanon are co-authors of the NeuroBrowser paper). In the original study this reduced expert annotation time by ~70% and yielded a database of 19,000+ spikes from 100 patients.
This repository reimplements that engine in pure Python (NumPy/SciPy) so it can be used without MATLAB, on any platform.
See REPRODUCE.md (what regenerates + one command) and DATA_SOURCE.md (data provenance). Verified 2026-07-06: Fig 2b separation, Fig 6/7 alignment, ~112x speed, 34/34 tests.
| Module | Purpose |
|---|---|
neurobrowser/dtw.py |
Constrained (Sakoe–Chiba) DTW + z-normalized subsequence k-NN search with LB_Keogh early-abandoning (the "fast DTW"). |
neurobrowser/_ucrmodule.c |
C accelerator: the full UCR Suite cascade (online z-norm, LB_Kim, LB_Keogh, early-abandoning DTW). ~100× faster, identical results. Built automatically; pure-Python fallback if no compiler. |
neurobrowser/preprocessing.py |
Band-pass filter, Teager NLEO energy operator, CAR montage. |
neurobrowser/matching.py |
The template-matching pipeline (port of main_TMbyDTW.m). |
neurobrowser/io.py |
Loaders for the .mat template bank and EEG event segments. |
examples/demo_match.py |
Match one event and plot the DTW alignment + detected spike. |
examples/reproduce_fig2b.py |
Reproduce the paper's spike-vs-background DTW-distance separation (Fig. 2b). |
tests/ |
Correctness tests (banded DTW vs brute force; pruned search vs exhaustive). |
git clone git@github.com:bdsp-core/neurobrowser.git
cd neurobrowser
pip install -e ".[plot,dev]"Requires Python ≥ 3.9, NumPy, SciPy (matplotlib for the figures).
import neurobrowser as nb
templates = nb.load_templates("data/Callbacks/Templates.mat")
event = nb.load_event("data/Data/s117_SEG_10783.mat")
result = nb.match_event(event, templates)
print(result.template_index, result.distance, result.location)Core search on your own signals:
import numpy as np, neurobrowser as nb
query = np.sin(np.linspace(0, 3*np.pi, 40)) # a template
data = np.random.randn(2000) # a long recording (1 channel)
dist, loc = nb.ucr_knn_dtw(query, data, r=0.05, k=3) # 3 best matchesThe similarity search uses the UCR Suite cascade compiled in C. On a 20,000-sample signal with a 50-sample query it runs in ~3 ms vs ~385 ms for the pure-Python reference (~114×), returning bit-for-bit identical matches. Check which path is active and benchmark:
>>> from neurobrowser.dtw import HAVE_C; HAVE_C
Truepython examples/benchmark.py # naive DP vs Python vs CThe C accelerator builds automatically during pip install. If no C compiler is
present the package still installs and transparently falls back to pure Python
(backend="python").
The headline ~70% time-savings is a human user-study result and is not computationally reproducible. The algorithmic claims are:
# 1) DTW cleanly matches a template to a spike and localizes it:
python examples/demo_match.py --data /path/to/bundle
# 2) DTW distance separates spikes from background EEG (Fig. 2b):
python examples/reproduce_fig2b.py --data /path/to/bundleOn the shipped demo bundle, (2) yields a clear separation (median DTW distance ≈ 1.8 at spikes vs ≈ 3.8 at background).
The original engine was a MATLAB MEX (UCR_KNN_DTW_Mex) wrapping a
custom-modified UCR Suite in C. Only a compiled Windows binary survived, so this
port re-derives the algorithm from the paper and the surviving MATLAB wrappers
(UCR_KNN_DTW.m, main_TMbyDTW.m, energyop.m). By default the DTW local cost
is squared-Euclidean to match the UCR Suite / MEX numerics; pass metric="abs"
for the L1 form written in Eq. (2) of the paper. A MATLAB cross-validation
harness lives under matlab/ (see its README).
The demo EEG segments are de-identified 10-second snippets. They are not
committed here pending release approval; the dataset's canonical home is
bdsp.io. See data/README.md.
BSD 3-Clause — see LICENSE.