Python, FFT and DC offset analysis - python

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

Related

How to get phase DC offset and amplitude of sine wave in Python

I have a sine wave of the known frequency with some noise with uniform samples near Nyquist frequency. I want to get approximate values of amplitude, phase, and DC offset.
I searched for an answer and found a couple of answers close to what I needed, but still was unable to write a proper code that achieves what I need.
When I run the code below, I get the wrong phase and amplitude.
Would be happy to get some help.
import sys
import numpy
import pylab as plt
def cosfunc(time, amplitude, omega, phase, offset):
''' Function to create sine wave. Phase in radians '''
return amplitude * numpy.cos(omega*time + phase) + offset
def get_cosine_approx(timeline,sine_data):
points_num=len(timeline)
fft_freq = numpy.fft.fftfreq(points_num-1, timeline[1]-timeline[0]) # assume uniform spacing
fft_result=numpy.fft.fft(sine_data)
#Remove negative frequencies
for i in range(len(fft_freq)):
if fft_freq[i]<0:
fft_result[i]=0
ampl=numpy.abs(fft_result)/points_num*2
max_index=numpy.argmax(ampl)
guess_amplitude=ampl[max_index]
phase_unwrapped=numpy.unwrap(numpy.angle(fft_result))
guess_phase=phase_unwrapped[max_index]
guess_phase_dig=guess_phase*180./numpy.pi
print("freq",fft_freq[max_index])
print("amplitude",guess_amplitude)
print("phase",guess_phase_dig)
plt.plot(timeline, sine_data, "ok", label="sine")
new_timeline=numpy.linspace(timeline[0], timeline[-1], len(timeline)*1000)
plt.plot(new_timeline, cosfunc(new_timeline,guess_amplitude,2.*numpy.pi*56e9,guess_phase,0), "r-", label="fit")
plt.legend(loc="best")
plt.show()
return {"amp":guess_amplitude, "ph":guess_phase,"ph_dig":guess_phase_dig}
N = 256 # Sample points
f=56e9 #56GHz
t = numpy.linspace(0.0, 100./f, N) # Time
omega = 2.*numpy.pi*f
offset=0
phase=0
A=1.
cos=cosfunc(t,A,omega,phase,offset)
result=get_cosine_approx(t,cos)
You are catching the phase at an inflection point, where the phase is suddenly transitioning from +pi/2 to -pi/2, and the bin you are looking at is just partway through the downhill slide. This is just because the FFT results are not continuous. A single bin spans a range of frequencies.
Notice when we plot the phase and the amplitude:
import sys
import numpy as np
import matplotlib.pyplot as plt
def cosfunc(time, amplitude, omega, phase, offset):
''' Function to create sine wave. Phase in radians '''
return amplitude * np.cos(omega*time + phase) + offset
def get_cosine_approx(timeline,sine_data):
points_num=len(timeline)
fft_freq = np.fft.fftfreq(points_num, timeline[1]-timeline[0])
fft_result=np.fft.fft(sine_data)
fft_freq = np.fft.fftshift(fft_freq)
fft_result = np.fft.fftshift(fft_result)
ampl = np.abs(fft_result) * 2 / points_num
phase = np.angle(fft_result)
plt.plot(fft_freq, ampl, label='ampl' )
plt.plot(fft_freq, phase, label='phase' )
plt.legend(loc="best")
plt.show()
return 0
N = 256 # Sample points
f=56e9 #56GHz
t = np.linspace(0.0, 100./f, N) # Time
omega = 2.*np.pi*f
offset=0
phase=0
A=1.
cos=cosfunc(t,A,omega,phase,offset)
result=get_cosine_approx(t,cos)
The plot shows the inflection point right at the peak frequency bin.

Signal processing with Fourier transform

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)))

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()

Unexpected phase shift in the results of Fourier transform (np.fft)

