使用numpy.irfft将算法转换为JavaScript

时间:2013-08-05 15:17:22

标签: javascript python numpy fft ifft

我正在尝试将最初使用numpy编写的算法转换为JavaScript,而我无法通过反向FFT重现结果。

原始算法使用numpy.fft.rfftnumpy.fft.irfft

# Get the amplitude
amplitudes = abs(np.fft.rfft(buf, axis=0))

# Randomize phases
ph = np.random.uniform(0, 2*np.pi, (amplitudes.shape[0], 1)) * 1j
amplitudes = amplitudes * np.exp(ph)

# do the inverse FFT 
buf = np.fft.irfft(amplitudes, axis=0)

我发现a JavaScript library似乎可以完成FFT的工作,我正在使用mathjs进行矩阵/矢量工作。

我做了很多尝试,事情是我不知道我应该做些什么来模仿numpy.fft.irfft

2 FFT之间的差异:

  • JavaScript FFT函数返回带有负频率的复数输出,因此它包含的结果比使用numpy.fft.rfft获得的结果多2倍。虽然正频率[0, WIN/2]的幅度似乎匹配。

  • JavaScript iFFT返回复杂输出,而numpy.fft.rfft返回实际输出。

ANSWER

感谢@ hotpaw2我设法解决了我的问题。

实信号的频谱是对称的,numpy.fft.rfft仅返回该频谱的唯一分量。因此,对于128个样本的块,numpy.fft.rfft返回包含128/2 + 1值的频谱,即65值。

因此,如果我想做同样的事情,我需要从幅度中丢弃所有对称值,然后应用相位变化。

对于反向FFT:“要从全长IFFT获得实数输出,输入必须是复共轭对称的”。所以我需要通过使实部对称,重构光谱,以及镜像对称的虚部。

以下是算法:

fft(1, re, im)

amplitudes = math.select(re)
  .subset([math.range(0, frameCount / 2)])   // get only the unique part
  .abs().done()                       // input signal is real, so abs value of `re` is the amplitude

// Apply the new phases
re = math.emultiply(math.cos(phases), amplitudes)
im = math.emultiply(math.sin(phases), amplitudes)

// Rebuild `re` and `im` by adding the symetric part
re = math.concat(re, math.subset(re, [symRange]).reverse())
im = math.concat(im, math.select(im).subset([symRange]).emultiply(-1).done().reverse())

// do the inverse FFT
fft(-1, re, im)

2 个答案:

答案 0 :(得分:2)

要从全长IFFT中获得实数输出,输入必须是复共轭对称的(实数分量相同和虚部在镜像对称中否定为频率输入的上半部分或负半部分)。 / p>

对于复共轭输入,正向或反向FFT计算应仅在结果的虚部中以微小的近零数值噪声值(由于有限精度舍入)结束。

答案 1 :(得分:0)

我很难再现你的问题。我把numpy和nfftd中的一个玩具问题混在一起试图用振幅复制你的问题,而我却失败了。

玩具问题

我已经计算了一个离散的sin波(10个点),并将sin波放在你上面描述的变换中,尽管我已经用随机数组替换了随机数,该随机数不会从一次迭代变为下一个。

Python代码

首先,python代码:

# create the discrete sin wave
buf = np.sin(np.linspace(0,2*np.pi,10))

# run through transform described by sebpiq
amp = np.abs(np.fft.rfft(buf))
ph = np.array([ 3.69536029,  1.99564315,  1.046197  ,  4.43086754,  0.01415843, 3.53100037])
ph = ph*1j
amp = amp * np.exp(ph)
buf = np.fft.irfft(amp)

结果:

array([-0.28116423, -0.8469374 , -1.11143881, -0.68594442, -0.04085493,
    0.60202526,  0.4990367 ,  0.85927706,  0.76606064,  0.23994014])

Javascript代码

其次,请参阅等效的JavaScript代码:

// Require stuff
var math = require('mathjs');
var ndfft = require("ndfft");

// setup sin(x) in the real part, and 0 in the imag part
var re = [  0.00000000e+00,   6.42787610e-01,   9.84807753e-01, 8.66025404e-01,   3.42020143e-01,  -3.42020143e-01, -8.66025404e-01,  -9.84807753e-01,  -6.42787610e-01, -2.44929360e-16]
var im = [0,0,0,0,0,0,0,0,0,0]

// Cache a "random" matrix for easy comparison
ph = [ 3.69536029,  1.99564315,  1.046197  ,  4.43086754,  0.01415843, 3.53100037,  0.01420613,  4.19132513,  1.08002181,  3.05840211];

// Run algorithm
ndfft(1,re,im);
amplitudes = math.epow(math.add(math.epow(re, 2), math.epow(im, 2)), 0.5);
re = math.emultiply(math.cos(ph), amplitudes);
im = math.emultiply(math.sin(ph), amplitudes);
ndfft(-1,re,im);

结果:

> re
[ -0.44298344101499465,
  -1.0485812598130462,
  -1.028287331663926,
  -0.37462920250565557,
  0.5543077299497436,
  0.7410571497545398,
  0.7829965195020553,
  0.26939736089453314,
  0.3029516683194694,
  -2.440823114672447e-16 ]
> im
[ -0.019894821927674437,
  0.027734906190559794,
  -0.0766942109405363,
  -0.017488411630453154,
  0.04089362484484916,
  -0.17252218798632196,
  -0.11135041005265467,
  -0.008717609033075929,
  0.5669181583191372,
  2.0352312370257754e-17 ]

结果的大小

据我所知,结果非常相似。 python结果的平均幅度为0.593,javascript结果的平均幅度为0.592。我一路上哪儿出错了?

谢谢, 斯潘塞

规范化更新

我没有找到Jaime提到的任何代码库的规范化问题。我尝试的第一件事就是一个正弦波的向前fft,紧接着是结果的向后fft,在numpy和nfftd中,结果都被正确归一化。