I am trying to determine the most dominant frequency of a signal. However, when artificially creating a 50 Hz signal and applying sufficient zeropadding to enhance fft resolution, I get a top frequency of 49,997 Hz. For my application this is a significant difference. Did I do something wrong here?
import numpy as np
import matplotlib.pyplot as plt
fs = 2**12
x = np.linspace(0,1,fs+1)
signal = np.sin(50*2*np.pi*x)
spect = abs(np.fft.fft(np.append(signal,np.zeros(999*fs))))
plt.figure('Four Coef')
plt.plot(spect)
plt.axis([49995,49999,2048.01,2048.05])
plt.show()
Note that coefficient 49997 corresponds to a frequency of 49,997 Hz due to the zero-padding.
Edits: The array represents exactly 1 seconds of 50 Hz signal. The last 999 seconds are zeros in order to increase the fft "resolution" to 1 mHz. I have only 1 second of signal available, from which i need the top frequency, accurate up to the mHz
Changing the the sample rate fs = 2**8 gives a maximum of 49.999 so i suppose the way of sampling is critical here...
You are not taking the FFT of 1000 s of a 50 Hz wave: the array you pass to np.fft.fft is 1 second of signal followed by 999 seconds of silence zeros). So your clipped signal FFTs to a funky, multi-peaked thing.
When I do the following with a continuous signal, I see the peak at index 50000 as expected:
import numpy as np
import matplotlib.pyplot as plt
fs = 2**12
x = np.linspace(0,1000,fs*1000)
signal = np.sin(50*2*np.pi*x)
spect = abs(np.fft.fft(signal))
plt.figure('Four Coef')
plt.plot(spect)
print np.argmax(spect), np.max(spect)
plt.show()
Output:
50000 2047497.79244
NB1/ just repeating your array won't work properly either, because the ends won't "match up" (the signal will jump from the end of one 1 s array to the beginning of th next).
NB2/ You might consider using rfft and rfftfreq to get the frequencies here.
The frequency of the peak magnitude FFT result bin will indicate the frequency of a signal only if the length of the FFT is an exact integer multiple of the period of the frequency of that signal. For any other frequency, try using a frequency estimation algorithms (such as parabolic or Sinc interpolation of the FFT result, but there are many other estimation methods) instead of just the raw single peak magnitude bin frequency.
Related
My goal is to detect if a certain frequency is present in an audio recording and output a binary response. To do this, I plan on performing a Fourier transform on the audio file, and querying the values contained in the frequency bins. If I find that the bin associated with the frequency I am looking for has a high value, this should mean that it is present (if my thinking is correct). However, I am having trouble generating my transform correctly. My code is below:
from scipy.io import wavfile
from scipy.fft import fft, fftfreq
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
user_in = input("Please enter the relative path to your wav file --> ")
sampling_rate, data = wavfile.read(user_in)
print("sampling rate:", sampling_rate)
duration = len(data) / float(sampling_rate)
print("duration:", duration)
number_samples_in_seg = int(sampling_rate * duration)
fft_of_data = fft(data)
fft_bins_from_data = fftfreq(number_samples_in_seg, 1 / sampling_rate)
print(fft_bins_from_data.size)
plt.plot(fft_bins_from_data, fft_of_data, label="Real part")
plt.show()
Trying this code using a few different wav files leads me to wonder whether I am displaying my transform in the time domain, rather than the frequency domain, which I need:
Input: 200hz.wav
Output:
sampling rate: 48000
duration: 60.000375
2880018
Input: 8000hz.wav
Output:
sampling rate: 48000
duration: 60.000375
2880018
With these files that should contain a pure signal, I would expect to see only one spike on my plot, where x = 200 or x = 800. One final file contributes to my concern that I am not viewing the frequency domain:
Input: beep.wav
Output:
sampling rate: 48000
duration: 5.061958333333333
24297
This appears to show the distinct beeping as it progresses over an x-axis of time.
I attempted to clean up the plotting by only plotting the magnitude of the positive values. Unfortunately, I am still not seeing the frequencies isolated on a frequency spectrum:
plt.plot(fft_bins_from_data[0:number_samples_in_seg//2], abs(fft_of_data[0:number_samples_in_seg//2])
plt.show()
beep output updated
I have referred to these resources before posting:
How to get a list of frequencies in a wav file
Python frequency detection
Fourier Transforms With scipy.fft: Python Signal Processing
Calculate the magnitude and phase of a signal at a particular frequency in python
What is the difference between numpy.fft.fft and numpy.fft.fftfreq
A summary of my questions:
Are my plots displaying the time domain or frequency domain of the signal?
Why is the number of samples equal to the number of bins, and should this be the case for frequency domain?
If these plots are indeed the frequency domain, how do I interpret them and query the values in the bins?
Try this:
import scipy as sp
import scipy.signal as sig
import numpy as np
from numpy import fft
import matplotlib.pyplot as plt
number_samples_in_seg = len(data)
time_axis = np.arange(0, number_samples_in_seg)/sampling_rate
win = sig.windows.hann(number_samples_in_seg)
windowed_data = win*data
plt.plot(time_axis, windowed_data)
That will plot the signal in the time domain if that's not obvious. I applied a Hann window to the signal, which will reduce artifacts if the start and end of the signal don't match up (as the FFT assumes that the snippet of the signal is periodic).
For the plotting of the FFT:
fft_data = fft.fft(windowed_data)[0:int(np.floor(number_samples_in_seg/2))]
freq_axis = sp.fft.fftfreq(number_samples_in_seg, 1.0/sample_rate)[0:int(np.floor(number_samples_in_seg/2))]
plt.plot(freq_axis, 20.0*np.log10(np.abs(fft_data)))
The square bracket indexing on fft_data and freq_axis are to eliminate the negative frequency portion of the FFT. I generated a 200Hz sine wave in Audacity with a length of 4096 samples (just so that it fit within a power of two for nice FFT-ing) and there is a peak at 200Hz in my plot. Also note the 20*log10(abs(fft_data)) thing for plotting in dB.
The above should answer your question #3. As for question #2, the FFT always has the same number of time and frequency points. Not sure about question #1, but again, the above code should sort that out.
I'm trying to improve my understanding of power spectral density (PSD) by computing it various ways and it's not clear to me the correct manner for doing so using the autocorrelation.
Here's my signal and PSD using periodogram
import numpy as np
from numpy.fft import fft
from scipy.signal import periodogram,chirp
t = np.arange(0, .5, 0.001);
dt = t[1] - t[0]
fs=1/dt #sampling frequency is 1000 hz
f1=100
f2=375
x=chirp(t,f1,t[-1],f2) #chirp signal of length 500, increasing from 100 to 375 hz over .5 seconds.
f,Pxx=periodogram(x,nfft=len(x))
In this case Pxx has length 251 and the frequency vector is given by f=[0,2,4,...,500]
The first four values of Pxx are given by [7.98721667e-30 9.05062335e-04 3.62840614e-03 8.19785378e-03]
For my autocorrelation function, I used the formula
rxx[k]=(sum of x[i]*x[i+k] from i=0 to i=N) all divided by (sum of x[i]^2 from i=0 to i=N). In this formula our signal is given by x=[x[0],x[1],...,x[N]], which has been demeaned. I did a simple by-hand example calculation and found I could get results for a numpy array, x, using the following function:
def autocorr(x):
result = np.correlate(x, x, mode='full')/np.linalg.norm(x)**2
return result[int(result.size/2):]
For example, if x=np.array([-1,0,1,0,-2,2]), then autocorr(x)=[1,-.4,-.3,.2,.2,-.2]
In an attempt to get the PSD for my chirp signal using the autocorrelation, I simply used
x=x-x.mean()
rxx=autocorr(x)
Pxx=fft(rxx)
The length of Pxx is 500 and its first four values are given by
[0.5+0.j 0.5+0.0098j 0.5+0.0196j 0.5+0.0294j It's not clear to me why the length is 500. What is the corresponding range of frequencies? 0 to 1000?
Also, why are the results different, or even not scalar multiples of one other?
I have a signal for which I need to calculate the magnitude and phase at 200 Hz frequency only. I would like to use Fourier transform for it. I am very new to signal processing. And this is my first time using a fourier transform.
I found that I can use the scipy.fftpack.fft to calculate the fft of the signal. Then use numpy.mag and numpyh.phase to calculate the magnitude and phases of the entire signal. But I would like to get the magnitude and phase value of the signal corresponding to 200 Hz frequency only. How can I do this using Python?
So far I have done.
from scipy.fftpack import fft
import numpy as np
fft_data = fft(signal)
magnitude = np.mag(fft_data)
phase = np.phase(fft_data)
I tried to create a spectogram of magnitudes using scipy.signal.spectogram.
Unfortunately I didn't get it working.
My test signal should be a sine with frequency 400 Hz and an amplitude of 1. The result for the magnitude of the spectogram seems to be 0.5 instead of 1.0. I have no idea what the problem could be.
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
# 2s time range with 44kHz
t = np.arange(0, 2, 1/44000)
# test signal: sine with 400Hz amplitude 1
x = np.sin(t*2*np.pi*440)
# spectogram for spectrum of magnitudes
f, t, Sxx = signal.spectrogram(x,
44000,
"hanning",
nperseg=1000,
noverlap=0,
scaling="spectrum",
return_onesided=True,
mode="magnitude"
)
# plot last frequency plot
plt.plot(f, Sxx[:,-1])
print("highest magnitude is: %f" %np.max(Sxx))
A strictly real time domain signal is conjugate symmetric in the frequency domain. e.g. will appear in both the positive and negative (or upper) half of a complex result FFT.
Thus you need to add together the two "halves" together of an FFT result to get the total energy (Parseval's theorem). Or just double one side, since complex conjugates have equal magnitudes.
I am trying to use a fast fourier transform to extract the phase shift of a single sinusoidal function. I know that on paper, If we denote the transform of our function as T, then we have the following relations:
However, I am finding that while I am able to accurately capture the frequency of my cosine wave, the phase is inaccurate unless I sample at an extremely high rate. For example:
import numpy as np
import pylab as pl
num_t = 100000
t = np.linspace(0,1,num_t)
dt = 1.0/num_t
w = 2.0*np.pi*30.0
phase = np.pi/2.0
amp = np.fft.rfft(np.cos(w*t+phase))
freqs = np.fft.rfftfreq(t.shape[-1],dt)
print (np.arctan2(amp.imag,amp.real))[30]
pl.subplot(211)
pl.plot(freqs[:60],np.sqrt(amp.real**2+amp.imag**2)[:60])
pl.subplot(212)
pl.plot(freqs[:60],(np.arctan2(amp.imag,amp.real))[:60])
pl.show()
Using num=100000 points I get a phase of 1.57173880459.
Using num=10000 points I get a phase of 1.58022110476.
Using num=1000 points I get a phase of 1.6650441064.
What's going wrong? Even with 1000 points I have 33 points per cycle, which should be enough to resolve it. Is there maybe a way to increase the number of computed frequency points? Is there any way to do this with a "low" number of points?
EDIT: from further experimentation it seems that I need ~1000 points per cycle in order to accurately extract a phase. Why?!
EDIT 2: further experiments indicate that accuracy is related to number of points per cycle, rather than absolute numbers. Increasing the number of sampled points per cycle makes phase more accurate, but if both signal frequency and number of sampled points are increased by the same factor, the accuracy stays the same.
Your points are not distributed equally over the interval, you have the point at the end doubled: 0 is the same point as 1. This gets less important the more points you take, obviusly, but still gives some error. You can avoid it totally, the linspace has a flag for this. Also it has a flag to return you the dt directly along with the array.
Do
t, dt = np.linspace(0, 1, num_t, endpoint=False, retstep=True)
instead of
t = np.linspace(0,1,num_t)
dt = 1.0/num_t
then it works :)
The phase value in the result bin of an unrotated FFT is only correct if the input signal is exactly integer periodic within the FFT length. Your test signal is not, thus the FFT measures something partially related to the phase difference of the signal discontinuity between end-points of the test sinusoid. A higher sample rate will create a slightly different last end-point from the sinusoid, and thus a possibly smaller discontinuity.
If you want to decrease this FFT phase measurement error, create your test signal so the your test phase is referenced to the exact center (sample N/2) of the test vector (not the 1st sample), and then do an fftshift operation (rotate by N/2) so that there will be no signal discontinuity between the 1st and last point in your resulting FFT input vector of length N.
This snippet of code might help:
def reconstruct_ifft(data):
"""
In this function, we take in a signal, find its fft, retain the dominant modes and reconstruct the signal from that
Parameters
----------
data : Signal to do the fft, ifft
Returns
-------
reconstructed_signal : the reconstructed signal
"""
N = data.size
yf = rfft(data)
amp_yf = np.abs(yf) #amplitude
yf = yf*(amp_yf>(THRESHOLD*np.amax(amp_yf)))
reconstructed_signal = irfft(yf)
return reconstructed_signal
The 0.01 is the threshold of amplitudes of the fft that you would want to retain. Making the THRESHOLD greater(more than 1 does not make any sense), will give
fewer modes and cause higher rms error but ensures higher frequency selectivity.
(Please adjust the TABS for the python code)