Endpoint-corrected Hilbert transform (ECHT) phase estimation#

This example shows how to causally estimate the phase of a signal using the Endpoint-corrected Hilbert transform (ECHT) [1].

Uses meegkit.phase.ECHT().

References#

import matplotlib.pyplot as plt
import numpy as np
from scipy.signal import hilbert

from meegkit.phase import ECHT

rng = np.random.default_rng(38872)

plt.rcParams["axes.grid"] = True
plt.rcParams["grid.linestyle"] = ":"

Build data#

First, we generate a multi-component signal with amplitude and phase modulations, as described in the paper [1].

f0 = 2

N = 500
sfreq = 100
time = np.linspace(0, N / sfreq, N)
X = np.cos(2 * np.pi * f0 * time - np.pi / 4)
phase_true = np.angle(hilbert(X))
X += rng.normal(0, 0.5, N)  # Add noise

Compute phase and amplitude#

We compute the Hilbert phase, as well as the phase obtained with the ECHT filter.

phase_hilbert = np.angle(hilbert(X))  # Hilbert phase

# Compute ECHT-filtered signal
filt_BW = f0 / 2
l_freq = f0 - filt_BW / 2
h_freq = f0 + filt_BW / 2
echt = ECHT(l_freq, h_freq, sfreq)

Xf = echt.fit_transform(X)
phase_echt = np.angle(Xf)

Visualize signal#

Here we plot the original signal, its Fourier spectrum, and the phase obtained with the Hilbert transform and the ECHT filter. The ECHT filter provides a much smoother phase estimate than the Hilbert transform

fig, ax = plt.subplots(3, 1, figsize=(8, 6))
ax[0].plot(time, X)
ax[0].set_xlabel("Time (s)")
ax[0].set_title("Test signal")
ax[0].set_ylabel("Amplitude")
ax[1].psd(X, Fs=sfreq, NFFT=2048*4, noverlap=sfreq)
ax[1].set_ylabel("PSD (dB/Hz)")
ax[1].set_title("Test signal's Fourier spectrum")
ax[2].plot(time, phase_true, label="True phase", ls=":")
ax[2].plot(time, phase_echt, label="ECHT phase", lw=.5, alpha=.8)
ax[2].plot(time, phase_hilbert, label="Hilbert phase", lw=.5, alpha=.8)
ax[2].set_title("Phase")
ax[2].set_ylabel("Amplitude")
ax[2].set_xlabel("Time (s)")
ax[2].legend(loc="upper right", fontsize="small")
plt.tight_layout()
plt.show()
Test signal, Test signal's Fourier spectrum, Phase

Total running time of the script: (0 minutes 0.305 seconds)

Gallery generated by Sphinx-Gallery