How to limit frequency range using scipy FFT - python

I am using FFT do find the frequencies of a signal. I am only interested in a certain range of frequencies, between 1 and 4 Hz.
I have this code to compute frequencies:
from scipy.fft import rfft, rfftfreq, irfft
plt.plot(d)
plt.show()
N = len(d)
yf = rfft(d)
xf = rfftfreq(N, 1 / sample_rate) # 29
plt.plot(xf, np.abs(yf))
plt.show()
Which results in :
How do I modify my code so that xf and yf only correspond to frequencies in my desired range of 1-4 Hz, instead of the 0-15 seen in the plot?

You can use xlim feature of matplotlib to modify x axis.
Here is the example code that you can refer.
from scipy.fft import fft, fftfreq
import numpy as np
# Number of sample points
N = 600
# sample spacing
T = 1.0 / 800.0
x = np.linspace(0.0, N*T, N, endpoint=False)
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
yf = fft(y)
xf = fftfreq(N, T)[:N//2]
import matplotlib.pyplot as plt
plt.plot(xf, 2.0/N * np.abs(yf[0:N//2]),'b')
plt.plot()
plt.grid()
plt.show()
plt.plot(xf, 2.0/N * np.abs(yf[0:N//2]),'b')
plt.xlim(0,100) # you need this
plt.grid()
plt.show()

Related

Signal generation from "custom" fft

I am looking for a way to specify the shape of a frequency/amplitude plot (the result of an fft) by hand, so that I could use that to create a signal. I would think this would be a good way to generate a signal with multiple frequencies without having to superposition a number of sine/cosine curves. Is this possible?
I have tried working with ffts and iffts in scipy, in the case of a simple sine curve with a frequency of 50Hz I can plot the fft like:
from scipy.fft import fft, fftfreq, ifft
# Number of sample points
N = 600
# sample spacing
T = 1.0 / 800.0 # in seconds?
x = np.linspace(0.0, N*T, N, endpoint=False)
y = np.sin(50* 2 * np.pi * x) # np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
yf = fft(y)
xf = fftfreq(N, T)[:N//2]
import matplotlib.pyplot as plt
plt.plot(xf, 2.0/N * np.abs(yf[0:N//2]))
plt.grid()
plt.show()
And I can use the inverse fast fourier transform ifft to reconstruct the sine curve by
f_inv = ifft(yf)
However, yf in looks like this:

Unknown factor when using the SciPy FFTpack module

The following code example from this site illustrates the use of SciPy FFTpack. I am aware that the Fourier transformation in the real-valued case is symmetrical and therefore only the values of 0:N/2 are used in the representation. Unfortunately, I do not understand why these values are scaled with the factor 2.0/N. I would be very pleased to receive an explanation or a hint.
from scipy.fftpack import fft
# Number of sample points
N = 600
# sample spacing
T = 1.0 / 800.0
x = np.linspace(0.0, N*T, N)
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
yf = fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), N//2)
import matplotlib.pyplot as plt
plt.plot(xf, 2.0/N * np.abs(yf[0:N//2]))
plt.grid()
plt.show()

Plot a fourier transform of a sin wav with matplotlib

I am trying to plot a fourier transform of a sign wave based on the scipy documentation
import numpy as np
import matplotlib.pyplot as plt
import scipy.fft
def sinWav(amp, freq, time, phase=0):
return amp * np.sin(2 * np.pi * (freq * time - phase))
def plotFFT(f, speriod, time):
"""Plots a fast fourier transform
Args:
f (np.arr): A signal wave
speriod (int): Number of samples per second
time ([type]): total seconds in wave
"""
N = speriod * time
# sample spacing
T = 1.0 / 800.0
x = np.linspace(0.0, N*T, N, endpoint=False)
yf = scipy.fft.fft(f)
xf = scipy.fft.fftfreq(N, T)[:N//2]
plt.plot(xf, 2.0/N * np.abs(yf[0:N//2]))
plt.grid()
plt.xlim([1,3])
plt.show()
speriod = 1000
time = {
0: np.arange(0, 4, 1/speriod),
1: np.arange(4, 8, 1/speriod),
2: np.arange(8, 12, 1/speriod)
}
signal = np.concatenate([
sinWav(amp=0.25, freq=2, time=time[0]),
sinWav(amp=1, freq=2, time=time[1]),
sinWav(amp=0.5, freq=2, time=time[2])
]) # generate signal
plotFFT(signal, speriod, 12)
Desired output
I want to be getting a fourier transform graph which looks like this
Current output
But instead it looks like this
Extra
This is the sin wave I am working with
import numpy as np
import matplotlib.pyplot as plt
import scipy.fft
def sinWav(amp, freq, time, phase=0):
return amp * np.sin(2 * np.pi * (freq * time - phase))
def plotFFT(f, speriod, time):
"""Plots a fast fourier transform
Args:
f (np.arr): A signal wave
speriod (int): Number of samples per second
time ([type]): total seconds in wave
"""
N = speriod * time
# sample spacing
T = 1.0 / 800.0
x = np.linspace(0.0, N*T, N, endpoint=False)
yf = scipy.fft.fft(f)
xf = scipy.fft.fftfreq(N, T)[:N//2]
amplitudes = 1/speriod* np.abs(yf[:N//2])
plt.plot(xf, amplitudes)
plt.grid()
plt.xlim([1,3])
plt.show()
speriod = 800
time = {
0: np.arange(0, 4, 1/speriod),
1: np.arange(4, 8, 1/speriod),
2: np.arange(8, 12, 1/speriod)
}
signal = np.concatenate([
sinWav(amp=0.25, freq=2, time=time[0]),
sinWav(amp=1, freq=2, time=time[1]),
sinWav(amp=0.5, freq=2, time=time[2])
]) # generate signal
plotFFT(signal, speriod, 12)
You should have what you want. Your amplitudes were not properly computed, as your resolution and speriod were inconsistent.
Longer data acquisition:
import numpy as np
import matplotlib.pyplot as plt
import scipy.fft
def sinWav(amp, freq, time, phase=0):
return amp * np.sin(2 * np.pi * (freq * time - phase))
def plotFFT(f, speriod, time):
"""Plots a fast fourier transform
Args:
f (np.arr): A signal wave
speriod (int): Number of samples per second
time ([type]): total seconds in wave
"""
N = speriod * time
# sample spacing
T = 1.0 / 800.0
x = np.linspace(0.0, N*T, N, endpoint=False)
yf = scipy.fft.fft(f)
xf = scipy.fft.fftfreq(N, T)[:N//2]
amplitudes = 1/(speriod*4)* np.abs(yf[:N//2])
plt.plot(xf, amplitudes)
plt.grid()
plt.xlim([1,3])
plt.show()
speriod = 800
time = {
0: np.arange(0, 4*4, 1/speriod),
1: np.arange(4*4, 8*4, 1/speriod),
2: np.arange(8*4, 12*4, 1/speriod)
}
signal = np.concatenate([
sinWav(amp=0.25, freq=2, time=time[0]),
sinWav(amp=1, freq=2, time=time[1]),
sinWav(amp=0.5, freq=2, time=time[2])
]) # generate signal
plotFFT(signal, speriod, 48)
You can also interactively plot this. You may need to install the pip install scikit-dsp-comm
# !pip install scikit-dsp-comm
# Make an interactive version of the above
from ipywidgets import interact, interactive
import numpy as np
import matplotlib.pyplot as plt
from scipy import fftpack
plt.rcParams['figure.figsize'] = [10, 8]
font = {'weight' : 'bold',
'size' : 14}
plt.rc('font', **font)
def pulse_plot(fm = 1000, Fs = 2010):
tlen = 1.0 # length in seconds
# generate time axis
tt = np.arange(np.round(tlen*Fs))/float(Fs)
# generate sine
xt = np.sin(2*np.pi*fm*tt)
plt.subplot(211)
plt.plot(tt[:500], xt[:500], '-b')
plt.plot(tt[:500], xt[:500], 'or', label='xt values')
plt.ylabel('$x(t)$')
plt.xlabel('t [sec]')
strt2 = 'Sinusoidal Waveform $x(t)$'
strt2 = strt2 + ', $f_m={}$ Hz, $F_s={}$ Hz'.format(fm, Fs)
plt.title(strt2)
plt.legend()
plt.grid()
X = fftpack.fft(xt)
freqs = fftpack.fftfreq(len(xt)) * Fs
plt.subplot(212)
N = xt.size
# DFT
X = np.fft.fft(xt)
X_db = 20*np.log10(2*np.abs(X)/N)
#f = np.fft.fftfreq(N, 1/Fs)
f = np.arange(0, N)*Fs/N
plt.plot(f, X_db, 'b')
plt.xlabel('Frequency in Hertz [Hz]')
plt.ylabel('Frequency Domain\n (Spectrum) Magnitude')
plt.grid()
plt.tight_layout()
interactive_plot = interactive(pulse_plot,fm = (1000,20000,1000), Fs = (1000,40000,10));
output = interactive_plot.children[-1]
# output.layout.height = '350px'
interactive_plot

How to display a nice fft waterfall

The goal is to display a nice waterfall from an existing fft
Start from an existing fft which can be found at https://docs.scipy.org/doc/scipy/reference/tutorial/fftpack.html
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
from scipy.fftpack import fft
from mpl_toolkits.mplot3d import Axes3D
N = 600
T = 1.0 / 800.0
x = np.linspace(0.0, N*T, N)
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
yf = fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), N//2)
import matplotlib.pyplot as plt
plt.plot(xf, 2.0/N * np.abs(yf[0:N//2]))
plt.grid()
plt.show()
The result is the expected spectrum. Now we want to plot a waterfall as depicted at https://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html#wireframe-plots
We want something like that :
x, y = (xf, np.arange(N))
X,Y=np.meshgrid(x,y)
Z = yf
Axes3D.plot_wireframe(X, Y, Z,rstride=1,cstride=len(xf), lw=.5, alpha=0.5)
plt.show()
where X is the freq range of the Fft, Y the plot number axis and Z the 2D array with Fft data. But we get this error :
TypeError: plot_wireframe() missing 1 required positional argument: 'Z'
What is the problem ?
Thanks for the help.
To display a waterfall, you need a 2D array of FFTs (usually of different time windows), not just one FFT result.

FFT coefficients using python

I am a newbie in Signal Processing. In here, I want to ask how to get FFT coeffients from FFT from in python. This is the example of my code:
from scipy.fftpack import fft
# Number of samplepoints
N = 600
# sample spacing
T = 1.0 / 800.0
x = np.linspace(0.0, N*T, N)
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
yf = fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), N/2)
import matplotlib.pyplot as plt
plt.plot(xf, 2.0/N * np.abs(yf[0:N/2]))
plt.grid()
plt.show()
Hmm I don't really know about signal processing either but maybe this works:
from scipy.signal import argrelmax
f = xf[scipy.signal.argrelmax(yf[0:N/2])]
Af = np.abs(yf[argrelmax(yf[0:N/2])])
Quoting #hotpaw, in this similar answer:
"The real and imaginary arrays, when put together, can represent a complex array. Every complex element of the complex array in the frequency domain can be considered a frequency coefficient, and has a magnitude sqrt(RR + II))".
So, the coefficients are the complex elements in the array returned by the fft function. Also, it is important to play with the size (the number) of the bins for the FFT function. It would make sense to test a bunch of values and pick the one that makes more sense to your application. Often, it is in the same magnitude of the number of samples. This was as assumed by most of the answers given, and produces great and reasonable results. In case one wants to explore that, here is my code version:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import scipy.fftpack
fig = plt.figure(figsize=[14,4])
N = 600 # Number of samplepoints
Fs = 800.0
T = 1.0 / Fs # N_samps*T (#samples x sample period) is the sample spacing.
N_fft = 80 # Number of bins (chooses granularity)
x = np.linspace(0, N*T, N) # the interval
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x) # the signal
# removing the mean of the signal
mean_removed = np.ones_like(y)*np.mean(y)
y = y - mean_removed
# Compute the fft.
yf = scipy.fftpack.fft(y,n=N_fft)
xf = np.arange(0,Fs,Fs/N_fft)
##### Plot the fft #####
ax = plt.subplot(121)
pt, = ax.plot(xf,np.abs(yf), lw=2.0, c='b')
p = plt.Rectangle((Fs/2, 0), Fs/2, ax.get_ylim()[1], facecolor="grey", fill=True, alpha=0.75, hatch="/", zorder=3)
ax.add_patch(p)
ax.set_xlim((ax.get_xlim()[0],Fs))
ax.set_title('FFT', fontsize= 16, fontweight="bold")
ax.set_ylabel('FFT magnitude (power)')
ax.set_xlabel('Frequency (Hz)')
plt.legend((p,), ('mirrowed',))
ax.grid()
##### Close up on the graph of fft#######
# This is the same histogram above, but truncated at the max frequence + an offset.
offset = 1 # just to help the visualization. Nothing important.
ax2 = fig.add_subplot(122)
ax2.plot(xf,np.abs(yf), lw=2.0, c='b')
ax2.set_xticks(xf)
ax2.set_xlim(-1,int(Fs/6)+offset)
ax2.set_title('FFT close-up', fontsize= 16, fontweight="bold")
ax2.set_ylabel('FFT magnitude (power) - log')
ax2.set_xlabel('Frequency (Hz)')
ax2.hold(True)
ax2.grid()
plt.yscale('log')
Output:

Categories