void smf_tswcsOrder( AstFrameSet **tswcs, int isTordered, int *status ){ /* Local Variables*/ AstFrameSet *newfs; const char *domain; int perm[ 3 ]; int swap; /* Check inherited status */ if (*status != SAI__OK) return; /* Check if the axes in the FrameSet are already in the right order. */ domain = astGetC( *tswcs, "Domain(3)" ); if( *status == SAI__OK ) { if( !strcmp( domain, "TIME" ) ) { swap = ( isTordered == 0 ); } else { swap = ( isTordered != 0 ); } /* Swap axes if required. */ if( swap ) { /* Get the a list of the old WCS axis indices in their new order. */ if( isTordered ) { perm[ 0 ] = 2; perm[ 1 ] = 3; perm[ 2 ] = 1; } else { perm[ 0 ] = 3; perm[ 1 ] = 1; perm[ 2 ] = 2; } /* Permute the axes in the current Frame of the WCS FrmeSet. This also adjusts the mappings that connects the current Frame to the other Frames. */ astPermAxes( *tswcs, perm ); /* We also need to permute the Mapping that connects the base Frame (i.e. grid coords) to the other Frames in the same way. Since astPermAxes operates on the current Frame of a FrameSet, we need to invert the FrameSet temporarily. */ astInvert( *tswcs ); astPermAxes( *tswcs, perm ); astInvert( *tswcs ); /* Simplify the modified FrameSet. */ newfs = astSimplify( *tswcs ); (void) astAnnul( *tswcs ); *tswcs = newfs; } } }
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; }
void smf_rebincube_ast( ThrWorkForce *wf, smfData *data, int first, int last, int *ptime, dim_t nchan, dim_t ndet, dim_t nslice, dim_t nel, dim_t nxy, dim_t nout, dim_t dim[3], AstMapping *ssmap, AstSkyFrame *abskyfrm, AstMapping *oskymap, Grp *detgrp, int moving, int usewgt, int spread, const double params[], int genvar, double tfac, double fcon, float *data_array, float *var_array, double *wgt_array, float *texp_array, float *teff_array, int *good_tsys, int *nused, int *status ){ /* Local Variables */ AstCmpMap *detmap = NULL; /* Mapping from 1D det. index to 2D i/p "grid" coords */ AstMapping *dtotmap = NULL; /* 1D det index->o/p GRID Mapping */ AstMapping *fullmap = NULL; /* WCS->GRID LutMap from input WCS FrameSet */ AstMapping *lutmap = NULL; /* Mapping that identifies detectors to be used */ AstMapping *splut = NULL; /* Spatial LutMap */ AstMapping *sslut = NULL; /* Spectral LutMap */ AstMapping *totmap = NULL; /* WCS->GRID Mapping from input WCS FrameSet */ AstPermMap *pmap; /* Mapping to rearrange output axes */ const char *name = NULL; /* Pointer to current detector name */ const double *tsys = NULL; /* Pointer to Tsys value for first detector */ dim_t iv; /* Vector index into output 3D array */ double *detlut = NULL; /* Work space for detector mask */ double blk_bot[ 2*MAXTHREADS + 1 ]; /* First o/p channel no. in each block */ double con; /* Constant value */ double dtemp; /* Temporary value */ double tcon; /* Variance factor for whole time slice */ float *detwork = NULL; /* Work array for detector values */ float *tdata = NULL; /* Pointer to start of input time slice data */ float *varwork = NULL; /* Work array holding variances for 1 slice/channel */ float *vp = NULL; /* Pointer to next "varwork" element */ float invar; /* Input variance */ float rtsys; /* Tsys value */ float teff; /* Effective integration time */ float texp; /* Total time ( = ton + toff ) */ int *nexttime; /* Pointer to next time slice index to use */ int ast_flags; /* Basic flags to use with astRebinSeq */ int blk_size; /* Number of channels processed by a single thread */ int found; /* Was current detector name found in detgrp? */ int iblock; /* Index of current spectral block */ dim_t ichan; /* Index of current channel */ dim_t idet; /* detector index */ int ignore; /* Ignore this time slice? */ int inperm[ 3 ]; /* Input axis permutation array */ dim_t itime; /* Index of current time slice */ int junk; /* Unused parameter */ int lbnd_in[ 2 ]; /* Lower input bounds on receptor axis */ int ldim[ 3 ]; /* Output array lower GRID bounds */ int maxthreads; /* Max no. of threads to use when re-binning */ int nblock; /* Number of spectral blocks */ int nthreads; /* Number of threads to use when re-binning */ int outperm[ 3 ]; /* Output axis permutation array */ int timeslice_size; /* Number of elements in a time slice */ int ubnd_in[ 2 ]; /* Upper input bounds on receptor axis */ int uddim[ 1 ]; /* Detector array upper GRID bounds */ int udim[ 3 ]; /* Output array upper GRID bounds */ smfHead *hdr = NULL; /* Pointer to data header for this time slice */ /* Check the inherited status. */ if( *status != SAI__OK ) return; /* Store a pointer to the input NDFs smfHead structure. */ hdr = data->hdr; /* Fill an array with the lower grid index bounds of the output. */ ldim[ 0 ] = 1; ldim[ 1 ] = 1; ldim[ 2 ] = 1; /* Integer upper grid index bounds of the output. */ udim[ 0 ] = dim[ 0 ]; udim[ 1 ] = dim[ 1 ]; udim[ 2 ] = dim[ 2 ]; /* Integer upper bounds of detector array. */ uddim[ 0 ] = ndet; /* Store the size of an input time slice. */ timeslice_size = nel/nslice; /* Create a LutMap that holds the output spectral axis GRID value at the centre of each input spectral axis pixel. LutMaps are faster to evaluate, and so astRebinSeq will go faster. We can use LutMaps without loosing accuracy since astRebinSeq only ever transforms the GRID values at input pixel centres (i.e. integer GRID values), and so the LutMap will always return a tabulated value rather than an interpolated value. */ atlTolut( (AstMapping *) ssmap, 1.0, (double) nchan, 1.0, "LutInterp=1", &sslut, status ); /* If this is the first pass through this file, initialise the arrays. */ if( first ) smf_rebincube_init( 0, nxy, nout, genvar, data_array, var_array, wgt_array, texp_array, teff_array, &junk, status ); /* Initialisation the flags for astRebinSeq (we do not include flag AST__REBININIT because the arrays have been initialised). */ ast_flags = AST__USEBAD; if( usewgt ) ast_flags = ast_flags | AST__VARWGT; if( genvar == 1 ) { ast_flags = ast_flags | AST__GENVAR; } else if( genvar == 2 ) { ast_flags = ast_flags | AST__USEVAR; } /* If required, allocate a work array to hold all the input variances for a single time slice. */ if( usewgt || genvar == 2 ) varwork = astMalloc( timeslice_size * sizeof( float ) ); /* Allocate a work array to hold the exposure time for each detector. */ detwork = astMalloc( ndet * sizeof( float ) ); /* If we are dealing with more than 1 detector, create a LutMap that holds the input GRID index of every detector to be included in the output, and AST__BAD for every detector that is not to be included in the output cube. First allocate the work space for the LUT. */ if( ndet > 1 ) { detlut = astMalloc( ndet*sizeof( double ) ); /* Initialise a string to point to the name of the first detector for which data is available */ name = hdr->detname; /* Loop round all detectors for which data is available. */ for( idet = 0; idet < ndet; idet++ ) { /* Store the input GRID coord of this detector. GRID coords start at 1, not 0. */ detlut[ idet ] = idet + 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 coord bad. This will cause astRebinSeq to ignore data from the detector. */ if( detgrp ) { found = grpIndex( name, detgrp, 1, status ); if( !found ) detlut[ idet ] = AST__BAD; } /* Move on to the next available detector name. */ name += strlen( name ) + 1; } /* Create the LutMap. */ lutmap = (AstMapping *) astLutMap( ndet, detlut, 1.0, 1.0, "LutInterp=1" ); /* If we only have 1 detector, use a UnitMap instead of a LutMap (lutMaps must have 2 or more table entries). */ } else { lutmap = (AstMapping *) astUnitMap( 1, " " ); } /* Combine the above LutMap with a 1-input, 2-output PermMap that copies its input to create its first output, and assigns a constant value of 1.0 to its second output. We need to do this because smf_tslice returns a 2D GRID system (even though the second GRID axis is not actually used). */ inperm[ 0 ] = 1; outperm[ 0 ] = 1; outperm[ 1 ] = -1; con = 1.0; detmap = astCmpMap( lutmap, astPermMap( 1, inperm, 2, outperm, &con, " " ), 1, " " ); /* Store the bounds of a single time slice grid. */ lbnd_in[ 0 ] = 1; ubnd_in[ 0 ] = nchan; lbnd_in[ 1 ] = 1; ubnd_in[ 1 ] = ndet; /* Create a PermMap that can be used to re-order the output axes so that channel number is axis 3. */ outperm[ 0 ] = 2; outperm[ 1 ] = 3; outperm[ 2 ] = 1; inperm[ 0 ] = 3; inperm[ 1 ] = 1; inperm[ 2 ] = 2; pmap = astPermMap( 3, inperm, 3, outperm, NULL, " " ); /* If we are using multiple threads to rebin spectral blocks in parallel, calculate the number of channels that are processed by each thread, and the number of threads to use. The whole output spectrum is divided up into blocks. The number of blocks is two times the number of threads, and each thread rebins two adjacent blocks. Alternate blocks are re-binned simultanously. First, the odd numbered blocks are re-binned (one by each thread). When all odd numbered blocks have been re-binned, the even numbered blocks are re-binned. We ensure that the number of threads used results in a block size that is larger than the spreading width produced by the requested spreading scheme. This means that no pair of simultanously executing threads will ever try to write to the same channel of the output spectrum. */ maxthreads = wf ? wf->nworker : 1; if( maxthreads > MAXTHREADS ) maxthreads = MAXTHREADS; if( maxthreads > 1 ) { /* Find the largest number of threads into which each output spectrum can be split. The limit is imposes by the requirement that each block is larger than the pixel spreading produced by the requested spreading scheme. */ nthreads = ( ( dim[ 2 ] + 1 )/2 )/smf_spreadwidth( spread, params, status ); /* If the spectral range is less than twice the spreading width, we cannot use multiple threads. */ if( nthreads > 1 ) { /* Restrict the number of threads to be no more than the number of workers available in the work force. */ if( nthreads > maxthreads ) nthreads = maxthreads; /* Find the number of output channels in each spectral block. */ blk_size = ( dim[ 2 ] - 1 )/( 2*nthreads ) + 1; /* Set up the first output channel number within each block. */ nblock = 2*nthreads; for( iblock = 0; iblock < nblock; iblock++ ) { blk_bot[ iblock ] = (double) ( iblock*blk_size + 1 ); } /* Add in the first channel number beyond the last block. */ blk_bot[ nblock ] = blk_bot[ nblock - 1 ] + blk_size; /* If the output spectrum is too short to guarantee that there are any independent blocks of output channels, we process the whole spectrum in a single thread. */ } else { nthreads = 1; nblock = 1; blk_bot[ 0 ] = 1.0; blk_bot[ 1 ] = (double) ( dim[ 2 ] + 1 ); } /* If multiple threads are not available, we process the whole spectrum in a single thread. */ } else { nthreads = 1; nblock = 1; blk_bot[ 0 ] = 1.0; blk_bot[ 1 ] = (double) ( dim[ 2 ] + 1 ); } /* Convert the block boundaries from output channel numbers into input channel numbers. */ astTran1( ssmap, nblock + 1, blk_bot, 0, blk_bot ); /* Ensure they are in increasing order, and are not outside the bounds of the input array. */ if( blk_bot[ 0 ] > blk_bot[ 1 ] ) { for( iblock = 0; iblock < ( nblock + 1 )/2; iblock++ ) { dtemp = blk_bot[ nblock - iblock ]; blk_bot[ nblock - iblock ] = blk_bot[ iblock ]; blk_bot[ iblock ] = dtemp; } } for( iblock = 0; iblock <= nblock; iblock++ ) { if( blk_bot[ iblock ] < 1 ) { blk_bot[ iblock ] = 1.0; } else if( blk_bot[ iblock ] > nchan ) { blk_bot[ iblock ] = nchan; } } /* Initialise a pointer to the next time slice index to be used. */ nexttime = ptime; /* Initialise the progress meter. */ smf_reportprogress( nslice, status ); /* Loop round all time slices in the input NDF. */ for( itime = 0; itime < nslice && *status == SAI__OK; itime++ ) { /* If this time slice is not being pasted into the output cube, pass on. */ if( nexttime ){ if( *nexttime != (int) itime ) continue; nexttime++; } /* Store a pointer to the first input data value in this time slice. */ tdata = ( (float *) (data->pntr)[ 0 ] ) + itime*timeslice_size; /* Begin an AST context. Having this context within the time slice loop helps keep the number of AST objects in use to a minimum. */ astBegin; /* Get a Mapping from the spatial GRID axes in the input the spatial GRID axes in the output for the current time slice. Note this has to be done first since it stores details of the current time slice in the "smfHead" structure inside "data", and this is needed by subsequent functions. */ totmap = smf_rebin_totmap( data, itime, abskyfrm, oskymap, moving, status ); if( !totmap ) break; /* Get the effective exposure time, the total exposure time, and the Tsys->Variance onversion factor for this time slice. Also get a pointer to the start of the Tsys array. */ tsys = smf_rebincube_tcon( hdr, itime, fcon, &texp, &teff, &tcon, status ); /* So "totmap" is a 2-input, 2-output Mapping that transforms the input spatial GRID coords into output spatial GRID coords. In order to speed up astRebinSeq we represent this by a pair of parallel LutMaps. To do this (using atlTolut) we need a Mapping which only has 1 input, so we preceed "totmap" with "detmap" (which also has the effect of exluding data from unrequired detectors). We then combine this Mapping in parallel with the spectral LutMap to get a 2-input (channel number, detector index) and 3-output (output grid coords) Mapping. We finally add a PermMap to re-arrange the output axes so that channel number is axis 3 in the output. */ dtotmap = (AstMapping *) astCmpMap( detmap, totmap, 1, " " ); if( ndet > 1 ) { atlTolut( dtotmap, 1.0, (double) ndet, 1.0, "LutInterp=1", &splut, status ); } else { splut = astClone( dtotmap ); } fullmap = astSimplify( astCmpMap( astCmpMap( sslut, splut, 0, " " ), pmap, 1, " " ) ); /* If required calculate the variance associated with each value in the current time slice. based on the input Tsys values. If they are needed, but not available, ignored the time slice. */ ignore = 0; if( varwork ) { ignore = 1; vp = varwork; for( idet = 0; idet < ndet; idet++ ) { invar = VAL__BADR; rtsys = tsys ? (float) tsys[ idet ] : VAL__BADR; if( rtsys <= 0.0 ) rtsys = VAL__BADR; if( rtsys != VAL__BADR ) { *good_tsys = 1; if( tcon != VAL__BADD ) { invar = tcon*rtsys*rtsys; ignore = 0; } } for( ichan = 0; ichan < nchan; ichan++ ) *(vp++) = invar; } } /* Unless we are ignoring this time slice, paste it into the 3D output cube. The smf_rebincube_seqf function is a wrapper for astRebinSeqF that splits the total job up between "nthreads" threads running in parallel. */ if( !ignore ) { smf_rebincube_seqf( wf, nthreads, blk_bot, fullmap, 0.0, 2, lbnd_in, ubnd_in, tdata, varwork, spread, params, ast_flags, 0.0, 50, VAL__BADR, 3, ldim, udim, lbnd_in, ubnd_in, data_array, var_array, wgt_array, nused, status ); /* Now we update the total exposure time array. Scale the exposure time of this time slice in order to reduce its influence on the output expsoure times if it does not have much spectral overlap with the output cube. then fill the 1D work array with this constant value and paste it into the 2D texp_array using the spatial mapping. Note we want the simple sum of the exposure times, with no normalisation. SO we use the AST__NONORM flag which means we do not need to supply a weights array. */ if( texp != VAL__BADR ) { texp *= tfac; for( iv = 0; iv < ndet; iv++ ) detwork[ iv ] = texp; astRebinSeqF( splut, 0.0, 1, ldim, uddim, detwork, NULL, spread, params, AST__NONORM, 0.0, 50, VAL__BADR, 2, ldim, udim, ldim, uddim, texp_array, NULL, NULL, NULL ); } /* Now do the same with the effective exposure time. */ if( teff != VAL__BADR ) { teff *= tfac; for( iv = 0; iv < ndet; iv++ ) detwork[ iv ] = teff; astRebinSeqF( splut, 0.0, 1, ldim, uddim, detwork, NULL, spread, params, AST__NONORM, 0.0, 50, VAL__BADR, 2, ldim, udim, ldim, uddim, teff_array, NULL, NULL, NULL ); } } /* Update the progress meter. */ smf_reportprogress( 0, status ); /* End the AST context. */ astEnd; } /* If this is the final pass through this function, normalise the returned data and variance values. */ if( last ) { /* Create a dummy mapping that can be used with astRebinSeq (it is not actually used for anything since we are not adding any more data into the output arrays). */ fullmap = (AstMapping *) astPermMap( 2, NULL, 3, NULL, NULL, " " ); /* Normalise the data values. We do not normalise the exposure time arrays. */ astRebinSeqF( fullmap, 0.0, 2, lbnd_in, ubnd_in, NULL, NULL, spread, params, AST__REBINEND | ast_flags, 0.0, 50, VAL__BADR, 3, ldim, udim, lbnd_in, ubnd_in, data_array, var_array, wgt_array, nused ); fullmap = astAnnul(fullmap); } /* Free resources. */ detlut = astFree( detlut ); detwork = astFree( detwork ); varwork = astFree( varwork ); }
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_calc_mapcoord( ThrWorkForce *wf, AstKeyMap *config, smfData *data, AstFrameSet *outfset, int moving, int *lbnd_out, int *ubnd_out, fts2Port fts_port, int flags, int *status ) { /* Local Variables */ AstSkyFrame *abskyfrm = NULL;/* Output SkyFrame (always absolute) */ AstMapping *bolo2map=NULL; /* Combined mapping bolo->map coordinates */ int bndndf=NDF__NOID; /* NDF identifier for map bounds */ void *data_pntr[1]; /* Array of pointers to mapped arrays in ndf */ int *data_index; /* Mapped DATA_ARRAY part of NDF */ int docalc=1; /* If set calculate the LUT */ int doextension=0; /* Try to write LUT to MAPCOORD extension */ smfFile *file=NULL; /* smfFile pointer */ AstObject *fstemp = NULL; /* AstObject version of outfset */ int ii; /* loop counter */ int indf_lat = NDF__NOID; /* Identifier for NDF to receive lat values */ int indf_lon = NDF__NOID; /* Identifier for NDF to receive lon values */ smfCalcMapcoordData *job_data=NULL; /* Array of job */ int lbnd[1]; /* Pixel bounds for 1d pointing array */ int lbnd_old[2]; /* Pixel bounds for existing LUT */ int lbnd_temp[1]; /* Bounds for bounds NDF component */ int lutndf=NDF__NOID; /* NDF identifier for coordinates */ AstMapping *map2sky_old=NULL;/* Existing mapping map->celestial coord. */ HDSLoc *mapcoordloc=NULL; /* HDS locator to the MAPCOORD extension */ int nw; /* Number of worker threads */ AstFrameSet *oldfset=NULL; /* Pointer to existing WCS info */ AstSkyFrame *oskyfrm = NULL; /* SkyFrame from the output WCS Frameset */ smfCalcMapcoordData *pdata=NULL; /* Pointer to job data */ double *lat_ptr = NULL; /* Pointer to array to receive lat values */ double *lon_ptr = NULL; /* Pointer to array to receive lon values */ int ubnd[1]; /* Pixel bounds for 1d pointing array */ int ubnd_old[2]; /* Pixel bounds for existing LUT */ int ubnd_temp[1]; /* Bounds for bounds NDF component */ int *lut = NULL; /* The lookup table */ dim_t nbolo=0; /* Number of bolometers */ dim_t ntslice=0; /* Number of time slices */ int nmap; /* Number of mapped elements */ AstMapping *sky2map=NULL; /* Mapping celestial->map coordinates */ size_t step; /* step size for dividing up work */ AstCmpMap *testcmpmap=NULL; /* Combined forward/inverse mapping */ AstMapping *testsimpmap=NULL;/* Simplified testcmpmap */ double *theta = NULL; /* Scan direction at each time slice */ int tstep; /* Time slices between full Mapping calculations */ int exportlonlat; /* Dump longitude and latitude values? */ /* Main routine */ if (*status != SAI__OK) return; /* How many threads do we get to play with */ nw = wf ? wf->nworker : 1; /* Initialize bounds to avoid compiler warnings */ lbnd_old[0] = 0; lbnd_old[1] = 0; ubnd_old[0] = 0; ubnd_old[1] = 0; /* Check for pre-existing LUT and de-allocate it. This will only waste time if the MAPCOORD extension is found to be valid and it has to be re-loaded from disk. */ smf_close_mapcoord( data, status ); /* Assert ICD data order */ smf_dataOrder( data, 1, status ); /* Get the data dimensions */ smf_get_dims( data, NULL, NULL, &nbolo, &ntslice, NULL, NULL, NULL, status ); /* If SMF__NOCREATE_FILE is not set, and file associated with an NDF, map a new MAPCOORD extension (or verify an existing one) */ if( !(flags & SMF__NOCREATE_FILE) && data->file ) { doextension = 1; } else { doextension = 0; docalc = 1; } /* Create / check for existing MAPCOORD extension */ if( doextension ) { file = data->file; /* Check type of file before proceeding */ if( file->isSc2store ) { *status = SAI__ERROR; errRep(FUNC_NAME, "File was opened by sc2store library (raw data?)", status); } if( !file->isTstream ) { *status = SAI__ERROR; errRep(FUNC_NAME, "File does not contain time stream data",status); } /* Get HDS locator to the MAPCOORD extension */ mapcoordloc = smf_get_xloc( data, "MAPCOORD", "MAP_PROJECTION", "UPDATE", 0, 0, status ); /* Obtain NDF identifier/placeholder for LUT in MAPCOORD extension*/ lbnd[0] = 0; ubnd[0] = nbolo*ntslice-1; lutndf = smf_get_ndfid( mapcoordloc, "LUT", "UPDATE", "UNKNOWN", "_INTEGER", 1, lbnd, ubnd, status ); if( *status == SAI__OK ) { /* store the NDF identifier */ file->mapcoordid = lutndf; /* Create sky to output grid mapping using the base coordinates to get the coordinates of the tangent point if it hasn't been done yet. */ sky2map = astGetMapping( outfset, AST__CURRENT, AST__BASE ); } /* Before mapping the LUT, first check for existing WCS information and LBND/UBND for the output map. If they are already correct don't bother re-calculating the LUT! */ if( *status == SAI__OK ) { /* Try reading in the WCS information */ kpg1Wread( mapcoordloc, "WCS", &fstemp, status ); oldfset = (AstFrameSet*)fstemp; if( *status == SAI__OK ) { /* Check that the old and new mappings are the same by checking that combining one with the inverse of the other reduces to a UnitMap. */ map2sky_old = astGetMapping( oldfset, AST__BASE, AST__CURRENT ); testcmpmap = astCmpMap( map2sky_old, sky2map, 1, " " ); testsimpmap = astSimplify( testcmpmap ); if( astIsAUnitMap( testsimpmap ) ) { /* The mappings are the same, now just check the pixel bounds in the output map */ lbnd_temp[0] = 1; ubnd_temp[0] = 2; bndndf = smf_get_ndfid( mapcoordloc, "LBND", "READ", "UNKNOWN", "_INTEGER", 1, lbnd_temp, ubnd_temp, status ); if( *status == SAI__OK ) { ndfMap( bndndf, "DATA", "_INTEGER", "READ", data_pntr, &nmap, status ); data_index = data_pntr[0]; if( *status == SAI__OK ) { lbnd_old[0] = data_index[0]; lbnd_old[1] = data_index[1]; } ndfAnnul( &bndndf, status ); } bndndf = smf_get_ndfid( mapcoordloc, "UBND", "READ", "UNKNOWN", "_INTEGER", 1, lbnd_temp, ubnd_temp, status ); if( *status == SAI__OK ) { ndfMap( bndndf, "DATA", "_INTEGER", "READ", data_pntr, &nmap, status ); data_index = data_pntr[0]; if( *status == SAI__OK ) { ubnd_old[0] = data_index[0]; ubnd_old[1] = data_index[1]; } ndfAnnul( &bndndf, status ); } if( *status == SAI__OK ) { /* If we get this far finally do the bounds check! */ if( (lbnd_old[0] == lbnd_out[0]) && (lbnd_old[1] == lbnd_out[1]) && (ubnd_old[0] == ubnd_out[0]) && (ubnd_old[1] == ubnd_out[1]) ) { docalc = 0; /* We don't have to re-calculate the LUT */ msgOutif(MSG__VERB," ",FUNC_NAME ": Existing LUT OK", status); } } } /* Bad status / AST errors at this point due to problems with MAPCOORD. Annul and continue calculating new MAPCOORD extension. */ astClearStatus; errAnnul(status); } else { /* Bad status due to non-existence of MAPCOORD. Annul and continue */ errAnnul(status); } } } /* If we need to calculate the LUT do it here */ if( docalc && (*status == SAI__OK) ) { msgOutif(MSG__VERB," ", FUNC_NAME ": Calculate new LUT", status); /* Get the increment in time slices between full Mapping calculations. The Mapping for intermediate time slices will be approximated. */ dim_t dimval; smf_get_nsamp( config, "TSTEP", data, &dimval, status ); tstep = dimval; /* Get space for the LUT */ if( doextension ) { /* Map the LUT array */ ndfMap( lutndf, "DATA", "_INTEGER", "WRITE", data_pntr, &nmap, status ); data_index = data_pntr[0]; if( *status == SAI__OK ) { lut = data_index; } else { errRep( FUNC_NAME, "Unable to map LUT in MAPCOORD extension", status); } } else { /* alloc the LUT and THETA arrays */ lut = astMalloc( (nbolo*ntslice)*sizeof(*(data->lut)) ); theta = astMalloc( ntslice*sizeof(*(data->theta)) ); } /* Retrieve the sky2map mapping from the output frameset (actually map2sky) */ oskyfrm = astGetFrame( outfset, AST__CURRENT ); sky2map = astGetMapping( outfset, AST__BASE, AST__CURRENT ); /* If the longitude and latitude is being dumped, create new NDFs to hold them, and map them. */ if( config ) { astMapGet0I( config, "EXPORTLONLAT", &exportlonlat ); if( exportlonlat ) { lon_ptr = smf1_calc_mapcoord1( data, nbolo, ntslice, oskyfrm, &indf_lon, 1, status ); lat_ptr = smf1_calc_mapcoord1( data, nbolo, ntslice, oskyfrm, &indf_lat, 2, status ); } } /* Invert the mapping to get Output SKY to output map coordinates */ astInvert( sky2map ); /* Create a SkyFrame in absolute coordinates */ abskyfrm = astCopy( oskyfrm ); astClear( abskyfrm, "SkyRefIs" ); astClear( abskyfrm, "SkyRef(1)" ); astClear( abskyfrm, "SkyRef(2)" ); if( *status == SAI__OK ) { /* --- Begin parellelized portion ------------------------------------ */ /* Start a new job context. Each call to thrWait within this context will wait until all jobs created within the context have completed. Jobs created in higher contexts are ignored by thrWait. */ thrBeginJobContext( wf, status ); /* Allocate job data for threads */ job_data = astCalloc( nw, sizeof(*job_data) ); if( *status == SAI__OK ) { /* Set up job data, and start calculating pointing for blocks of time slices in different threads */ if( nw > (int) ntslice ) { step = 1; } else { step = ntslice/nw; } for( ii=0; (*status==SAI__OK)&&(ii<nw); ii++ ) { pdata = job_data + ii; /* Blocks of time slices */ pdata->t1 = ii*step; pdata->t2 = (ii+1)*step-1; /* Ensure that the last thread picks up any left-over tslices */ if( (ii==(nw-1)) && (pdata->t1<(ntslice-1)) ) { pdata->t2=ntslice-1; } pdata->ijob = -1; pdata->lut = lut; pdata->theta = theta; pdata->lbnd_out = lbnd_out; pdata->moving = moving; pdata->ubnd_out = ubnd_out; pdata->tstep = tstep; pdata->lat_ptr = lat_ptr; pdata->lon_ptr = lon_ptr; pdata->fts_port = fts_port; /* Make deep copies of AST objects and unlock them so that each thread can then lock them for their own exclusive use */ pdata->abskyfrm = astCopy( abskyfrm ); astUnlock( pdata->abskyfrm, 1 ); pdata->sky2map = astCopy( sky2map ); astUnlock( pdata->sky2map, 1 ); /* Similarly, make a copy of the smfData, including only the header information which each thread will need in order to make calls to smf_rebin_totmap */ pdata->data = smf_deepcopy_smfData( data, 0, SMF__NOCREATE_FILE | SMF__NOCREATE_DA | SMF__NOCREATE_FTS | SMF__NOCREATE_DATA | SMF__NOCREATE_VARIANCE | SMF__NOCREATE_QUALITY, 0, 0, status ); smf_lock_data( pdata->data, 0, status ); } for( ii=0; ii<nw; ii++ ) { /* Submit the job */ pdata = job_data + ii; pdata->ijob = thrAddJob( wf, THR__REPORT_JOB, pdata, smfCalcMapcoordPar, 0, NULL, status ); } /* Wait until all of the jobs submitted within the current job context have completed */ thrWait( wf, status ); } /* End the current job context. */ thrEndJobContext( wf, status ); /* --- End parellelized portion -------------------------------------- */ /* Set the lut pointer in data to the buffer */ data->lut = lut; data->theta = theta; /* Write the WCS for the projection to the extension */ if( doextension ) { kpg1Wwrt( (AstObject*)outfset, "WCS", mapcoordloc, status ); /* Write the pixel bounds for the map to the extension */ lbnd_temp[0] = 1; /* Don't get confused! Bounds for NDF that will */ ubnd_temp[0] = 2; /* contain the bounds for the output 2d map! */ bndndf = smf_get_ndfid( mapcoordloc, "LBND", "UPDATE", "UNKNOWN", "_INTEGER", 1, lbnd_temp, ubnd_temp, status ); ndfMap( bndndf, "DATA", "_INTEGER", "WRITE", data_pntr, &nmap, status ); data_index = data_pntr[0]; if( *status == SAI__OK ) { data_index[0] = lbnd_out[0]; data_index[1] = lbnd_out[1]; } else { errRep( FUNC_NAME, "Unable to map LBND in MAPCOORD extension", status); } ndfAnnul( &bndndf, status ); bndndf = smf_get_ndfid( mapcoordloc, "UBND", "UPDATE", "UNKNOWN", "_INTEGER", 1, lbnd_temp, ubnd_temp, status ); ndfMap( bndndf, "DATA", "_INTEGER", "WRITE", data_pntr, &nmap, status ); data_index = data_pntr[0]; if( *status == SAI__OK ) { data_index[0] = ubnd_out[0]; data_index[1] = ubnd_out[1]; } else { errRep( FUNC_NAME, "Unable to map UBND in MAPCOORD extension", status); } ndfAnnul( &bndndf, status ); } } } /* Clean Up */ if( testsimpmap ) testsimpmap = astAnnul( testsimpmap ); if( testcmpmap ) testcmpmap = astAnnul( testcmpmap ); if( map2sky_old ) map2sky_old = astAnnul( map2sky_old ); if( oldfset ) oldfset = astAnnul( oldfset ); if (sky2map) sky2map = astAnnul( sky2map ); if (bolo2map) bolo2map = astAnnul( bolo2map ); if( abskyfrm ) abskyfrm = astAnnul( abskyfrm ); if( oskyfrm ) oskyfrm = astAnnul( oskyfrm ); if( mapcoordloc ) datAnnul( &mapcoordloc, status ); if( indf_lat != NDF__NOID ) ndfAnnul( &indf_lat, status ); if( indf_lon != NDF__NOID ) ndfAnnul( &indf_lon, status ); /* If we get this far, docalc=0, and status is OK, there must be a good LUT in there already. Map it so that it is accessible to the caller; "UPDATE" so that the caller can modify it if desired. */ if( (*status == SAI__OK) && (docalc == 0) ) { smf_open_mapcoord( data, "UPDATE", status ); } /* Clean up job data */ if( job_data ) { for( ii=0; (*status==SAI__OK)&&(ii<nw); ii++ ) { pdata = job_data + ii; if( pdata->data ) { smf_lock_data( pdata->data, 1, status ); smf_close_file( &(pdata->data), status ); } astLock( pdata->abskyfrm, 0 ); pdata->abskyfrm = astAnnul( pdata->abskyfrm ); astLock( pdata->sky2map, 0 ); pdata->sky2map = astAnnul( pdata->sky2map ); } job_data = astFree( job_data ); } }
/* Main entry */ void smf_jsadicer( int indf, const char *base, int trim, smf_inst_t instrument, smf_jsaproj_t proj, size_t *ntile, Grp *grp, int *status ){ /* Local Variables: */ AstBox *box; AstFitsChan *fc; AstFrame *specfrm = NULL; AstFrame *tile_frm = NULL; AstFrameSet *iwcs; AstFrameSet *tfs = NULL; AstFrameSet *tile_wcs; AstMapping *ndf_map = NULL; AstMapping *p2pmap = NULL; AstMapping *specmap = NULL; AstMapping *tile_map = NULL; AstRegion *region; Grp *grpt = NULL; char *path; char dtype[ NDF__SZFTP + 1 ]; char jsatile_comment[45]; char type[ NDF__SZTYP + 1 ]; const char *dom = NULL; const char *keyword; const char *latsys = NULL; const char *lonsys = NULL; double *pd; double dlbnd[3]; double dubnd[3]; double gcen[3]; double lbnd_in[3]; double lbnd_out[3]; double ubnd_in[3]; double ubnd_out[3]; float *pf; int *created_tiles = NULL; int *tiles; int axlat; int axlon; int axspec; int bbox[ 6 ]; int i; int ifrm; int igrid; int indfo; int indfs; int indfx; int inperm[3]; int ipixel; int ishpx; int isxph; int itile; int ix; int iy; int iz; int junk; int latax = -1; int lbnd[3]; int lbnd_tile[ 3 ]; int lbndx[ NDF__MXDIM ]; int lonax = -1; int nbase; int ndim; int ndimx; int nfrm; int nsig; int ntiles; int olbnd[ 3 ]; int oubnd[ 3 ]; int outperm[ 3 ]; int place; int qual; int tile_index; int tile_lbnd[2]; int tile_ubnd[2]; int ubnd[3]; int ubnd_tile[ 3 ]; int ubndx[ NDF__MXDIM ]; int var; size_t iext; size_t size; smfJSATiling tiling; unsigned char *ipq = NULL; void *ipd = NULL; void *ipv = NULL; /* Initialise */ *ntile = 0; /* Check inherited status */ if( *status != SAI__OK ) return; /* Begin an AST context. */ astBegin; /* Begin an NDF context. */ ndfBegin(); /* Note the used length of the supplied base string. If it ends with ".sdf", reduce it by 4. */ nbase = astChrLen( base ); if( !strcmp( base + nbase - 4, ".sdf" ) ) nbase -= 4; /* Allocate a buffer large enough to hold the full path for an output NDF. */ path = astMalloc( nbase + 25 ); /* Get the WCS from the NDF. */ kpg1Gtwcs( indf, &iwcs, status ); /* Note if the NDF projection is HPX or XPH. */ ishpx = astChrMatch( astGetC( iwcs, "Projection" ), "HEALPix" ); isxph = astChrMatch( astGetC( iwcs, "Projection" ), "polar HEALPix" ); /* Report an error if the NDFs projection is neither of these. */ if( !ishpx && !isxph && *status == SAI__OK ) { ndfMsg( "N", indf ); *status = SAI__ERROR; errRep( "", "The input NDF (^N) does not appear to be gridded " "on the JSA all-sky pixel grid.", status ); } /* Get the bounds of the NDF in pixel indices and the the corresponding double precision GRID bounds (reduce the size of the grid by a small amount to avoid problems with tiles that are on the edge of the valid sky regions - astMapRegion can report an error for such tiles). Also store the GRID coords of the centre. Also count the number of significant pixel axes. */ ndfBound( indf, 3, lbnd, ubnd, &ndim, status ); nsig = 0; for( i = 0; i < ndim; i++ ) { dlbnd[ i ] = 0.5 + 0.1; dubnd[ i ] = ubnd[ i ] - lbnd[ i ] + 1.5 - 0.1; gcen[ i ] = 0.5*( dlbnd[ i ] + dubnd[ i ] ); if( ubnd[ i ] > lbnd[ i ] ) nsig++; } /* Find the one-based indices of the RA, Dec and spectral axes in the current Frame of the NDF. */ axlon = 0; if( astGetI( iwcs, "IsLonAxis(1)" ) ) { axlon = 1; lonsys = astGetC( iwcs, "System(1)" ); } else if( astGetI( iwcs, "IsLonAxis(2)" ) ) { axlon = 2; lonsys = astGetC( iwcs, "System(2)" ); } else if( ndim == 3 && astGetI( iwcs, "IsLonAxis(3)" ) ) { axlon = 3; lonsys = astGetC( iwcs, "System(3)" ); } else if( *status == SAI__OK ) { *status = SAI__ERROR; errRep( "", "smf_jsadicer: Cannot find the longitude axis in the " "input NDF.", status ); } axlat = 0; if( astGetI( iwcs, "IsLatAxis(1)" ) ) { axlat = 1; latsys = astGetC( iwcs, "System(1)" ); } else if( astGetI( iwcs, "IsLatAxis(2)" ) ) { axlat = 2; latsys = astGetC( iwcs, "System(2)" ); } else if( ndim == 3 && astGetI( iwcs, "IsLatAxis(3)" ) ) { axlat = 3; latsys = astGetC( iwcs, "System(3)" ); } else if( *status == SAI__OK ) { *status = SAI__ERROR; errRep( "", "smf_jsadicer: Cannot find the latitude axis in the " "input NDF.", status ); } axspec = 6 - axlon - axlat; /* Report an error if the spatial axes are not ICRS RA and Dec. */ if( ( lonsys && strcmp( lonsys, "ICRS" ) ) || ( latsys && strcmp( latsys, "ICRS" ) ) ) { if( *status == SAI__OK ) { *status = SAI__ERROR; ndfMsg( "N", indf ); errRep( "", "smf_jsadicer: The spatial axes in '^N' are not " "ICRS RA and Dec.", status ); } } /* Create a Box describing the region covered by the NDF pixel grid in GRID coords. */ box = astBox( astGetFrame( iwcs, AST__BASE ), 1, dlbnd, dubnd, AST__NULL, " " ); /* Map this Box into the current WCS Frame of the NDF. */ region = astMapRegion( box, iwcs, iwcs ); /* If no instrument was specified, we will determine the instrument from the contexts of the FITS extension. Copy the NDF FITS extension to a FitsChan. Annul the error if the NDF no FITS extension. */ if( instrument == SMF__INST_NONE && *status == SAI__OK ) { kpgGtfts( indf, &fc, status ); if( *status == KPG__NOFTS ) { errAnnul( status ); fc = NULL; } } else { fc = NULL; } /* Get the parameters of the required tiling scheme. */ smf_jsainstrument( NULL, fc, instrument, &tiling, status ); /* Get a list of the JSA tiles touched by the supplied NDF. */ tiles = smf_jsatiles_region( region, &tiling, &ntiles, status ); if( ntiles == 0 && *status == SAI__OK ) { *status = SAI__ERROR; errRep( "", "smf_jsadicer: No JSA tiles found touching supplied NDF " "(programming error).", status ); } /* Does the input NDF have a Variance component? */ ndfState( indf, "Variance", &var, status ); /* Does the input NDF have a Quality component? */ ndfState( indf, "Quality", &qual, status ); /* Decide on the data type to use: _REAL or _DOUBLE. */ ndfMtype( "_REAL,_DOUBLE", indf, indf, "Data", type, sizeof(type), dtype, sizeof(dtype), status ); /* Tell the user what is happening. */ msgBlank( status ); msgOutf( "", "Dicing %s into JSA tiles:", status, ( nsig == 2 ) ? "map" : "cube" ); /* Loop round all tiles that overlap the supplied NDF. */ for( itile = 0; itile < ntiles && *status == SAI__OK; itile++ ) { tile_index = tiles[ itile ]; /* Get the spatial pixel bounds of the current tile within the requested JSA all-sky projection. Also get the (2D) WCS FrameSet for the tile. */ smf_jsatile( tile_index, &tiling, 0, proj, NULL, &tile_wcs, NULL, tile_lbnd, tile_ubnd, status ); /* Extract the tile pixel->WCS mapping and WCS Frame. We know the indices of the required Frames because they are hard-wired in smf_jsatile. */ tile_map = astGetMapping( tile_wcs, 3, 2 ); tile_frm = astGetFrame( tile_wcs, 2 ); /* Find the indices of the grid and pixel frames in the input NDF. */ ipixel = -1; igrid = astGetI( iwcs, "Base" ); nfrm = astGetI( iwcs, "NFrame" ); for( ifrm = 0; ifrm < nfrm; ifrm++ ) { dom = astGetC( astGetFrame( iwcs, ifrm + 1 ), "Domain" ); if( astChrMatch( dom, "PIXEL" ) ) ipixel = ifrm + 1; } /* If required, extract the pixel->spectral mapping and spectral frame in the input NDF, and add it in parallel with the above tile mapping. */ if( ndim == 3 ) { astSetI( iwcs, "Base", ipixel ); tfs = atlFrameSetSplit( iwcs, "DSBSPECTRUM SPECTRUM", NULL, NULL, status ); astSetI( iwcs, "Base", igrid ); if( tfs ) { specmap = astGetMapping( tfs, AST__BASE, AST__CURRENT ); specfrm = astGetFrame( tfs, AST__CURRENT ); } else if( *status == SAI__OK ) { *status = SAI__ERROR; ndfMsg( "N", indf ); errRep( "", "smf_jsadicer: Cannot find the spectral axis " "in '^N'.", status ); } tile_map = (AstMapping *) astCmpMap( tile_map, specmap, 0, " " ); tile_frm = (AstFrame *) astCmpFrame( tile_frm, specfrm, " " ); } /* Ensure the Epoch is inherited form the input NDF. */ astSetD( tile_frm, "Epoch", astGetD( iwcs, "Epoch" ) ); /* Currently tile axis 1 is RA, axis 2 is Dec and axis 3 (if present) is spectral. Append a PermMap that re-orders these tile WCS axes to match those of the NDF. */ outperm[ axlon - 1 ] = 1; outperm[ axlat - 1 ] = 2; outperm[ axspec - 1 ] = 3; inperm[ 0 ] = axlon; inperm[ 1 ] = axlat; inperm[ 2 ] = axspec; tile_map = (AstMapping *) astCmpMap( tile_map, astPermMap( ndim, inperm, ndim, outperm, NULL, " " ), 1, " " ); tile_map = astSimplify( tile_map ); /* Also re-order the WCS axes in the tile frame. */ astPermAxes( tile_frm, outperm ); /* We want the zero-based indicies of the input pixel axes corresponding to ra, dec and spectral. So find the indicies of the pixel axes in the supplied NDF that are most closely aligned with each WCS axis. */ atlPairAxes( iwcs, NULL, gcen, NULL, inperm, status ); if( inperm[ 0 ] == axlon ) { lonax = 0; } else if( inperm[ 1 ] == axlon ) { lonax = 1; } else { lonax = 2; } if( inperm[ 0 ] == axlat ) { latax = 0; } else if( inperm[ 1 ] == axlat ) { latax = 1; } else { latax = 2; } /* To get the mapping from pixel coords in the input NDF to pixel coords in the output NDF, we invert the above mapping so that it goes from WCS to pixel, and append it to the end of the NDF pixel->WCS mapping. */ ndf_map = astGetMapping( iwcs, ipixel, AST__CURRENT ); astInvert( tile_map ); p2pmap = (AstMapping *) astCmpMap( ndf_map, tile_map, 1, " " ); p2pmap = astSimplify( p2pmap ); astInvert( tile_map ); /* Show the bounds of the tile within the input NDF. */ msgOutiff( MSG__DEBUG, "", " tile %d has bounds (%d:%d,%d:%d) " "within the output NDF.", status, tile_index, tile_lbnd[ 0 ], tile_ubnd[ 0 ], tile_lbnd[ 1 ], tile_ubnd[ 1 ] ); /* Next job is to find the pixel bounds of the output NDF to create which will hold data for the current tile. First map the pixel bounds of the whole tile from output to input. */ lbnd_in[ 0 ] = tile_lbnd[ 0 ] - 0.5; lbnd_in[ 1 ] = tile_lbnd[ 1 ] - 0.5; lbnd_in[ 2 ] = lbnd[ 2 ] - 0.5; ubnd_in[ 0 ] = tile_ubnd[ 0 ] - 0.5; ubnd_in[ 1 ] = tile_ubnd[ 1 ] - 0.5; ubnd_in[ 2 ] = ubnd[ 2 ] - 0.5; astMapBox( p2pmap, lbnd_in, ubnd_in, 0, 1, lbnd_out + 0, ubnd_out + 0, NULL, NULL ); astMapBox( p2pmap, lbnd_in, ubnd_in, 0, 2, lbnd_out + 1, ubnd_out + 1, NULL, NULL ); if( ndim == 3 ) astMapBox( p2pmap, lbnd_in, ubnd_in, 0, 3, lbnd_out + 2, ubnd_out + 2, NULL, NULL ); lbnd_tile[ 0 ] = floor( lbnd_out[ 0 ] ) + 1; lbnd_tile[ 1 ] = floor( lbnd_out[ 1 ] ) + 1; lbnd_tile[ 2 ] = floor( lbnd_out[ 2 ] ) + 1; ubnd_tile[ 0 ] = floor( ubnd_out[ 0 ] ) + 1; ubnd_tile[ 1 ] = floor( ubnd_out[ 1 ] ) + 1; ubnd_tile[ 2 ] = floor( ubnd_out[ 2 ] ) + 1; /* Show the bounds of the tile within the input NDF. */ msgOutiff( MSG__DEBUG, "", " tile %d has bounds (%d:%d,%d:%d) " "within the input NDF.", status, tile_index, lbnd_tile[ 0 ], ubnd_tile[ 0 ], lbnd_tile[ 1 ], ubnd_tile[ 1 ] ); /* If required, trim the bounds to the extent of the input NDF. */ if( trim ) { if( lbnd_tile[ 0 ] < lbnd[ 0 ] ) lbnd_tile[ 0 ] = lbnd[ 0 ]; if( lbnd_tile[ 1 ] < lbnd[ 1 ] ) lbnd_tile[ 1 ] = lbnd[ 1 ]; if( lbnd_tile[ 2 ] < lbnd[ 2 ] ) lbnd_tile[ 2 ] = lbnd[ 2 ]; if( ubnd_tile[ 0 ] > ubnd[ 0 ] ) ubnd_tile[ 0 ] = ubnd[ 0 ]; if( ubnd_tile[ 1 ] > ubnd[ 1 ] ) ubnd_tile[ 1 ] = ubnd[ 1 ]; if( ubnd_tile[ 2 ] > ubnd[ 2 ] ) ubnd_tile[ 2 ] = ubnd[ 2 ]; } /* Check there is some overlap. */ if( lbnd_tile[ 0 ] <= ubnd_tile[ 0 ] && lbnd_tile[ 1 ] <= ubnd_tile[ 1 ] && lbnd_tile[ 2 ] <= ubnd_tile[ 2 ] ){ /* Now need to check if this section of the input NDF contains any good values. We also find the bounding box of the good values (within the input pixel coordinate system). So first obtain and map the required section of the input NDF. */ ndfSect( indf, ndim, lbnd_tile, ubnd_tile, &indfs, status ); ndfMap( indfs, "Data", type, "Read", &ipd, &junk, status ); if( var ) ndfMap( indfs, "Variance", type, "Read", &ipv, &junk, status ); if( qual ) ndfMap( indfs, "Quality", "_UBYTE", "Read", (void **) &ipq, &junk, status ); /* Initialise the pixel bounds (within the input NDF) of the box holding good data values for the current tile. */ bbox[ 0 ] = INT_MAX; bbox[ 1 ] = INT_MAX; bbox[ 2 ] = INT_MAX; bbox[ 3 ] = -INT_MAX; bbox[ 4 ] = -INT_MAX; bbox[ 5 ] = -INT_MAX; /* Loop round all pixels in the section. */ if( *status == SAI__OK ) { if( !strcmp( type, "_REAL" ) ) { pf = (float *) ipd; for( iz = lbnd_tile[ 2 ]; iz <= ubnd_tile[ 2 ]; iz++ ) { for( iy = lbnd_tile[ 1 ]; iy <= ubnd_tile[ 1 ]; iy++ ) { for( ix = lbnd_tile[ 0 ]; ix <= ubnd_tile[ 0 ]; ix++ ) { if( *(pf++) != VAL__BADR ) { if( ix < bbox[ 0 ] ) bbox[ 0 ] = ix; if( iy < bbox[ 1 ] ) bbox[ 1 ] = iy; if( iz < bbox[ 2 ] ) bbox[ 2 ] = iz; if( ix > bbox[ 3 ] ) bbox[ 3 ] = ix; if( iy > bbox[ 4 ] ) bbox[ 4 ] = iy; if( iz > bbox[ 5 ] ) bbox[ 5 ] = iz; } } } } } else { pd = (double *) ipd; for( iz = lbnd_tile[ 2 ]; iz <= ubnd_tile[ 2 ]; iz++ ) { for( iy = lbnd_tile[ 1 ]; iy <= ubnd_tile[ 1 ]; iy++ ) { for( ix = lbnd_tile[ 0 ]; ix <= ubnd_tile[ 0 ]; ix++ ) { if( *(pd++) != VAL__BADD ) { if( ix < bbox[ 0 ] ) bbox[ 0 ] = ix; if( iy < bbox[ 1 ] ) bbox[ 1 ] = iy; if( iz < bbox[ 2 ] ) bbox[ 2 ] = iz; if( ix > bbox[ 3 ] ) bbox[ 3 ] = ix; if( iy > bbox[ 4 ] ) bbox[ 4 ] = iy; if( iz > bbox[ 5 ] ) bbox[ 5 ] = iz; } } } } } /* Skip empty tiles. */ if( bbox[ 0 ] != INT_MAX ) { msgOutf( "", " tile %d", status, tile_index ); /* If required, trim the bounds to the edges of the bounding box. */ if( trim >= 2 ) { olbnd[ 0 ] = bbox[ 0 ]; olbnd[ 1 ] = bbox[ 1 ]; olbnd[ 2 ] = bbox[ 2 ]; oubnd[ 0 ] = bbox[ 3 ]; oubnd[ 1 ] = bbox[ 4 ]; oubnd[ 2 ] = bbox[ 5 ]; } else { olbnd[ 0 ] = lbnd_tile[ 0 ]; olbnd[ 1 ] = lbnd_tile[ 1 ]; olbnd[ 2 ] = lbnd_tile[ 2 ]; oubnd[ 0 ] = ubnd_tile[ 0 ]; oubnd[ 1 ] = ubnd_tile[ 1 ]; oubnd[ 2 ] = ubnd_tile[ 2 ]; } /* Modify these pixel bounds so that they refer to the output NDF. */ lbnd_in[ 0 ] = olbnd[ 0 ] - 0.5; lbnd_in[ 1 ] = olbnd[ 1 ] - 0.5; lbnd_in[ 2 ] = olbnd[ 2 ] - 0.5; ubnd_in[ 0 ] = oubnd[ 0 ] - 0.5; ubnd_in[ 1 ] = oubnd[ 1 ] - 0.5; ubnd_in[ 2 ] = oubnd[ 2 ] - 0.5; astMapBox( p2pmap, lbnd_in, ubnd_in, 1, 1, lbnd_out + 0, ubnd_out + 0, NULL, NULL ); astMapBox( p2pmap, lbnd_in, ubnd_in, 1, 2, lbnd_out + 1, ubnd_out + 1, NULL, NULL ); if( ndim == 3 ) astMapBox( p2pmap, lbnd_in, ubnd_in, 1, 3, lbnd_out + 2, ubnd_out + 2, NULL, NULL ); olbnd[ 0 ] = floor( lbnd_out[ 0 ] ) + 1; olbnd[ 1 ] = floor( lbnd_out[ 1 ] ) + 1; olbnd[ 2 ] = floor( lbnd_out[ 2 ] ) + 1; oubnd[ 0 ] = floor( ubnd_out[ 0 ] ) + 1; oubnd[ 1 ] = floor( ubnd_out[ 1 ] ) + 1; oubnd[ 2 ] = floor( ubnd_out[ 2 ] ) + 1; /* Get the full path to the output NDF for the current tile, and create an NDF placeholder for it. */ sprintf( path, "%.*s_%d", nbase, base, tile_index ); ndfPlace( NULL, path, &place, status ); /* Create a new output NDF by copying the meta-data from the input NDF section. */ ndfScopy( indfs, "Units", &place, &indfo, status ); /* Set the pixel bounds of the output NDF to the values found above and copy the input data for the current tile into it. */ smf1_jsadicer( indfo, olbnd, oubnd, tile_map, tile_frm, p2pmap, ipd, ipv, ipq, status ); /* Add the name of this output NDF to the group holding the names of the output NDFs that have actually been created. */ if( grp ) grpPut1( grp, path, 0, status ); /* Add a TILENUM header to the output FITS extension. */ kpgGtfts( indfo, &fc, status ); if( *status == KPG__NOFTS ) { errAnnul( status ); fc = astFitsChan( NULL, NULL, " " ); /* If the last card is "END", remove it. */ } else { astSetI( fc, "Card", astGetI( fc, "NCARD" ) ); keyword = astGetC( fc, "CardName" ); if( keyword && !strcmp( keyword, "END" ) ) astDelFits( fc ); } one_snprintf(jsatile_comment, 45, "JSA all-sky tile index (Nside=%i)", status, tiling.ntpf); atlPtfti( fc, "TILENUM", tile_index, jsatile_comment, status ); kpgPtfts( indfo, fc, status ); fc = astAnnul( fc ); /* Now store an STC-S polygon that describes the shortest boundary enclosing the good data in the output NDF, and store it as an NDF extension. */ kpgPutOutline( indfo, 0.5, 1, status ); /* We now reshape any extension NDFs contained within the output NDF to have the same spatial bounds as the main NDF (but only for extension NDFs that originally have the same spatial bounds as the supplied NDF). Get a group containing paths to all extension NDFs in the output NDF. */ ndgMoreg( indfo, &grpt, &size, status ); /* Loop round each output extension NDF. */ for( iext = 1; iext <= size && *status == SAI__OK; iext++ ) { ndgNdfas( grpt, iext, "Update", &indfx, status ); /* Get its bounds. */ ndfBound( indfx, NDF__MXDIM, lbndx, ubndx, &ndimx, status ); /* See if this extension NDF has the same bounds on the spatial axes as the supplied NDF. */ if( ndimx > 1 && lbndx[ lonax ] == lbnd[ lonax ] && lbndx[ latax ] == lbnd[ latax ] && ubndx[ lonax ] == ubnd[ lonax ] && ubndx[ latax ] == ubnd[ latax ] ) { /* If so, change the bounds of the output extension NDF so that they are the same as the main NDF on the spatial axes, and map the original contents of the NDF onto the new pixel grid. */ smf1_jsadicer( indfx, olbnd, oubnd, tile_map, tile_frm, p2pmap, NULL, NULL, NULL, status ); } /* Annul the extension NDF identifier. */ ndfAnnul( &indfx, status ); } /* Free resources associated with the current tile. */ grpDelet( &grpt, status ); ndfAnnul( &indfo, status ); /* Issue warnings about empty tiles. */ } else { msgOutiff( MSG__VERB, "", " tile %d is empty and so will not be " "created", status, tile_index ); } } /* Free the section of the input NDF. */ ndfAnnul( &indfs, status ); /* Append the index of this tile in the list of tiles to be created. */ created_tiles = astGrow( created_tiles, ++(*ntile), sizeof( *created_tiles ) ); if( *status == SAI__OK ) created_tiles[ *ntile - 1 ] = tile_index; } else { msgOutiff( MSG__DEBUG, "", " Tile %d does not overlap the input " "NDF after trimming.", status, tile_index ); } } msgBlank( status ); /* Write the indicies of the created tiles out to a parameter. */ if( *ntile ) parPut1i( "JSATILELIST", *ntile, created_tiles, status ); /* Free resources. */ created_tiles = astFree( created_tiles ); tiles = astFree( tiles ); path = astFree( path ); /* End the NDF context. */ ndfEnd( status ); /* End the AST context. */ astEnd; }
void smf_coords_lut( smfData *data, int tstep, dim_t itime_lo, dim_t itime_hi, AstSkyFrame *abskyfrm, AstMapping *oskymap, int moving, int olbnd[ 2 ], int oubnd[ 2 ], fts2Port fts_port, int *lut, double *angle, double *lon, double *lat, int *status ) { /* Local Variables */ AstCmpMap *bsmap = NULL; /* Tracking -> output grid Mapping */ AstFrame *trfrm = NULL; /* Tracking Frame */ AstFrameSet *fs = NULL; /* Tracking -> output sky FrameSet */ AstMapping *fullmap = NULL; /* Full Mapping from bolo GRID to output map GRID */ AstMapping *offmap = NULL; /* Mapping from absolute to offset sky coords */ AstMapping *tr2skyabs = NULL;/* Tracking -> output sky Mapping */ AstSkyFrame *offsky = NULL; /* Offset sky frame */ JCMTState *state; /* Pointer to telescope info for time slice */ dim_t ibolo; /* Vector index of bolometer */ dim_t idimx; /* Bolometers per row */ dim_t idimy; /* Bolometers per column */ dim_t ilut; /* Index of LUT element */ dim_t itime0; /* Time slice index at next full calculation */ dim_t itime; /* Time slice index */ dim_t nbolo; /* Total number of bolometers */ double *outmapcoord; /* Array holding output map GRID coords */ double *px; /* Pointer to next output map X GRID coord */ double *py; /* Pointer to next output map Y GRID coord */ double *pgx; /* Pointer to next X output grid coords */ double *pgy; /* Pointer to next Y output grid coords */ double *wgx = NULL; /* Work space to hold X output grid coords */ double *wgy = NULL; /* Work space to hold Y output grid coords */ double bsx0; /* Boresight output map GRID X at previous full calc */ double bsx; /* Boresight output map GRID X at current time slice */ double bsxlast; /* Boresight output map GRID X at previous time slice*/ double bsy0; /* Boresight output map GRID Y at previous full calc */ double bsy; /* Boresight output map GRID Y at current time slice */ double bsylast; /* Boresight output map GRID Y at previous time slice*/ double dx; /* Offset in GRID X from previous full calc */ double dxlast; /* Offset in GRID X from previous time slice */ double dy; /* Offset in GRID Y from previous full calc */ double dylast; /* Offset in GRID Y from previous time slice */ double shift[ 2 ]; /* Shift from PIXEL to GRID in output map */ double x; /* Output GRID X at current bolo in current row */ double xin[ 2 ]; /* Input X values */ double xout[ 2 ]; /* Output X values */ double y; /* Output GRID Y at current bolo in current row */ double yin[ 2 ]; /* Input Y values */ double yout[ 2 ]; /* Output Y values */ int lbnd_in[ 2 ]; /* Lower bounds of input array */ int np; /* Number of positions to transform */ int odimx; /* Output map X dimension in pixels */ int odimy; /* Output map Y dimension in pixels */ int ox; /* Output X GRID index (-1) containing current bolo */ int oy; /* Output Y GRID index (-1) containing current bolo */ int ubnd_in[ 2 ]; /* Upper bounds of input array */ /* Check the inherited status. */ if( *status != SAI__OK ) return; /* Begin an AST context. */ astBegin; /* Get the dimensions of the output map. */ odimx = oubnd[ 0 ] - olbnd[ 0 ] + 1; odimy = oubnd[ 1 ] - olbnd[ 1 ] + 1; /* Get the dimensions of the bolometer array. */ idimx = (data->dims)[ 0 ]; idimy = (data->dims)[ 1 ]; /* Store integer bounds within the input bolometer GRID system. */ lbnd_in[ 0 ] = 1; lbnd_in[ 1 ] = 1; ubnd_in[ 0 ] = idimx; ubnd_in[ 1 ] = idimy; /* Determine the number of bolometers. */ nbolo = idimx*idimy; /* Initialise the index of the next LUT element to write. */ ilut = 0; /* Ensure tstep is at least one. */ if( tstep < 1 ) tstep = 1; /* Get the time slice index at which to do the next full calculation. */ itime0 = itime_lo; /* We only need the following AST objects if we will be approximating some caclulations. */ if( tstep > 1 ) { /* We need to find the first good TCS index in order to get the tracking system (Which for SCUBA-2 won't be changing in the sequence) */ itime0 = VAL__BADI; for (itime = itime_lo; itime <= itime_hi; itime++) { JCMTState * slice = &((data->hdr->allState)[itime]); if (!(slice->jos_drcontrol >= 0 && slice->jos_drcontrol & DRCNTRL__POSITION)) { itime0 = itime; break; } } if ((int)itime0 != VAL__BADI) { /* We need a Frame describing absolute tracking system coords. Take a copy of the supplied skyframe (to inherit obslat, obslon, epoch, etc), and then set its system to the tracking system. */ trfrm = astCopy( abskyfrm ); astSetC( trfrm, "System", sc2ast_convert_system( (data->hdr->allState)[itime0].tcs_tr_sys, status ) ); /* Get the Mapping from the tracking system to the output (absolute, since abskyfrm is absolute) sky system. */ fs = astConvert( trfrm, abskyfrm, " " ); if( !fs && *status == SAI__OK ) { *status = SAI__ERROR; errRep( " ", "smf_coords_lut: Failed to convert from " "tracking system to output WCS system.", status ); } tr2skyabs = astSimplify( astGetMapping( fs, AST__BASE, AST__CURRENT ) ); /* For moving targets, we also need a Frame describing offset sky coordinates in the output map, in which the reference point is the current telescope base position. This will involve changing the SkyRef attribute of the Frame for every time slice, so take a copy of the supplied SkyFrame to avoid changing it. */ if( moving ) { offsky = astCopy( abskyfrm ); astSet( offsky, "SkyRefIs=Origin" ); } /* Create the Mapping from offsets within the output map sky coordinate system output to map GRID coords to output map GRID coords. This uses a ShiftMap to convert from output PIXEL coords (produced by "oskymap") to output GRID coords. Note, if the target is moving, "oskymap" maps from sky *offsets* to output map PIXEL coords. */ shift[ 0 ] = 1.5 - olbnd[ 0 ]; shift[ 1 ] = 1.5 - olbnd[ 1 ]; bsmap = astCmpMap( oskymap, astShiftMap( 2, shift, " " ), 1, " " ); } else { /* We did not find any good TCS data so force us into tstep==1 case and end up with VAL__BADI for every element. */ tstep = 1; itime0 = itime_lo; msgOutiff(MSG__VERB, "", "All time slices from %zu -- %zu had bad TCS data.", status, (size_t)itime_lo, (size_t)itime_hi ); } } /* Allocate memory to hold the (x,y) output map grid coords at each bolo for a single time slice. */ outmapcoord = astMalloc( sizeof( *outmapcoord )*2*nbolo ); /* Initialise boresight position for the benefit of the tstep == 1 case. */ bsx = bsy = 0.0; bsx0 = bsy0 = AST__BAD; bsxlast = bsylast = AST__BAD; /* If lon and lat arrays are to be returned, allocate memory to hold the grid coords of every bolometer for a single sample. */ if( lon && lat ) { wgx = astMalloc( nbolo*sizeof( *wgx ) ); wgy = astMalloc( nbolo*sizeof( *wgy ) ); } /* Loop round each time slice. */ state = data->hdr->allState + itime_lo; for( itime = itime_lo; itime <= itime_hi && *status == SAI__OK; itime++,state++ ) { /* No need to get the boresight position if we are doing full calculations at every time slice. If this time slice has bad TCS data then the problem will be caught in smf_rebin_totmap. */ if( tstep > 1 ) { /* Transform the current boresight and base (if moving) positions from tracking coords to absolute output map sky coords. */ xin[ 0 ] = state->tcs_tr_ac1; yin[ 0 ] = state->tcs_tr_ac2; if( moving ) { xin[ 1 ] = state->tcs_tr_bc1; yin[ 1 ] = state->tcs_tr_bc2; np = 2; } else { np = 1; } astTran2( tr2skyabs, np, xin, yin, 1, xout, yout ); /* If the target is moving, find the offsets within the output map sky coordinate system, from base to boresight at the current time slice. These offsets become the new "boresight" position (in xin/yin). Guard against assigning bad values to the ref pos, which can cause AST to report errors. */ if( moving && xout[ 1 ] != AST__BAD && yout[ 1 ] != AST__BAD ) { /* Set the current telescope base position as the reference point in "offsky". Then get the Mapping from absolute to offset sky coords. */ astSetD( offsky, "SkyRef(1)", xout[ 1 ] ); astSetD( offsky, "SkyRef(2)", yout[ 1 ] ); offmap = astSkyOffsetMap( offsky ); /* Use this Mapping to convert the current boresight position from absolute sky coords to offsets from the current base position. */ astTran2( offmap, 1, xout, yout, 1, xin, yin ); /* Annul the Mapping to avoid keep the number of AST objects to a minimum. */ offmap = astAnnul( offmap ); /* If the target is stationary, we can just use the absolute boresight position as it is. */ } else { xin[ 0 ] = xout[ 0 ]; yin[ 0 ] = yout[ 0 ]; } /* Transform the above boresight position from output map sky coords to output map GRID coords. */ astTran2( bsmap, 1, xin, yin, 1, &bsx, &bsy ); } /* If we have reached the next full calculation... */ if( itime == itime0 ) { /* Calculate the full bolometer to map-pixel transformation for the current time slice */ fullmap = smf_rebin_totmap( data, itime, abskyfrm, oskymap, moving, fts_port, status ); /* If succesful, use it to transform every bolometer position from bolo GRID coords to output map GRID coords. */ if( fullmap ) { itime0 += tstep; astTranGrid( fullmap, 2, lbnd_in, ubnd_in, 0.1, 1000000, 1, 2, nbolo, outmapcoord ); fullmap = astAnnul( fullmap ); /* Record the boresight grid coords at this time slice. */ bsx0 = bsx; bsy0 = bsy; /* If we cannot determine a full Mapping for this time slice, move the node to the next time slice (otherwise we would loose all the data to the next node), and set the boresight position bad to indicate we have no mapping for this time slice. */ } else { itime0++; bsx0 = AST__BAD; bsy0 = AST__BAD; } } /* Get the offset from the boresight position at the previous full calculation and the current boresight position, in output map GRID coords. */ dx = ( bsx != AST__BAD && bsx0 != AST__BAD ) ? bsx - bsx0 : AST__BAD; dy = ( bsy != AST__BAD && bsy0 != AST__BAD ) ? bsy - bsy0 : AST__BAD; /* Work out the scan direction based on the GRID offsets between this and the previous time slice. Angles are calculated using atan2, with values ranging from -pi to +pi. */ if( angle ) { double theta = AST__BAD; dxlast = ( bsx != AST__BAD && bsxlast != AST__BAD ) ? bsx - bsxlast : AST__BAD; dylast = ( bsy != AST__BAD && bsylast != AST__BAD ) ? bsy - bsylast : AST__BAD; if( dxlast != AST__BAD && dylast != AST__BAD && !( !dxlast && !dylast ) ) { theta = atan2( dylast, dxlast ); } angle[itime-itime_lo] = theta; bsxlast = bsx; bsylast = bsy; } /* Initialise pointers to the place to store the final grid coords for each bolometer. */ pgx = wgx; pgy = wgy; /* Loop round all bolometers. */ px = outmapcoord; py = outmapcoord + nbolo; for( ibolo = 0; ibolo < nbolo; ibolo++ ){ /* If good, get the x and y output map GRID coords for this bolometer. */ if( dx != AST__BAD && dy != AST__BAD ) { x = *(px++) + dx; y = *(py++) + dy; /* If required, store them so that we can convert them into lon/lat values later. */ if( pgx && pgy ) { *(pgx++) = x; *(pgy++) = y; } /* Find the grid indices (minus one) of the output map pixel containing the mapped bolo grid coords. One is subtracted in order to simplify the subsequent calculation of the vector index. */ ox = (int) ( x - 0.5 ); oy = (int) ( y - 0.5 ); /* Check it is within the output map */ if( ox >= 0 && ox < odimx && oy >= 0 && oy < odimy ) { /* Find the 1-dimensional vector index into the output array for this pixel and store in the next element of the returned LUT. */ lut[ ilut++ ] = ox + oy*odimx; /* Store a bad value for points that are off the edge of the output map. */ } else { lut[ ilut++ ] = VAL__BADI; } /* If good, store a bad index in the LUT and move on to the next pixel. */ } else { lut[ ilut++ ] = VAL__BADI; px++; py++; if( pgx && pgy ) { *(pgx++) = AST__BAD; *(pgy++) = AST__BAD; } } } /* If required transform the grid coords into (lon,lat) coords and store in the relevant elements of the supplied "lon" and "lat" arrays. */ if( wgx && wgy ) { astTran2( oskymap, nbolo, wgx, wgy, 0, lon + itime*nbolo, lat + itime*nbolo ); /* Convert from rads to degs. */ pgx = lon + itime*nbolo; pgy = lat + itime*nbolo; for( ibolo = 0; ibolo < nbolo; ibolo++ ) { if( *pgx != AST__BAD && *pgy != AST__BAD ) { *(pgx++) *= AST__DR2D; *(pgy++) *= AST__DR2D; } else { pgx++; pgy++; } } } } /* To obtain a reasonable value for the first entry of angle, we simply duplicate the second value. */ if( angle && (itime_hi > itime_lo) ) { angle[0] = angle[1]; } /* Free remaining work space. */ outmapcoord = astFree( outmapcoord ); wgx = astFree( wgx ); wgy = astFree( wgy ); /* Export the WCS pointer in the data header since it will be annulled at a higher level. Note the FrameSet pointer may be null if the last full calculation was for a slice with bad telescope data. */ if( data->hdr->wcs ) astExport( data->hdr->wcs ); /* End the AST context. */ astEnd; }
/* Returns an array holding the angle (rad.s) from north to focal plane Y, measured positive in the sense of rotation from focal plane Y to focal plane X, for every bolometer sample in a smfData. The values are bolo ordered so that "bstride" is 1 and "tstsride" is nbolo. The returned array should be freed using astFre when no longer needed. */ static double *smf1_calcang( smfData *data, int *status ){ /* Local Variables: */ AstFrameSet *fpfset; AstFrameSet *wcs; AstMapping *g2s; AstMapping *s2f; const char *usesys; dim_t ibolo; dim_t itime; dim_t nbolo; dim_t ncol; dim_t ntslice; double *fx2; double *fx; double *fy2; double *fy; double *gx; double *gy; double *pr; double *result; double *sx; double *sy; int subsysnum; /* Check the inherited status. */ if( *status != SAI__OK ) return NULL; /* Get the number of bolometers and time slices, together with the strides between adjacent bolometers and adjacent time slices. */ smf_get_dims( data, NULL, &ncol, &nbolo, &ntslice, NULL, NULL, NULL, status ); /* Allocate the returned array. */ result = astMalloc( nbolo*ntslice*sizeof( *result ) ); /* Allocate arrays to hold the grid coords for every bolometer. */ gx = astMalloc( nbolo*sizeof( *gx ) ); gy = astMalloc( nbolo*sizeof( *gy ) ); /* Allocate arrays to hold the sky coords for every bolometer. */ sx = astMalloc( nbolo*sizeof( *sx ) ); sy = astMalloc( nbolo*sizeof( *sy ) ); /* Allocate arrays to hold the focal plane coords for every bolometer. */ fx = astMalloc( nbolo*sizeof( *fx ) ); fy = astMalloc( nbolo*sizeof( *fy ) ); /* Allocate arrays to hold the focal plane coords of a point slightly to the north of every bolometer. */ fx2 = astMalloc( nbolo*sizeof( *fx2 ) ); fy2 = astMalloc( nbolo*sizeof( *fy2 ) ); /* Get the AST code equivalent to the tracking system. */ usesys = sc2ast_convert_system( (data->hdr->allState)[0].tcs_tr_sys, status ); if( *status == SAI__OK ) { /* Initialise the arrays holding the grid coords for every bolometer. */ for( ibolo = 0; ibolo < nbolo; ibolo++ ) { gx[ ibolo ] = ibolo % ncol + 1; gy[ ibolo ] = ibolo / ncol + 1; } /* Get the GRID->focal plane FrameSet (the same for every time slice). */ smf_find_subarray( data->hdr, NULL, 0, &subsysnum, status ); sc2ast_createwcs( subsysnum, NULL, data->hdr->instap, data->hdr->telpos, NO_FTS, &fpfset, status); /* Use this to transform the bolometrer GRID coords to focal plane. */ astTran2( fpfset, nbolo, gx, gy, 1, fx, fy ); /* Loop over all time slices. */ pr = result; for( itime = 0; itime < ntslice; itime++ ) { /* Get the WCS FrameSet for the time slice, and set its current Frame to the tracking frame. */ smf_tslice_ast( data, itime, 1, NO_FTS, status ); wcs = data->hdr->wcs; if( wcs ) { astSetC( wcs, "System", usesys ); /* Get the mapping from GRID to SKY. */ astBegin; g2s = astSimplify( astGetMapping( wcs, AST__BASE, AST__CURRENT )); /* Get the mapping from SKY to focal plane (x,y) (the index of the FPLANE Frame is fixed at 3 by file sc2ast.c). */ s2f = astSimplify( astGetMapping( wcs, AST__CURRENT, 3 ) ); /* Transform the grid coords of all bolometers to SKY coordinates using the FrameSet. */ astTran2( g2s, nbolo, gx, gy, 1, sx, sy ); /* Increment the sky positions slightly to the north. */ for( ibolo = 0; ibolo < nbolo; ibolo++ ) sy[ ibolo ] += 1.0E-6; /* Transform these modified sky coordinates to focal plane. */ astTran2( s2f, nbolo, sx, sy, 1, fx2, fy2 ); astEnd; /* Loop round all bolometers. */ for( ibolo = 0; ibolo < nbolo; ibolo++ ) { /* Get the angle from north to focal plane Y, measured positive in the sense of rotation from focal plane Y to focal plane X. */ if( fx[ibolo] != VAL__BADD && fy[ibolo] != VAL__BADD && fx2[ibolo] != VAL__BADD && fy2[ibolo] != VAL__BADD ) { *(pr++) = atan2( fx[ibolo] - fx2[ibolo], fy2[ibolo] - fy[ibolo] ); } else { *(pr++) = VAL__BADD; } } } else { for( ibolo = 0; ibolo < nbolo; ibolo++ ) *(pr++) = VAL__BADD; } } } /* Free resources. */ fx = astFree( fx ); fy = astFree( fy ); fx2 = astFree( fx2 ); fy2 = astFree( fy2 ); sx = astFree( sx ); sy = astFree( sy ); gx = astFree( gx ); gy = astFree( gy ); /* Return the array of angle values. */ return result; }
void smf_addpolanal( AstFrameSet *fset, smfHead *hdr, AstKeyMap *config, int *status ){ /* Local Variables */ AstCmpMap *tmap; AstFrame *cfrm; AstFrame *pfrm; AstFrame *tfrm; AstFrameSet *tfs; AstPermMap *pm; char *polnorth = NULL; const char *cursys; const char *trsys; int aloff; int icurr; int inperm[2]; int outperm[2]; int pol2fp; /* Check inherited status, and also check the supplied angle is not bad. */ if( *status != SAI__OK ) return; /* Begin an AST object context. */ astBegin; /* Get the value of the POLNORTH FITS keyword from the supplied header. The rest only happens if the keyword is found. */ if( astGetFitsS( hdr->fitshdr, "POLNORTH", &polnorth ) ) { /* Normally, we do not allow maps to be made from Q/U time streams that use focal plane Y as the reference direction (because of the problems of sky rotation). Therefore we report an error. However, we do need to make such maps as part of the process of determining the parameters of the Instrumental Polarisation (IP) model. So only report the error if "pol2fp" config parameter is non-zero. */ if( !strcmp( polnorth, "FPLANE" ) ) { astMapGet0I( config, "POL2FP", &pol2fp ); if( pol2fp ) { msgBlank( status ); msgOut( "", "WARNING: The input NDFs hold POL-2 Q/U data specified " "with respect to focal plane Y.",status ); msgOut( "", "Maps should normally be made from POL-2 data specified " "with respect to celestial north.",status ); msgOut( "", "The output map will not contain a POLANAL Frame and " "so will be unusable by POLPACK applications.",status ); msgBlank( status ); } else if( *status == SAI__OK ) { *status = SAI__ERROR; errRep( "", "The input NDFs hold POL-2 Q/U data specified with " "respect to focal plane Y.",status ); errRep( "", "Maps can only be made from POL-2 data specified with " "respect to celestial north.",status ); } /* If the ref. direction is celestial north, create a suitable Frame and Mapping and add them into the supplied FrameSet. */ } else { /* Check the current Frame is a SkyFrame. */ cfrm = astGetFrame( fset, AST__CURRENT ); if( astIsASkyFrame( cfrm ) ) { /* Create a POLANAL Frame. */ pfrm = astFrame( 2, "Domain=POLANAL" ); astSet( pfrm, "Title=Polarimetry reference frame" ); astSet( pfrm, "Label(1)=Polarimetry reference direction" ); astSet( pfrm, "Label(2)=" ); /* Create a PermMap that ensures that axis 1 of the POLANAL Frame is parallel to the latitude axis (i.e. north) of the curent Frame (the current Frame axes may have been swapped). */ outperm[ 0 ] = astGetI( cfrm, "LatAxis" ); outperm[ 1 ] = astGetI( cfrm, "LonAxis" ); inperm[ outperm[ 0 ] - 1 ] = 1; inperm[ outperm[ 1 ] - 1 ] = 2; pm = astPermMap( 2, inperm, 2, outperm, NULL, " " ); /* Record the index of the original current Frame. */ icurr = astGetI( fset, "Current" ); /* Determine the system to use. */ if( !strcmp( polnorth, "TRACKING" ) ) { trsys = sc2ast_convert_system( hdr->state->tcs_tr_sys, status ); } else { trsys = polnorth; } /* If the current Frame in the supplied FrameSet has this system. Then we use the above PermMap to connect the POLANAL Frame directly to the current Frame. */ cursys = astGetC( cfrm, "System" ); if( trsys && cursys && !strcmp( cursys, trsys ) ) { astAddFrame( fset, AST__CURRENT, pm, pfrm ); /* Otherwise we need to get a Mapping from the current Frame to the required frame. */ } else { /* Take a copy of the current Frame (in order to pick up epoch, observatory position, etc), and set its System to the required system. */ tfrm = astCopy( cfrm ); astSetC( tfrm, "System", trsys ); /* Get the Mapping from the original current Frame to this modified copy. Ensure alignment happens in absolute coords (alignment in offset coords is always a unit mapping and so no rotation occurs). */ aloff = astGetI( cfrm, "AlignOffset" ); if( aloff ) { astSetI( cfrm, "AlignOffset", 0 ); astSetI( tfrm, "AlignOffset", 0 ); } tfs = astConvert( cfrm, tfrm, "SKY" ); if( aloff ) { astSetI( cfrm, "AlignOffset", 1 ); astSetI( tfrm, "AlignOffset", 1 ); } if( tfs ) { /* Use it, in series with with the above PermMap, to connect the POLANAL frame to the current Frame. */ tmap = astCmpMap( astGetMapping( tfs, AST__BASE, AST__CURRENT ), pm, 1, " " ); astAddFrame( fset, AST__CURRENT, astSimplify( tmap ), pfrm ); /* Report an error if the mapping from current to required system could not be found. */ } else if( *status == SAI__OK ) { *status = SAI__ERROR; errRepf( "", "smf_addpolanal: Could not convert Frame " "from %s to %s (prgramming error).", status, cursys, trsys ); } } /* Re-instate the original current Frame. */ astSetI( fset, "Current", icurr ); /* Report an error if the current Frame is not a SkyFrame. */ } else if( *status == SAI__OK ) { *status = SAI__ERROR; errRep( "", "smf_addpolanal: The current Frame in the " "supplied FrameSet is not a SkyFrame (prgramming " "error).", status ); } } } /* End the AST object context. */ astEnd; }
static void smf1_fit_qui_job( void *job_data, int *status ) { /* * Name: * smf1_fit_qui_job * Purpose: * Calculate I, Q and U for a block of bolometers. * Invocation: * void smf1_fit_qui_job( void *job_data, int *status ) * Arguments: * job_data = void * (Given) * Pointer to the data needed by the job. Should be a pointer to a * smfFitQUIJobData structure. * status = int * (Given and Returned) * Pointer to global status. * Description: * This routine calculate the I, Q and U values for each bolometer in * a block of bolometers. It runs within a thread instigated by * smf_fit_qui. */ /* Local Variables: */ AstFrameSet *wcs; /* WCS FrameSet for current time slice */ AstMapping *g2s; /* GRID to SKY mapping */ AstMapping *s2f; /* SKY to focal plane mapping */ const JCMTState *allstates;/* Pointer to array of JCMTState structures */ const JCMTState *state; /* JCMTState info for current time slice */ dim_t b1; /* First bolometer index */ dim_t b2; /* Last bolometer index */ dim_t box_size; /* NFirst time slice in box */ dim_t ibolo; /* Bolometer index */ dim_t ibox; dim_t ncol; dim_t nbolo; /* Total number of bolometers */ double *dat; /* Pointer to start of input data values */ double *din; /* Pointer to input data array for bolo/time */ double *ipi; /* Pointer to output I array */ double *ipq; /* Pointer to output Q array */ double *ipu; /* Pointer to output U array */ double *ipv; /* Pointer to output weights array */ double *pm; double *ps; double angle; /* Phase angle for FFT */ double angrot; /* Angle from focal plane X axis to fixed analyser */ double c1; double c2; double c4; double c8; double cosval; /* Cos of angrot */ double fit; double fx[2]; /* Focal plane X coord at bolometer and northern point*/ double fy[2]; /* Focal plane Y coord at bolometer and northern point*/ double gx; /* GRID X coord at bolometer */ double gy; /* GRID Y coord at bolometer */ double matrix[ NPAR*NPAR ]; double paoff; /* WPLATE value corresponding to POL_ANG=0.0 */ double phi; /* Angle from fixed analyser to effective analyser */ double res; double s1; /* Sum of weighted cosine terms */ double s2; /* Sum of weighted sine terms */ double s4; double s8; double sinval; /* Sin of angrot */ double solution[ NPAR ]; double sum1; /* Sum of squared residuals */ double sums[NSUM]; /* Sum of bolometer values */ double sx[2]; /* SKY X coord at bolometer and northern point*/ double sy[2]; /* SKY Y coord at bolometer and northern point*/ double tr_angle; double twophi; double vector[ NPAR ]; double wplate; /* Angle from fixed analyser to have-wave plate */ gsl_matrix_view gsl_m; gsl_vector_view gsl_b; gsl_vector_view gsl_x; int ipolcrd; /* Reference direction for pol_ang */ int nsum1; int pasign; /* +1 or -1 indicating sense of POL_ANG value */ smfFitQUIJobData *pdata; /* Pointer to job data */ smf_qual_t *qin; /* Pointer to input quality array for bolo/time */ smf_qual_t *qua; /* Pointer to start of input quality values */ /* Check inherited status */ if( *status != SAI__OK ) return; /* Begin an AST context */ astBegin; /* Create views of the matrix and vector buffers that can be used by GSL. */ gsl_m = gsl_matrix_view_array( matrix, NPAR, NPAR ); gsl_b = gsl_vector_view_array( vector, NPAR ); gsl_x = gsl_vector_view_array( solution, NPAR ); /* Get a pointer to the job data, and then extract its contents into a set of local variables. */ pdata = (smfFitQUIJobData *) job_data; b1 = pdata->b1; b2 = pdata->b2; nbolo = pdata->nbolo; ncol = pdata->ncol; dat = pdata->dat + b1; qua = pdata->qua + b1; allstates = pdata->allstates; ipi = pdata->ipi ? pdata->ipi + b1 : NULL; ipq = pdata->ipq + b1; ipu = pdata->ipu + b1; ipv = pdata->ipv + b1; ipolcrd = pdata->ipolcrd; pasign = pdata->pasign; paoff = pdata->paoff; angrot = pdata->angrot; box_size = pdata->box_size; wcs = pdata->wcs; if( wcs ) { astLock( wcs, 0 ); /* Get the mapping from GRID to SKY. */ g2s = astSimplify( astGetMapping( wcs, AST__BASE, AST__CURRENT )); /* Get the mapping from SKY to focal plane (x,y) (the index of the FPLANE Frame is fixed at 3 by file sc2ast.c). */ s2f = astSimplify( astGetMapping( wcs, AST__CURRENT, 3 ) ); } else{ g2s = s2f = NULL; } /* Check we have something to do. */ if( b1 < nbolo && *status == SAI__OK ) { /* Loop round all bolometers to be processed by this thread. */ for( ibolo = b1; ibolo <= b2; ibolo++,qua++,dat++ ) { /* If the returned Stokes parameters are to be with respect to Tracking North, get the angle from tracking north at the current bolometer to focal plane Y, measured positive in the sense of rotation from focal plane Y to focal plane X (note this angle may change across the focal plane due to focal plane distortion). Otherwise, use zero. */ if( pdata->wcs ) { /* Get the grid coords of the current bolometer, and transform them to SKY coordinates using the FrameSet. */ gx = ibolo % ncol + 1; gy = ibolo / ncol + 1; astTran2( g2s, 1, &gx, &gy, 1, sx, sy ); /* Increment the sky position slightly to the north. */ sx[ 1 ] = sx[ 0 ]; sy[ 1 ] = sy[ 0 ] + 1.0E-6; /* Transform both sky positions into focal plane coords. */ astTran2( s2f, 2, sx, sy, 1, fx, fy ); /* Get the angle from north to focal plane Y, measured positive in the sense of rotation from focal plane Y to focal plane X. */ if( fx[0] != VAL__BADD && fy[0] != VAL__BADD && fx[1] != VAL__BADD && fy[1] != VAL__BADD ) { tr_angle = atan2( fx[0] - fx[1], fy[1] - fy[0] ); } else { tr_angle = VAL__BADD; } } else { tr_angle = 0.0; } /* If the whole bolometer is bad, put bad values into the outputs. */ if( *qua & SMF__Q_BADB || tr_angle == VAL__BADD ) { if( ipi ) *(ipi++) = VAL__BADD; *(ipq++) = VAL__BADD; *(ipu++) = VAL__BADD; *(ipv++) = VAL__BADD; /* If the bolometer is good, calculate and store the output i, q and u values. */ } else { /* Initialise pointers to the first input data value, quality value and state info to be used in the current fitting box. */ din = dat; qin = qua; state = allstates; /* Form the sums needed to calculate the best fit Q, U and I. This involves looping over all input samples that fall within the fitting box centred on the current output sample. The 44 sums are stored in the "sums" array. Initialise it to hold zeros. */ memset( sums, 0, NSUM*sizeof(*sums) ); for( ibox = 0; ibox < box_size; ibox++,state++ ) { /* Get the POL_ANG value for this time slice. */ angle = state->pol_ang; /* Check the input sample has not been flagged during cleaning and is not bad. */ if( !( *qin & SMF__Q_FIT ) && *din != VAL__BADD && angle != VAL__BADD ) { /* Following SUN/223 (section "Single-beam polarimetry"/"The Polarimeter"), get the angle from the fixed analyser to the half-waveplate axis, in radians. Positive rotation is from focal plane axis 1 (x) to focal plane axis 2 (y). Not sure about the sign of tcs_az/tr_ang at the moment so do not use them yet. */ wplate = 0.0; if( ipolcrd == 0 ) { wplate = pasign*angle + paoff; } else if( *status == SAI__OK ) { *status = SAI__ERROR; errRepf( "", "smf_fit_qui: currently only POL_CRD = " "FPLANE is supported.", status ); } /* if( ipolcrd == 1 ) { wplate += state->tcs_az_ang; } else if( ipolcrd == 2 ) { wplate += state->tcs_tr_ang; } */ /* Get the angle from the fixed analyser to the effective analyser position (see SUN/223 again). The effective analyser angle rotates twice as fast as the half-wave plate which is why there is a factor of 2 here. */ phi = 2*wplate; twophi = 2*phi; /* Form the trig values needed for the sums. */ s8 = sin( 2*twophi ); c8 = cos( 2*twophi ); s4 = sin( twophi ); c4 = cos( twophi ); s2 = sin( phi ); c2 = cos( phi ); s1 = sin( wplate ); c1 = cos( wplate ); /* Update the sums. The order of the following lines define the index within "sums" at which each sum is stored. */ ps = sums; *(ps++) += s4*s4; *(ps++) += s4*c4; *(ps++) += s4*s2; *(ps++) += s4*c2; *(ps++) += s4*s1; *(ps++) += s4*c1; *(ps++) += s4*ibox; *(ps++) += s4; *(ps++) += s4*(*din); *(ps++) += s2*c4; *(ps++) += s2*s2; *(ps++) += s2*c2; *(ps++) += s2*s1; *(ps++) += s2*c1; *(ps++) += s2*ibox; *(ps++) += s2; *(ps++) += s2*(*din); *(ps++) += s1*c4; *(ps++) += s1*c2; *(ps++) += s1*s1; *(ps++) += s1*c1; *(ps++) += s1*ibox; *(ps++) += s1; *(ps++) += s1*(*din); *(ps++) += c4*c4; *(ps++) += c4*c2; *(ps++) += c4*c1; *(ps++) += c4*ibox; *(ps++) += c4; *(ps++) += c4*(*din); *(ps++) += c2*c2; *(ps++) += c2*c1; *(ps++) += c2*ibox; *(ps++) += c2; *(ps++) += c2*(*din); *(ps++) += c1*c1; *(ps++) += c1*ibox; *(ps++) += c1; *(ps++) += c1*(*din); *(ps++) += ibox*ibox; *(ps++) += ibox; *(ps++) += ibox*(*din); *(ps++) += 1.0; *(ps++) += *din; *(ps++) += s4*s8; *(ps++) += s4*c8; *(ps++) += s2*s8; *(ps++) += s2*c8; *(ps++) += s1*s8; *(ps++) += s1*c8; *(ps++) += s8*c4; *(ps++) += s8*c2; *(ps++) += s8*c1; *(ps++) += s8*ibox; *(ps++) += s8; *(ps++) += s8*(*din); *(ps++) += s8*s8; *(ps++) += s8*c8; *(ps++) += c4*c8; *(ps++) += c2*c8; *(ps++) += c1*c8; *(ps++) += c8*ibox; *(ps++) += c8; *(ps++) += c8*(*din); *(ps++) += c8*c8; } din += nbolo; qin += nbolo; } /* Now find the parameters of the best fit. First check that there were sufficient good samples in the fitting box. */ if( sums[42] > 0.8*box_size ) { /* Copy the sums to the correct elements of the 10x10 matrix. */ pm = matrix; *(pm++) = sums[ 0 ]; *(pm++) = sums[ 1 ]; *(pm++) = sums[ 2 ]; *(pm++) = sums[ 3 ]; *(pm++) = sums[ 4 ]; *(pm++) = sums[ 5 ]; *(pm++) = sums[ 6 ]; *(pm++) = sums[ 7 ]; *(pm++) = sums[ 44 ]; *(pm++) = sums[ 45 ]; *(pm++) = sums[ 1 ]; *(pm++) = sums[ 24 ]; *(pm++) = sums[ 9 ]; *(pm++) = sums[ 25 ]; *(pm++) = sums[ 17 ]; *(pm++) = sums[ 26 ]; *(pm++) = sums[ 27 ]; *(pm++) = sums[ 28 ]; *(pm++) = sums[ 50 ]; *(pm++) = sums[ 58 ]; *(pm++) = sums[ 2 ]; *(pm++) = sums[ 9 ]; *(pm++) = sums[ 10 ]; *(pm++) = sums[ 11 ]; *(pm++) = sums[ 12 ]; *(pm++) = sums[ 13 ]; *(pm++) = sums[ 14 ]; *(pm++) = sums[ 15 ]; *(pm++) = sums[ 46 ]; *(pm++) = sums[ 47 ]; *(pm++) = sums[ 3 ]; *(pm++) = sums[ 25 ]; *(pm++) = sums[ 11 ]; *(pm++) = sums[ 30 ]; *(pm++) = sums[ 18 ]; *(pm++) = sums[ 31 ]; *(pm++) = sums[ 32 ]; *(pm++) = sums[ 33 ]; *(pm++) = sums[ 51 ]; *(pm++) = sums[ 59 ]; *(pm++) = sums[ 4 ]; *(pm++) = sums[ 17 ]; *(pm++) = sums[ 12 ]; *(pm++) = sums[ 18 ]; *(pm++) = sums[ 19 ]; *(pm++) = sums[ 20 ]; *(pm++) = sums[ 21 ]; *(pm++) = sums[ 22 ]; *(pm++) = sums[ 48 ]; *(pm++) = sums[ 49 ]; *(pm++) = sums[ 5 ]; *(pm++) = sums[ 26 ]; *(pm++) = sums[ 13 ]; *(pm++) = sums[ 31 ]; *(pm++) = sums[ 20 ]; *(pm++) = sums[ 35 ]; *(pm++) = sums[ 36 ]; *(pm++) = sums[ 37 ]; *(pm++) = sums[ 52 ]; *(pm++) = sums[ 60 ]; *(pm++) = sums[ 6 ]; *(pm++) = sums[ 27 ]; *(pm++) = sums[ 14 ]; *(pm++) = sums[ 32 ]; *(pm++) = sums[ 21 ]; *(pm++) = sums[ 36 ]; *(pm++) = sums[ 39 ]; *(pm++) = sums[ 40 ]; *(pm++) = sums[ 53 ]; *(pm++) = sums[ 61 ]; *(pm++) = sums[ 7 ]; *(pm++) = sums[ 28 ]; *(pm++) = sums[ 15 ]; *(pm++) = sums[ 33 ]; *(pm++) = sums[ 22 ]; *(pm++) = sums[ 37 ]; *(pm++) = sums[ 40 ]; *(pm++) = sums[ 42 ]; *(pm++) = sums[ 54 ]; *(pm++) = sums[ 62 ]; *(pm++) = sums[ 44 ]; *(pm++) = sums[ 50 ]; *(pm++) = sums[ 46 ]; *(pm++) = sums[ 51 ]; *(pm++) = sums[ 48 ]; *(pm++) = sums[ 52 ]; *(pm++) = sums[ 53 ]; *(pm++) = sums[ 54 ]; *(pm++) = sums[ 56 ]; *(pm++) = sums[ 57 ]; *(pm++) = sums[ 45 ]; *(pm++) = sums[ 58 ]; *(pm++) = sums[ 47 ]; *(pm++) = sums[ 59 ]; *(pm++) = sums[ 49 ]; *(pm++) = sums[ 60 ]; *(pm++) = sums[ 61 ]; *(pm++) = sums[ 62 ]; *(pm++) = sums[ 57 ]; *(pm++) = sums[ 64 ]; /* Copy the remaining sums to the correct elements of the 8 vector. */ pm = vector; *(pm++) = sums[ 8 ]; *(pm++) = sums[ 29 ]; *(pm++) = sums[ 16 ]; *(pm++) = sums[ 34 ]; *(pm++) = sums[ 23 ]; *(pm++) = sums[ 38 ]; *(pm++) = sums[ 41 ]; *(pm++) = sums[ 43 ]; *(pm++) = sums[ 55 ]; *(pm++) = sums[ 63 ]; /* Find the solution to the 10x10 set of linear equations. The matrix is symmetric and positive-definite so use Cholesky decomposition. */ memset( solution, 0, NPAR*sizeof(*solution) ); gsl_linalg_cholesky_decomp( &gsl_m.matrix ); gsl_linalg_cholesky_solve( &gsl_m.matrix, &gsl_b.vector, &gsl_x.vector ); /* Modify Q and U so they use the requested reference direction, and store in the output arrays. */ cosval = cos( 2*( angrot - tr_angle ) ); sinval = sin( 2*( angrot - tr_angle ) ); *(ipq++) = 2*( -solution[ 1 ]*cosval + solution[ 0 ]*sinval ); *(ipu++) = 2*( -solution[ 1 ]*sinval - solution[ 0 ]*cosval ); /* Store the correspoinding I value. */ if( ipi ) *(ipi++) = solution[ 6 ]*box_size + 2*solution[ 7 ]; /* Loop over the data again in the same way to calculate the variance of the residuals between the above fit and the supplied data. */ din = dat; qin = qua; state = allstates; sum1 = 0.0; nsum1 = 0; for( ibox = 0; ibox < box_size; ibox++,state++ ) { angle = state->pol_ang; if( !( *qin & SMF__Q_FIT ) && *din != VAL__BADD && angle != VAL__BADD ) { wplate = pasign*angle + paoff; /* if( ipolcrd == 1 ) { wplate += state->tcs_az_ang; } else if( ipolcrd == 2 ) { wplate += state->tcs_tr_ang; } */ phi = 2*wplate; twophi = 2*phi; s8 = sin( 2*twophi ); c8 = cos( 2*twophi ); s4 = sin( twophi ); c4 = cos( twophi ); s2 = sin( phi ); c2 = cos( phi ); s1 = sin( wplate ); c1 = cos( wplate ); fit = solution[0]*s4 + solution[1]*c4 + solution[2]*s2 + solution[3]*c2 + solution[4]*s1 + solution[5]*c1 + solution[6]*ibox + solution[7] + solution[8]*s8 + solution[9]*c8; res = *din - fit; sum1 += res*res; nsum1++; } din += nbolo; qin += nbolo; } /* Calculate the variance of the residuals, and then scale it to get the notional variance for the returned Q,. U and I values. The scaling factor is determined emprically to get reasonable agreement between these notional variances and the noise actually seen in the Q and U values for 10 test observations. The reason for storing these as Q/U variances rather than as a weights component in the SMURF extension is so that makemap can pick them up easily and use them to initialise the NOI model, which is used for weighting the bolometer data when forming the COM model on the first iteration. */ *(ipv++) = 0.0253*sum1/nsum1; /* Store bad values if there were too few good samples in the fitting box. */ } else { if( ipi ) *(ipi++) = VAL__BADD; *(ipq++) = VAL__BADD; *(ipu++) = VAL__BADD; *(ipv++) = VAL__BADD; } } } } if( wcs ) { g2s = astAnnul( g2s ); s2f = astAnnul( s2f ); astUnlock( wcs, 1 ); } /* End the AST context */ astEnd; }