UNPKG

clmtrackr

Version:

Javascript library for precise tracking of facial features via Constrained Local Models

335 lines (319 loc) 6.84 kB
/** * Fast Fourier Transform * 1D-FFT/IFFT, 2D-FFT/IFFT (radix-2) * * @author ryo / github.com/wellflat * Based on https://github.com/wellflat/javascript-labs with some tiny optimizations */ function FFT() { var _n = 0, // order _bitrev = null, // bit reversal table _cstb = null; // sin/cos table var _tre, _tim; this.init = function (n) { if(n !== 0 && (n & (n - 1)) === 0) { _n = n; _setVariables(); _makeBitReversal(); _makeCosSinTable(); } else { throw new Error('init: radix-2 required'); } } // 1D-FFT this.fft1d = function (re, im) { fft(re, im, 1); } // 1D-IFFT this.ifft1d = function (re, im) { var n = 1/_n; fft(re, im, -1); for(var i=0; i<_n; i++) { re[i] *= n; im[i] *= n; } } // 2D-FFT this.fft2d = function (re, im) { var i = 0; // x-axis for(var y=0; y<_n; y++) { i = y*_n; for(var x1=0; x1<_n; x1++) { _tre[x1] = re[x1 + i]; _tim[x1] = im[x1 + i]; } this.fft1d(_tre, _tim); for(var x2=0; x2<_n; x2++) { re[x2 + i] = _tre[x2]; im[x2 + i] = _tim[x2]; } } // y-axis for(var x=0; x<_n; x++) { for(var y1=0; y1<_n; y1++) { i = x + y1*_n; _tre[y1] = re[i]; _tim[y1] = im[i]; } this.fft1d(_tre, _tim); for(var y2=0; y2<_n; y2++) { i = x + y2*_n; re[i] = _tre[y2]; im[i] = _tim[y2]; } } } // 2D-IFFT this.ifft2d = function (re, im) { var i = 0; // x-axis for(var y=0; y<_n; y++) { i = y*_n; for(var x1=0; x1<_n; x1++) { _tre[x1] = re[x1 + i]; _tim[x1] = im[x1 + i]; } this.ifft1d(_tre, _tim); for(var x2=0; x2<_n; x2++) { re[x2 + i] = _tre[x2]; im[x2 + i] = _tim[x2]; } } // y-axis for(var x=0; x<_n; x++) { for(var y1=0; y1<_n; y1++) { i = x + y1*_n; _tre[y1] = re[i]; _tim[y1] = im[i]; } this.ifft1d(_tre, _tim); for(var y2=0; y2<_n; y2++) { i = x + y2*_n; re[i] = _tre[y2]; im[i] = _tim[y2]; } } } // 2D-IFFT, real-valued // only outputs the real valued part this.real_ifft2d = function (re, im) { var i2; var i = 0; // x-axis for(var y=0; y<_n; y++) { i = y*_n; for(var x1=0; x1<_n; x1++) { _tre[x1] = re[x1 + i]; _tim[x1] = im[x1 + i]; } this.ifft1d(_tre, _tim); for(var x2=0; x2<_n; x2++) { re[x2 + i] = _tre[x2]; im[x2 + i] = _tim[x2]; } } // y-axis var halfn = _n/2; var rowIdx = 0; for(var x=0; x<_n; x+=2) { //untangle i = x; i2 = x+1; _tre[0] = re[0 + i]; _tim[0] = re[0 + i2]; _tre[_n/2] = re[(halfn*_n) + i]; _tim[_n/2] = re[(halfn*_n) + i2]; for (var x2=1;x2<halfn;x2++) { rowIdx = x2*_n; _tre[x2] = re[rowIdx+i] - im[rowIdx + i2]; _tre[_n - x2] = re[rowIdx+i] + im[rowIdx + i2]; _tim[x2] = im[rowIdx+i] + re[rowIdx+i2]; _tim[_n - x2] = re[rowIdx+i2] - im[rowIdx+i]; } this.ifft1d(_tre, _tim); for(var y2=0; y2<_n; y2++) { i = x + y2*_n; i2 = (x + 1) + y2*_n; re[i] = _tre[y2]; re[i2] = _tim[y2]; } } } // 2D-FFT, real-valued only // ignores the imaginary input // see: // http://www.inf.fu-berlin.de/lehre/SS12/SP-Par/download/fft1.pdf // http://cnx.org/content/m12021/latest/ // http://images.apple.com/acg/pdf/g4fft.pdf // http://www.ti.com/lit/an/spra291/spra291.pdf this.real_fft2d = function (re, im) { var i = 0, i2 = 0; // x-axis for(var y=0; y<_n; y += 2) { i = y*_n; i2 = (y+1)*_n; // tangle for(var x1=0; x1<_n; x1++) { _tre[x1] = re[x1 + i]; _tim[x1] = re[x1 + i2]; } this.fft1d(_tre, _tim); // untangle re[0 + i] = _tre[0]; re[0 + i2] = _tim[0]; im[0 + i] = 0; im[0 + i2] = 0; re[_n/2 + i] = _tre[_n/2]; re[_n/2 + i2] = _tim[_n/2]; im[_n/2 + i] = 0; im[_n/2 + i2] = 0; for(var x2=1;x2<(_n/2);x2++) { re[x2 + i] = 0.5 * (_tre[x2] + _tre[_n - x2]); im[x2 + i] = 0.5 * (_tim[x2] - _tim[_n - x2]); re[x2 + i2] = 0.5 * (_tim[x2] + _tim[_n - x2]); im[x2 + i2] = -0.5 * (_tre[x2] - _tre[_n - x2]); re[(_n-x2) + i] = re[x2 + i]; im[(_n-x2) + i] = -im[x2 + i]; re[(_n-x2) + i2] = re[x2 + i2]; im[(_n-x2) + i2] = -im[x2 + i2]; } } // y-axis for(var x=0; x<_n; x++) { for(var y1=0; y1<_n; y1++) { i = x + y1*_n; _tre[y1] = re[i]; _tim[y1] = im[i]; } this.fft1d(_tre, _tim); for(var y2=0; y2<_n; y2++) { i = x + y2*_n; re[i] = _tre[y2]; im[i] = _tim[y2]; } } } // core operation of FFT function fft(re, im, inv) { var d, h, ik, m, tmp, wr, wi, xr, xi, n4 = _n >> 2; // bit reversal for(var l=0; l<_n; l++) { m = _bitrev[l]; if(l < m) { tmp = re[l]; re[l] = re[m]; re[m] = tmp; tmp = im[l]; im[l] = im[m]; im[m] = tmp; } } // butterfly operation //butfly(re,im,inv,n4); for(var k=1; k<_n; k<<=1) { h = 0; d = _n/(k << 1); for(var j=0; j<k; j++) { wr = _cstb[h + n4]; wi = inv*_cstb[h]; for(var i=j; i<_n; i+=(k<<1)) { ik = i + k; xr = wr*re[ik] + wi*im[ik]; xi = wr*im[ik] - wi*re[ik]; re[ik] = re[i] - xr; re[i] += xr; im[ik] = im[i] - xi; im[i] += xi; } h += d; } } } function butfly(re, im, inv, n4) { var h,d,wr,wi,ik,xr,xi; for(var k=1; k<_n; k<<=1) { h = 0; d = _n/(k << 1); for(var j=0; j<k; j++) { wr = _cstb[h + n4]; wi = inv*_cstb[h]; for(var i=j; i<_n; i+=(k<<1)) { ik = i + k; xr = wr*re[ik] + wi*im[ik]; xi = wr*im[ik] - wi*re[ik]; re[ik] = re[i] - xr; re[i] += xr; im[ik] = im[i] - xi; im[i] += xi; } h += d; } } } // set variables function _setVariables() { if(typeof Uint8Array !== 'undefined') { _bitrev = new Uint8Array(_n); } else { _bitrev = new Array(_n); } if(typeof Float64Array !== 'undefined') { _cstb = new Float64Array(_n*1.25); _tre = new Float64Array(_n); _tim = new Float64Array(_n); } else { _cstb = new Array(_n*1.25); _tre = new Array(_n); _tim = new Array(_n); } } // make bit reversal table function _makeBitReversal() { var i = 0, j = 0, k = 0; _bitrev[0] = 0; while(++i < _n) { k = _n >> 1; while(k <= j) { j -= k; k >>= 1; } j += k; _bitrev[i] = j; } } // make trigonometric function table function _makeCosSinTable() { var n2 = _n >> 1, n4 = _n >> 2, n8 = _n >> 3, n2p4 = n2 + n4, t = Math.sin(Math.PI/_n), dc = 2*t*t, ds = Math.sqrt(dc*(2 - dc)), c = _cstb[n4] = 1, s = _cstb[0] = 0; t = 2*dc; for(var i=1; i<n8; i++) { c -= dc; dc += t*c; s += ds; ds -= t*s; _cstb[i] = s; _cstb[n4 - i] = c; } if(n8 !== 0) { _cstb[n8] = Math.sqrt(0.5); } for(var j=0; j<n4; j++) { _cstb[n2 - j] = _cstb[j]; } for(var k=0; k<n2p4; k++) { _cstb[k + n2] = -_cstb[k]; } } } export default FFT;