Signal processing with Fourier transform - python

I need to process a periodic signal and obtain its frequency.
This can be easily done with scipy.fft and it works fine.
However, I have a particular signal that is not strictly periodic. The period changes slightly but randomly.
Applying the fft to this signal is quite hard. Many peaks are obtained and I cannot extrapolate only the range of frequencies that are near the (1/period) I am interested in.
How can I do this?
This is a simple code snippet of what I am doing:
df = pd.read_csv('data.txt', header=None)
x = np.asarray(df.iloc[:, 0])
y = np.asarray(df.iloc[:, 1])
yf = fft(y)
xf = fftfreq(len(y))
plt.plot(xf, abs(yf))
You can find an example of such signal at the following GitHub repository, inside the README file: https://github.com/crazydecibel/Stack-Overflow-question

What about taking the weighted average of frequency of top energy bins?
import numpy as np
import matplotlib.pyplot as plt
your_data = '15.042,...' # your github data
t, y = np.array([list(map(float, row.split(','))) for row in your_data.split()]).T
# here is the solution in terms of t and y.
Y = np.fft.fftshift(np.fft.fft(y))
Ts = (t[-1] - t[0]) / (len(t) + 1) # sampling period
Fs = 1 / Ts # sampling frequency
f = np.fft.fftshift(np.fft.fftfreq(len(y))) * Fs
# get top 5%
top = np.argsort(abs(Y))[-10:]
plt.subplot(211)
plt.stem(f, abs(Y), 'o')
plt.stem(f[top], abs(Y[top]), '+r')
plt.xlim(-0.05 * Fs, 0.05 * Fs)
fc = np.sum(abs(f[top]*Y[top]**2))/np.sum(abs(Y[top])**2)
plt.subplot(212)
plt.plot(t, y)
plt.plot(t, np.sin(t*(2*np.pi*fc)))

Related

scipy Fast fourier transform doesn't recognize the signal

