void smf_filter_edge( smfFilter *filt, double f, double w, int lowpass, int *status ) { size_t base; /* Index to start of memory to be zero'd */ size_t i; /* Channel index */ size_t iedge; /* Index corresponding to the edge frequency */ size_t hw; /* The half-width of transition zone in channels */ size_t len; /* Length of memory to be zero'd */ if( *status != SAI__OK ) return; if( !filt ) { *status = SAI__ERROR; errRep( FUNC_NAME, "NULL smfFilter supplied.", status ); return; } if( filt->ndims != 1 ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": function only generates filters for time-series", status ); return; } /* If filt->real is NULL, create a real identity filter first */ if( !filt->real ) { smf_filter_ident( filt, 0, status ); if( *status != SAI__OK ) return; } /* Calculate offset of edge frequency in filter */ iedge = smf_get_findex( f, filt->df[0], filt->fdims[0], status ); /* Get half the width of the transition zone in channels. */ hw = 0.5*w/filt->df[0]; /* Hard-edged... */ if( hw <= 0 ) { /* Since we're zero'ing a continuous piece of memory, just use memset */ if( lowpass ) { /* Zero frequencies beyond edge */ base = iedge; len = filt->fdims[0] - iedge; } else { /* Zero frequencies from 0 to edge */ base = 0; len = iedge + 1; } memset( ((unsigned char *) filt->real) + base*sizeof(*filt->real), 0, len*sizeof(*filt->real) ); if( filt->isComplex ) { memset( ((unsigned char *) filt->imag) + base*sizeof(*filt->imag), 0, len*sizeof(*filt->imag) ); } /* Soft-edged */ } else { size_t ilo = ( iedge > hw ) ? iedge - hw : 0; size_t ihi = ( iedge + hw < filt->fdims[0] ) ? iedge + hw : filt->fdims[0]; /* For low pass, we can skip the channels below the transisition zone, which will already hold 1.0 */ if( lowpass ) { for( i = ilo; i < filt->fdims[0]; i++ ) { if( i > ihi ) { (filt->real)[ i ] = 0.0; } else { (filt->real)[ i ] = 0.5*( 1.0 - sin(AST__DPIBY2*( (float) i - (float) iedge )/hw)); } } /* For high pass, we can skip the channels above the transisition zone, which will already hold 1.0 */ } else { for( i = 0; i < ihi; i++ ) { if( i < ilo ) { (filt->real)[ i ] = 0.0; } else { (filt->real)[ i ] = 0.5*( 1.0 + sin(AST__DPIBY2*( (float) i - (float) iedge )/hw)); } } } /* For complex filters, copy the real part into the imaginary part. */ if( filt->isComplex ) { memcpy( filt->imag, filt->real, filt->fdims[0]*sizeof(*filt->imag) ); } } }
void smf_bolonoise( ThrWorkForce *wf, smfData *data, double gfrac, size_t window, double f_low, double f_white1, double f_white2, int nep, size_t len, double *whitenoise, double *fratio, smfData **fftpow,int *status ) { double *base=NULL; /* Pointer to base coordinates of array */ size_t bstride; /* bolometer index stride */ double df=1; /* Frequency step size in Hz */ size_t i; /* Loop counter */ size_t i_low; /* Index in power spectrum to f_low */ size_t i_w1; /* Index in power spectrum to f_white1 */ size_t i_w2; /* Index in power spectrum to f_white2 */ size_t j; /* Loop counter */ size_t mingood; /* Min. required no. of good values in bolometer */ dim_t nbolo; /* Number of bolometers */ dim_t ndata; /* Number of data points */ dim_t nf=0; /* Number of frequencies */ size_t ngood; /* Number of good samples */ dim_t ntslice; /* Number of time slices */ double p_low; /* Power at f_low */ double p_white; /* Average power from f_white1 to f_white2 */ smfData *pow=NULL; /* Pointer to power spectrum data */ smf_qual_t *qua=NULL; /* Pointer to quality component */ double steptime=1; /* Length of a sample in seconds */ size_t tstride; /* time index stride */ if (*status != SAI__OK) return; /* Check inputs */ if (!smf_dtype_check_fatal( data, NULL, SMF__DOUBLE, status )) return; if( !data->hdr ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": smfData has no header", status ); return; } /* Obtain dimensions */ smf_get_dims( data, NULL, NULL, &nbolo, &ntslice, &ndata, &bstride, &tstride, status ); if( *status==SAI__OK ) { steptime = data->hdr->steptime; if( steptime < VAL__SMLD ) { *status = SAI__ERROR; errRep("", FUNC_NAME ": FITS header error, STEPTIME must be > 0", status); } else { /* Frequency steps in the FFT */ df = 1. / (steptime * (double) ntslice ); } } /* Initialize arrays */ if( whitenoise ) for(i=0; i<nbolo; i++) whitenoise[i] = VAL__BADD; if( fratio ) for(i=0; i<nbolo; i++) fratio[i] = VAL__BADD; /* FFT the data and convert to polar power spectral density form */ pow = smf_fft_data( wf, data, NULL, 0, len, status ); smf_convert_bad( wf, pow, status ); smf_fft_cart2pol( wf, pow, 0, 1, status ); { dim_t fdims[2]; smf_isfft( pow, NULL, NULL, fdims, NULL, NULL, status ); if( *status == SAI__OK ) nf=fdims[0]; } /* Check for reasonble frequencies, and integer offsets in the array */ i_low = smf_get_findex( f_low, df, nf, status ); i_w1 = smf_get_findex( f_white1, df, nf, status ); i_w2 = smf_get_findex( f_white2, df, nf, status ); /* Get the quality pointer from the smfData so that we can mask known bad bolometer. */ qua = smf_select_qualpntr( data, NULL, status ); /* The minimum required number of good values in a bolometer. */ mingood = ( gfrac > 0.0 ) ? ntslice*gfrac : 0; /* Loop over detectors */ for( i=0; (*status==SAI__OK)&&(i<nbolo); i++ ) if( !qua || !(qua[i*bstride]&SMF__Q_BADB) ) { /* Pointer to start of power spectrum */ base = pow->pntr[0]; base += nf*i; /* Smooth the power spectrum */ smf_boxcar1D( base, nf, 1, window, NULL, 0, 1, NULL, status ); /* Measure the power */ if( *status == SAI__OK ) { p_low = base[i_low]; smf_stats1D( base+i_w1, 1, i_w2-i_w1+1, NULL, 0, 0, &p_white, NULL, NULL, &ngood, status ); /* It's OK if bad status was generated as long as a mean was calculated */ if( *status==SMF__INSMP ) { errAnnul( status ); /* if we had no good data there was probably a problem with SMF__Q_BADB so we simply go to the next bolometer */ if (ngood == 0) continue; } /* Count the number of initially good values for the current bolometer. */ if( (*status==SAI__OK) && qua ) { ngood = 0; for( j=0; j<ntslice; j++ ) { if( qua[i*bstride + j*tstride] == 0 ) ngood++; } /* Set bolometer to bad if no power detected, or the number of good values is too low. */ if( (p_low <= 0) || (p_white <= 0) || (ngood < mingood) ) { for( j=0; j<ntslice; j++ ) { qua[i*bstride + j*tstride] |= SMF__Q_BADB; } } } } if( (*status==SAI__OK) && (!qua || !(qua[i*bstride]&SMF__Q_BADB)) ) { /* Power ratio requested */ if ( fratio ) { fratio[i] = p_low/p_white; } /* Store values */ if( whitenoise ) { /* Integrate the PSD by multiplying the average white noise level by total number of samples and the frequency spacing: this calculates the time-domain variance (in 200 Hz SCUBA-2 samples for example) assuming this level holds at all frequencies. */ whitenoise[i] = p_white * ntslice * df; /* If NEP set, scale this to variance in a 1-second average by dividing by the sampling frequency (equivalent to multiplying by sample length). */ if( nep ) { whitenoise[i] *= steptime; } } } } /* Clean up if the caller does not want to take over the power spectrum */ if( pow ) { if (fftpow) { *fftpow = pow; } else { smf_close_file( &pow, status ); } } }
void smf_fit_pspec( const double *pspec, dim_t nf, size_t box, double df, double minfreq, double whitefreq, double maxfreq, double *a, double *b, double *w, int *status ) { double A=VAL__BADD; /* Amplitude of 1/f component */ double B=VAL__BADD; /* Exponent of 1/f component */ int converged; /* Has white noise level calc converged? */ double fit[2]; /* fit coefficients */ size_t i; /* Loop counter */ size_t i_flo; /* Index of lowest frequency for calculating 1/f */ size_t i_whi; /* Index of high-freq. edge for white-noise level */ size_t i_wlo; /* Index of low-freq. edge for white-noise level */ size_t nbad; /* Number of outliers in white noise calc */ smf_qual_t *qual=NULL; /* Quality to go with pspec */ double sigma; /* Uncertainty in white noise level */ size_t thisnbad; /* Outliers in white noise calc, this iteration */ double white=VAL__BADD;/* White noise level */ double *x=NULL; /* Independent axis for 1/f fit */ double *y=NULL; /* Dependent axis for 1/f fit */ if (*status != SAI__OK) return; if( (df<=0) || (nf<1) ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": invalid frequency information supplied.", status ); return; } /* Convert frequencies to array indices (but only consider above DC) */ i_flo = smf_get_findex( minfreq, df, nf, status ); i_wlo = smf_get_findex( whitefreq, df, nf, status ); if( !maxfreq ) { /* If maxfreq is 0 set to Nyquist */ i_whi = nf-1; } else { i_whi = smf_get_findex( maxfreq, df, nf, status ); } if( i_flo+box*2 > nf ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": box too large compared to number of frequencies", status ); return; } /* Create a local quality array to go with pspec */ qual = astCalloc( nf, sizeof(*qual) ); if( i_flo == 0 ) i_flo = 1; if( (*status==SAI__OK) && ((i_flo > i_wlo) || (i_wlo > i_whi)) ) { *status = SAI__ERROR; errRepf( "", FUNC_NAME ": must have i_maxfreq (%zu) > i_whitefreq (%zu) > " "i_minfreq (%zu)", status, i_whi, i_wlo, i_flo ); } /* Calculate the white noise level */ converged = 0; nbad = 0; while( (!converged) && (*status==SAI__OK) ) { double thresh; smf_stats1D( pspec+i_wlo, 1, i_whi-i_wlo+1, qual, 1, SMF__Q_SPIKE, &white, &sigma, NULL, NULL, status ); if( *status==SAI__OK ) { thresh = white + SMF__FPSPEC_WHITESNR*sigma; thisnbad = 0; for( i=i_wlo; i<=i_whi; i++ ) { if( pspec[i] > thresh ) { qual[i] = SMF__Q_SPIKE; thisnbad++; } } if( thisnbad==nbad ) { converged = 1; } nbad = thisnbad; } } /* Now identify and fit the 1/f part of the power spectrum. We use a rolling box and increase the frequency of its centre until the fraction of samples below some threshold above the white level is exceeded. We then fit a straight line to the logarithm of the x- and y-axes. */ if( *status == SAI__OK ) { size_t nfit; size_t ngood = 0; size_t nused; double thresh = white + SMF__FPSPEC_KNEESNR*sigma; /* Initialize ngood -- skip the DC term in the FFT */ for( i=i_flo; i<(i_flo+box); i++ ) { if( pspec[i] > thresh ) { ngood++; } } /* Continuing from next element, and go until we hit the end or we reach the threshold number of samples below the threshold SNR above the white noise level. */ for( i=i_flo+1; (i<(nf-(box-1))) && (ngood >= SMF__FPSPEC_KNEETHRESH*box); i++ ) { /* If the previous first element was good remove it */ if( pspec[i-1] >= thresh ) { ngood--; } /* If the new last element is good add it */ if( pspec[i+box-1] >= thresh ) { ngood++; } } /* We will fit the power-law from elements i_flo to nfit-1. We then evaluate the fitted power law as y = A * x^B where x is the index in the frequency array A = exp( fit[0] ) B = fit[1] */ nfit = i+box/2-i_flo; msgOutiff( MSG__DEBUG, "", FUNC_NAME ": i_flow=%zu nfit=%zu i_wlo=%zu i_whi=%zu\n", status, i_flo, nfit-1, i_wlo, i_whi); /* If we've entered the white-noise band in order to fit the 1/f noise give up */ if( i >= i_wlo ) { *status = SMF__BADFIT; errRep( "", FUNC_NAME ": unable to fit 1/f spectrum with provided frequency ranges", status); goto CLEANUP; } /* Now fit a straight line to the log of the two axes */ x = astMalloc( (nfit-1)*sizeof(*x) ); y = astMalloc( (nfit-1)*sizeof(*y) ); if( *status == SAI__OK ) { for( i=0; i<(nfit-1); i++ ) { x[i] = log((i+i_flo)*df); y[i] = log(pspec[i+i_flo]); } } smf_fit_poly1d( 1, nfit-1, 0, x, y, NULL, NULL, fit, NULL, NULL, &nused, status ); if( *status == SAI__OK ) { /* if the exponent is positive the fit is either garbage, or else there just isn't very much low frequency noise compared to the white noise level. Set B to 0 but generate a warning message */ if( fit[1] >= 0 ) { *status = SMF__BADFIT; errRep( "", FUNC_NAME ": fit to 1/f component encountered rising spectrum!", status); goto CLEANUP; } else { B = fit[1]; } A = (exp(fit[0])); } } /* Return fit values */ if( a ) *a = A; if( b ) *b = B; if( w ) *w = white; CLEANUP: qual = astFree( qual ); x = astFree( x ); y = astFree( y ); }