int smf_apply_dark( smfData *indata, const smfArray *darks, int *status) { size_t dark1; size_t dark2; smfData * dkdata1 = NULL; smfData * dkdata2 = NULL; int retval = 0; if (*status != SAI__OK) return retval; if (!darks) return retval; /* work out which darks are suitable */ smf_choose_darks( darks, indata, &dark1, &dark2, status ); /* get the file struct and create a token */ smf_smfFile_msg( indata->file, "FILE", 1, "<no file>" ); /* and correct for dark */ if (dark1 != SMF__BADIDX) dkdata1 = darks->sdata[dark1]; if (dark2 != SMF__BADIDX) dkdata2 = darks->sdata[dark2]; if (dkdata1 || dkdata2) { if (dkdata1) { msgSetc("PRIOR", "yes"); } else { msgSetc("PRIOR", "no"); } if (dkdata2) { msgSetc("POST", "yes"); } else { msgSetc("POST", "no"); } msgOutif(MSG__VERB," ", "Dark subtracting ^FILE." " Prior dark: ^PRIOR Following dark: ^POST", status); smf_subtract_dark( indata, dkdata1, dkdata2, SMF__DKSUB_CHOOSE, status ); retval = 1; } else { msgOutif(MSG__QUIET, " ", "Warning: File ^FILE has no suitable dark frame", status); } return retval; }
void smf_rebinsparse( smfData *data, int first, int *ptime, AstFrame *ospecfrm, AstMapping *ospecmap, AstSkyFrame *oskyframe, Grp *detgrp, int lbnd_out[ 3 ], int ubnd_out[ 3 ], int genvar, float *data_array, float *var_array, int *ispec, float *texp_array, float *teff_array, double *fcon, int *status ){ /* Local Variables */ AstCmpMap *fmap = NULL; /* Mapping from spectral grid to topo freq Hz */ AstCmpMap *ssmap = NULL; /* I/p GRID-> o/p PIXEL Mapping for spectral axis */ AstFitsChan *fc = NULL; /* Storage for FITS headers */ AstFrame *specframe = NULL; /* Spectral Frame in input FrameSet */ AstFrame *specframe2 = NULL; /* Temporary copy of SpecFrame in input WCS */ AstFrameSet *fs = NULL; /* A general purpose FrameSet pointer */ AstFrameSet *swcsin = NULL; /* FrameSet describing spatial input WCS */ AstMapping *fsmap = NULL; /* Base->Current Mapping extracted from a FrameSet */ AstMapping *specmap = NULL; /* PIXEL -> Spec mapping in input FrameSet */ char *fftwin = NULL; /* Name of FFT windowing function */ const char *name = NULL; /* Pointer to current detector name */ const double *tsys=NULL; /* Pointer to Tsys value for first detector */ dim_t timeslice_size; /* No of detector values in one time slice */ double *spectab = NULL;/* Workspace for spectral output grid positions */ double *xin = NULL; /* Workspace for detector input grid positions */ double *xout = NULL; /* Workspace for detector output pixel positions */ double *yin = NULL; /* Workspace for detector input grid positions */ double *yout = NULL; /* Workspace for detector output pixel positions */ double at; /* Frequency at which to take the gradient */ double dnew; /* Channel width in Hz */ double fcon2; /* Variance factor for whole file */ double k; /* Back-end degradation factor */ double tcon; /* Variance factor for whole time slice */ float *pdata = NULL; /* Pointer to next data sample */ float *qdata = NULL; /* Pointer to next data sample */ float rtsys; /* Tsys value */ float teff; /* Effective integration time, times 4 */ float texp; /* Total time ( = ton + toff ) */ float toff; /* Off time */ float ton; /* On time */ int *nexttime = NULL; /* Pointer to next time slice index to use */ int dim[ 3 ]; /* Output array dimensions */ int found; /* Was current detector name found in detgrp? */ int good; /* Are there any good detector samples? */ int ibasein; /* Index of base Frame in input FrameSet */ int ichan; /* Index of current channel */ int iv; /* Offset to next element */ int iz; /* Output grid index on axis 3 */ int nchan; /* Number of input spectral channels */ int pixax[ 3 ]; /* The output fed by each selected mapping input */ int specax; /* Index of spectral axis in input FrameSet */ size_t irec; /* Index of current input detector */ size_t itime; /* Index of current time slice */ smfHead *hdr = NULL; /* Pointer to data header for this time slice */ /* Check inherited status */ if( *status != SAI__OK ) return; /* Begin an AST context.*/ astBegin; /* Store a pointer to the input NDFs smfHead structure. */ hdr = data->hdr; /* Store the dimensions of the output array. */ dim[ 0 ] = ubnd_out[ 0 ] - lbnd_out[ 0 ] + 1; dim[ 1 ] = ubnd_out[ 1 ] - lbnd_out[ 1 ] + 1; dim[ 2 ] = ubnd_out[ 2 ] - lbnd_out[ 2 ] + 1; /* Store the number of pixels in one time slice */ timeslice_size = (data->dims)[ 0 ]*(data->dims)[ 1 ]; /* We want a description of the spectral WCS axis in the input file. If the input file has a WCS FrameSet containing a SpecFrame, use it, otherwise we will obtain it from the FITS header later. NOTE, if we knew that all the input NDFs would have the same spectral axis calibration, then the spectral WCS need only be obtained from the first NDF. However, in the general case, I presume that data files may be combined that use different spectral axis calibrations, and so these differences need to be taken into account. */ if( hdr->tswcs ) { fs = astClone( hdr->tswcs ); /* The first axis should be a SpecFrame. See if this is so. If not annul the specframe pointer. */ specax = 1; specframe = astPickAxes( fs, 1, &specax, NULL ); if( !astIsASpecFrame( specframe ) ) specframe = astAnnul( specframe ); } /* If the above did not yield a SpecFrame, use the FITS-WCS headers in the FITS extension of the input NDF. Take a copy of the FITS header (so that the contents of the header are not changed), and then read a FrameSet out of it. */ if( !specframe ) { fc = astCopy( hdr->fitshdr ); astClear( fc, "Card" ); fs = astRead( fc ); /* Extract the SpecFrame that describes the spectral axis from the current Frame of this FrameSet. This is assumed to be the third WCS axis (NB the different axis number). */ specax = 3; specframe = astPickAxes( fs, 1, &specax, NULL ); } /* Split off the 1D Mapping for this single axis from the 3D Mapping for the whole WCS. This results in "specmap" holding the Mapping from SpecFrame value to GRID value. */ fsmap = astGetMapping( fs, AST__CURRENT, AST__BASE ); astMapSplit( fsmap, 1, &specax, pixax, &specmap ); /* Invert the Mapping for the spectral axis so that it goes from input GRID coord to spectral coord. */ astInvert( specmap ); /* Get a Mapping that converts values in the input spectral system to the corresponding values in the output spectral system. */ fs = astConvert( specframe, ospecfrm, "" ); /* Concatenate these Mappings with the supplied spectral Mapping to get a Mapping from the input spectral grid axis (pixel axis 1) to the output spectral grid axis (pixel axis 3). Simplify the Mapping. */ ssmap = astCmpMap( astCmpMap( specmap, astGetMapping( fs, AST__BASE, AST__CURRENT ), 1, " " ), ospecmap, 1, " " ); ssmap = astSimplify( ssmap ); /* Create a table with one element for each channel in the input array, holding the index of the nearest corresponding output channel. */ nchan = (data->dims)[ 0 ]; spectab = astMalloc( sizeof( *spectab )*nchan ); if( spectab ) { for( ichan = 0; ichan < nchan; ichan++ ) spectab[ ichan ] = ichan + 1; astTran1( ssmap, nchan, spectab, 1, spectab ); for( ichan = 0; ichan < nchan; ichan++ ) { if( spectab[ ichan ] != AST__BAD ) { iz = floor( spectab[ ichan ] + 0.5 ); if( iz >= 1 && iz <= dim[ 2 ] ) { spectab[ ichan ] = iz; } else { spectab[ ichan ] = 0; } } else { spectab[ ichan ] = 0; } } } /* Allocate work arrays big enough to hold the coords of all the detectors in the current input file.*/ xin = astMalloc( (data->dims)[ 1 ] * sizeof( *xin ) ); yin = astMalloc( (data->dims)[ 1 ] * sizeof( *yin ) ); xout = astMalloc( (data->dims)[ 1 ] * sizeof( *xout ) ); yout = astMalloc( (data->dims)[ 1 ] * sizeof( *yout ) ); /* Initialise a string to point to the name of the first detector for which data is available */ name = hdr->detname; /* Store input coords for the detectors. Axis 1 is the detector index, and axis 2 is a dummy axis that always has the value 1. */ for( irec = 0; irec < (data->dims)[ 1 ]; irec++ ) { xin[ irec ] = irec + 1.0; yin[ irec ] = 1.0; /* If a group of detectors to be used was supplied, search the group for the name of the current detector. If not found, set the GRID coords bad. */ if( detgrp ) { found = grpIndex( name, detgrp, 1, status ); if( !found ) { xin[ irec ] = AST__BAD; yin[ irec ] = AST__BAD; } } /* Move on to the next available detector name. */ name += strlen( name ) + 1; } /* Find the constant factor associated with the current input file. This is the squared backend degradation factor, divided by the noise bandwidth. Get the required FITS headers, checking they were found. */ if( astGetFitsF( hdr->fitshdr, "BEDEGFAC", &k ) && astGetFitsS( hdr->fitshdr, "FFT_WIN", &fftwin ) ){ /* Get a Mapping that converts values in the input spectral system to topocentric frequency in Hz, and concatenate this Mapping with the Mapping from input GRID coord to the input spectral system. The result is a Mapping from input GRID coord to topocentric frequency in Hz. */ specframe2 = astCopy( specframe ); astSet( specframe2, "system=freq,stdofrest=topo,unit=Hz" ); fmap = astCmpMap( specmap, astGetMapping( astConvert( specframe, specframe2, "" ), AST__BASE, AST__CURRENT ), 1, " " ); /* Differentiate this Mapping at the mid channel position to get the width of an input channel in Hz. */ at = 0.5*nchan; dnew = astRate( fmap, &at, 1, 1 ); /* Modify the channel width to take account of the effect of the FFT windowing function. Allow undef value because FFT_WIN for old data had a broken value in hybrid subband modes. */ if( dnew != AST__BAD ) { dnew = fabs( dnew ); if( !strcmp( fftwin, "truncate" ) ) { dnew *= 1.0; } else if( !strcmp( fftwin, "hanning" ) ) { dnew *= 1.5; } else if( !strcmp( fftwin, "<undefined>" ) ) { /* Deal with broken data - make an assumption */ dnew *= 1.0; } else if( *status == SAI__OK ) { *status = SAI__ERROR; msgSetc( "W", fftwin ); errRep( FUNC_NAME, "FITS header FFT_WIN has unknown value " "'^W' (programming error).", status ); } /* Form the required constant. */ fcon2 = k*k/dnew; } else { fcon2 = VAL__BADD; } } else { fcon2 = VAL__BADD; } /* Return the factor needed for calculating Tsys from the variance. */ if( first ) { *fcon = fcon2; } else if( fcon2 != *fcon ) { *fcon = VAL__BADD; } /* Initialise a pointer to the next time slice index to be used. */ nexttime = ptime; /* Loop round all the time slices in the input file. */ for( itime = 0; itime < (data->dims)[ 2 ] && *status == SAI__OK; itime++ ) { /* If this time slice is not being pasted into the output cube, pass on. */ if( nexttime ){ if( *nexttime != itime ) continue; nexttime++; } /* Store a pointer to the first input data value in this time slice. */ pdata = ( (float *) (data->pntr)[ 0 ] ) + itime*timeslice_size; /* Get a FrameSet describing the spatial coordinate systems associated with the current time slice of the current input data file. The base frame in the FrameSet will be a 2D Frame in which axis 1 is detector number and axis 2 is unused. The current Frame will be a SkyFrame (the SkyFrame System may be any of the JCMT supported systems). The Epoch will be set to the epoch of the time slice. */ smf_tslice_ast( data, itime, 1, NO_FTS, status ); swcsin = hdr->wcs; /* Note the total exposure time (texp) for all the input spectra produced by this time slice. */ ton = hdr->state->acs_exposure; if( ton == 0.0 ) ton = VAL__BADR; toff = hdr->state->acs_offexposure; if( toff == 0.0 ) toff = VAL__BADR; if( ton != VAL__BADR && toff != VAL__BADR ) { texp = ton + toff; teff = 4*ton*toff/( ton + toff ); } else { texp = VAL__BADR; teff = VAL__BADR; } /* If output variances are being calculated on the basis of Tsys values in the input, find the constant factor associated with the current time slice. */ tcon = AST__BAD; if( genvar == 2 && fcon2 != AST__BAD && texp != VAL__BADR ) { tcon = fcon2*( 1.0/ton + 1.0/toff ); /* Get a pointer to the start of the Tsys values for this time slice. */ tsys = hdr->tsys + hdr->ndet*itime; } /* We now create a Mapping from detector index to position in oskyframe. */ astInvert( swcsin ); ibasein = astGetI( swcsin, "Base" ); fs = astConvert( swcsin, oskyframe, "SKY" ); astSetI( swcsin, "Base", ibasein ); astInvert( swcsin ); if( fs == NULL ) { if( *status == SAI__OK ) { if (data->file) { smf_smfFile_msg(data->file, "FILE", 1, "<unknown>"); } else { msgSetc( "FILE", "<unknown>" ); } *status = SAI__ERROR; errRep( FUNC_NAME, "The spatial coordinate system in ^FILE " "is not compatible with the spatial coordinate " "system in the first input file.", status ); } break; } /* Transform the positions of the detectors from input GRID to oskyframe coords. */ astTran2( fs, (data->dims)[ 1 ], xin, yin, 1, xout, yout ); /* Loop round all detectors. */ for( irec = 0; irec < (data->dims)[ 1 ]; irec++ ) { /* If the detector has a valid position, see if it produced any good data values. */ if( xout[ irec ] != AST__BAD && yout[ irec ] != AST__BAD ) { qdata = pdata; good = 0; for( ichan = 0; ichan < nchan; ichan++ ){ if( *(qdata++) != VAL__BADR ) { good = 1; break; } } /* If it did, calculate the variance associated with each detector sample (if required), based on the input Tsys values, and copy the spectrum to the output NDF. */ if( good ) { if( *ispec < dim[ 0 ] ){ rtsys = tsys ? (float) tsys[ irec ] : VAL__BADR; if( rtsys <= 0.0 ) rtsys = VAL__BADR; if( tcon != AST__BAD && genvar == 2 && rtsys != VAL__BADR ) { var_array[ *ispec ] = tcon*rtsys*rtsys; } else if( var_array ) { var_array[ *ispec ] = VAL__BADR; } if( texp != VAL__BADR ) { texp_array[ *ispec ] = texp; teff_array[ *ispec ] = teff; } for( ichan = 0; ichan < nchan; ichan++, pdata++ ) { iz = spectab[ ichan ] - 1; if( iz >= 0 && iz < dim[ 2 ] ) { iv = *ispec + dim[ 0 ]*iz; data_array[ iv ] = *pdata; } } (*ispec)++; } else if( *status == SAI__OK ){ *status = SAI__ERROR; msgSeti( "DIM", dim[ 0 ] ); errRep( " ", "Too many spectra (more than ^DIM) for " "the output NDF (programming error).", status ); break; } /* If this detector does not have any valid data values, increment the data pointer to point at the first sample for the next detector. */ } else { pdata += nchan; } /* If this detector does not have a valid position, increment the data pointer to point at the first sample for the next detector. */ } else { pdata += nchan; } } /* For efficiency, explicitly annul the AST Objects created in this tight loop. */ fs = astAnnul( fs ); } /* Free resources */ spectab = astFree( spectab ); xin = astFree( xin ); yin = astFree( yin ); xout = astFree( xout ); yout = astFree( yout ); /* End the AST context. This will annul all AST objects created within the context (except for those that have been exported from the context). */ astEnd; }
int *smf_jsatiles_data( Grp *igrp, size_t size, int *ntile, int *status ){ /* Local Variables */ AstFrame *frm = NULL; AstFrameSet *fs; JCMTState *state; const char *trsys; const char *trsys_last; dim_t *hits = NULL; dim_t *ph; dim_t iframe; double *gx = NULL; double *gy = NULL; double *p1; double *p2; double *px; double *py; double *trac1 = NULL; double *trac2 = NULL; double fov; double point1[ 2 ]; double point2[ 2 ]; double search; int *tiles = NULL; int dim[ 2 ]; int i; int ix; int iy; int lbnd[ 2 ]; int ubnd[ 2 ]; size_t ifile; smfData *data = NULL; smfHead *hdr = NULL; smfJSATiling skytiling; smf_inst_t inst = SMF__INST_NONE; smf_inst_t instrument; smf_subinst_t subinst; /* Initialise */ *ntile = 0; /* Check inherited status */ if( *status != SAI__OK ) return tiles; /* Start an AST context so that all AST objects created in this function are annulled automatically. */ astBegin; /* Loop round all the input NDFs. */ trsys_last = ""; for( ifile = 1; ifile <= size && *status == SAI__OK; ifile++ ) { /* Obtain information about the current input NDF. */ smf_open_file( igrp, ifile, "READ", SMF__NOCREATE_DATA, &data, status ); /* Get a pointer to the header. */ hdr = data->hdr; /* Get the instrument. */ if( hdr->instrument == INST__SCUBA2 ) { subinst = smf_calc_subinst( hdr, status ); if( subinst == SMF__SUBINST_850 ) { inst = SMF__INST_SCUBA_2_850; } else { inst = SMF__INST_SCUBA_2_450; } } else if( hdr->instrument == INST__ACSIS ) { inst = SMF__INST_HARP; } else if( *status == SAI__OK ) { *status = SAI__ERROR; smf_smfFile_msg( data->file, "FILE", 1, "<unknown>" ); errRep( "", "No tiles are yet defined for the instrument that " "created ^FILE.", status ); } /* If this is the first file, it defines the instrument in use. */ if( ifile == 1 ) { instrument = inst; /* Get the parameters that define the layout of sky tiles for the instrument. */ smf_jsatiling( instrument, &skytiling, status ); /* Create a FrameSet describing the whole sky in which each pixel corresponds to a single tile. The current Frame is ICRS (RA,Dec) and the base Frame is grid coords in which each grid pixel corresponds to a single tile. */ smf_jsatile( 0, &skytiling, 0, NULL, &fs, NULL, lbnd, ubnd, status ); /* Allocate an image with one pixel for each tile, and fill it with zeros. */ dim[ 0 ] = ubnd[ 0 ] - lbnd[ 0 ] + 1; dim[ 1 ] = ubnd[ 1 ] - lbnd[ 1 ] + 1; hits = astCalloc( dim[0]*dim[1], sizeof( *hits ) ); /* Get the radius of the field of view in radians. */ fov = 0.5*(skytiling.fov*AST__DD2R)/3600.0; /* If this is not the first file, report an error if the instrument has changed... */ } else if( instrument != inst && *status == SAI__OK ) { smf_smfFile_msg( data->file, "FILE", 1, "<unknown>" ); errRep( "", "The file ^FILE was created by a different instrument " "to the previous files.", status ); } /* Re-map the current Frame of the hits map WCS FrameSet to be the tracking system used by the current file (if it has changed). */ trsys = sc2ast_convert_system( (hdr->allState)[0].tcs_tr_sys, status ); if( *status == SAI__OK && strcmp( trsys, trsys_last ) ) { astSetC( fs, "System", trsys ); trsys_last = trsys; frm = astGetFrame( fs, AST__CURRENT ); } /* Get the radius of the search circle. */ search = fov + sqrt( hdr->instap[ 0 ]*hdr->instap[ 0 ] + hdr->instap[ 1 ]*hdr->instap[ 1 ] ); /* Ensure our work arrays are big enough to hold the current file. */ trac1 = astGrow( trac1, 4*hdr->nframes, sizeof( *trac1 ) ); trac2 = astGrow( trac2, 4*hdr->nframes, sizeof( *trac2 ) ); gx = astGrow( gx, 4*hdr->nframes, sizeof( *gx ) ); gy = astGrow( gy, 4*hdr->nframes, sizeof( *gy ) ); /* Check pointers can be used safely. */ if( *status == SAI__OK ) { /* Loop round all time slices, getting the tracking coords at the corners of a box that enclose the field of view. */ p1 = trac1; p2 = trac2; state = hdr->allState; for( iframe = 0; iframe < hdr->nframes; iframe++,state++ ) { point1[ 0 ] = state->tcs_tr_ac1; point1[ 1 ] = state->tcs_tr_ac2; for( i = 0; i < 4; i++ ) { astOffset2( frm, point1, i*AST__DPIBY2, search, point2 ); *(p1++) = point2[ 0 ]; *(p2++) = point2[ 1 ]; } } /* Convert them to grid coords in the hits map. */ astTran2( fs, 4*hdr->nframes, trac1, trac2, 0, gx, gy ); /* Loop round them all again. Update the hits map to indicate how many points fall in each tile. */ px = gx; py = gy; for( iframe = 0; iframe < 4*hdr->nframes; iframe++,px++,py++ ) { ix = (int)( *px + 0.5 ) - 1; iy = (int)( *py + 0.5 ) - 1; hits[ ix + iy*dim[ 0 ] ]++; } } /* Close the current input data file. */ smf_close_file( &data, status); data = NULL; } /* Form a list of all the tiles that receive any data. */ ph = hits; for( iy = 0; iy < dim[ 1 ]; iy++ ) { for( ix = 0; ix < dim[ 0 ]; ix++,ph++ ) { if( *ph > 0 ) { tiles = astGrow( tiles, ++(*ntile), sizeof( *tiles ) ); if( *status == SAI__OK ) { tiles[ *ntile - 1 ] = smf_jsatilexy2i( ix, iy, &skytiling, status ); } } } } /* Free resources. */ hits = astFree( hits ); trac1 = astFree( trac1 ); trac2 = astFree( trac2 ); gx = astFree( gx ); gy = astFree( gy ); if( *status != SAI__OK ) { tiles = astFree( tiles ); *ntile = 0; } astEnd; return tiles; }
void smf_mapbounds( int fast, Grp *igrp, int size, const char *system, AstFrameSet *spacerefwcs, int alignsys, int *lbnd_out, int *ubnd_out, AstFrameSet **outframeset, int *moving, smfBox ** boxes, fts2Port fts_port, int *status ) { /* Local Variables */ AstSkyFrame *abskyframe = NULL; /* Output Absolute SkyFrame */ int actval; /* Number of parameter values supplied */ AstMapping *bolo2map = NULL; /* Combined mapping bolo->map coordinates, WCS->GRID Mapping from input WCS FrameSet */ smfBox *box = NULL; /* smfBox for current file */ smfData *data = NULL; /* pointer to SCUBA2 data struct */ double dlbnd[ 2 ]; /* Floating point lower bounds for output map */ drcntrl_bits drcntrl_mask = 0;/* Mask to use for DRCONTROL on this instrument */ double dubnd[ 2 ]; /* Floating point upper bounds for output map */ AstMapping *fast_map = NULL; /* Mapping from tracking to absolute map coords */ smfFile *file = NULL; /* SCUBA2 data file information */ int first; /* Is this the first good subscan ? */ AstFitsChan *fitschan = NULL;/* Fits channels to construct WCS header */ AstFrameSet *fs = NULL; /* A general purpose FrameSet pointer */ smfHead *hdr = NULL; /* Pointer to data header this time slice */ int i; /* Loop counter */ dim_t j; /* Loop counter */ AstSkyFrame *junksky = NULL; /* Unused SkyFrame argument */ dim_t k; /* Loop counter */ int lbnd0[ 2 ]; /* Defaults for LBND parameter */ double map_pa=0; /* Map PA in output coord system (rads) */ dim_t maxloop; /* Number of times to go round the time slice loop */ dim_t nbadt = 0; /* Number of bad time slices */ dim_t ngoodt = 0; /* Number of good time slices */ double par[7]; /* Projection parameters */ double shift[ 2 ]; /* Shifts from PIXEL to GRID coords */ AstMapping *oskymap = NULL; /* Mapping celestial->map coordinates, Sky <> PIXEL mapping in output FrameSet */ AstSkyFrame *oskyframe = NULL;/* Output SkyFrame */ char *refsys = NULL; /* Sky system from supplied reference FrameSet */ dim_t textreme[4]; /* Time index corresponding to minmax TCS posn */ AstFrame *skyin = NULL; /* Sky Frame in input FrameSet */ double skyref[ 2 ]; /* Values for output SkyFrame SkyRef attribute */ struct timeval tv1; /* Timer */ struct timeval tv2; /* Timer */ AstMapping *tmap; /* Temporary Mapping */ int trim; /* Trim borders of bad pixels from o/p image? */ int ubnd0[ 2 ]; /* Defaults for UBND parameter */ double x_array_corners[4]; /* X-Indices for corner bolos in array */ double x_map[4]; /* Projected X-coordinates of corner bolos */ double y_array_corners[4]; /* Y-Indices for corner pixels in array */ double y_map[4]; /* Projected X-coordinates of corner bolos */ /* Main routine */ if (*status != SAI__OK) return; /* Start a timer to see how long this takes */ smf_timerinit( &tv1, &tv2, status ); /* Initialize pointer to output FrameSet and moving-source flag */ *outframeset = NULL; *moving = 0; /* initialize double precision output bounds and the proj pars */ for( i = 0; i < 7; i++ ) par[ i ] = AST__BAD; dlbnd[ 0 ] = VAL__MAXD; dlbnd[ 1 ] = VAL__MAXD; dubnd[ 0 ] = VAL__MIND; dubnd[ 1 ] = VAL__MIND; /* If we have a supplied reference WCS we can use that directly without having to calculate it from the data. Replace the requested system with the system from the reference FrameSet (take a copy of the string since astGetC may re-use its buffer). */ if (spacerefwcs) { oskyframe = astGetFrame( spacerefwcs, AST__CURRENT ); int nc = 0; refsys = astAppendString( NULL, &nc, astGetC( oskyframe, "System" ) ); system = refsys; } /* Create array of returned smfBox structures and store a pointer to the next one to be initialised. */ *boxes = astMalloc( sizeof( smfBox ) * size ); box = *boxes; astBegin; /* Loop over all files in the Grp */ first = 1; for( i=1; i<=size; i++, box++ ) { /* Initialise the spatial bounds of section of the the output cube that is contributed to by the current ionput file. */ box->lbnd[ 0 ] = VAL__MAXD; box->lbnd[ 1 ] = VAL__MAXD; box->ubnd[ 0 ] = VAL__MIND; box->ubnd[ 1 ] = VAL__MIND; /* Read data from the ith input file in the group */ smf_open_file( NULL, igrp, i, "READ", SMF__NOCREATE_DATA, &data, status ); if (*status != SAI__OK) { msgSeti( "I", i ); errRep( "smf_mapbounds", "Could not open data file no ^I.", status ); break; } else { if( *status == SAI__OK ) { if( data->file == NULL ) { *status = SAI__ERROR; errRep( FUNC_NAME, "No smfFile associated with smfData.", status ); break; } else if( data->hdr == NULL ) { *status = SAI__ERROR; errRep( FUNC_NAME, "No smfHead associated with smfData.", status ); break; } else if( data->hdr->fitshdr == NULL ) { *status = SAI__ERROR; errRep( FUNC_NAME, "No FITS header associated with smfHead.", status ); break; } } } /* convenience pointers */ file = data->file; hdr = data->hdr; /* report name of the input file */ smf_smfFile_msg( file, "FILE", 1, "<unknown>" ); msgSeti("I", i); msgSeti("N", size); msgOutif(MSG__VERB, " ", "SMF_MAPBOUNDS: Processing ^I/^N ^FILE", status); /* Check that there are 3 pixel axes. */ if( data->ndims != 3 ) { smf_smfFile_msg( file, "FILE", 1, "<unknown>" ); msgSeti( "NDIMS", data->ndims ); *status = SAI__ERROR; errRep( FUNC_NAME, "^FILE has ^NDIMS pixel axes, should be 3.", status ); break; } /* Check that the data dimensions are 3 (for time ordered data) */ if( *status == SAI__OK ) { /* If OK Decide which detectors (GRID coord) to use for checking bounds, depending on the instrument in use. */ switch( hdr->instrument ) { case INST__SCUBA2: drcntrl_mask = DRCNTRL__POSITION; /* 4 corner bolometers of the subarray */ x_array_corners[0] = 1; x_array_corners[1] = 1; x_array_corners[2] = (data->dims)[0]; x_array_corners[3] = (data->dims)[0]; y_array_corners[0] = 1; y_array_corners[1] = (data->dims)[1]; y_array_corners[2] = 1; y_array_corners[3] = (data->dims)[1]; break; case INST__AZTEC: /* Rough guess for extreme bolometers around the edge */ x_array_corners[0] = 22; x_array_corners[1] = 65; x_array_corners[2] = 73; x_array_corners[3] = 98; y_array_corners[0] = 1; /* Always 1 for AzTEC */ y_array_corners[1] = 1; y_array_corners[2] = 1; y_array_corners[3] = 1; break; case INST__ACSIS: smf_find_acsis_corners( data, x_array_corners, y_array_corners, status); break; default: *status = SAI__ERROR; errRep(FUNC_NAME, "Don't know how to calculate mapbounds for data created with this instrument", status); } } if( *status == SAI__OK) { size_t goodidx = SMF__BADSZT; /* Need to build up a frameset based on good telescope position. We can not assume that we the first step will be a good TCS position so we look for one. If we can not find anything we skip to the next file. */ maxloop = (data->dims)[2]; for (j=0; j<maxloop; j++) { JCMTState state = (hdr->allState)[j]; if (state.jos_drcontrol >= 0 && state.jos_drcontrol & drcntrl_mask ) { /* bad TCS - so try again */ } else { /* Good tcs */ goodidx = j; break; } } if (goodidx == SMF__BADSZT) { smf_smfFile_msg( data->file, "FILE", 1, "<unknown>"); msgOutif( MSG__QUIET, "", "No good telescope positions found in file ^FILE. Ignoring", status ); smf_close_file( NULL, &data, status ); continue; } /* If we are dealing with the first good file, create the output SkyFrame. */ if( first ) { first = 0; /* Create output SkyFrame if it has not come from a reference */ if ( oskyframe == NULL ) { /* smf_tslice_ast only needs to get called once to set up framesets */ if( hdr->wcs == NULL ) { smf_tslice_ast( data, goodidx, 1, fts_port, status); } /* Retrieve input SkyFrame */ skyin = astGetFrame( hdr->wcs, AST__CURRENT ); smf_calc_skyframe( skyin, system, hdr, alignsys, &oskyframe, skyref, moving, status ); /* Get the orientation of the map vertical within the output celestial coordinate system. This is derived form the MAP_PA FITS header, which gives the orientation of the map vertical within the tracking system. */ map_pa = smf_calc_mappa( hdr, system, skyin, status ); /* Provide a sensible default for the pixel size based on wavelength */ par[4] = smf_calc_telres( hdr->fitshdr, status ); par[4] *= AST__DD2R/3600.0; par[5] = par[4]; /* Calculate the projection parameters. We do not enable autogrid determination for SCUBA-2 so we do not need to obtain all the data before calculating projection parameters. */ smf_get_projpar( oskyframe, skyref, *moving, 0, 0, NULL, 0, map_pa, par, NULL, NULL, status ); if (skyin) skyin = astAnnul( skyin ); /* If the output skyframe has been supplied, we still need to determine whether the source is moving or not, and set the reference position. */ } else { /* smf_tslice_ast only needs to get called once to set up framesets */ if( hdr->wcs == NULL ) { smf_tslice_ast( data, goodidx, 1, fts_port, status); } /* Retrieve input SkyFrame */ skyin = astGetFrame( hdr->wcs, AST__CURRENT ); smf_calc_skyframe( skyin, system, hdr, alignsys, &junksky, skyref, moving, status ); /* Store the sky reference position. If the target is moving, ensure the returned SkyFrame represents offsets from the reference position rather than absolute coords. */ astSetD( oskyframe, "SkyRef(1)", skyref[ 0 ] ); astSetD( oskyframe, "SkyRef(2)", skyref[ 1 ] ); if( *moving ) astSet( oskyframe, "SkyRefIs=Origin" ); /* Ensure the Epoch attribute in the map is set to the date of the first data in the map, rather than the date in supplied reference WCS. */ astSetD( oskyframe, "Epoch", astGetD( junksky, "Epoch" ) ); } if ( *outframeset == NULL && oskyframe != NULL && (*status == SAI__OK)){ /* Now created a spatial Mapping. Use the supplied reference frameset if supplied */ if (spacerefwcs) { oskymap = astGetMapping( spacerefwcs, AST__BASE, AST__CURRENT ); } else { /* Now populate a FitsChan with FITS-WCS headers describing the required tan plane projection. The longitude and latitude axis types are set to either (RA,Dec) or (AZ,EL) to get the correct handedness. */ fitschan = astFitsChan ( NULL, NULL, " " ); smf_makefitschan( astGetC( oskyframe, "System"), &(par[0]), &(par[2]), &(par[4]), par[6], fitschan, status ); astClear( fitschan, "Card" ); fs = astRead( fitschan ); /* Extract the output PIXEL->SKY Mapping. */ oskymap = astGetMapping( fs, AST__BASE, AST__CURRENT ); /* Tidy up */ fs = astAnnul( fs ); } /* Create the output FrameSet */ *outframeset = astFrameSet( astFrame(2, "Domain=GRID"), " " ); /* Now add the SkyFrame to it */ astAddFrame( *outframeset, AST__BASE, oskymap, oskyframe ); /* Now add a POLANAL Frame if required (i.e. if the input time series are POL-2 Q/U values). */ smf_addpolanal( *outframeset, hdr, status ); /* Invert the oskymap mapping */ astInvert( oskymap ); } /* End WCS FrameSet construction */ } /* Get a copy of the output SkyFrame and ensure it represents absolute coords rather than offset coords. */ abskyframe = astCopy( oskyframe ); astClear( abskyframe, "SkyRefIs" ); astClear( abskyframe, "AlignOffset" ); maxloop = (data->dims)[2]; if (fast) { /* For scan map we scan through looking for largest telescope moves. For dream/stare we just look at the start and end time slices to account for sky rotation. */ if (hdr->obsmode != SMF__OBS_SCAN) { textreme[0] = 0; textreme[1] = (data->dims)[2] - 1; maxloop = 2; } else { const char *tracksys; double *ac1list, *ac2list, *bc1list, *bc2list, *p1, *p2, *p3, *p4; double flbnd[4], fubnd[4]; JCMTState state; /* If the output and tracking systems are different, get a Mapping between them. */ tracksys = sc2ast_convert_system( (hdr->allState)[goodidx].tcs_tr_sys, status ); if( strcmp( system, tracksys ) ) { AstSkyFrame *tempsf = astCopy( abskyframe ); astSetC( tempsf, "System", tracksys ); AstFrameSet *tempfs = astConvert( tempsf, abskyframe, "" ); tmap = astGetMapping( tempfs, AST__BASE, AST__CURRENT ); fast_map = astSimplify( tmap ); tmap = astAnnul( tmap ); tempsf = astAnnul( tempsf ); tempfs = astAnnul( tempfs ); } else { fast_map = NULL; } /* Copy all ac1/2 positions into two array, and transform them from tracking to absolute output sky coords. */ ac1list = astMalloc( maxloop*sizeof( *ac1list ) ); ac2list = astMalloc( maxloop*sizeof( *ac2list ) ); if( *status == SAI__OK ) { p1 = ac1list; p2 = ac2list; for( j = 0; j < maxloop; j++ ) { state = (hdr->allState)[ j ]; *(p1++) = state.tcs_tr_ac1; *(p2++) = state.tcs_tr_ac2; } if( fast_map ) astTran2( fast_map, maxloop, ac1list, ac2list, 1, ac1list, ac2list ); } /* If the target is moving, we need to adjust these ac1/2 values to represent offsets from the base position. */ if( *moving ) { /* Copy all bc1/2 positions into two arrays. */ bc1list = astMalloc( maxloop*sizeof( *bc1list ) ); bc2list = astMalloc( maxloop*sizeof( *bc2list ) ); if( *status == SAI__OK ) { p1 = bc1list; p2 = bc2list; for( j = 0; j < maxloop; j++ ) { state = (hdr->allState)[ j ]; *(p1++) = state.tcs_tr_bc1; *(p2++) = state.tcs_tr_bc2; } /* Transform them from tracking to absolute output sky coords. */ if( fast_map ) astTran2( fast_map, maxloop, bc1list, bc2list, 1, bc1list, bc2list ); /* Replace each ac1/2 position with the offsets from the corresponding base position. */ p1 = bc1list; p2 = bc2list; p3 = ac1list; p4 = ac2list; for( j = 0; j < maxloop; j++ ) { smf_offsets( *(p1++), *(p2++), p3++, p4++, status ); } } /* We no longer need the base positions. */ bc1list = astFree( bc1list ); bc2list = astFree( bc2list ); } /* Transform the ac1/2 position from output sky coords to output pixel coords. */ astTran2( oskymap, maxloop, ac1list, ac2list, 1, ac1list, ac2list ); /* Find the bounding box containing these pixel coords and the time slices at which the boresight touches each edge of this box. */ flbnd[ 0 ] = VAL__MAXD; flbnd[ 1 ] = VAL__MAXD; fubnd[ 0 ] = VAL__MIND; fubnd[ 1 ] = VAL__MIND; for( j = 0; j < 4; j++ ) textreme[ j ] = (dim_t) VAL__BADI; if( *status == SAI__OK ) { p1 = ac1list; p2 = ac2list; for( j = 0; j < maxloop; j++,p1++,p2++ ) { if( *p1 != VAL__BADD && *p2 != VAL__BADD ){ if ( *p1 < flbnd[0] ) { flbnd[0] = *p1; textreme[0] = j; } if ( *p2 < flbnd[1] ) { flbnd[1] = *p2; textreme[1] = j; } if ( *p1 > fubnd[0] ) { fubnd[0] = *p1; textreme[2] = j; } if ( *p2 > fubnd[1] ) { fubnd[1] = *p2; textreme[3] = j; } } } } maxloop = 4; msgSetd("X1", textreme[0]); msgSetd("X2", textreme[1]); msgSetd("X3", textreme[2]); msgSetd("X4", textreme[3]); msgOutif( MSG__DEBUG, " ", "Extrema time slices are ^X1, ^X2, ^X3 and ^X4", status); ac1list = astFree( ac1list ); ac2list = astFree( ac2list ); } } /* Get the astrometry for all the relevant time slices in this data file */ for( j=0; j<maxloop; j++ ) { dim_t ts; /* Actual time slice to use */ /* if we are doing the fast loop, we need to read the time slice index from textreme. Else we just use the index */ if (fast) { /* get the index but make sure it is good */ ts = textreme[j]; if (ts == (dim_t)VAL__BADI) continue; } else { ts = j; } /* Calculate the bolo to map-pixel transformation for this tslice */ bolo2map = smf_rebin_totmap( data, ts, abskyframe, oskymap, *moving, fts_port, status ); if ( *status == SAI__OK ) { /* skip if we did not get a mapping this time round */ if (!bolo2map) continue; /* Check corner pixels in the array for their projected extent on the sky to set the pixel bounds */ astTran2( bolo2map, 4, x_array_corners, y_array_corners, 1, x_map, y_map ); /* Update min/max for this time slice */ for( k=0; k<4; k++ ) { if( x_map[k] != AST__BAD && y_map[k] != AST__BAD ) { if( x_map[k] < dlbnd[0] ) dlbnd[0] = x_map[k]; if( y_map[k] < dlbnd[1] ) dlbnd[1] = y_map[k]; if( x_map[k] > dubnd[0] ) dubnd[0] = x_map[k]; if( y_map[k] > dubnd[1] ) dubnd[1] = y_map[k]; if( x_map[k] < box->lbnd[0] ) box->lbnd[0] = x_map[k]; if( y_map[k] < box->lbnd[1] ) box->lbnd[1] = y_map[k]; if( x_map[k] > box->ubnd[0] ) box->ubnd[0] = x_map[k]; if( y_map[k] > box->ubnd[1] ) box->ubnd[1] = y_map[k]; } else if( *status == SAI__OK ) { *status = SAI__ERROR; errRep( FUNC_NAME, "Extreme positions are bad.", status ); break; } } } /* Explicitly annul these mappings each time slice for reduced memory usage */ if (bolo2map) bolo2map = astAnnul( bolo2map ); if (fs) fs = astAnnul( fs ); /* Break out of loop over time slices if bad status */ if (*status != SAI__OK) goto CLEANUP; } /* Annul any remaining Ast objects before moving on to the next file */ if (fs) fs = astAnnul( fs ); if (bolo2map) bolo2map = astAnnul( bolo2map ); } /* Close the data file */ smf_close_file( NULL, &data, status); /* Break out of loop over data files if bad status */ if (*status != SAI__OK) goto CLEANUP; } /* make sure we got values - should not be possible with good status */ if (dlbnd[0] == VAL__MAXD || dlbnd[1] == VAL__MAXD) { if (*status == SAI__OK) { *status = SAI__ERROR; errRep( " ", "Unable to find any valid map bounds", status ); } } if (nbadt > 0) { msgOutf( "", " Processed %zu time slices to calculate bounds," " of which %zu had bad telescope data and were skipped", status, (size_t)(ngoodt+nbadt), (size_t)nbadt ); } /* If spatial reference wcs was supplied, store par values that result in no change to the pixel origin. */ if( spacerefwcs ){ par[ 0 ] = 0.5; par[ 1 ] = 0.5; } /* Need to re-align with the interim GRID coordinates */ lbnd_out[0] = ceil( dlbnd[0] - par[0] + 0.5 ); ubnd_out[0] = ceil( dubnd[0] - par[0] + 0.5 ); lbnd_out[1] = ceil( dlbnd[1] - par[1] + 0.5 ); ubnd_out[1] = ceil( dubnd[1] - par[1] + 0.5 ); /* Do the same with the individual input file bounding boxes */ box = *boxes; for (i = 1; i <= size; i++, box++) { box->lbnd[0] = ceil( box->lbnd[0] - par[0] + 0.5); box->ubnd[0] = ceil( box->ubnd[0] - par[0] + 0.5); box->lbnd[1] = ceil( box->lbnd[1] - par[1] + 0.5); box->ubnd[1] = ceil( box->ubnd[1] - par[1] + 0.5); } /* Apply a ShiftMap to the output FrameSet to re-align the GRID coordinates */ shift[0] = 2.0 - par[0] - lbnd_out[0]; shift[1] = 2.0 - par[1] - lbnd_out[1]; astRemapFrame( *outframeset, AST__BASE, astShiftMap( 2, shift, " " ) ); /* Set the dynamic defaults for lbnd/ubnd */ lbnd0[ 0 ] = lbnd_out[ 0 ]; lbnd0[ 1 ] = lbnd_out[ 1 ]; parDef1i( "LBND", 2, lbnd0, status ); ubnd0[ 0 ] = ubnd_out[ 0 ]; ubnd0[ 1 ] = ubnd_out[ 1 ]; parDef1i( "UBND", 2, ubnd0, status ); parGet1i( "LBND", 2, lbnd_out, &actval, status ); if( actval == 1 ) lbnd_out[ 1 ] = lbnd_out[ 0 ]; parGet1i( "UBND", 2, ubnd_out, &actval, status ); if( actval == 1 ) ubnd_out[ 1 ] = ubnd_out[ 0 ]; /* Ensure the bounds are the right way round. */ if( lbnd_out[ 0 ] > ubnd_out[ 0 ] ) { int itmp = lbnd_out[ 0 ]; lbnd_out[ 0 ] = ubnd_out[ 0 ]; ubnd_out[ 0 ] = itmp; } if( lbnd_out[ 1 ] > ubnd_out[ 1 ] ) { int itmp = lbnd_out[ 1 ]; lbnd_out[ 1 ] = ubnd_out[ 1 ]; ubnd_out[ 1 ] = itmp; } /* If borders of bad pixels are being trimmed from the output image, then do not allow the user-specified bounds to extend outside the default bounding box (since we know that the default bounding box encloses all available data). */ parGet0l( "TRIM", &trim, status ); if( trim ) { if( lbnd_out[ 0 ] < lbnd0[ 0 ] ) lbnd_out[ 0 ] = lbnd0[ 0 ]; if( lbnd_out[ 1 ] < lbnd0[ 1 ] ) lbnd_out[ 1 ] = lbnd0[ 1 ]; if( ubnd_out[ 0 ] > ubnd0[ 0 ] ) ubnd_out[ 0 ] = ubnd0[ 0 ]; if( ubnd_out[ 1 ] > ubnd0[ 1 ] ) ubnd_out[ 1 ] = ubnd0[ 1 ]; } /* Modify the returned FrameSet to take account of the new pixel origin. */ shift[ 0 ] = lbnd0[ 0 ] - lbnd_out[ 0 ]; shift[ 1 ] = lbnd0[ 1 ] - lbnd_out[ 1 ]; if( shift[ 0 ] != 0.0 || shift[ 1 ] != 0.0 ) { astRemapFrame( *outframeset, AST__BASE, astShiftMap( 2, shift, " " ) ); } /* Report the pixel bounds of the cube. */ if( *status == SAI__OK ) { msgOutif( MSG__NORM, " ", " ", status ); msgSeti( "XL", lbnd_out[ 0 ] ); msgSeti( "YL", lbnd_out[ 1 ] ); msgSeti( "XU", ubnd_out[ 0 ] ); msgSeti( "YU", ubnd_out[ 1 ] ); msgOutif( MSG__NORM, " ", " Output map pixel bounds: ( ^XL:^XU, ^YL:^YU )", status ); if( ( ubnd_out[ 0 ] - lbnd_out[ 0 ] + 1 ) > MAX_DIM || ( ubnd_out[ 1 ] - lbnd_out[ 1 ] + 1 ) > MAX_DIM ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": The map is too big. Check your list of input " "data files does not include widely separated observations.", status ); } } /* If no error has occurred, export the returned FrameSet pointer from the current AST context so that it will not be annulled when the AST context is ended. Otherwise, ensure a null pointer is returned. */ if( *status == SAI__OK ) { astExport( *outframeset ); } else { *outframeset = astAnnul( *outframeset ); } msgOutiff( SMF__TIMER_MSG, "", "Took %.3f s to calculate map bounds", status, smf_timerupdate( &tv1, &tv2, status ) ); /* Clean Up */ CLEANUP: if (*status != SAI__OK) { errRep(FUNC_NAME, "Unable to determine map bounds", status); } if (oskymap) oskymap = astAnnul( oskymap ); if (bolo2map) bolo2map = astAnnul( bolo2map ); if (fitschan) fitschan = astAnnul( fitschan ); if( data != NULL ) smf_close_file( NULL, &data, status ); refsys = astFree( refsys ); astEnd; }
void smf_uncalc_iqu( ThrWorkForce *wf, smfData *data, double *idata, double *qdata, double *udata, 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 ); if( !strcmp( headval, "FPLANE" ) ) { ipolcrd = 0; } else if( !strcmp( headval, "AZEL" ) ) { ipolcrd = 1; } else if( !strcmp( headval, "TRACKING" ) ) { ipolcrd = 2; } else if( *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 ); } /* 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->ipolcrd = ipolcrd; pdata->old = old; pdata->ntslice = ntslice; /* 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_mapbounds_approx( Grp *igrp, size_t index, char *system, int *lbnd_out, int *ubnd_out, AstFrameSet **outframeset, int *moving, int *status ) { /* Local variables */ smfData *data = NULL; /* pointer to SCUBA2 data struct */ int dxpix; /* Map X offset in pixels */ int dypix; /* Map Y offset in pixels */ smfFile *file = NULL; /* SCUBA2 data file information */ AstFitsChan *fitschan = NULL;/* Fits channels to construct WCS header */ AstFrameSet *fs = NULL; /* A general purpose FrameSet pointer */ smfHead *hdr = NULL; /* Pointer to data header this time slice */ double hghtbox; /* Map height in arcsec */ int hghtpix; /* RA-Dec map height in pixels */ int i; /* loop counter */ dim_t k; /* Loop counter */ double maphght = 0.0; /* Map height in radians */ double mappa = 0.0; /* Map position angle in radians */ double mapwdth = 0.0; /* Map width in radians */ double mapx; /* Map X offset in radians */ double mapy; /* Map Y offset in radians */ double par[7]; /* Projection parameters */ double pixsize = 0.0; /* Requested pixel size */ double shift[ 2 ]; /* Shifts from PIXEL to GRID coords */ AstMapping *sky2map = NULL; /* Mapping celestial->map coordinates */ AstSkyFrame *skyframe = NULL;/* Output SkyFrame */ AstFrame *skyin = NULL; /* Sky Frame in input FrameSet */ double skyref[ 2 ]; /* Values for output SkyFrame SkyRef attribute */ AstFrameSet *swcsin = NULL; /* FrameSet describing input WCS */ int temp; /* Temporary variable */ double wdthbox; /* Map width in arcsec */ int wdthpix; /* RA-Dec map width in pixels */ double x_array_corners[4]; /* X-Indices for corner bolos in array */ double y_array_corners[4]; /* Y-Indices for corner pixels in array */ /* Main routine */ if (*status != SAI__OK) return; /* Begin an AST context to ensure that all AST objects are annuled before returning to caller */ astBegin; /* Initialize output frameset pointer to NULL */ *outframeset = NULL; for( i = 0; i < 7; i++ ) par[ i ] = AST__BAD; /* Read data from the given input file in the group - note index should be 1 as we use the first file in the Grp to define the map bounds */ smf_open_file( igrp, index, "READ", SMF__NOCREATE_DATA, &data, status ); /* Simply abort if it is not a scan */ if (*status == SAI__OK && data->hdr->obsmode != SMF__OBS_SCAN) { *status = SAI__ERROR; errRep(" ", "Can not call smf_mapbounds_approx with non-scan observation" " (possible programming error)", status); goto CLEANUP; } /* Retrieve file name for use feedback */ file = data->file; smf_smfFile_msg( file, "FILE", 1, "<unknown>" ); if( *status == SAI__OK ) { msgOutif(MSG__VERB, " ", "SMF_MAPBOUNDS_APPROX: Processing ^FILE", status); } else { errRep( "smf_mapbounds_approx", "Couldn't open input file, ^FILE", status ); } /* Check that the data dimensions are 3 (for time ordered data) */ if( *status == SAI__OK ) { if( data->ndims != 3 ) { smf_smfFile_msg( file, "FILE", 1, "<unknown>" ); msgSeti("THEDIMS", data->ndims); *status = SAI__ERROR; errRep("smf_mapbounds_approx", "^FILE data has ^THEDIMS dimensions, should be 3.", status); } } /* Construct the WCS for the first time slice in this file */ smf_tslice_ast( data, 1, 1, NO_FTS, status); /* Retrieve header for later constructing output WCS */ if( *status == SAI__OK) { hdr = data->hdr; swcsin = hdr->wcs; /* Calculate default pixel size */ pixsize = smf_calc_telres( hdr->fitshdr, status ); /* Get the user defined pixel size - we trust that smf_get_projpar will also read PIXSIZE and get the same answer. We pre-fill par[] to allow PIXSIZE=! to accept the dynamic default in both places.*/ parGdr0d( "PIXSIZE", pixsize, 0, 60, 1, &pixsize, status ); par[4] = pixsize*AST__DD2R/3600.0; par[5] = par[4]; /* Retrieve input SkyFrame */ skyin = astGetFrame( swcsin, AST__CURRENT ); /* Retrieve map height and width from header - will be undef for non-scan so set up defaults first. */ mapwdth = 0.0; maphght = 0.0; smf_getfitsd( hdr, "MAP_WDTH", &mapwdth, status ); smf_getfitsd( hdr, "MAP_HGHT", &maphght, status ); /* Make an approximation if map height and width are not set - note that this should ONLY apply for non-scan mode data */ if ( !mapwdth || !maphght ) { if (*status == SAI__OK) { *status = SAI__ERROR; errRep(" ", "MAP_WDTH and MAP_HGHT must be > 0", status); goto CLEANUP; } } mapx = 0.0; /* Used if the FITS keyword values are undefed */ mapy = 0.0; smf_getfitsd( hdr, "MAP_X", &mapx, status ); smf_getfitsd( hdr, "MAP_Y", &mapy, status ); /* Convert map Position Angle to radians */ mappa = 0.0; smf_fits_getD( hdr, "MAP_PA", &mappa, status ); mappa *= AST__DD2R; /* Calculate size of output map in pixels */ /* Note: this works for the simulator... */ wdthbox = mapwdth*fabs(cos(mappa)) + maphght*fabs(sin(mappa)); hghtbox = maphght*fabs(cos(mappa)) + mapwdth*fabs(sin(mappa)); wdthpix = (int) ( wdthbox / pixsize); hghtpix = (int) ( wdthbox / pixsize); dxpix = (int) (mapx / pixsize); dypix = (int) (mapy / pixsize); /* Get the offsets for each corner of the array */ temp = (wdthpix - 1) / 2; x_array_corners[0] = dxpix - temp; x_array_corners[1] = dxpix - temp; x_array_corners[2] = dxpix + temp; x_array_corners[3] = dxpix + temp; temp = (hghtpix - 1) / 2; y_array_corners[0] = dypix - temp; y_array_corners[1] = dypix + temp; y_array_corners[2] = dypix - temp; y_array_corners[3] = dypix + temp; lbnd_out[0] = x_array_corners[0]; ubnd_out[0] = x_array_corners[0]; lbnd_out[1] = y_array_corners[0]; ubnd_out[1] = y_array_corners[0]; /* Update min/max */ for( k=0; k<4; k++ ) { if( x_array_corners[k] < lbnd_out[0] ) lbnd_out[0] = x_array_corners[k]; if( y_array_corners[k] < lbnd_out[1] ) lbnd_out[1] = y_array_corners[k]; if( x_array_corners[k] > ubnd_out[0] ) ubnd_out[0] = x_array_corners[k]; if( y_array_corners[k] > ubnd_out[1] ) ubnd_out[1] = y_array_corners[k]; } } else { goto CLEANUP; } /* Now create the output FrameSet. */ smf_calc_skyframe( skyin, system, hdr, 0, &skyframe, skyref, moving, status ); /* Get the orientation of the map vertical within the output celestial coordinate system. This is derived form the MAP_PA FITS header, which gives the orientation of the map vertical within the tracking system. */ mappa = smf_calc_mappa( hdr, system, skyin, status ); /* Calculate the projection parameters. We do not enable autogrid determination for SCUBA-2 so we do not need to obtain all the data before calculating projection parameters. */ smf_get_projpar( skyframe, skyref, *moving, 0, 0, NULL, 0, mappa, par, NULL, NULL, status ); /* Now populate a FitsChan with FITS-WCS headers describing the required tan plane projection. The longitude and latitude axis types are set to either (RA,Dec) or (AZ,EL) to get the correct handedness. */ fitschan = astFitsChan ( NULL, NULL, " " ); smf_makefitschan( astGetC( skyframe, "System"), &(par[0]), &(par[2]), &(par[4]), par[6], fitschan, status ); astClear( fitschan, "Card" ); fs = astRead( fitschan ); /* Extract the output PIXEL->SKY Mapping - note this is will be inverted later to create the sk2map mapping */ sky2map = astGetMapping( fs, AST__BASE, AST__CURRENT ); /* Create the output FrameSet */ *outframeset = astFrameSet( astFrame(2, "Domain=GRID"), " " ); /* Now add the SkyFrame to it */ astAddFrame( *outframeset, AST__BASE, sky2map, skyframe ); /* Apply a ShiftMap to the output FrameSet to re-align the GRID coordinates */ shift[0] = -lbnd_out[0]; shift[1] = -lbnd_out[1]; astRemapFrame( *outframeset, AST__BASE, astShiftMap( 2, shift, " " ) ); astExport( *outframeset ); /* Report the pixel bounds of the cube. */ if( *status == SAI__OK ) { msgOutif( MSG__NORM, " ", " ", status ); msgSeti( "XL", lbnd_out[ 0 ] ); msgSeti( "YL", lbnd_out[ 1 ] ); msgSeti( "XU", ubnd_out[ 0 ] ); msgSeti( "YU", ubnd_out[ 1 ] ); msgOutif( MSG__NORM, " ", " Output map pixel bounds: ( ^XL:^XU, ^YL:^YU )", status ); } /* Change the pixel bounds to be consistent with the new CRPIX */ ubnd_out[0] -= lbnd_out[0]-1; lbnd_out[0] = 1; ubnd_out[1] -= lbnd_out[1]-1; lbnd_out[1] = 1; /* Clean Up */ CLEANUP: if (*status != SAI__OK) { errRep(FUNC_NAME, "Unable to determine map bounds", status); } if( data != NULL ) smf_close_file( &data, status); astEnd; }
void smurf_unmakecube( int *status ) { /* Local Variables */ AstFrame *tfrm = NULL; /* Current Frame from input WCS */ AstFrameSet *wcsin = NULL; /* WCS Frameset for input cube */ AstMapping *tmap = NULL; /* Base->current Mapping from input WCS */ AstSkyFrame *iskyfrm = NULL; /* SkyFrame from the input WCS Frameset */ Grp *detgrp = NULL; /* Group of detector names */ Grp *igrp1 = NULL; /* Group of input sky cube files */ Grp *igrp2 = NULL; /* Group of input template files */ Grp *ogrp = NULL; /* Group containing output file */ NdgProvenance *oprov = NULL;/* Provenance for the output NDF */ SkyCube *sky_cubes = NULL; /* Pointer to array of sky cube descriptions */ SkyCube *skycube = NULL; /* Pointer to next sky cube description */ char pabuf[ 10 ]; /* Text buffer for parameter value */ double params[ 4 ]; /* astResample parameters */ int axes[ 2 ]; /* Indices of selected axes */ int blank; /* Was a blank line just output? */ int flag; /* Was the group expression flagged? */ int ifile; /* Input file index */ int interp = 0; /* Pixel interpolation method */ int iskycube; /* Index of current sky cube */ int nel; /* Number of elements in 3D array */ int nparam = 0; /* No. of parameters required for interpolation scheme */ int ondf; /* Output time series NDF identifier */ int outax[ 2 ]; /* Indices of corresponding output axes */ int overlap; /* Does time series overlap sky cube? */ int sdim[3]; /* Array of significant pixel axes */ int usedetpos; /* Should the detpos array be used? */ size_t ndet; /* Number of detectors supplied for "DETECTORS" */ size_t nskycube; /* Number of supplied sky cubes */ size_t outsize; /* Number of files in output group */ size_t size; /* Number of files in input group */ smfData *data = NULL; /* Pointer to data struct */ void *in_data = NULL; /* Pointer to the input cube data array */ void *out_data = NULL; /* Pointer to the output cube data array */ #if defined(FPTRAP) feenableexcept(FE_DIVBYZERO|FE_INVALID|FE_OVERFLOW); #endif /* Check inherited status */ if( *status != SAI__OK ) return; /* We have not yet displayed a blank line on stdout. */ blank = 0; /* Begin an AST context */ astBegin; /* Begin an NDF context. */ ndfBegin(); /* Get a group holding the input sky cubes. */ kpg1Rgndf( "IN", 0, 1, "", &igrp1, &nskycube, status ); /* Create an array of structures to hold information about each input sky cube. */ sky_cubes = astMalloc( sizeof( SkyCube )*(size_t) nskycube ); /* Store a description of each sky cube. */ if( sky_cubes ) { for( iskycube = 0; iskycube < nskycube; iskycube++ ) { skycube = sky_cubes + iskycube; /* Get an NDF identifier for the next sky cube. */ ndgNdfas( igrp1, iskycube + 1, "READ", &(skycube->indf), status ); /* Get the WCS FrameSet from the sky cube, together with its pixel index bounds. */ kpg1Asget( skycube->indf, 3, 0, 1, 1, sdim, skycube->slbnd, skycube->subnd, &wcsin, status ); /* Get the base->current Mapping from the input WCS FrameSet, and split it into two Mappings; one (iskymap) that maps the first 2 GRID axes into celestial sky coordinates, and one (ispecmap) that maps the third GRID axis into a spectral coordinate. Also extract the SpecFrame and SkyFrame from the current Frame. */ tmap = astGetMapping( wcsin, AST__BASE, AST__CURRENT ); tfrm = astGetFrame( wcsin, AST__CURRENT ); axes[ 0 ] = 1; axes[ 1 ] = 2; astMapSplit( tmap, 2, axes, outax, &(skycube->iskymap) ); iskyfrm = astPickAxes( tfrm, 2, outax, NULL ); axes[ 0 ] = 3; astMapSplit( tmap, 1, axes, outax, &(skycube->ispecmap) ); skycube->ispecfrm = astPickAxes( tfrm, 1, outax, NULL ); /* Create a copy of "iskyfrm" representing absolute coords rather than offsets. We assume the target is moving if the cube represents offsets. */ skycube->abskyfrm = astCopy( iskyfrm ); astClear( skycube->abskyfrm, "SkyRefIs" ); skycube->moving = ( *status == SAI__OK && !strcmp( astGetC( iskyfrm, "SkyRefIs" ), "Origin" ) ) ? 1 : 0; /* Invert the Mappings (for the convenience of smf_resamplecube), so that they go from current Frame to grid axis. */ astInvert( skycube->ispecmap ); astInvert( skycube->iskymap ); /* For efficiency, annul manually the unneeded AST objects created in this loop. */ wcsin = astAnnul( wcsin ); tmap = astAnnul( tmap ); tfrm = astAnnul( tfrm ); iskyfrm = astAnnul( iskyfrm ); } } /* See if the detector positions are to be read from the RECEPPOS array in the template NDFs. Otherwise, they are calculated on the basis of the FPLANEX/Y arrays. */ parGet0l( "USEDETPOS", &usedetpos, status ); /* Get the detectors to use. If a null value is supplied, annull the error. Otherwise, make the group case insensitive. */ detgrp = NULL; if( *status == SAI__OK ) { kpg1Gtgrp( "DETECTORS", &detgrp, &ndet, status ); if( *status == PAR__NULL ) { errAnnul( status ); if (detgrp) { grpDelet( &detgrp, status ); } } else { grpSetcs( detgrp, 0, status ); } } /* Get the pixel interpolation scheme to use. */ parChoic( "INTERP", "NEAREST", "NEAREST,LINEAR,SINC," "SINCSINC,SINCCOS,SINCGAUSS,SOMB,SOMBCOS", 1, pabuf, 10, status ); if( !strcmp( pabuf, "NEAREST" ) ) { interp = AST__NEAREST; nparam = 0; } else if( !strcmp( pabuf, "LINEAR" ) ) { interp = AST__LINEAR; nparam = 0; } else if( !strcmp( pabuf, "SINC" ) ) { interp = AST__SINC; nparam = 1; } else if( !strcmp( pabuf, "SINCSINC" ) ) { interp = AST__SINCSINC; nparam = 2; } else if( !strcmp( pabuf, "SINCCOS" ) ) { interp = AST__SINCCOS; nparam = 2; } else if( !strcmp( pabuf, "SINCGAUSS" ) ) { interp = AST__SINCGAUSS; nparam = 2; } else if( !strcmp( pabuf, "SOMB" ) ) { interp = AST__SOMB; nparam = 1; } else if( !strcmp( pabuf, "SOMBCOS" ) ) { interp = AST__SOMBCOS; nparam = 2; } else if( *status == SAI__OK ) { nparam = 0; *status = SAI__ERROR; msgSetc( "V", pabuf ); errRep( "", "Support not available for INTERP = ^V (programming " "error)", status ); } /* Get an additional parameter vector if required. */ if( nparam > 0 ) parExacd( "PARAMS", nparam, params, status ); /* Get a group of reference time series files to use as templates for the output time series files.*/ ndgAssoc( "REF", 1, &igrp2, &size, &flag, status ); /* Create a group holding the names of the corresponding output NDFs. */ ndgCreat ( "OUT", igrp2, &ogrp, &outsize, &flag, status ); if( outsize != size && *status == SAI__OK ) { *status = SAI__ERROR; msgSeti( "O", outsize ); msgSeti( "I", size ); errRep( "", "Numbers of input reference cubes (^I) and output " "cubes (^O) differ.", status ); } /* Loop round all the template time series files. */ for( ifile = 1; ifile <= size && *status == SAI__OK; ifile++ ) { /* Start a new NDF context. */ ndfBegin(); /* Obtain information about the current template NDF, but do not map the arrays. */ smf_open_file( igrp2, ifile, "READ", SMF__NOCREATE_DATA, &data, status ); /* Issue a suitable message and abort if anything went wrong. */ if( *status != SAI__OK ) { errRep( FUNC_NAME, "Could not open input template file.", status ); break; } else { if( data->file == NULL ) { *status = SAI__ERROR; errRep( FUNC_NAME, "No smfFile associated with smfData.", status ); break; } else if( data->hdr == NULL ) { *status = SAI__ERROR; errRep( FUNC_NAME, "No smfHead associated with smfData.", status ); break; } } /* Report the name of the input template. */ smf_smfFile_msg( data->file, "FILE", 1, "<unknown>" ); msgSeti( "THISFILE", ifile ); msgSeti( "NUMFILES", size ); msgOutif( MSG__NORM, " ", "Simulating ^THISFILE/^NUMFILES ^FILE", status ); /* Create the output NDF by propagation from the input template NDF. Everything is copied except for the array components and any PROVENANCE extension. */ ndgNdfpr( data->file->ndfid, "TITLE,LABEL,UNITS,AXIS,WCS,HISTORY," "NOEXTENSION(PROVENANCE)", ogrp, ifile, &ondf, status ); /* Ensure the output NDF has a history component. */ ndfHcre( ondf, status ); /* Get a pointer to the mapped output data array. Set all values bad. */ ndfMap( ondf, "DATA", "_REAL", "WRITE/BAD", &out_data, &nel, status ); /* If the detector positions are to calculated on the basis of FPLANEX/Y rather than detpos, then free the detpos array in the templates smfHead structure. This will cause smf_tslice_ast to use the fplanex/y values. */ if( !usedetpos && data->hdr->detpos ) { astFree( (double *) data->hdr->detpos ); data->hdr->detpos = NULL; } /* Get a pointer to a structure holding provenance information for the output time series. */ oprov = ndgReadProv( ondf, "SMURF:UNMAKECUBE", status ); /* Record details of the template in the provenance structure for the output time series. */ ndgPutProv( oprov, data->file->ndfid, NULL, 0, status ); /* Loop round all input sky cubes. */ for( iskycube = 0; iskycube < nskycube; iskycube++ ) { skycube = sky_cubes + iskycube; /* Record details of the input cube in the provenance extension of the output time series. */ ndgPutProv( oprov, skycube->indf, NULL, 0, status ); /* See if the current time series overlaps the current sky cube. */ smf_resampcube( data, skycube->abskyfrm, skycube->iskymap, skycube->ispecfrm, skycube->ispecmap, detgrp, skycube->moving, skycube->slbnd, skycube->subnd, interp, params, NULL, NULL, &overlap, status ); /* If not, pass on to the next sky cube. */ if( overlap ) { /* Report the name of the sky cube. */ ndfMsg( "NDF", skycube->indf ); msgOutif( MSG__NORM, " ", " Re-sampling ^NDF", status ); /* Map the data array in the current sky cube. */ ndfMap( skycube->indf, "DATA", "_REAL", "READ", &in_data, &nel, status ); /* Resample the cube data into the output time series. */ smf_resampcube( data, skycube->abskyfrm, skycube->iskymap, skycube->ispecfrm, skycube->ispecmap, detgrp, skycube->moving, skycube->slbnd, skycube->subnd, interp, params, in_data, out_data, &overlap, status ); /* Unmap the data array. */ ndfUnmap( skycube->indf, "DATA", status ); } } /* Write the provenance structure to the output NDF, and then free it. */ ndgWriteProv( oprov, ondf, 1, status ); oprov =ndgFreeProv( oprov, status ); /* Close the input time series file. */ if( data != NULL ) { smf_close_file( &data, status ); data = NULL; } /* End the NDF context. */ ndfEnd( status ); } /* Close any input data file that is still open due to an early exit from the above loop. */ if( data != NULL ) { smf_close_file( &data, status ); data = NULL; } /* Free remaining resources. */ if( detgrp != NULL) grpDelet( &detgrp, status); if( igrp1 != NULL) grpDelet( &igrp1, status); if( igrp2 != NULL) grpDelet( &igrp2, status); if( ogrp != NULL) grpDelet( &ogrp, status); sky_cubes = astFree( sky_cubes ); /* End the NDF context. */ ndfEnd( status ); /* End the tile's AST context. */ astEnd; /* Issue a status indication.*/ if( *status == SAI__OK ) { msgOutif(MSG__VERB," ",TASK_NAME " succeeded, time series written.", status); } else { msgOutif(MSG__VERB," ",TASK_NAME " failed.", status); } }
void smf_find_science(const Grp * ingrp, Grp **outgrp, int reverttodark, Grp **darkgrp, Grp **flatgrp, int reducedark, int calcflat, smf_dtype darktype, smfArray ** darks, smfArray **fflats, AstKeyMap ** heateffmap, double * meanstep, int * status ) { smfSortInfo *alldarks; /* array of sort structs for darks */ smfSortInfo *allfflats; /* array of fast flat info */ Grp * dgrp = NULL; /* Internal dark group */ double duration_darks = 0.0; /* total duration of all darks */ double duration_sci = 0.0; /* Duration of all science observations */ size_t dkcount = 0; /* Dark counter */ size_t ffcount = 0; /* Fast flat counter */ Grp * fgrp = NULL; /* Fast flat group */ size_t i; /* loop counter */ smfData *infile = NULL; /* input file */ size_t insize; /* number of input files */ size_t nsteps_dark = 0; /* Total number of steps for darks */ size_t nsteps_sci = 0; /* Total number of steps for science */ AstKeyMap * heatermap = NULL; /* Heater efficiency map */ AstKeyMap * obsmap = NULL; /* Info from all observations */ AstKeyMap * objmap = NULL; /* All the object names used */ AstKeyMap * scimap = NULL; /* All non-flat obs indexed by unique key */ Grp *ogrp = NULL; /* local copy of output group */ size_t sccount = 0; /* Number of accepted science files */ struct timeval tv1; /* Timer */ struct timeval tv2; /* Timer */ if (meanstep) *meanstep = VAL__BADD; if (outgrp) *outgrp = NULL; if (darkgrp) *darkgrp = NULL; if (darks) *darks = NULL; if (fflats) *fflats = NULL; if (heateffmap) *heateffmap = NULL; if (*status != SAI__OK) return; /* Sanity check to make sure we return some information */ if ( outgrp == NULL && darkgrp == NULL && darks == NULL && fflats == NULL) { *status = SAI__ERROR; errRep( " ", FUNC_NAME ": Must have some non-NULL arguments" " (possible programming error)", status); return; } /* Start a timer to see how long this takes */ smf_timerinit( &tv1, &tv2, status ); /* Create new group for output files */ ogrp = smf_grp_new( ingrp, "Science", status ); /* and a new group for darks */ dgrp = smf_grp_new( ingrp, "DarkFiles", status ); /* and for fast flats */ fgrp = smf_grp_new( ingrp, "FastFlats", status ); /* and also create a keymap for the observation description */ obsmap = astKeyMap( "KeyError=1" ); /* and an object map */ objmap = astKeyMap( "KeyError=1" ); /* This keymap contains the sequence counters for each related subarray/obsidss/heater/shutter combination and is used to decide if a bad flat is relevant */ scimap = astKeyMap( "KeyError=1,KeyCase=0" ); /* This keymap is used to contain relevant heater efficiency data */ heatermap = astKeyMap( "KeyError=1,KeyCase=0" ); /* Work out how many input files we have and allocate sufficient sorting space */ insize = grpGrpsz( ingrp, status ); alldarks = astCalloc( insize, sizeof(*alldarks) ); allfflats = astCalloc( insize, sizeof(*allfflats) ); /* check each file in turn */ for (i = 1; i <= insize; i++) { int seqcount = 0; char keystr[100]; /* Key for scimap entry */ /* open the file but just to get the header */ smf_open_file( ingrp, i, "READ", SMF__NOCREATE_DATA, &infile, status ); if (*status != SAI__OK) break; /* Fill in the keymap with observation details */ smf_obsmap_fill( infile, obsmap, objmap, status ); /* Find the heater efficiency map if required */ if (*status == SAI__OK && heateffmap) { char arrayidstr[32]; smf_fits_getS( infile->hdr, "ARRAYID", arrayidstr, sizeof(arrayidstr), status ); if (!astMapHasKey( heatermap, arrayidstr ) ) { smfData * heateff = NULL; dim_t nbolos = 0; smf_flat_params( infile, "RESIST", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &heateff, status ); smf_get_dims( heateff, NULL, NULL, &nbolos, NULL, NULL, NULL, NULL, status ); if (heateff) astMapPut0P( heatermap, arrayidstr, heateff, NULL ); } } /* Get the sequence counter for the file. We do not worry about duplicate sequence counters (at the moment) */ smf_find_seqcount( infile->hdr, &seqcount, status ); /* The key identifying this subarray/obsidss/heater/shutter combo */ smf__calc_flatobskey( infile->hdr, keystr, sizeof(keystr), status ); if (smf_isdark( infile, status )) { /* Store the sorting information */ dkcount = smf__addto_sortinfo( infile, alldarks, i, dkcount, "Dark", status ); smf__addto_durations( infile, &duration_darks, &nsteps_dark, status ); astMapPutElemI( scimap, keystr, -1, seqcount ); } else { /* compare sequence type with observation type and drop it (for now) if they differ */ if ( infile->hdr->obstype == infile->hdr->seqtype ) { /* Sanity check the header for corruption. Compare RTS_NUM with SEQSTART and SEQEND. The first RTS_NUM must either be SEQSTART or else between SEQSTART and SEQEND (if someone has giving us a section) */ int seqstart = 0; int seqend = 0; int firstnum = 0; JCMTState *tmpState = NULL; smf_getfitsi( infile->hdr, "SEQSTART", &seqstart, status ); smf_getfitsi( infile->hdr, "SEQEND", &seqend, status ); tmpState = infile->hdr->allState; if( tmpState ) { firstnum = (tmpState[0]).rts_num; smf_smfFile_msg( infile->file, "F", 1, "<unknown file>"); if ( firstnum >= seqstart && firstnum <= seqend ) { /* store the file in the output group */ ndgCpsup( ingrp, i, ogrp, status ); msgOutif(MSG__DEBUG, " ", "Non-dark file: ^F",status); smf__addto_durations( infile, &duration_sci, &nsteps_sci, status ); astMapPutElemI( scimap, keystr, -1, seqcount ); sccount++; } else { msgOutif( MSG__QUIET, "", "File ^F has a corrupt FITS header. Ignoring it.", status ); } } else { smf_smfFile_msg( infile->file, "F", 1, "<unknown file>"); /* store the file in the output group */ ndgCpsup( ingrp, i, ogrp, status ); msgOutif( MSG__DEBUG, " ", "File ^F lacks JCMTState: assuming it is non-dark",status); smf__addto_durations( infile, &duration_sci, &nsteps_sci, status ); astMapPutElemI( scimap, keystr, -1, seqcount ); sccount++; } } else if (infile->hdr->seqtype == SMF__TYP_FASTFLAT ) { ffcount = smf__addto_sortinfo( infile, allfflats, i, ffcount, "Fast flat", status ); } else { smf_smfFile_msg( infile->file, "F", 1, "<unknown file>"); msgOutif(MSG__DEBUG, " ", "Sequence type mismatch with observation type: ^F",status); } } /* close the file */ smf_close_file( &infile, status ); } /* Store output group in return variable or else free it */ if (outgrp) { *outgrp = ogrp; } else { grpDelet( &ogrp, status ); } /* process flatfields if necessary */ if (ffcount > 0 && fflats ) { smfArray * array = NULL; /* sort flats into order */ qsort( allfflats, ffcount, sizeof(*allfflats), smf_sort_bydouble); if (fflats) array = smf_create_smfArray( status ); /* now open the flats and store them if requested */ if (*status == SAI__OK && array && ffcount) { size_t start_ffcount = ffcount; AstKeyMap * flatmap = NULL; if (calcflat) { /* Use AgeUp so that we get the keys out in the sorted order that allfflats used */ flatmap = astKeyMap( "KeyCase=0,KeyError=1,SortBy=AgeDown" ); } /* Read each flatfield. Calculate a responsivity image and a flatfield solution. Store these in a keymap along with related information which is itself stored in a keymap indexed by a string made of OBSIDSS, reference heater value, shutter and subarray. */ for (i = 0; i < start_ffcount; i++ ) { size_t ori_index = (allfflats[i]).index; smfData * outfile = NULL; char keystr[100]; AstKeyMap * infomap = astKeyMap( "KeyError=1" ); int oplen = 0; char thisfile[MSG__SZMSG]; int seqcount = 0; /* read filename from group */ infile = NULL; smf_open_file( ingrp, ori_index, "READ", 0, &infile, status ); if ( *status != SAI__OK ) { /* This should not happen because we have already opened the file. If it does happen we abort with error. */ if (infile) smf_close_file( &infile, status ); break; } /* Calculate the key for this observation */ smf__calc_flatobskey( infile->hdr, keystr, sizeof(keystr), status ); /* Get the file name for error messages */ smf_smfFile_msg( infile->file, "F", 1, "<unknown file>" ); msgLoad( "", "^F", thisfile, sizeof(thisfile), &oplen, status ); /* And the sequence counter to link against science observations */ smf_find_seqcount( infile->hdr, &seqcount, status ); /* Prefill infomap */ astMapPut0C( infomap, "FILENAME", thisfile, ""); astMapPut0I( infomap, "SEQCOUNT", seqcount, ""); /* Collapse it */ if (*status == SAI__OK) { smf_flat_fastflat( infile, &outfile, status ); if (*status == SMF__BADFLAT) { errFlush( status ); if (calcflat) { /* Need to generate an outfile like smf_flat_fastflat and one heater setting will force smf_flat_calcflat to fail */ smf_flat_malloc( 1, infile, NULL, &outfile, status ); } else { if (outfile) smf_close_file( &outfile, status ); if (infile) smf_close_file( &infile, status ); infomap = astAnnul( infomap ); ffcount--; continue; } } } if (outfile && *status == SAI__OK) { smf_close_file( &infile, status ); infile = outfile; if (calcflat) { size_t ngood = 0; smfData * curresp = NULL; int utdate; if (*status == SAI__OK) { ngood = smf_flat_calcflat( MSG__VERB, NULL, "RESIST", "FLATMETH", "FLATORDER", NULL, "RESPMASK", "FLATSNR", NULL, infile, &curresp, status ); if (*status != SAI__OK) { /* if we failed to calculate a flatfield we continue but force the flatfield to be completely bad. This will force the science data associated with the flatfield to be correctly blanked. We do not annul though if we have a SUBPAR error telling us that we have failed to define our parameters properly. */ if (*status != SUBPAR__NOPAR) errAnnul(status); /* parameters of flatfield */ ngood = 0; /* Generate a blank flatfield and blank responsivity image */ smf_flat_badflat( infile, &curresp, status ); } /* Retrieve the UT date so we can decide whether to compare flatfields */ smf_getfitsi( infile->hdr, "UTDATE", &utdate, status ); /* Store the responsivity data for later on and the processed flatfield until we have vetted it */ astMapPut0P( infomap, "CALCFLAT", infile, "" ); astMapPut0P( infomap, "RESP", curresp, "" ); astMapPut0I( infomap, "UTDATE", utdate, "" ); astMapPut0I( infomap, "ISGOOD", 1, "" ); astMapPut0I( infomap, "NGOOD", ngood, "" ); astMapPut0I( infomap, "GRPINDEX", ori_index, "" ); astMapPut0I( infomap, "SMFTYP", infile->hdr->obstype, "" ); astMapPutElemA( flatmap, keystr, -1, infomap ); } } else { /* if (calcflat) */ /* Store the collapsed flatfield - the processed flat is not stored here yet */ smf_addto_smfArray( array, infile, status ); /* Copy the group info */ ndgCpsup( ingrp, ori_index, fgrp, status ); } } /* if (outfile) */ /* Annul the keymap (will be fine if it is has been stored in another keymap) */ infomap = astAnnul( infomap ); } /* End loop over flatfields */ /* Now we have to loop over the related flatfields to disable bolometers that are not good and also decide whether we need to set status to bad. */ if (*status == SAI__OK && calcflat ) { size_t nkeys = astMapSize( flatmap ); for (i = 0; i < nkeys; i++ ) { const char *key = astMapKey( flatmap, i ); int nf = 0; AstKeyMap ** kmaps = NULL; int nelem = astMapLength( flatmap, key ); kmaps = astMalloc( sizeof(*kmaps) * nelem ); astMapGet1A( flatmap, key, nelem, &nelem, kmaps ); for ( nf = 0; nf < nelem && *status == SAI__OK; nf++ ) { AstKeyMap * infomap = kmaps[nf]; int isgood = 0; astMapGet0I( infomap, "ISGOOD", &isgood ); if (isgood) { /* The flatfield worked */ size_t ngood = 0; int itemp; int utdate = 0; int ratioFlats = 0; /* Get the UT date - we do not compare flatfields after the time we enabled heater tracking at each sequence. */ astMapGet0I( infomap, "UTDATE", &utdate ); /* Get the number of good bolometers at this point */ astMapGet0I( infomap, "NGOOD", &itemp ); ngood = itemp; /* Decide if we want to do the ratio test. We default to not doing it between 20110901 and 20120827 which is the period when we did mini-heater tracks before each flat. ! indicates that we choose based on date. */ if (*status == SAI__OK) { parGet0l( "FLATUSENEXT", &ratioFlats, status ); if ( *status == PAR__NULL ) { errAnnul( status ); if (utdate >= 20110901 || utdate <= 20120827 ) { ratioFlats = 0; } else { ratioFlats = 1; } } } /* Can we compare with the next flatfield? */ if (ngood < SMF__MINSTATSAMP || !ratioFlats ) { /* no point doing all the ratio checking for this */ } else if ( nelem - nf >= 2 ) { AstKeyMap * nextmap = kmaps[nf+1]; const char *nextfname = NULL; const char *fname = NULL; smfData * curresp = NULL; smfData * nextresp = NULL; smfData * curflat = NULL; void *tmpvar = NULL; size_t bol = 0; smfData * ratio = NULL; double *in1 = NULL; double *in2 = NULL; double mean = VAL__BADD; size_t nbolo; double *out = NULL; double sigma = VAL__BADD; float clips[] = { 5.0, 5.0 }; /* 5.0 sigma iterative clip */ size_t ngoodz = 0; astMapGet0C( nextmap, "FILENAME", &nextfname ); astMapGet0C( infomap, "FILENAME", &fname ); /* Retrieve the responsivity images from the keymap */ astMapGet0P( infomap, "RESP", &tmpvar ); curresp = tmpvar; astMapGet0P( nextmap, "RESP", &tmpvar ); nextresp = tmpvar; astMapGet0P( infomap, "CALCFLAT", &tmpvar ); curflat = tmpvar; nbolo = (curresp->dims)[0] * (curresp->dims)[1]; /* get some memory for the ratio if we have not already. We could get some memory once assuming each flat has the same number of bolometers... */ ratio = smf_deepcopy_smfData( curresp, 0, 0, 0, 0, status ); if( *status == SAI__OK ) { /* divide: smf_divide_smfData ? */ in1 = (curresp->pntr)[0]; in2 = (nextresp->pntr)[0]; out = (ratio->pntr)[0]; for (bol=0; bol<nbolo;bol++) { if ( in1[bol] != VAL__BADD && in1[bol] != 0.0 && in2[bol] != VAL__BADD && in2[bol] != 0.0 ) { out[bol] = in1[bol] / in2[bol]; } else { out[bol] = VAL__BADD; } } } /* find some statistics */ smf_clipped_stats1D( out, 2, clips, 1, nbolo, NULL, 0, 0, &mean, &sigma, NULL, 0, &ngoodz, status ); if (*status == SMF__INSMP) { errAnnul(status); msgOutiff( MSG__QUIET, "", "Flatfield ramp ratio of %s with %s had too few bolometers (%zu < %d).", status, fname, nextfname, ngoodz, SMF__MINSTATSAMP ); ngood = ngoodz; /* Must be lower or equal to original ngood */ } else if (*status == SAI__OK && mean != VAL__BADD && sigma != VAL__BADD && curflat->da) { /* Now flag the flatfield as bad for bolometers that have changed more than n%. We expect the variation to be 1+/-a small bit */ const double pmrange = 0.10; double thrlo = 1.0 - pmrange; double thrhi = 1.0 + pmrange; size_t nmasked = 0; double *flatcal = curflat->da->flatcal; msgOutiff( MSG__DEBUG, "", "Flatfield fast ramp ratio mean = %g +/- %g (%zu bolometers)", status, mean, sigma, ngood); /* we can just set the first slice of the flatcal to bad. That should be enough to disable the entire bolometer. We have just read these data so they should be in ICD order. */ for (bol=0; bol<nbolo;bol++) { if ( out[bol] != VAL__BADD && (out[bol] < thrlo || out[bol] > thrhi ) ) { flatcal[bol] = VAL__BADD; nmasked++; } else if ( in1[bol] != VAL__BADD && in2[bol] == VAL__BADD ) { /* A bolometer is bad next time but good now so we must set it bad now */ flatcal[bol] = VAL__BADD; nmasked++; } } if ( nmasked > 0 ) { msgOutiff( MSG__NORM, "", "Masked %zu bolometers in %s from unstable flatfield", status, nmasked, fname ); /* update ngood to take into account the masking */ ngood -= nmasked; } } smf_close_file( &ratio, status ); } /* End of flatfield responsivity comparison */ /* if we only have a few bolometers left we now consider this a bad flat unless it is actually an engineering measurement where expect some configurations to give zero bolometers */ if (ngood < SMF__MINSTATSAMP) { const char *fname = NULL; void * tmpvar = NULL; int smftyp = 0; smfData * curflat = NULL; astMapGet0I( infomap, "SMFTYP", &smftyp ); astMapGet0C( infomap, "FILENAME", &fname ); if (smftyp != SMF__TYP_NEP) { msgOutiff( MSG__QUIET, "", "Flatfield %s has %zu good bolometer%s.%s", status, fname, ngood, (ngood == 1 ? "" : "s"), ( ngood == 0 ? "" : " Keeping none.") ); isgood = 0; /* Make sure that everything is blanked. */ if (ngood > 0) { astMapGet0P( infomap, "CALCFLAT", &tmpvar ); curflat = tmpvar; if (curflat && curflat->da) { size_t bol; size_t nbolo = (curflat->dims)[0] * (curflat->dims)[1]; double *flatcal = curflat->da->flatcal; for (bol=0; bol<nbolo; bol++) { /* Just need to set the first element to bad */ flatcal[bol] = VAL__BADD; } } } } else { msgOutiff( MSG__NORM, "", "Flatfield ramp file %s has %zu good bolometer%s. Eng mode.", status, fname, ngood, (ngood == 1 ? "" : "s") ); } } /* We do not need the responsivity image again */ { void *tmpvar = NULL; smfData * resp = NULL; astMapGet0P( infomap, "RESP", &tmpvar ); resp = tmpvar; if (resp) smf_close_file( &resp, status ); astMapRemove( infomap, "RESP" ); } } /* End of isgood comparison */ /* We are storing flats even if they failed. Let the downstream software worry about it */ { int ori_index; smfData * flatfile = NULL; void *tmpvar = NULL; /* Store in the output group */ astMapGet0I( infomap, "GRPINDEX", &ori_index ); ndgCpsup( ingrp, ori_index, fgrp, status ); /* And store in the smfArray */ astMapGet0P( infomap, "CALCFLAT", &tmpvar ); astMapRemove( infomap, "CALCFLAT" ); flatfile = tmpvar; smf_addto_smfArray( array, flatfile, status ); } /* Free the object as we go */ kmaps[nf] = astAnnul( kmaps[nf] ); } /* End of loop over this obsidss/subarray/heater */ kmaps = astFree( kmaps ); } } if (array->ndat) { if (fflats) *fflats = array; } else { smf_close_related(&array, status ); if (fflats) *fflats = NULL; } } } /* no need to do any more if neither darks nor darkgrp are defined or we might be wanting to revert to darks. */ if (dkcount > 0 && (darks || darkgrp || reverttodark ) ) { smfArray * array = NULL; /* sort darks into order */ qsort( alldarks, dkcount, sizeof(*alldarks), smf_sort_bydouble); if (darks) array = smf_create_smfArray( status ); /* now open the darks and store them if requested */ if (*status == SAI__OK) { for (i = 0; i < dkcount; i++ ) { size_t ori_index = (alldarks[i]).index; /* Store the entry in the output group */ ndgCpsup( ingrp, ori_index, dgrp, status ); if (darks) { /* read the value from the new group */ smf_open_file( dgrp, i+1, "READ", 0, &infile, status ); /* do we have to process these darks? */ if (reducedark) { smfData *outfile = NULL; smf_reduce_dark( infile, darktype, &outfile, status ); if (outfile) { smf_close_file( &infile, status ); infile = outfile; } } smf_addto_smfArray( array, infile, status ); } } if (darks) *darks = array; } } /* free memory */ alldarks = astFree( alldarks ); allfflats = astFree( allfflats ); if( reverttodark && outgrp && (grpGrpsz(*outgrp,status)==0) && (grpGrpsz(dgrp,status)>0) ) { /* If outgrp requested but no science observations were found, and dark observations were found, return darks in outgrp and set flatgrp and darkgrp to NULL. This is to handle cases where we want to process data taken in the dark like normal science data. To activate this behaviour set reverttodark */ msgOutiff( MSG__NORM, "", "Treating the dark%s as science data", status, ( dkcount > 1 ? "s" : "" ) ); *outgrp = dgrp; if( darkgrp ){ *darkgrp = NULL; } if( flatgrp ) { *flatgrp = NULL; } grpDelet( &ogrp, status); grpDelet( &fgrp, status); if (meanstep && nsteps_dark > 0) *meanstep = duration_darks / nsteps_dark; /* Have to clear the darks smfArray as well */ if (darks) smf_close_related( darks, status ); } else { /* Store the output groups in the return variable or free it */ if (darkgrp) { *darkgrp = dgrp; } else { grpDelet( &dgrp, status); } if (flatgrp) { *flatgrp = fgrp; } else { grpDelet( &fgrp, status); } if (meanstep && nsteps_sci > 0) *meanstep = duration_sci / nsteps_sci; } msgSeti( "ND", sccount ); msgSeti( "DK", dkcount ); msgSeti( "FF", ffcount ); msgSeti( "TOT", insize ); if ( insize == 1 ) { if (dkcount == 1) { msgOutif( MSG__VERB, " ", "Single input file was a dark", status); } else if (ffcount == 1) { msgOutif( MSG__VERB, " ", "Single input file was a fast flatfield", status); } else if (sccount == 1) { msgOutif( MSG__VERB, " ", "Single input file was accepted (observation type same as sequence type)", status); } else { msgOutif( MSG__VERB, " ", "Single input file was not accepted.", status); } } else { if (dkcount == 1) { msgSetc( "DKTXT", "was a dark"); } else { msgSetc( "DKTXT", "were darks"); } if (ffcount == 1) { msgSetc( "FFTXT", "was a fast flat"); } else { msgSetc( "FFTXT", "were fast flats"); } if (sccount == 1) { msgSetc( "NDTXT", "was science"); } else { msgSetc( "NDTXT", "were science"); } /* This might be a useful message */ msgOutif( MSG__NORM, " ", "Out of ^TOT input files, ^DK ^DKTXT, ^FF ^FFTXT " "and ^ND ^NDTXT", status ); } if (meanstep && *meanstep != VAL__BADD) { msgOutiff( MSG__VERB, "", "Mean step time for input files = %g sec", status, *meanstep ); } /* Store the heater efficiency map */ if (*status != SAI__OK) heatermap = smf_free_effmap( heatermap, status ); if (heateffmap) *heateffmap = heatermap; /* Now report the details of the observation */ smf_obsmap_report( MSG__NORM, obsmap, objmap, status ); obsmap = astAnnul( obsmap ); objmap = astAnnul( objmap ); scimap = astAnnul( scimap ); msgOutiff( SMF__TIMER_MSG, "", "Took %.3f s to find science observations", status, smf_timerupdate( &tv1, &tv2, status ) ); return; }
void smf_check_pol2( Grp *igrp, int size, int raw, int *status ) { /* Local Variables */ smfData *data = NULL; /* pointer to SCUBA2 data struct */ int i; /* Loop counter */ /* Check inherited status */ if( *status != SAI__OK ) return; /* Loop over all files in the Grp */ for( i = 1; i <= size && *status == SAI__OK; i++ ) { /* Read header info from the ith input file in the group */ smf_open_file( NULL, igrp, i, "READ", SMF__NOCREATE_DATA, &data, status ); /* Check there is a file. */ if( *status == SAI__OK && data->file == NULL ) { *status = SAI__ERROR; errRep( FUNC_NAME, "No smfFile associated with smfData.", status ); /* Check there is a header. */ } else if( *status == SAI__OK && data->hdr == NULL ) { smf_smfFile_msg( data->file, "FILE", 1, "<unknown>" ); *status = SAI__ERROR; errRep( FUNC_NAME, "No smfHead associated with ^FILE.", status ); } /* Check that there are 3 pixel axes. */ if( *status == SAI__OK && data->ndims != 3) { smf_smfFile_msg( data->file, "FILE", 1, "<unknown>" ); msgSeti( "NDIMS", data->ndims ); *status = SAI__ERROR; errRep( FUNC_NAME, "^FILE has ^NDIMS pixel axes, should be 3.", status ); } /* Check that POL2 is in the beam. */ if( *status == SAI__OK && !(data->hdr->inbeam & SMF__INBEAM_POL) ) { smf_smfFile_msg( data->file, "FILE", 1, "<unknown>" ); *status = SAI__ERROR; errRep( FUNC_NAME, "^FILE does not contain POL2 data.", status ); } /* For raw data check that the NDF Label component is "Q" or "U". */ if( *status == SAI__OK ) { if( raw ) { if( strcmp( data->hdr->dlabel, "Signal" ) ) { smf_smfFile_msg( data->file, "FILE", 1, "<unknown>" ); msgSetc( "L", data->hdr->dlabel ); *status = SAI__ERROR; errRep( FUNC_NAME, "File ^FILE does not contain POL2 " "analysed intensity data (NDF label is '^L' " "but should be 'Signal').", status ); } /* For Q/U data check that the NDF Label component is "Q" or "U". */ } else { if( strcmp( data->hdr->dlabel, "Q" ) && strcmp( data->hdr->dlabel, "U" ) ) { smf_smfFile_msg( data->file, "FILE", 1, "<unknown>" ); msgSetc( "L", data->hdr->dlabel ); *status = SAI__ERROR; errRep( FUNC_NAME, "File ^FILE does not contain POL2 " "Q or U data (NDF label is '^L' but should be " "'Q' or 'U').", status ); } } } /* Close the data file */ smf_close_file( NULL, &data, status); } }
int smf_fix_metadata_scuba2 ( msglev_t msglev, smfData * data, int have_fixed, int *ncards, int * status ) { AstFitsChan * fits = NULL; /* FITS header (FitsChan) */ struct FitsHeaderStruct fitsvals; /* Quick access Fits header struct */ smfHead *hdr = NULL; /* Data header struct */ AstKeyMap * obsmap = NULL; /* Info from all observations */ AstKeyMap * objmap = NULL; /* All the object names used */ if (*status != SAI__OK) return have_fixed; /* Validate arguments - need smfFile and smfHead */ smf_validate_smfData( data, 1, 1, status ); if (*status != SAI__OK) return have_fixed; hdr = data->hdr; smf_validate_smfHead( hdr, 1, 1, status ); if (*status != SAI__OK) return have_fixed; fits = hdr->fitshdr; if (hdr->instrument != INST__SCUBA2) { if (*status != SAI__OK) { *status = SAI__ERROR; errRep("", " Attempting to fix metadata using SCUBA-2 algorithms but this is not SCUBA-2 data", status ); } return have_fixed; } /* Update units string to something that is FITS standard compliant - we used "DAC units" for a while but in FITS land this becomes "decacoulomb * units" */ if ( strncmp( hdr->units, "DAC", 3) == 0 ) { one_strlcpy( hdr->units, "adu", SMF__CHARLABEL, status ); } /* Clock jitter and readout efficiencies mean we need to recalculate STEPTIME from the data. This is possible because we know that we have a continuous sequence in each file (unlike ACSIS). */ if (hdr->allState) { /* it will be odd if it is not there */ size_t nframes = hdr->nframes; size_t istart = 0; double start_time = (hdr->allState)[istart].rts_end; while( start_time == VAL__BADD && ++istart < nframes ) { start_time = (hdr->allState)[istart].rts_end; } size_t iend = nframes - 1; double end_time = (hdr->allState)[iend].rts_end; while( end_time == VAL__BADD && iend-- > 0 ) { end_time = (hdr->allState)[iend].rts_end; } double steptime = VAL__BADD; double newstep; smf_getfitsd( hdr, "STEPTIME", &steptime, status ); newstep = steptime; /* it is possible for a file to contain only one step since the DA just dumps every N-steps. We can not recalculate the step time in that case. */ nframes = iend - istart + 1; if (nframes > 1) { /* duration of file in days */ newstep = end_time - start_time; /* convert to seconds */ newstep *= SPD; /* Convert to step time */ newstep /= (nframes - 1); } else if( nframes > 0 ) { /* work it out from RTS_END and TCS_TAI */ JCMTState * onlystate = &((hdr->allState)[istart]); if ( onlystate->tcs_tai != VAL__BADD && onlystate->tcs_tai != onlystate->rts_end) { /* TCS_TAI is in the middle of the step */ newstep = 2.0 * ( onlystate->rts_end - onlystate->tcs_tai ) * SPD; } } else if( *status == SAI__OK ) { *status = SAI__ERROR; if( data->file ) { smf_smfFile_msg( data->file, "N", 1, "<unknown>" ); errRep("", "No valid RTS_END values found in NDF '^N'.", status ); } else { errRep("", "No valid RTS_END values found.", status ); } } if (steptime != newstep) { msgOutiff( msglev, "", INDENT "Recalculated step time as %g sec from JCMTSTATE (was %g sec)", status, newstep, steptime); smf_fits_updateD( hdr, "STEPTIME", newstep, NULL, status ); have_fixed |= SMF__FIXED_FITSHDR; } } /* Read some FITS headers, intialising the struct first */ fitsvals.utdate = VAL__BADI; *(fitsvals.instrume) = '\0'; smf_getfitsi( hdr, "UTDATE", &(fitsvals.utdate), status ); smf_getfitss( hdr, "INSTRUME", fitsvals.instrume, sizeof(fitsvals.instrume), status ); /* Print out summary of this observation - this may get repetitive if multiple files come from the same observation in one invocation but it seems better to describe each fix up separately and in context. */ obsmap = astKeyMap( " " ); objmap = astKeyMap( " " ); smf_obsmap_fill( data, obsmap, objmap, status ); smf_obsmap_report( msglev, obsmap, objmap, status ); obsmap = astAnnul( obsmap ); objmap = astAnnul( objmap ); /* First we need to look for a BACKEND header which we do not write to the raw data files but CADC would like to see equal to INSTRUME */ if (!astTestFits( fits, "BACKEND", NULL ) ) { have_fixed |= SMF__FIXED_FITSHDR; smf_fits_updateS( hdr, "BACKEND", fitsvals.instrume, "Name of the backend", status ); msgOutif( msglev, "", INDENT "Setting backend for SCUBA-2 observation.", status); } /* BASETEMP was reading MUXTEMP for pre-20091101 data */ if ( fitsvals.utdate < 20091101 ) { double muxtemp = 0.0; have_fixed |= SMF__FIXED_FITSHDR; smf_getfitsd( hdr, "BASETEMP", &muxtemp, status ); smf_fits_updateU( hdr, "BASETEMP", "[K] Base temperature", status ); smf_fits_updateD( hdr, "MUXTEMP", muxtemp, "[K] Mux temperature", status ); msgOutif( msglev, "", INDENT "Mux temperature is being read from BASETEMP header.", status ); } /* Sometime before 20091119 the SHUTTER keyword was written as a string OPEN or CLOSED. Rewrite those as numbers */ if (fitsvals.utdate < 20091119) { double shutval = 0.0; /* Try to read as a double. */ smf_fits_getD( hdr, "SHUTTER", &shutval, status ); /* Old data was a string. Convert to a double */ if (*status == AST__FTCNV) { char shutter[100]; errAnnul( status ); smf_fits_getS( hdr, "SHUTTER", shutter, sizeof(shutter), status); if (strcmp(shutter, "CLOSED") == 0) { shutval = 0.0; } else { shutval = 1.0; } /* update the value */ have_fixed |= SMF__FIXED_FITSHDR; smf_fits_updateD( hdr, "SHUTTER", shutval, "shutter position 0-Closed 1-Open", status ); msgOutif( msglev, "", INDENT "Forcing SHUTTER header to be numeric", status ); } } /* Engineering data with just SCUBA2 and no RTS left the RTS_NUM field filled with zeroes. Just assume that a zero in RTS_NUM is always indicative of a private sequence. */ if (fitsvals.utdate < 20110401) { size_t nframes = hdr->nframes; JCMTState * curstate = &((hdr->allState)[0]); JCMTState * endstate = &((hdr->allState)[nframes-1]); if (curstate->rts_num == 0 && endstate->rts_num == 0) { /* have to set the values from the SEQSTART and SEQEND headers since those were set correctly (although any value would do of course apart from the sanity check in smf_find_science. */ size_t i; int seqnum = 1; smf_fits_getI( hdr, "SEQSTART", &seqnum, status ); for ( i=0; i<nframes; i++) { curstate = &((hdr->allState)[i]); curstate->rts_num = seqnum; seqnum++; } have_fixed |= SMF__FIXED_JCMTSTATE; msgOutif( msglev, "", INDENT "Private RTS sequence. Fixing RTS_NUM.", status ); } } /* work out if this is a fast flat observation taken before May 2010 */ if (fitsvals.utdate > 20100218 && fitsvals.utdate < 20100501) { char buff[100]; /* need to know whether this is a FASTFLAT */ smf_getfitss( hdr, "SEQ_TYPE", buff, sizeof(buff), status ); if (strcmp( buff, "FASTFLAT" ) == 0 ) { /* Fast flats had incorrect SHUTTER settings for one night */ if (fitsvals.utdate == 20100223) { have_fixed |= SMF__FIXED_FITSHDR; smf_fits_updateD( hdr, "SHUTTER", 1.0, "shutter position 0-Closed 1-Open", status ); msgOutif( msglev, "", INDENT "Shutter was open for fast flatfield ramp. Correcting.", status ); } /* Need to fix up SC2_HEAT ramps */ /* the problem is that the data were assumed to be taken with 3 measurements in each heater setting. What actually happened was that the first 5 were done at the reference setting and then the data were grouped in threes finishing with a single value at the reference setting again. For example the heater values and the actual values look something like Stored 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 Actual 0 0 0 0 0 2 2 2 3 3 3 4 4 4 5 So we can correct for this by starting at the end and copying in the value two slots further down until we get to position #4. Then replacing that with the PIXHEAT number. */ { size_t i; int pixheat = 0; size_t nframes = hdr->nframes; smf_getfitsi( hdr, "PIXHEAT", &pixheat, status ); /* shift everything up by 2 */ for (i=nframes-1; i > 4; i--) { JCMTState * curstate = &((hdr->allState)[i]); JCMTState * prevstate = &((hdr->allState)[i-2]); curstate->sc2_heat = prevstate->sc2_heat; } /* fill in the first 5 slots with the same value */ for (i=0; i<5; i++) { JCMTState * curstate = &((hdr->allState)[i]); curstate->sc2_heat = pixheat; } have_fixed |= SMF__FIXED_JCMTSTATE; } } } /* We always recalculate the WVM start and end tau values so that the header reflects something approximating the value that was actually used in the extinction correction. Note that smf_calc_smoothedwvm can do a better job because it has multiple subarrays to get all the values from. We just have to try with what we have from a single subarray. We do step into the time series until we find something good. The header values should mostly agree with the recalculated values if the WVM code at the time matches the code in SMURF for that date. This has not been true in cases where we have retrospectively realised that there has been a calibration error in the WVM. So that we do not have to keep track of those times explicitly we currently recalculate every time. If this recalculation becomes a problem (smf_calc_wvm has a cache now to minimize this) it should be possible to disable this recalculation if the file is less than, say, 30 minutes old to indicate we are running in near realtime. As a special case we do not recalculate the headers for FOCUS observations as the WVM reading is somewhat irrelevant and simply slows things down. */ if( *status == SAI__OK ){ /* Have not parsed header yet to extract type so do it explicitly here */ char obstype[100]; smf_getfitss( hdr, "OBS_TYPE", obstype, sizeof(obstype), status ); if (strcasecmp( obstype, "focus") != 0) { size_t i; size_t nframes = hdr->nframes; double starttau = VAL__BADD; double starttime = VAL__BADD; double endtau = VAL__BADD; double endtime = VAL__BADD; /* Create a TimeFrame that can be used to format MJD values into ISO date-time strings, including a "T" separator between time and date. */ AstTimeFrame *tf = astTimeFrame( "Format=iso.0T" ); for (i=0; i < nframes && *status == SAI__OK; i++) { smf__calc_wvm_index( hdr, "AMSTART", i, &starttau, &starttime, status ); if (starttau != VAL__BADD) break; if (*status == SAI__ERROR) errAnnul( status ); } /* if we did not get a start tau we are not going to get an end tau */ if (starttau != VAL__BADD) { for (i=0; i < nframes && *status == SAI__OK; i++) { smf__calc_wvm_index( hdr, "AMEND", nframes - 1 - i, &endtau, &endtime, status ); if (endtau != VAL__BADD) break; if (*status == SAI__ERROR) errAnnul( status ); } } /* If we could not find any WVM readings then we have a bit of a problem. Do we clear the FITS headers or do we leave them untouched? Leave them alone for now. */ if (starttau != VAL__BADD && starttime != VAL__BADD) { smf_fits_updateD( hdr, "WVMTAUST", starttau, "186GHz Tau from JCMT WVM at start", status ); /* Convert starttime MJD to ISO format and update the value in the FITS header. */ smf_fits_updateS( hdr, "WVMDATST", astFormat( tf, 1, starttime ), "Time of WVMTAUST", status ); have_fixed |= SMF__FIXED_FITSHDR; } if (endtau != VAL__BADD && endtime != VAL__BADD) { smf_fits_updateD( hdr, "WVMTAUEN", endtau, "186GHz Tau from JCMT WVM at end", status ); /* Convert endtime MJD to ISO format and update the value in the FITS header. */ smf_fits_updateS( hdr, "WVMDATEN", astFormat( tf, 1, endtime ), "Time of WVMTAUEN", status ); have_fixed |= SMF__FIXED_FITSHDR; } /* Free the TimeFrame. */ tf = astAnnul( tf ); } } /* SEQ_TYPE header turned up in 20091125. Before that date the SEQ_TYPE only had two values. If the shutter was open then SEQ_TYPE is just OBS_TYPE. In the dark only a FLATFIELD sometimes finished with a noise but in that case CALCFLAT doesn't care so we just call it a flatfield sequence anyhow. We could look at the OBSEND flag but I'm not sure it makes a difference. */ if ( fitsvals.utdate < 20091125 ) { char obstype[100]; char seqtype[100]; double shutval = 0.0; /* need to know what type of observation this is */ smf_getfitss( hdr, "OBS_TYPE", obstype, sizeof(obstype), status ); /* and the shutter status */ smf_fits_getD( hdr, "SHUTTER", &shutval, status ); if (shutval == 0.0 && strcasecmp( obstype, "flatfield" ) != 0 ) { /* flatfield was the only non-noise observation in the dark */ one_strlcpy( seqtype, "NOISE", sizeof(seqtype), status ); msgOutif( msglev, "", INDENT "Setting sequence type to NOISE", status ); } else { /* Shutter was open so SEQ_TYPE is just OBS_TYPE */ one_strlcpy( seqtype, obstype, sizeof(seqtype), status ); msgOutif( msglev, "", INDENT "Setting sequence type to obs type", status); } smf_fits_updateS( hdr, "SEQ_TYPE", seqtype, "Type of sequence", status ); have_fixed |= SMF__FIXED_FITSHDR; } /* The telescope goes crazy at the end of observation 56 on 20110530. Null the telescope data for subscans 30, 31 and 32 */ if (fitsvals.utdate == 20110530) { char obsid[81]; smf_getobsidss( hdr->fitshdr, obsid, sizeof(obsid), NULL, 0, status); if (strcmp(obsid, "scuba2_00056_20110530T135530") == 0 ) { int subscan; smf_getfitsi( hdr, "NSUBSCAN", &subscan, status ); if (subscan == 30 || subscan == 31 || subscan == 32) { size_t nframes = hdr->nframes; JCMTState * curstate; size_t i; for ( i=0; i<nframes; i++ ) { curstate = &((hdr->allState)[i]); curstate->jos_drcontrol |= DRCNTRL__PTCS_BIT; } msgOutif( msglev, "", INDENT "Blanking telescope data due to extreme excursion", status ); have_fixed |= SMF__FIXED_JCMTSTATE; } } } /* The second half of observation 14 on 20111215 (scuba2_00014_20111215T061536) has a elevation pointing shift */ if (fitsvals.utdate == 20111215) { char obsid[81]; const char fitskey[] = "FIXPCORR"; smf_getobsidss( hdr->fitshdr, obsid, sizeof(obsid), NULL, 0, status); if (strcmp(obsid, "scuba2_00014_20111215T061536") == 0 ) { int seqcount; smf_getfitsi( hdr, "SEQCOUNT", &seqcount, status ); if (seqcount == 5) { int have_fixed_pntg = 0; smf_fits_getL( hdr, fitskey, &have_fixed_pntg, status ); if (*status == SMF__NOKWRD) { have_fixed = 0; errAnnul( status ); } if (!have_fixed_pntg) { size_t nframes = hdr->nframes; size_t i; const double dlon = 0.0; const double dlat = -16.83; /* From making maps of each half */ /* Correct the pointing */ msgOutif( msglev, "", INDENT "Applying pointing anomaly correction", status ); for (i=0;i<nframes;i++) { JCMTState * curstate = &((hdr->allState)[i]); /* This is an AZEL correction */ smf_add_smu_pcorr( curstate, 1, dlon, dlat, status ); } smf_fits_updateL(hdr, fitskey, 1, "Applied internal pointing correction", status); have_fixed |= SMF__FIXED_JCMTSTATE; } } } } /* For POL-2 data prior to 18-JAN-2013, the POL_CRD header was always "FPLANE" in reality, even if the POL_CRD value in JCMTSTATE said something else. */ if ( fitsvals.utdate < 20130118 ) { char polcrd[80] = "<unset>"; smf_getfitss( hdr, "POL_CRD", polcrd, sizeof(polcrd), status ); if (*status == SMF__NOKWRD) { errAnnul( status ); } else if( !strcmp( polcrd, "TRACKING" ) || !strcmp( polcrd, "AZEL" ) ) { msgOutiff( msglev, "", INDENT "Changing POL_CRD from %s to FPLANE", status, polcrd); smf_fits_updateS( hdr, "POL_CRD", "FPLANE", "Coordinate system of polarimeter", status ); have_fixed |= SMF__FIXED_FITSHDR; } } return have_fixed; }
void smf_grp_related( const Grp *igrp, const size_t grpsize, const int grouping, const int checksubinst, double maxlen_s, double *srate_maxlen, AstKeyMap *keymap, dim_t *maxconcatlen, dim_t *maxfilelen, smfGroup **group, Grp **basegrp, dim_t *pad, int *status ) { /* Local variables */ size_t *chunk=NULL; /* Array of flags for continuous chunks */ dim_t * chunklen = NULL; /* Length of continuous chunk */ size_t currentindex = 0; /* Counter */ char cwave[10]; /* String containing wavelength */ smfData *data = NULL; /* Current smfData */ double downsampscale=0; /* Angular scale downsampling size */ double downsampfreq=0; /* Target downsampling frequency */ AstKeyMap * grouped = NULL; /* Primary AstKeyMap for grouping */ size_t i; /* Loop counter for index into Grp */ int isFFT=0; /* Set if data are 4d FFT */ size_t j; /* Loop counter */ int *keepchunk=NULL; /* Flag for chunks that will be kept */ dim_t maxconcat=0; /* Longest continuous chunk length */ dim_t maxflen=0; /* Max file length in time steps */ dim_t maxlen=0; /* Maximum concat length in samples */ int maxlen_scaled=0; /* Set once maxlen has been scaled, if needed */ dim_t maxpad=0; /* Maximum padding neeed for any input file */ size_t maxrelated = 0; /* Keep track of max number of related items */ size_t *new_chunk=NULL; /* keeper chunks associated with subgroups */ dim_t *new_tlen=NULL; /* tlens for new_subgroup */ size_t ngroups = 0; /* Counter for subgroups to be stored */ size_t nkeep = 0; /* Number of chunks to keep */ dim_t * piecelen = NULL; /* Length of single file */ smf_subinst_t refsubinst; /* Subinst of first file */ size_t **subgroups = NULL; /* Array containing index arrays to parent Grp */ smf_subinst_t subinst; /* Subinst of current file */ if ( *status != SAI__OK ) return; if( maxlen_s < 0 ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": maxlen_s cannot be < 0!", status ); return; } /* Get downsampling parameters */ if( keymap ) { smf_get_cleanpar( keymap, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &downsampscale, &downsampfreq, NULL, NULL, NULL, NULL, status ); if( downsampscale && downsampfreq ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": both downsampscale and downsampfreq are set", status ); return; } } /* Initialize refcwave */ refsubinst = SMF__SUBINST_NONE; /* Loop over files in input Grp: remember Grps are indexed from 1 */ grouped = astKeyMap( "SortBy=KeyUp" ); for (i=1; i<=grpsize; i++) { char newkey[128]; char dateobs[81]; char subarray[10]; size_t nrelated = 0; AstKeyMap * filemap = NULL; AstKeyMap * indexmap = NULL; /* First step: open file and harvest metadata */ smf_open_file( NULL, igrp, i, "READ", SMF__NOCREATE_DATA, &data, status ); if (*status != SAI__OK) break; if( i==1 ) { isFFT = smf_isfft( data, NULL, NULL, NULL, NULL, NULL, status ); } else if( smf_isfft(data, NULL, NULL, NULL, NULL, NULL, status) != isFFT ){ *status = SAI__ERROR; errRep( "", FUNC_NAME ": mixture of time-series and FFT data encountered!", status ); break; } /* If maxlen has not been set, do it here */ if( !maxlen && maxlen_s && data->hdr->steptime) { maxlen = (dim_t) (maxlen_s / data->hdr->steptime ); } /* Return srate_maxlen if requested: may want to know this number even if maxlen_s is not set. Only calculate once, although it gets overwritten once later if down-sampling. */ if( (i==1) && srate_maxlen && data->hdr->steptime ) { *srate_maxlen = 1. / (double) data->hdr->steptime; } /* If requested check to see if we are mixing wavelengths */ if( checksubinst ) { if( refsubinst == SMF__SUBINST_NONE ) { refsubinst = smf_calc_subinst( data->hdr, status ); } subinst = smf_calc_subinst( data->hdr, status ); if( subinst != refsubinst ) { const char *refsubstr = smf_subinst_str( refsubinst, status ); const char *substr = smf_subinst_str( subinst, status ); *status = SAI__ERROR; smf_smfFile_msg( data->file, "FILE", 1, "<unknown>" ); msgSetc( "REFSUB", refsubstr ); msgSetc( "SUB", substr ); errRep( "", FUNC_NAME ": ^FILE uses sub-instrument ^SUB which doesn't match " "reference ^REFSUB", status ); } } /* Want to form a key that will be unique for a particular subscan We know that DATE-OBS will be set for SCUBA-2 files and be the same for a single set. Prefix by wavelength if we are grouping by wavelength. */ newkey[0] = '\0'; smf_find_subarray( data->hdr, subarray, sizeof(subarray), NULL, status ); if( grouping == 1 ) { /* Group different wavelengths separately */ smf_fits_getS( data->hdr, "WAVELEN", cwave, sizeof(cwave), status); one_strlcat( newkey, cwave, sizeof(newkey), status ); one_strlcat( newkey, "_", sizeof(newkey), status ); } if( grouping == 2 ) { /* Group different subarrays separately */ one_strlcat( newkey, subarray, sizeof(newkey), status ); } smf_fits_getS( data->hdr, "DATE-OBS", dateobs, sizeof(dateobs), status ); one_strlcat( newkey, dateobs, sizeof(newkey), status ); /* Include the dimentionality of the time series in the primary key so that we do not end up doing something confusing like relating a truncated file with a full length file */ if (*status == SAI__OK) { dim_t dims[3]; char formatted[32]; smf_get_dims( data, &dims[0], &dims[1], NULL, &dims[2], NULL, NULL, NULL, status ); sprintf(formatted, "_%" DIM_T_FMT "_%" DIM_T_FMT "_%" DIM_T_FMT, dims[0], dims[1], dims[2]); one_strlcat( newkey, formatted, sizeof(newkey), status ); } /* May want to read the dimensionality of the file outside of loop so that we can compare values when storing in the keymap */ /* Now we want to create a keymap based on this key */ if (!astMapGet0A( grouped, newkey, &filemap ) ) { int itemp = 0; double steptime = data->hdr->steptime; dim_t ntslice = 0; dim_t thispad; /* Padding neeed for current input file */ filemap = astKeyMap( " " ); astMapPut0A( grouped, newkey, filemap, NULL ); /* Fill up filemap with general information on this file */ smf_find_seqcount( data->hdr, &itemp, status ); astMapPut0I( filemap, "SEQCOUNT", itemp, NULL ); smf_fits_getI( data->hdr, "NSUBSCAN", &itemp, status ); astMapPut0I( filemap, "NSUBSCAN", itemp, NULL ); /* Number of time slices */ smf_get_dims( data, NULL, NULL, NULL, &ntslice, NULL, NULL, NULL, status ); /* Find length of down-sampled data, new steptime and maxlen */ if( (downsampscale || downsampfreq) && data->hdr && (*status==SAI__OK) ) { double scalelen; if( downsampscale ) { if( data->hdr->scanvel != VAL__BADD ) { double oldscale = steptime * data->hdr->scanvel; scalelen = oldscale / downsampscale; } else if( *status == SAI__OK ) { *status = SAI__ERROR; scalelen = VAL__BADD; smf_smfFile_msg( data->file, "FILE", 1, "" ); errRep( "", FUNC_NAME ": can't resample ^FILE because it has " "unknown scan velocity", status ); } } else { if( steptime ) { double oldsampfreq = 1./steptime; scalelen = downsampfreq / oldsampfreq; } else { *status = SAI__ERROR; scalelen = VAL__BADD; smf_smfFile_msg( data->file, "FILE", 1, "" ); errRep( "", FUNC_NAME ": can't resample ^FILE because it has " "unknown sample rate", status ); } } /* only down-sample if it will be a reasonable factor */ if( (*status==SAI__OK) && (scalelen <= SMF__DOWNSAMPLIMIT) ) { smf_smfFile_msg(data->file, "FILE", 1, "" ); msgOutiff( MSG__VERB, "", FUNC_NAME ": will down-sample file ^FILE from %5.1lf Hz to " "%5.1lf Hz", status, (1./steptime), (scalelen/steptime) ); ntslice = round(ntslice * scalelen); /* If maxlen has been requested, and we have not already worked out a scaled version (just uses the sample rates for the first file... should be close enough -- the alternative is a 2-pass system). */ if( !maxlen_scaled ) { maxlen = round(maxlen*scalelen); maxlen_scaled = 1; msgOutiff( MSG__VERB, "", FUNC_NAME ": requested maxlen %g seconds = %" DIM_T_FMT " down-sampled " "time-slices", status, maxlen_s, maxlen ); /* Return updated srate_maxlen for down-sampling if requested */ if( srate_maxlen ) { *srate_maxlen = scalelen/steptime; } } } } /* Check that an individual file is too long (we assume related files are all the same) */ if( maxlen && (ntslice > maxlen) && *status == SAI__OK) { *status = SAI__ERROR; msgSeti("NTSLICE",ntslice); msgSeti("MAXLEN",maxlen); smf_smfFile_msg( data->file, "FILE", 1, "" ); errRep(FUNC_NAME, "Number of time steps in file ^FILE time exceeds maximum " "(^NTSLICE>^MAXLEN)", status); } /* Scaled values of ntslice and maximum length */ astMapPut0I( filemap, "NTSLICE", ntslice, NULL ); /* Work out the padding needed for this file including downsampling. */ if( keymap ) { thispad = smf_get_padding( keymap, 0, data->hdr, VAL__BADD, status ); if( thispad > maxpad ) maxpad = thispad; } else { thispad = 0; } astMapPut0I( filemap, "PADDING", thispad, NULL ); /* Update maxflen */ if( ntslice > maxflen ) { maxflen = ntslice; } /* Store OBSID or OBSIDSS depending on whether we are grouping by wavelength */ if (grouping) { astMapPut0C( filemap, "OBSID", data->hdr->obsidss, NULL ); } else { char obsid[81]; smf_getobsidss( data->hdr->fitshdr, obsid, sizeof(obsid), NULL, 0, status ); astMapPut0C( filemap, "OBSID", obsid, NULL ); } } /* Store the file index in another keymap indexed by subarray */ if ( !astMapGet0A( filemap, "GRPINDICES", &indexmap ) ) { indexmap = astKeyMap( "SortBy=KeyUp" ); astMapPut0A( filemap, "GRPINDICES", indexmap, NULL ); } astMapPut0I( indexmap, subarray, i, NULL ); /* Need to track the largest number of related subarrays in a single slot */ nrelated = astMapSize( indexmap ); if (nrelated > maxrelated) maxrelated = nrelated; /* Free resources */ filemap = astAnnul( filemap ); indexmap = astAnnul( indexmap ); smf_close_file( NULL, &data, status ); } /* We now know how many groups there are */ ngroups = astMapSize( grouped ); /* Sort out chunking. The items are sorted by date and then by wavelength. We define a continuous chunk if it has the same OBSID, the same SEQCOUNT and NSUBSCAN increments by one from the previous entry. Also count number of related items in each slot. */ if (*status == SAI__OK) { typedef struct { /* somewhere to store the values easily */ char obsid[81]; char related[81]; /* for concatenated subarrays */ int nsubscan; int seqcount; } smfCompareSeq; smfCompareSeq current; smfCompareSeq previous; dim_t totlen = 0; size_t thischunk; /* Get the chunk flags and also store the size of the chunk */ chunk = astCalloc( ngroups, sizeof(*chunk) ); chunklen = astCalloc( ngroups, sizeof(*chunklen) ); piecelen = astCalloc( ngroups, sizeof(*piecelen) ); thischunk = 0; /* The current chunk */ for (i=0; i<ngroups; i++) { AstKeyMap * thismap = NULL; AstKeyMap * grpindices = NULL; const char * tempstr = NULL; int thistlen = 0; size_t nsubarrays = 0; /* Get the keymap entry for this slot */ astMapGet0A( grouped, astMapKey(grouped, i), &thismap ); /* Get info for length limits */ astMapGet0I( thismap, "NTSLICE", &thistlen ); piecelen[i] = thistlen; if (isFFT) { /* Never concatenate FFT data */ thismap = astAnnul(thismap); chunk[i] = i; chunklen[i] = thistlen; continue; } /* Get indices information and retrieve the sub-instrument names in sort order to concatenate for comparison. We only store in a continuous chunk if we have the same subarrays for the whole chunk. */ astMapGet0A( thismap, "GRPINDICES", &grpindices ); nsubarrays = astMapSize( grpindices ); (current.related)[0] = '\0'; for (j = 0; j < nsubarrays; j++ ) { one_strlcat( current.related, astMapKey(grpindices, j), sizeof(current.related), status ); } grpindices = astAnnul( grpindices ); /* Fill in the current struct */ astMapGet0I( thismap, "SEQCOUNT", &(current.seqcount) ); astMapGet0I( thismap, "NSUBSCAN", &(current.nsubscan) ); astMapGet0C( thismap, "OBSID", &tempstr ); one_strlcpy( current.obsid, tempstr, sizeof(current.obsid), status ); /* First chunk is special, else compare */ if (i == 0) { totlen = thistlen; } else { if ( ( current.seqcount == previous.seqcount ) && ( current.nsubscan - previous.nsubscan == 1 ) && ( strcmp( current.obsid, previous.obsid ) == 0 ) && ( strcmp( current.related, previous.related ) == 0 ) ) { /* continuous - check length */ totlen += thistlen; if ( maxlen && totlen > maxlen ) { thischunk++; totlen = thistlen; /* reset length */ } else { /* Continuous */ } } else { /* discontinuity */ thischunk++; totlen = thistlen; /* Update length of current chunk */ } } chunklen[thischunk] = totlen; chunk[i] = thischunk; memcpy( &previous, ¤t, sizeof(current) ); thismap = astAnnul( thismap ); } } /* Decide if we are keeping a chunk by looking at the length. */ maxconcat = 0; nkeep = 0; keepchunk = astMalloc( ngroups*sizeof(*keepchunk) ); for (i=0; i<ngroups; i++) { size_t thischunk; thischunk = chunk[i]; if ( chunklen[thischunk] < SMF__MINCHUNKSAMP ) { /* Warning message */ msgSeti("LEN",chunklen[thischunk]); msgSeti("MIN",SMF__MINCHUNKSAMP); msgOut( " ", "SMF_GRP_RELATED: ignoring short chunk (^LEN<^MIN)", status); keepchunk[i] = 0; } else { keepchunk[i] = 1; if (maxconcat < chunklen[thischunk]) maxconcat = chunklen[thischunk]; nkeep++; } } /* If no useful chunks generate an error */ if( (*status==SAI__OK) && (!nkeep) ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": No useful chunks.", status ); goto CLEANUP; } /* Allocate a subgroup array of the right size and fill it. They keymap is sorted by date (and wavelength) so we can always index into it by using indices from the subgroup. */ subgroups = astCalloc( nkeep, sizeof(*subgroups) ); new_chunk = astCalloc( nkeep, sizeof(*new_chunk) ); new_tlen = astCalloc( nkeep, sizeof(*new_tlen) ); currentindex = 0; for (i=0;i<ngroups;i++) { AstKeyMap * thismap = NULL; AstKeyMap * grpindices = NULL; size_t nsubarrays = 0; size_t *indices = astCalloc( maxrelated, sizeof(*indices) ); /* skip if we are dropping this chunk */ if (!keepchunk[i]) continue; /* Get the keymap entry for this slot */ astMapGet0A( grouped, astMapKey(grouped, i), &thismap ); /* Get the indices keymap */ astMapGet0A( thismap, "GRPINDICES", &grpindices ); nsubarrays = astMapSize( grpindices ); for (j=0; j<nsubarrays; j++) { int myindex; astMapGet0I( grpindices, astMapKey(grpindices, j), &myindex ); indices[j] = myindex; } grpindices = astAnnul( grpindices ); thismap = astAnnul( thismap ); subgroups[currentindex] = indices; new_chunk[currentindex] = chunk[i]; new_tlen[currentindex] = piecelen[i]; currentindex++; } /* Create the smfGroup */ *group = smf_construct_smfGroup( igrp, subgroups, new_chunk, new_tlen, nkeep, maxrelated, 0, status ); /* Return maxfilelen if requested */ if( maxfilelen ) { *maxfilelen = maxflen; } /* Return maxconcatlen if requested */ if( maxconcatlen ) { *maxconcatlen = maxconcat; } /* Create a base group for output files if required */ /* Create a base group of filenames */ if (*status == SAI__OK && basegrp ) { *basegrp = smf_grp_new( (*group)->grp, "Base Group", status ); /* Loop over time chunks */ for( i=0; (*status==SAI__OK)&&(i<(*group)->ngroups); i++ ) { size_t idx; /* Check for new continuous chunk */ if( i==0 || ( (*group)->chunk[i] != (*group)->chunk[i-1]) ) { /* Loop over subarray */ for( idx=0; idx<(*group)->nrelated; idx++ ) { size_t grpindex = (*group)->subgroups[i][idx]; if ( grpindex > 0 ) { ndgCpsup( (*group)->grp, grpindex, *basegrp, status ); } } } } } CLEANUP: keepchunk = astFree( keepchunk ); chunk = astFree( chunk ); chunklen = astFree( chunklen ); piecelen = astFree( piecelen ); grouped = astAnnul( grouped ); if( *status != SAI__OK ) { /* free the group */ if (basegrp && *basegrp) grpDelet( basegrp, status ); if (group && *group) { smf_close_smfGroup( group, status ); } else { /* have to clean up manually */ new_chunk = astFree( new_chunk ); new_tlen = astFree( new_tlen ); if( subgroups ) { size_t isub; for( isub=0; isub<nkeep; isub++ ) { subgroups[isub] = astFree( subgroups[isub] ); } subgroups = astFree( subgroups ); } } } /* Return the maximum padding if required. */ if( pad ) *pad = maxpad; }
void smf_getrefwcs( const char *param, Grp *igrp, AstFrameSet **specwcs, AstFrameSet **spacewcs, int *isjsa, int *status ){ /* Local Variables */ AstFrame *frm = NULL; AstFrameSet *refwcs = NULL; /* The WCS FrameSet from the reference NDF */ AstRegion *circle; char text[ 255 ]; /* Parameter value */ int *tiles; int i; int jsatiles; int lbnd[2]; /* Lower pixel index bounds of mid tile */ int ntile; int perm[ 2 ]; int refndf; /* NDF identifier for the refence NDF */ int ubnd[2]; /* Upper pixel index bounds of mid tile */ size_t code; smfData *data = NULL; /* Structure describing 1st input file */ smfJSATiling skytiling; smf_inst_t inst = SMF__INST_NONE; smf_jsaproj_t proj; /* Specific JSA projection to use */ smf_subinst_t subinst; /* Initialise the returned values. */ *specwcs = NULL; *spacewcs = NULL; *isjsa = 0; /* Check inherited status */ if( *status != SAI__OK ) return; /* Begin an AST context. */ astBegin; /* If the JSAILES parameter is TRUE, then we use the JSA all-sky pixel grid regardless of the setting of REF. */ parGet0l( "JSATILES", &jsatiles, status ); if( jsatiles ) { strcpy( text, "JSA" ); *isjsa = 1; /* Otherwise, first get the parameter value as a string. Use subpar to avoid problem caused by interpretion of the text within the parameter system. */ } else { subParFindpar( param, &code, status ); subParGetname( code, text, sizeof(text), status ); } /* If no value was supplied, annul the error and do nothing more. */ if( *status == PAR__NULL ) { errAnnul( status ); /* If it is "JSA", or one of the JSA projection codes, we return WCS that describes one of the the JSA all-sky pixel grids. */ } else if( *status == SAI__OK ) { proj = smf_jsaproj_fromstr( text, 0, status ); if( astChrMatch( text, "JSA" ) || proj != SMF__JSA_NULL ) { *isjsa = 1; /* Report an error if the instrument cannot be determined. */ if( !igrp ) { *status = SAI__ERROR; errRep( "", "smf_getrefwcs: Cannot use the JSA all-sky pixel " "grid since no input group has been supplied (possibly " "programming error).", status ); } else { /* Open the first input file. */ smf_open_file( NULL, igrp, 1, "READ", SMF__NOCREATE_DATA, &data, status ); if( *status == SAI__OK ) { /* Get the instrument. */ if( data->hdr->instrument == INST__SCUBA2 ) { subinst = smf_calc_subinst( data->hdr, status ); if( subinst == SMF__SUBINST_850 ) { inst = SMF__INST_SCUBA_2_850; } else { inst = SMF__INST_SCUBA_2_450; } } else if( data->hdr->instrument == INST__ACSIS ) { inst = SMF__INST_ACSIS; } else if( *status == SAI__OK ) { *status = SAI__ERROR; if( data->file ) { smf_smfFile_msg( data->file, "FILE", 1, "one or more of " "the input data files" ); } else { msgSetc( "FILE", "one or more of the input data files" ); } errRep( "", "No tiles are yet defined for the instrument that " "created ^FILE.", status ); } /* Get the parameters that define the layout of sky tiles for the instrument. */ smf_jsatiling( inst, &skytiling, status ); /* For "JSA" - choose the best projection. */ if( astChrMatch( text, "JSA" ) ) { /* Use the FITS headers in the first raw data file to create an AST Circle describing the approximate area of the observation within the tracking system. */ circle = smf_mapregion_approx( igrp, status ); /* Convert the circle to ICRS (as used by the JSA all-sky grid). */ astSetC( circle, "System", "ICRS" ); /* Get a list of the tiles that touch this circle. */ tiles = smf_jsatiles_region( circle, &skytiling, &ntile, status ); /* Choose the best projection (i.e. the projection that puts the circle furthest away from any singularities). */ proj = smf_jsaproj( ntile, tiles, &skytiling, status); /* Free resources. */ tiles = astFree( tiles ); circle = astAnnul( circle ); /* If a good projection was specified, use it. Otherwise report an error. */ } else if( proj == SMF__JSA_NULL && *status == SAI__OK ) { *status = SAI__ERROR; errRepf( "", "Bad value '%s' supplied for parameter %s.", status, text, param ); } /* Report the projection type. */ msgOutf( " ", "The %s will be created on the JSA %s " "pixel grid.", status, (data->hdr->instrument==INST__ACSIS)?"cube":"map", smf_jsaproj_tostr( proj ) ); /* All tiles within the same JSA projection use the same WCS, so we get the WCS FrameSet for an arbitrary central tile, and use it for the full map. The exception is that tiles within the HPX facet that is split between bottom-left and top-right, use a different WCS (they have different reference points). But our choice of projection should mean that the map never falls in that facet. The base Frame will be GRID coords within the tile, and the current Frame will be ICRS (RA,Dec). */ smf_jsatile( ((skytiling.ntpf * skytiling.ntpf - 1) * 2) / 3, &skytiling, 0, proj, NULL, spacewcs, NULL, lbnd, ubnd, status ); /* Change the base Frame to be PIXEL. */ for( i = 1; i <= astGetI( *spacewcs, "NFrame" ); i++ ) { frm = astGetFrame( *spacewcs, i ); if( astChrMatch( astGetC( frm, "Domain" ), "PIXEL" ) ) { astSetI( *spacewcs, "Base", i ); } frm = astAnnul( frm ); } } /* Close the current input data file. */ smf_close_file( NULL, &data, status); } /* Otherwise get the parameter value as an NDF. */ } else { ndfAssoc( param, "READ", &refndf, status ); /* Get the WCS FrameSet from the reference NDF. */ ndfGtwcs( refndf, &refwcs, status ); /* Attempt to extract a new FrameSet from this WCS FrameSet, in which the current Frame is a SkyFrame, and the base Frame is a 2D PIXEL Frame. Since the NDF library sets the GRID Frame to be the Base Frame, we need to make the PIXEL Frame the base Frame first. The NDF library ensures that the pixel Frame is Frame 2. */ astSetI( refwcs, "Base", 2 ); *spacewcs = atlFrameSetSplit( refwcs, "SKY", NULL, NULL, status ); if( !(*spacewcs) ) { if( *status == SAI__OK ) { ndfMsg( "N", refndf ); *status = SAI__ERROR; errRep( "", "The supplied reference NDF (^N) either has no " "celestial WCS axes, or the celestial axes cannot " "be separated from the non-celestial axes.", status ); } /* The rest of makemap assumes that the sky frame axes are in the default order (lon,lat). If this is not the case, permute them. */ } else if( astGetI( *spacewcs, "IsLatAxis(1)" ) ) { perm[ 0 ] = 2; perm[ 1 ] = 1; astPermAxes( *spacewcs, perm ); } /* Now look for the spectral WCS (described by a DSBSpecFrame). */ smf_getspectralwcs( refwcs, 1, specwcs, status ); /* We no longer need the NDF so annul it. */ ndfAnnul( &refndf, status ); } } /* If no error has occurred, export any returned FrameSet pointers from the current AST context so that it will not be annulled when the AST context is ended. Otherwise, ensure a null pointer is returned. */ if( *status == SAI__OK ) { if( *spacewcs ) astExport( *spacewcs ); if( *specwcs ) astExport( *specwcs ); } else { if( *spacewcs ) *spacewcs = astAnnul( *spacewcs ); if( *specwcs ) *specwcs = astAnnul( *specwcs ); } /* End the AST context. This will annul all AST objects created within the context (except for those that have been exported from the context). */ astEnd; }
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 || 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, 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_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) { /* Calculate the fit but we can use the same cache that WVM uses */ size_t nframes = 0; smf_calc_csofit( data, extpars, &wvmtau, &nframes, status ); 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; } /* 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 ); } } /* 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) { size_t nframes = 0; smf_calc_csofit( data, 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. Both WVM 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; }
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_labelunit( Grp *igrp, int size, smfData *odata, int *status ){ /* Local Variables */ char label1[ SMF__CHARLABEL ];/* Label from first NDF */ char unit1[ SMF__CHARLABEL ]; /* Unit from first NDF */ int ifile; /* Index of current input file */ smfData *data = NULL; /* Pointer to data struct for current input file */ /* Check inherited status */ if( *status != SAI__OK ) return; /* Loop round all the input NDFs. */ for( ifile = 1; ifile <= size && *status == SAI__OK; ifile++ ) { char * unit = NULL; char * label = NULL; /* Obtain information about the current input NDF. */ smf_open_file( NULL, igrp, ifile, "READ", 0, &data, status ); if (*status == SAI__OK) { unit = data->hdr->units; label = data->hdr->dlabel; /* If this is the first input NDF, copy the Label and Unit string to the output NDF, and save them for later use. */ if( ifile == 1 ) { if( strlen( label ) ) { ndfCput( label, odata->file->ndfid, "Label", status ); } if( strlen( unit ) ) { ndfCput( unit, odata->file->ndfid, "Unit", status ); } one_strlcpy( label1, label, sizeof(label1), status ); one_strlcpy( unit1, unit, sizeof(unit1), status ); /* Otherwise, compare the Label and Unit strings for this input NDF with those from the first input NDF. If any difference is found issue a warning. */ } else if( *status == SAI__OK && strcmp( label, label1 ) ) { msgSeti( "I", ifile ); msgSetc( "L1", label1 ); msgSetc( "L", label ); smf_smfFile_msg( data->file, "N", 1, "<unknown file>" ); msgOutif( MSG__NORM, " ", " WARNING: Input ^I (^N) has Label " "'^L' but the first input had Label '^L1'.", status ); } else if( *status == SAI__OK && strcmp( unit, unit1 ) ) { msgSeti( "I", ifile ); msgSetc( "U1", unit1 ); msgSetc( "U", unit ); smf_smfFile_msg( data->file, "N", 1, "<unknown file>" ); msgOutif( MSG__NORM, " ", " WARNING: Input ^I (^N) has Unit " "'^U' but the first input had Unit '^U1'.", status ); } } /* Close the current input data file. */ smf_close_file( NULL, &data, status ); data = NULL; } }