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; }
int main( int argc, char **argv ){ /* Local variables: */ AstBox *pixbox; AstFitsChan *fchan; AstFrame *pixfrm; AstFrame *wcsfrm; AstFrameSet *frameset; AstKeyMap *warnings; AstMapping *pix2wcs; AstObject *object; AstRegion *wcsbox; AstStcsChan *schan; FILE *fd; char key[ 15 ]; char keyword[ 9 ]; const char *message; double p1[ MAX_AXES ]; double p2[ MAX_AXES ]; int axis; int iwarn; int naxis; int status; /* Initialised the returned system status to indicate success. */ status = 0; /* Check a file was specified on the command line, and attempt to open it for read access. */ if( argc < 2 ) { printf( "Usage: stcschan-demo2 <header-file>\n" ); status = 1; } else { fd = fopen( argv[ 1 ], "r" ); if( !fd ) { printf("Failed to open input file '%s'.\n", argv[ 1 ] ); status = 1; } } /* If a disk file was opened successfully... */ if( !status ) { /* Start an AST object context. This means we do not need to annull each AST Object individually. Instead, all Objects created within this context will be annulled automatically by the corresponding invocation of astEnd. */ astBegin; /* Create a FitsChan. This is the object that converts external FITS headers into corresponding AST Objects. Tell it to use the "source" function for obtaining lines of text from the disk file. */ fchan = astFitsChan( source, NULL, " " ); /* Associate the descriptor for the input disk file with the StcsChan. This makes it available to the "source" function. Since this application is single threaded, we could instead have made "fd" a global variable, but the ChannelData facility is used here to illustrate how to pass data to a source or sink function safely in a multi-threaded application. */ astPutChannelData( fchan, fd ); /* Attempt to read the FITS heades and convert them into an AST FrameSet. */ object = astRead( fchan ); /* The astRead function is a generic function and so returns a generic AstObject pointer. Check an Object was created successfully. */ if( !object ) { printf( "Failed to read an AST Object from file '%s'.\n", argv[ 1 ] ); status = 1; /* Now check that the object read is actually an AST FrameSet, rather than some other class of AST Object. */ } else if( !astIsAFrameSet( object ) ) { printf( "Expected a FrameSet but read a %s from file '%s'.\n", astGetC( object, "Class" ), argv[ 1 ] ); status = 1; /* We now know we have a FrameSet so it is safe to use the pointer returned by astRead as a FrameSet pointer. Do the cast now to avoid repeated casting in future. */ } else { frameset = (AstFrameSet *) object; /* Get a pointer to the Frame that describes the attributes of the FITS world coordinate system. This is the current Frame in the FrameSet read from the FITS headers. */ wcsfrm = astGetFrame( frameset, AST__CURRENT ); /* Get a pointer to the Frame that describes the attributes of the FITS pixel coordinate system. This is the base Frame in the FrameSet read from the FITS headers. */ pixfrm = astGetFrame( frameset, AST__BASE ); /* Get the Mapping that transforms pixel positions into WCS positions. The is the Mapping from base to current Frame in the FrameSet read from the FITS headers. */ pix2wcs = astGetMapping( frameset, AST__BASE, AST__CURRENT ); /* Get the number of axes in ther pixel Frame. */ naxis = astGetI( pixfrm, "Naxes" ); /* For each pixel axis, form the name of the corresponding NAXISi keyword. */ for( axis = 0; axis < naxis; axis++ ) { sprintf( keyword, "NAXIS%d", axis + 1 ); /* Store the pixel coordinate on the current axis at the lower left corner of the first pixel. */ p1[ axis ] = 0.5; /* Get the NAXISi value for the current axis from the FITS header, and store it in array "p2". Report an error if NAXISi is not found. */ if( !astGetFitsF( fchan, keyword, p2 + axis ) ){ printf("Keyword '%s' not found in header\n", keyword ); status = 1; break; /* If it is found, modify "p2" so that it holds the pixel coordinate on the current axis at the upper right corner of the last pixel. */ } else { p2[ axis ] += 0.5; } } } /* If all has gone well, create an AST Region (a Box) describing the rectangular region of pixel coordinates covered by the pixel array. */ if( !status ) { pixbox = astBox( pixfrm, 1, p1, p2, NULL, " " ); /* Map this box into the FITS world coordinate system. The Mapping is specified by "pix2wcs", and the attributes of the resulting axes is described by "wcsfrm". */ wcsbox = astMapRegion( pixbox, pix2wcs, wcsfrm ); /* Create an StcsChan. This is the object that converts (either way) between external STC-S descriptions and their corresponding AST Objects. Tell it to use the "source" function for obtaining lines of text from the disk file. Also tell it to store all warnings generated by the conversion for later use. Other attributes of the StcsChan class retain their default values. */ schan = astStcsChan( NULL, NULL, "ReportLevel=3" ); /* Attempt to write out the Region describing the pixel array (in WCS) as an STC-S description. Report an error if this fails. */ if( ! astWrite( schan, wcsbox ) && astOK ) { printf( "Failed to convert the Region into an STC-S " "description.\n" ); } } /* We asked the StcsChan to record any warnings that were generated whilst converting the AST Region into a corresponding STC-S description. We now see if any such warnings were generated by the earlier call to astWrite. */ warnings = astWarnings( schan ); /* If any warnings were generated, and if no other error has occurred so far, display the warnings. */ if( warnings && !status && astOK ) { printf( "\nThe following warnings were issued:\n" ); /* The warnings are stored in an AST KeyMap (a sort of hashmap). Each warning message is associated with a key of the form "Warning_1", "Warning_2", etc. Loop round successive keys, obtaining a value for each key from the warnings KeyMap, and displaying it. */ iwarn = 1; while( astOK ) { sprintf( key, "Warning_%d", iwarn++ ); if( astMapGet0C( warnings, key, &message ) ) { printf( "\n- %s\n", message ); } else { break; } } } /* End the AST Object context. All Objects created since the corresponding invocation of astbegin will be annulled automatically. */ astEnd; /* Close the disk file. */ (void) fclose( fd ); } /* If an error occurred in the AST library, set the retiurns system status non-zero. */ if( !astOK ) status = 1; return status; }
void smf_mapbounds_approx( Grp *igrp, size_t index, char *system, int *lbnd_out, int *ubnd_out, AstFrameSet **outframeset, int *moving, int *status ) { /* Local variables */ smfData *data = NULL; /* pointer to SCUBA2 data struct */ int dxpix; /* Map X offset in pixels */ int dypix; /* Map Y offset in pixels */ smfFile *file = NULL; /* SCUBA2 data file information */ AstFitsChan *fitschan = NULL;/* Fits channels to construct WCS header */ AstFrameSet *fs = NULL; /* A general purpose FrameSet pointer */ smfHead *hdr = NULL; /* Pointer to data header this time slice */ double hghtbox; /* Map height in arcsec */ int hghtpix; /* RA-Dec map height in pixels */ int i; /* loop counter */ dim_t k; /* Loop counter */ double maphght = 0.0; /* Map height in radians */ double mappa = 0.0; /* Map position angle in radians */ double mapwdth = 0.0; /* Map width in radians */ double mapx; /* Map X offset in radians */ double mapy; /* Map Y offset in radians */ double par[7]; /* Projection parameters */ double pixsize = 0.0; /* Requested pixel size */ double shift[ 2 ]; /* Shifts from PIXEL to GRID coords */ AstMapping *sky2map = NULL; /* Mapping celestial->map coordinates */ AstSkyFrame *skyframe = NULL;/* Output SkyFrame */ AstFrame *skyin = NULL; /* Sky Frame in input FrameSet */ double skyref[ 2 ]; /* Values for output SkyFrame SkyRef attribute */ AstFrameSet *swcsin = NULL; /* FrameSet describing input WCS */ int temp; /* Temporary variable */ double wdthbox; /* Map width in arcsec */ int wdthpix; /* RA-Dec map width in pixels */ double x_array_corners[4]; /* X-Indices for corner bolos in array */ double y_array_corners[4]; /* Y-Indices for corner pixels in array */ /* Main routine */ if (*status != SAI__OK) return; /* Begin an AST context to ensure that all AST objects are annuled before returning to caller */ astBegin; /* Initialize output frameset pointer to NULL */ *outframeset = NULL; for( i = 0; i < 7; i++ ) par[ i ] = AST__BAD; /* Read data from the given input file in the group - note index should be 1 as we use the first file in the Grp to define the map bounds */ smf_open_file( igrp, index, "READ", SMF__NOCREATE_DATA, &data, status ); /* Simply abort if it is not a scan */ if (*status == SAI__OK && data->hdr->obsmode != SMF__OBS_SCAN) { *status = SAI__ERROR; errRep(" ", "Can not call smf_mapbounds_approx with non-scan observation" " (possible programming error)", status); goto CLEANUP; } /* Retrieve file name for use feedback */ file = data->file; smf_smfFile_msg( file, "FILE", 1, "<unknown>" ); if( *status == SAI__OK ) { msgOutif(MSG__VERB, " ", "SMF_MAPBOUNDS_APPROX: Processing ^FILE", status); } else { errRep( "smf_mapbounds_approx", "Couldn't open input file, ^FILE", status ); } /* Check that the data dimensions are 3 (for time ordered data) */ if( *status == SAI__OK ) { if( data->ndims != 3 ) { smf_smfFile_msg( file, "FILE", 1, "<unknown>" ); msgSeti("THEDIMS", data->ndims); *status = SAI__ERROR; errRep("smf_mapbounds_approx", "^FILE data has ^THEDIMS dimensions, should be 3.", status); } } /* Construct the WCS for the first time slice in this file */ smf_tslice_ast( data, 1, 1, NO_FTS, status); /* Retrieve header for later constructing output WCS */ if( *status == SAI__OK) { hdr = data->hdr; swcsin = hdr->wcs; /* Calculate default pixel size */ pixsize = smf_calc_telres( hdr->fitshdr, status ); /* Get the user defined pixel size - we trust that smf_get_projpar will also read PIXSIZE and get the same answer. We pre-fill par[] to allow PIXSIZE=! to accept the dynamic default in both places.*/ parGdr0d( "PIXSIZE", pixsize, 0, 60, 1, &pixsize, status ); par[4] = pixsize*AST__DD2R/3600.0; par[5] = par[4]; /* Retrieve input SkyFrame */ skyin = astGetFrame( swcsin, AST__CURRENT ); /* Retrieve map height and width from header - will be undef for non-scan so set up defaults first. */ mapwdth = 0.0; maphght = 0.0; smf_getfitsd( hdr, "MAP_WDTH", &mapwdth, status ); smf_getfitsd( hdr, "MAP_HGHT", &maphght, status ); /* Make an approximation if map height and width are not set - note that this should ONLY apply for non-scan mode data */ if ( !mapwdth || !maphght ) { if (*status == SAI__OK) { *status = SAI__ERROR; errRep(" ", "MAP_WDTH and MAP_HGHT must be > 0", status); goto CLEANUP; } } mapx = 0.0; /* Used if the FITS keyword values are undefed */ mapy = 0.0; smf_getfitsd( hdr, "MAP_X", &mapx, status ); smf_getfitsd( hdr, "MAP_Y", &mapy, status ); /* Convert map Position Angle to radians */ mappa = 0.0; smf_fits_getD( hdr, "MAP_PA", &mappa, status ); mappa *= AST__DD2R; /* Calculate size of output map in pixels */ /* Note: this works for the simulator... */ wdthbox = mapwdth*fabs(cos(mappa)) + maphght*fabs(sin(mappa)); hghtbox = maphght*fabs(cos(mappa)) + mapwdth*fabs(sin(mappa)); wdthpix = (int) ( wdthbox / pixsize); hghtpix = (int) ( wdthbox / pixsize); dxpix = (int) (mapx / pixsize); dypix = (int) (mapy / pixsize); /* Get the offsets for each corner of the array */ temp = (wdthpix - 1) / 2; x_array_corners[0] = dxpix - temp; x_array_corners[1] = dxpix - temp; x_array_corners[2] = dxpix + temp; x_array_corners[3] = dxpix + temp; temp = (hghtpix - 1) / 2; y_array_corners[0] = dypix - temp; y_array_corners[1] = dypix + temp; y_array_corners[2] = dypix - temp; y_array_corners[3] = dypix + temp; lbnd_out[0] = x_array_corners[0]; ubnd_out[0] = x_array_corners[0]; lbnd_out[1] = y_array_corners[0]; ubnd_out[1] = y_array_corners[0]; /* Update min/max */ for( k=0; k<4; k++ ) { if( x_array_corners[k] < lbnd_out[0] ) lbnd_out[0] = x_array_corners[k]; if( y_array_corners[k] < lbnd_out[1] ) lbnd_out[1] = y_array_corners[k]; if( x_array_corners[k] > ubnd_out[0] ) ubnd_out[0] = x_array_corners[k]; if( y_array_corners[k] > ubnd_out[1] ) ubnd_out[1] = y_array_corners[k]; } } else { goto CLEANUP; } /* Now create the output FrameSet. */ smf_calc_skyframe( skyin, system, hdr, 0, &skyframe, skyref, moving, status ); /* Get the orientation of the map vertical within the output celestial coordinate system. This is derived form the MAP_PA FITS header, which gives the orientation of the map vertical within the tracking system. */ mappa = smf_calc_mappa( hdr, system, skyin, status ); /* Calculate the projection parameters. We do not enable autogrid determination for SCUBA-2 so we do not need to obtain all the data before calculating projection parameters. */ smf_get_projpar( skyframe, skyref, *moving, 0, 0, NULL, 0, mappa, par, NULL, NULL, status ); /* Now populate a FitsChan with FITS-WCS headers describing the required tan plane projection. The longitude and latitude axis types are set to either (RA,Dec) or (AZ,EL) to get the correct handedness. */ fitschan = astFitsChan ( NULL, NULL, " " ); smf_makefitschan( astGetC( skyframe, "System"), &(par[0]), &(par[2]), &(par[4]), par[6], fitschan, status ); astClear( fitschan, "Card" ); fs = astRead( fitschan ); /* Extract the output PIXEL->SKY Mapping - note this is will be inverted later to create the sk2map mapping */ sky2map = astGetMapping( fs, AST__BASE, AST__CURRENT ); /* Create the output FrameSet */ *outframeset = astFrameSet( astFrame(2, "Domain=GRID"), " " ); /* Now add the SkyFrame to it */ astAddFrame( *outframeset, AST__BASE, sky2map, skyframe ); /* Apply a ShiftMap to the output FrameSet to re-align the GRID coordinates */ shift[0] = -lbnd_out[0]; shift[1] = -lbnd_out[1]; astRemapFrame( *outframeset, AST__BASE, astShiftMap( 2, shift, " " ) ); astExport( *outframeset ); /* Report the pixel bounds of the cube. */ if( *status == SAI__OK ) { msgOutif( MSG__NORM, " ", " ", status ); msgSeti( "XL", lbnd_out[ 0 ] ); msgSeti( "YL", lbnd_out[ 1 ] ); msgSeti( "XU", ubnd_out[ 0 ] ); msgSeti( "YU", ubnd_out[ 1 ] ); msgOutif( MSG__NORM, " ", " Output map pixel bounds: ( ^XL:^XU, ^YL:^YU )", status ); } /* Change the pixel bounds to be consistent with the new CRPIX */ ubnd_out[0] -= lbnd_out[0]-1; lbnd_out[0] = 1; ubnd_out[1] -= lbnd_out[1]-1; lbnd_out[1] = 1; /* Clean Up */ CLEANUP: if (*status != SAI__OK) { errRep(FUNC_NAME, "Unable to determine map bounds", status); } if( data != NULL ) smf_close_file( &data, status); astEnd; }
/* 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; }
Hero * Hero::constructFromFitsFile(const QString &fname) { FitsParser parser; bool parsedOk = parser.loadFile( FitsFileLocation::fromLocal( fname)); if( ! parsedOk) { dbg(1) << "Parser failed to load " << fname; Hero * heroPtr = new Hero; heroPtr-> addError( "FitsParser failed to load the file"); return heroPtr; } // alias hdr auto & hdr = parser.getHeaderInfo().headerLines; Hero * heroPtr = new Hero; Hero & hero = * heroPtr; AstErrorGuard guard( heroPtr); AstGCGuard gcGuard; // set naxes in case AST fails to read this file hero.m_ast.naxes = parser.getHeaderInfo().naxis; // set up bunit { hero.m_bunit = parser.getHeaderInfo().bunit; } // and the nicer version of bunit { QString u = hero.m_bunit.simplified(); if( u.toLower() == "kelvin") { hero.m_bunitNiceHtml = "K"; } else { hero.m_bunitNiceHtml = u; } } // Create a FitsChan and feed it the fits header AstFitsChan *fitschan; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-zero-length" fitschan = astFitsChan( NULL, NULL, "" ); #pragma GCC diagnostic pop std::cout << "astOK = " << astOK << "\n"; // feed the header lines one by one and check for errors for( const QString & s : hdr) { std::string stdstr = s.toStdString(); astPutFits( fitschan, stdstr.c_str(), 1); if( ! astOK) { astClearStatus; QString ss = s.trimmed(); std::cout << "Skipping bad card: " << ss << "\n"; hero.addError( "Skipping card: " + ss); } } // reposition to the beginning of the channel (ast thing, it's required, hmmmkey) astClear( fitschan, "Card" ); std::cout << "astOK = " << astOK << "\n"; std::cout << "Here\n"; auto encoding = AstWrappers::getC( fitschan, "Encoding" ); std::cout << "Encoding = " << encoding << "\n"; // do we have warnings? AstKeyMap * warnings = static_cast<AstKeyMap *>( astWarnings( fitschan)); if( warnings && astOK ) { std::cout << "Warnings:\n"; int iwarn = 1; while( astOK ) { std::string key = QString("Warning_%1").arg( iwarn).toStdString(); const char * message = nullptr; if( astMapGet0C( warnings, key.c_str(), & message ) ) { printf( "\n- %s\n", message ); hero.addError( QString( "Warning: %1").arg( message)); } else { break; } } } else { std::cout << "No warnings\n"; } // create a frameset for this file AstFrameSet * wcsinfo = static_cast<AstFrameSet *> ( astRead( fitschan )); std::cout << "astOK = " << astOK << "\n"; if ( ! astOK ) { std::cout << "astOK is not ok\n"; hero.addError( "astRead failed"); astClearStatus; return heroPtr; } else if ( wcsinfo == AST__NULL ) { hero.addError( "No WCS found in the fits file"); std::cout << "No WCS found\n"; return heroPtr; } else if ( AstWrappers::getC( wcsinfo, "Class" ) != "FrameSet") { std::cout << "Some other weird error occured\n"; hero.addError( "AstLib returned non-frame-set"); return heroPtr; } // frame was read in OK, save it hero.m_ast.origWcsInfo = wcsinfo; astExempt( hero.m_ast.origWcsInfo); hero.m_ast.currWcsInfo = astClone( hero.m_ast.origWcsInfo); astExempt( hero.m_ast.currWcsInfo); astShow( wcsinfo); // extract the current sky system // TODO: this assumes axis1 is a skycs QString skysys = AstWrappers::getC( wcsinfo, "System(1)"); hero.m_currentSkyCs = string2skycs( skysys); hero.m_originalSkyCs = hero.m_currentSkyCs; // extract the labels/etc for axes hero.m_ast.naxes = AstWrappers::getI( wcsinfo, "Naxes" ); hero.parseAxesInfo(); return heroPtr; }
void smf_write_itermap( ThrWorkForce *wf, const double *map, const double *mapvar, const smf_qual_t *mapqua, dim_t msize, const Grp *iterrootgrp, size_t contchunk, int iter, const int *lbnd_out, const int *ubnd_out, AstFrameSet *outfset, const smfHead *hdr, const smfArray *qua, int *status ) { int flags; /* Flags indicating required NDF components */ Grp *mgrp=NULL; /* Temporary group to hold map name */ smfData *imapdata=NULL; /* smfData for this iteration map */ char name[GRP__SZNAM+1]; /* Buffer for storing name */ char *pname=NULL; /* Poiner to name */ char tmpname[GRP__SZNAM+1]; /* temp name buffer */ char tempstr[20]; if( *status != SAI__OK ) return; if( !map || !mapvar || !iterrootgrp || !lbnd_out || !ubnd_out || !outfset ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": NULL inputs supplied", status ); return; } if( hdr && !qua ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": hdr supplied but qua is NULL", status ); return; } /* Create a name for this iteration map, take into account the chunk number. Only required if we are using a single output container. */ pname = tmpname; grpGet( iterrootgrp, 1, 1, &pname, sizeof(tmpname), status ); one_strlcpy( name, tmpname, sizeof(name), status ); one_strlcat( name, ".", sizeof(name), status ); /* Continuous chunk number */ sprintf(tempstr, "CH%02zd", contchunk); one_strlcat( name, tempstr, sizeof(name), status ); /* Iteration number */ sprintf( tempstr, "I%03i", iter+1 ); one_strlcat( name, tempstr, sizeof(name), status ); mgrp = grpNew( "itermap", status ); grpPut1( mgrp, name, 0, status ); msgOutf( "", "*** Writing map from this iteration to %s", status, name ); flags = SMF__MAP_VAR; if( mapqua ) flags |= SMF__MAP_QUAL; smf_open_newfile ( wf, mgrp, 1, SMF__DOUBLE, 2, lbnd_out, ubnd_out, flags, &imapdata, status); /* Copy over the signal and variance maps */ if( *status == SAI__OK ) { memcpy( imapdata->pntr[0], map, msize*sizeof(*map) ); memcpy( imapdata->pntr[1], mapvar, msize*sizeof(*mapvar) ); if( mapqua ) memcpy( imapdata->qual, mapqua, msize*sizeof(*mapqua) ); } /* Write out a FITS header */ if( (*status == SAI__OK) && hdr && hdr->allState ) { AstFitsChan *fitschan=NULL; JCMTState *allState = hdr->allState; char *obsidss=NULL; char obsidssbuf[SZFITSTR]; double iter_nboloeff; size_t nmap; size_t ngood_tslices; dim_t ntslice; /* Number of time slices */ fitschan = astFitsChan ( NULL, NULL, " " ); obsidss = smf_getobsidss( hdr->fitshdr, NULL, 0, obsidssbuf, sizeof(obsidssbuf), status ); if( obsidss ) { atlPtfts( fitschan, "OBSIDSS", obsidss, "Unique observation subsys identifier", status ); } atlPtfti( fitschan, "SEQSTART", allState[0].rts_num, "RTS index number of first frame", status ); ntslice = hdr->nframes; atlPtfti( fitschan, "SEQEND", allState[ntslice-1].rts_num, "RTS index number of last frame", status ); /* calculate the effective number of bolometers for this iteration */ smf_qualstats_model( wf, SMF__QFAM_TSERIES, 1, qua, NULL, NULL, &nmap, NULL, NULL, &ngood_tslices, NULL, NULL, status ); iter_nboloeff = (double)nmap / (double)ngood_tslices; atlPtftd( fitschan, "NBOLOEFF", iter_nboloeff, "Effective bolometer count", status ); kpgPtfts( imapdata->file->ndfid, fitschan, status ); if( fitschan ) fitschan = astAnnul( fitschan ); } /* Write WCS (protecting the pointer dereference) */ smf_set_moving(outfset,NULL,status); if (*status == SAI__OK && imapdata) { ndfPtwcs( outfset, imapdata->file->ndfid, status ); } /* Clean up */ if( mgrp ) grpDelet( &mgrp, status ); smf_close_file( wf, &imapdata, status ); }
void smf_write_bolomap( ThrWorkForce *wf, smfArray *res, smfArray *lut, smfArray *qua, smfDIMMData *dat, dim_t msize, const Grp *bolrootgrp, int varmapmethod, const int *lbnd_out, const int *ubnd_out, AstFrameSet *outfset, int *status ) { int addtomap=0; /* Set if adding to existing map */ size_t bstride; /* Bolometer stride */ double *curmap=NULL; /* Pointer to current map being rebinned */ double *curvar=NULL; /* Pointer to variance associate with curmap */ dim_t dsize; /* Size of data arrays in containers */ size_t idx=0; /* index within subgroup */ size_t k; /* loop counter */ int *lut_data=NULL; /* Pointer to DATA component of lut */ char name[GRP__SZNAM+1]; /* Buffer for storing names */ dim_t nbolo; /* Number of bolometers */ size_t nbolomaps = 0; /* Number of bolomaps written */ char *pname=NULL; /* Poiner to name */ smf_qual_t *qua_data=NULL; /* Pointer to DATA component of qua */ double *res_data=NULL; /* Pointer to DATA component of res */ if( *status != SAI__OK ) return; if( !res || !lut || !qua || !dat || !bolrootgrp || !lbnd_out || !ubnd_out || !outfset ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": NULL inputs supplied", status ); return; } /* Loop over subgroup index (subarray) */ for( idx=0; idx<res->ndat; idx++ ) { smf_qual_t *bolomask = NULL; double *bmapweight = NULL; double *bmapweightsq = NULL; int *bhitsmap = NULL; /* Pointers to everything we need */ res_data = res->sdata[idx]->pntr[0]; lut_data = lut->sdata[idx]->pntr[0]; qua_data = qua->sdata[idx]->pntr[0]; smf_get_dims( res->sdata[idx], NULL, NULL, &nbolo, NULL, &dsize, &bstride, NULL, status ); /* Make a copy of the quality at first time slice as a good bolo mask, and then set quality to SMF__Q_BADB. Later we will unset BADB for one bolo at a time to make individual maps. */ bolomask = astMalloc( nbolo*sizeof(*bolomask) ); bmapweight = astMalloc( msize*sizeof(*bmapweight) ); bmapweightsq = astMalloc( msize*sizeof(*bmapweightsq) ); bhitsmap = astMalloc( msize*sizeof(*bhitsmap) ); if( *status == SAI__OK ) { for( k=0; k<nbolo; k++ ) { bolomask[k] = qua_data[k*bstride]; qua_data[k*bstride] = SMF__Q_BADB; } /* Identify good bolos in the copied mask and produce a map */ for( k=0; (k<nbolo)&&(*status==SAI__OK); k++ ) { if( !(bolomask[k]&SMF__Q_BADB) ) { Grp *mgrp=NULL; /* Temporary group to hold map names */ smfData *mapdata=NULL;/* smfData for new map */ char tmpname[GRP__SZNAM+1]; /* temp name buffer */ char thisbol[20]; /* name particular to this bolometer */ size_t col, row; char subarray[10]; nbolomaps++; /* Set the quality back to good for this single bolometer */ qua_data[k*bstride] = bolomask[k]; /* Create a name for the new map, take into account the chunk number and subarray. Only required if we are using a single output container. */ pname = tmpname; grpGet( bolrootgrp, 1, 1, &pname, sizeof(tmpname), status ); one_strlcpy( name, tmpname, sizeof(name), status ); one_strlcat( name, ".", sizeof(name), status ); /* Subarray, column and row. HDS does not care about case but we convert to upper case anyhow. */ smf_find_subarray( res->sdata[idx]->hdr, subarray, sizeof(subarray), NULL, status ); if (*status == SAI__OK) { size_t len = strlen(subarray); size_t n = 0; for (n=0; n<len; n++) { subarray[n] = toupper(subarray[n]); } } col = (k % res->sdata[idx]->dims[1])+1; row = (k / res->sdata[idx]->dims[1])+1; sprintf( thisbol, "%3sC%02zuR%02zu", subarray, col, /* x-coord */ row ); /* y-coord */ one_strlcat( name, thisbol, sizeof(name), status ); mgrp = grpNew( "bolomap", status ); grpPut1( mgrp, name, 0, status ); msgOutf( "", "*** Writing single bolo map %s", status, name ); /* Try to open an existing extention first. Create a new map array, and then later we'll add it to the existing one. If it isn't there, create it. */ smf_open_file( mgrp, 1, "UPDATE", 0, &mapdata, status ); if( *status == SAI__OK ) { /* Allocate memory for the new rebinned data */ curmap = astCalloc( msize, sizeof(*curmap) ); curvar = astCalloc( msize, sizeof(*curvar) ); addtomap = 1; } else if( *status == DAT__NAMIN ) { /* Create a new extension */ errAnnul( status ); smf_open_newfile ( mgrp, 1, SMF__DOUBLE, 2, lbnd_out, ubnd_out, SMF__MAP_VAR, &mapdata, status); /* Rebin directly into the newly mapped space */ if( *status == SAI__OK ) { curmap = mapdata->pntr[0]; curvar = mapdata->pntr[1]; addtomap = 0; } } /* Rebin the data for this single bolometer. Don't care about variance weighting because all samples from same detector are about the same. */ smf_rebinmap1( wf, res->sdata[idx], dat->noi ? dat->noi[0]->sdata[idx] : NULL, lut_data, 0, 0, 0, NULL, 0, SMF__Q_GOOD, varmapmethod, AST__REBININIT | AST__REBINEND, curmap, bmapweight, bmapweightsq, bhitsmap, curvar, msize, NULL, status ); /* If required, add this new map to the existing one */ if( addtomap ) { size_t i; double *oldmap=NULL; double *oldvar=NULL; double weight; if( *status == SAI__OK ) { oldmap = mapdata->pntr[0]; oldvar = mapdata->pntr[1]; for( i=0; i<msize; i++ ) { if( oldmap[i]==VAL__BADD ) { /* No data in this pixel in the old map, just copy */ oldmap[i] = curmap[i]; oldvar[i] = curvar[i]; } else if( curmap[i]!=VAL__BADD && oldvar[i]!=VAL__BADD && oldvar[i]!=0 && curvar[i]!=VAL__BADD && curvar[i]!=0 ) { /* Both old and new values available */ weight = 1/oldvar[i] + 1/curvar[i]; oldmap[i] = (oldmap[i]/oldvar[i] + curmap[i]/curvar[i]) / weight; oldvar[i] = 1/weight; } } } /* Free up temporary arrays */ curmap = astFree( curmap ); curvar = astFree( curvar ); } /* Write out COLNUM and ROWNUM to FITS header */ if( *status == SAI__OK ) { AstFitsChan *fitschan=NULL; fitschan = astFitsChan ( NULL, NULL, " " ); atlPtfti( fitschan, "COLNUM", col, "bolometer column", status); atlPtfti( fitschan, "ROWNUM", row, "bolometer row", status ); atlPtfts( fitschan, "SUBARRAY", subarray, "Subarray identifier", status ); kpgPtfts( mapdata->file->ndfid, fitschan, status ); if( fitschan ) fitschan = astAnnul( fitschan ); /* Set the bolo to bad quality again */ qua_data[k*bstride] = SMF__Q_BADB; /* Write WCS */ smf_set_moving(outfset,NULL,status); ndfPtwcs( outfset, mapdata->file->ndfid, status ); } /* Clean up */ if( mgrp ) grpDelet( &mgrp, status ); if( mapdata ) smf_close_file( &mapdata, status ); } } /* Set quality back to its original state */ for( k=0; k<nbolo; k++ ) { qua_data[k*bstride] = bolomask[k]; } } /* Free up memory */ bolomask = astFree( bolomask ); bmapweight = astFree( bmapweight ); bmapweightsq = astFree( bmapweightsq ); bhitsmap = astFree( bhitsmap ); } msgOutf( "", "*** Wrote %zu bolo maps", status, nbolomaps ); }
void smf_write_shortmap( ThrWorkForce *wf, int shortmap, smfArray *res, smfArray *lut, smfArray *qua, smfDIMMData *dat, dim_t msize, const Grp *shortrootgrp, size_t contchunk, int varmapmethod, const int *lbnd_out, const int *ubnd_out, AstFrameSet *outfset, int *status ) { dim_t dsize; /* Size of data arrays in containers */ size_t i; /* loop counter */ size_t idx=0; /* index within subgroup */ size_t istart; /* First useful timeslice */ size_t iend; /* Last useful timeslice */ int *lut_data=NULL; /* Pointer to DATA component of lut */ char name[GRP__SZNAM+1]; /* Buffer for storing names */ size_t nshort=0; /* Number of short maps */ dim_t ntslice; /* Number of time slices */ char *pname=NULL; /* Poiner to name */ smf_qual_t *qua_data=NULL; /* Pointer to DATA component of qua */ double *res_data=NULL; /* Pointer to DATA component of res */ size_t sc; /* Short map counter */ double *shortmapweight=NULL; /* buffer for shotmap weights */ double *shortmapweightsq=NULL;/* buffer for shotmap weights squared */ int *shorthitsmap=NULL; /* buffer for shotmap hits */ size_t shortstart; /* first time slice of short map */ size_t shortend; /* last time slice of short map */ size_t tstride; /* Time stride */ if( *status != SAI__OK ) return; if( !res || !lut || !qua || !dat || !shortrootgrp || !lbnd_out || !ubnd_out || !outfset || !shortmap ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": NULL inputs supplied", status ); return; } if( !res || !res->sdata || !res->sdata[idx] || !res->sdata[idx]->hdr || !res->sdata[idx]->hdr->allState ) { *status = SAI__ERROR; errRep( "", FUNC_NAME ": RES does not contain JCMTState", status ); return; } /* Allocate space for the arrays */ shortmapweight = astMalloc( msize*sizeof(*shortmapweight) ); shortmapweightsq = astMalloc( msize*sizeof(*shortmapweightsq) ); shorthitsmap = astMalloc( msize*sizeof(*shorthitsmap) ); /* Use first subarray to figure out time dimension. Get the useful start and end points of the time series, and then determine "nshort" -- the number of complete blocks of shortmap time slices in the useful range. */ smf_get_dims( qua->sdata[0], NULL, NULL, NULL, &ntslice, NULL, NULL, &tstride, status ); qua_data = (qua->sdata[0]->pntr)[0]; smf_get_goodrange( qua_data, ntslice, tstride, SMF__Q_BOUND, &istart, &iend, status ); shortstart = istart; if( *status == SAI__OK ) { if( shortmap == -1 ) { nshort = res->sdata[idx]->hdr->allState[iend].tcs_index - res->sdata[idx]->hdr->allState[istart].tcs_index + 1; msgOutf( "", FUNC_NAME ": writing %zu short maps, once each time TCS_INDEX increments", status, nshort ); } else { nshort = (iend-istart+1)/shortmap; if( nshort ) { msgOutf( "", FUNC_NAME ": writing %zu short maps of length %i time slices.", status, nshort, shortmap ); } else { /* Generate warning message if requested short maps are too long*/ msgOutf( "", FUNC_NAME ": Warning! short maps of lengths %i requested, but " "data only %zu time slices.", status, shortmap, iend-istart+1 ); } } } /* Loop over short maps */ for( sc=0; (sc<nshort)&&(*status==SAI__OK); sc++ ) { Grp *mgrp=NULL; /* Temporary group for map names */ smfData *mapdata=NULL; /* smfData for new map */ char tempstr[20]; /* Temporary string */ char tmpname[GRP__SZNAM+1]; /* temp name buffer */ char thisshort[20]; /* name particular to this shortmap */ /* Create a name for the new map, take into account the chunk number. Only required if we are using a single output container. */ pname = tmpname; grpGet( shortrootgrp, 1, 1, &pname, sizeof(tmpname), status ); one_strlcpy( name, tmpname, sizeof(name), status ); one_strlcat( name, ".", sizeof(name), status ); /* Continuous chunk number */ sprintf(tempstr, "CH%02zd", contchunk); one_strlcat( name, tempstr, sizeof(name), status ); /* Shortmap number */ sprintf( thisshort, "SH%06zu", sc ); one_strlcat( name, thisshort, sizeof(name), status ); mgrp = grpNew( "shortmap", status ); grpPut1( mgrp, name, 0, status ); msgOutf( "", "*** Writing short map (%zu / %zu) %s", status, sc+1, nshort, name ); smf_open_newfile ( wf, mgrp, 1, SMF__DOUBLE, 2, lbnd_out, ubnd_out, SMF__MAP_VAR, &mapdata, status); /* Time slice indices for start and end of short map -- common to all subarrays */ if( shortmap > 0) { /* Evenly-spaced shortmaps in time */ shortstart = istart+sc*shortmap; shortend = istart+(sc+1)*shortmap-1; } else { /* One map each time TCS_INDEX increments -- just uses header for the first subarray */ for(i=shortstart+1; (i<=iend) && (res->sdata[0]->hdr->allState[i].tcs_index == res->sdata[0]->hdr->allState[shortstart].tcs_index); i++ ); shortend = i-1; } /* Bad status if we have invalid shortmap ranges. This might happen if there is ever a jump in TCS_INDEX for the shortmap=-1 case since the total number of shortmaps is calculated simply as the difference between the first and final TCS indices. */ if( !nshort || (iend<istart) || (iend>=ntslice) ) { *status = SAI__ERROR; errRepf( "", FUNC_NAME ": invalid shortmap range (%zu--%zu, ntslice=%zu)" "encountered", status, istart, iend, ntslice ); break; } /* Loop over subgroup index (subarray) */ for( idx=0; (idx<res->ndat)&&(*status==SAI__OK); idx++ ) { int rebinflag = 0; /* Pointers to everything we need */ res_data = (res->sdata[idx]->pntr)[0]; lut_data = (lut->sdata[idx]->pntr)[0]; qua_data = (qua->sdata[idx]->pntr)[0]; smf_get_dims( res->sdata[idx], NULL, NULL, NULL, &ntslice, &dsize, NULL, &tstride, status ); /* Rebin the data for this range of tslices. */ if( idx == 0 ) { rebinflag |= AST__REBININIT; } if( idx == (res->ndat-1) ) { rebinflag |= AST__REBINEND; } smf_rebinmap1( NULL, res->sdata[idx], dat->noi ? dat->noi[0]->sdata[idx] : NULL, lut_data, shortstart, shortend, 1, NULL, 0, SMF__Q_GOOD, varmapmethod, rebinflag, mapdata->pntr[0], shortmapweight, shortmapweightsq, shorthitsmap, mapdata->pntr[1], msize, NULL, status ); /* Write out FITS header */ if( (*status == SAI__OK) && res->sdata[idx]->hdr && res->sdata[idx]->hdr->allState ) { AstFitsChan *fitschan=NULL; JCMTState *allState = res->sdata[idx]->hdr->allState; size_t midpnt = (shortstart + shortend) / 2; fitschan = astFitsChan ( NULL, NULL, " " ); atlPtfti( fitschan, "SEQSTART", allState[shortstart].rts_num, "RTS index number of first frame", status ); atlPtfti( fitschan, "SEQEND", allState[shortend].rts_num, "RTS index number of last frame", status); atlPtftd( fitschan, "MJD-AVG", allState[midpnt].rts_end, "Average MJD of this map", status ); atlPtfts( fitschan, "TIMESYS", "TAI", "Time system for MJD-AVG", status ); atlPtfti( fitschan, "TCSINDST", allState[shortstart].tcs_index, "TCS index of first frame", status ); atlPtfti( fitschan, "TCSINDEN", allState[shortend].tcs_index, "TCS index of last frame", status ); kpgPtfts( mapdata->file->ndfid, fitschan, status ); if( fitschan ) fitschan = astAnnul( fitschan ); } } /* Update shortstart in case we are counting steps in TCS_INDEX */ shortstart = shortend+1; /* Write WCS */ smf_set_moving( (AstFrame *) outfset, NULL, status ); ndfPtwcs( outfset, mapdata->file->ndfid, status ); /* Clean up */ if( mgrp ) grpDelet( &mgrp, status ); smf_close_file( wf, &mapdata, status ); } /* Free up memory */ shortmapweight = astFree( shortmapweight ); shortmapweightsq = astFree( shortmapweightsq ); shorthitsmap = astFree( shorthitsmap ); }
void atlGetPixelParams( AstFrameSet *fset, int *dims, int degs, double *crpix, double *crval, double *cdelt, double *crota, int *status ){ /* *+ * Name: * atlGetPixelParams * Purpose: * Find typical values for "FITS-like" parameters describing a FrameSet. * Invocation: * void atlGetPixelParams( AstFrameSet *fset, int *dims, int degs, * double *crpix, double *crval, double *cdelt, * double *crota, int *status ) * Description: * This function finds values that resemble the the FITS keywords * CRVAL1/2/3.., CRPIX1/2/3..., CRDELT1/2/3... and CROTA2, on the * assumption that the base Frame in the supplied FrameSet describe * GRID coords (i.e. FITS pixel coords), and the current Frame describe * the required WCS. It is not restricted to 2D FrameSets. * * If the FrameSet can be written to a FitsChan successfully using * FITS-WCS encoding, the the resulting keyword values are returned. * Otherwise, the values are estimated by transforming closely spaced * pixel positions along each axis. If the current Frame contains a * SkyFrame, and the SkyFrame has a defined reference position, then * this position specifies the returned CRVAL values. Otherwise, the * reference position is assumed to be at the central pixel. * Arguments: * fset * The FrameSet. * dims * Pointer to an array supplied holding the number of pixels along * each edge of the pixel array. The number of elements in this array * should match the number of axes in the base Frame of "fset". * degs * If non-zero, then the crval, cdelt and crota values for sky axes * are returned in units of degrees. Otherwise they are returned in * radians. * crpix * Pointer to an array returned holding the position of the * reference pixel in the base Frame of "fset". The number of * elements in this array should match the number of axes in the base * Frame of "fset". * crval * Pointer to an array returned holding the position of the * reference pixel in the current Frame of "fset". The number of * elements in this array should match the number of axes in the * current Frame of "fset". * cdelt * Pointer to an array returned holding the geodesic distance * along each edge of the reference pixel, measured within the * current Frame of "fset". The number of elements in this array * should match the number of axes in the base Frame of "fset". * crota * Pointer to a double in which to return the angle from north in * the current frame of "fset" to the second spatial pixel axis, * measured positive through east. This will be returned set to * AST__BAD if the current frame of "fset" does not contain a SkyFrame. * status * The global status. * Copyright: * Copyright (C) 2013 Science & Technology Facilities Council. * All Rights Reserved. * Licence: * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be * useful,but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street,Fifth Floor, Boston, MA * 02110-1301, USA * Authors: * DSB: David S. Berry (JAC, Hawaii) * {enter_new_authors_here} * History: * 13-DEC-2013 (DSB): * Original version. * {enter_further_changes_here} *- */ /* Local Variables: */ AstFitsChan *fc; AstMapping *map; int npix; int nwcs; int lataxis; int lonaxis; char name[20]; const char *cval; int ipix; double dval1; double dval2; int ival; int iwcs; double pixpos[ ATL__MXDIM ]; double wcspos[ ATL__MXDIM ]; int pixaxes[ ATL__MXDIM ]; int wcsaxes[ ATL__MXDIM ]; int skyaxis1; int skyaxis2; /* Initialise returned values. */ *crota = AST__BAD; /* Check the inherited status. */ if( *status != SAI__OK ) return; /* Begin an AST context so that all AST objects created in this function are freed automatically when the context is ended. */ astBegin; /* Get the number of pixel axes (base frame) and wcs axes (current frame). */ npix = astGetI( fset, "Nin" ); if( npix > ATL__MXDIM && *status == SAI__OK ) { *status = SAI__ERROR; errRepf( "", "atlGetPixelParams: Too many pixel axes (%d). Must be " "no more than %d.", status, npix, ATL__MXDIM ); } nwcs = astGetI( fset, "Nout" ); if( nwcs > ATL__MXDIM && *status == SAI__OK ) { *status = SAI__ERROR; errRepf( "", "atlGetPixelParams: Too many WCS axes (%d). Must be " "no more than %d.", status, nwcs, ATL__MXDIM ); } /* Attempt to find a pair of sky axes in the current Frame by checking the "Domain" value for each WCS axis. */ lataxis = -1; lonaxis = -1; skyaxis1 = -1; skyaxis2 = -1; for( iwcs = 0; iwcs < nwcs; iwcs++ ) { sprintf( name, "Domain(%d)", iwcs + 1 ); cval = astGetC( fset, name ); if( cval && !strcmp( cval, "SKY" ) ) { /* Determine if this sky axis is a longitude or latitude axis, and record the zero-based indicies of the longitude and latitude axes. */ sprintf( name, "IsLatAxis(%d)", iwcs + 1 ); ival = astGetI( fset, name ); if( ival ) { lataxis = iwcs; } else { lonaxis = iwcs; } } } /* If a pair of sky axes were found in the current Frame, get the indices of the corresponding pair of pixel axes. */ if( lonaxis >= 0 && lataxis >= 0 ) { /* If there are only two pixel axes, they must be the sky axes. */ if( nwcs == 2 ) { skyaxis1 = 0; skyaxis2 = 1; /* If there are more than two pixel axes, we need to work harder. */ } else { /* Use astMapSplit to find the two pixel axes that feed the two sky axes. astMapSplit identifes outputs corresponding to specified mapping inputs, so we need to invert the FrameSet first so that the WCS Frame becomes the input (i.e. base Frame). Remember to un-invert the FrameSet afterwards. */ astInvert( fset ); wcsaxes[ 0 ] = lataxis + 1; wcsaxes[ 1 ] = lonaxis + 1; astMapSplit( fset, 2, wcsaxes, pixaxes, &map ); astInvert( fset ); /* If the wcs->pixel mapping was split succesfully, the pixaxes array will contain the one-based pixel axes that feed the sky axes. Convert them to zero-based and note the lowest and highest. */ if( map && astGetI( map, "Nout" ) == 2 ) { if( pixaxes[ 0 ] < pixaxes[ 1 ] ) { skyaxis1 = pixaxes[ 0 ] - 1; skyaxis2 = pixaxes[ 1 ] - 1; } else { skyaxis1 = pixaxes[ 1 ] - 1; skyaxis2 = pixaxes[ 0 ] - 1; } /* If it could not be split, it means the spatial and non-spatial axes are tangle up by the pixel->wcs mapping to such an extent that they cannot be separated. */ } else if( *status == SAI__OK ) { *status = SAI__ERROR; errRepf( "", "atlGetPixelParams: Cannot separate the spatial " "axes from the non-spatial axes.", status ); } } } /* Attempt to write the supplied FrameSet to FitsChan using FITS-WCS encoding. If successful, retrieve the required values. Convert sky values from degrees to radians if required. */ fc = astFitsChan( NULL, NULL, "Encoding=FITS-WCS" ); if( astWrite( fc, fset ) == 1 ) { for( ipix = 0; ipix < npix; ipix++ ) { sprintf( name, "CRPIX%d", ipix + 1 ); if( !astGetFitsF( fc, name, crpix + ipix ) && *status == SAI__OK ) { *status = SAI__ERROR; errRepf( "", "atlGetPixelParams: %s not found in FitsChan " "(possible programming error).", status, name ); } sprintf( name, "CDELT%d", ipix + 1 ); if( !astGetFitsF( fc, name, cdelt + ipix ) && *status == SAI__OK ) { *status = SAI__ERROR; errRepf( "", "atlGetPixelParams: %s not found in FitsChan " "(possible programming error).", status, name ); } if( !degs && ( ipix == skyaxis1 || ipix == skyaxis2 ) ){ cdelt[ ipix ] *= AST__DD2R; } } for( iwcs = 0; iwcs < nwcs; iwcs++ ) { sprintf( name, "CRVAL%d", iwcs + 1 ); if( !astGetFitsF( fc, name, crval + iwcs ) && *status == SAI__OK ) { *status = SAI__ERROR; errRepf( "", "atlGetPixelParams: %s not found in FitsChan " "(possible programming error).", status, name ); } if( !degs && ( iwcs == lonaxis || iwcs == lataxis ) ){ crval[ iwcs ] *= AST__DD2R; } } /* Derive the position angle (within the sky frame) of the second spatial pixel axis, based on the PCi_j rotation matrix elements. Note, there is no assumption here that the latitude and longitude axes are orthogonal in the pixel frame. FITS-WCS allows for shear, so this value says nothing about the orientation of the first spatial pixel axis. But in practice images nearly always have no shear. */ if( lataxis >= 0 && lonaxis >= 0 ) { sprintf( name, "PC%d_%d", lonaxis + 1, skyaxis1 + 1 ); if( !astGetFitsF( fc, name, &dval1 ) && *status == SAI__OK ) { dval1 = ( lonaxis == skyaxis1 ) ? 1.0 : 0.0; } sprintf( name, "PC%d_%d", lonaxis + 1, skyaxis2 + 1 ); if( !astGetFitsF( fc, name, &dval2 ) && *status == SAI__OK ) { dval2 = ( lonaxis == skyaxis2 ) ? 1.0 : 0.0; } *crota = atan2( dval2, dval1 ); if( *crota < 0.0 ) *crota += 2*AST__DPI; if( degs ) *crota *= AST__DR2D; } /* If the supplied FrameSet could not be converted to a set of FITS-WCS keywords, we derive similar values by looking at small increments of pixel position. */ } else { /* First job is to decide on the reference position. By default we use the central pixel. Store the corresponding pixel coords. */ for( ipix = 0; ipix < npix; ipix++ ) { crpix[ ipix ] = ( 1.0 + dims[ ipix ] )/2.0; } /* Convert this pixel position to the WCS Frame. */ astTranN( fset, 1, npix, 1, crpix, 1, nwcs, 1, crval ); /* If the current Frame contains a pair of sky axes, then the associated SkyFrame may include a reference position. If so, we will use it instead of the central pixel. First test to see if the SkyFrame has a reference position. If so get the reference longitude and latitude in radians, and convert the new reference position back to pixel coords. */ if( lonaxis >= 0 && lataxis >= 0 ) { sprintf( name, "SkyRef(%d)", lonaxis + 1 ); if( astTest( fset, name ) ) { crval[ lonaxis ] = astGetD( fset, name ); sprintf( name, "SkyRef(%d)", lataxis + 1 ); crval[ lataxis ] = astGetD( fset, name ); astTranN( fset, 1, nwcs, 1, crval, 0, npix, 1, crpix ); /* If we have sky axes but the skyframe has no reference position, we need to check that the central pixel is a good default. For instance, it may be off the edge of an all-sky map, in which case it is no good as a reference position. */ } else if( ( crval[ lonaxis ] == AST__BAD || crval[ lataxis ] == AST__BAD ) && *status == SAI__OK ) { *status = SAI__ERROR; errRepf( "", "atlGetPixelParams: No reference position can be " "determined.", status ); } } /* Normalize the reference position. */ astNorm( fset, crval ); /* Now we find the pixel size on each pixel axis. First take a copy of the pixel reference position. */ memcpy( pixpos, crpix, npix*sizeof( *pixpos ) ); for( ipix = 0; ipix < npix; ipix++ ) { /* Store a pixel position which is offset away from the reference position by one pixel along the current pixel axis, and then transform it into the WCS Frame. */ pixpos[ ipix ] += 1.0; astTranN( fset, 1, npix, 1, pixpos, 1, nwcs, 1, wcspos ); pixpos[ ipix ] -= 1.0; /* Find the geodesic distance between this WCS position and the reference position. */ cdelt[ ipix ] = astDistance( fset, crval, wcspos ); } /* Find the crota value if we have a pair of sky axes. */ if( lonaxis >= 0 && lataxis >= 0 ){ /* Get a WCS position about one arc-second north of the reference position. */ memcpy( wcspos, crval, nwcs*sizeof( *wcspos ) ); wcspos[ lataxis ] += 5.0E-6; /* Transform to pixel coordinates. */ astTranN( fset, 1, nwcs, 1, wcspos, 0, npix, 1, pixpos ); /* Get the required angle. */ *crota = atan2( pixpos[ skyaxis1 ] - crpix[ skyaxis1 ], pixpos[ skyaxis1 ] - crpix[ skyaxis2 ] ); if( *crota < 0.0 ) *crota += 2*AST__DPI; if( !degs ) *crota *= AST__DR2D; } /* Convert the returned angles to degrees if required. */ if( !degs && ( lonaxis >= 0 && lataxis >= 0 ) ){ crval[ lataxis ] *= AST__DR2D; crval[ lonaxis ] *= AST__DR2D; cdelt[ skyaxis1 ] *= AST__DR2D; cdelt[ skyaxis2 ] *= AST__DR2D; } } /* End the AST context. This will annull all AST objects created in this function. */ astEnd; }