jakevdp / nfft

Lightweight non-uniform Fast Fourier Transform in Python

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Reconstructing signal with the nfft result

a-z-e-r-i-l-a opened this issue · comments

I have a signal r evaluated at time z which looks like the below graph for each time step z.

r = np.array([119.75024144, 119.77177673, 119.79671626, 119.81566188,
       119.81291201, 119.71610143, 119.24156708, 117.66932347,
       114.22145178, 109.27266933, 104.57675147, 101.63381325,
       100.42623807, 100.09436745, 100.02798438, 100.02696846,
       100.05422613, 100.12216521, 100.27569606, 100.60962812,
       101.32023289, 102.71102637, 105.01826819, 108.17052642,
       111.67848758, 114.78442424, 116.95337537, 118.19437002,
       118.84307457, 119.19571404, 119.40326818, 119.53101551,
       119.61170874, 119.66610072, 119.68315253, 119.53757829,
       118.83748609, 116.90425868, 113.32095843, 108.72465638,
       104.58292906, 101.93316248, 100.68856962, 100.22523098,
       100.08558767, 100.07194691, 100.11193397, 100.19142891,
       100.33208922, 100.5849306 , 101.04224415, 101.87565882,
       103.33985519, 105.63631456, 108.64972952, 111.86837667,
       114.67115037, 116.69548163, 117.96207449, 118.69589499,
       119.11781077, 119.36770681, 119.51566311, 119.59301667])

z = np.array ([-422.05230434, -408.98182253, -395.78387843, -382.43143962,
       -368.92341485, -355.26851343, -341.47780372, -327.56493425,
       -313.54536462, -299.43740189, -285.26768576, -271.07676026,
       -256.92098157, -242.86416227, -228.95449427, -215.207069  ,
       -201.61590575, -188.17719265, -174.89201262, -161.75452196,
       -148.74812279, -135.85126854, -123.04093538, -110.29151714,
        -97.57502515,  -84.86119278,  -72.1145478 ,  -59.2947726 ,
        -46.36450604,  -33.29821629,  -20.08471733,   -6.72030326,
          6.80047849,   20.48309726,   34.32320864,   48.30267819,
         62.393214  ,   76.56022602,   90.76260159,  104.94787451,
        119.04731699,  132.98616969,  146.71491239,  160.23436159,
        173.58582543,  186.81849059,  199.96724955,  213.05229133,
        226.08870416,  239.09310452,  252.08377421,  265.0769367 ,
        278.08234368,  291.10215472,  304.13509998,  317.18351924,
        330.25976991,  343.38777732,  356.59626164,  369.90725571,
        383.33109354,  396.87227086,  410.5309987 ,  424.28994387])

image

Since the sampling period is not constant, I'm using the nfft from which I should get the frequency components with complex numbers that represent a magnitude and phase.

rf = nfft.nfft(z, r)
Then I want to reproduce the signal with the nfft result:

N   = rf.shape[0]
amp = np.abs(rf)/N
phi = np.angle(rf)

s = 0
for i in range(N//2):
    
    f = i*fs/N
    s += amp[i]*np.cos(2*np.pi*f *z    +phi[i])
    
plt.plot(z, s, label='nfft result')

image

But it looks not at all like the original signal.

#7 has info on this.

In particular, unlike the FFT, the NFFT cannot exactly reconstruct a given signal from a band-limited frequency representation, because in general the effective Nyquist frequency of an unevenly-sampled signal is very large. Your code is working as expected, and if you evaluate the NFFT at a wider frequency grid, your reconstruction will have more fidelity. For example:

N = 100000
k = -N / 2 + np.arange(N)
r_hat = nfft.nfft_adjoint(z, r, N)

r_reconstructed = nfft.nfft(z, r_hat) / N
plt.plot(z, r, '.', color='blue')
plt.plot(z, r_reconstructed, '-', color='red')

chart

@jakevdp
Thank you.
How about if we want to reconstruct the signal by sum of Sines or Cosines functions how can we find the coefficients regarding amplitude, phase shift, and frequency.

image

Something similar to:

s = r0
for i in range(N):
    s += amp[i]*np.sin(w[i]*z  +phi[i])

That's precisely what the NFFT calculates (well, generalized to complex exponentials).

If we use original fft for example and assume the sampling period is constant 13 ( which is not true), I reproduce the signal like the following using a cosine function:

from scipy.fftpack import fft

Ts = 13.00
fs = 1/Ts

rf = fft(r)
amp = np.abs(rf)/r.shape[0]
phi = np.angle(rf)

s = amp[0]
for i in range(n//2):
    if i>0:
        
        k   = i*fs/n
        s += 2*amp[i]*np.cos(2*np.pi*k  *(z)  +phi[i])
    
plt.plot(z, s, label='fft result')
plt.plot(z, r, label='data')
plt.legend()

image

which because of the non-uniform sampling period, the reconstruction is not good.

But if I use nfft:

N = 100000
r_hat = nfft.nfft_adjoint(z, r, N)
r_reconstructed = nfft.nfft(z, r_hat) / N

I haven't been able to do the same. What doesn't make sense to me is the amplitudes first. For example I don't find the DC constant amplitude which is approx. 110.

np.abs(r_reconstructed)

array([119.05166608, 119.79233619, 119.77732404, 119.85819179,
119.23368786, 119.98718045, 119.28788431, 117.64866232,
114.24173965, 109.24599923, 104.89324134, 101.60449633,
100.463555 , 99.78755809, 99.97843652, 100.06441681,
100.13686047, 100.07421509, 100.25667645, 100.5371113 ,
101.24062906, 102.73378377, 105.07478861, 108.06663284,
111.67608816, 114.89775712, 116.99564855, 118.2330805 ,
118.81533953, 119.31003932, 119.38999452, 119.54839654,
119.61643871, 119.68894329, 119.75074337, 119.48817088,
118.7799233 , 116.77295372, 113.30843251, 107.94473218,
104.48122313, 101.93228577, 100.72753523, 100.1778597 ,
100.09855226, 100.00882029, 100.11827024, 100.16555776,
100.38244013, 100.62187254, 100.92887294, 101.50904132,
103.42187277, 105.59074733, 108.4194843 , 111.86892277,
114.65794209, 116.70597202, 117.95747533, 118.77837264,
119.15335268, 119.39858413, 119.57918671, 119.61015863])

or:
np.abs(r_hat)

array([ 953.28285834, 847.26002895, 1514.53901906, ..., 659.27302669,
1514.53901906, 847.26002895])

The DC offset is here:

>>> r_hat[len(r_hat) // 2] / len(z)
(110.40382390478732+0j)

If you want to understand what's going on mathematically, I'd suggest starting with the two formulas in the documentation: https://github.com/jakevdp/nfft#about

All this package does is compute those sums in ~O[Mlog(N)] rather than ~O[MN] if you have problems that you can express in terms of those sums, this package will help you. Otherwise, it won't.