I am trying to calculate a signal-frequency by using scipy FFT. By calculating the frequency "by hand" its obviously around 2.5 Hz.
So this is my input signal:
Signal Amplitude over Time
this is the code i am using:
import matplotlib.pyplot as plt
import numpy as np
from scipy import fft
##---Get Data
AccZ = np.loadtxt('...', delimiter=';', usecols=(0,3))
Time = np.array(AccZ[:,0])
AccZ = np.array(AccZ[:,1])
Time_Step = Time[1]-Time[0]
##---FFT
AccZ_fft = fft.fft(AccZ)
Amp = np.abs(AccZ_fft)
Sample_Freq = fft.fftfreq(AccZ.size, d=Time_Step)
Amp_Freq = np.array([Amp, Sample_Freq])
Amp_Pos = Amp_Freq[0,:].argmax()
Peak_Freq = Amp_Freq[1, Amp_Pos]
This is what i get from the FFT:
Amplitude over Frequency
Unfortunatly my highest value is always in array position [0] which means my peak frequency from my Sample_Freq array is always 0.
What am i doing wrong here? Would apreciate any help.
The zeroth bin is always the DC component, i.e. vertical offset of the input function. If you're not interested in this value, you can simply do:
AccZ_fft[0] = 0
This is equivalent to doing this to the input:
AccZ -= np.mean(AccZ)
and should simplify the the peak finding.
By the way, for real-valued input signals (as yours seems to be), it is advisable to use np.fft.rfft instead of np.fft.fft. This way, you won't get this symmetrical output with redundant positive and negative frequencies.
Related
I didn't add a phase to my cosine function but nonetheless I get a phase. Does anyone know why?
Here is the result:
Here is the code:
import numpy as np
import matplotlib.pyplot as plt
import scipy.fftpack
from scipy.fftpack import fftfreq
from scipy.fft import fft, fftshift
from skimage.filters import window
k = np.linspace(0,50,2400)
rx = np.array([0.4])
outer = np.outer(rx,k)
y = 0.5*np.cos(2*np.pi*outer)
yy = np.sum(y,axis=0)
f = fftfreq(len(k), np.diff(k)[0])
yf = fft(yy, norm = "forward")
phase = np.angle(yf)
phase[np.abs(yf) < 0.1] = 0
plt.figure(2)
plt.xlim(0, 100)
plt.plot(phase[:k.size//2])
As Cris suggested you have non-integer number o periods of your cosine.
You have little mistake that causes this. You are using np.linspace which adds values both on start and end points provided (in your case 0 and 50). Since cosine period is 0.4, both far left and far right values will have the same phase (that is 0 degree/radians). From perspective of FFT, those points are next to each other -> both have the same phase -> its like two consecutive samples are the same, which does not happen in cosine function -> you get some phase (and some spectral leakage)
Try fixing linspace by removing last sample:
k = np.linspace(0,50 - 50/2400,2400)
This will make phase go out (there is only some negligible numeric error):
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 perform Fourier transform using numpy's fft as follows:
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(0,1, 128)
x = np.cos(2*np.pi*t)
s_fft = np.fft.fft(x)
s_fft_freq = np.fft.fftshift(np.fft.fftfreq(t.shape[-1], t[1]-t[0]))
plt.plot(s_fft_freq, np.abs(s_fft))
The result I get is
which is wrong, as I know the FT should peak at f = 1, as the frequency of the cos is 1.
What am I doing wrong?
You are only applying fftshift to the x-axis labels, not the actual FFT magnitudes - you just need to apply s_fft = np.fft.fftshift(np.fft.fft(x)) too.
There are 2 or 3 things you have gotten wrong:
The FFT will peak at two positions for a pure real-valued frequency. This is the plus and minus frequencies. The only way to get a single peak in the Fourier domain is by having a complex valued signal (or having the trivial DC component).
(if with f, you mean frequency index) When using the DFT, the number of samples will determine how many frequency components you have. At the highest frequency index, you are always close to the per-sample oscilation: (-1)^t
(if with f, you mean amplitude) There are many definitions of the DFT, affecting both the forward and backward transform. This will affect how the values are interpreted when reading the spectrum.
The script below filters frequencies by cutting of all frequencies larger than 6.
However instead of using the seemingly correct function rfftfreq, fftfreq is being used.
To my understandnig rfftfreq should be used together with rfft. Why does this code work although it uses fftfreq with rfft?
import numpy as np
from scipy.fftpack import rfft, irfft, fftfreq
time = np.linspace(0,10,2000)
signal = np.cos(5*np.pi*time) + np.cos(7*np.pi*time)
W = fftfreq(signal.size, d=time[1]-time[0])
f_signal = rfft(signal)
# If our original signal time was in seconds, this is now in Hz
cut_f_signal = f_signal.copy()
cut_f_signal[(W<6)] = 0
cut_signal = irfft(cut_f_signal)
Background: rfft gives an array sorting the fourier modes with real and imaginery in separate entries. Such as [R(0), R(1), I(1), ... R(N/2),I(N/2)] for R(n) and I(n) being the real and imaginery part of the fourier mode respectively. (assuming an even entry array)
Therefore, rfftfreq yields an array of frequencies corresponding to this array such as (assuming an even entry array and sampling spacing of 1):
[0, 1/n, 1/n, 2/n, 2/n ... n/(2n), n/(2n)]
However this code works with fftfreq where the output of the function is
[0, 1/n, ... n/(2n), -n/(2n), ..., -1/n]
Clearly, fftfreq should lead to wrong results when used together with rfft because the frequencies and FFT bins do not match.
You mis-specified the frequencies in the original signal.
A sine wave is parameterized according to this equation (from Wikipedia):
The factor 2 is missing in the definition of signal = np.cos(5*np.pi*time) + np.cos(7*np.pi*time). Therefore, the actual frequencies are
5*pi*t = 2*pi*t * f
f = (5*pi*t) / (2*pi*t) = 5 / 2
7*pi*t = 2*pi*t * f
f = (7*pi*t) / (2*pi*t) = 7 / 2
In words, the two frequencies are half of what you think they are. Ironically, that is why it seems to work with fftfreq instead of rfftfreq. The former covers twice the frequency range (positive and negative frequencies), and therefore compensates for the missing factor 2.
This is the correct code:
signal = np.cos(5 * 2*np.pi * time) + np.cos(7 * 2*np.pi * time)
W = rfftfreq(signal.size, d=time[1]-time[0])
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.