size_t smf_check_quality( ThrWorkForce *wf, smfData *data, int showbad, int *status ) { double *d=NULL; /* Pointer to data array */ size_t nbad=0; /* inconsistency counter */ size_t nnan = 0; /* Number of nan values found */ size_t ninf = 0; /* Number of inf values found */ size_t nqualincon = 0; /* Number of inconsistent bad/qual */ dim_t ndata; /* Number of data points */ size_t bstride; /* bol stride */ size_t tstride; /* time slice stride */ smf_qual_t *qual=NULL; /* Pointer to the QUALITY array */ int nw; /* Number of worker threads */ int iw; /* Thread index */ SmfCheckQualityData *job_data = NULL; /* Array of job descriptions */ SmfCheckQualityData *pdata; /* Pointer to next job description */ size_t sampstep; /* Number of samples per thread */ if ( *status != SAI__OK ) return 0; /* Check for DATA */ if( !data ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": NULL data supplied", status ); return 0; } if( !data->pntr[0] ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": smfData does not contain a DATA component", status ); return 0; } if( data->dtype != SMF__DOUBLE ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": smfData does not have type SMF__DOUBLE", status ); return 0; } d = (double *) data->pntr[0]; /* Check for QUALITY */ qual = smf_select_qualpntr( data, NULL, status ); if( !qual ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": NULL quality supplied", status); return 0; } /* Calculate data dimensions */ smf_get_dims( data, NULL, NULL, NULL, NULL, &ndata, &bstride, &tstride, status ); if( *status == SAI__OK ) { /* How many threads do we get to play with */ nw = wf ? wf->nworker : 1; /* Find how many samples to process in each worker thread. */ sampstep = ndata/nw; if( sampstep == 0 ) sampstep = 1; /* Allocate job data for threads, and store the range of samples to be processed by each one. Ensure that the last thread picks up any left-over samples. */ job_data = astCalloc( nw, sizeof(*job_data) ); if( *status == SAI__OK ) { for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->d1 = iw*sampstep; if( iw < nw - 1 ) { pdata->d2 = pdata->d1 + sampstep - 1; } else { pdata->d2 = ndata - 1 ; } /* Store other values common to all jobs. */ pdata->qual = qual; pdata->d = d; pdata->showbad = showbad; pdata->bstride = bstride; pdata->tstride = tstride; /* Submit the job to the workforce. */ thrAddJob( wf, 0, pdata, smf1_check_quality, 0, NULL, status ); } /* Wait for all jobs to complete. */ thrWait( wf, status ); /* Accumulate the results from all the worker threads. */ for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; nbad += pdata->nbad; nnan += pdata->nnan; ninf += pdata->ninf; nqualincon += pdata->nqualincon; } /* Free the job data. */ job_data = astFree( job_data ); } } if (nbad > 0) { msgOutiff( MSG__VERB, "", "Quality inconsistency found: %zu DATA/QUAL, %zu NaN, %zu Inf", status, nqualincon, nnan, ninf ); } return nbad; }
size_t smf_clean_pca( ThrWorkForce *wf, smfData *data, size_t t_first, size_t t_last, double thresh, size_t ncomp, smfData **components, smfData **amplitudes, int flagbad, int sub, AstKeyMap *keymap, smf_qual_t mask, int *status ){ double *amp=NULL; /* matrix of components amplitudes for each bolo */ size_t abstride; /* bolo stride in amp array */ size_t acompstride; /* component stride in amp array */ size_t bstride; /* bolo stride */ double *comp=NULL; /* data cube of components */ size_t ccompstride; /* component stride in comp array */ size_t ctstride; /* time stride in comp array */ gsl_matrix *cov=NULL; /* bolo-bolo covariance matrix */ size_t i; /* Loop counter */ int ii; /* Loop counter */ size_t j; /* Loop counter */ smfPCAData *job_data=NULL;/* job data */ size_t k; /* Loop counter */ size_t *goodbolo=NULL; /* Indices of the good bolometers for analysis */ dim_t nbolo; /* number of bolos */ dim_t ndata; /* number of samples in data */ size_t ngoodbolo; /* number good bolos = number principal components */ dim_t ntslice; /* number of time slices */ int nw; /* total available worker threads */ smfPCAData *pdata=NULL; /* Pointer to job data */ smf_qual_t *qua=NULL; /* Pointer to quality array */ gsl_vector *s=NULL; /* singular values for SVD */ size_t bstep; /* Bolo step size for job division */ size_t step; /* step size for job division */ size_t tlen; /* Length of the time-series used for PCA */ size_t tstride; /* time slice stride */ gsl_vector *work=NULL; /* workspace for SVD */ if (*status != SAI__OK) return 0; /* How many threads do we get to play with */ nw = wf ? wf->nworker : 1; /* Check for NULL smfData pointer */ if( !data || !data->pntr[0]) { *status = SAI__ERROR; errRep( " ", FUNC_NAME ": possible programming error, NULL data supplied", status ); return 0; } smf_get_dims( data, NULL, NULL, &nbolo, &ntslice, &ndata, &bstride, &tstride, status ); if( data->ndims != 3 ) { *status = SAI__ERROR; errRep( " ", FUNC_NAME ": possible programming error, smfData should be 3-dimensional", status ); return 0; } if( data->dtype != SMF__DOUBLE ) { *status = SAI__ERROR; errRep( " ", FUNC_NAME ": possible programming error, smfData should be double precision", status ); return 0; } if( ntslice <= 2 ) { *status = SAI__ERROR; errRep( " ", FUNC_NAME ": fewer than 2 time slices!", status ); goto CLEANUP; } /* If the range of time slices has not been specified, us the total range excluding padding and apodizing. */ qua = smf_select_qualpntr( data, 0, status ); if( !t_last ) { if( qua ) { smf_get_goodrange( qua, ntslice, tstride, (SMF__Q_PAD | SMF__Q_APOD), &t_first, &t_last, status ); } else { t_last = ntslice-1; } } if( t_last > (ntslice-1) ) { *status = SAI__ERROR; errRep( " ", FUNC_NAME ": t_last is set past the last time slice!", status ); goto CLEANUP; } if( (t_last < t_first) || ( (t_last - t_first) < 1 ) ) { *status = SAI__ERROR; errRep( " ", FUNC_NAME ": t_last - t_first must be > 1", status ); goto CLEANUP; } tlen = t_last - t_first + 1; if( flagbad && (tlen != ntslice ) ) { *status = SAI__ERROR; errRep( " ", FUNC_NAME ": flagbad unsupported if t_first/last do not span full data", status ); goto CLEANUP; } if( qua ) { /* If quality supplied, identify good bolometers */ ngoodbolo = 0; for( i=0; i<nbolo; i++ ) { if( !(qua[i*bstride]&SMF__Q_BADB) ) { ngoodbolo++; } } /* Now remember which were the good bolometers */ goodbolo = astCalloc( ngoodbolo, sizeof(*goodbolo) ); ngoodbolo = 0; for( i=0; i<nbolo; i++ ) { if( !(qua[i*bstride]&SMF__Q_BADB) ) { goodbolo[ngoodbolo] = i; ngoodbolo++; } } } else { /* Otherwise assume all bolometers are good */ ngoodbolo = nbolo; goodbolo = astCalloc( ngoodbolo, sizeof(*goodbolo) ); for( i=0; i<ngoodbolo; i++ ) { goodbolo[i] = i; } } if( ngoodbolo <= 2 ) { *status = SAI__ERROR; errRep( " ", FUNC_NAME ": fewer than 2 working bolometers!", status ); goto CLEANUP; } /* Fill bad values and values flagged via "mask" (except entirely bad bolometers) with interpolated data values. */ mask &= ~SMF__Q_BADB; smf_fillgaps( wf, data, mask, status ); /* Allocate arrays */ amp = astCalloc( nbolo*ngoodbolo, sizeof(*amp) ); comp = astCalloc( ngoodbolo*tlen, sizeof(*comp) ); cov = gsl_matrix_alloc( ngoodbolo, ngoodbolo ); s = gsl_vector_alloc( ngoodbolo ); work = gsl_vector_alloc( ngoodbolo ); /* These strides will make comp time-ordered */ ccompstride = 1; ctstride = ngoodbolo; /* These strides will also make amp look time-ordered (sort-of: the time axis is now the component number */ abstride = 1; acompstride = nbolo; /* Allocate job data for threads */ job_data = astCalloc( nw, sizeof(*job_data) ); /* Set up the division of labour for threads: independent blocks of time */ if( nw > (int) tlen ) { step = 1; } else { step = tlen/nw; } if( nw > (int) ngoodbolo ) { bstep = 1; } else { bstep = ngoodbolo/nw; } for( ii=0; (*status==SAI__OK)&&(ii<nw); ii++ ) { pdata = job_data + ii; /* Blocks of time slices */ pdata->t1 = ii*step + t_first; pdata->t2 = (ii+1)*step + t_first - 1; /* Blocks of bolometers. */ pdata->b1 = ii*bstep; pdata->b2 = (ii+1)*bstep - 1; /* Ensure that the last thread picks up any left-over tslices */ if( (ii==(nw-1)) ) { pdata->t2 = t_first + tlen - 1; pdata->b2 = ngoodbolo - 1; } /* initialize work data */ pdata->amp = NULL; pdata->abstride = abstride; pdata->acompstride = acompstride; pdata->bstride = bstride; pdata->comp = comp; pdata->cov = NULL; pdata->covwork = NULL; pdata->ccompstride = ccompstride; pdata->ctstride = ctstride; pdata->data = data; pdata->goodbolo = NULL; pdata->ijob = -1; pdata->nbolo = nbolo; pdata->ngoodbolo = ngoodbolo; pdata->t_first = t_first; pdata->t_last = t_last; pdata->tlen = tlen; pdata->operation = 0; pdata->tstride = tstride; /* Each thread will accumulate the projection of its own portion of the time-series. We'll add them to the master amp at the end */ pdata->amp = astCalloc( nbolo*ngoodbolo, sizeof(*(pdata->amp)) ); /* Each thread will accumulate sums of x, y, and x*y for each bolo when calculating the covariance matrix */ pdata->covwork = astCalloc( ngoodbolo*ngoodbolo, sizeof(*(pdata->covwork)) ); /* each thread gets its own copy of the goodbolo lookup table */ pdata->goodbolo = astCalloc( ngoodbolo, sizeof(*(pdata->goodbolo)) ); if( *status == SAI__OK ) { memcpy( pdata->goodbolo, goodbolo, ngoodbolo*sizeof(*(pdata->goodbolo)) ); } } if( *status == SAI__OK ) { /* Remove the mean from each gap-filled bolometer time stream ---------------------*/ msgOutif( MSG__VERB, "", FUNC_NAME ": removing bolometer means...", status ); for( ii=0; ii<nw; ii++ ) { pdata = job_data + ii; pdata->operation = -1; thrAddJob( wf, 0, pdata, smfPCAParallel, 0, NULL, status ); } /* Wait until all of the submitted jobs have completed */ thrWait( wf, status ); /* Measure the covariance matrix using parallel code ---------------------*/ msgOutif( MSG__VERB, "", FUNC_NAME ": measuring bolo-bolo covariance matrix...", status ); /* Set up the jobs to calculate sums for each time block and submit */ for( ii=0; ii<nw; ii++ ) { pdata = job_data + ii; pdata->operation = 0; pdata->ijob = thrAddJob( wf, THR__REPORT_JOB, pdata, smfPCAParallel, 0, NULL, status ); } /* Wait until all of the submitted jobs have completed */ thrWait( wf, status ); /* We now have to add together all of the sums from each thread and normalize */ if( *status == SAI__OK ) { for( i=0; i<ngoodbolo; i++ ) { for( j=i; j<ngoodbolo; j++ ) { double c; double *covwork=NULL; double sum_xy; sum_xy = 0; for( ii=0; ii<nw; ii++ ) { pdata = job_data + ii; covwork = pdata->covwork; sum_xy += covwork[ i + j*ngoodbolo ]; } c = sum_xy / ((double)tlen-1); gsl_matrix_set( cov, i, j, c ); gsl_matrix_set( cov, j, i, c ); } } } } /* Factor cov = u s v^T, noting that the SVD routine calculates v^T in in-place of cov. --------------------------------------------------------*/ msgOutif( MSG__VERB, "", FUNC_NAME ": perfoming singular value decomposition...", status ); smf_svd( wf, ngoodbolo, cov->data, s->data, NULL, 10*VAL__EPSD, 1, status ); if( CHECK ) { double check=0; for( i=0; i<ngoodbolo; i++ ) { for( j=0; j<ngoodbolo; j++ ) { check += gsl_matrix_get( cov, j, i ); } } printf("--- check inverted: %lf\n", check); } /* Calculate normalized eigenvectors with parallel code --------------------*/ msgOutif( MSG__VERB, "", FUNC_NAME ": calculating statistically-independent components...", status ); /* The above calculation tells us what linear combinations of the original bolometer time series will give us the statistically independent new set of basis vectors (components), which we then normalize by their RMS. */ /* Set up the jobs to calculate sums for each time block and submit */ if( *status == SAI__OK ) { for( ii=0; ii<nw; ii++ ) { pdata = job_data + ii; pdata->cov = cov; pdata->operation = 1; pdata->ijob = thrAddJob( wf, THR__REPORT_JOB, pdata, smfPCAParallel, 0, NULL, status ); } } /* Wait until all of the submitted jobs have completed */ thrWait( wf, status ); /* Then normalize. Some of the components may have zero amplitude and so cannot be used (i.e. we are trying to use more components than there is evidence for in the data). So we check for zero sigma. In fact, we check for silly small sigma, not just zero sigma. Any component for which the sigma is less than 1E-10 of the log-mean sigma is excluded. */ { double *sigmas = astMalloc( ngoodbolo*sizeof( *sigmas ) ); double check = 0; double s1 = 0.0; int s2 = 0; int nlow = 0; for( i=0; (*status==SAI__OK)&&(i<ngoodbolo); i++ ) { double sigma; smf_stats1D( comp + i*ccompstride, ctstride, tlen, NULL, 0, 0, NULL, &sigma, NULL, NULL, status ); /* Apparently we need this to get the normalization right */ sigma *= sqrt((double) tlen); if( *status == SAI__OK ) { if( sigma > 0.0 ) { for( k=0; k<tlen; k++ ) { comp[i*ccompstride + k*ctstride] /= sigma; sigmas[ i ] = sigma; s1 += log10( sigma ); s2++; } } else { for( k=0; k<tlen; k++ ) { comp[i*ccompstride + k*ctstride] = VAL__BADD; sigmas[ i ] = VAL__BADD; } nlow++; } } } /* Exclude any components that have a silly small standard deviation (less that 1E-10 of the logmean of all components). Any with zero standard deviation will already have been excluded. */ if( s2 > 0 ) { double logmean = s1/s2; for( i=0; i<ngoodbolo; i++ ) { if( sigmas[ i ] != VAL__BADD && sigmas[ i ] < 1E-10*logmean ) { for( k=0; k<tlen; k++ ) { comp[i*ccompstride + k*ctstride] = VAL__BADD; nlow++; } } } } msgOutiff( MSG__DEBUG, "", FUNC_NAME ": rejecting %d (out of %zu) components" " because they are too weak to normalise", status, nlow, ngoodbolo ); for( i=0; i<ngoodbolo*tlen; i++ ) { if( comp[i] != VAL__BADD ) check += comp[i]; } sigmas = astFree( sigmas ); //printf("--- check component: %lf\n", check); } /* Now project the data along each of these normalized basis vectors to figure out the amplitudes of the components in each bolometer time series. ------------------------------------------------------------*/ msgOutif( MSG__VERB, "", FUNC_NAME ": calculating component amplitudes in each bolo...", status ); /* Set up the jobs */ if( *status == SAI__OK ) { for( ii=0; ii<nw; ii++ ) { pdata = job_data + ii; pdata->operation = 2; pdata->ijob = thrAddJob( wf, THR__REPORT_JOB, pdata, smfPCAParallel, 0, NULL, status ); } } /* Wait until all of the submitted jobs have completed */ thrWait( wf, status ); /* Add all of the amp arrays together from the threads */ if( *status == SAI__OK ) { size_t index; for( ii=0; ii<nw; ii++ ) { pdata = job_data + ii; for( i=0; i<ngoodbolo; i++ ) { /* Loop over good bolo */ for( j=0; j<ngoodbolo; j++ ) { /* Loop over component */ index = goodbolo[i]*abstride + j*acompstride; amp[index] += pdata->amp[index]; } } } } if( CHECK ){ double check=0; for( i=0; i<nbolo*ngoodbolo; i++ ) { check += amp[i]; } printf("--- check combined amp: %lf\n", check); } if( CHECK ){ double check=0; for( i=0; i<ngoodbolo*tlen; i++ ) { if( comp[i] != VAL__BADD ) check += comp[i]; } printf("--- check component A: %lf\n", check); } /* Check to see if the amplitudes are mostly negative or positive. If mostly negative, flip the sign of both the component and amplitudes */ if( *status == SAI__OK ) { double total; for( j=0; j<ngoodbolo; j++ ) { /* loop over component */ total = 0; for( i=0; i<ngoodbolo; i++ ) { /* loop over bolometer */ total += amp[goodbolo[i]*abstride + j*acompstride]; } /* Are most amplitudes negative for this component? */ if( total < 0 ) { /* Flip sign of the amplitude */ for( i=0; i<ngoodbolo; i++ ) { /* loop over bolometer */ amp[goodbolo[i]*abstride + j*acompstride] = -amp[goodbolo[i]*abstride + j*acompstride]; } /* Flip sign of the component */ for( k=0; k<tlen; k++ ) { if( comp[j*ccompstride + k*ctstride] != VAL__BADD ) { comp[j*ccompstride + k*ctstride] *= -1; } } } } } /* Finally, copy the master amp array back into the workspace for each thread */ if( *status == SAI__OK ) { for( ii=0; ii<nw; ii++ ) { pdata = job_data + ii; memcpy( pdata->amp, amp, sizeof(*(pdata->amp))*nbolo*ngoodbolo ); } } if( CHECK ){ double check=0; for( i=0; i<ngoodbolo*tlen; i++ ) { if( comp[i] != VAL__BADD ) check += comp[i]; } printf("--- check component B: %lf\n", check); } /* Flag outlier bolometers if requested ------------------------------------*/ if( (*status==SAI__OK) && flagbad ) { smfArray *data_array=NULL; smfArray *gain_array=NULL; smfGroup *gain_group=NULL; AstKeyMap *kmap=NULL; /* Local keymap */ AstObject *obj=NULL; /* Used to avoid compiler warnings */ double *template=NULL;
smf_qual_t * smf_qual_map( ThrWorkForce *wf, int indf, const char mode[], smf_qfam_t *family, size_t *nmap, int * status ) { size_t i; /* Loop counter */ int itemp = 0; /* temporary int */ smf_qfam_t lfamily = SMF__QFAM_NULL; /* Local quality family */ size_t nout; /* Number of elements mapped */ size_t numqn = 0; /* number of quality names */ IRQLocs *qlocs = NULL;/* IRQ Quality */ unsigned char *qmap; /* pointer to mapped unsigned bytes */ void *qpntr[1]; /* Somewhere to put the mapped pointer */ smf_qual_t *retval = NULL; /* Returned pointer */ int there; /* Does the NDF Have a Quality component? */ char xname[DAT__SZNAM+1]; /* Name of extension holding quality names */ SmfQualMapData *job_data = NULL; SmfQualMapData *pdata; int nw; size_t step; int iw; if (*status != SAI__OK) return retval; /* Ensure jobs submitted to the workforce within this function are handled separately to any jobs submitted earlier (or later) by any other function. */ thrBeginJobContext( wf, status ); /* how many elements do we need */ ndfSize( indf, &itemp, status ); nout = itemp; if (nmap) *nmap = nout; /* malloc the QUALITY buffer. Initialise to zero to simplify logic below. It is difficult to determine in advance which case can use initialisation. */ retval = astCalloc( nout, sizeof(*retval) ); /* How many threads do we get to play with */ nw = wf ? wf->nworker : 1; /* Find how many elements to process in each worker thread. */ step = nout/nw; if( step == 0 ) step = 1; /* Allocate job data for threads, and store common values. Ensure that the last thread picks up any left-over elements. */ job_data = astCalloc( nw, sizeof(*job_data) ); if( *status == SAI__OK ) { for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->i1 = iw*step; if( iw < nw - 1 ) { pdata->i2 = pdata->i1 + step - 1; } else { pdata->i2 = nout - 1 ; } pdata->retval = retval; } } /* If the NDF has no QUality component, return the buffer filled with zeros. */ ndfState( indf, "QUALITY", &there, status ); if( there ) { /* READ and UPDATE mode require that the QUALITY is processed and copied before being returned. WRITE mode means that the buffer contains no information to copy yet. WRITE/ZERO and WRITE/BAD also require that we do not do any quality handling */ if ( strncmp(mode, "WRITE",5) == 0 ) { /* WRITE and WRITE/ZERO are actually treated the same way because we always initialise */ if ( strcmp( mode, "WRITE/BAD") == 0 ) { for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->operation = 1; thrAddJob( wf, 0, pdata, smf1_qual_map, 0, NULL, status ); } thrWait( wf, status ); } /* unmap the NDF buffer and return the pointer */ if (family) *family = lfamily; return retval; } /* Map the quality component (we always need to do this) */ ndfMap( indf, "QUALITY", "_UBYTE", mode, &qpntr[0], &itemp, status ); qmap = qpntr[0]; /* Need to find out what quality names are in play so we can work out which family to translate them to */ irqFind( indf, &qlocs, xname, status ); numqn = irqNumqn( qlocs, status ); if ( *status == IRQ__NOQNI || numqn == 0) { /* do not have any names defined so we have no choice in copying the values directly out the file */ if (*status != SAI__OK) errAnnul( status ); /* simple copy with type conversion */ for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->qmap = qmap; pdata->operation = 2; thrAddJob( wf, 0, pdata, smf1_qual_map, 0, NULL, status ); } thrWait( wf, status ); } else { IRQcntxt contxt = 0; int ndfqtosmf[NDFBITS]; /* NDF bit (arr index) and SMURF alternative */ int ndfqtoval[NDFBITS]; /* NDF bit (arr index) and corresponding Qual value */ int ndfqval[NDFBITS]; /* Bit values for NDF quality */ int identity = 1; /* Is this a simple identity map? */ /* prefill the mapping with bit to bit mapping */ for (i=0; i<NDFBITS; i++) { ndfqtosmf[i] = i; ndfqtoval[i] = BIT_TO_VAL(i); ndfqval[i] = ndfqtoval[i]; } /* Now translate each name to a bit */ for (i = 0; i < numqn && *status == SAI__OK; i++) { char qname[IRQ__SZQNM+1]; char commnt[IRQ__SZCOM+1]; int fixed; int value; int bit; int done; smf_qual_t qval; smf_qfam_t tmpfam = 0; irqNxtqn( qlocs, &contxt, qname, &fixed, &value, &bit, commnt,sizeof(commnt), &done, status ); bit--; /* IRQ starts at 1 */ /* Now convert the quality name to a quality value and convert that to a bit. These should all be less than 9 bits because they are in the NDF file. */ qval = smf_qual_str_to_val( qname, &tmpfam, status ); if (*status == SMF__BADQNM ) { /* annul status and just copy this bit from the file to SMURF without change. This might result in a clash of bits but we either do that or drop out the loop and assume everything is broken */ if (*status != SAI__OK) errAnnul(status); ndfqtosmf[bit] = bit; ndfqtoval[bit] = BIT_TO_VAL(bit); } else if( *status == SAI__OK ){ if (lfamily == SMF__QFAM_NULL) { lfamily = tmpfam; } else if (lfamily != tmpfam) { msgOutif(MSG__QUIET, "", "WARNING: Quality names in file come from different families", status ); } ndfqtosmf[bit] = smf_qual_to_bit( qval, status ); ndfqtoval[bit] = qval; /* not a 1 to 1 bit translation */ if (bit != ndfqtosmf[bit]) identity = 0; } } /* Now copy from the file and translate the bits. If this is an identity mapping or we do not know the family then we go quick. */ if (*status == SAI__OK) { if ( (identity && lfamily != SMF__QFAM_TCOMP) || lfamily == SMF__QFAM_NULL) { for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->qmap = qmap; pdata->ndfqval = ndfqval; pdata->lfamily = lfamily; pdata->ndfqtoval = ndfqtoval; pdata->ndfqval = ndfqval; pdata->operation = 2; thrAddJob( wf, 0, pdata, smf1_qual_map, 0, NULL, status ); } thrWait( wf, status ); } else { for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->qmap = qmap; pdata->ndfqval = ndfqval; pdata->lfamily = lfamily; pdata->ndfqtoval = ndfqtoval; pdata->ndfqval = ndfqval; pdata->operation = 3; thrAddJob( wf, 0, pdata, smf1_qual_map, 0, NULL, status ); } thrWait( wf, status ); /* we have uncompressed */ if (lfamily == SMF__QFAM_TCOMP) lfamily = SMF__QFAM_TSERIES; } } } /* Free quality */ irqRlse( &qlocs, status ); /* no longer need the mapped data */ ndfUnmap( indf, "QUALITY", status ); } /* End the Thr job context */ thrEndJobContext( wf, status ); /* Free other resources. */ job_data = astFree( job_data ); if (family) *family = lfamily; return retval; }
void smf_uncalc_iqu( ThrWorkForce *wf, smfData *data, double *idata, double *qdata, double *udata, double *angdata, int pasign, double paoff, double angrot, double amp2, double phase2, double amp4, double phase4, double amp16, double phase16, const double *qinst, const double *uinst, int harmonic, int *status ){ /* Local Variables: */ const JCMTState *state; /* JCMTState info for current time slice */ dim_t nbolo; /* No. of bolometers */ dim_t ntslice; /* Number of time-slices in data */ int bstep; /* Bolometer step between threads */ int itime; /* Time slice index */ int iworker; /* Index of a worker thread */ int ntime; /* Time slices to check */ int nworker; /* No. of worker threads */ int old; /* Data has old-style POL_ANG values? */ size_t bstride; /* Stride between adjacent bolometer values */ size_t tstride; /* Stride between adjacent time slice values */ smfHead *hdr; /* Pointer to data header this time slice */ smfUncalcIQUJobData *job_data = NULL; /* Pointer to all job data */ smfUncalcIQUJobData *pdata = NULL;/* Pointer to next job data */ char headval[ 81 ]; /* FITS header value */ int ipolcrd; /* Reference direction for waveplate angles */ /* Check the inherited status. */ if( *status != SAI__OK ) return; /* Convenience pointer. */ hdr = data->hdr; /* Check the half-waveplate and analyser were in the beam. */ headval[ 0 ] = 0; smf_getfitss( hdr, "POLWAVIN", headval, sizeof(headval), status ); if( strcmp( headval, "Y" ) && *status == SAI__OK ) { smf_smfFile_msg( data->file, "N", 0, "" ); *status = SAI__ERROR; errRep( " ", "Half-waveplate was not in the beam for " "input NDF ^N.", status ); } headval[ 0 ] = 0; smf_getfitss( hdr, "POLANLIN", headval, sizeof(headval), status ); if( strcmp( headval, "Y" ) && *status == SAI__OK ) { smf_smfFile_msg( data->file, "N", 0, "" ); *status = SAI__ERROR; errRep( " ", "Analyser was not in the beam for input " "NDF ^N.", status ); } /* Get the reference direction for JCMTSTATE:POL_ANG values. */ smf_getfitss( hdr, "POL_CRD", headval, sizeof(headval), status ); ipolcrd = 0; if( !strcmp( headval, "AZEL" ) ) { ipolcrd = 1; } else if( !strcmp( headval, "TRACKING" ) ) { ipolcrd = 2; } else if( strcmp( headval, "FPLANE" ) && *status == SAI__OK ) { *status = SAI__ERROR; smf_smfFile_msg( data->file, "N", 0, "" ); msgSetc( "V", headval ); errRep( " ", "Input NDF ^N contains unknown value " "'^V' for FITS header 'POL_CRD'.", status ); } /* Can only handle POL_CRD = FPLANE at the moment. */ if( ipolcrd != 0 && *status == SAI__OK ) { *status = SAI__ERROR; errRepf( "", "smf_uncalc_iqu: currently only POL_CRD = FPLANE is " "supported.", status ); } /* Obtain number of time slices - will also check for 3d-ness. Also get the dimensions of the bolometer array and the strides between adjacent bolometer values. */ smf_get_dims( data, NULL, NULL, &nbolo, &ntslice, NULL, &bstride, &tstride, status ); /* Create structures used to pass information to the worker threads. */ nworker = wf ? wf->nworker : 1; job_data = astMalloc( nworker*sizeof( *job_data ) ); /* Check the above pointers can be used safely. */ if( *status == SAI__OK ) { /* Go through the first thousand POL_ANG values to see if they are in units of radians (new data) or arbitrary encoder units (old data). They are assumed to be in radians if no POL_ANG value is larger than 20. */ old = 0; state = hdr->allState; ntime = ( ntslice > 1000 ) ? 1000 : ntslice; for( itime = 0; itime < ntime; itime++,state++ ) { if( state->pol_ang > 20 ) { old = 1; msgOutif( MSG__VERB, ""," POL2 data contains POL_ANG values " "in encoder units - converting to radians.", status ); break; } } /* Determine which bolometers are to be processed by which threads. */ bstep = nbolo/nworker; if( bstep < 1 ) bstep = 1; for( iworker = 0; iworker < nworker; iworker++ ) { pdata = job_data + iworker; pdata->b1 = iworker*bstep; pdata->b2 = pdata->b1 + bstep - 1; } /* Ensure that the last thread picks up any left-over bolometers */ pdata->b2 = nbolo - 1; /* Store all the other info needed by the worker threads, and submit the jobs to calculate the analysed intensity values in each bolo, and then wait for them to complete. */ for( iworker = 0; iworker < nworker; iworker++ ) { pdata = job_data + iworker; pdata->bstride = bstride; pdata->nbolo = nbolo; pdata->tstride = tstride; pdata->allstates = hdr->allState; pdata->ipi = idata; pdata->ipq = qdata; pdata->ipu = udata; pdata->ipang = angdata; pdata->ipolcrd = ipolcrd; pdata->old = old; pdata->ntslice = ntslice; pdata->pasign = pasign ? +1: -1; pdata->paoff = paoff; pdata->angrot = angrot; pdata->angfac = harmonic/4.0; pdata->amp2 = amp2; pdata->amp4 = amp4; pdata->amp16 = amp16; pdata->phase2 = phase2; pdata->phase4 = phase4; pdata->phase16 = phase16; pdata->qinst = qinst; pdata->uinst = uinst; /* Pass the job to the workforce for execution. */ thrAddJob( wf, THR__REPORT_JOB, pdata, smf1_uncalc_iqu_job, 0, NULL, status ); } /* Wait for the workforce to complete all jobs. */ thrWait( wf, status ); } /* Free resources. */ job_data = astFree( job_data ); }
void smf_snrmask( ThrWorkForce *wf, int abssnr, unsigned char *oldmask, const double *map, const double *mapvar, const dim_t *dims, double snr_hi, double snr_lo, unsigned char *mask, int *status ){ /* Local Variables: */ const double *pm = NULL; const double *pv = NULL; dim_t i; dim_t j; double snr; int *cindex = NULL; int *ps = NULL; int *psn = NULL; int *table = NULL; int iass; int iclean; int iclump; int ineb; int itemp; int itop1; int itop2; int iworker; int neb_offset[ 4 ]; int nworker; int ok; int rowstep; int top; smfSnrMaskJobData *job_data = NULL; smfSnrMaskJobData *pdata = NULL; unsigned char *maskold = NULL; /* Check inherited status */ if( *status != SAI__OK ) return; /* Save a copy of the old mask, if supplied. Doing it now, means that the old and new mask pointers can be the same. */ if( oldmask ) maskold = astStore( NULL, oldmask, sizeof(*oldmask)*dims[0]*dims[1] ); /* Allocate an array to hold a clump index for every map pixel. Initialise it to hold zeros. */ cindex = astCalloc( dims[ 0 ]*dims[ 1 ], sizeof( *cindex ) ); /* Initialise the index to assign to the next clump of pixels found above the lower SNR limit. Note, no clump is given an index of zero. */ top = 1; /* Initialise the pointer to the table holding associated clump indices. The first element is unused, so set it to a safe value of zero (i.e. "no clump"). */ table = astCalloc( top, sizeof( *table ) ); /* Set up the vector offsets to the three neighbouring pixels in the lower row, and the left hand neighbour in the current row. */ neb_offset[ 0 ] = -1; /* Left neighbour in current row */ neb_offset[ 1 ] = -dims[ 0 ] - 1; /* Left neighbour in lower row */ neb_offset[ 2 ] = -dims[ 0 ]; /* Central neighbour in lower row */ neb_offset[ 3 ] = -dims[ 0 ] + 1; /* Right neighbour in lower row */ /* Loop round the map, looking for pixels that are above the lower SNR limit. Within this loop we store a positive clump index for each pixel that is above the lower SNR limit. Each clump of contiguous pixel above the limit has a separate clump index. If two clumps touch each other, we associate their indices together using a table to indicate that they are part of the same physical clump. */ pm = map; pv = mapvar; ps = cindex; for( j = 0; j < dims[ 1 ] && *status == SAI__OK; j++ ) { for( i = 0; i < dims[ 0 ]; i++, pm++, pv++, ps++ ) { /* Get the SNR value. */ if( mapvar ) { if( *pm != VAL__BADD && *pv != VAL__BADD && *pv > 0.0 ){ snr = *pm / sqrt( *pv ); } else { snr = VAL__BADD; } } else { snr = *pm; } /* If source can be negative as well as positive, use the absolute SNR in the following check. */ if( abssnr && snr != VAL__BADD ) snr = fabs( snr ); /* Check the SNR is good and above the lower limit. */ if( snr != VAL__BADD && snr > snr_lo ){ /* The three neighbouring pixels on row (j-1), and the left hand neighbouring pixel on row j, have already been checked on earlier passes round this loop. Check each of these four pixels in turn to see if they were flagged as being above the lower SNR limit. */ itop1 = 0; for( ineb = 0; ineb < 4; ineb++ ) { /* Get a pointer to the neighbouring clump index value, checking it is not off the edge of the array. */ if( ineb == 0 ) { ok = ( i > 0 ); } else if( ineb == 1 ) { ok = ( i > 0 && j > 0 ); } else if( ineb == 2 ) { ok = ( j > 0 ); } else { ok = ( i < dims[ 0 ] - 1 && j > 0 ); } if( ok ) { psn = ps + neb_offset[ ineb ]; /* If this neighbour is flagged as above the lower SNR limit (i.e. has a positive clump index), and the current pixel has not yet been assigned to an existing clump, assign the neighbour's clump index to the current pixel. */ if( *psn > 0 ) { if( *ps == 0 ) { *ps = *psn; /* Find the clump index at the top of the tree containing the neighbouring pixel. */ itop1 = *psn; while( table[ itop1 ] ) itop1 = table[ itop1 ]; /* If this neighbour is flagged as above the lower SNR limit, but the current pixel has already been assigned to an existing clump, the current pixel is adjacent to both clumps and so joins them into one. So record that this neighbours clump index should be associated with the clump index of the current pixel. */ } else { /* We need to check first that the two clump indices are not already part of the same tree of associated clumps. Without this we could produce loops in the tree. Find the clump indices at the top of the tree containing the neighbouring pixel. */ itop2 = *psn; while( table[ itop2 ] ) itop2 = table[ itop2 ]; /* If the two clumps are not in the same tree, indicate that the pixel index at the top of the tree for the neighbouring pixels clump index is associated with the central pixel's clump index. */ if( itop1 != itop2 ) table[ itop2 ] = *ps; } } } } /* If the current pixel has no neighbours that are above the lower SNR limit, we start a new clump for the current pixel. */ if( *ps == 0 ) { /* Assign the next clump index to the current pixel, and then increment the next clump index. Report an error if we have reached the max allowable clump index value. */ if( top == INT_MAX ) { *status = SAI__ERROR; errRep( "", "smf_snrmask: Too many low-SNR clumps found.", status ); break; } *ps = top++; /* Extend the table that holds the associations between clumps. This table has one element for each clump index (plus an unused element at the start for the unused clump index "0"). The value stored in this table for a given clump index is the index of another clump with which the first clump should be associated. If two clumps are associated it indicates that they are part of the same physical clump. Associations form a tree structure. A value of zero in this table indicates that the clump is either unassociated with any other clump, or is at the head of a tree of associated clumps. */ table = astGrow( table, top, sizeof( *table ) ); if( *status != SAI__OK ) break; table[ *ps ] = 0; } } } } /* We now loop round the map again, this time looking for pixels that are above the higher SNR limit. */ pm = map; pv = mapvar; ps = cindex; for( j = 0; j < dims[ 1 ]; j++ ) { for( i = 0; i < dims[ 0 ]; i++, pm++, pv++, ps++ ) { /* Get the SNR value. */ if( mapvar ) { if( *pm != VAL__BADD && *pv != VAL__BADD && *pv > 0.0 ){ snr = *pm / sqrt( *pv ); } else { snr = VAL__BADD; } } else { snr = *pm; } /* If source can be negative as well as positive, use the absolute SNR. */ if( abssnr && snr != VAL__BADD ) snr = fabs( snr ); /* Check the SNR is good and above the upper limit. */ if( snr != VAL__BADD && snr > snr_hi ){ /* Since this pixel is above the higher SNR limit, it must also be above the lower SNR Limit, and so will have a non-zero clump index. We flag that this clump contains "source" pixels by storing a value of -1 for it in the clump association table. First record the original value for later use. */ iass = table[ *ps ]; table[ *ps ] = -1; /* If this clump index is associated with another clump (i.e. had a non-zero value in the clump association table), the two clumps adjoins each other. So indicate that the second clump also contains "source" pixels by changing its table value to -1. Enter a loop to do this all the way up to the top of the association tree. Note, this is not necessarily all adjoining clumps, since we have only gone "up" the tree - there may be other adjoining clumps lower down the tree. */ while( iass > 0 ) { itemp = table[ iass ]; table[ iass ] = -1; iass = itemp; } } } } /* Now check all cumps to see if they adjoin a "source" clump. Note, no clumps are given the index zero, so we skip the first element of the table. */ for( iclump = 1; iclump < top; iclump++ ) { iass = table[ iclump ]; /* Work up the tree of neighbouring clumps until we find a clump that has an index of 0 or -1. If 0, it means that we have reached the top of the tree without finding a "source" clump. If -1 it means we have reached a source clump. */ while( iass > 0 ) { iass = table[ iass ]; } /* If we have found a source clump, then all clumps above it in the tree should already be set to -1. We now walk up the tree from the current clump until we reach the source clump, marking all intermediate clumps as source clumps by setting them to -1 in the table. */ if( iass < 0 ) { iass = iclump; while( iass > 0 ) { itemp = table[ iass ]; table[ iass ] = -1; iass = itemp; } /* If no source clump was found, mark all intermediate clumps as non-source by setting theem to zero in the table. This may give us a little extra speed (maybe) since subsequent walks will terminate sooner. */ } else { iass = iclump; while( iass > 0 ) { itemp = table[ iass ]; table[ iass ] = 0; iass = itemp; } } } /* One last pass, to store the final mask values. We can multi-thread this bit. Create structures used to pass information to the worker threads. If we have more threads than rows, we will process one row in each thread and so we can reduce the number of threads used to equal the number of rows. */ nworker = wf ? wf->nworker : 1; if( nworker > (int) dims[ 1 ] ) nworker = dims[ 1 ]; job_data = astMalloc( nworker*sizeof( *job_data ) ); /* Check we can de-reference the job data pointer safely. */ if( *status == SAI__OK ) { /* Decide how many rows to process in each thread. */ rowstep = dims[ 1 ]/nworker; if( rowstep == 0 ) rowstep = 1; /* Set up the information needed by each thread, */ for( iworker = 0; iworker < nworker; iworker++ ) { pdata = job_data + iworker; pdata->operation = 1; pdata->cindex = cindex; pdata->jlo = iworker*rowstep; if( iworker == nworker - 1 ) { pdata->jhi = dims[ 1 ] - 1; } else { pdata->jhi = pdata->jlo + rowstep - 1; } pdata->rowlen = dims[ 0 ]; pdata->mask = mask; pdata->table = table; /* Pass the job to the workforce for execution. */ thrAddJob( wf, 0, pdata, smf1_snrmask_job, 0, NULL, status ); } /* Wait for the workforce to complete all jobs. */ thrWait( wf, status ); /* Now clean up the very crinkly edges of the mask. Also, the mask may contain small holes which need to be cleaned. Clean it NCLEAN times. */ for( iclean = 0; iclean < NCLEAN; iclean++ ) { /* Clean the mask, putting the cleaned mask into "cindex" array. We exclude pixels in the first and last rows since they do not have a complete set of neighbours (each worker thread also ignores the first and last pixel in each row for the same reason). Decide how many rows to process in each thread. */ rowstep = ( dims[ 1 ] - 2 )/nworker; if( rowstep == 0 ) rowstep = 1; /* Modify the information needed by each thread, */ for( iworker = 0; iworker < nworker; iworker++ ) { pdata = job_data + iworker; pdata->operation = 2; pdata->jlo = iworker*rowstep + 1; if( iworker == nworker - 1 ) { pdata->jhi = dims[ 1 ] - 2; } else { pdata->jhi = pdata->jlo + rowstep - 1; } /* Pass the job to the workforce for execution. */ thrAddJob( wf, 0, pdata, smf1_snrmask_job, 0, NULL, status ); } /* Wait for the workforce to complete all jobs. */ thrWait( wf, status ); /* Transfer the new mask from the "cindex" array back to the "mask" array. Add in any source pixels from the old mask if required. */ for( iworker = 0; iworker < nworker; iworker++ ) { pdata = job_data + iworker; pdata->maskold = maskold; pdata->operation = 3; thrAddJob( wf, 0, pdata, smf1_snrmask_job, 0, NULL, status ); } thrWait( wf, status ); /* If an old mask was supplied, ensure any source pixels in the old mask are also source pixels in the new mask. */ if( oldmask ) { for( iworker = 0; iworker < nworker; iworker++ ) { pdata = job_data + iworker; pdata->maskold = maskold; pdata->operation = 4; thrAddJob( wf, 0, pdata, smf1_snrmask_job, 0, NULL, status ); } thrWait( wf, status ); } } } /* Free resources. */ job_data = astFree( job_data ); maskold = astFree( maskold ); table = astFree( table ); cindex = astFree( cindex ); }
int smf_correct_extinction(ThrWorkForce *wf, smfData *data, smf_tausrc *thetausrc, smf_extmeth method, AstKeyMap * extpars, double tau, double *allextcorr, double **wvmtaucache, int *status) { /* Local variables */ int allquick = 0; /* Is the extinction for all bolometers the same? */ double amstart = VAL__BADD; /* Airmass at start */ double amend = VAL__BADD; /* Airmass at end */ double elstart = VAL__BADD; /* Elevation at start (radians) */ double elend = VAL__BADD;/* Elevation at end (radians) */ smfHead *hdr = NULL; /* Pointer to full header struct */ double *indata = NULL; /* Pointer to data array */ int isTordered; /* data order of input data */ int lbnd[2]; /* Lower bound */ size_t ndims; /* Number of dimensions in input data */ dim_t nframes = 0; /* Number of frames */ dim_t npts = 0; /* Number of data points */ dim_t nx = 0; /* # pixels in x-direction */ dim_t ny = 0; /* # pixels in y-direction */ smf_tausrc tausrc; /* Local copy of tausrc value */ int ubnd[2]; /* Upper bound */ double *vardata = NULL; /* Pointer to variance array */ double * wvmtau = NULL; /* WVM tau (smoothed or not) for these data */ int nw; /* Number of worker threads */ int iw; /* Thread index */ SmfCorrectExtinctionData *job_data = NULL; /* Array of job descriptions */ SmfCorrectExtinctionData *pdata; /* Pointer to next job description */ size_t framestep; /* Number of frames per thread */ /* Check status */ if (*status != SAI__OK) return allquick; /* If no correction requested, return */ if( method==SMF__EXTMETH_NONE ) { msgOutif(MSG__VERB, "", FUNC_NAME ": Extinction method=none, returning", status ); return allquick; } if ( ! thetausrc ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": Must supply a thetausrc argument. Possible programming error.", status ); return allquick; } /* Use a local value for tausrc as we update it in this routine. In particular, CSOFIT becomes WVMRAW and this would be confusing to the caller */ tausrc = *thetausrc; /* If no opacity monitor specified generate bad status */ if( tausrc==SMF__TAUSRC_NULL ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": No source of opacity information could be determined", status ); return allquick; } if( smf_history_check( data, FUNC_NAME, status) ) { /* If caller not requesting allextcorr fail here */ if( !allextcorr ) { msgSetc("F", FUNC_NAME); msgOutif(MSG__VERB," ", "^F has already been run on these data, returning to caller", status); return allquick; } } /* Acquire the data order */ isTordered = data->isTordered; /* make sure we have a header */ hdr = data->hdr; if( hdr == NULL ) { *status = SAI__ERROR; errRep( FUNC_NAME, "Input data has no header", status); return allquick; } /* Do we have 2-D image data? */ ndims = data->ndims; if (ndims == 2) { nframes = 1; nx = (data->dims)[0]; ny = (data->dims)[1]; npts = nx*ny; } else { /* this routine will also check for dimensionality */ smf_get_dims( data, &nx, &ny, &npts, &nframes, NULL, NULL, NULL, status ); } /* Tell user we're correcting for extinction */ msgOutif(MSG__VERB," ", "Correcting for extinction.", status); /* Should check data type for double if not allextcorr case */ if( !allextcorr ) { if (!smf_dtype_check_fatal( data, NULL, SMF__DOUBLE, status)) return allquick; } /* Check that we're not trying to use the WVM for 2-D data */ if ( ndims == 2 && tausrc == SMF__TAUSRC_WVMRAW ) { if ( *status == SAI__OK ) { *status = SAI__ERROR; errRep( FUNC_NAME, "Method WVMRaw can not be used on 2-D image data", status ); return allquick; } } else if (ndims == 2 && tausrc == SMF__TAUSRC_CSOFIT ) { /* This is CSOTAU mode with the value calculated from the fits. We have to either calculate the value here based on the FITS headers or we have to ensure that when this mode triggers we've been given the fallback tau derived in this manner. Revisit this as the code develops (we do not want to be reading fits multiple times). */ if (*status == SAI__OK) { *status = SAI__ERROR; errRep( FUNC_NAME, "Method CSOFIT not yet supported on 2-D image data", status ); return allquick; } } else if (ndims == 2 && tausrc == SMF__TAUSRC_WVMFIT ) { if (*status == SAI__OK) { *status = SAI__ERROR; errRep( FUNC_NAME, "Method WVMFIT not yet supported on 2-D image data", status ); return allquick; } } else if (ndims < 2 || ndims > 3) { if (*status == SAI__OK) { *status = SAI__ERROR; errRepf( FUNC_NAME, "Can not extinction correct data with %zd dimension(s)", status, ndims ); return allquick; } } /* if we are WVMRAW, WVMFIT, CSOFIT or AUTO and we have a cache we should always use it since we assume it was filled in properly the previous time. */ if (wvmtaucache && *wvmtaucache && (tausrc == SMF__TAUSRC_WVMRAW || tausrc == SMF__TAUSRC_WVMFIT || tausrc == SMF__TAUSRC_AUTO || tausrc == SMF__TAUSRC_CSOFIT)) { wvmtau = *wvmtaucache; smf_smfFile_msg( data->file, "FILE", 1, "<unknown>"); msgOutiff( MSG__VERB, "", "Using cached high resolution data for extinction correction of ^FILE", status); tausrc = SMF__TAUSRC_WVMRAW; /* We are now WVMRAW as we have the data */ /* Assume that we only do not know the provenance if in AUTO mode */ if (tausrc == SMF__TAUSRC_AUTO) *thetausrc = SMF__TAUSRC_CACHED; } if (!wvmtau && tausrc == SMF__TAUSRC_WVMRAW) { size_t ntotaltau = 0; size_t ngoodtau = 0; smf_calc_smoothedwvm( wf, NULL, data, extpars, &wvmtau, &ntotaltau, &ngoodtau, status ); smf_smfFile_msg( data->file, "FILE", 1, "<unknown>"); msgOutiff( MSG__VERB, "", "Using WVM mode for extinction correction of ^FILE" " %.0f %% of WVM data are present", status, (double)(100.0*(double)ngoodtau/(double)ntotaltau) ); } if (*status == SAI__OK && (tausrc == SMF__TAUSRC_CSOFIT || tausrc == SMF__TAUSRC_WVMFIT)) { /* Calculate the fit but we can use the same cache that WVM uses */ dim_t nframes = 0; smf_calc_taufit( data, tausrc, extpars, &wvmtau, &nframes, status ); smf_smfFile_msg( data->file, "FILE", 1, "<unknown>"); msgSetc("FITSRC", (tausrc == SMF__TAUSRC_CSOFIT) ? "CSO" : "WVM"); msgOutiff( MSG__QUIET, "", "Using ^FITSRC fits for extinction correction of ^FILE", status ); /* Rebrand as WVM data from this point on */ tausrc = SMF__TAUSRC_WVMRAW; } /* AUTO mode logic */ /* * Default position is to use WVM data but we have two caveats * * 1. Was this observation done during a period where the WVM has been flagged as unreliable? * 2. If the WVM is nominally okay, do we have sufficient good data during this period? * * If the WVM should not be used we fallback to seeing if we have a fit available for this * night from the CSO data. * * If we do not have a reliable WVM or CSO fit then we fallback to using a fixed CSO number * from the header. * * This final fallback position is unfortunate as it is highly likely that this is not a reliable * number if we have fits for every night of observing (we have no information on whether a missing * fit indicates the CSO was too unstable to use or whether it means we simply haven't got to it * yet). * */ /* Check auto mode */ if (tausrc == SMF__TAUSRC_AUTO && *status == SAI__OK) { smf_smfFile_msg( data->file, "FILE", 1, "<unknown>" ); if (ndims == 2) { /* have to use CSO mode */ tausrc = SMF__TAUSRC_CSOTAU; *thetausrc = tausrc; } else if (ndims == 3) { /* We have already done the cache test so not needed here */ /* Is the WVM nominally stable for this night? */ if (smf_is_wvm_usable( data->hdr, status ) ) { /* Calculate the WVM tau data and see if we have enough good data */ size_t ngoodtau = 0; size_t ntotaltau = 0; double percentgood = 0.0; smf_calc_smoothedwvm( wf, NULL, data, extpars, &wvmtau, &ntotaltau, &ngoodtau, status ); percentgood = 100.0 * ((double)ngoodtau / (double)ntotaltau); if ( percentgood > 80.0) { tausrc = SMF__TAUSRC_WVMRAW; msgOutiff( MSG__VERB, "", "Selecting WVM mode for extinction correction of ^FILE." " %.0f %% of WVM data are present", status, percentgood ); *thetausrc = tausrc; } else { tausrc = SMF__TAUSRC_AUTO; /* keep it AUTO (a no-op but make it clear) */ if (wvmtau) wvmtau = astFree( wvmtau ); } } /* Next check for an available WVM fit. */ if (tausrc == SMF__TAUSRC_AUTO && *status == SAI__OK) { dim_t nframes = 0; smf_calc_taufit( data, SMF__TAUSRC_WVMFIT, extpars, &wvmtau, &nframes, status ); if (*status == SAI__OK) { smf_smfFile_msg( data->file, "FILE", 1, "<unknown>"); msgOutiff( MSG__QUIET, "", "Using WVM fits for extinction correction of ^FILE", status ); /* Rebrand as WVM data from this point on */ tausrc = SMF__TAUSRC_WVMRAW; *thetausrc = SMF__TAUSRC_WVMFIT; } else if (*status == SMF__BADFIT) { /* No fit, carry on. */ errAnnul( status ); } } /* at this point we either have WVM data handled or we still think we are AUTO. Do a CSO FIT check */ if (tausrc == SMF__TAUSRC_AUTO && *status == SAI__OK) { dim_t nframes = 0; smf_calc_taufit( data, SMF__TAUSRC_CSOFIT, extpars, &wvmtau, &nframes, status ); if (*status == SAI__OK) { smf_smfFile_msg( data->file, "FILE", 1, "<unknown>"); msgOutiff( MSG__QUIET, "", "Using CSO fits for extinction correction of ^FILE", status ); /* Rebrand as WVM data from this point on */ tausrc = SMF__TAUSRC_WVMRAW; *thetausrc = SMF__TAUSRC_CSOFIT; } else if (*status == SMF__BADFIT) { /* No fit, carry on. */ errAnnul( status ); } } /* At this point if we are not WVMRAW then we have a serious issue. It means that WVM was unusable and we did not have a good CSO fit. We should not continue at this point as to continue implies that we know what we should do. The user should decide how much they trust the opacity for the night. There has to be a reason why there is no CSO fit for the night. */ if (*status == SAI__OK && tausrc != SMF__TAUSRC_WVMRAW) { *status = SAI__ERROR; errRep("", "Unable to determine opacity data for this observation. Raw WVM, WVM fits and CSO fits failed. Please investigate and if necessary use CSO mode explicitly but proceed with caution.", status ); } } } /* If we have a CSO Tau then convert it to the current filter. This will also convert bad values to a value derived from the header if appropriate. */ if ( tausrc == SMF__TAUSRC_CSOTAU ) { tau = smf_cso2filt_tau( hdr, tau, extpars, status ); /* The tau source is now a real tau */ tausrc = SMF__TAUSRC_TAU; } /* Find the airmass range for this data */ smf_find_airmass_interval( hdr, &amstart, &amend, &elstart, &elend, status ); if (*status == SAI__OK && (amstart == VAL__BADD || amend == VAL__BADD)) { *status = SAI__ERROR; errRep( "", "No good airmass values found in JCMTSTATE structure for these data", status ); } /* if we are not doing WVM correction but are in adaptive mode we can determine whether or not we will have to use full or single mode just by looking at the airmass data. */ if (ndims == 3 && tausrc != SMF__TAUSRC_WVMRAW && method == SMF__EXTMETH_ADAPT) { /* first and last is a good approximation given that most SCUBA-2 files will only be a minute duration. */ double refel; double refam; /* only need to examine the largest airmass */ if (amstart > amend) { refam = amstart; refel = elstart; } else { refam = amend; refel = elend; } /* and choose a correction method */ if (is_large_delta_atau( refam, refel, tau, status) ) { method = SMF__EXTMETH_FULL; msgOutiff(MSG__DEBUG, " ", "Adaptive extinction algorithm selected per-bolometer airmass value " "per time slice (am=%g, tau=%g)", status, refam, tau); } else { msgOutiff(MSG__DEBUG, " ", "Adaptive extinction algorithm selected single airmass value per time slice" " (am=%g, tau=%g)", status, refam, tau); method = SMF__EXTMETH_SINGLE; } } /* Assign pointer to input data array if status is good */ if ( *status == SAI__OK ) { indata = (data->pntr)[0]; vardata = (data->pntr)[1]; } /* Jump to the cleanup section if status is bad by this point since we need to free memory */ if (*status != SAI__OK) goto CLEANUP; /* Array bounds for astTranGrid call */ lbnd[0] = 1; lbnd[1] = 1; ubnd[0] = nx; ubnd[1] = ny; /* Unlock the AST objects in the smfData so that the worker threads can lock them. */ smf_lock_data( data, 0, status ); /* How many threads do we get to play with */ nw = wf ? wf->nworker : 1; /* Find how many frames to process in each worker thread. */ framestep = nframes/nw; if( framestep == 0 ) { framestep = 1; nw = nframes; } /* Allocate job data for threads, and store the range of frames to be processed by each one. Ensure that the last thread picks up any left-over frames. */ job_data = astCalloc( nw, sizeof(*job_data) ); if( *status == SAI__OK ) { for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->f1 = iw*framestep; if( iw < nw - 1 ) { pdata->f2 = pdata->f1 + framestep - 1; } else { pdata->f2 = nframes - 1 ; } pdata->nframes = nframes; pdata->npts = npts; pdata->allextcorr = allextcorr; pdata->indata = indata; pdata->tau = tau; pdata->vardata = vardata; pdata->wvmtau = wvmtau; pdata->amstart = amstart; pdata->amfirst = amstart + ( amend - amstart )*pdata->f1/( nframes - 1 ); pdata->lbnd = lbnd; pdata->ubnd = ubnd; pdata->isTordered = isTordered; pdata->ndims = ndims; pdata->data = data; pdata->hdr = hdr; pdata->method = method; pdata->tausrc = tausrc; /* Submit the job to the workforce. */ thrAddJob( wf, 0, pdata, smf1_correct_extinction, 0, NULL, status ); } /* Wait for all jobs to complete. */ thrWait( wf, status ); /* Record if all time slices used a single air mass. */ allquick = 1; for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; if( ! pdata->allquick ) { allquick = 0; break; } } /* Free the job data. */ job_data = astFree( job_data ); } /* Lock the AST objects in the smfData for use by this thread. */ smf_lock_data( data, 1, status ); /* Add history entry if !allextcorr */ if( (*status == SAI__OK) && !allextcorr ) { smf_history_add( data, FUNC_NAME, status); } CLEANUP: if (wvmtaucache) { if (!*wvmtaucache) { *wvmtaucache = wvmtau; } } else { wvmtau = astFree( wvmtau ); } return allquick; }
void smf_fit_poly( ThrWorkForce *wf, smfData *data, const size_t order, int remove, double *poly, int *status) { /* Local variables */ size_t bstride; /* bolo strides */ int i; /* Loop counter */ smfFitPolyData *job_data=NULL;/* Array of job data for each thread */ dim_t nbolo=0; /* Number of bolometers */ int njobs=0; /* Number of jobs to be processed */ dim_t ntslice = 0; /* Number of time slices */ int nw; /* Number of worker threads */ smfFitPolyData *pdata=NULL; /* Pointer to job data */ const smf_qual_t *qual; /* pointer to the quality array */ size_t step; /* step size for dividing up work */ size_t tstride; /* time strides */ /* Check status */ if (*status != SAI__OK) return; /* How many threads do we get to play with */ nw = wf ? wf->nworker : 1; /* Should check data type for double */ if (!smf_dtype_check_fatal( data, NULL, SMF__DOUBLE, status)) return; if ( smf_history_check( data, FUNC_NAME, status) ) { msgSetc("F", FUNC_NAME); msgOutif(MSG__VERB," ", "^F has already been run on these data, returning to caller", status); return; } /* Get the dimensions */ smf_get_dims( data, NULL, NULL, &nbolo, &ntslice, NULL, &bstride, &tstride, status); /* Return with error if there is no QUALITY component */ qual = smf_select_cqualpntr( data, NULL, status ); if( !qual && (*status == SAI__OK) ) { *status = SAI__ERROR; errRep( FUNC_NAME, "Data doesn't have a QUALITY component.", status ); return; } /* Return with error if order is greater than the number of data points */ if ( order >= ntslice ) { if ( *status == SAI__OK) { msgSeti("O",order); msgSeti("NF",ntslice); *status = SAI__ERROR; errRep( FUNC_NAME, "Requested polynomial order, ^O, greater than or " "equal to the number of points, ^NF. Unable to fit polynomial.", status ); } return; } /* Set up the job data */ if( nw > (int) nbolo ) { step = 1; } else { step = nbolo/nw; if( !step ) { step = 1; } } job_data = astCalloc( nw, sizeof(*job_data) ); for( i=0; (*status==SAI__OK)&&i<nw; i++ ) { pdata = job_data + i; pdata->b1 = i*step; pdata->b2 = (i+1)*step-1; /* if b1 is greater than the number of bolometers, we've run out of jobs */ if( pdata->b1 >= nbolo ) { break; } /* increase the jobs counter */ njobs++; /* Ensure that the last thread picks up any left-over bolometers */ if( (i==(nw-1)) && (pdata->b1<(nbolo-1)) ) { pdata->b2=nbolo-1; } pdata->ijob = -1; /* Flag job as ready to start */ pdata->bstride = bstride; pdata->indata = data->pntr[0]; pdata->isTordered = data->isTordered; pdata->nbolo = nbolo; pdata->ntslice = ntslice; pdata->order = order; pdata->poly = poly; pdata->qual = qual; pdata->remove = remove; pdata->tstride = tstride; } /* Submit jobs to fit polynomial baselines to block of bolos */ thrBeginJobContext( wf, status ); for( i=0; (*status==SAI__OK)&&i<njobs; i++ ) { pdata = job_data + i; pdata->ijob = thrAddJob( wf, THR__REPORT_JOB, pdata, smfFitPolyPar, 0, NULL, status ); } /* Wait until all of the submitted jobs have completed */ thrWait( wf, status ); thrEndJobContext( wf, status ); /* Free local resources. */ job_data = astFree( job_data ); }
void smf_fit_qui( ThrWorkForce *wf, smfData *idata, smfData **odataq, smfData **odatau, smfData **odatai, dim_t box, int ipolcrd, int pasign, double paoff, double angrot, int north, int *status ){ /* Local Variables: */ AstFrameSet *wcs; /* WCS FrameSet for current time slice */ JCMTState *instate=NULL; /* Pointer to input JCMTState */ JCMTState *outstate=NULL;/* Pointer to output JCMTState */ const char *usesys; /* Tracking system */ dim_t *box_starts; /* Array holding time slice at start of each box */ dim_t box_size; /* First time slice in box */ dim_t intslice; /* ntslice of idata */ dim_t istart; /* Input time index at start of fitting box */ dim_t itime; /* Time slice index */ dim_t nbolo; /* No. of bolometers */ dim_t ncol; /* No. of columns of bolometers in the array */ dim_t ntime; /* Time slices to check */ dim_t ondata; /* ndata of odata */ dim_t ontslice; /* ntslice of odata */ double scale; /* how much longer new samples are */ int bstep; /* Bolometer step between threads */ int iworker; /* Index of a worker thread */ int nworker; /* No. of worker threads */ size_t i; /* loop counter */ smfData *indksquid=NULL; /* Pointer to input dksquid data */ smfFitQUIJobData *job_data = NULL; /* Pointer to all job data */ smfFitQUIJobData *pdata = NULL;/* Pointer to next job data */ smfHead *hdr; /* Pointer to data header this time slice */ smf_qual_t *qua; /* Input quality pointer */ /* Check inherited status */ if( *status != SAI__OK ) return; /* Check supplied arguments. */ if( !idata || !odataq || !odatau ) { *status = SAI__ERROR; errRep( "", "smf_fit_qui: NULL inputs supplied", status ); return; } if( idata->ndims != 3 ) { *status = SAI__ERROR; errRep( "", "smf_fit_qui: idata is not 3-dimensional", status ); return; } /* Ensure the supplied smfData is time-ordered. So "bstride" is 1 and "tstride" is nbolo. */ smf_dataOrder( wf, idata, 1, status ); /* Dimensions of input. */ smf_get_dims( idata, NULL, &ncol, &nbolo, &intslice, NULL, NULL, NULL, status ); /* Store a pointer to the quality array for the input smfData. */ qua = smf_select_qualpntr( idata, NULL, status );; /* Go through the first thousand POL_ANG values to see if they are in units of radians (new data) or arbitrary encoder units (old data). They are assumed to be in radians if no POL_ANG value is larger than 20. This function can only handle new data. */ hdr = idata->hdr; instate = hdr->allState; ntime = ( intslice > 1000 ) ? 1000 : intslice; for( itime = 0; itime < ntime; itime++,instate++ ) { if( instate->pol_ang > 20 ) { *status = SAI__ERROR; errRep( " "," POL2 data contains POL_ANG values in encoder " "units - connot fit to such old data.", status ); break; } } /* Find the input time slice at which each fitting box starts, and the length of the output time axis (in time-slices). */ smf1_find_boxes( intslice, hdr->allState, box, &ontslice, &box_starts, status ); /* Time axis scaling factor. */ scale = (double) intslice / (double) ontslice; /* First copy everything from input to output except for the data that needs to be downsampled */ /* We want to copy everything in the smfHead except for allState. So we make a copy of the allState pointer, and then set it to NULL in the header before the copy */ if( idata->hdr ) { instate = idata->hdr->allState; idata->hdr->allState = NULL; } /* Similarly, we want everything in the smfDa except for the dksquid. */ if( idata->da ) { indksquid = idata->da->dksquid; idata->da->dksquid = NULL; } /* Create copies, storing them in the supplied output smfData structures. Omit the header for U and I, as we will be copying the Q header into them. */ *odataq = smf_deepcopy_smfData( wf, idata, 0, SMF__NOCREATE_DATA | SMF__NOCREATE_VARIANCE | SMF__NOCREATE_QUALITY, 0, 0, status ); *odatau = smf_deepcopy_smfData( wf, idata, 0, SMF__NOCREATE_DATA | SMF__NOCREATE_VARIANCE | SMF__NOCREATE_QUALITY | SMF__NOCREATE_HEAD, 0, 0, status ); if( odatai ) { *odatai = smf_deepcopy_smfData( wf, idata, 0, SMF__NOCREATE_DATA | SMF__NOCREATE_VARIANCE | SMF__NOCREATE_QUALITY | SMF__NOCREATE_HEAD, 0, 0, status ); } /* Restore values in idata now that we're done */ if( instate ) idata->hdr->allState = instate; if( indksquid ) idata->da->dksquid = indksquid; /* Store the required length for the output time axis. The time axis is axis two because the data is time-ordered. */ (*odataq)->dims[ 2 ] = ontslice; (*odatau)->dims[ 2 ] = ontslice; if( odatai) (*odatai)->dims[ 2 ] = ontslice; /* Get output dimensions - assumed to be the same for all three outputs. */ ondata = ontslice*idata->dims[0]*idata->dims[1]; /* Allocate the data arrays for the outputs. */ (*odataq)->pntr[0] = astCalloc( ondata, sizeof(double) ); (*odatau)->pntr[0] = astCalloc( ondata, sizeof(double) ); if( odatai ) (*odatai)->pntr[0] = astCalloc( ondata, sizeof(double) ); /* Allocate arrays for the output variances. */ (*odataq)->pntr[1] = astCalloc( ondata, sizeof(double) ); (*odatau)->pntr[1] = astCalloc( ondata, sizeof(double) ); if( odatai ) (*odatai)->pntr[1] = astCalloc( ondata, sizeof(double) ); /* Create structures used to pass information to the worker threads. */ nworker = wf ? wf->nworker : 1; job_data = astMalloc( nworker*sizeof( *job_data ) ); if( *status == SAI__OK ) { /* Determine which bolometers are to be processed by which threads. */ bstep = nbolo/nworker; if( bstep < 1 ) bstep = 1; for( iworker = 0; iworker < nworker; iworker++ ) { pdata = job_data + iworker; pdata->b1 = iworker*bstep; pdata->b2 = pdata->b1 + bstep - 1; } /* Ensure that the last thread picks up any left-over bolometers */ pdata->b2 = nbolo - 1; /* Loop round all output time slices. */ for( itime = 0; itime < ontslice; itime++ ) { /* Get the index of the first input time slice that contributes to the current output time slice. */ istart = box_starts[ itime ]; /* Get the number of input time slices that contribute to the output time slice. */ box_size = box_starts[ itime + 1 ] - istart; /* If we are using north as the reference direction, get the WCS FrameSet for the input time slice that is at the middle of the output time slice, and set its current Frame to the tracking frame. */ if( north ) { smf_tslice_ast( idata, istart + box_size/2, 1, NO_FTS, status ); wcs = idata->hdr->wcs; usesys = sc2ast_convert_system( (idata->hdr->allState)[0].tcs_tr_sys, status ); astSetC( wcs, "System", usesys ); } else { wcs = NULL; } /* Now enter the parellel code in which each thread calculates the values for a range of bolometers at the current output slice. */ for( iworker = 0; iworker < nworker; iworker++ ) { pdata = job_data + iworker; pdata->dat = ((double *) idata->pntr[0] ) + istart*nbolo; pdata->qua = qua + istart*nbolo; pdata->allstates = hdr->allState + istart; pdata->ipi = odatai ? ( (double*) (*odatai)->pntr[0] ) + itime*nbolo : NULL; pdata->ipq = ( (double*) (*odataq)->pntr[0] ) + itime*nbolo; pdata->ipu = ( (double*) (*odatau)->pntr[0] ) + itime*nbolo; pdata->ipv = ( (double*) (*odataq)->pntr[1] ) + itime*nbolo; pdata->nbolo = nbolo; pdata->ncol = ncol; pdata->box_size = box_size; pdata->ipolcrd = ipolcrd; pdata->pasign = pasign ? +1: -1; pdata->paoff = paoff; pdata->angrot = angrot; if( wcs ) { pdata->wcs = astCopy( wcs ); astUnlock( pdata->wcs, 1 ); } else { pdata->wcs = NULL; } /* Pass the job to the workforce for execution. */ thrAddJob( wf, THR__REPORT_JOB, pdata, smf1_fit_qui_job, 0, NULL, status ); } /* Wait for the workforce to complete all jobs. */ thrWait( wf, status ); /* Lock and annul the AST objects used by each thread. */ if( wcs ) { for( iworker = 0; iworker < nworker; iworker++ ) { pdata = job_data + iworker; astLock( pdata->wcs, 0 ); pdata->wcs = astAnnul( pdata->wcs ); } } } /* Down-sample the smfHead -------------------------------------------------*/ smfHead *hdr = (*odataq)->hdr; hdr->curframe = (dim_t) (((double) hdr->curframe + 0.5) / scale); hdr->nframes = ontslice; hdr->steptime *= scale; strcpy( hdr->dlabel, "Q" ); strncpy( hdr->title, "POL-2 Stokes parameter Q", SMF__CHARLABEL ); /* Down-sample all the JCMTState values using nearest neighbours */ instate = idata->hdr->allState; if( instate ) { hdr->allState = astCalloc( ontslice, sizeof(*instate) ); outstate = hdr->allState; if( *status == SAI__OK ) { size_t frame; /* index of nearest neighbour JCMTState */ for( i=0; i<ontslice; i++ ) { frame = (size_t) round(((double) i + 0.5)*scale); memcpy( outstate + i, instate + frame, sizeof(*instate) ); } /* Then go back and properly down-sample the more important fast-changing fields like pointing. Note that since there are approximate values there already we need to explicitly re-initialize to 0. */ RESAMPSTATE(instate, outstate, rts_end, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_az_jig_x, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_az_jig_y, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_az_chop_x, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_az_chop_y, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_tr_jig_x, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_tr_jig_y, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_tr_chop_x, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_tr_chop_y, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_tai, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_airmass, intslice, ontslice, 0); /* Second coordinates (Dec, El etc) can not wrap 0 to 360 so we do not need to test for those cases */ RESAMPSTATE(instate, outstate, tcs_az_ang, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_az_ac1, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_az_ac2, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_az_dc1, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_az_dc2, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_az_bc1, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_az_bc2, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_tr_ang, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_tr_ac1, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_tr_ac2, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_tr_dc1, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_tr_dc2, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_tr_bc1, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_tr_bc2, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_en_dc1, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_en_dc2, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_dm_abs, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_dm_rel, intslice, ontslice, 0); /* Wait for all the above smf_downsamp1 jobs to finish. */ thrWait( wf, status ); } } /* Add a keyword to the Q header indicating the polarimetric reference direction. */ smf_fits_updateL( (*odataq)->hdr, "POLNORTH", north, north ? "Pol ref dir is tracking north" : "Pol ref dir is focal plane Y", status ); /* Copy the Q header to the other outputs. */ hdr = smf_deepcopy_smfHead( (*odataq)->hdr, status ); (*odatau)->hdr = hdr; if( *status == SAI__OK ) { strcpy( hdr->dlabel, "U" ); strncpy( hdr->title, "POL-2 Stokes parameter U", SMF__CHARLABEL ); } if( odatai ) { hdr = smf_deepcopy_smfHead( (*odataq)->hdr, status ); (*odatai)->hdr = hdr; if( *status == SAI__OK ) { strcpy( hdr->dlabel, "I" ); strncpy( hdr->title, "POL-2 Stokes parameter I", SMF__CHARLABEL ); } } } /* Copy the variances from the Q smfData into the U and (and I) smfData. */ if( *odataq && *status == SAI__OK ) { if( *odatau ) { memcpy( (*odatau)->pntr[1], (*odataq)->pntr[1], ondata*sizeof(double)); } if( odatai && *odatai ) { memcpy( (*odatai)->pntr[1], (*odataq)->pntr[1], ondata*sizeof(double)); } } /* Ensure all smfDatas are time-ordered. */ smf_dataOrder( wf, idata, 1, status ); if( odatai && *odatai ) smf_dataOrder( wf, *odatai, 1, status ); if( *odataq ) smf_dataOrder( wf, *odataq, 1, status ); if( *odatau ) smf_dataOrder( wf, *odatau, 1, status ); /* Free resources. */ job_data = astFree( job_data ); box_starts = astFree( box_starts ); }
int smf_correct_extinction(ThrWorkForce *wf, smfData *data, smf_tausrc tausrc, smf_extmeth method, AstKeyMap * extpars, double tau, double *allextcorr, double **wvmtaucache, int *status) { /* Local variables */ int allquick = 0; /* Is the extinction for all bolometers the same? */ double amstart = VAL__BADD; /* Airmass at start */ double amend = VAL__BADD; /* Airmass at end */ double elstart = VAL__BADD; /* Elevation at start (radians) */ double elend = VAL__BADD;/* Elevation at end (radians) */ smfHead *hdr = NULL; /* Pointer to full header struct */ double *indata = NULL; /* Pointer to data array */ int isTordered; /* data order of input data */ int lbnd[2]; /* Lower bound */ size_t ndims; /* Number of dimensions in input data */ dim_t nframes = 0; /* Number of frames */ dim_t npts = 0; /* Number of data points */ dim_t nx = 0; /* # pixels in x-direction */ dim_t ny = 0; /* # pixels in y-direction */ int ubnd[2]; /* Upper bound */ double *vardata = NULL; /* Pointer to variance array */ double * wvmtau = NULL; /* WVM tau (smoothed or not) for these data */ int nw; /* Number of worker threads */ int iw; /* Thread index */ SmfCorrectExtinctionData *job_data = NULL; /* Array of job descriptions */ SmfCorrectExtinctionData *pdata; /* Pointer to next job description */ size_t framestep; /* Number of frames per thread */ /* Check status */ if (*status != SAI__OK) return allquick; /* If no correction requested, return */ if( method==SMF__EXTMETH_NONE ) { msgOutif(MSG__VERB, "", FUNC_NAME ": Extinction method=none, returning", status ); return allquick; } /* If no opacity monitor specified generate bad status */ if( tausrc==SMF__TAUSRC_NULL ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": No extinction monitor specified", status ); return allquick; } if( smf_history_check( data, FUNC_NAME, status) ) { /* If caller not requesting allextcorr fail here */ if( !allextcorr ) { msgSetc("F", FUNC_NAME); msgOutif(MSG__VERB," ", "^F has already been run on these data, returning to caller", status); return allquick; } } /* Acquire the data order */ isTordered = data->isTordered; /* make sure we have a header */ hdr = data->hdr; if( hdr == NULL ) { *status = SAI__ERROR; errRep( FUNC_NAME, "Input data has no header", status); return allquick; } /* Do we have 2-D image data? */ ndims = data->ndims; if (ndims == 2) { nframes = 1; nx = (data->dims)[0]; ny = (data->dims)[1]; npts = nx*ny; } else { /* this routine will also check for dimensionality */ smf_get_dims( data, &nx, &ny, &npts, &nframes, NULL, NULL, NULL, status ); } /* Tell user we're correcting for extinction */ msgOutif(MSG__VERB," ", "Correcting for extinction.", status); /* Should check data type for double if not allextcorr case */ if( !allextcorr ) { if (!smf_dtype_check_fatal( data, NULL, SMF__DOUBLE, status)) return allquick; } /* Check that we're not trying to use the WVM for 2-D data */ if ( ndims == 2 && tausrc == SMF__TAUSRC_WVMRAW ) { if ( *status == SAI__OK ) { *status = SAI__ERROR; errRep( FUNC_NAME, "Method WVMRaw can not be used on 2-D image data", status ); return allquick; } } else if (ndims < 2 || ndims > 3) { if (*status == SAI__OK) { *status = SAI__ERROR; errRepf( FUNC_NAME, "Can not extinction correct data with %zd dimension(s)", status, ndims ); return allquick; } } if (tausrc == SMF__TAUSRC_WVMRAW) { size_t ntotaltau = 0; size_t ngoodtau = 0; /* calculate WVM unless we have external values */ if (wvmtaucache && *wvmtaucache) { wvmtau = *wvmtaucache; smf_smfFile_msg( data->file, "FILE", 1, "<unknown>"); msgOutiff( MSG__VERB, "", "Using cached WVM data for extinction correction of ^FILE", status); } else { smf_calc_smoothedwvm( wf, NULL, data, extpars, &wvmtau, &ntotaltau, &ngoodtau, status ); smf_smfFile_msg( data->file, "FILE", 1, "<unknown>"); msgOutiff( MSG__VERB, "", "Using WVM mode for extinction correction of ^FILE" " %.0f %% of WVM data are present", status, (double)(100.0*(double)ngoodtau/(double)ntotaltau) ); } } /* Check auto mode */ if (tausrc == SMF__TAUSRC_AUTO && *status == SAI__OK) { smf_smfFile_msg( data->file, "FILE", 1, "<unknown>" ); if (ndims == 2) { /* have to use CSO mode */ tausrc = SMF__TAUSRC_CSOTAU; } else if (ndims == 3) { /* Calculate the WVM tau data and see if we have enough good data */ size_t ngoodtau = 0; size_t ntotaltau = 0; double percentgood = 0.0; if (wvmtaucache && *wvmtaucache) { wvmtau = *wvmtaucache; tausrc = SMF__TAUSRC_WVMRAW; smf_smfFile_msg( data->file, "FILE", 1, "<unknown>"); msgOutiff( MSG__VERB, "", "Using cached WVM data for extinction correction of ^FILE", status ); } else { smf_calc_smoothedwvm( wf, NULL, data, extpars, &wvmtau, &ntotaltau, &ngoodtau, status ); percentgood = 100.0 * ((double)ngoodtau / (double)ntotaltau); if ( percentgood > 80.0) { tausrc = SMF__TAUSRC_WVMRAW; msgOutiff( MSG__VERB, "", "Selecting WVM mode for extinction correction of ^FILE." " %.0f %% of WVM data are present", status, percentgood ); } else { tausrc = SMF__TAUSRC_CSOTAU; if (wvmtau) wvmtau = astFree( wvmtau ); } } } if (tausrc == SMF__TAUSRC_CSOTAU) { msgOutiff( MSG__VERB, "", "Selecting CSO mode for extinction correction of ^FILE", status ); } else if (tausrc == SMF__TAUSRC_WVMRAW) { /* Dealt with this above */ } else { /* oops. Fall back position */ tausrc = SMF__TAUSRC_CSOTAU; msgOutiff( MSG__VERB, "", "Selecting CSO mode as unexpected fallback for extinction correction of ^FILE", status ); } } /* If we have a CSO Tau then convert it to the current filter. This will also convert bad values to a value derived from the header if appropriate. */ if ( tausrc == SMF__TAUSRC_CSOTAU ) { tau = smf_cso2filt_tau( hdr, tau, extpars, status ); /* The tau source is now a real tau */ tausrc = SMF__TAUSRC_TAU; } /* Find the airmass range for this data */ smf_find_airmass_interval( hdr, &amstart, &amend, &elstart, &elend, status ); if (*status == SAI__OK && (amstart == VAL__BADD || amend == VAL__BADD)) { *status = SAI__ERROR; errRep( "", "No good airmass values found in JCMTSTATE structure for these data", status ); } /* if we are not doing WVM correction but are in adaptive mode we can determine whether or not we will have to use full or single mode just by looking at the airmass data. */ if (ndims == 3 && tausrc != SMF__TAUSRC_WVMRAW && method == SMF__EXTMETH_ADAPT) { /* first and last is a good approximation given that most SCUBA-2 files will only be a minute duration. */ double refel; double refam; /* only need to examine the largest airmass */ if (amstart > amend) { refam = amstart; refel = elstart; } else { refam = amend; refel = elend; } /* and choose a correction method */ if (is_large_delta_atau( refam, refel, tau, status) ) { method = SMF__EXTMETH_FULL; msgOutiff(MSG__DEBUG, " ", "Adaptive extinction algorithm selected per-bolometer airmass value " "per time slice (am=%g, tau=%g)", status, refam, tau); } else { msgOutiff(MSG__DEBUG, " ", "Adaptive extinction algorithm selected single airmass value per time slice" " (am=%g, tau=%g)", status, refam, tau); method = SMF__EXTMETH_SINGLE; } } /* Assign pointer to input data array if status is good */ if ( *status == SAI__OK ) { indata = (data->pntr)[0]; vardata = (data->pntr)[1]; } /* Jump to the cleanup section if status is bad by this point since we need to free memory */ if (*status != SAI__OK) goto CLEANUP; /* Array bounds for astTranGrid call */ lbnd[0] = 1; lbnd[1] = 1; ubnd[0] = nx; ubnd[1] = ny; /* Unlock the AST objects in the smfData so that the worker threads can lock them. */ smf_lock_data( data, 0, status ); /* How many threads do we get to play with */ nw = wf ? wf->nworker : 1; /* Find how many frames to process in each worker thread. */ framestep = nframes/nw; if( framestep == 0 ) { framestep = 1; nw = nframes; } /* Allocate job data for threads, and store the range of frames to be processed by each one. Ensure that the last thread picks up any left-over frames. */ job_data = astCalloc( nw, sizeof(*job_data) ); if( *status == SAI__OK ) { for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->f1 = iw*framestep; if( iw < nw - 1 ) { pdata->f2 = pdata->f1 + framestep - 1; } else { pdata->f2 = nframes - 1 ; } pdata->nframes = nframes; pdata->npts = npts; pdata->allextcorr = allextcorr; pdata->indata = indata; pdata->tau = tau; pdata->vardata = vardata; pdata->wvmtau = wvmtau; pdata->amstart = amstart; pdata->amfirst = amstart + ( amend - amstart )*pdata->f1/( nframes - 1 ); pdata->lbnd = lbnd; pdata->ubnd = ubnd; pdata->isTordered = isTordered; pdata->ndims = ndims; pdata->data = data; pdata->hdr = hdr; pdata->method = method; pdata->tausrc = tausrc; /* Submit the job to the workforce. */ thrAddJob( wf, 0, pdata, smf1_correct_extinction, 0, NULL, status ); } /* Wait for all jobs to complete. */ thrWait( wf, status ); /* Record if all time slices used a single air mass. */ allquick = 1; for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; if( ! pdata->allquick ) { allquick = 0; break; } } /* Free the job data. */ job_data = astFree( job_data ); } /* Lock the AST objects in the smfData for use by this thread. */ smf_lock_data( data, 1, status ); /* Add history entry if !allextcorr */ if( (*status == SAI__OK) && !allextcorr ) { smf_history_add( data, FUNC_NAME, status); } CLEANUP: if (wvmtaucache) { if (!*wvmtaucache) { *wvmtaucache = wvmtau; } } else { wvmtau = astFree( wvmtau ); } return allquick; }
void smf_rebinmap1( ThrWorkForce *wf, smfData *data, smfData *variance, int *lut, size_t tslice1, size_t tslice2, int trange, int *whichmap, dim_t nmap, smf_qual_t mask, int sampvar, int flags, double *map, double *mapweight, double *mapweightsq, int *hitsmap, double *mapvar, dim_t msize, double *scalevariance, int *status ) { /* Local Variables */ SmfRebinMap1Data *job_data = NULL; SmfRebinMap1Data *pdata; double *dat=NULL; /* Pointer to data array */ size_t dbstride; /* bolo stride of data */ size_t dtstride; /* tstride of data */ int iw; /* Thread index */ dim_t mbufsize; /* Size of full (multi-map) map buffers */ dim_t nbolo; /* number of bolos */ dim_t ntslice; /* number of time slices */ int nw; /* Number of worker threads */ size_t pixstep; /* Number of map pixels per thread */ smf_qual_t * qual = NULL; /* Quality pointer */ double scalevar; /* variance scale factor */ double scaleweight; /* weights for calculating scalevar */ size_t t1, t2; /* range of time slices to re-grid */ double *var=NULL; /* Pointer to variance array */ size_t vbstride; /* bolo stride of variance */ dim_t vnbolo; /* number of bolos in variance */ dim_t vntslice; /* number of bolos in variance */ size_t vtstride; /* tstride of variance */ /* Main routine */ if (*status != SAI__OK) return; /* Check inputs */ if( !data || !map || !lut || !mapweight || !mapweightsq || !mapvar || !hitsmap ) { *status = SAI__ERROR; errRep(" ", FUNC_NAME ": Null inputs", status ); return; } if( !data->pntr[0] ) { *status = SAI__ERROR; errRep(" ", FUNC_NAME ": supplied data is empty", status ); return; } dat = data->pntr[0]; qual = smf_select_qualpntr( data, NULL, status ); smf_get_dims( data, NULL, NULL, &nbolo, &ntslice, NULL, &dbstride, &dtstride, status ); /* Size of full map buffers */ if( whichmap ) { mbufsize = nmap * msize; } else { mbufsize = msize; } if( variance ) { var = variance->pntr[0]; smf_get_dims( variance, NULL, NULL, &vnbolo, &vntslice, NULL, &vbstride, &vtstride, status ); /* Check that the variance dimensions are compatible with data */ if( (*status==SAI__OK) && ((vnbolo != nbolo) || ((vntslice>1)&&(vntslice!=ntslice))) ) { *status = SAI__ERROR; errRep(" ", FUNC_NAME ": variance dimensions incompatible with data", status ); return; } } /* Range of time slices to regrid */ if( trange ) { if( tslice2 >= ntslice ) { *status = SAI__ERROR; errRepf( "", FUNC_NAME ": tslice2 (%zu) can't be >= ntslice (%zu)", status, tslice2, ntslice ); return; } if( tslice1 > tslice2 ) { *status = SAI__ERROR; errRepf( "", FUNC_NAME ": tslice1 (%zu) > tslice2 (%zu)", status, tslice1, tslice2 ); return; } t1 = tslice1; t2 = tslice2; } else { t1 = 0; t2 = ntslice-1; } /* If this is the first data to be accumulated zero the arrays */ if( flags & AST__REBININIT ) { memset( map, 0, mbufsize*sizeof(*map) ); memset( mapweight, 0, mbufsize*sizeof(*mapweight) ); memset( mapweightsq, 0, mbufsize*sizeof(*mapweightsq) ); memset( mapvar, 0, mbufsize*sizeof(*mapvar) ); memset( hitsmap, 0, mbufsize*sizeof(*hitsmap) ); } /* How many threads do we get to play with */ nw = wf ? wf->nworker : 1; /* Find how many map pixels to process in each worker thread. */ pixstep = msize/nw; if( pixstep == 0 ) pixstep = 1; /* Allocate job data for threads, and store the range of pixels to be processed by each one. Ensure that the last thread picks up any left-over time pixels. */ job_data = astCalloc( nw, sizeof(*job_data) ); if( *status == SAI__OK ) { for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->p1 = iw*pixstep; if( iw < nw - 1 ) { pdata->p2 = pdata->p1 + pixstep - 1; } else { pdata->p2 = msize - 1 ; } /* Store other values common to all jobs. */ pdata->msize = msize; pdata->nbolo = nbolo; pdata->t1 = t1; pdata->t2 = t2; pdata->vntslice = vntslice; pdata->dat = dat; pdata->map = map; pdata->mapvar = mapvar; pdata->mapweightsq = mapweightsq; pdata->mapweight = mapweight; pdata->var = var; pdata->hitsmap = hitsmap; pdata->lut = lut; pdata->whichmap = whichmap; pdata->dbstride = dbstride; pdata->dtstride = dtstride; pdata->vbstride = vbstride; pdata->vtstride = vtstride; pdata->mask = mask; pdata->qual = qual; pdata->mbufsize = mbufsize; } } if( var ) { /* Accumulate data and weights in the case that variances are given*/ if( sampvar ) { /* Measure weighted sample variance for varmap */ if( qual ) { /* QUALITY checking version */ /* Set up jobs to add the previous estimate of COM back on to the residuals, and then wait for the jobs to complete. These jobs also clear any SMF__Q_COM flags set by previous iterations. */ for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->operation = 1; thrAddJob( wf, 0, pdata, smf1_rebinmap1, 0, NULL, status ); } thrWait( wf, status ); } else { /* VAL__BADD checking version */ for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->operation = 2; thrAddJob( wf, 0, pdata, smf1_rebinmap1, 0, NULL, status ); } thrWait( wf, status ); } } else { /* Otherwise use simple error propagation for varmap */ if( qual ) { /* QUALITY checking version */ for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->operation = 3; thrAddJob( wf, 0, pdata, smf1_rebinmap1, 0, NULL, status ); } thrWait( wf, status ); } else { /* VAL__BADD checking version */ for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->operation = 4; thrAddJob( wf, 0, pdata, smf1_rebinmap1, 0, NULL, status ); } thrWait( wf, status ); } } } else { /* Accumulate data and weights when no variances are given. In this case the variance map is always estimated from the sample variance */ if( qual ) { /* QUALITY checking version */ for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->operation = 5; thrAddJob( wf, 0, pdata, smf1_rebinmap1, 0, NULL, status ); } thrWait( wf, status ); } else { /* VAL__BADD checking version */ for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->operation = 6; thrAddJob( wf, 0, pdata, smf1_rebinmap1, 0, NULL, status ); } thrWait( wf, status ); } } /* If this is the last data to be accumulated re-normalize */ if( flags & AST__REBINEND ) { /* Find how many buffer pixels to process in each worker thread. May be different to the number of map pixels set up earlier. */ pixstep = mbufsize/nw; if( pixstep == 0 ) pixstep = 1; for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->p1 = iw*pixstep; if( iw < nw - 1 ) { pdata->p2 = pdata->p1 + pixstep - 1; } else { pdata->p2 = mbufsize - 1 ; } } if( sampvar || !var ) { for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->operation = 7; thrAddJob( wf, 0, pdata, smf1_rebinmap1, 0, NULL, status ); } thrWait( wf, status ); scaleweight=0; scalevar=0; for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; scaleweight += pdata->scaleweight; scalevar += pdata->scalevar; } /* Re-normalize scalevar */ if( scaleweight ) { scalevar /= scaleweight; if( scalevariance ) { *scalevariance = scalevar; } } } else { /* Re-normalization for error propagation case */ for( iw = 0; iw < nw; iw++ ) { pdata = job_data + iw; pdata->operation = 8; thrAddJob( wf, 0, pdata, smf1_rebinmap1, 0, NULL, status ); } thrWait( wf, status ); } } job_data = astFree( job_data ); }
/* Main entry point. */ void smf_calcmodel_smo( ThrWorkForce *wf, smfDIMMData *dat, int chunk, AstKeyMap *keymap, smfArray **allmodel, int flags __attribute__((unused)), int *status) { /* Local Variables */ size_t bstride; /* bolo stride */ dim_t boxcar = 0; /* size of boxcar smooth window */ smf_filt_t filter_type; /* The type of smoothing to perform */ size_t i; /* Loop counter */ dim_t idx=0; /* Index within subgroup */ int iworker; /* Owkrer index */ smfCalcmodelSmoJobData *job_data=NULL; /* Pointer to all job data structures */ AstKeyMap *kmap=NULL; /* Pointer to PLN-specific keys */ smfArray *model=NULL; /* Pointer to model at chunk */ double *model_data=NULL; /* Pointer to DATA component of model */ double *model_data_copy=NULL; /* Copy of model_data for one bolo */ dim_t nbolo=0; /* Number of bolometers */ dim_t ndata=0; /* Total number of data points */ int notfirst=0; /* flag for delaying until after 1st iter */ dim_t ntslice=0; /* Number of time slices */ int nworker; /* No. of worker threads in supplied Workforce */ smfCalcmodelSmoJobData *pdata=NULL; /* Pointer to current data structure */ smfArray *qua=NULL; /* Pointer to QUA at chunk */ smf_qual_t *qua_data=NULL; /* Pointer to quality data */ smfArray *res=NULL; /* Pointer to RES at chunk */ double *res_data=NULL; /* Pointer to DATA component of res */ int step; /* Number of bolometers per thread */ size_t tstride; /* Time slice stride in data array */ const char * typestr = NULL; /* smo.type value */ /* Main routine */ if (*status != SAI__OK) return; /* Obtain pointer to sub-keymap containing PLN parameters. Something will always be available.*/ astMapGet0A( keymap, "SMO", &kmap ); /* Are we skipping the first iteration? */ astMapGet0I(kmap, "NOTFIRST", ¬first); if( notfirst && (flags & SMF__DIMM_FIRSTITER) ) { msgOutif( MSG__VERB, "", FUNC_NAME ": skipping SMO this iteration", status ); return; } /* Get the boxcar size */ if( kmap ) smf_get_nsamp( kmap, "BOXCAR", res->sdata[0], &boxcar, status ); /* Get the type of smoothing filter to use. Anthing that is not "MEDIAN" is mean */ filter_type = SMF__FILT_MEAN; if (astMapGet0C( kmap, "TYPE", &typestr ) ) { if (strncasecmp( typestr, "MED", 3 ) == 0 ) { filter_type = SMF__FILT_MEDIAN; } } /* Obtain pointers to relevant smfArrays for this chunk */ res = dat->res[chunk]; qua = dat->qua[chunk]; /* Assert bolo-ordered data */ smf_model_dataOrder( dat, allmodel, chunk, SMF__RES|SMF__QUA, 0, status ); smf_get_dims( res->sdata[0], NULL, NULL, NULL, &ntslice, &ndata, NULL, NULL, status); model = allmodel[chunk]; msgOutiff(MSG__VERB, "", " Calculating smoothed model using boxcar of width %" DIM_T_FMT " time slices", status, boxcar); /* Create structures used to pass information to the worker threads. */ nworker = wf ? wf->nworker : 1; job_data = astMalloc( nworker*sizeof( *job_data ) ); /* Loop over index in subgrp (subarray) and put the previous iteration of the filtered component back into the residual before calculating and removing the new filtered component */ for( idx=0; (*status==SAI__OK)&&(idx<res->ndat); idx++ ) { /* Obtain dimensions of the data */ smf_get_dims( res->sdata[idx], NULL, NULL, &nbolo, &ntslice, &ndata, &bstride, &tstride, status); /* Get pointers to data/quality/model */ res_data = (res->sdata[idx]->pntr)[0]; qua_data = (qua->sdata[idx]->pntr)[0]; model_data = (model->sdata[idx]->pntr)[0]; if( (res_data == NULL) || (model_data == NULL) || (qua_data == NULL) ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": Null data in inputs", status); } else { /* Uncomment to aid debugging */ /* smf_write_smfData( res->sdata[idx], NULL, qua_data, "res_in", NULL, 0, 0, MSG__VERB, status ); */ if( *status == SAI__OK ) { /* Place last iteration back into residual if this is a smoothable section of the time series */ for (i=0; i< ndata; i++) { if ( !(qua_data[i]&SMF__Q_FIT) && res_data[i] != VAL__BADD && model_data[i] != VAL__BADD ) { res_data[i] += model_data[i]; } } } /* Uncomment to aid debugging */ /* smf_write_smfData( model->sdata[idx], NULL, qua_data, "model_b4", NULL, 0, 0, MSG__VERB, status ); smf_write_smfData( res->sdata[idx], NULL, qua_data, "res_b4", NULL, 0, 0, MSG__VERB, status ); */ /* Determine which bolometers are to be processed by which threads. */ step = nbolo/nworker; if( step < 1 ) step = 1; for( iworker = 0; iworker < nworker; iworker++ ) { pdata = job_data + iworker; pdata->b1 = iworker*step; pdata->b2 = pdata->b1 + step - 1; } /* Ensure that the last thread picks up any left-over bolometers */ pdata->b2 = nbolo - 1; /* Store all the other info needed by the worker threads, and submit the jobs to apply the smoothing. */ for( iworker = 0; iworker < nworker; iworker++ ) { pdata = job_data + iworker; pdata->boxcar = boxcar; pdata->bstride = bstride; pdata->bstride = bstride; pdata->filter_type = filter_type; pdata->model_data = model_data; pdata->nbolo = nbolo; pdata->nbolo = nbolo; pdata->ntslice = ntslice; pdata->ntslice = ntslice; pdata->qua_data = qua_data; pdata->qua_data = qua_data; pdata->res_data = res_data; pdata->res_data = res_data; pdata->tstride = tstride; pdata->tstride = tstride; thrAddJob( wf, THR__REPORT_JOB, pdata, smf1_calcmodel_smo_job, 0, NULL, status ); } thrWait( wf, status ); /* Uncomment to aid debugging */ /* smf_write_smfData( res->sdata[idx], NULL, qua_data, "res_af", NULL, 0, 0, MSG__VERB, status ); smf_write_smfData( model->sdata[idx], NULL, qua_data, "model_af", NULL, 0, 0, MSG__VERB, status ); */ } } /* Free work space (astFree returns without action if a NULL pointer is supplied). */ model_data_copy = astFree( model_data_copy ); job_data = astFree( job_data ); /* Annul AST Object pointers (astAnnul reports an error if a NULL pointer is supplied). */ if( kmap ) kmap = astAnnul( kmap ); }