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 ); } } }
void smf_filter2d_execute( ThrWorkForce *wf, smfData *data, smfFilter *filt, int complement, int *status ) { double *data_i=NULL; /* Imaginary part of the transformed data */ double *data_r=NULL; /* Real part of the transformed data */ smfData *fdata=NULL; /* Transform of data */ dim_t fdims[2]={0,0}; /* Frequency dimensions */ size_t i; /* loop counter */ size_t ndims=0; /* Number of real-space dimensions */ size_t nfdata; /* Total number of frequency data points */ smfData *varfilt=NULL; /* real-space square of supplied filter for var */ AstFrameSet *wcs=NULL; /* Copy of real-space WCS */ /* Main routine */ if (*status != SAI__OK) return; /* Check for NULL pointers */ if( !data ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": NULL smfData pointer", status ); return; } if( !filt ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": NULL smfFilter pointer", status ); return; } if( filt->ndims != 2 ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": Filter must be 2-dimensional", status ); return; } if( smf_isfft( data, NULL, NULL, fdims, NULL, &ndims, status ) ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": FFT'd data supplied!", status ); return; } if( ndims != 2 ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": supplied data are not a 2-d map", status ); return; } /* Check that the filter dimensions are appropriate for the data */ for( i=0; i<ndims; i++ ) { if( fdims[i] != filt->fdims[i] ) { *status = SAI__ERROR; errRepf( "", FUNC_NAME ": Filter axis %zu has length %zu, doesn't match data %zu", status, i, filt->fdims[i], fdims[i] ); return; } } /* Dimensions of the transformed data */ nfdata = 1; for( i=0; i<ndims; i++ ) { if( filt->fdims[i] != fdims[i] ) { *status = SAI__ERROR; errRepf( "", FUNC_NAME ": Filter axis %zu has dim %zu doesn't match data dim %zu", status, i, filt->fdims[i], fdims[i]); return; } nfdata *= fdims[i]; } /* Using complement of the filter? */ if( complement ) smf_filter_complement( filt, status ); /* Get a copy of the wcs of the input map */ if( data->hdr && data->hdr->wcs ) { wcs = astCopy( data->hdr->wcs ); } /* Transform the data */ fdata = smf_fft_data( wf, data, NULL, 0, 0, status ); /* Copy the FFT of the data if we will also be filtering the VARIANCE since this will get us a useful container of the correct dimensions for the squared filter */ if( data->pntr[1] ) { varfilt = smf_deepcopy_smfData( wf, fdata, 0, SMF__NOCREATE_VARIANCE | SMF__NOCREATE_QUALITY | SMF__NOCREATE_FILE | SMF__NOCREATE_DA, 0, 0, status ); } /* Apply the frequency-domain filter. */ if( *status == SAI__OK ) { data_r = fdata->pntr[0]; data_i = data_r + nfdata; if( filt->isComplex ) { double ac, bd, aPb, cPd; for( i=0; i<nfdata; i++ ) { ac = data_r[i] * filt->real[i]; bd = data_i[i] * filt->imag[i]; aPb = data_r[i] + data_i[i]; cPd = filt->real[i] + filt->imag[i]; } } else { for( i=0; i<nfdata; i++ ) { data_r[i] *= filt->real[i]; data_i[i] *= filt->real[i]; } } } /* Transform back */ smf_fft_data( wf, fdata, data, 1, 0, status ); /* Insert the copy of original real-space WCS into the smfHead since smf_fft_data does not currently calculate it for inverse transforms. */ if( data->hdr ) { /* Annul current wcs if it exists */ if( data->hdr->wcs ) { data->hdr->wcs = astAnnul( data->hdr->wcs ); } data->hdr->wcs = wcs; } /* If we have a VARIANCE component we also need to smooth it. This is slightly complicated because we have to do the equivalent of a real-space convolution between the variance map and the element-wise square of the real-space filter. So we first stuff the supplied filter into a smfData (frequency space), take its inverse to real space and square it. We then transform back to frequency space, and run it through smf_filter2d_execute to apply it to the VARIANCE map (which is also stuffed into its own smfData and then copied into the correct location of the supplied smfData when finished. */ if( (data->pntr[1]) && (*status==SAI__OK) ) { dim_t ndata; double *ptr=NULL; smfData *realfilter=NULL; /* Real space smfData container for var filter */ smfData *vardata=NULL; /* smfData container for variance only */ smfFilter *vfilt=NULL; /* The var filter */ /* Copy the filter into the smfData container and transform into the time domain. */ ptr = varfilt->pntr[0]; memcpy(ptr, filt->real, nfdata*sizeof(*ptr)); if( filt->imag) { memcpy(ptr+nfdata, filt->imag, nfdata*sizeof(*ptr)); } else { memset(ptr+nfdata, 0, nfdata*sizeof(*ptr)); } realfilter = smf_fft_data( wf, varfilt, NULL, 1, 0, status ); smf_close_file( wf, &varfilt, status ); /* Square each element of the real-space filter and then transform back to the frequency domain and stuff into a smfFilter (vfilt). We just point the real and imaginary parts of the smfFilter to the respective regions of the smfData to save memory/time, but we need to be careful when freeing at the end. */ if( *status == SAI__OK ) { ptr = realfilter->pntr[0]; smf_get_dims( realfilter, NULL, NULL, NULL, NULL, &ndata, NULL, NULL, status ); if( *status == SAI__OK ) { double norm = 1. / (double) ndata; for(i=0; i<ndata; i++) { /* Note that we need an additional normalization of N samples */ ptr[i] *= ptr[i] * norm; } } } varfilt = smf_fft_data( wf, realfilter, NULL, 0, 0, status ); if( *status == SAI__OK ) { ptr = varfilt->pntr[0]; vfilt = smf_create_smfFilter( data, status ); vfilt->real = ptr; if( filt->isComplex ) { /* Only worry about imaginary part if the original filter was complex. */ vfilt->isComplex = 1; vfilt->imag = ptr + nfdata; } } /* Now stuff the variance array into a smfData and filter it. */ vardata = smf_deepcopy_smfData( wf, data, 0, SMF__NOCREATE_VARIANCE | SMF__NOCREATE_QUALITY | SMF__NOCREATE_FILE | SMF__NOCREATE_DA, 0, 0, status ); if( *status == SAI__OK ) { ptr = vardata->pntr[0]; memcpy( ptr, data->pntr[1], ndata*sizeof(*ptr) ); smf_filter2d_execute( wf, vardata, vfilt, 0, status ); } /* Finally, copy the filtered variance into our output filtered smfData */ if( *status == SAI__OK ) { ptr = data->pntr[1]; memcpy( ptr, vardata->pntr[0], ndata*sizeof(*ptr) ); } /* Clean up */ if( realfilter ) smf_close_file( wf, &realfilter, status ); if( vardata ) smf_close_file( wf, &vardata, status ); if( vfilt ) { vfilt->real = NULL; vfilt->imag = NULL; vfilt = smf_free_smfFilter( vfilt, status ); } } /* Return the filter to its original state if required */ if( complement == -1 ) smf_filter_complement( filt, status ); /* Clean up */ if( varfilt ) smf_close_file( wf, &varfilt, status ); if( fdata ) smf_close_file( wf, &fdata, status ); }
void smf_filter2d_whiten( ThrWorkForce *wf, smfFilter *filt, smfData *map, double minfreq, double maxfreq, size_t smooth, int *status ) { double A; /* Amplitude 1/f component */ double B; /* exponent of 1/f component */ double df; /* Frequency spacing in ref pspec */ size_t i; /* Loop counter */ size_t j; /* Loop counter */ size_t ndims=0; /* Number of real-space dimensions */ size_t nf=0; /* Number of frequencies in ref pspec */ smfData *map_fft=NULL; /* FFT of the map */ smfData *pspec=NULL; /* Az-averaged PSPEC of map */ double *pspec_data=NULL; /* Pointer to DATA comp. of pspec */ double *smoothed_filter=NULL; /* Smoothed power spectrum */ double W; /* White noise level */ if( *status != SAI__OK ) return; if( !filt ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": NULL smfFilter supplied.", status ); return; } if( filt->ndims != 2 ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": supplied filter is not for 2-d map.", status ); return; } if( smf_isfft( map, NULL, NULL, NULL, NULL, &ndims, status ) ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": supplied map is FFT instead of real space!", status ); return; } /* Check for reasonable frequencies -- noting that maxfreq=0 defaults to nyquist. */ if( maxfreq && (maxfreq < minfreq) ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": maxfreq < minfreq!", status ); return; } /* Calculate azimuthally-averaged angular power spectrum */ map_fft = smf_fft_data( wf, map, NULL, 0, 0, status ); smf_fft_cart2pol( wf, map_fft, 0, 1, status ); pspec = smf_fft_2dazav( map_fft, &df, status ); if( *status == SAI__OK ) { nf = pspec->dims[0]; pspec_data = pspec->pntr[0]; } smf_close_file( &map_fft, status ); /* Fit power-law + white level model */ smf_fit_pspec( pspec_data, pspec->dims[0], 10, df, minfreq, 0.1, maxfreq, &A, &B, &W, status ); msgOutiff( MSG__DEBUG, "", FUNC_NAME ": P(f) = %lg*f^%lg + %lg\n", status, A, B, W ); if( smooth ) { int whichaxis=0; /* index of higher-resolution axis */ double df_s; /* Frequency spacing of smoothed_filter */ double nf_s; /* Size of the smoothed 1-d filter */ /* Re-sample the radial power spectrum measured in the map on to an array that corresponds to the highest-resolution (longer real-space) dimension of the supplied filter */ if( filt->rdims[1] > filt->rdims[0] ) { whichaxis = 1; } df_s = filt->df[whichaxis]; nf_s = filt->rdims[whichaxis]/2 + 1; smoothed_filter = astMalloc( nf_s*sizeof(*smoothed_filter) ); if( *status == SAI__OK ) { /* Nearest-neighbour... */ for( i=0; i<nf_s; i++ ) { size_t nearest = round(i * df_s / df ); if( nearest >= nf ) nearest = nf-1; smoothed_filter[i] = pspec_data[nearest]; } } /* Smooth pspec to estimate the radial power spectrum, but normalize by the white-noise level from the fit to preserve normalization -------------------------------------------------------- */ smf_tophat1D( smoothed_filter, nf_s, smooth, NULL, 0, 0.5, status ); /* Replace bad values (where not enough values to calculate median) with the original values. Then normalize by the white noise level, and take inverse square root */ if( *status == SAI__OK ) { for( i=0; i<nf_s; i++ ) { if( smoothed_filter[i] != VAL__BADD ) { smoothed_filter[i] = 1./sqrt(smoothed_filter[i]/W); } else if( i < smooth ) { /* Filter to 0 at low frequencies where we can't estimate median */ smoothed_filter[i] = 0; } else { /* Close to Nyquist we set the filter gain to 1 so that it doesn't do anything */ smoothed_filter[i] = 1; } } } /* Copy radial filter into 2d filter */ if( *status == SAI__OK ) { size_t d; /* radial distance (FFT pixels) in smoothed filter */ double model; /* the value of the model (smoothed filter) at d */ double x; /* x- spatial frequency */ double y; /* y- spatial frequency */ for( i=0; i<filt->fdims[0]; i++ ) { x = FFT_INDEX_TO_FREQ(i,filt->rdims[0]) * filt->df[0]; for( j=0; j<filt->fdims[1]; j++ ) { y = FFT_INDEX_TO_FREQ(j,filt->rdims[1]) * filt->df[1]; d = (size_t) round(sqrt(x*x + y*y)/df_s); if( d < nf_s) { model = smoothed_filter[d]; } else { model = 1; } filt->real[i + j*filt->fdims[0]] *= model; if( filt->imag ) filt->imag[i + j*filt->fdims[0]] *= model; } } } } else { /* --- otherwise use the smooth fitted model ---------------------------- */ if( *status == SAI__OK ) { double d; double model; double x; double y; /* We fit a model to the power spectrum, but we want to apply its complement to the FFT of the data. So we normalize by the white-noise level, and take the square root of the model before writing its complement to the filter buffer */ A = sqrt(A / W); B = B / 2.; for( i=0; i<filt->fdims[0]; i++ ) { x = FFT_INDEX_TO_FREQ(i,filt->rdims[0]) * filt->df[0]; for( j=0; j<filt->fdims[1]; j++ ) { y = FFT_INDEX_TO_FREQ(j,filt->rdims[1]) * filt->df[1]; d = sqrt(x*x + y*y); model = 1. + A * pow(d,B); filt->real[i + j*filt->fdims[0]] /= model; if( filt->imag ) filt->imag[i + j*filt->fdims[0]] /= model; } } } } /* Clean up */ if( pspec ) smf_close_file( &pspec, status ); if( smoothed_filter ) smoothed_filter = astFree( smoothed_filter ); }