Initialize NeuroAnalyzer
using FFTW
using NeuroAnalyzerusing FFTW
using NeuroAnalyzerTapering multiplies a signal by a window function before spectral analysis. It is one of the most common preprocessing steps in EEG frequency analysis and has a direct impact on the quality of power spectra and spectrograms.
Any real EEG epoch has a finite length. When you compute its power spectrum via the Fourier transform, the algorithm implicitly assumes the signal repeats periodically. Unless the signal happens to complete an exact whole number of cycles inside the epoch, the values at the two endpoints will be different, creating an artificial sharp discontinuity. That discontinuity smears power from the true frequency into neighboring frequencies - this is called spectral leakage.
Spectral leakage: a phenomenon in which signal at a specific frequency is leaked to other frequencies within the spectrum of the data:
A taper is a smooth window that gently fades the signal to zero at both ends, removing the discontinuity before the Fourier transform sees it.
Tapering always involves a trade-off between two spectral properties:
| Property | Without taper (rectangular window) | With taper (e.g. Hann) |
|---|---|---|
| Spectral leakage | High - power spills into neighboring bins | Low - power stays near the true frequency |
| Amplitude accuracy | High - signal is unmodified | Lower - taper attenuates the signal |
| Frequency resolution | Highest possible for the epoch length | Slightly reduced (broader spectral peaks) |
Rule of thumb
Taper whenever the shape or presence of spectral peaks matters — which is most of the time. Skip tapering only when you need the most accurate peak amplitude measurement and spectral leakage is not a concern.
The effect of tapering is easiest to understand with a simple sine wave whose frequency does not fit an exact number of cycles into the epoch.
# sampling rate (Hz)
fs = 1000
# 2-second time vector
t = collect(0:1/fs:2)
# 10 Hz sine wave, amplitude 2 units
s = generate_sine(10, t, 2)
NeuroAnalyzer.plot(t,
s,
ylabel="Amplitude [AU]")Apply a Hann taper and plot the result:
# generate a Hann window
w = generate_window(:hann, length(s))
# multiply signal by window
sw = s .* w
NeuroAnalyzer.plot(t,
sw,
ylabel="amplitude [AU]")You will see the tapered signal smoothly reach zero at both ends. Plotting the power spectra of s and sw side by side shows the leakage reduction: the untapered spectrum has broad skirts around the 10 Hz peak; the tapered spectrum has a much cleaner, tighter peak.
NeuroAnalyzer provides a set of built-in windows via generate_window(). Each has different leakage and resolution characteristics:
| Window | Key characteristic | Typical use |
|---|---|---|
:hann |
Tapers fully to zero; good leakage suppression with moderate resolution loss | General-purpose default for EEG spectral analysis |
:bh (Blackman-Harris) |
Very low leakage; wider main lobe than Hann | When leakage suppression is the priority |
:bn (Blackman-Nutall) |
Refined version of the Blackman window | When leakage suppression is the priority |
:flat |
Flat-top | Excellent amplitude accuracy for spectral analysis |
:exp |
Symmetric exponential (left half ↑, right half ↓) | Baseline comparison only |
:triangle |
Symmetric triangle (left half ↑, right half ↓) | Used to reduce the effects of spectral leakage |
:bohman |
The product of a triangular window and a single cycle of a cosine | Very low sidelobes, which helps significantly reduce spectral leakage |
:hamming† |
Does not fully taper to zero; slightly better amplitude accuracy than Hann | When a small amount of leakage is acceptable |
:kaiser† |
Adjustable trade-off via a shape parameter | When you need explicit control over leakage vs. resolution |
:gaussian† |
Can be tuned; becomes narrow if forced to zero at edges | Specific signal processing applications |
:cosine† |
Gentle half-cosine fade at edges | Mild leakage reduction |
:rectangular† |
No tapering at all | Baseline comparison only |
†: These window functions are available via DSP.jl.
To see the full current list accepted by generate_window(): ?generate_window
To choose a window for your analysis, plot the windows and their frequency responses side by side:
windows=[:hann, :bh, :bn, :flat, :exp, :triangle, :bohman]
fs = 1000
n = 1000
for w in windows
win = generate_window(w, n)
println("$w: peak sidelobe ≈
$(round(minimum(20 .* log10.(abs.(fft(win) ./ n))), digits=1)) dB")
endhann: peak sidelobe ≈
-350.1 dB
bh: peak sidelobe ≈
-355.0 dB
bn: peak sidelobe ≈
-350.1 dB
flat: peak sidelobe ≈
-351.4 dB
exp: peak sidelobe ≈
-368.3 dB
triangle: peak sidelobe ≈
-108.0 dB
bohman: peak sidelobe ≈
-401.9 dB
Lower (more negative) peak sidelobe values indicate better leakage suppression. The Blackman window typically achieves around −58 dB, Hann around −31 dB, and a rectangular window only around −13 dB.
Use taper() to apply a window to all channels of a NeuroAnalyzer.NEURO object. The taper is applied epoch-by-epoch, so epoch_len(eeg) gives the correct window length.
taper(eeg,
ch="all",
t=generate_window(:hann, epoch_len(eeg)))To apply in-place (modifies the original object):
taper!(eeg,
ch="all",
t=generate_window(:hann, epoch_len(eeg)))Use ch="eeg" to taper only EEG signal channels, leaving ECG, EOG and reference channels unchanged. Use ch="all" to taper every channel.
If you need a window type not available in NeuroAnalyzer, you can pass any vector of the same length as the epoch, including windows from DSP.jl:
taper(eeg,
ch="all",
t=DSP.hanning(epoch_len(eeg)))Tapering is applied after epoching and artifact rejection, and immediately before spectral analysis. It should not be applied to continuous (un-epoched) data intended for further editing, since the amplitude attenuation at epoch edges will corrupt subsequent processing steps.
Typical order of operations:
NeuroAnalyzer.filter!(eeg,
ch="eeg",
fprototype=:butterworth,
ftype=:bp,
cutoff=(1, 40),
order=4)
reference_avg!(eeg)
eeg_ep=epoch(eeg, ep_len=2)
# ... reject bad epochs ...
taper!(eeg_ep, ch="eeg", t=generate_window(:hann, epoch_len(eeg_ep)))
psd_result=psd(eeg_ep, ch="eeg")Do not taper before filtering
Tapering before filtering attenuates edge regions that the filter needs for accurate transient response. Always filter first, then epoch, then taper.