void smurf_sc2fft( int *status ) { int avpspec=0; /* Flag for doing average power spectrum */ double avpspecthresh=0; /* Threshold noise for detectors in avpspec */ Grp * basegrp = NULL; /* Basis group for output filenames */ smfArray *bbms = NULL; /* Bad bolometer masks */ smfArray *concat=NULL; /* Pointer to a smfArray */ size_t contchunk; /* Continuous chunk counter */ smfArray *darks = NULL; /* dark frames */ int ensureflat; /* Flag for flatfielding data */ Grp *fgrp = NULL; /* Filtered group, no darks */ smfArray *flatramps = NULL;/* Flatfield ramps */ AstKeyMap *heateffmap = NULL; /* Heater efficiency data */ size_t gcount=0; /* Grp index counter */ size_t i; /* Loop counter */ smfGroup *igroup=NULL; /* smfGroup corresponding to igrp */ Grp *igrp = NULL; /* Input group of files */ int inverse=0; /* If set perform inverse transform */ int isfft=0; /* Are data fft or real space? */ dim_t maxconcat=0; /* Longest continuous chunk length in samples */ size_t ncontchunks=0; /* Number continuous chunks outside iter loop */ smfData *odata=NULL; /* Pointer to output smfData to be exported */ Grp *ogrp = NULL; /* Output group of files */ size_t outsize; /* Total number of NDF names in the output group */ int polar=0; /* Flag for FFT in polar coordinates */ int power=0; /* Flag for squaring amplitude coeffs */ size_t size; /* Number of files in input group */ smfData *tempdata=NULL; /* Temporary smfData pointer */ int weightavpspec=0; /* Flag for 1/noise^2 weighting */ ThrWorkForce *wf = NULL; /* Pointer to a pool of worker threads */ int zerobad; /* Zero VAL__BADD before taking FFT? */ /* Main routine */ ndfBegin(); /* Find the number of cores/processors available and create a pool of threads of the same size. */ wf = thrGetWorkforce( thrGetNThread( SMF__THREADS, status ), status ); /* Get input file(s) */ kpg1Rgndf( "IN", 0, 1, "", &igrp, &size, status ); /* Filter out darks */ smf_find_science( igrp, &fgrp, 1, NULL, NULL, 1, 1, SMF__NULL, &darks, &flatramps, &heateffmap, NULL, status ); /* input group is now the filtered group so we can use that and free the old input group */ size = grpGrpsz( fgrp, status ); grpDelet( &igrp, status); igrp = fgrp; fgrp = NULL; /* We now need to combine files from the same subarray and same sequence to form a continuous time series */ smf_grp_related( igrp, size, 1, 0, 0, NULL, NULL, &maxconcat, NULL, &igroup, &basegrp, NULL, status ); /* Get output file(s) */ size = grpGrpsz( basegrp, status ); if( size > 0 ) { kpg1Wgndf( "OUT", basegrp, size, size, "More output files required...", &ogrp, &outsize, status ); } else { msgOutif(MSG__NORM, " ", TASK_NAME ": All supplied input frames were DARK," " nothing to do", status ); } /* Get group of bolometer masks and read them into a smfArray */ smf_request_mask( "BBM", &bbms, status ); /* Obtain the number of continuous chunks and subarrays */ if( *status == SAI__OK ) { ncontchunks = igroup->chunk[igroup->ngroups-1]+1; } msgOutiff( MSG__NORM, "", "Found %zu continuous chunk%s", status, ncontchunks, (ncontchunks > 1 ? "s" : "") ); /* Are we flatfielding? */ parGet0l( "FLAT", &ensureflat, status ); /* Are we doing an inverse transform? */ parGet0l( "INVERSE", &inverse, status ); /* Are we using polar coordinates instead of cartesian for the FFT? */ parGet0l( "POLAR", &polar, status ); /* Are we going to assume amplitudes are squared? */ parGet0l( "POWER", &power, status ); /* Are we going to zero bad values first? */ parGet0l( "ZEROBAD", &zerobad, status ); /* Are we calculating the average power spectrum? */ parGet0l( "AVPSPEC", &avpspec, status ); if( avpspec ) { power = 1; parGet0d( "AVPSPECTHRESH", &avpspecthresh, status ); parGet0l( "WEIGHTAVPSPEC", &weightavpspec, status ); } /* If power is true, we must be in polar form */ if( power && !polar) { msgOutif( MSG__NORM, " ", TASK_NAME ": power spectrum requested so setting POLAR=TRUE", status ); polar = 1; } gcount = 1; for( contchunk=0;(*status==SAI__OK)&&contchunk<ncontchunks; contchunk++ ) { size_t idx; /* Concatenate this continuous chunk but forcing a raw data read. We will need quality. */ smf_concat_smfGroup( wf, NULL, igroup, darks, NULL, flatramps, heateffmap, contchunk, ensureflat, 1, NULL, 0, NULL, NULL, 0, 0, 0, &concat, NULL, status ); /* Now loop over each subarray */ /* Export concatenated data for each subarray to NDF file */ for( idx=0; (*status==SAI__OK)&&idx<concat->ndat; idx++ ) { if( concat->sdata[idx] ) { smfData * idata = concat->sdata[idx]; int provid = NDF__NOID; dim_t nbolo; /* Number of detectors */ dim_t ndata; /* Number of data points */ /* Apply a mask to the quality array and data array */ smf_apply_mask( idata, bbms, SMF__BBM_QUAL|SMF__BBM_DATA, 0, status ); smf_get_dims( idata, NULL, NULL, &nbolo, NULL, &ndata, NULL, NULL, status ); /* Check for double precision data */ if( idata->dtype != SMF__DOUBLE ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": data are not double precision.", status ); } /* Are we zeroing VAL__BADD? */ if( (*status==SAI__OK) && zerobad ) { double *data= (double *) idata->pntr[0]; for( i=0; i<ndata; i++ ) { if( data[i] == VAL__BADD ) { data[i] = 0; } } } /* Check whether we need to transform the data at all */ isfft = smf_isfft(idata,NULL,NULL,NULL,NULL,NULL,status); if( isfft && avpspec && (*status == SAI__OK) ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": to calculate average power spectrum input data cannot " "be FFT", status ); } if( (*status == SAI__OK) && (isfft == inverse) ) { if( avpspec ) { /* If calculating average power spectrum do the transforms with smf_bolonoise so that we can also measure the noise of each detector */ double *whitenoise=NULL; smf_qual_t *bolomask=NULL; double mean, sig, freqlo; size_t ngood, newgood; whitenoise = astCalloc( nbolo, sizeof(*whitenoise) ); bolomask = astCalloc( nbolo, sizeof(*bolomask) ); freqlo = 1. / (idata->hdr->steptime * idata->hdr->nframes); smf_bolonoise( wf, idata, 1, freqlo, SMF__F_WHITELO, SMF__F_WHITEHI, 1, 0, whitenoise, NULL, &odata, status ); /* Initialize quality */ for( i=0; i<nbolo; i++ ) { if( whitenoise[i] == VAL__BADD ) { bolomask[i] = SMF__Q_BADB; } else { /* smf_bolonoise returns a variance, so take sqrt */ whitenoise[i] = sqrt(whitenoise[i]); } } ngood=-1; newgood=0; /* Iteratively cut n-sigma noisy outlier detectors */ while( ngood != newgood ) { ngood = newgood; smf_stats1D( whitenoise, 1, nbolo, bolomask, 1, SMF__Q_BADB, &mean, &sig, NULL, NULL, status ); msgOutiff( MSG__DEBUG, "", TASK_NAME ": mean=%lf sig=%lf ngood=%li\n", status, mean, sig, ngood); newgood=0; for( i=0; i<nbolo; i++ ) { if( whitenoise[i] != VAL__BADD ){ if( (whitenoise[i] - mean) > avpspecthresh *sig ) { whitenoise[i] = VAL__BADD; bolomask[i] = SMF__Q_BADB; } else { newgood++; } } } } msgOutf( "", TASK_NAME ": Calculating average power spectrum of best %li " " bolometers.", status, newgood); /* If using 1/noise^2 weights, calculate 1/whitenoise^2 in-place to avoid allocating another array */ if( weightavpspec ) { msgOutif( MSG__VERB, "", TASK_NAME ": using 1/noise^2 weights", status ); for( i=0; i<nbolo; i++ ) { if( whitenoise[i] && (whitenoise[i] != VAL__BADD) ) { whitenoise[i] = 1/(whitenoise[i]*whitenoise[i]); } } } /* Calculate the average power spectrum of good detectors */ tempdata = smf_fft_avpspec( odata, bolomask, 1, SMF__Q_BADB, weightavpspec ? whitenoise : NULL, status ); smf_close_file( &odata, status ); whitenoise = astFree( whitenoise ); bolomask = astFree( bolomask ); odata = tempdata; tempdata = NULL; /* Store the number of good bolometers */ parPut0i( "NGOOD", newgood, status ); } else { /* Otherwise do forward/inverse transforms here as needed */ /* If inverse transform convert to cartesian representation first */ if( inverse && polar ) { smf_fft_cart2pol( wf, idata, 1, power, status ); } /* Tranform the data */ odata = smf_fft_data( wf, idata, NULL, inverse, 0, status ); smf_convert_bad( wf, odata, status ); if( inverse ) { /* If output is time-domain, ensure that it is ICD bolo-ordered */ smf_dataOrder( odata, 1, status ); } else if( polar ) { /* Store FFT of data in polar form */ smf_fft_cart2pol( wf, odata, 0, power, status ); } } /* open a reference input file for provenance propagation */ ndgNdfas( basegrp, gcount, "READ", &provid, status ); /* Export the data to a new file */ smf_write_smfData( odata, NULL, NULL, ogrp, gcount, provid, MSG__VERB, 0, status ); /* Free resources */ ndfAnnul( &provid, status ); smf_close_file( &odata, status ); } else { msgOutif( MSG__NORM, " ", "Data are already transformed. No output will be produced", status ); } } /* Update index into group */ gcount++; } /* Close the smfArray */ smf_close_related( &concat, status ); } /* Write out the list of output NDF names, annulling the error if a null parameter value is supplied. */ if( *status == SAI__OK ) { grpList( "OUTFILES", 0, 0, NULL, ogrp, status ); if( *status == PAR__NULL ) errAnnul( status ); } /* Tidy up after ourselves: release the resources used by the grp routines */ grpDelet( &igrp, status); grpDelet( &ogrp, status); if (basegrp) grpDelet( &basegrp, status ); if( igroup ) smf_close_smfGroup( &igroup, status ); if( flatramps ) smf_close_related( &flatramps, status ); if (heateffmap) heateffmap = smf_free_effmap( heateffmap, status ); if (bbms) smf_close_related( &bbms, status ); ndfEnd( status ); /* Ensure that FFTW doesn't have any used memory kicking around */ fftw_cleanup(); }
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 ); } } }