Frequency resolution issue using FFT in numpy - python

I use Tektronix oscilloscope to perform some signal acquisition. I get 10.000 measurement points (few signal periods) and I have to do a frequency analysis on that set of data. My signal is 8MHz sine wave. When I use either SciPy or NumPy I get the same result - frequencies are spreaded too wide. The distance between two values is 500kHz and the highest frequency is 2.5GHz (absurd). When I want to measure frequency bandwidth around 8MHz I can only get exact values of 7.5, 8.0 and 8.5 MHz. I tried to change sample spacing determined by (x[1]-x[0]) and I got nothing better.
def CalculateFFT(t_val,p_val):
x = t_val #Two parameters: [x,y] values
y = lambda x: p_val
com_signal = y(x) # Combined signal
FFT_val = abs(scipy.fft(com_signal))
freq_val = scipy.fftpack.fftfreq(len(com_signal), x[1]-x[0])
spec_val = 20*scipy.log10(FFT_val)
return freq_val, spec_val

It is worth reading in more depth how DFFTs work but you should always have the following formulae in mind. For a time series with n points and maximum time Tmax, the time resolution is given by dt = Tmax / n
A DFFT will produce n points with
Fmax = 1 / dt
dF = 1 / Tmax
You seem to suggest the maximum frequency is sufficient (so the time resolution is okay) but the frequency resolution isn't good enough: you need to collect more data, at the same time resolution.

If (1) the sampling time is too short, (2) you require higher estimation frequency accuracy, and, (3) you know that your signal is a sine wave, then you can fit the signal to a sine wave. Like in How do I fit a sine curve to my data with pylab and numpy?,
with the exception that the frequency needs to be added.
Here is an example figure with a frequency of around 8 MHz:
Below is the example code:
""" Modified from https://stackoverflow.com/a/16716964/6036470 """
from numpy import sin, linspace, pi,average;
from pylab import plot, show, title, xlabel, ylabel, subplot, scatter
from scipy import fft, arange, ifft
import scipy
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import leastsq
ff = 8e6; # frequency of the signal
Fs = ff*128; # sampling rate
Ts = 1.0/Fs; # sampling interval
t = arange(0,((1/ff)/128)*(128)*5,Ts) # time vector
A = 2.5;
ff_0 = 8.1456e6
y = A*np.sin(2*np.pi*ff_0*t+15.38654*pi/180) + np.random.randn(len(t))/5
guess_b = 0
guess_a = y.std()*2**0.5;
guess_c = 10*pi/180
guess_d = ff*0.98*2*pi
fig = plt.figure(facecolor="white")
plt.plot(t,y,'.', label='Signal Fred. %0.4f Hz'%(ff_0/1e6))
plt.xlabel('Time')
plt.ylabel('Amplitude')
plt.grid(alpha=0.5);
optimize_func = lambda x: (x[0]*np.sin(x[2]*t+x[1]) - y);
est_a, est_c, est_d = leastsq(optimize_func, [guess_a, guess_c, guess_d])[0]
data_fit = est_a*np.sin(est_d*t+est_c) ;
plt.plot(t,data_fit,label='Fitted Est. Freq. %0.4f Hz'%(est_d/(2*pi)/1e6))
plt.legend()
plt.tight_layout();
plt.show();
fig.save("sinfit.png")

Related

Dirac Delta and FFT in Python

