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