jakevdp / nfft

Lightweight non-uniform Fast Fourier Transform in Python
MIT License
201 stars 29 forks source link

Reconstructing signal with the nfft result #14

Closed a-z-e-r-i-l-a closed 4 years ago

a-z-e-r-i-l-a commented 4 years ago

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.

jakevdp commented 4 years ago

7 has info on this.

jakevdp commented 4 years ago

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

a-z-e-r-i-l-a commented 4 years ago

@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])
jakevdp commented 4 years ago

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

a-z-e-r-i-l-a commented 4 years ago

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])

jakevdp commented 4 years ago

The DC offset is here:

>>> r_hat[len(r_hat) // 2] / len(z)
(110.40382390478732+0j)
jakevdp commented 4 years ago

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.