Theoretically, the use of Fourier Transform with the Dirac Delta Function allows for the production of exponential functions in the time domain if Dirac Delta functions are in the frequency domain. I have tried to carry out this idea for the following code in an attempt to use sound waves to encode information stored in the frequency domain.
I've been working on a project to send data via sound waves.
The way I've carried this out is by encoding the data in pulses in the frequency domain, and then creating a unique sound wave containing that information. I've used python to plot that, which has given me:
import scipy as sp
import math
import numpy as np
from scipy import signal
from scipy import fft
import matplotlib.pyplot as plt
paynowString = "00020101021126380009SG.PAYNOW010100211+658128992803010520400005303702545800.005802SG59006009Singapore620401006304"
characters = []
def split(word):
return [char for char in word]
characters = split(paynowString)
print(characters)
ascii_characters = [ord(char) for char in characters]
print(ascii_characters)
positive_x_domain = np.zeros(9*len(ascii_characters))
rhs = []
xr = list(positive_x_domain)
for i in range(len(ascii_characters)):
rhs = rhs + xr[i*9:(i*9+9)]+[ascii_characters[i]]
positive_x_domain = rhs + xr[(i+9)*9:]
positive_x_domain = np.asarray(positive_x_domain)
print(positive_x_domain)
negative_x_domain = np.flip(positive_x_domain)
print(negative_x_domain)
origin = np.array([0])
x_domain = np.concatenate((negative_x_domain, origin), axis = None)
x_domain = np.concatenate((x_domain, positive_x_domain), axis = None)
print(x_domain)
plt.plot(np.arange((-(len(x_domain)-1)/2), ((len(x_domain)-1)/2)+1), x_domain)
plt.margins(0.1, 0.1)
plt.xlabel('Time [samples]')
plt.ylabel('Amplitude')
plt.grid(True)
plt.show()
After which, this gives me a graph that represents the frequency domain:
Following this, I then apply the Fourier Transform on the frequency graph to get:
sound_wave = fft.ifft(x_domain)
plt.plot((-(len(x_domain)-1)/2), (((len(x_domain)-1)/2)+1), sound_wave)
plt.margins(0.1, 0.1)
plt.xlabel('Time [samples]')
plt.ylabel('Amplitude')
plt.axis([-0.1, 2500, -0.1, 1])
plt.grid(True)
plt.show()
#end of sending the sound wave
This gives me another graph that represents the sound wave:
In this attempt, I tried to carry this out by making sample points in the frequency domain to represent the Dirac Delta function, but this does not give me a periodic sound wave as the result due to the issue of not producing a completely exponential function in the time domain.
Is there any way I can change my representation in the frequency domain to properly emulate the Dirac Delta function such that the result in the time domain is periodic? Or is there a better alternative that allows me to produce a periodic sound wave in the time domain without using the Dirac Delta function?

Is there an easy way of finding frequency of envelopes in sound signals?

I have a sound signal of 5 secs length and it is from the sound of a propeller. I need to find rpm of the propeller by finding frequency of the envelopes.
import wave
import numpy as np
import matplotlib.pyplot as plt
raw = wave.open('/content/drive/MyDrive/Demon.wav','r')
signal = raw.readframes(-1)
signal = np.frombuffer(signal , dtype="int16")
frate = raw.getframerate()
time = np.linspace(0,len(signal) / frate,num = len(signal))
plt.figure(1)
plt.title("Sound Wave")
plt.xlabel("Time")
plt.plot(time, signal)
plt.show()
Here is the link to the sound file itself: https://sndup.net/5v3j
And since it is a 5 second-length signal and has 80.000 samples, I want to see it in details by looking 1 second part of the signal.
partial_signal = signal [1 : 16000]
partial_time = time[1 : 16000]
plt.plot(partial_time,partial_signal)
plt.show()
Output of the plot is shown below.
Edit: Looks like image will not show up here is the link to the image:
https://imgur.com/P5lnSM1
Now I need to find frequency of the envelopes thus rpm of the propeller by using only python.
You can do that quite easily with a fast Fourier transform (FFT) applied on the signal amplitude. Here is an example:
import wave
import numpy as np
import matplotlib.pyplot as plt
from scipy.fft import rfft, rfftfreq
from scipy.ndimage import gaussian_filter
raw = wave.open('Demon.wav','r')
signal = raw.readframes(-1)
signal = np.frombuffer(signal , dtype="int16")
frate = raw.getframerate()
time = np.linspace(0,len(signal) / frate,num = len(signal))
# Compute the amplitude of the sound signal
signalAmplitude = signal.astype(np.float64)**2
# Filter the signal to remove very short-timed amplitude modulations (<= 1 ms)
signalAmplitude = gaussian_filter(signalAmplitude, sigma=frate/1000)
# Compute the frequency amplitude of the FFT signal
tmpFreq = np.abs(rfft(signalAmplitude))
# Get the associated practical frequency for this signal
hzFreq = rfftfreq(signal.shape[0], d=1/frate)
finalFrequency = hzFreq[1+tmpFreq[1:].argmax()]
print(finalFrequency)
# Show sound frequency diagram
plt.xticks(np.arange(21))
plt.xlim([1, 20]) # Show only interesting low frequencies
plt.plot(hzFreq, tmpFreq)
plt.show()
The frequency diagram is the following:
The final detected frequency is 3.0 Hz which is very consistent with what we can hear.

