File indexing completed on 2024-06-02 04:35:37
0001 /*************************************************************************** 0002 psdcalculator.cpp: Power Spectra Calculator for KST 0003 ------------------- 0004 begin : 2006 0005 copyright : (C) 2006 by Kst 0006 email : netterfield@astro.utoronto.ca 0007 ***************************************************************************/ 0008 0009 /*************************************************************************** 0010 * * 0011 * This program is free software; you can redistribute it and/or modify * 0012 * it under the terms of the GNU General Public License as published by * 0013 * the Free Software Foundation; either version 2 of the License, or * 0014 * (at your option) any later version. * 0015 * * 0016 ***************************************************************************/ 0017 0018 /** A utility class for calculating power spectra 0019 */ 0020 0021 #include "psdcalculator.h" 0022 0023 #include <assert.h> 0024 0025 0026 0027 #include "debug.h" 0028 #include "vector.h" 0029 0030 #include <qnamespace.h> 0031 #include <math_kst.h> 0032 #include "measuretime.h" 0033 0034 extern "C" void rdft(int n, int isgn, double *a); 0035 0036 #define PSDMINLEN 2 0037 #define PSDMAXLEN 27 0038 0039 inline double PSDCalculator::cabs2(double r, double i) { 0040 return r*r + i*i; 0041 } 0042 0043 PSDCalculator::PSDCalculator() 0044 { 0045 _a = 0L; 0046 _b = 0L; 0047 _w = 0L; 0048 0049 _fft_len = 0; 0050 0051 _prev_apodize_function = WindowUndefined; 0052 _prev_gaussian_sigma = 1.0; 0053 _prev_output_len = 0; 0054 _prev_cross_spec = false; 0055 } 0056 0057 0058 PSDCalculator::~PSDCalculator() { 0059 delete[] _w; 0060 _w = 0L; 0061 delete[] _a; 0062 _a = 0L; 0063 delete[] _b; 0064 _b = 0L; 0065 } 0066 0067 void PSDCalculator::updateWindowFxn(ApodizeFunction apodizeFxn, double gaussianSigma) { 0068 const double a = double(_fft_len) / 2.0; 0069 double x; 0070 double sW = 0.0; 0071 0072 switch (apodizeFxn) { 0073 case WindowOriginal: 0074 for (int i = 0; i < _fft_len; ++i) { 0075 _w[i] = 1.0 - cos(2.0 * M_PI * double(i) / double(_fft_len)); 0076 sW += _w[i] * _w[i]; 0077 } 0078 break; 0079 0080 case WindowBartlett: 0081 for (int i = 0; i < _fft_len; ++i) { 0082 x = i - a; 0083 _w[i] = 1.0 - fabs(x) / a; 0084 sW += _w[i] * _w[i]; 0085 } 0086 break; 0087 0088 case WindowBlackman: 0089 for (int i = 0; i < _fft_len; ++i) { 0090 x = i - a; 0091 _w[i] = 0.42 + 0.5 * cos(M_PI * x / a) + 0.08 * cos(2 * M_PI * x/a); 0092 sW += _w[i] * _w[i]; 0093 } 0094 break; 0095 0096 case WindowConnes: 0097 for (int i = 0; i < _fft_len; ++i) { 0098 x = i - a; 0099 _w[i] = pow(static_cast<double>(1.0 - (x * x) / (a * a)), 2); 0100 sW += _w[i] * _w[i]; 0101 } 0102 break; 0103 0104 case WindowCosine: 0105 for (int i = 0; i < _fft_len; ++i) { 0106 x = i - a; 0107 _w[i] = cos(M_PI * x / (2.0 * a)); 0108 sW += _w[i] * _w[i]; 0109 } 0110 break; 0111 0112 case WindowGaussian: 0113 for (int i = 0; i < _fft_len; ++i) { 0114 x = i - a; 0115 _w[i] = exp(-1.0 * x * x/(2.0 * gaussianSigma * gaussianSigma)); 0116 } 0117 break; 0118 0119 case WindowHamming: 0120 for (int i = 0; i < _fft_len; ++i) { 0121 x = i - a; 0122 _w[i] = 0.53836 + 0.46164 * cos(M_PI * x / a); 0123 sW += _w[i] * _w[i]; 0124 } 0125 break; 0126 0127 case WindowHann: 0128 for (int i = 0; i < _fft_len; ++i) { 0129 x = i - a; 0130 _w[i] = pow(static_cast<double>(cos(M_PI * x/(2.0 * a))), 2); 0131 sW += _w[i] * _w[i]; 0132 } 0133 break; 0134 0135 case WindowWelch: 0136 for (int i = 0; i < _fft_len; ++i) { 0137 x = i - a; 0138 _w[i] = 1.0 - x * x / (a * a); 0139 sW += _w[i] * _w[i]; 0140 } 0141 break; 0142 0143 case WindowUniform: 0144 default: 0145 for (int i = 0; i < _fft_len; ++i) { 0146 _w[i] = 1.0; 0147 } 0148 sW = _fft_len; 0149 break; 0150 } 0151 0152 double norm = sqrt((double)_fft_len/sW); // normalization constant s.t. sum over (w^2) is _awLen 0153 0154 for (int i = 0; i < _fft_len; ++i) { 0155 _w[i] *= norm; 0156 } 0157 0158 _prev_apodize_function = apodizeFxn; 0159 _prev_gaussian_sigma = gaussianSigma; 0160 } 0161 0162 0163 int PSDCalculator::calculatePowerSpectrum( 0164 double const *input, int input_len, 0165 double *output, int output_len, 0166 bool removeMean, 0167 bool average, int average_len, 0168 bool apodize, ApodizeFunction apodize_function, double gaussian_sigma, 0169 PSDType output_type, double sampling_freq, 0170 double const *input2, int input2_len, double *output2) { 0171 0172 bool cross_spectra = false; 0173 0174 if ((input2_len == input_len) && input2) { 0175 cross_spectra = true; 0176 } 0177 0178 if ((input2) && (input2_len == input_len)) { 0179 cross_spectra = true; 0180 } 0181 0182 if (output_len != calculateOutputVectorLength(input_len, average, average_len)) { 0183 Kst::Debug::self()->log(Kst::Debug::tr("in PSDCalculator::calculatePowerSpectrum: received output array with wrong length."), Kst::Debug::Error); 0184 return -1; 0185 } 0186 0187 if (output_len != _prev_output_len) { 0188 delete[] _a; 0189 delete[] _w; 0190 delete[] _b; 0191 0192 _fft_len = output_len*2; 0193 _prev_output_len = output_len; 0194 0195 _a = new double[_fft_len]; 0196 _b = new double[_fft_len]; 0197 _w = new double[_fft_len]; 0198 0199 updateWindowFxn(apodize_function, gaussian_sigma); 0200 } 0201 0202 if ( (_prev_apodize_function != apodize_function) || (_prev_gaussian_sigma != gaussian_sigma) ) { 0203 updateWindowFxn(apodize_function, gaussian_sigma); 0204 } 0205 0206 int currentCopyLen; 0207 int nsamples = 0; 0208 int i_samp; 0209 int ioffset; 0210 0211 memset(output, 0, sizeof(double)*output_len); // initialize output. 0212 if (cross_spectra) { 0213 memset(output2, 0, sizeof(double)*output_len); // initialize complex output for xspectra. 0214 } 0215 0216 // Mingw build could be 10 times slower (Gaussian apod, mostly 0 then?) 0217 //MeasureTime time_in_rfdt("rdft()"); 0218 0219 bool done = false; 0220 for (int i_subset = 0; !done; i_subset++) { 0221 ioffset = i_subset*output_len; //overlapping average => i_subset*outputLen 0222 0223 // only zero pad if we really have to. It is better to adjust the last chunk's 0224 // overlap. 0225 if (ioffset + _fft_len*5/4 < input_len) { 0226 currentCopyLen = _fft_len; //will copy a complete window. 0227 } else if (_fft_len<input_len) { // count the last one from the end. 0228 ioffset = input_len-_fft_len - 1; 0229 currentCopyLen = _fft_len; //will copy a complete window. 0230 done = true; 0231 } else { 0232 currentCopyLen = input_len - ioffset; //will copy a partial window. 0233 memset(&_a[currentCopyLen], 0, sizeof(double)*(_fft_len - currentCopyLen)); //zero the leftovers. 0234 if (cross_spectra) { 0235 memset(&_b[currentCopyLen], 0, sizeof(double)*(_fft_len - currentCopyLen)); //zero the leftovers. 0236 } 0237 done = true; 0238 } 0239 0240 double mean = 0.0; 0241 double mean2 = 0.0; 0242 0243 if (removeMean) { 0244 for (i_samp = 0; i_samp < currentCopyLen; ++i_samp) { 0245 mean += input[i_samp + ioffset]; 0246 } 0247 mean /= (double)currentCopyLen; 0248 if (cross_spectra) { 0249 for (i_samp = 0; i_samp < currentCopyLen; ++i_samp) { 0250 mean2 += input2[i_samp + ioffset]; 0251 } 0252 mean2 /= (double)currentCopyLen; 0253 } 0254 } 0255 0256 // apply the PSD options (removeMean, apodize, etc.) 0257 // separate cases for speed- although this shouldn't really matter- the rdft should be the most time consuming step by far for any large data set. 0258 if (removeMean && apodize) { 0259 for (i_samp = 0; i_samp < currentCopyLen; ++i_samp) { 0260 _a[i_samp] = (input[i_samp + ioffset] - mean)*_w[i_samp]; 0261 } 0262 } else if (removeMean) { 0263 for (i_samp = 0; i_samp < currentCopyLen; ++i_samp) { 0264 _a[i_samp] = input[i_samp + ioffset] - mean; 0265 } 0266 } else if (apodize) { 0267 for (i_samp = 0; i_samp < currentCopyLen; ++i_samp) { 0268 _a[i_samp] = input[i_samp + ioffset]*_w[i_samp]; 0269 } 0270 } else { 0271 for (i_samp = 0; i_samp < currentCopyLen; ++i_samp) { 0272 _a[i_samp] = input[i_samp + ioffset]; 0273 } 0274 } 0275 0276 if (cross_spectra) { 0277 if (removeMean && apodize) { 0278 for (i_samp = 0; i_samp < currentCopyLen; ++i_samp) { 0279 _b[i_samp] = (input2[i_samp + ioffset] - mean2)*_w[i_samp]; 0280 } 0281 } else if (removeMean) { 0282 for (i_samp = 0; i_samp < currentCopyLen; ++i_samp) { 0283 _b[i_samp] = input2[i_samp + ioffset] - mean2; 0284 } 0285 } else if (apodize) { 0286 for (i_samp = 0; i_samp < currentCopyLen; ++i_samp) { 0287 _b[i_samp] = input2[i_samp + ioffset]*_w[i_samp]; 0288 } 0289 } else { 0290 for (i_samp = 0; i_samp < currentCopyLen; ++i_samp) { 0291 _b[i_samp] = input2[i_samp + ioffset]; 0292 } 0293 } 0294 } 0295 0296 nsamples += currentCopyLen; 0297 0298 #if !defined(__QNX__) 0299 rdft(_fft_len, 1, _a); //real discrete fourier transorm on _a. 0300 if (cross_spectra) { 0301 rdft(_fft_len, 1, _b); //real discrete fourier transorm on _b. 0302 } 0303 #else 0304 Q_ASSERT(0); // there is a linking problem when not compling with pch. . . 0305 #endif 0306 0307 if (cross_spectra) { 0308 output[0] += _a[0] * _b[0]; 0309 output2[0] = 0; 0310 output[output_len-1] += _a[1] * _b[1]; 0311 output2[output_len-1] = 0; 0312 for (i_samp = 1; i_samp < output_len - 1; ++i_samp) { 0313 output[i_samp] += _a[i_samp*2] * _b[i_samp*2] + 0314 _a[i_samp*2+1] * _b[i_samp*2+1]; 0315 output2[i_samp] = -_a[i_samp*2] * _b[i_samp*2+1] + 0316 _a[i_samp*2+1] * _b[i_samp*2]; 0317 } 0318 } else { 0319 output[0] += _a[0] * _a[0]; 0320 output[output_len-1] += _a[1] * _a[1]; 0321 for (i_samp = 1; i_samp < output_len - 1; ++i_samp) { 0322 output[i_samp] += cabs2(_a[i_samp * 2], _a[i_samp * 2 + 1]); 0323 } 0324 } 0325 } 0326 0327 // FIXME: NORMALIZATION. 0328 /* This normalization doesn't give the same results as the original KstPSD. 0329 0330 double frequencyStep = .5*(double)inputSamplingFreq/(double)(outputLen-1); 0331 0332 //normalization factors which were left out earlier for speed. 0333 // - 2.0 for the negative frequencies which were neglected by the rdft //FIXME: double check. 0334 // - /(_awLen*_awLen) for the constant Wss from numerical recipes in C. (ensure that the window function agrees with this.) 0335 // - /i_subset to average the powers in all the subsets. 0336 double norm = 2.0/(double)_awLen/(double)_awLen/(double)i_subset; 0337 */ 0338 0339 // original normalization 0340 double frequencyStep = 2.0*(double)sampling_freq/(double)nsamples; //OLD value for frequencyStep. 0341 double norm = 2.0/(double)nsamples*2.0/(double)nsamples; //OLD value for norm. 0342 0343 switch (output_type) { 0344 default: 0345 case PSDAmplitudeSpectralDensity: // amplitude spectral density (default) [V/Hz^1/2] 0346 norm /= frequencyStep; 0347 for (i_samp = 0; i_samp < output_len; ++i_samp) { 0348 output[i_samp] = sqrt(output[i_samp]*norm); 0349 } 0350 break; 0351 0352 case PSDPowerSpectralDensity: // power spectral density [V^2/Hz] 0353 norm /= frequencyStep; 0354 for (i_samp = 0; i_samp < output_len; ++i_samp) { 0355 output[i_samp] *= norm; 0356 } 0357 if (cross_spectra) { 0358 for (i_samp = 0; i_samp < output_len; ++i_samp) { 0359 output2[i_samp] *= norm; 0360 } 0361 } 0362 break; 0363 0364 case PSDAmplitudeSpectrum: // amplitude spectrum [V] 0365 for (i_samp = 0; i_samp < output_len; ++i_samp) { 0366 output[i_samp] = sqrt(output[i_samp]*norm); 0367 } 0368 break; 0369 0370 case PSDPowerSpectrum: // power spectrum [V^2] 0371 for (i_samp = 0; i_samp < output_len; ++i_samp) { 0372 output[i_samp] *= norm; 0373 } 0374 if (cross_spectra) { 0375 for (i_samp = 0; i_samp < output_len; ++i_samp) { 0376 output2[i_samp] *= norm; 0377 } 0378 } 0379 break; 0380 } 0381 0382 return 0; 0383 } 0384 0385 0386 int PSDCalculator::calculateOutputVectorLength(int inputLen, bool average, int averageLen) { 0387 int psdloglen; 0388 0389 if (average && pow(2.0, averageLen) < inputLen) { 0390 psdloglen = averageLen; 0391 } else { 0392 psdloglen = int(ceil(log(double(inputLen)) / log(2.0))); 0393 } 0394 0395 if (psdloglen < PSDMINLEN) { 0396 psdloglen = PSDMINLEN; 0397 } 0398 0399 if (psdloglen > PSDMAXLEN) { 0400 psdloglen = PSDMAXLEN; 0401 } 0402 0403 return int(pow(2.0, psdloglen - 1)); 0404 } 0405 0406 // vim: ts=2 sw=2 et