3

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:

FFT of signal

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.

Alienbash
  • 554
  • 7
  • 17
  • As your graph shows, the frequency domain is quantised - and it seems that your "problem" is that the quanta aren't centred on the frequency of your signal. That's JTWIW. Do you think the FFT should magically know that the input has a single frequency component? If you want it better centred you can increase the number of samples, or adjust the spacing. – DisappointedByUnaccountableMod Jul 31 '17 at 21:20
  • Of course FFT does not "magically" know that the input has a single frequency component, but as you suggested, one would expect, that this centering improves as the sampling frequency increases. I also did simulate this with "N" several orders of magnitude higher (N = 10000.0), which shifts the frequency slightly too far to the right. This brings me to the conclusion that it is not a matter of sampling frequency here. – Alienbash Jul 31 '17 at 21:30
  • So edit that research into the question rather than leaving possible answerers to waste their time wondering what other research you have done and not bothered to put into the question. Or not bothering to waste their time. – DisappointedByUnaccountableMod Jul 31 '17 at 21:33
  • As i stated in my question: I have an assumption, what the issue might be and it is not the resolution. It is a little childish to accuse me of withholding information, which just disproved your suggestion and then giving a down vote for that. – Alienbash Jul 31 '17 at 21:42
  • An assumption is different from having done more analysis and not included that in the question. Go figure. – DisappointedByUnaccountableMod Jul 31 '17 at 21:46
  • You were really confident that your suggestion did solve the problem and implied that it was easy to see, but it was actually not. So how about contributing something constructive, instead of just complaining? – Alienbash Jul 31 '17 at 21:54
  • @barny Actually the whole "discussion" with you is the waste of time you were complaining about without you adding just anything useful to the discussion at all. – Alienbash Mar 04 '20 at 10:54
  • Oh I say! - no need to throw the toys out of the pram. Holding a grudge for 2.5 years and then complaining about waste of time is pretty (unin)impressive. I note that one of the ‘number of issues’ (not my phrase, note) with your code that is identified in the answer you accepted is that the quantisation you’d assumed, and which I commented seemed to be a possible source of your problem, was in fact wrong. Ah well, I hope you have better luck your future endeavours. – DisappointedByUnaccountableMod Mar 04 '20 at 20:59

1 Answers1

3

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: enter image description here

f5r5e5d
  • 3,656
  • 3
  • 14
  • 18
  • Thank you a lot for your effort! This actually fixes the problem. "matshow()" seems to be the appropriate choice here. – Alienbash Jul 31 '17 at 23:32
  • This would raise another question: Isn't all the trouble with using 'fftfreq' and 'fftshift' kind of redundant, when just using 'matshow()' instead of 'imshow()' ? – Alienbash Jul 31 '17 at 23:40
  • i meant more general: Why would they be used at all, when they won't give the desired result in combination with 'imshow()' ? – Alienbash Jul 31 '17 at 23:58