I compared the results of Hilbert Transform as provided in scipy.signal.hilbert() (which is uses I/FFT as you can see in the source code) with its approximation implemented as FIR filter using coeffs that I found in this paper. I constructed a quick example where the input signal x0
is a sine with a linearly decreasing frequency w0
. The Hilbert transform h
is calculated using the scipy function[1], the quadrature Q
is calculated using the FIR filter with coeffs from the code in paper [3] and the in-phase component I
is a copy of the input signal - it should be the input signal delayed by 3, I know, but for the sake of the example I wanted to remove the lag, because I wanted to see how well the reconstructed signal x2
is matching the original x0
. Further the amplitude and phase series A1, ph1
where computed from h
and A2, ph2
where computed from I
and Q
. Reconstructed signals x1
and x2
were computed from the corresponding amplitude and phase series, then finally the instantaneous frequencies w1
and w2
were calculated from ph1
and ph2
respectively. Here is the Python code with all the calculations and plotting:
import numpy as np
from scipy.signal import lfilter
import matplotlib.pyplot as plt
# Construct the input signal
idx = np.arange(512)
w0 = [2*np.pi/(80 + x/8) for x in idx]
x0 = np.sin(w0*idx)
# Calculate the transforms
h = hilbert(x0)
I = x0 # normally it should be np.roll(x0, 3)
Q = lfilter([0.25, 0, 0.75, 0, -0.25, 0, -0.75], [1], x0)
# Calculate amplitude arrays
A1 = np.abs(h)
A2 = np.sqrt(I**2 + Q**2)
# Calcuclate phase arrays
ph1 = np.angle(h)
ph2 = -np.arctan2(Q, I)
# Calculate instant. frequency
w1 = np.diff(np.unwrap(ph1))
w2 = np.diff(np.unwrap(ph2))
# Plot the results
fig = plt.figure()
ax = plt.subplot(311)
plt.plot(x0, lw=3, color='b', label='signal')
plt.plot(A1*np.cos(ph1), lw=2, color='g', label='x1')
plt.plot(A1, lw=2, ls='dotted', color='g', label='A1')
plt.plot(A2*np.cos(ph2), lw=1, color='k', label='x2')
plt.plot(A2, lw=1, ls='dotted', color='k', label='A2')
plt.legend(bbox_to_anchor=(1, 1), loc=2, borderaxespad=0.)
plt.subplot(312, sharex=ax)
plt.plot(ph1, lw=2, color='g', label='ph1')
plt.plot(ph2, lw=1, color='k', label='ph2')
plt.legend(bbox_to_anchor=(1, 1), loc=2, borderaxespad=0.)
plt.subplot(313, sharex=ax)
plt.plot(w0, lw=3, color='b', label='w0')
plt.plot(w1, lw=2, color='g', label='w1')
plt.plot(w2, lw=1, color='k', label='w2')
plt.legend(bbox_to_anchor=(1, 1), loc=2, borderaxespad=0.)
fig.show()
And this is how the figure looks like:
The reconstructed signals x1
and x2
seem to be tightly matching the input signal despite of the discrepancies between the amplitudes A1
and A2
, and phases ph1
and ph2
. The latter further affect the instantaneous frequencies w1
and w2
. The one calculated from scipy.signal.hilbert()
is at least a fairly straight line (displaced by an offset which should probably be included in the calculations at some point), but w2
has sudden peaks in places where A2
has dips and it does not follow w0
that well.
Hilbert Transform is widely used for instantaneous frequency estimation, so considering the above example I have been wondering whether there are some steps or details missing from my calculations or is that how accurate the approximation should be? I have been using scipy.signal.hilbert
for some time and am aware of the edge effects, but the FIR method seems to be out of line throughout. Could that be due to the truncated coeffs? I did not try longer filters, because I do not know exactly how to calculate the coeffs, but the paper [3] promised that the proposed approximation was good enough to be used to successfully predict turns in gold prices during high volatility periods, so I took the authors' word for that. I actually saw similar phase shifting filters in other books or papers and one thing that really surprised me was that every time authors used exactly the same arrays of coeffs for detrending filters as they used to calculate the quadrature component ([3] is no exception: detrender and Q1 are results of exactly the same filters). I am still new to DSP, so if I am missing something important here please let me know.