
Simple Spectral Analysis Functions

kravlor
#pragma rtGlobals=1 // Use modern global access method. Function CrossPower(srcX, srcY, bw_res) WAVE srcX, srcY Variable bw_res // Desired spectral resolution, Hz Variable retval retval = CrossPowerDensity(srcX, srcY, bw_res) if(retval < 0) return -1 // Error! endif WAVE/C G_XY Duplicate/C/O G_XY P_XY Redimension/R P_XY WAVE pxy = P_XY // Cross-power is modulus of G_XY pxy = cabs(G_XY) // Set units correctly; should be (srcUnits)^2 / Hz SetScale d, 0, 0, "(" + WaveUnits(srcX, 1) + ")^2/Hz", pxy KillWaves/Z G_XY End Function Coherence(srcX, srcY, bw_res, [noBiasError]) WAVE srcX, srcY Variable bw_res // Desired spectral resolution, Hz Variable noBiasError // Flag; set to 1 to skip computation of bias error if(ParamIsDefault(noBiasError)) noBiasError = 0 endif // Compute Cross- and Autospectral density estimates from the source waves Variable retval retval =CrossPowerDensity(srcX, srcX, bw_res) if(retval < 0) return -1 // Error! endif WAVE/C G_XY Duplicate/O/C G_XY G_XX CrossPowerDensity(srcY, srcY, bw_res) WAVE/C G_XY Duplicate/O/C G_XY G_YY CrossPowerDensity(srcX, srcY, bw_res) WAVE/C G_XY Duplicate/O/C G_XY gamma_XY gamma_XY *= conj(G_XY) // |G_xy|^2 == G_xy * conj(G_xy) gamma_XY /= (G_XX * G_YY) Redimension/R gamma_XY // coherence is dimensionless SetScale d, 0, 0, "", gamma_XY Duplicate/O gamma_xy unc_gamma_xy, bias_gamma_xy NVAR Nchunk unc_gamma_xy = sqrt(2)*(1-gamma_xy) / (sqrt(gamma_xy) * sqrt(Nchunk)) // 1-std deviation, Bendat 11.47, p 275 (random error) if(!noBiasError) bias_gamma_xy = coherenceBiasError(gamma_xy, Nchunk) else KillWaves/Z bias_gamma_xy endif //Xcoher = sqrt( magsqr(G_XY) / (G_XX * G_YY) ) // Compute coherent output power spectrum, Bendat 3.71; g_y:x = gamma_xy(f) * g_yy(f) Duplicate/O G_YY, cohP_YX Redimension/R cohP_YX cohP_YX = gamma_XY * cabs(G_YY) // Set units correctly; should be (srcUnits)^2 / Hz (same as cross-power, since gamma_xy is dimensionless) SetScale d, 0, 0, "(" + WaveUnits(srcY, 1) + ")^2/Hz", cohP_YX KillWaves/Z G_XX, G_YY, G_XY End // coherenceBiasError: Given an estimate of coherence, and the number of averages used in the // FFT computation of the Gxy values to derive it, returns the 1-sigma bias error. // Equation IIe from Carter1973; IIa is available in commented-out portion due to compute inefficiency. Function coherenceBiasError(gamSqrEst, Nd) Variable gamSqrEst // coherence estimated from Nd spectral averages Variable Nd // number of distinct FFT averages used to compute gamSqrEstimate // Equation IIa; exact, but sometimes leads to infinite compute time in hyperG2F1 function. // Return (1/Nd) + (Nd - 1)/(Nd + 1) * gamSqrEst * hyperG2F1(1,1,Nd+2,gamSqrEst) - gamSqrEst Return (1/Nd)*(1-gamSqrEst)^2 // Eq. IIe, approximation for large Nd End Function CrossPhase(srcX, srcY, bw_res) WAVE srcX, srcY Variable bw_res // Desired spectral resolution, Hz Variable retval retval = CrossPowerDensity(srcX, srcY, bw_res) if(retval < 0) return -1 // Error! endif WAVE/C G_XY Duplicate/O/C G_XY theta_XY Redimension/R theta_XY WAVE xphs = theta_XY xphs = atan2(imag(G_XY) , real(G_XY)) * 180/Pi // Units... SetScale d, 0, 0, "Degrees", xphs // Uncertainty estimate: need gamma_xy! coherence(srcX, srcY, bw_res, noBiasError=1) WAVE gamma_xy Duplicate/O xphs unc_theta_XY NVAR Nchunk // 1-std deviation formula; Bendat 11.62. unc_theta_XY = sqrt(1-gamma_xy)/(sqrt(gamma_xy)*sqrt(2*Nchunk)) * 180/Pi KillWaves/Z G_XY End // CrossPowerDensity: Computes the estimated cross-power spectral density G_XY given two source waves // of identical length, units, and scaling. Creates (and overwrites if pre-existing) destination wave G_XY, // which is the result of computing Bendat eq. 3.90, p. 72. // Note that autopower spectral density estimates can be also computed with this function, letting the // srcX and srcY waves be identical. Function CrossPowerDensity(srcX, srcY, bw_res) WAVE srcX, srcY Variable bw_res // Desired spectral resolution, Hz if(numpnts(srcX) != numpnts(srcY) || !((deltax(srcX) - deltax(srcY)) < 1e-8)) // Waves are of incompatible scale print "CrossPowerDensity is not yet equipped to handle differently-scaled waves. Aborting..." print numpnts(srcX), numpnts(srcY), numpnts(srcX)-numpnts(srcY) print deltax(srcX), deltax(srcY), deltax(srcX)-deltax(srcY) return -1 endif fCrossPowerSpectralDensity(srcX, srcY, bw_res) WAVE/C G_XY // Correct Units on cross-spectral density result SetScale d, 0, 0, "(" + WaveUnits(srcX, 1) + ")^2/Hz", G_XY End // fCrossPowerSpectralDensity: Computes the complex cross power spectral density Gxy between waves // w, w2 given a bandwidth resolution request bw_res. // Expanded from the Wavemetrics fPowerSpectralDensity function, which computes a properly normalized // (i.e. time-average power conserving) spectral density of a single wave (autopower spectral density). // fCrossPowerSpectralDensity may be used to compute autopower spectra as well by specifying w = w2. Function/S fCrossPowerSpectralDensity(w, w2, bw_res) Wave w, w2 Variable bw_res // Desired bandwidth, Hz // Variable npsd // SegLen - the number of input points in each segment // String windowName // one of "Square;Hann;Parzen;Welch;Hamming;BlackmanHarris3;KaiserBessel" Variable removeDC = 1 Variable npsd = getNpsd(w, bw_res) String destw="G_XY" String srctmp=NameOfWave(w)+"_tmp" String srctmp2 =NameOfWave(w)+"_tm2" String winw=NameOfWave(w)+"_psdWin" Make/O/C/N=(npsd/2+1) $destw= 0 WAVE/C psd= $destw Make/O/N=(npsd) $srctmp,$winw, $srctmp2 WAVE tmp= $srctmp Wave tmp2 = $srctmp2 WAVE win= $winw win= 1 Variable winNorm Hanning win // winNorm=0.375 // theoretical avg squared value WaveStats/Q win winNorm= V_rms*V_rms // actual value is more accurate than a theoretical value. Variable dc= 0 Variable dc2 = 0 if( removeDC ) // boolean WaveStats/Q w dc= V_Avg WaveStats/Q w2 dc2 = V_Avg endif Variable psdFirst= 0 Variable psdOffset= npsd/2 Variable nsrc= numpnts(w) Variable nsegs for( nsegs= 0; psdFirst+npsd <= nsrc; nsegs += 1, psdFirst += psdOffset) Duplicate/O/R=[psdFirst,psdFirst+npsd-1] w, tmp Duplicate/O/R=[psdFirst,psdFirst+npsd-1] w2, tmp2 if(mod(numpnts(tmp), 2)) // odd; add one point of zero padding to end of element InsertPoints (numpnts(tmp)-1), 1, tmp, tmp2 endif tmp = (tmp-dc) * win tmp2 = (tmp2-dc) * win FFT/DEST=ctmp tmp // result is a one-sided spectrum of complex values FFT/DEST=ctmp2 tmp2 WAVE/C ctmp, ctmp2 psd += conj(ctmp)*ctmp2 // summed Fourier power of one-sided spectrum // (we're missing all negative frequency powers except for the Nyquist frequency) endfor Variable/G Nchunk = floor(nsegs + 1) / 2 // Print "Used",Nchunk, "unique bins" CopyScales/P ctmp, psd // normalize the sum of PSDs Variable deltaF= deltax(psd) // deltaF is the frequency bin width Variable norm= 2 / (npsd * npsd * nsegs * winNorm * deltaF) psd *= norm // Explanation of norm values: // * 2 total power = magnitude^2(-f) + magnitude^2(f), and we've only accumulated magnitude^2(f) // / (npsd * npsd * nsegs) converts to average power // / winNorm compensates for power lost by windowing the time data. // / deltaF converts power to power density (per Hertz) psd[0] /= 2 // we're not missing 1/2 the power of DC bin from the two-sided FFT, restore original value psd[npsd/2] /= 2 // there aren't two Nyquist bins, either. // Parseval's theorem (power in time-domain = power in frequency domain) // is satisfied if you compare: // time-domain average power ("mean squared amplitude" in Numerical Recipies) = 1/N * sum(t=0...N-1) w[t]^2 // frequency-domain average power= deltaf * sum(f=0...npsd/2) destw[f]^2 KillWaves/Z tmp, tmp2, win, ctmp, ctmp2 return NameOfWave(psd) // in the current data folder End // getNpsd: Given a source wave, computes the number of points to use for the // 'npsd' parameter in fCrossPowerSpectralDensity to obtain an effective bandwidth // of bw_res. // Also, marginally improved to improve resilience against unattainable bandwidth requests. Function getNpsd(src, bw_res) WAVE src Variable bw_res NVAR/Z quiet Variable nsrc = numpnts(src) Variable nBins = (rightX(src) - leftX(src))*bw_res Variable nPsd = floor(4*(nsrc/(2*nBins))) Variable roundBins = floor(nBins) if(roundBins < 1 || nPsd > nsrc) nPsd = nsrc if(NVAR_Exists(quiet)) if(quiet == 1) return nPsd endif endif Print "getNpsd: Requested resolution of", bw_res,"is not possible; using one bin..." endif return nPsd End

Forum

Support

Gallery
Igor Pro 9
Learn More
Igor XOP Toolkit
Learn More
Igor NIDAQ Tools MX
Learn More