I am trying to use a Fast Fourier Transform to extract the amplitude and phase shift of two sinusoidal waves. By experimenting, I found out that transform returned from the FFT had an amplitude that was actually an N/2 times multiple of my actual signal (where N is the number of samples in the wave). So, to extract and plot the actual transform, I had to multiply the gain by 2/N.
The portion of the code showing this is attached below:
from scipy.fft import fft, rfft
import numpy as np
import matplotlib.pyplot as plt
N = 600 # number of sample points
d = 1.0 # time domain
f = 50 # frequency
u = 0.1 # mean inlet velocity
du = 0.1 # velocity perturbation rate
T = 1.0 / f # period
s = d/N # sample spacing
# 1st sine wave
x1 = np.linspace(0.0, d, N)
y1 = u*du* np.sin(f * 2.0*np.pi*x1)
yf1 = rfft(y1)
xf1 = np.linspace(0.0, 1.0/(2.0*s), N//2)
# 2nd sine wave
q = 0.08
dq = 0.1
phi = np.pi / 2 # phase delay (rad)
x2 = np.linspace(0.0, d, N)
y2 = q*dq* np.sin(f * 2.0*np.pi*x2 - phi)
yf2 = fft(y2)
xf2 = np.linspace(0.0, 1.0/(2.0*s), N//2)
#plt.plot(x,y)
plt.plot(xf1, 2.0/N * np.abs(yf1[0:N//2]))
plt.plot(xf2, 2.0/N * np.abs(yf2[0:N//2]))
plt.grid()
plt.show()
I cannot figure out why the FFT returns this amplitude multiplied by N/2.
A secondary problem I face is how to extract the phase shift (phi) from the 2 transformed waves. Any help would be appreciated.
Related
I am trying to estimate the instantaneous power spectrum density of two different frequency bands using scipy.signal.cwt and the morlet2 wavelet. This is my approach so far:
Generate sample data and set signal parameters
from scipy import signal
import matplotlib.pyplot as plt
import numpy as np
sampling_rate = 130 # hz
window_length = 50 # seconds
num_samples = sampling_rate * window_length # number of samples in each window
x = np.linspace(start = 0, stop = num_samples, num = num_samples) # sample numbers
data = np.cos(2 * np.pi * 7 * x) + signal.gausspulse(x - 0.4, fc=2) # generate signal value for each sample
plt.plot(data)
Create wavelet time-frequency matrix
width = 6 # morlet2 width
low_f = 0.01 #lowest frequency of interest
high_f = 0.5 # highest frequency of interest
freq = np.linspace(low_f, high_f, num = int(high_f / low_f)) # frequency resolution of time-frequency plot
widths = width * sampling_rate / (2 * freq * np.pi) # wavelet widths for all frequencies of interest
cwtm = signal.cwt(data, signal.morlet2, widths, w=width)
plt.pcolormesh(x, freq, np.abs(cwtm), cmap='viridis', shading='gouraud')
plt.show()
Calculate powers for bands of interest
time_point = 48.6 # time point at which to calculate instantaneous power
sample_index = int(time_point * sampling_rate) # sample index at which to calculate instantaneous power
# bands of interest
band1_low = 0.04 # hz
band1_hi = 0.15 # hz
band2_low = 0.15 # hz
band2_hi = 0.4 # hz
# row indices for band cutoffs in cwt matrix
band1_low = int(np.where(abs(freq - band1_low) < 0.0001)[0])
band1_hi = int(np.where(abs(freq - band1_hi) < 0.0001)[0])
band2_low = int(np.where(abs(freq - band2_low) < 0.0001)[0])
band2_hi = int(np.where(abs(freq - band2_hi) < 0.0001)[0])
# get rows x column in cwt matrix corresponding to band and time point
band1 = cwtm[band1_low: band1_hi, sample_index]
band2 = cwtm[band2_low: band2_hi, sample_index]
# calculate powers
band1_power = sum(abs(band1))
band2_power = sum(abs(band2))
I'm confused for 2 reasons:
I'm not sure I'm doing this entirely correctly vis a vis the scale to frequency conversion and selecting the band powers of interest. Relying on this documentation for reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.morlet2.html
How to account for signal attenuation at the edge of the window (altering wavelet width or some other parameter?)
Thanks!
I set up a sine wave of a certain amplitude, frequency and phase, and tried recovering the amplitude and phase:
import numpy as np
import matplotlib.pyplot as plt
N = 1000 # Sample points
T = 1 / 800 # Spacing
t = np.linspace(0.0, N*T, N) # Time
frequency = np.fft.fftfreq(t.size, d=T) # Normalized Fourier frequencies in spectrum.
f0 = 25 # Frequency of the sampled wave
phi = np.pi/6 # Phase
A = 50 # Amplitude
s = A * np.sin(2 * np.pi * f0 * t - phi) # Signal
S = np.fft.fft(s) # Unnormalized FFT
fig, [ax1,ax2] = plt.subplots(nrows=2, ncols=1, figsize=(10, 5))
ax1.plot(t,s,'.-', label='time signal')
ax2.plot(freq[0:N//2], 2/N * np.abs(S[0:N//2]), '.', label='amplitude spectrum')
plt.show()
index, = np.where(np.isclose(frequency, f0, atol=1/(T*N))) # Getting the normalized frequency close to f0 in Hz)
magnitude = np.abs(S[index[0]]) # Magnitude
phase = np.angle(S[index[0]]) # Phase
print(magnitude)
print(phase)
phi
#21785.02149316858
#-1.2093259641890741
#0.5235987755982988
Now the amplitude should be 50, instead of 21785, and the phase pi/6=0.524, instead of -1.2.
Am I misinterpreting the output, or the answer on the post referred to in the link above?
You need to normalize the fft by 1/N with one of the two following changes (I used the 2nd one):
S = np.fft.fft(s) --> S = 1/N*np.fft.fft(s)
magnitude = np.abs(S[index[0]]) --> magnitude = 1/N*np.abs(S[index[0]])
Don't use index, = np.where(np.isclose(frequency, f0, atol=1/(T*N))), the fft is not exact and the highest magnitude may
not be at f0, use np.argmax(np.abs(S)) instead which will give
you the peak of the signal which will be very close to f0
np.angle messes up (I think its one of those pi,pi/2 arctan offset
things) just do it manually with np.arctan(np.real(x)/np.imag(x))
use more points (I made N higher) and make T smaller for higher accuracy
since a DFT (discrete fourier transform) is double sided and has peak signals in both the negative and positive frequencies, the peak in the positive side will only be half the actual magnitude. For an fft you need to multiply every frequency by two except for f=0 to acount for this. I multiplied by 2 in magnitude = np.abs(S[index])*2/N
N = 10000
T = 1/5000
...
index = np.argmax(np.abs(S))
magnitude = np.abs(S[index])*2/N
freq_max = frequency[index]
phase = np.arctan(np.imag(S[index])/np.real(S[index]))
print(f"magnitude: {magnitude}, freq_max: {freq_max}, phase: {phase}") print(phi)
Output: magnitude: 49.996693276663564, freq_max: 25.0, phase: 0.5079341239733628
I'm working on my end of the degree thesis in which I have to measure the Sound Pressure Level of underwater recordings (wav files) at a particular frequency (2000Hz). So I came up with this code:
'''
def get_value(filename, f0, NFFT=8192, plot = False):
#Load audio
data, sampling_frequency = soundfile.read(filename)
# remove stereo
if len(data.shape)> 1:
data = data[:, 0]
# remove extra length
if len(data)>sampling_frequency:
data = data[0:sampling_frequency]
# remove DC
data = data - data.mean()
# power without filtering
total_power = 10*np.log10(np.mean(data**2))
# fft
NFFT = 4096 # number of samples in the FFT
window = np.array(1) #np.hamming(len(data))
fftdata = np.fft.fft(data / NFFT, n = NFFT)
SPL = 20 * np.log10(np.abs(fftdata)) # Sound Pressure Level [dB]
freq = np.linspace(0, sampling_frequency, NFFT) # frequency axis [Hz]
# take value at desired frequency
power_at_frequency = SPL[np.argmin(np.abs(freq-f0))]
print(power_at_frequency)
'''
However, I checked the value with audacity and is completely different.
Thanks beforehand.
If you are interested in only one frequency you don't have to compute the FFT you can simply use
totalEnergy = np.sum((data - np.mean(data)) ** 2)
freqEnergy = np.abs(np.sum(data * np.exp(2j * np.pi * np.arange(len(data)) * target_freq / sampling_freq)))
And if you are using FFT and the window size is not a multiple of the wave period the frequency will leak to other frequencies. To avoid this your
import numpy as np;
import matplotlib.pyplot as plt
sampling_frequency = 48000;
target_frequency = 2000.0;
ns = 1000000;
data = np.sin(2*np.pi * np.arange(ns) * target_frequency / sampling_frequency);
# power
print('a sine wave have power 0.5 ~', np.mean(data**2), 'that will be split in two ')
## Properly scaled frequency
plt.figure(figsize=(12, 5))
plt.subplot(121);
z = np.abs(np.fft.fft(data[:8192])**2) / 8192**2
print('tuned with 8192 samples', max(z), ' some power leaked in other frequencies')
plt.semilogy(np.fft.fftfreq(len(z)) * sampling_frequency, z)
plt.ylabel('power')
plt.title('some power leaked')
plt.subplot(122);
# 6000 samples = 1/8 second is multiple of 1/2000 second
z = np.abs(np.fft.fft(data[:6000])**2) / 6000**2
print('tuned with 6000 samples', max(z))
plt.semilogy(np.fft.fftfreq(len(z)) * sampling_frequency, z)
plt.xlabel('frequency')
plt.title('all power in exact two symmetric bins')
## FFT of size not multiple of 2000
print(np.sum(np.abs(np.fft.fft(data[:8192]))**2) / 8192)
I know there have been several questions about using the Fast Fourier Transform (FFT) method in python, but unfortunately none of them could help me with my problem:
I want to use python to calculate the Fast Fourier Transform of a given two dimensional signal f, i.e. f(x,y). Pythons documentation helps a lot, solving a few issues, which the FFT brings with it, but i still end up with a slightly shifted frequency compared to the frequency i expect it to show. Here is my python code:
from scipy.fftpack import fft, fftfreq, fftshift
import matplotlib.pyplot as plt
import numpy as np
import math
fq = 3.0 # frequency of signal to be sampled
N = 100.0 # Number of sample points within interval, on which signal is considered
x = np.linspace(0, 2.0 * np.pi, N) # creating equally spaced vector from 0 to 2pi, with spacing 2pi/N
y = x
xx, yy = np.meshgrid(x, y) # create 2D meshgrid
fnc = np.sin(2 * np.pi * fq * xx) # create a signal, which is simply a sine function with frequency fq = 3.0, modulating the x(!) direction
ft = np.fft.fft2(fnc) # calculating the fft coefficients
dx = x[1] - x[0] # spacing in x (and also y) direction (real space)
sampleFrequency = 2.0 * np.pi / dx
nyquisitFrequency = sampleFrequency / 2.0
freq_x = np.fft.fftfreq(ft.shape[0], d = dx) # return the DFT sample frequencies
freq_y = np.fft.fftfreq(ft.shape[1], d = dx)
freq_x = np.fft.fftshift(freq_x) # order sample frequencies, such that 0-th frequency is at center of spectrum
freq_y = np.fft.fftshift(freq_y)
half = len(ft) / 2 + 1 # calculate half of spectrum length, in order to only show positive frequencies
plt.imshow(
2 * abs(ft[:half,:half]) / half,
aspect = 'auto',
extent = (0, freq_x.max(), 0, freq_y.max()),
origin = 'lower',
interpolation = 'nearest',
)
plt.grid()
plt.colorbar()
plt.show()
And what i get out of this when running it, is:
Now you see that the frequency in x direction is not exactly at fq = 3, but slightly shifted to the left. Why is this?
I would assume that is has to do with the fact, that FFT is an algorithm using symmetry arguments and
half = len(ft) / 2 + 1
is used to show the frequencies at the proper place. But I don't quite understand what the exact problem is and how to fix it.
Edit: I have also tried using a higher sampling frequency (N = 10000.0), which did not solve the issue, but instead shifted the frequency slightly too far to the right. So i am pretty sure that the problem is not the sampling frequency.
Note: I'm aware of the fact, that the leakage effect leads to unphysical amplitudes here, but in this post I am primarily interested in the correct frequencies.
I found a number of issues
you use 2 * np.pi twice, you should choose one of either linspace or the arg to sine as radians if you want a nice integer number of cycles
additionally np.linspace defaults to endpoint=True, giving you an extra point for 101 instead of 100
fq = 3.0 # frequency of signal to be sampled
N = 100 # Number of sample points within interval, on which signal is considered
x = np.linspace(0, 1, N, endpoint=False) # creating equally spaced vector from 0 to 2pi, with spacing 2pi/N
y = x
xx, yy = np.meshgrid(x, y) # create 2D meshgrid
fnc = np.sin(2 * np.pi * fq * xx) # create a signal, which is simply a sine function with frequency fq = 3.0, modulating the x(!) direction
you can check these issues:
len(x)
Out[228]: 100
plt.plot(fnc[0])
fixing the linspace endpoint now means you have an even number of fft bins so you drop the + 1 in the half calc
matshow() appears to have better defaults, your extent = (0, freq_x.max(), 0, freq_y.max()), in imshow appears to fubar the fft bin numbering
from scipy.fftpack import fft, fftfreq, fftshift
import matplotlib.pyplot as plt
import numpy as np
import math
fq = 3.0 # frequency of signal to be sampled
N = 100 # Number of sample points within interval, on which signal is considered
x = np.linspace(0, 1, N, endpoint=False) # creating equally spaced vector from 0 to 2pi, with spacing 2pi/N
y = x
xx, yy = np.meshgrid(x, y) # create 2D meshgrid
fnc = np.sin(2 * np.pi * fq * xx) # create a signal, which is simply a sine function with frequency fq = 3.0, modulating the x(!) direction
plt.plot(fnc[0])
ft = np.fft.fft2(fnc) # calculating the fft coefficients
#dx = x[1] - x[0] # spacing in x (and also y) direction (real space)
#sampleFrequency = 2.0 * np.pi / dx
#nyquisitFrequency = sampleFrequency / 2.0
#
#freq_x = np.fft.fftfreq(ft.shape[0], d=dx) # return the DFT sample frequencies
#freq_y = np.fft.fftfreq(ft.shape[1], d=dx)
#
#freq_x = np.fft.fftshift(freq_x) # order sample frequencies, such that 0-th frequency is at center of spectrum
#freq_y = np.fft.fftshift(freq_y)
half = len(ft) // 2 # calculate half of spectrum length, in order to only show positive frequencies
plt.matshow(
2 * abs(ft[:half, :half]) / half,
aspect='auto',
origin='lower'
)
plt.grid()
plt.colorbar()
plt.show()
zoomed the plot:
I am trying to apply a Hann window to a sinusoidal signal with the idea of applying an FFT to recover the frequency and the amplitude. This is a canonical case I have created to increase my understanding before I move onto my data (real time signal where I want to accurately determine the frequency content and amplitude). In the code below I need to multiple by an additional factor of 2.0 to recover the amplitudes. I understand multiplying by 2/N but now I am multiplying by 4/N. Has anyone come across this or can anyone provide an explanation to why this is? Here is my code:
import numpy as np
import matplotlib.pyplot as plt
# first create the time signal, which has two frequencies 13.2 hz and 43.9 hz
f_s = 100.0 # Hz sampling frequency
f = 1.0 # Hz
time = np.arange(0.0, 10.0, 1/f_s)
x = 5 * np.sin(13.2 * 2 * np.pi * f * time) + 3 * np.sin(43.9 * 2 * np.pi * f * time)
x = x + np.random.randn(len(time)) #inject some noise
# apply hann window and take the FFT
win = np.hanning(len(x))
FFT = np.fft.fft(win * x) * 2.0 # IT SEEMS I NEED AN ADDITIONAL FACTOR OF 2 TO RECOVER THE AMPLITUDES
n = len(FFT)
freq_hanned = np.fft.fftfreq(n, 1/f_s)
half_n = np.ceil(n/2.0)
fft_hanned_half = (2.0 / n) * FFT[:half_n]
freq_hanned_half = freq_hanned[:half_n]
# and plot
plt.plot(freq_hanned_half, np.abs(fft_hanned_half))
plt.xlabel("Frequency (Hz)")
plt.ylabel("Amplitude")
The mean value of the von Hann window is (approximately) 0.5, for N=1000 you have
>>> N=1000 ; print sum(np.hanning(N))/N
0.4995
>>>
Does this explain the necessity of multiplying by two to recover the discrete amplitudes?