1
votes

I've created an FFT class/object that takes signal stored in a 2D array and produces the subsequent FFT of its input, before printing it to a matplotlib graph.

After a great deal of reading, I appreciate that due to windowing, the need to have an ideally 2^x number of points and an integer number of cycles in the data set, that the amplitude of the peaks will never be 100% accurate (but will be approximately right).

However, when I add a DC offset to the signal, for some reason, the 0 Hz frequency has a peak that is always exactly double the actual DC/constant offset! For example, if I add 2 to a sine wave of x Hz, I get a peak at x Hz on the FFT, and a peak of 4 at 0.

Why is this - and can I correct this?

Thanks!

The code I am using is below:

import numpy as np
import matplotlib.pyplot as plt

class FFT:
    def __init__(self, time, signal, buff=1, scaling=2, centre=False): 
        self.signal = signal
        self.buff = buff
        self.time = time
        self.scaling = scaling
        self.centre = centre
        if (centre):
            self.scaling = 1
    def fft(self):
        self.Y = np.fft.fft(self.signal, self.buff * len(self.signal))  # Do fft on signal and store
        if (self.centre is True):
            self.Y = np.fft.fftshift(self.Y)  # centre 0 frequency in centre
        self.__graph__()
    def __graph__(self):
        self.N = len(self.Y) / self.scaling  # get FFT length (halved to avoid reflection)
        print (self.N)
        self.fa = 1 / (self.time[1] - self.time[0])  # get time interval & sampling frequency of FFT
        if (self.centre is True):
        self.t_axis = np.linspace(-self.fa / 2 * self.scaling, self.fa / 2 * self.scaling, self.N, endpoint=True)  # create x axis vector from 0 to nyquist freq. (fa/2) with N values
        else:
            self.t_axis = np.linspace(0, self.fa / self.scaling, self.N, endpoint=True)  # create x axis vector from 0 to nyquist freq. (fa/2) with N values
    def show(self, absolute=True):

        if absolute:
            plt.plot(self.t_axis, ((2.0) * self.buff / (self.N * (self.scaling))) * np.abs(self.Y[0:self.N]))
        else:
            plt.plot(self.t_axis, ((2.0) * self.buff / (self.Ns * (self.scaling))) * self.Y[0:self.N])  # multiply y axis by 2/N to get actual values
        plt.grid()
        plt.show()

def sineExample(start=0, dur=128, samples=16384):    
    t = np.linspace(start, dur + start, samples, True)
    print(t)
    f = 10.0  # Frequency in Hz
    A = 10.0  # Amplitude in Unit
    retarr = np.zeros(len(t))    
    retarr = np.column_stack((t, retarr))
    for row in range(len(retarr)):
        retarr[row][1] = A * np.sin(2 * np.pi * f * retarr[row][0]) + 2 # Signal  
    print(retarr)
    return retarr

hTArray = sineExample()
# plt.plot(hTArray[:,0], hTArray[:,1])
# plt.grid()
# plt.show()

myFFT = FFT(hTArray[:, 0], hTArray[:, 1], scaling=2,centre=False)
myFFT.fft()
myFFT.show()
1
What's wrong with simply doing np.fft.fft(hTArray[:, 1]) for your signal? I would say that your factor ((2.0) * self.buff / (self.N * (self.scaling))) must be wrong if you are not getting the correct amplitude of the DC component.rth
The factor affects all terms equally - and yet it is only the DC/0 Hz term that is consistently double its true value.davidhood2

1 Answers

1
votes

Actually, the opposite. All the other frequencies in a full complex FFT result of strictly real data are split into 2 result bins and mirrored as complex conjugated, thus half a pure sinusoids amplitude when scaled by 1/N, except the DC component and N/2 cosine component, which are not split into 2 FFT results, and thus not halved.