AverageSpectrum.c

Go to the documentation of this file.
00001 /*
00002 *  Copyright (C) 2007 Bernd Machenschalk, Jolien Creighton, Kipp Cannon
00003 *
00004 *  This program is free software; you can redistribute it and/or modify
00005 *  it under the terms of the GNU General Public License as published by
00006 *  the Free Software Foundation; either version 2 of the License, or
00007 *  (at your option) any later version.
00008 *
00009 *  This program is distributed in the hope that it will be useful,
00010 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012 *  GNU General Public License for more details.
00013 *
00014 *  You should have received a copy of the GNU General Public License
00015 *  along with with program; see the file COPYING. If not, write to the
00016 *  Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
00017 *  MA  02111-1307  USA
00018 */
00019 
00020 #include <math.h>
00021 #include <string.h>
00022 #include <lal/FrequencySeries.h>
00023 #include <lal/LALStdlib.h>
00024 #include <lal/LALConstants.h>
00025 #include <lal/AVFactories.h>
00026 #include <lal/Sequence.h>
00027 #include <lal/TimeFreqFFT.h>
00028 #include <lal/Units.h>
00029 #include <lal/Window.h>
00030 
00031 #include <lal/LALRCSID.h>
00032 NRCSID (AVERAGESPECTRUMC,"$Id: AverageSpectrum.c,v 1.17 2007/07/12 22:33:51 jolien Exp $");
00033 
00034 
00035 
00036 
00037 /**
00038  *
00039  * Compute a "modified periodogram," i.e., the power spectrum of a windowed
00040  * time series.
00041  *
00042  */
00043 int XLALREAL4ModifiedPeriodogram(
00044     REAL4FrequencySeries        *periodogram,
00045     const REAL4TimeSeries       *tseries,
00046     const REAL4Window           *window,
00047     const REAL4FFTPlan          *plan
00048     )
00049 {
00050   static const char *func = "XLALREAL4ModifiedPeriodogram";
00051   REAL4Sequence *work;
00052   REAL4 normfac;
00053   UINT4 k;
00054   int result;
00055 
00056   if ( ! periodogram || ! tseries || ! plan )
00057       XLAL_ERROR( func, XLAL_EFAULT );
00058   if ( ! periodogram->data || ! tseries->data )
00059       XLAL_ERROR( func, XLAL_EINVAL );
00060   if ( tseries->deltaT <= 0.0 )
00061       XLAL_ERROR( func, XLAL_EINVAL );
00062   if ( periodogram->data->length != tseries->data->length/2 + 1 )
00063       XLAL_ERROR( func, XLAL_EBADLEN );
00064   if ( window )
00065   {
00066     if ( ! window->data )
00067       XLAL_ERROR( func, XLAL_EINVAL );
00068     if ( window->sumofsquares <= 0.0 )
00069       XLAL_ERROR( func, XLAL_EINVAL );
00070     if ( window->data->length != tseries->data->length )
00071       XLAL_ERROR( func, XLAL_EBADLEN );
00072   }
00073 
00074   /* if the window has been specified, apply it to data */
00075   if ( window )
00076   {
00077     UINT4 j;
00078 
00079     /* make a working copy */
00080     work = XLALCutREAL4Sequence( tseries->data, 0, tseries->data->length );
00081     if ( ! work )
00082       XLAL_ERROR( func, XLAL_EFUNC );
00083 
00084     /* apply windowing to data */
00085     for ( j = 0; j < tseries->data->length; ++j )
00086       work->data[j] *= window->data->data[j];
00087   }
00088   else
00089     /* point to original data */
00090     work = tseries->data;
00091 
00092   /* compute the power spectrum of the (windowed) timeseries */
00093   /* CHECKME: are DC and Nyquist right? */
00094   result = XLALREAL4PowerSpectrum( periodogram->data, work, plan );
00095   /* destroy the workspace if it was created */
00096   if ( window )
00097     XLALDestroyREAL4Sequence( work );
00098   /* check for errors from the PowerSpectrum call */
00099   if ( result == XLAL_FAILURE )
00100     XLAL_ERROR( func, XLAL_EFUNC );
00101 
00102   /* normalize power spectrum to give correct units */
00103   /* CHECKME: is this the right factor? */
00104   if ( window )
00105     normfac = tseries->deltaT / window->sumofsquares;
00106   else
00107     normfac = tseries->deltaT / tseries->data->length;
00108   for ( k = 0; k < periodogram->data->length; ++k )
00109     periodogram->data->data[k] *= normfac;
00110 
00111   /* now set rest of metadata */
00112   periodogram->epoch  = tseries->epoch;
00113   periodogram->f0     = tseries->f0; /* FIXME: is this right? */
00114   periodogram->deltaF = 1.0 / ( tseries->data->length * tseries->deltaT );
00115 
00116   /* compute units */
00117   if ( ! XLALUnitSquare( &periodogram->sampleUnits, &tseries->sampleUnits ) )
00118     XLAL_ERROR( func, XLAL_EFUNC );
00119   if ( ! XLALUnitMultiply( &periodogram->sampleUnits,
00120                            &periodogram->sampleUnits, &lalSecondUnit ) )
00121     XLAL_ERROR( func, XLAL_EFUNC );
00122 
00123   return 0;
00124 }
00125 
00126 /**
00127  *
00128  * Compute a "modified periodogram," i.e., the power spectrum of a windowed
00129  * time series.
00130  *
00131  */
00132 int XLALREAL8ModifiedPeriodogram(
00133     REAL8FrequencySeries        *periodogram,
00134     const REAL8TimeSeries       *tseries,
00135     const REAL8Window           *window,
00136     const REAL8FFTPlan          *plan
00137     )
00138 {
00139   static const char *func = "XLALREAL8ModifiedPeriodogram";
00140   REAL8Sequence *work;
00141   REAL8 normfac;
00142   UINT4 k;
00143   int result;
00144 
00145   if ( ! periodogram || ! tseries || ! plan )
00146       XLAL_ERROR( func, XLAL_EFAULT );
00147   if ( ! periodogram->data || ! tseries->data )
00148       XLAL_ERROR( func, XLAL_EINVAL );
00149   if ( tseries->deltaT <= 0.0 )
00150       XLAL_ERROR( func, XLAL_EINVAL );
00151   if ( periodogram->data->length != tseries->data->length/2 + 1 )
00152       XLAL_ERROR( func, XLAL_EBADLEN );
00153   if ( window )
00154   {
00155     if ( ! window->data )
00156       XLAL_ERROR( func, XLAL_EINVAL );
00157     if ( window->sumofsquares <= 0.0 )
00158       XLAL_ERROR( func, XLAL_EINVAL );
00159     if ( window->data->length != tseries->data->length )
00160       XLAL_ERROR( func, XLAL_EBADLEN );
00161   }
00162 
00163   /* if the window has been specified, apply it to data */
00164   if ( window )
00165   {
00166     UINT4 j;
00167 
00168     /* make a working copy */
00169     work = XLALCutREAL8Sequence( tseries->data, 0, tseries->data->length );
00170     if ( ! work )
00171       XLAL_ERROR( func, XLAL_EFUNC );
00172 
00173     /* apply windowing to data */
00174     for ( j = 0; j < tseries->data->length; ++j )
00175       work->data[j] *= window->data->data[j];
00176   }
00177   else
00178     /* point to original data */
00179     work = tseries->data;
00180 
00181   /* compute the power spectrum of the (windowed) timeseries */
00182   /* CHECKME: are DC and Nyquist right? */
00183   result = XLALREAL8PowerSpectrum( periodogram->data, work, plan );
00184   /* destroy the workspace if it was created */
00185   if ( window )
00186     XLALDestroyREAL8Sequence( work );
00187   /* check for errors from the PowerSpectrum call */
00188   if ( result == XLAL_FAILURE )
00189     XLAL_ERROR( func, XLAL_EFUNC );
00190 
00191   /* normalize power spectrum to give correct units */
00192   /* CHECKME: is this the right factor? */
00193   if ( window )
00194     normfac = tseries->deltaT / window->sumofsquares;
00195   else
00196     normfac = tseries->deltaT / tseries->data->length;
00197   for ( k = 0; k < periodogram->data->length; ++k )
00198     periodogram->data->data[k] *= normfac;
00199 
00200   /* now set rest of metadata */
00201   periodogram->epoch  = tseries->epoch;
00202   periodogram->f0     = tseries->f0; /* FIXME: is this right? */
00203   periodogram->deltaF = 1.0 / ( tseries->data->length * tseries->deltaT );
00204 
00205   /* compute units */
00206   if ( ! XLALUnitSquare( &periodogram->sampleUnits, &tseries->sampleUnits ) )
00207     XLAL_ERROR( func, XLAL_EFUNC );
00208   if ( ! XLALUnitMultiply( &periodogram->sampleUnits,
00209                            &periodogram->sampleUnits, &lalSecondUnit ) )
00210     XLAL_ERROR( func, XLAL_EFUNC );
00211 
00212   return 0;
00213 }
00214 
00215 
00216 /**
00217  *
00218  * Use Welch's method to compute the average power spectrum of a time series.
00219  *
00220  * See: Peter D. Welch "The Use of Fast Fourier Transform for the Estimation
00221  * of Power Spectra: A Method Based on Time Averaging Over Short, Modified
00222  * Periodograms"  IEEE Transactions on Audio and Electroacoustics,
00223  * Vol. AU-15, No. 2, June 1967.
00224  *
00225  */
00226 int XLALREAL4AverageSpectrumWelch(
00227     REAL4FrequencySeries        *spectrum,
00228     const REAL4TimeSeries       *tseries,
00229     UINT4                        seglen,
00230     UINT4                        stride,
00231     const REAL4Window           *window,
00232     const REAL4FFTPlan          *plan
00233     )
00234 {
00235   static const char *func = "XLALREAL4AverageSpectrumWelch";
00236   REAL4FrequencySeries *work; /* workspace */
00237   REAL4Sequence sequence; /* working copy of input time series data */
00238   REAL4TimeSeries tseriescopy; /* working copy of input time series */
00239   UINT4 numseg;
00240   UINT4 seg;
00241   UINT4 k;
00242 
00243   if ( ! spectrum || ! tseries || ! plan )
00244       XLAL_ERROR( func, XLAL_EFAULT );
00245   if ( ! spectrum->data || ! tseries->data )
00246       XLAL_ERROR( func, XLAL_EINVAL );
00247   if ( tseries->deltaT <= 0.0 )
00248       XLAL_ERROR( func, XLAL_EINVAL );
00249 
00250   /* construct local copy of time series */
00251   sequence = *tseries->data;
00252   tseriescopy = *tseries;
00253   tseriescopy.data = &sequence;
00254   tseriescopy.data->length = seglen;
00255 
00256   numseg = 1 + (tseries->data->length - seglen)/stride;
00257 
00258   /* consistency check for lengths: make sure that the segments cover the
00259    * data record completely */
00260   if ( (numseg - 1)*stride + seglen != tseries->data->length )
00261     XLAL_ERROR( func, XLAL_EBADLEN );
00262   if ( spectrum->data->length != seglen/2 + 1 )
00263     XLAL_ERROR( func, XLAL_EBADLEN );
00264 
00265   /* make sure window, if present, is appropriate */
00266   if ( window )
00267   {
00268     if ( ! window->data )
00269       XLAL_ERROR( func, XLAL_EINVAL );
00270     if ( window->sumofsquares <= 0.0 )
00271       XLAL_ERROR( func, XLAL_EINVAL );
00272     if ( window->data->length != seglen )
00273       XLAL_ERROR( func, XLAL_EBADLEN );
00274   }
00275 
00276   /* clear spectrum data */
00277   memset( spectrum->data->data, 0,
00278       spectrum->data->length * sizeof( *spectrum->data->data ) );
00279 
00280   /* create frequency series data workspace */
00281   work = XLALCutREAL4FrequencySeries( spectrum, 0, spectrum->data->length );
00282   if( ! work )
00283     XLAL_ERROR( func, XLAL_EFUNC );
00284 
00285   for ( seg = 0; seg < numseg; seg++, tseriescopy.data->data += stride )
00286   {
00287     /* compute the modified periodogram; clean up and exit on failure */
00288     if ( XLALREAL4ModifiedPeriodogram( work, &tseriescopy, window, plan ) == XLAL_FAILURE )
00289     {
00290       XLALDestroyREAL4FrequencySeries( work );
00291       XLAL_ERROR( func, XLAL_EFUNC );
00292     }
00293 
00294     /* add the periodogram to the running sum */
00295     for ( k = 0; k < spectrum->data->length; ++k )
00296       spectrum->data->data[k] += work->data->data[k];
00297   }
00298 
00299   /* set metadata */
00300   spectrum->epoch       = work->epoch;
00301   spectrum->f0          = work->f0;
00302   spectrum->deltaF      = work->deltaF;
00303   spectrum->sampleUnits = work->sampleUnits;
00304 
00305   /* divide spectrum data by the number of segments in average */
00306   for ( k = 0; k < spectrum->data->length; ++k )
00307     spectrum->data->data[k] /= numseg;
00308 
00309   /* clean up */
00310   XLALDestroyREAL4FrequencySeries( work );
00311 
00312   return 0;
00313 }
00314 
00315 /**
00316  *
00317  * Use Welch's method to compute the average power spectrum of a time series.
00318  *
00319  * See: Peter D. Welch "The Use of Fast Fourier Transform for the Estimation
00320  * of Power Spectra: A Method Based on Time Averaging Over Short, Modified
00321  * Periodograms"  IEEE Transactions on Audio and Electroacoustics,
00322  * Vol. AU-15, No. 2, June 1967.
00323  *
00324  */
00325 int XLALREAL8AverageSpectrumWelch(
00326     REAL8FrequencySeries        *spectrum,
00327     const REAL8TimeSeries       *tseries,
00328     UINT4                        seglen,
00329     UINT4                        stride,
00330     const REAL8Window           *window,
00331     const REAL8FFTPlan          *plan
00332     )
00333 {
00334   static const char *func = "XLALREAL8AverageSpectrumWelch";
00335   REAL8FrequencySeries *work; /* workspace */
00336   REAL8Sequence sequence; /* working copy of input time series data */
00337   REAL8TimeSeries tseriescopy; /* working copy of input time series */
00338   UINT4 numseg;
00339   UINT4 seg;
00340   UINT4 k;
00341 
00342   if ( ! spectrum || ! tseries || ! plan )
00343       XLAL_ERROR( func, XLAL_EFAULT );
00344   if ( ! spectrum->data || ! tseries->data )
00345       XLAL_ERROR( func, XLAL_EINVAL );
00346   if ( tseries->deltaT <= 0.0 )
00347       XLAL_ERROR( func, XLAL_EINVAL );
00348 
00349   /* construct local copy of time series */
00350   sequence = *tseries->data;
00351   tseriescopy = *tseries;
00352   tseriescopy.data = &sequence;
00353   tseriescopy.data->length = seglen;
00354 
00355   numseg = 1 + (tseries->data->length - seglen)/stride;
00356 
00357   /* consistency check for lengths: make sure that the segments cover the
00358    * data record completely */
00359   if ( (numseg - 1)*stride + seglen != tseries->data->length )
00360     XLAL_ERROR( func, XLAL_EBADLEN );
00361   if ( spectrum->data->length != seglen/2 + 1 )
00362     XLAL_ERROR( func, XLAL_EBADLEN );
00363 
00364   /* make sure window, if present, is appropriate */
00365   if ( window )
00366   {
00367     if ( ! window->data )
00368       XLAL_ERROR( func, XLAL_EINVAL );
00369     if ( window->sumofsquares <= 0.0 )
00370       XLAL_ERROR( func, XLAL_EINVAL );
00371     if ( window->data->length != seglen )
00372       XLAL_ERROR( func, XLAL_EBADLEN );
00373   }
00374 
00375   /* clear spectrum data */
00376   memset( spectrum->data->data, 0,
00377       spectrum->data->length * sizeof( *spectrum->data->data ) );
00378 
00379   /* create frequency series data workspace */
00380   work = XLALCutREAL8FrequencySeries( spectrum, 0, spectrum->data->length );
00381   if( ! work )
00382     XLAL_ERROR( func, XLAL_EFUNC );
00383 
00384   for ( seg = 0; seg < numseg; seg++, tseriescopy.data->data += stride )
00385   {
00386     /* compute the modified periodogram; clean up and exit on failure */
00387     if ( XLALREAL8ModifiedPeriodogram( work, &tseriescopy, window, plan ) == XLAL_FAILURE )
00388     {
00389       XLALDestroyREAL8FrequencySeries( work );
00390       XLAL_ERROR( func, XLAL_EFUNC );
00391     }
00392 
00393     /* add the periodogram to the running sum */
00394     for ( k = 0; k < spectrum->data->length; ++k )
00395       spectrum->data->data[k] += work->data->data[k];
00396   }
00397 
00398   /* set metadata */
00399   spectrum->epoch       = work->epoch;
00400   spectrum->f0          = work->f0;
00401   spectrum->deltaF      = work->deltaF;
00402   spectrum->sampleUnits = work->sampleUnits;
00403 
00404   /* divide spectrum data by the number of segments in average */
00405   for ( k = 0; k < spectrum->data->length; ++k )
00406     spectrum->data->data[k] /= numseg;
00407 
00408   /* clean up */
00409   XLALDestroyREAL8FrequencySeries( work );
00410 
00411   return 0;
00412 }
00413 
00414 
00415 /* 
00416  *
00417  * Median Method: use median average rather than mean.
00418  *
00419  */
00420 
00421 /* compute the median bias */
00422 REAL8 XLALMedianBias( UINT4 nn )
00423 {
00424   const UINT4 nmax = 1000;
00425   REAL8 ans = 1;
00426   UINT4 n = (nn - 1)/2;
00427   UINT4 i;
00428 
00429   if ( nn >= nmax )
00430     return LAL_LN2;
00431 
00432   for ( i = 1; i <= n; ++i )
00433   {
00434     ans -= 1.0/(2*i);
00435     ans += 1.0/(2*i + 1);
00436   }
00437 
00438   return ans;
00439 }
00440 
00441 /* cleanup temporary workspace... ignore xlal errors */
00442 static void median_cleanup_REAL4( REAL4FrequencySeries *work, UINT4 n )
00443 {
00444   int saveErrno = xlalErrno;
00445   UINT4 i;
00446   for ( i = 0; i < n; ++i )
00447     if ( work[i].data )
00448       XLALDestroyREAL4Vector( work[i].data );
00449   LALFree( work );
00450   xlalErrno = saveErrno;
00451   return;
00452 }
00453 static void median_cleanup_REAL8( REAL8FrequencySeries *work, UINT4 n )
00454 {
00455   int saveErrno = xlalErrno;
00456   UINT4 i;
00457   for ( i = 0; i < n; ++i )
00458     if ( work[i].data )
00459       XLALDestroyREAL8Vector( work[i].data );
00460   LALFree( work );
00461   xlalErrno = saveErrno;
00462   return;
00463 }
00464 
00465 /* comparison for floating point numbers */
00466 static int compare_REAL4( const void *p1, const void *p2 )
00467 {
00468   REAL4 x1 = *(const REAL4 *)p1;
00469   REAL4 x2 = *(const REAL4 *)p2;
00470   return (x1 > x2) - (x1 < x2);
00471 }
00472 static int compare_REAL8( const void *p1, const void *p2 )
00473 {
00474   REAL8 x1 = *(const REAL8 *)p1;
00475   REAL8 x2 = *(const REAL8 *)p2;
00476   return (x1 > x2) - (x1 < x2);
00477 }
00478 
00479 
00480 /**
00481  *
00482  * Median Method: use median average rather than mean.  Note: this will
00483  * cause a bias if the segments overlap, i.e., if the stride is less than
00484  * the segment length -- even though the median bias for Gaussian noise
00485  * is accounted for -- because the segments are not independent and their
00486  * correlation is non-zero.
00487  *
00488  */
00489 int XLALREAL4AverageSpectrumMedian(
00490     REAL4FrequencySeries        *spectrum,
00491     const REAL4TimeSeries       *tseries,
00492     UINT4                        seglen,
00493     UINT4                        stride,
00494     const REAL4Window           *window,
00495     const REAL4FFTPlan          *plan
00496     )
00497 {
00498   static const char *func = "XLALREAL4AverageSpectrumMedian";
00499   REAL4FrequencySeries *work; /* array of frequency series */
00500   REAL4 *bin; /* array of bin values */
00501   REAL4 biasfac; /* median bias factor */
00502   REAL4 normfac; /* normalization factor */
00503   UINT4 reclen; /* length of entire data record */
00504   UINT4 numseg;
00505   UINT4 seg;
00506   UINT4 k;
00507 
00508   if ( ! spectrum || ! tseries || ! plan )
00509       XLAL_ERROR( func, XLAL_EFAULT );
00510   if ( ! spectrum->data || ! tseries->data )
00511       XLAL_ERROR( func, XLAL_EINVAL );
00512   if ( tseries->deltaT <= 0.0 )
00513       XLAL_ERROR( func, XLAL_EINVAL );
00514 
00515   reclen = tseries->data->length;
00516   numseg = 1 + (reclen - seglen)/stride;
00517 
00518   /* consistency check for lengths: make sure that the segments cover the
00519    * data record completely */
00520   if ( (numseg - 1)*stride + seglen != reclen )
00521     XLAL_ERROR( func, XLAL_EBADLEN );
00522   if ( spectrum->data->length != seglen/2 + 1 )
00523     XLAL_ERROR( func, XLAL_EBADLEN );
00524 
00525   /* make sure window, if present, is appropriate */
00526   if ( window )
00527   {
00528     if ( ! window->data )
00529       XLAL_ERROR( func, XLAL_EINVAL );
00530     if ( window->sumofsquares <= 0.0 )
00531       XLAL_ERROR( func, XLAL_EINVAL );
00532     if ( window->data->length != seglen )
00533       XLAL_ERROR( func, XLAL_EBADLEN );
00534   }
00535 
00536   /* create frequency series data workspaces */
00537   work = LALCalloc( numseg, sizeof( *work ) );
00538   if ( ! work )
00539     XLAL_ERROR( func, XLAL_ENOMEM );
00540   for ( seg = 0; seg < numseg; ++seg )
00541   {
00542     work[seg].data = XLALCreateREAL4Vector( spectrum->data->length );
00543     if ( ! work[seg].data )
00544     {
00545       median_cleanup_REAL4( work, numseg ); /* cleanup */
00546       XLAL_ERROR( func, XLAL_EFUNC );
00547     }
00548   }
00549 
00550   for ( seg = 0; seg < numseg; ++seg )
00551   {
00552     REAL4Vector savevec; /* save the time series data vector */
00553     int code;
00554 
00555     /* save the time series data vector */
00556     savevec = *tseries->data;
00557 
00558     /* set the data vector to be appropriate for the even segment */
00559     tseries->data->length  = seglen;
00560     tseries->data->data   += seg * stride;
00561 
00562     /* compute the modified periodogram for the even segment */
00563     code = XLALREAL4ModifiedPeriodogram( work + seg, tseries, window, plan );
00564     
00565     /* restore the time series data vector to its original state */
00566     *tseries->data = savevec;
00567 
00568     /* now check for failure of the XLAL routine */
00569     if ( code == XLAL_FAILURE )
00570     {
00571       median_cleanup_REAL4( work, numseg ); /* cleanup */
00572       XLAL_ERROR( func, XLAL_EFUNC );
00573     }
00574   }
00575 
00576   /* create array to hold a particular frequency bin data */
00577   bin = LALMalloc( numseg * sizeof( *bin ) );
00578   if ( ! bin )
00579   {
00580     median_cleanup_REAL4( work, numseg ); /* cleanup */
00581     XLAL_ERROR( func, XLAL_ENOMEM );
00582   }
00583 
00584   /* compute median bias factor */
00585   biasfac = XLALMedianBias( numseg );
00586 
00587   /* normaliztion takes into account bias */
00588   normfac = 1.0 / biasfac;
00589 
00590   /* now loop over frequency bins and compute the median-mean */
00591   for ( k = 0; k < spectrum->data->length; ++k )
00592   {
00593     /* assign array of even segment values to bin array for this freq bin */
00594     for ( seg = 0; seg < numseg; ++seg )
00595       bin[seg] = work[seg].data->data[k];
00596 
00597     /* sort them and find median */
00598     qsort( bin, numseg, sizeof( *bin ), compare_REAL4 );
00599     if ( numseg % 2 ) /* odd number of evens */
00600       spectrum->data->data[k] = bin[numseg/2];
00601     else /* even number... take average */
00602       spectrum->data->data[k] = 0.5*(bin[numseg/2-1] + bin[numseg/2]);
00603 
00604     /* remove median bias */
00605     spectrum->data->data[k] *= normfac;
00606   }
00607 
00608   /* set metadata */
00609   spectrum->epoch       = work->epoch;
00610   spectrum->f0          = work->f0;
00611   spectrum->deltaF      = work->deltaF;
00612   spectrum->sampleUnits = work->sampleUnits;
00613 
00614   /* free the workspace data */
00615   LALFree( bin );
00616   median_cleanup_REAL4( work, numseg );
00617 
00618   return 0;
00619 }
00620 
00621 /**
00622  *
00623  * Median Method: use median average rather than mean.  Note: this will
00624  * cause a bias if the segments overlap, i.e., if the stride is less than
00625  * the segment length -- even though the median bias for Gaussian noise
00626  * is accounted for -- because the segments are not independent and their
00627  * correlation is non-zero.
00628  *
00629  */
00630 int XLALREAL8AverageSpectrumMedian(
00631     REAL8FrequencySeries        *spectrum,
00632     const REAL8TimeSeries       *tseries,
00633     UINT4                        seglen,
00634     UINT4                        stride,
00635     const REAL8Window           *window,
00636     const REAL8FFTPlan          *plan
00637     )
00638 {
00639   static const char *func = "XLALREAL8AverageSpectrumMedian";
00640   REAL8FrequencySeries *work; /* array of frequency series */
00641   REAL8 *bin; /* array of bin values */
00642   REAL8 biasfac; /* median bias factor */
00643   REAL8 normfac; /* normalization factor */
00644   UINT4 reclen; /* length of entire data record */
00645   UINT4 numseg;
00646   UINT4 seg;
00647   UINT4 k;
00648 
00649   if ( ! spectrum || ! tseries || ! plan )
00650       XLAL_ERROR( func, XLAL_EFAULT );
00651   if ( ! spectrum->data || ! tseries->data )
00652       XLAL_ERROR( func, XLAL_EINVAL );
00653   if ( tseries->deltaT <= 0.0 )
00654       XLAL_ERROR( func, XLAL_EINVAL );
00655 
00656   reclen = tseries->data->length;
00657   numseg = 1 + (reclen - seglen)/stride;
00658 
00659   /* consistency check for lengths: make sure that the segments cover the
00660    * data record completely */
00661   if ( (numseg - 1)*stride + seglen != reclen )
00662     XLAL_ERROR( func, XLAL_EBADLEN );
00663   if ( spectrum->data->length != seglen/2 + 1 )
00664     XLAL_ERROR( func, XLAL_EBADLEN );
00665 
00666   /* make sure window, if present, is appropriate */
00667   if ( window )
00668   {
00669     if ( ! window->data )
00670       XLAL_ERROR( func, XLAL_EINVAL );
00671     if ( window->sumofsquares <= 0.0 )
00672       XLAL_ERROR( func, XLAL_EINVAL );
00673     if ( window->data->length != seglen )
00674       XLAL_ERROR( func, XLAL_EBADLEN );
00675   }
00676 
00677   /* create frequency series data workspaces */
00678   work = LALCalloc( numseg, sizeof( *work ) );
00679   if ( ! work )
00680     XLAL_ERROR( func, XLAL_ENOMEM );
00681   for ( seg = 0; seg < numseg; ++seg )
00682   {
00683     work[seg].data = XLALCreateREAL8Vector( spectrum->data->length );
00684     if ( ! work[seg].data )
00685     {
00686       median_cleanup_REAL8( work, numseg ); /* cleanup */
00687       XLAL_ERROR( func, XLAL_EFUNC );
00688     }
00689   }
00690 
00691   for ( seg = 0; seg < numseg; ++seg )
00692   {
00693     REAL8Vector savevec; /* save the time series data vector */
00694     int code;
00695 
00696     /* save the time series data vector */
00697     savevec = *tseries->data;
00698 
00699     /* set the data vector to be appropriate for the even segment */
00700     tseries->data->length  = seglen;
00701     tseries->data->data   += seg * stride;
00702 
00703     /* compute the modified periodogram for the even segment */
00704     code = XLALREAL8ModifiedPeriodogram( work + seg, tseries, window, plan );
00705     
00706     /* restore the time series data vector to its original state */
00707     *tseries->data = savevec;
00708 
00709     /* now check for failure of the XLAL routine */
00710     if ( code == XLAL_FAILURE )
00711     {
00712       median_cleanup_REAL8( work, numseg ); /* cleanup */
00713       XLAL_ERROR( func, XLAL_EFUNC );
00714     }
00715   }
00716 
00717   /* create array to hold a particular frequency bin data */
00718   bin = LALMalloc( numseg * sizeof( *bin ) );
00719   if ( ! bin )
00720   {
00721     median_cleanup_REAL8( work, numseg ); /* cleanup */
00722     XLAL_ERROR( func, XLAL_ENOMEM );
00723   }
00724 
00725   /* compute median bias factor */
00726   biasfac = XLALMedianBias( numseg );
00727 
00728   /* normaliztion takes into account bias */
00729   normfac = 1.0 / biasfac;
00730 
00731   /* now loop over frequency bins and compute the median-mean */
00732   for ( k = 0; k < spectrum->data->length; ++k )
00733   {
00734     /* assign array of even segment values to bin array for this freq bin */
00735     for ( seg = 0; seg < numseg; ++seg )
00736       bin[seg] = work[seg].data->data[k];
00737 
00738     /* sort them and find median */
00739     qsort( bin, numseg, sizeof( *bin ), compare_REAL8 );
00740     if ( numseg % 2 ) /* odd number of evens */
00741       spectrum->data->data[k] = bin[numseg/2];
00742     else /* even number... take average */
00743       spectrum->data->data[k] = 0.5*(bin[numseg/2-1] + bin[numseg/2]);
00744 
00745     /* remove median bias */
00746     spectrum->data->data[k] *= normfac;
00747   }
00748 
00749   /* set metadata */
00750   spectrum->epoch       = work->epoch;
00751   spectrum->f0          = work->f0;
00752   spectrum->deltaF      = work->deltaF;
00753   spectrum->sampleUnits = work->sampleUnits;
00754 
00755   /* free the workspace data */
00756   LALFree( bin );
00757   median_cleanup_REAL8( work, numseg );
00758 
00759   return 0;
00760 }
00761 
00762 
00763 /* 
00764  *
00765  * Median-Mean Method
00766  *
00767  */
00768 
00769 
00770 /* cleanup temporary workspace... ignore xlal errors */
00771 static void median_mean_cleanup_REAL4( REAL4FrequencySeries *even, REAL4FrequencySeries *odd, UINT4 n )
00772 {
00773   int saveErrno = xlalErrno;
00774   UINT4 i;
00775   for ( i = 0; i < n; ++i )
00776   {
00777     if ( even[i].data )
00778       XLALDestroyREAL4Vector( even[i].data );
00779     if ( odd[i].data )
00780       XLALDestroyREAL4Vector( odd[i].data );
00781   }
00782   LALFree( even );
00783   LALFree( odd );
00784   xlalErrno = saveErrno;
00785   return;
00786 }
00787 static void median_mean_cleanup_REAL8( REAL8FrequencySeries *even, REAL8FrequencySeries *odd, UINT4 n )
00788 {
00789   int saveErrno = xlalErrno;
00790   UINT4 i;
00791   for ( i = 0; i < n; ++i )
00792   {
00793     if ( even[i].data )
00794       XLALDestroyREAL8Vector( even[i].data );
00795     if ( odd[i].data )
00796       XLALDestroyREAL8Vector( odd[i].data );
00797   }
00798   LALFree( even );
00799   LALFree( odd );
00800   xlalErr