i'm trying to get the frequency of a signal via fourier transform but it's not able to recognize it (sets the peak to f=0). Maybe something is wrong in my code (FULL reprudible code at the end of the page):
PF = fft.fft(Y[0,:])/Npoints #/Npoints to get the true amplitudes
ZF = fft.fft(Y[1,:])/Npoints
freq = fft.fftfreq(Npoints,deltaT)
PF = fft.fftshift(PF) #change of ordering so that the frequencies are increasing
ZF = fft.fftshift(ZF)
freq = fft.fftshift(freq)
plt.plot(freq, np.abs(PF))
plt.show()
plt.plot(T,Y[0,:])
plt.show()
where Npoints is the number of intervals (points) and deltaT is the time spacing of the intervals. You can see that the peak is at f=0
I show also a plot of Y[0,:] (my signal) over time where it's clear that the signal has a characteristic frequency
FULL REPRUDICIBLE CODE
import numpy as np
import matplotlib.pyplot as plt
#numerical integration
from scipy.integrate import solve_ivp
import scipy.fft as fft
r=0.5
g=0.4
e=0.6
H=0.6
m=0.15
#define a vector of K between 0 and 4 with 50 componets
K=np.arange(0.1,4,0.4)
tsteps=np.arange(7200,10000,5)
Npoints=len(tsteps)
deltaT=2800/Npoints #sample spacing
for k in K :
i=0
def RmAmodel(t,y):
return [r*y[0]*(1-y[0]/k)-g*y[0]/(y[0]+H)*y[1], e*g*y[0]/(y[1]+H)*y[1]-m*y[1]]
sol = solve_ivp(RmAmodel, [0,10000], [3,3], t_eval=tsteps) #t_eval specify the points where the solution is desired
T=sol.t
Y=sol.y
vk=[]
for i in range(Npoints):
vk.append(k)
XYZ=[vk,Y[0,:],Y[1,:]]
#check periodicity over P and Z with fourier transform
#try Fourier analysis just for the last value of K
PF = fft.fft(Y[0,:])/Npoints #/Npoints to get the true amplitudes
ZF = fft.fft(Y[1,:])/Npoints
freq = fft.fftfreq(Npoints,deltaT)
PF = fft.fftshift(PF) #change of ordering so that the frequencies are increasing
ZF = fft.fftshift(ZF)
freq = fft.fftshift(freq)
plt.plot(T,Y[0,:])
plt.show()
plt.plot(freq, np.abs(PF))
plt.show()
I can't pinpoint where the problem is. It looks like there is some problem in the fft code. Anyway, I have little time so I will just put a sample code I made before. You can use it as reference or copy-paste it. It should work.
import numpy as np
import matplotlib.pyplot as plt
from scipy.fft import fft, fftfreq
fs = 1000 #sampling frequency
T = 1/fs #sampling period
N = int((1 / T) + 1) #number of sample points for 1 second
t = np.linspace(0, 1, N) #time array
pi = np.pi
sig1 = 1 * np.sin(2*pi*10*t)
sig2 = 2 * np.sin(2*pi*30*t)
sig3 = 3 * np.sin(2*pi*50*t)
#generate signal
signal = sig1 + sig2 + sig3
#plot signal
plt.plot(t, signal)
plt.show()
signal_fft = fft(signal) #getting fft
f2 = np.abs(signal_fft / N) #full spectrum
f1 = f2[:N//2] #half spectrum
f1[1:] = 2*f1[1:] #actual amplitude
freq = fs * np.linspace(0,N/2,int(N/2)) / N #frequency array
#plot fft result
plt.plot(freq, f1)
plt.xlim(0,100)
plt.show()

Python, FFT and DC offset analysis

I test an FFT on a square signal (100Hz, 0V-5V) of 50% duty cycle and i don't understand why my DC offset is huge ?
In theory it should be 2.5V ?
Thanks,
Best regards.
The fundamental is ok and others harmonics are correct.
square signal 100Hz, TTL compatible 0V-5V, 50% duty cycle
FFT, DC offset problem, fundamental ok, harmonics ok
from scipy.fftpack import fft
from scipy import signal
import matplotlib.pyplot as plt
import numpy as np
#
# configuration
# time analyse = L * (1/Fsample)
#
L = 512 # lenght buffer
Fsample = 2000 # frequency sample
Fsignal = 100 # frequency
#********************************
# perdiode sample
Tsample = 1.0/Fsample
# time vector, start = 0s, stop = 0.1024s, step = (0.1024s / (1/Fe))
t = np.linspace(0.0, L*Tsample, L)
# signal definition, DC offet = 2.5V, Amplitude = 2.5V
signal = 2.5 + 2.5*signal.square(2 * np.pi * Fsignal * t, 0.5)
# plot time signal
plt.subplot(2,1,1)
plt.plot(t, signal)
# fft of time signal
yf = fft(signal)
# time vector of fft
xf = np.linspace(0.0, 1.0/(2.0*Tsample), L//2)
# plot spectral
plt.subplot(2,1,2)
plt.plot(xf, 2.0/L * np.abs(yf[0:L//2]))
On the last line, the normalization factor was wrong.
It was not 2/L but 1/L.
The correct normalization factor plt.plot(xf, 1.0/L * np.abs(yf[0:L//2]))
The code work fine now !
FFT correct amplitude

scipy fft returns null imaginary part

First of all, I apologize for being an absolute beginner in both python and signal processing.
I am trying to simulate an impulse signal (or a delta function) propagating along spatial x-axis over time. Then, I would like to perform Fourier Transformation on amplitude vs x-axis for each time and then amplitude vs t-axis for each point in space. The problem I'm facing is that the Fourier coefficients are all real valued. If I "implot" the imaginary part over spatial and temporal axis, you can see, all of these are shown to be zero. However, my understanding was that, the impulse signal at t = 0, x = 0, should have null imaginary coefficient. But after that, for all the other t and/or x's, there should be a real valued imaginary coefficient.
Please refer to this site http://madebyevan.com/dft/ where one can interactively make waveforms and observe the Fourier Transformation. In the f(x) box, please put "spike(x-0)", "spike(x-1)" etc. to simulate my problem and expected result.
I have tried the following code using scipy.fftpack. There are some extra lines to analyze the impulse signal travelling in x axis and x-t plane.
import numpy as np
from numpy import pi
import matplotlib.pyplot as plt
from scipy import signal
import math
import scipy.fftpack
from scipy import ndimage
L = 10
k = np.pi/L
w = np.pi*2
n = 5
# Number of samplepoints
Nx = 1000
Nt = 500
# sample spacing
l = 1.0/Nx
T = 1.0/Nt
x = np.linspace(0, Nx*l*L, Nx)
t = np.linspace(0, Nt*T*L, Nt)
x = np.round(x,2)
t = np.round(t,2)
# function to produce impulse
def gw(xx, tt):
if xx == tt:
kk = 1
else:
kk = 0
return (kk)
fig = plt.figure()
yg = np.array([gw(i, j) for j in t for i in x])
YG = yg.reshape(Nt, Nx)
# how impulse propagate in x-t plane
plt.imshow(YG, interpolation='bilinear',aspect='auto')
plt.colorbar();
# how impulse propagate in x-axis for t = 2 and t = 100
fig, ax = plt.subplots()
ax.plot(x, YG[2,:], x, YG[100,:])
plt.show()
# FFT in x-axis at each point in time
yxf = np.zeros((Nt, Nx))
for i in range(Nt):
yx = YG[i,:]
yxf[i,:] = scipy.fftpack.fft(yx)
plt.imshow(np.imag(yxf[:,:Nx]), interpolation='bilinear',aspect='auto')
plt.colorbar();
plt.show()
# FFT in t-axis at each point in space
ytf = np.zeros((Nt, Nx))
for i in range(Nx):
yt = YG[:,i]
ytf[:,i] = scipy.fftpack.fft(yt)
plt.imshow(np.imag(ytf[:Nt,:]), interpolation='bilinear',aspect='auto')
plt.colorbar();
plt.show()

Spectrogram vs. Scaleogram for Time-Varying Frequency

I am comparing FFT vs. CWT for a specific signal.
It is not clear to me how to read of the respective frequencies and amplitudes from the corresponding scaleogram of the CWT. Furthermore, I have the impression that the CWT is quite imprecise?
The spectrogram seems to be quite good in predicting the precise frequencies, but for the CWT, I tried many different wavelets and the result is the same.
Did I oversee something? Is this just not the appropriate tool for this problem?
Below, you find my example sourcecode and the corresponding plot.
import matplotlib.pyplot as plt
import numpy as np
from numpy import pi as π
from scipy.signal import spectrogram
import pywt
f_s = 200 # Sampling rate = number of measurements per second in [Hz]
t = np.arange(-10,10, 1 / f_s) # Time between [-10s,10s].
T1 = np.tanh(t)/2 + 1.0 # Period in [s]
T2 = 0.125 # Period in [s]
f1 = 1 / T1 # Frequency in [Hz]
f2 = 1 / T2 # Frequency in [Hz]
N = len(t)
x = 13 * np.sin(2 * π * f1 * t) + 42 * np.sin(2 * π * f2 * t)
fig, (ax1, ax2, ax3, ax4) = plt.subplots(4,1, sharex = True, figsize = (10,8))
# Signal
ax1.plot(t, x)
ax1.grid(True)
ax1.set_ylabel("$x(t)$")
ax1.set_title("Signal x(t)")
# Frequency change
ax2.plot(t, f1)
ax2.grid(True)
ax2.set_ylabel("$f_1$ in [Hz]")
ax2.set_title("Change of frequency $f_1(t)$")
# Moving fourier transform, i.e. spectrogram
Δt = 4 # window length in [s]
Nw = np.int(2**np.round(np.log2(Δt * f_s))) # Number of datapoints within window
f, t_, Sxx = spectrogram(x, f_s, window='hanning', nperseg=Nw, noverlap = Nw - 100, detrend=False, scaling='spectrum')
Δf = f[1] - f[0]
Δt_ = t_[1] - t_[0]
t2 = t_ + t[0] - Δt_
im = ax3.pcolormesh(t2, f - Δf/2, np.sqrt(2*Sxx), cmap = "inferno_r")#, alpha = 0.5)
ax3.grid(True)
ax3.set_ylabel("Frequency in [Hz]")
ax3.set_ylim(0, 10)
ax3.set_xlim(np.min(t2),np.max(t2))
ax3.set_title("Spectrogram using FFT and Hanning window")
# Wavelet transform, i.e. scaleogram
cwtmatr, freqs = pywt.cwt(x, np.arange(1, 512), "gaus1", sampling_period = 1 / f_s)
im2 = ax4.pcolormesh(t, freqs, cwtmatr, vmin=0, cmap = "inferno" )
ax4.set_ylim(0,10)
ax4.set_ylabel("Frequency in [Hz]")
ax4.set_xlabel("Time in [s]")
ax4.set_title("Scaleogram using wavelet GAUS1")
# plt.savefig("./fourplot.pdf")
plt.show()
Your example waveform is composed of only a few pure tones at any given time, so the spectrogram plot is going to look very clean and readable.
The wavelet plot is going to look "messy" in comparison because you have to sum Gaussian wavelets of may different scales (and thus frequencies) to approximate each component pure tone in your original signal.
Both the short-time FFT and wavelet transforms are in the category time-frequency transforms, but have a different kernel. The kernel of the FFT is a pure tone, but the kernel of the wavelet transform you provided is a gaussian wavelet. The FFT pure tone kernel is going to cleanly correspond to the type of waveform you showed in your question, but the wavelet kernel will not.
I doubt that the result you got for different wavelets was numerically exactly the same. It probably just looks the same to the naked eye the way you've plotted it. It appears that you are using wavelets for the wrong purpose. Wavelets are more useful in analyzing the signal than plotting it. Wavelet analysis is unique as each datapoint in a wavelet composition encodes frequency, phase, and windowing information simultanesouly. This allows for designing algorithms that lie on a continuum between timeseries and frequency analysis, and can be very powerful.
As far as your claim about different wavelets giving the same results, this is clearly not true for all wavelets: I adapted your code, and it produces the image following it. Sure, GAUS2 and MEXH seem to produce similar plots (zoom in real close and you will find they are subtly different), but that's because the second-order gauss wavelet looks similar to the Mexican hat wavelet.
import matplotlib.pyplot as plt
import numpy as np
from numpy import pi as π
from scipy.signal import spectrogram, wavelets
import pywt
import random
f_s = 200 # Sampling rate = number of measurements per second in [Hz]
t = np.arange(-10,10, 1 / f_s) # Time between [-10s,10s].
T1 = np.tanh(t)/2 + 1.0 # Period in [s]
T2 = 0.125 # Period in [s]
f1 = 1 / T1 # Frequency in [Hz]
f2 = 1 / T2 # Frequency in [Hz]
N = len(t)
x = 13 * np.sin(2 * π * f1 * t) + 42 * np.sin(2 * π * f2 * t)
fig, (ax1, ax2, ax3, ax4) = plt.subplots(4,1, sharex = True, figsize = (10,8))
wvoptions=iter(['gaus1','gaus2','mexh','morl'])
axes=[ax1,ax2,ax3,ax4]
for ax in axes:
# Wavelet transform, i.e. scaleogram
try:
choice=next(wvoptions)
cwtmatr, freqs = pywt.cwt(x, np.arange(1, 512), choice, sampling_period = 1 / f_s)
im = ax.pcolormesh(t, freqs, cwtmatr, vmin=0, cmap = "inferno" )
ax.set_ylim(0,10)
ax.set_ylabel("Frequency in [Hz]")
ax.set_xlabel("Time in [s]")
ax.set_title(f"Scaleogram using wavelet {choice.upper()}")
except:
pass
# plt.savefig("./fourplot.pdf")
plt.tight_layout()
plt.show()

Strange FFT output python

I am trying to take the FFT and plot it. Problem is, my code works for small frequencies (like 50) but doesn't work for the bigger frequencies I need. What is going on with my code?! I expect to see a spike at the frequency of the sine wave I input, but the spike is at different frequencies depending on the sample spacing I use.
bins = 600
ss = 2048
freq = 44100
centerfreq = freq*bins/ss
# Number of samplepoints
N = ss
# sample spacing
T = 1 / 800.
x = np.linspace(0.0, N*T, N)
y = sin(2*np.pi*centerfreq*x)
yf = fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), N/2)
plt.plot(xf, 2.0/N * np.abs(yf[0:N/2]), 'r')
The code is right, you need to brush up your Fourier Theory and Nyquist Sampling Theorem and make sure the numbers make sense. The problem is with your x-axis scale. The plot function plots the first item in x with the first item in y, if x is not scaled up to your expectations, you are in for a surprise. You also see this if you plot a sinusoidal signal (sine wave) and expect 'degrees' and you get radians for instance. Its your duty to scale it up well so that it lines up to your expectation.
Refer to this SO answer https://stackoverflow.com/a/25735436/2061422.
from scipy import *
from numpy import *
from pylab import * # imports for me to get going
bins = 600
ss = 2048
freq = 44100
centerfreq = freq*bins/ss
print centerfreq
# Number of samplepoints
N = ss
# sample spacing
T = 1. / freq # i have decreased the spacing considerably
x = np.linspace(0.0, N*T, N)
sample_spacing = x[1] - x[0] # but this is the real sample spacing
y = sin(2*np.pi*centerfreq*x)
yf = fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), N/2)
freqs = np.fft.fftfreq(len(y), sample_spacing) # read the manual on this fella.
plt.plot(freqs[:N/2], 1.0/N * np.abs(yf[0:N/2]), 'r')
plt.grid()
plt.show()

Categories

Resources