I'm looking for a clarification of Fourier transform principles.
I try to do something quite simple: Create a signal (sine wave with a given frequency and phase shift) and recreate its params with Fourier transform. Frequency estimations work fine, but when it comes to phase, it looks like I get systematic shift (-pi/2).
import numpy as np
import matplotlib.pyplot as plt
duration = 4.0 # lenght of window (in sec)
ticks_per_sec = 400.0 # sampling interval
samples = int(ticks_per_sec*duration)
phase_shift = np.pi / 2 # sin wave shift in angle
freq = 1 # sine wave freq in Hz
phase_shift = round(ticks_per_sec/freq * phase_shift/(2*np.pi)) # angle value translated to no of ticks
t = np.arange(phase_shift, samples+phase_shift) / ticks_per_sec
s = 1 * np.sin(2.0 * np.pi * freq * t)
N = s.size
fig, axs = plt.subplots(1, 3, figsize=(18, 6))
axs[0].grid(True)
axs[0].set_ylabel("Amplitude")
axs[0].set_xlabel("Time [s]")
axs[0].set_title(f"F: {freq}, Phase shift: {phase_shift} ticks.")
axs[0].plot(np.arange(samples)/ticks_per_sec, s)
f = np.linspace(0, ticks_per_sec, N)
fft = np.fft.fft(s)
peak_pos = np.argmax(np.abs(fft[:N//2]))
axs[1].set_ylabel("Amplitude")
axs[1].set_xlabel("Frequency [Hz]")
axs[1].set_title(f"Peak bar: {peak_pos}")
barlist = axs[1].bar(f[:N // 2], np.abs(fft)[:N // 2] * (1 / (N//2)), width=1.5) # 1 / N is a normalization factor
barlist[peak_pos].set_color('r')
axs[2].set_ylabel("Angle")
axs[2].set_xlabel("Frequency [Hz]")
axs[2].set_title(f"Peak angle: {np.angle(fft[peak_pos])}")
barlist = axs[2].bar(f[:N // 2], np.angle(fft)[:N // 2], width=1.5)
barlist[peak_pos].set_color('r')
fig.show()
Plotted Results of the code above
Please help me if there's a bug in my code that I can't notice, or I misunderstand something.
Thank you in advance.
Your code is just fine, this is not a programming issue.
Let's recall that a sine wave can be expressed as a cosine wave with a phase shift (or vice versa), now remember that sine function as an inherent phase shift of -pi/2 in real Fourier basis relatively to cosine.
This means that your code should output a pi/2 phase angle when replacing np.sin by np.cos, i.e. returns input phase_shift, or equivalently, returns a phase angle of zero when specifying phase_shift = np.pi / 2, i.e. phase shift and sine phase compensate each other.

Big FFT amplitude difference between the existing (synthesized) signal and the filtered signal

I think, what amplitude in the noised regions on the FFT-plot of the filtered signal should be lower, then now. Probably, small numeric deviations/errors presented in the scipy.fftpack.lfilter().
I tried to add noise to the existing signal, but no result.
Why FFT-amplitude of the filtered signal (green) in the noise regions are so high?
Update:
300 dB of an FFT-amplitude are non-physical - it's clear, it's due to 64bit float in the Python-environment.
FFT of the filtered signal (green) has so low dB (as ~67 dB) due to ~4000 samples at all signal (not 'per second' as on the picture, little mistake, not critical), sample rate = 200 samples/sec. 1 frequency bin=200/4000/2=0.025Hz, 2000 bins shown.
If we take a longer signal, we get a more frequency resolution per bin (i.e. 40 000samples, 1 freq bin = 200/40 000/2 = 0.0025 Hz). And also we get FFT of a filtered signal ~87 dB.
(Numbers 67 dB and 87 dB seems to be non-physical, because of an initial signal SNR 300dB, but I try to add some noise to the existing signal and get the same numbers)
If you want to get a picture of a non-depend FFT to the number of samples in a signal, you should use a windowed FFT and slide window to compute a whole-signal-FFT.
'''
Created on 13.02.2013, python 2.7.3
#author:
'''
from numpy.random import normal
from numpy import sin, pi, absolute, arange, round
#from numpy.fft import fft
from scipy.fftpack import fft, ifft
from scipy.signal import kaiserord, firwin, lfilter, freqz
from pylab import figure, clf, plot, xlabel, ylabel, xlim, ylim, title, grid, axis, show, log10,\
subplots_adjust, subplot
def filter_apply(filename):
pass
def sin_generator(freq_hz = 1000, sample_rate = 8000, amplitude = 1.0, time_s = 1):
nsamples = round(sample_rate * time_s)
t = arange(nsamples) / float(sample_rate.__float__())
signal = amplitude * sin(2*pi*freq_hz.__float__()*t)
return signal, nsamples
def do_fir(signal, sample_rate):
return signal
#-----------------make a signal---------------
freq_hz = 10.0
sample_rate = 400
amplitude = 1.0
time_s = 10
a1, nsamples = sin_generator(freq_hz, sample_rate, amplitude, time_s)
a2, nsamples = sin_generator(50.0, sample_rate, 0.5*amplitude, time_s)
a3, nsamples = sin_generator(150.0, sample_rate, 0.5*amplitude, time_s)
mu, sigma = 0, 0.1 # mean and standard deviation
noise = normal(mu, sigma, nsamples)
signal = a1 + a2 + a3 # + noise
#----------------create low-pass FIR----
# The Nyquist rate of the signal.
nyq_rate = sample_rate / 2.0
# The desired width of the transition from pass to stop,
# relative to the Nyquist rate. We'll design the filter
# with a 5 Hz transition width.
width = 5.0/nyq_rate
# The desired attenuation in the stop band, in dB.
ripple_db = 60.0
# Compute the order and Kaiser parameter for the FIR filter.
N, beta = kaiserord(ripple_db, width)
print 'N = ',N, 'beta = kaiser param = ', beta
# The cutoff frequency of the filter.
cutoff_hz = 30.0
# Use firwin with a Kaiser window to create a lowpass FIR filter.
# Length of the filter (number of coefficients, i.e. the filter order + 1)
taps = firwin(N, cutoff_hz/nyq_rate, window=('kaiser', beta))
# Use lfilter to filter x with the FIR filter.
filtered_signal = lfilter(taps, 1.0, signal)
#----------------plot signal----------------------
hh,ww=2,2
figure(figsize=(12,9))
subplots_adjust(hspace=.5)
#figure(1)
subplot(hh,ww,1)
# existing signal
x = arange(nsamples) / float(sample_rate)
# The phase delay of the filtered signal.
delay = 0.5 * (N-1) / sample_rate
# original signal
plot(x, signal, '-bo' , linewidth=2)
# filtered signal shifted to compensate for
# the phase delay.
plot(x-delay, filtered_signal, 'r-' , linewidth=1)
# Plot just the "good" part of the filtered signal.
# The first N-1 samples are "corrupted" by the
# initial conditions.
plot(x[N-1:]-delay, filtered_signal[N-1:], 'g', linewidth=2)
xlabel('time (s)')
ylabel('amplitude')
axis([0, 1.0/freq_hz*2, -(amplitude*1.5),amplitude*1.5]) # two periods of freq_hz
title('Signal (%d samples)' % nsamples)
grid(True)
#-------------- FFT of the signal
subplot(hh,ww,2)
signal_fft=fft(signal)
filtered_fft =fft(filtered_signal[N-1:])
# existing signal
y = 20*log10( ( abs( signal_fft/nsamples )*2.0)/max( abs( signal_fft/nsamples )*2.0) )# dB Amplitude
x = arange(nsamples)/float(nsamples)*float(sample_rate)
# filtered signal
y_filtered = 20*log10( (abs(filtered_fft/ (nsamples - N + 1) )*2.0)/max(abs(signal_fft/ (nsamples - N + 1) )*2.0) )# dB Amplitude
x_filtered = arange(nsamples - N + 1)/float(nsamples - N + 1)*float(sample_rate)
yy = fft(ifft(filtered_fft))
plot(x,y, linewidth=1)
plot(x_filtered, y_filtered, 'g', linewidth=2)
xlim(0, sample_rate/2) # compensation of mirror (FFT imaginary part)
xlabel('freq (Hz)')
ylabel('amplitude, (dB)')
title('Signal (%d samples)' % nsamples)
grid(True)
#--------------FIR ampitude response
subplot(hh,ww,3)
w, h = freqz(taps, worN=8000)
#plot((w/pi)*nyq_rate, absolute(h), linewidth=2)
plot((w/pi)*nyq_rate, 20*log10(absolute(h)/1.0),'r', linewidth=1)
xlabel('Frequency (Hz)')
#ylabel('Gain -blue')
ylabel('Gain (dB)')
title('Frequency Response')
#ylim(-0.05, 1.05)
grid(True)
#--------------FIR coeffs
subplot(hh,ww,4)
plot(taps, 'bo-', linewidth=2)
title('Filter Coefficients (%d taps)' % N)
grid(True)
show()
I think you -300dB of noise is pretty low. Keep in mind the this is a log scale, so we are talking about a couple of digits at 64bit resolution.
Using double precision float (64bit) you won't get any lower.

Categories

Resources