Python: Spectrum's BURG Algorithm and Plotting

I am trying to visualize a frequency spectrum using the BURG algroithm. The data that I am trying to visualize is the distance between heartbeats in milliseconds (e.g: [700, 650, 689, ..., 702]). Time distance is measured from R peak to R peak of next heartbeat.
Now I would like to visualize the frequency band with python's spectrum library (I'm a total noob). The minimum frequency that I am trying to display is 0.0033Hz, so all time differences in my dataset summarized are 5 Minutes long.
My approach was to first take the reciprocal of each value, then multiply by 1000, and then multiply by 60. This should get me the Bpm for each heartbeat.
This is what it looks like: [67.11409396 64.72491909 ... 64.58557589]
Afterwards I use spectrum's burg algorithm to create the PSD. The "data" list contains my BpM for each heartbeat.
AR, rho, ref = arburg(data.tolist(), 7)
PSD = arma2psd(AR, rho=rho, NFFT=1024)
PSD = PSD[len(PSD):len(PSD)//2:-1]
plot(linspace(0, 0.5, len(PSD)), 10*log10(abs(PSD)*2./(1.*pi)))
pylab.legend(['PSD estimate of x using Burg AR(7)'])
The graph that I get looks like this:
5 Minutes Spectrogram
This specific data already exists as a 3D-Spectrogram (Graph above is the equivalent to the last 5 Minutes of 3D-Spectrogram):
Long Time 3D-Spectrogram
My Graph does not seem to match the 3D-Spectrogram. My frequencies are way off.... What causes this and how can I fix it?
Also I would like the y-Axis in my Graph not in [dB] but in absolute Values. I tried with:
plot(linspace(0, 0.5, len(PSD)), abs(PSD))
but that did not really seem to work. It just drew a hyperbole.
Thank you for your help!
The spectrum package comes with a pburg class than can generate a frequencies array, this is shown below. If you want direct comparison between a spectrogram and AR PSDs, I would take the time definition used to compute the spectrogram to also compute the AR PSD per window.
Also, your spectrogram example image looks focused on very low frequencies, so you may want to increase nfft to increase frequency resolution.
import matplotlib.pyplot as plt
from scipy.signal import spectrogram
import numpy as np
from spectrum import pburg
# Parameter settings
n_seconds = 10
fs = 1000 # sampling rate, in hz
freq = 10
nfft = 4096
nperseg = fs
order = 8
# Simulate 10 hz sine wave with white noise
x = np.sin(np.arange(0, n_seconds, 1/fs) * freq * 2 * np.pi)
x += np.random.rand(len(x)) / 10
# Compute spectrogram
freqs, times, powers = spectrogram(x, fs=fs, nfft=nfft)
# Get spectrogram time definition
times = (times * fs).astype(int)
window_times = np.array((times-times[0], times+times[0])).T
# Compute Burg's spectrum per window
powers_burg = np.array([pburg(x[t[0]:t[1]], order=order,
NFFT=nfft, sampling=fs).psd for t in window_times]).T
freqs_burg = np.array(pburg(x, order=order, NFFT=nfft, sampling=fs).frequencies())
# Plot
inds = np.where(freqs < 20)
inds_burg = np.where(freqs_burg < 20)
fig, axes = plt.subplots(ncols=2, figsize=(10, 5))
axes[0].pcolormesh(times/fs, freqs[inds], powers[inds], shading='gouraud')
axes[1].pcolormesh(times/fs, freqs_burg[inds_burg], powers_burg[inds_burg], shading='gouraud')
axes[0].set_title('Spectrogram')
axes[1].set_title('Burg\'s Spectrogram')

Generating Pulse Amplitude Modulation using sine wave and PWM signal using python

I am using the below codes so as to generate a Pulse Amplitude
Modulation signal by using the Boolean operation between sine wave and
Pulse Width Modulation(PWM) signal.I am using the vectorisation method
so as to get zero values where the PWM signal is low(zero or false) and
sine wave where the PWM values as high (True or one). Please refer the
below screen shot for the required output.In addition to this how do
you automate the PAM wave generation as I am facing problem with
spacing of x values?
import numpy as np
import matplotlib.pyplot as plt
from pylab import *
percent=50.0
TimePeriod=10.0 #Frozen Value Do not change
Cycles=10 #Frozen Value Do not change
dt=0.01 #Frozen Value Do not change
t=np.arange(0,Cycles*TimePeriod,dt);
pwm= t%TimePeriod<TimePeriod*percent/100
x=np.linspace(-10,10,10000) #Frozen Value Do not change
y=(np.sin(x))
y[(pwm =='False')] = 0 #Vectorisation for zero values
y[(pwm =='True')] = (y-pwm) # #Vectorisation for sine wave
plt.plot(t,y)
plt.ylim([-3,3])
plt.grid()
plt.show()
When removing the line y[(pwm =='True')] = (y-pwm) (which I don't understand) and not comparing to strings, you would get the following, which looks pretty much like the desired plot.
import numpy as np
import matplotlib.pyplot as plt
percent=40.0
TimePeriod=10.0
Cycles=30
dt=0.01
t=np.arange(0,Cycles*TimePeriod,dt);
pwm= (t%TimePeriod) < (TimePeriod*percent/100)
x=np.linspace(-10,10,len(pwm))
y=(np.sin(x))
y[pwm == 0] = 0
plt.plot(t,y)
plt.ylim([-3,3])
plt.grid()
plt.show()

Phase shift of the signal after using FIR filter firwin in Python

So after my two last questions I come to my actual problem. Maybe somebody finds the error in my theoretical procedure or I did something wrong in programming.
I am implementing a bandpass filter in Python using scipy.signal (using the firwin function). My original signal consists of two frequencies (w_1=600Hz, w_2=800Hz). There might be a lot more frequencies that's why I need a bandpass filter.
In this case I want to filter the frequency band around 600 Hz, so I took 600 +/- 20Hz as cutoff frequencies. When I implemented the filter and reproduce the signal in the time domain using lfilter the right frequency is filtered.
To get rid of the phase shift I plotted the frequency response by using scipy.signal.freqz with the return h of firwin as numerator and 1 as predefined denumerator.
As described in the documentation of freqz I plotted the phase (== angle in the doc) as well and was able to look at the frequency response plot to get the phase shift for the frequency 600 Hz of the filtered signal.
So the phase delay t_p is
t_p=-(Tetha(w))/(w)
Unfortunately when I add this phase delay to the time data of my filtered signal, it has not got the same phase as the original 600 Hz signal.
I added the code. It is weird, before eliminating some part of the code to keep the minimum, the filtered signal started at the correct amplitude - now it is even worse.
################################################################################
#
# Filtering test
#
################################################################################
#
from math import *
import numpy as np
from scipy import signal
from scipy.signal import firwin, lfilter, lti
from scipy.signal import freqz
import matplotlib.pyplot as plt
import matplotlib.colors as colors
################################################################################
# Nb of frequencies in the original signal
nfrq = 2
F = [60,80]
################################################################################
# Sampling:
nitper = 16
nper = 50.
fmin = np.min(F)
fmax = np.max(F)
T0 = 1./fmin
dt = 1./fmax/nitper
#sampling frequency
fs = 1./dt
nyq_rate= fs/2
nitpermin = nitper*fmax/fmin
Nit = int(nper*nitpermin+1)
tps = np.linspace(0.,nper*T0,Nit)
dtf = fs/Nit
################################################################################
# Build analytic signal
# s = completeSignal(F,Nit,tps)
scomplete = np.zeros((Nit))
omg1 = 2.*pi*F[0]
omg2 = 2.*pi*F[1]
scomplete=scomplete+np.sin(omg1*tps)+np.sin(omg2*tps)
#ssingle = singleSignals(nfrq,F,Nit,tps)
ssingle=np.zeros((nfrq,Nit))
ssingle[0,:]=ssingle[0,:]+np.sin(omg1*tps)
ssingle[1,:]=ssingle[0,:]+np.sin(omg2*tps)
################################################################################
## Construction of the desired bandpass filter
lowcut = (60-2) # desired cutoff frequencies
highcut = (60+2)
ntaps = 451 # the higher and closer the signal frequencies, the more taps for the filter are required
taps_hamming = firwin(ntaps,[lowcut/nyq_rate, highcut/nyq_rate], pass_zero=False)
# Use lfilter to get the filtered signal
filtered_signal = lfilter(taps_hamming, 1, scomplete)
# The phase delay of the filtered signal
delay = ((ntaps-1)/2)/fs
plt.figure(1, figsize=(12, 9))
# Plot the signals
plt.plot(tps, scomplete,label="Original signal with %s freq" % nfrq)
plt.plot(tps-delay, filtered_signal,label="Filtered signal %s freq " % F[0])
plt.plot(tps, ssingle[0,:],label="original signal %s Hz" % F[0])
plt.grid(True)
plt.legend()
plt.xlim(0,1)
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
# Plot the frequency responses of the filter.
plt.figure(2, figsize=(12, 9))
plt.clf()
# First plot the desired ideal response as a green(ish) rectangle.
rect = plt.Rectangle((lowcut, 0), highcut - lowcut, 5.0,facecolor="#60ff60", alpha=0.2,label="ideal filter")
plt.gca().add_patch(rect)
# actual filter
w, h = freqz(taps_hamming, 1, worN=1000)
plt.plot((fs * 0.5 / np.pi) * w, abs(h), label="designed rectangular window filter")
plt.xlim(0,2*F[1])
plt.ylim(0, 1)
plt.grid(True)
plt.legend()
plt.xlabel('Frequency (Hz)')
plt.ylabel('Gain')
plt.title('Frequency response of FIR filter, %d taps' % ntaps)
plt.show()'
The delay of your FIR filter is simply 0.5*(n - 1)/fs, where n is the number of filter coefficients (i.e. "taps") and fs is the sample rate. Your implementation of this delay is fine.
The problem is that your array of time values tps is not correct. Take a look
at 1.0/(tps[1] - tps[0]); you'll see that it does not equal fs.
Change this:
tps = np.linspace(0.,nper*T0,Nit)
to, for example, this:
T = Nit / fs
tps = np.linspace(0., T, Nit, endpoint=False)
and your plots of the original and filtered 60 Hz signals will line up beautifully.
For another example, see http://wiki.scipy.org/Cookbook/FIRFilter.
In the script there, the delay is calculated on line 86. Below this, the delay is used to plot the original signal aligned with the filtered signal.
Note: The cookbook example uses scipy.signal.lfilter to apply the filter. A more efficient approach is to use numpy.convolve.
Seems like you may have had this answered already, but I believe that this is what the filtfilt function is used for. Basically, it does both a forward sweep and a backward sweep through your data, thus reversing the phase shift introduced by the initial filtering. Might be worth looking into.

Categories