Here is a code that compares fft phase plotting with 2 different methods :
import numpy as np
import matplotlib.pyplot as plt
import scipy.fftpack
phase = np.pi / 4
f = 1
fs = f*20
dur=10
t = np.linspace(0, dur, num=fs*dur, endpoint=False)
y = np.cos(2 * np.pi * t + phase)
Y = scipy.fftpack.fftshift(scipy.fftpack.fft(y))
f = scipy.fftpack.fftshift(scipy.fftpack.fftfreq(len(t)))
p = np.angle(Y)
p[np.abs(Y) < 1] = 0
fig, ax = plt.subplots(2, 1)
ax[0].plot(t, y)
ax[1].plot(f*fs, p, label='from fft')
ax[1].phase_spectrum(y, fs, window=None, label='from phase_spectrum')
plt.legend()
plt.show()
here is the result :

Here is result when signal number of period is not an integer :

I have several questions :
Before the answer, just a small note:
Remove the p[np.abs(Y) < 1] = 0 line. Most of your spectrum has magnitude below 1, which is why, with this line, your spectrum looks mostly like a flat line at zero.
Now to the answer:
phase_spectrum does three things different than you:
np.unwrap(np.angle(Y)).angle_spectrum instead.window=None, but, for some reason, matplotlib decided that window=None means "use a hanning window, please" (see the docs).window=lambda x: x.
window=matplotlib.mlab.window_none, but the source for it is just a def window_none(x): return x.sides='twosided' to the phase_spectrum call.Now, about getting the phase at a frequency f:
To do this, you must use the phase without unwrapping.
You are right that you can't directly extract the phase of the single tone signal if you do not have an integer number of cycles. That is because the singal's frequency does not fall exactly on top of any frequency bin in the FFT. You can get an approximation with the phase of the nearest bin, though. You could also do a sinc interpolation of the spectrum to get its value at the frequency you want.
If you only care about the phase of a single frequency f, then you shouldn't use FFT at all. The FFT computes the phase and amplitue at all frequencies. If you only care about a single frequency, just do Y_at_f = y @ np.exp(2j * np.pi * f * t) and get that phase by np.angle(Y_at_f).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With