示例#1
0
void smf_calc_mode ( smfHead * hdr, int * status ) {

  char sam_mode[SZFITSTR];   /* Value of SAM_MODE header */
  char obs_type[SZFITSTR];   /* value of OBS_TYPE header */
  char sw_mode[SZFITSTR];    /* value of SW_MODE header */
  char seq_type[SZFITSTR];   /* value of SEQ_TYPE header */
  char inbeamstr[SZFITSTR];  /* value of INBEAM header */

  smf_obstype type = SMF__TYP_NULL;   /* temporary type */
  smf_obstype stype = SMF__TYP_NULL;   /* temporary seq type */
  smf_obsmode mode = SMF__OBS_NULL;   /* temporary mode */
  smf_swmode  swmode = SMF__SWM_NULL; /* Switching mode */
  smf_inbeam_t inbeam = SMF__INBEAM_NOTHING; /* what is in beam? */

  if (*status != SAI__OK) return;

  if (hdr == NULL) {
    *status = SAI__ERROR;
    errRep( " ", "Null pointer supplied to " FUNC_NAME, status );
    return;
  }

  /* Proceed if we're using a valid instrument */
  if( hdr->instrument != INST__NONE ) {

    /* Read the relevant headers */
    smf_fits_getS( hdr, "SAM_MODE", sam_mode, sizeof(sam_mode), status );
    smf_fits_getS( hdr, "SW_MODE", sw_mode, sizeof(sw_mode), status );
    smf_fits_getS( hdr, "OBS_TYPE", obs_type, sizeof(obs_type), status );

    /* INBEAM can be undef */
    inbeamstr[0] = '\0';
    smf_getfitss( hdr, "INBEAM", inbeamstr, sizeof(inbeamstr), status );

    /* SEQ_TYPE is "new" */
    if ( *status == SAI__OK ) {
      smf_fits_getS( hdr, "SEQ_TYPE", seq_type, sizeof(seq_type), status );
      if (*status == SMF__NOKWRD ) {
        errAnnul( status );
        one_strlcpy( seq_type, obs_type, sizeof(seq_type), status );
      }
    }

    /* start with sample type */
    if (strcasecmp( sam_mode, "SCAN" ) == 0 ||
        strcasecmp( sam_mode, "RASTER") == 0 ) {
      mode = SMF__OBS_SCAN;
    } else if (strcasecmp( sam_mode, "STARE" ) == 0) {
      mode = SMF__OBS_STARE;
    } else if (strcasecmp( sam_mode, "DREAM" ) == 0) {
      mode = SMF__OBS_DREAM;
    } else if (strcasecmp( sam_mode, "JIGGLE" ) == 0) {
      mode = SMF__OBS_JIGGLE;
    } else if (strcasecmp( sam_mode, "GRID" ) == 0) {
      mode = SMF__OBS_GRID;
    } else {
      if (*status != SAI__OK) {
        *status = SAI__ERROR;
        msgSetc( "MOD", sam_mode );
        errRep( " ", "Unrecognized observing mode '^MOD'", status );
      }
    }

    /* switching mode: options are "none", "pssw", "chop", "freqsw", "self" */
    if (strcasecmp( sw_mode, "NONE" ) == 0 ) {
      swmode = SMF__SWM_NULL;
    } else if (strcasecmp( sw_mode, "PSSW" ) == 0) {
      swmode = SMF__SWM_PSSW;
    } else if (strcasecmp( sw_mode, "CHOP" ) == 0) {
      swmode = SMF__SWM_CHOP;
    } else if (strcasecmp( sw_mode, "SELF" ) == 0) {
      swmode = SMF__SWM_SELF;
    } else if (strcasecmp( sw_mode, "FREQSW" ) == 0) {
      swmode = SMF__SWM_FREQSW;
    } else {
      if (*status != SAI__OK) {
        *status = SAI__ERROR;
        msgSetc( "MOD", sw_mode );
        errRep( " ", "Unrecognized switching mode '^MOD'", status );
      }
    }

    /* obs type */
    type = smf__parse_obstype( obs_type, status );
    stype = smf__parse_obstype( seq_type, status );

    /* in beam (convert to upper case to make it case insensitive) */
    astChrCase( NULL, inbeamstr, 1, 0 );
    if ( smf_pattern_extract( inbeamstr, "(POL)", NULL, NULL, 0, status ) ) {
      inbeam |= SMF__INBEAM_POL;
    }
    if ( smf_pattern_extract( inbeamstr, "(FTS)", NULL, NULL, 0, status ) ) {
      inbeam |= SMF__INBEAM_FTS;
    }
    if ( smf_pattern_extract( inbeamstr, "(BODY)", NULL, NULL, 0, status ) ) {
      inbeam |= SMF__INBEAM_BLACKBODY;
      /* We could consider ensuring FTS is not set in this case */
    }
  }

  hdr->obstype = type;
  hdr->seqtype = stype;
  hdr->obsmode = mode;
  hdr->swmode = swmode;
  hdr->inbeam = inbeam;

}
示例#2
0
void smurf_calcqu( int *status ) {

    /* Local Variables: */
    AstFitsChan *fc;           /* Holds FITS headers for output NDFs */
    AstKeyMap *config;         /* Holds all cleaning parameters */
    AstKeyMap *dkpars;         /* Holds dark squid cleaning parameters */
    AstKeyMap *heateffmap = NULL; /* Heater efficiency data */
    AstKeyMap *sub_instruments;/* Indicates which instrument is being used */
    Grp *bgrp = NULL;          /* Group of base names for each chunk */
    Grp *igrp = NULL;          /* Group of input files */
    Grp *ogrp = NULL;          /* Group of output files  */
    Grp *sgrp = NULL;          /* Group of science files */
    HDSLoc *loci = NULL;       /* Locator for output I container file */
    HDSLoc *locq = NULL;       /* Locator for output Q container file */
    HDSLoc *locu = NULL;       /* Locator for output U container file */
    NdgProvenance *oprov;      /* Provenance to store in each output NDF */
    ThrWorkForce *wf;          /* Pointer to a pool of worker threads */
    char headval[ 81 ];        /* FITS header value */
    char ndfname[ 30 ];        /* Name of output Q or U NDF */
    char polcrd[ 81 ];         /* FITS 'POL_CRD' header value */
    char subarray[ 10 ];       /* Subarray name (e.g. "s4a", etc) */
    double angrot;             /* Angle from focal plane X axis to fixed analyser */
    double paoff;              /* WPLATE value corresponding to POL_ANG=0.0 */
    float arcerror;            /* Max acceptable error (arcsec) in one block */
    int block_end;             /* Index of last time slice in block */
    int block_start;           /* Index of first time slice in block */
    int dkclean;               /* Clean dark squids? */
    int fix;                   /* Fix the POL-2 triggering issue? */
    int iblock;                /* Index of current block */
    int iplace;                /* NDF placeholder for current block's I image */
    int ipolcrd;               /* Reference direction for waveplate angles */
    int maxsize;               /* Max no. of time slices in a block */
    int minsize;               /* Min no. of time slices in a block */
    int nc;                    /* Number of characters written to a string */
    int pasign;                /* +1 or -1 indicating sense of POL_ANG value */
    int qplace;                /* NDF placeholder for current block's Q image */
    int submean;               /* Subtract mean value from each time slice? */
    int uplace;                /* NDF placeholder for current block's U image */
    size_t ichunk;             /* Continuous chunk counter */
    size_t idx;                /* Subarray counter */
    size_t igroup;             /* Index for group of related input NDFs */
    size_t inidx;              /* Index into group of science input NDFs */
    size_t nchunk;             /* Number continuous chunks outside iter loop */
    size_t ssize;              /* Number of science files in input group */
    smfArray *concat = NULL;   /* Pointer to smfArray holding bolometer data */
    smfArray *darks = NULL;    /* dark frames */
    smfArray *dkarray = NULL;  /* Pointer to smfArray holding dark squid data */
    smfArray *flatramps = NULL;/* Flatfield ramps */
    smfData *data = NULL;      /* Concatenated data for one subarray */
    smfData *dkdata = NULL;    /* Concatenated dark squid data for one subarray */
    smfGroup *sgroup = NULL;   /* smfGroup corresponding to sgrp */

    /* Check inhereited status */
    if( *status != SAI__OK ) return;

    /* Start new AST and NDF contexts. */
    astBegin;
    ndfBegin();

    /* Find the number of cores/processors available and create a work force
       holding the same number of threads. */
    wf = thrGetWorkforce( thrGetNThread( SMF__THREADS, status ), status );

    /* Get a group of input files */
    kpg1Rgndf( "IN", 0, 1, "  Give more NDFs...", &igrp, &ssize, status );

    /* Get a group containing just the files holding science data. */
    smf_find_science( igrp, &sgrp, 0, NULL, NULL, 1, 1, SMF__NULL, &darks,
                      &flatramps, &heateffmap, NULL, status );

    /* Check we have at least once science file. */
    ssize = grpGrpsz( sgrp, status );
    if( ssize == 0 ) {
        msgOutif( MSG__NORM, " ", "All supplied input frames were DARK.",
                  status );
    } else {

        /* See if a correction should be made for the POL2 triggering issue. */
        parGet0l( "FIX", &fix, status );

        /* Create HDS container files to hold the output NDFs. */
        datCreat( "OUTQ", "CALCQU", 0, 0, status );
        datCreat( "OUTU", "CALCQU", 0, 0, status );

        /* Associate the locators with the structures. */
        datAssoc( "OUTQ", "WRITE", &locq, status );
        datAssoc( "OUTU", "WRITE", &locu, status );

        /* The I images are optional. */
        if( *status == SAI__OK ) {
            datCreat( "OUTI", "CALCQU", 0, 0, status );
            datAssoc( "OUTI", "WRITE", &loci, status );
            if( *status == PAR__NULL ) {
                errAnnul( status );
                loci = NULL;
            }
        }

        /* Group the input files so that all files within a single group have the
           same wavelength and belong to the same subscan of the same observation.
           Also identify chunks of data that are contiguous in time, and
           determine to which such chunk each group belongs. All this information
           is returned in a smfGroup structure ("*sgroup"). */
        smf_grp_related( sgrp, ssize, 1, 1, 0, NULL, NULL, NULL,
                         NULL, &sgroup, &bgrp, NULL, status );

        /* Obtain the number of contiguous chunks. */
        if( *status == SAI__OK ) {
            nchunk = sgroup->chunk[ sgroup->ngroups - 1 ] + 1;
        } else {
            nchunk = 0;
        }

        /* Indicate we have not yet found a value for the ARCERROR parameter. */
        arcerror = 0.0;

        /* Loop over all contiguous chunks */
        for( ichunk = 0; ichunk < nchunk && *status == SAI__OK; ichunk++ ) {

            /* Display the chunk number. */
            if( nchunk > 1 ) {
                msgOutiff( MSG__VERB, "", "   Doing chunk %d of %d.",
                           status, (int) ichunk + 1, (int) nchunk );
            }

            /* Concatenate the data within this contiguous chunk. This produces a
               smfArray ("concat") containing a smfData for each subarray present in
               the chunk. Each smfData holds the concatenated data for a single
               subarray. */
            smf_concat_smfGroup( wf, NULL, sgroup, darks, NULL, flatramps,
                                 heateffmap, ichunk, 1, 1, NULL, 0, NULL, NULL,
                                 0, 0, 0, &concat, NULL, status );

            /* Get a KeyMap holding values for the configuration parameters. Since we
               sorted by wavelength when calling smf_grp_related, we know that all
               smfDatas in the current smfArray (i.e. chunk) will relate to the same
               wavelength. Therefore we can use the same parameters for all smfDatas in
               the current smfArray. */
            sub_instruments = smf_subinst_keymap( SMF__SUBINST_NONE,
                                                  concat->sdata[ 0 ], NULL,
                                                  0, status );
            config = kpg1Config( "CONFIG", "$SMURF_DIR/smurf_calcqu.def",
                                 sub_instruments, status );
            sub_instruments = astAnnul( sub_instruments );


            /* Get the CALCQU specific parameters. */
            if( !astMapGet0I( config, "PASIGN", &pasign ) ) pasign = 1;
            msgOutiff( MSG__VERB, "", "PASIGN=%d", status, pasign );
            if( !astMapGet0D( config, "PAOFF", &paoff ) ) paoff = 0.0;
            msgOutiff( MSG__VERB, "", "PAOFF=%g", status, paoff );
            if( !astMapGet0D( config, "ANGROT", &angrot ) ) angrot = 90.0;
            msgOutiff( MSG__VERB, "", "ANGROT=%g", status, angrot );
            if( !astMapGet0I( config, "SUBMEAN", &submean ) ) submean = 0;
            msgOutiff( MSG__VERB, "", "SUBMEAN=%d", status, submean );

            /* See if the dark squids should be cleaned. */
            if( !astMapGet0I( config, "DKCLEAN", &dkclean ) ) dkclean = 0;

            /* If required, clean the dark squids now since we might need to use them to
               clean the bolometer data. */
            if( dkclean ) {

                /* Create a smfArray containing the dark squid data. For each one, store
                   a pointer to the main header so that smf_clean_smfArray can get at the
                   JCMTState information. */
                dkarray = smf_create_smfArray( status );
                for( idx = 0; idx < concat->ndat && *status == SAI__OK; idx++ ) {
                    data = concat->sdata[ idx ];
                    if( data && data->da && data->da->dksquid ) {
                        dkdata = data->da->dksquid;
                        dkdata->hdr = data->hdr;
                        smf_addto_smfArray( dkarray, dkdata, status );
                    }
                }

                /* Clean the smfArray containing the dark squid data. Use the "CLEANDK.*"
                   parameters. */
                (void) astMapGet0A( config, "CLEANDK", &dkpars );
                smf_clean_smfArray( wf, dkarray, NULL, NULL, NULL, dkpars, status );
                dkpars = astAnnul( dkpars );

                /* Nullify the header pointers so that we don't accidentally close any. */
                if( dkarray ) {
                    for( idx = 0; idx < dkarray->ndat; idx++ ) {
                        dkdata = dkarray->sdata[ idx ];
                        dkdata->hdr = NULL;
                    }

                    /* Free the smfArray holding the dark squid data, but do not free the
                       individual smfDatas within it. */
                    dkarray->owndata = 0;
                    smf_close_related( &dkarray, status );
                }
            }

            /* Now clean the bolometer data */
            smf_clean_smfArray( wf, concat, NULL, NULL, NULL, config, status );

            /* If required correct for the POL2 triggering issue. */
            if( fix ) smf_fix_pol2( wf, concat, status );

            /* Loop round each sub-array in the current contiguous chunk of data. */
            for( idx = 0; idx < concat->ndat && *status == SAI__OK; idx++ ) {
                data = concat->sdata[ idx ];

                /* Find the name of the subarray that generated the data. */
                smf_find_subarray( data->hdr, subarray, sizeof(subarray), NULL,
                                   status );

                /* Display the sub-array. */
                if( concat->ndat > 1 ) {
                    msgOutiff( MSG__VERB, "", "   Doing sub-array %s.",
                               status, subarray );
                }

                /* Create an empty provenance structure. Each input NDF that contributes
                   to the current chunk and array will be added as an ancestor to this
                   structure, which will later be stored in each output NDF created for
                   this chunk and array. */
                oprov = ndgReadProv( NDF__NOID, "SMURF:CALCQU", status );

                /* Indicate we do not yet have any FITS headers for the output NDFs */
                fc = NULL;

                /* Indicate we do not yet know the coordinate reference frame for the
                   half-waveplate angle. */
                polcrd[ 0 ] = 0;
                ipolcrd = 0;

                /* Go through the smfGroup looking for groups of related input NDFs that
                   contribute to the current chunk. */
                for( igroup = 0; igroup < sgroup->ngroups; igroup++ ) {
                    if( sgroup->chunk[ igroup ] == ichunk ) {

                        /* Get the integer index into the GRP group (sgrp) that holds the input NDFs.
                           This index identifies the input NDF that provides the data for the current
                           chunk and subarray. This assumes that the order in which smf_concat_smfGroup
                           stores arrays in the "concat" smfArray matches the order in which
                           smf_grp_related stores arrays within the sgroup->subgroups. */
                        inidx = sgroup->subgroups[ igroup ][ idx ];

                        /* Add this input NDF as an ancestor into the output provenance structure. */
                        smf_accumulate_prov( NULL, sgrp, inidx, NDF__NOID,
                                             "SMURF:CALCQU", &oprov, status );

                        /* Merge the FITS headers from the current input NDF into the FitsChan
                           that holds headers for the output NDFs. The merging retains only those
                           headers which have the same value in all input NDFs. */
                        smf_fits_outhdr( data->hdr->fitshdr, &fc, status );

                        /* Get the polarimetry related FITS headers and check that all input NDFs
                           have usabie values. */
                        headval[ 0 ] = 0;
                        smf_getfitss( data->hdr, "POL_MODE", headval,
                                      sizeof(headval), status );
                        if( strcmp( headval, "CONSTANT" ) && *status == SAI__OK ) {
                            *status = SAI__ERROR;
                            grpMsg( "N", sgrp, inidx );
                            errRep( " ", "Input NDF ^N does not contain "
                                    "polarimetry data obtained with a spinning "
                                    "half-waveplate.", status );
                        }

                        headval[ 0 ] = 0;
                        smf_getfitss( data->hdr, "POLWAVIN", headval,
                                      sizeof(headval), status );
                        if( strcmp( headval, "Y" ) && *status == SAI__OK ) {
                            *status = SAI__ERROR;
                            grpMsg( "N", sgrp, inidx );
                            errRep( " ", "Half-waveplate was not in the beam for "
                                    "input NDF ^N.", status );
                        }

                        headval[ 0 ] = 0;
                        smf_getfitss( data->hdr, "POLANLIN", headval,
                                      sizeof(headval), status );
                        if( strcmp( headval, "Y" ) && *status == SAI__OK ) {
                            *status = SAI__ERROR;
                            grpMsg( "N", sgrp, inidx );
                            errRep( " ", "Analyser was not in the beam for input "
                                    "NDF ^N.", status );
                        }

                        if( polcrd[ 0 ] ) {
                            headval[ 0 ] = 0;
                            smf_getfitss( data->hdr, "POL_CRD", headval,
                                          sizeof(headval), status );
                            if( strcmp( headval, polcrd ) && *status == SAI__OK ) {
                                *status = SAI__ERROR;
                                errRep( " ", "Input NDFs have differing values for "
                                        "FITS header 'POL_CRD'.", status );
                            }

                        } else {
                            smf_getfitss( data->hdr, "POL_CRD", polcrd,
                                          sizeof(polcrd), status );
                            if( !strcmp( polcrd, "FPLANE" ) ) {
                                ipolcrd = 0;
                            } else if( !strcmp( polcrd, "AZEL" ) ) {
                                ipolcrd = 1;
                            } else if( !strcmp( polcrd, "TRACKING" ) ) {
                                ipolcrd = 2;
                            } else if( *status == SAI__OK ) {
                                *status = SAI__ERROR;
                                msgSetc( "N", data->file->name );
                                msgSetc( "V", polcrd );
                                errRep( " ", "Input NDF ^N contains unknown value "
                                        "'^V' for FITS header 'POL_CRD'.", status );
                            }
                        }
                    }
                }

                /* If not already done, get the maximum spatial drift (in arc-seconds) that
                   can be tolerated whilst creating a single I/Q/U image. The default value is
                   half the makemap default pixel size. Also get limits on the number of
                   time slices in any block. */
                if( arcerror == 0.0 ) {
                    parDef0d( "ARCERROR", 0.5*smf_calc_telres( data->hdr->fitshdr,
                              status ), status );
                    parGet0r( "ARCERROR", &arcerror, status );

                    parGet0i( "MAXSIZE", &maxsize, status );
                    parGet0i( "MINSIZE", &minsize, status );
                    if( maxsize > 0 && maxsize < minsize && *status == SAI__OK ) {
                        *status = SAI__ERROR;
                        errRepf( "", "Value of parameter MAXSIZE (%d) is less "
                                 "than value of parameter MINSIZE (%d)", status,
                                 maxsize, minsize );
                    }
                }

                /* The algorithm that calculates I, Q and U assumes that all samples for a
                   single bolometer measure flux from the same point on the sky. Due to
                   sky rotation, this will not be the case - each bolometer will drift
                   slowly across the sky. However, since the drift is (or should be)
                   slow we can apply the I/Q/U algorithm to blocks of contiguous data over
                   which the bolometers do not move significantly. We produce a separate
                   I, Q and U image for each such block. The first block starts at the first
                   time slice in the smfData. */
                block_start = 0;

                /* Find the time slice at which the corner bolometers have moved
                   a critical distance (given by parameter ARCERROR) from their
                   positions at the start of the block. Then back off some time slices
                   to ensure that the block holds an integral number of half-waveplate
                   rotations. */
                block_end = smf_block_end( data, block_start, ipolcrd, arcerror,
                                           maxsize, status );

                /* Loop round creating I/Q/U images for each block. Count them. */
                iblock = 0;
                while( block_end >= 0 && *status == SAI__OK ) {

                    /* Skip very short blocks. */
                    if( block_end - block_start > minsize ) {

                        /* Display the start and end of the block. */
                        msgOutiff( MSG__VERB, "", "   Doing time slice block %d "
                                   "-> %d", status, (int) block_start,
                                   (int) block_end );

                        /* Get the name for the Q NDF for this block. Start of with "Q" followed by
                           the block index. */
                        iblock++;
                        nc = sprintf( ndfname, "Q%d", iblock );

                        /* Append the subarray name to the NDF name. */
                        nc += sprintf( ndfname + nc, "_%s", subarray );

                        /* Append the chunk index to the NDF name. */
                        nc += sprintf( ndfname + nc, "_%d", (int) ichunk );

                        /* Get NDF placeholder for the Q NDF. The NDFs are created inside the
                           output container file. */
                        ndfPlace( locq, ndfname, &qplace, status );

                        /* The name of the U NDF is the same except the initial "Q" is changed to
                           "U". */
                        ndfname[ 0 ] = 'U';
                        ndfPlace( locu, ndfname, &uplace, status );

                        /* The name of the I NDF is the same except the initial "Q" is changed to
                           "I". */
                        if( loci ) {
                            ndfname[ 0 ] = 'I';
                            ndfPlace( loci, ndfname, &iplace, status );
                        } else {
                            iplace = NDF__NOPL;
                        }

                        /* Store the chunk and block numbers as FITS headers. */
                        atlPtfti( fc, "POLCHUNK", (int) ichunk, "Chunk index used by CALCQU", status );
                        atlPtfti( fc, "POLBLOCK", iblock, "Block index used by CALCQU", status );

                        /* Create the Q and U images for the current block of time slices from
                           the subarray given by "idx", storing them in the output container
                           file. */
                        smf_calc_iqu( wf, data, block_start, block_end, ipolcrd,
                                      qplace, uplace, iplace, oprov, fc,
                                      pasign, AST__DD2R*paoff, AST__DD2R*angrot,
                                      submean, status );

                        /* Warn about short blocks. */
                    } else {
                        msgOutiff( MSG__VERB, "", "   Skipping short block of %d "
                                   "time slices (parameter MINSIZE=%d).", status,
                                   block_end - block_start - 1, minsize );
                    }

                    /* The next block starts at the first time slice following the previous
                       block. */
                    block_start = block_end + 1;

                    /* Find the time slice at which the corner bolometers have moved
                       a critical distance (given by parameter ARCERROR) from their
                       positions at the start of the block. Then back off some time slices
                       to ensure that the block holds an integral number of half-waveplate
                       rotations. This returns -1 if all time slices have been used. */
                    block_end = smf_block_end( data, block_start, ipolcrd,
                                               arcerror, maxsize, status );
                }

                /* Free resources */
                oprov = ndgFreeProv( oprov, status );
                fc = astAnnul( fc );
            }
            config = astAnnul( config );

            /* Close the smfArray. */
            smf_close_related( &concat, status );
        }

        /* Annul the locators for the output container files. */
        datAnnul( &locq, status );
        datAnnul( &locu, status );
        if( loci ) datAnnul( &loci, status );

        /* The parameter system hangs onto a primary locator for each container
           file, so cancel the parameters to annul these locators. */
        datCancl( "OUTQ", status );
        datCancl( "OUTU", status );
        datCancl( "OUTI", status );
    }

    /* Free resources. */
    smf_close_related( &darks, status );
    smf_close_related( &flatramps, status );

    if( igrp ) grpDelet( &igrp, status);
    if( sgrp ) grpDelet( &sgrp, status);
    if( bgrp ) grpDelet( &bgrp, status );
    if( ogrp ) grpDelet( &ogrp, status );
    if( sgroup ) smf_close_smfGroup( &sgroup, status );
    if (heateffmap) heateffmap = smf_free_effmap( heateffmap, status );

    /* End the NDF and AST contexts. */
    ndfEnd( status );
    astEnd;

    /* Issue a status indication.*/
    if( *status == SAI__OK ) {
        msgOutif( MSG__VERB, " ", "CALCQU succeeded.", status);
    } else {
        msgOutif( MSG__VERB, " ", "CALCQU failed.", status);
    }
}
示例#3
0
void smf_obsmap_fill( const smfData * data, AstKeyMap * obsmap, AstKeyMap * objmap,
                      int * status ) {

  double dateobs = 0.0;    /* MJD UTC of start of observation */
  char object[SZFITSTR];   /* Object name */
  char obsid[SZFITSTR];    /* Observation ID */
  char instrume[SZFITSTR]; /* instrument name */
  AstKeyMap * obsinfo = NULL; /* observation information */


  if (*status != SAI__OK) return;

  /* if there is no hdr we can not index it in the reported summary */
  if (data->hdr && data->hdr->fitshdr) {

    /* Get observation ID and see if we already have an entry in the map */
    (void)smf_getobsidss( data->hdr->fitshdr, obsid, sizeof(obsid), NULL, 0, status );
    /* As of 20080718 OBSID is not unique per obs so chop off date*/
    obsid[22] = '\0';

    if (!astMapHasKey(obsmap, obsid )) {
      int itemp;

      /* Create a sub keymap and populate it */
      obsinfo = astKeyMap( " " );
      /* fill with default value in case undefined */
      one_strlcpy( object, "<undefined>", sizeof(object), status );
      smf_getfitss( data->hdr, "OBJECT", object, sizeof(object),
                    status );
      astMapPut0C( obsinfo, "OBJECT", object, NULL );
      astMapPut0I( obsinfo, "OBSMODE", data->hdr->obsmode, NULL );
      astMapPut0I( obsinfo, "OBSTYPE", data->hdr->obstype, NULL );
      astMapPut0I( obsinfo, "SWMODE", data->hdr->swmode, NULL );
      astMapPut0I( obsinfo, "INBEAM", data->hdr->inbeam, NULL );
      smf_fits_getI( data->hdr, "OBSNUM", &itemp, status );
      astMapPut0I( obsinfo, "OBSNUM", itemp, NULL );
      smf_fits_getI( data->hdr, "UTDATE", &itemp, status );
      astMapPut0I( obsinfo, "UTDATE", itemp, NULL );
      smf_fits_getL( data->hdr, "SIMULATE", &itemp, status );
      astMapPut0I( obsinfo, "SIMULATE", itemp, NULL );
      smf_getfitss( data->hdr, "INSTRUME", instrume, sizeof(instrume),
                    status);
      astMapPut0C( obsinfo, "INSTRUME", instrume, NULL );

      /* store the MJD of observation for sorting purposes */
      smf_find_dateobs( data->hdr, &dateobs, NULL, status );
      astMapPut0D( obsinfo, "MJD-OBS", dateobs, NULL );

      /* store information in the global observation map
         and also track how many distinct objects we have */
      astMapPut0A( obsmap, obsid, obsinfo, NULL );
      astMapPut0I( objmap, object, 0, NULL );

      obsinfo = astAnnul( obsinfo );
    } else {
      int curbeam = 0;

      astMapGet0A( obsmap, obsid, &obsinfo );

      /* INBEAM is interesting since technically each sequence can involve
         different hardware being in the beam so we have to retrieve the
         current value from the keymap and OR it with the current value
         from the data header. */

      /* we know that the value has been filled in previously so no
         need to check */
      astMapGet0I( obsinfo, "INBEAM", &curbeam );
      curbeam |= data->hdr->inbeam;
      astMapPut0I( obsinfo, "INBEAM", curbeam, NULL );

      obsinfo = astAnnul( obsinfo );

    }
  }

  return;
}
示例#4
0
void smf_uncalc_iqu( ThrWorkForce *wf, smfData *data,
                     double *idata, double *qdata, double *udata,
                     int *status ){

/* Local Variables: */
   const JCMTState *state;    /* JCMTState info for current time slice */
   dim_t nbolo;               /* No. of bolometers */
   dim_t ntslice;             /* Number of time-slices in data */
   int bstep;                 /* Bolometer step between threads */
   int itime;                 /* Time slice index */
   int iworker;               /* Index of a worker thread */
   int ntime;                 /* Time slices to check */
   int nworker;               /* No. of worker threads */
   int old;                   /* Data has old-style POL_ANG values? */
   size_t bstride;            /* Stride between adjacent bolometer values */
   size_t tstride;            /* Stride between adjacent time slice values */
   smfHead *hdr;              /* Pointer to data header this time slice */
   smfUncalcIQUJobData *job_data = NULL; /* Pointer to all job data */
   smfUncalcIQUJobData *pdata = NULL;/* Pointer to next job data */
   char headval[ 81 ];        /* FITS header value */
   int ipolcrd;               /* Reference direction for waveplate angles */

/* Check the inherited status. */
   if( *status != SAI__OK ) return;

/* Convenience pointer. */
   hdr = data->hdr;

/* Check the half-waveplate and analyser were in the beam. */
   headval[ 0 ] = 0;
   smf_getfitss( hdr, "POLWAVIN", headval, sizeof(headval), status );
   if( strcmp( headval, "Y" ) && *status == SAI__OK ) {
      smf_smfFile_msg( data->file, "N", 0, "" );
      *status = SAI__ERROR;
      errRep( " ", "Half-waveplate was not in the beam for "
              "input NDF ^N.", status );
   }

   headval[ 0 ] = 0;
   smf_getfitss( hdr, "POLANLIN", headval, sizeof(headval), status );
   if( strcmp( headval, "Y" ) && *status == SAI__OK ) {
      smf_smfFile_msg( data->file, "N", 0, "" );
      *status = SAI__ERROR;
      errRep( " ", "Analyser was not in the beam for input "
              "NDF ^N.", status );
   }

/* Get the reference direction for JCMTSTATE:POL_ANG values. */
   smf_getfitss( hdr, "POL_CRD", headval, sizeof(headval), status );
   if( !strcmp( headval, "FPLANE" ) ) {
      ipolcrd = 0;
   } else if( !strcmp( headval, "AZEL" ) ) {
      ipolcrd = 1;
   } else if( !strcmp( headval, "TRACKING" ) ) {
      ipolcrd = 2;
   } else if( *status == SAI__OK ) {
      *status = SAI__ERROR;
      smf_smfFile_msg( data->file, "N", 0, "" );
      msgSetc( "V", headval );
      errRep( " ", "Input NDF ^N contains unknown value "
              "'^V' for FITS header 'POL_CRD'.", status );
   }

/* Obtain number of time slices - will also check for 3d-ness. Also get
   the dimensions of the bolometer array and the strides between adjacent
   bolometer values. */
   smf_get_dims( data, NULL, NULL, &nbolo, &ntslice, NULL, &bstride,
                 &tstride, status );

/* Create structures used to pass information to the worker threads. */
   nworker = wf ? wf->nworker : 1;
   job_data = astMalloc( nworker*sizeof( *job_data ) );

/* Check the above pointers can be used safely. */
   if( *status == SAI__OK ) {

/* Go through the first thousand POL_ANG values to see if they are in
   units of radians (new data) or arbitrary encoder units (old data).
   They are assumed to be in radians if no POL_ANG value is larger than
   20. */
      old = 0;
      state = hdr->allState;
      ntime = ( ntslice > 1000 ) ? 1000 : ntslice;
      for( itime = 0; itime < ntime; itime++,state++ ) {
         if( state->pol_ang > 20 ) {
            old = 1;
            msgOutif( MSG__VERB, "","   POL2 data contains POL_ANG values "
                      "in encoder units - converting to radians.", status );
            break;
         }
      }

/* Determine which bolometers are to be processed by which threads. */
      bstep = nbolo/nworker;
      if( bstep < 1 ) bstep = 1;

      for( iworker = 0; iworker < nworker; iworker++ ) {
         pdata = job_data + iworker;
         pdata->b1 = iworker*bstep;
         pdata->b2 = pdata->b1 + bstep - 1;
      }

/* Ensure that the last thread picks up any left-over bolometers */
      pdata->b2 = nbolo - 1;

/* Store all the other info needed by the worker threads, and submit the
   jobs to calculate the analysed intensity values in each bolo, and then
   wait for them to complete. */
      for( iworker = 0; iworker < nworker; iworker++ ) {
         pdata = job_data + iworker;

         pdata->bstride = bstride;
         pdata->nbolo = nbolo;
         pdata->tstride = tstride;
         pdata->allstates = hdr->allState;
         pdata->ipi = idata;
         pdata->ipq = qdata;
         pdata->ipu = udata;
         pdata->ipolcrd = ipolcrd;
         pdata->old = old;
         pdata->ntslice = ntslice;

/* Pass the job to the workforce for execution. */
         thrAddJob( wf, THR__REPORT_JOB, pdata, smf1_uncalc_iqu_job, 0, NULL,
                      status );
      }

/* Wait for the workforce to complete all jobs. */
      thrWait( wf, status );
   }

/* Free resources. */
   job_data = astFree( job_data );
}
示例#5
0
int smf_fix_metadata_scuba2 ( msglev_t msglev, smfData * data, int have_fixed, int *ncards, int * status ) {

  AstFitsChan * fits = NULL; /* FITS header (FitsChan) */
  struct FitsHeaderStruct fitsvals; /* Quick access Fits header struct */
  smfHead *hdr = NULL;       /* Data header struct */
  AstKeyMap * obsmap = NULL; /* Info from all observations */
  AstKeyMap * objmap = NULL; /* All the object names used */

  if (*status != SAI__OK) return have_fixed;

  /* Validate arguments - need smfFile and smfHead */
  smf_validate_smfData( data, 1, 1, status );
  if (*status != SAI__OK) return have_fixed;

  hdr = data->hdr;
  smf_validate_smfHead( hdr, 1, 1, status );
  if (*status != SAI__OK) return have_fixed;

  fits = hdr->fitshdr;

  if (hdr->instrument != INST__SCUBA2) {
    if (*status != SAI__OK) {
      *status = SAI__ERROR;
      errRep("", " Attempting to fix metadata using SCUBA-2 algorithms but this is not SCUBA-2 data",
             status );
    }
    return have_fixed;
  }

  /* Update units string to something that is FITS standard compliant
     - we used "DAC units" for a while but in FITS land this becomes
     "decacoulomb * units" */
  if ( strncmp( hdr->units, "DAC", 3) == 0 ) {
    one_strlcpy( hdr->units, "adu", SMF__CHARLABEL, status );
  }

  /* Clock jitter and readout efficiencies mean we need to recalculate STEPTIME from the data.
     This is possible because we know that we have a continuous sequence in each file (unlike
     ACSIS). */
  if (hdr->allState) {
    /* it will be odd if it is not there */
    size_t nframes = hdr->nframes;

    size_t istart = 0;
    double start_time = (hdr->allState)[istart].rts_end;
    while( start_time == VAL__BADD && ++istart < nframes ) {
      start_time = (hdr->allState)[istart].rts_end;
    }

    size_t iend = nframes - 1;
    double end_time = (hdr->allState)[iend].rts_end;
    while( end_time == VAL__BADD && iend-- > 0 ) {
      end_time = (hdr->allState)[iend].rts_end;
    }

    double steptime = VAL__BADD;
    double newstep;

    smf_getfitsd( hdr, "STEPTIME", &steptime, status );
    newstep = steptime;

    /* it is possible for a file to contain only one step since
       the DA just dumps every N-steps. We can not recalculate the
       step time in that case. */
    nframes = iend - istart + 1;
    if (nframes > 1) {

      /* duration of file in days */
      newstep = end_time - start_time;

      /* convert to seconds */
      newstep *= SPD;

      /* Convert to step time */
      newstep /= (nframes - 1);
    } else if( nframes > 0 ) {
      /* work it out from RTS_END and TCS_TAI */
      JCMTState * onlystate = &((hdr->allState)[istart]);
      if ( onlystate->tcs_tai != VAL__BADD &&
           onlystate->tcs_tai != onlystate->rts_end) {
        /* TCS_TAI is in the middle of the step */
        newstep = 2.0 * ( onlystate->rts_end - onlystate->tcs_tai ) * SPD;
      }
    } else if( *status == SAI__OK ) {
      *status = SAI__ERROR;
      if( data->file ) {
         smf_smfFile_msg( data->file, "N", 1, "<unknown>" );
         errRep("", "No valid RTS_END values found in NDF '^N'.", status );
      } else {
         errRep("", "No valid RTS_END values found.", status );
      }
    }

    if (steptime != newstep) {
      msgOutiff( msglev, "", INDENT "Recalculated step time as %g sec from JCMTSTATE (was %g sec)",
                 status, newstep, steptime);
      smf_fits_updateD( hdr, "STEPTIME", newstep, NULL, status );
      have_fixed |= SMF__FIXED_FITSHDR;
    }
  }


  /* Read some FITS headers, intialising the struct first */
  fitsvals.utdate = VAL__BADI;
  *(fitsvals.instrume) = '\0';
  smf_getfitsi( hdr, "UTDATE", &(fitsvals.utdate), status );
  smf_getfitss( hdr, "INSTRUME", fitsvals.instrume, sizeof(fitsvals.instrume), status );

  /* Print out summary of this observation - this may get repetitive if multiple files come
     from the same observation in one invocation but it seems better to describe each fix up
     separately and in context. */
  obsmap = astKeyMap( " " );
  objmap = astKeyMap( " " );
  smf_obsmap_fill( data, obsmap, objmap, status );
  smf_obsmap_report( msglev, obsmap, objmap, status );
  obsmap = astAnnul( obsmap );
  objmap = astAnnul( objmap );

  /* First we need to look for a BACKEND header which we do not write to the raw
     data files but CADC would like to see equal to INSTRUME */
  if (!astTestFits( fits, "BACKEND", NULL ) ) {
    have_fixed |= SMF__FIXED_FITSHDR;
    smf_fits_updateS( hdr, "BACKEND", fitsvals.instrume, "Name of the backend", status );
    msgOutif( msglev, "",  INDENT "Setting backend for SCUBA-2 observation.", status);
  }

  /* BASETEMP was reading MUXTEMP for pre-20091101 data */
  if ( fitsvals.utdate < 20091101 ) {
    double muxtemp = 0.0;
    have_fixed |= SMF__FIXED_FITSHDR;
    smf_getfitsd( hdr, "BASETEMP", &muxtemp, status );
    smf_fits_updateU( hdr, "BASETEMP", "[K] Base temperature", status );
    smf_fits_updateD( hdr, "MUXTEMP", muxtemp, "[K] Mux temperature", status );
    msgOutif( msglev, "", INDENT "Mux temperature is being read from BASETEMP header.", status );
  }

  /* Sometime before 20091119 the SHUTTER keyword was written as a string
     OPEN or CLOSED. Rewrite those as numbers */
  if (fitsvals.utdate < 20091119) {
    double shutval = 0.0;
    /* Try to read as a double. */
    smf_fits_getD( hdr, "SHUTTER", &shutval, status );

    /* Old data was a string. Convert to a double */
    if (*status == AST__FTCNV) {
      char shutter[100];
      errAnnul( status );
      smf_fits_getS( hdr, "SHUTTER", shutter, sizeof(shutter), status);
      if (strcmp(shutter, "CLOSED") == 0) {
        shutval = 0.0;
      } else {
        shutval = 1.0;
      }
      /* update the value */
      have_fixed |= SMF__FIXED_FITSHDR;
      smf_fits_updateD( hdr, "SHUTTER", shutval, "shutter position 0-Closed 1-Open", status );
      msgOutif( msglev, "", INDENT "Forcing SHUTTER header to be numeric", status );
    }
  }

  /* Engineering data with just SCUBA2 and no RTS left the RTS_NUM field
     filled with zeroes. Just assume that a zero in RTS_NUM is always
     indicative of a private sequence. */
  if (fitsvals.utdate < 20110401) {
    size_t nframes = hdr->nframes;
    JCMTState * curstate = &((hdr->allState)[0]);
    JCMTState * endstate = &((hdr->allState)[nframes-1]);
    if (curstate->rts_num == 0 && endstate->rts_num == 0) {
      /* have to set the values from the SEQSTART and SEQEND headers
         since those were set correctly (although any value would
         do of course apart from the sanity check in smf_find_science. */
      size_t i;
      int seqnum = 1;
      smf_fits_getI( hdr, "SEQSTART", &seqnum, status );
      for ( i=0; i<nframes; i++) {
        curstate = &((hdr->allState)[i]);
        curstate->rts_num = seqnum;
        seqnum++;
      }
      have_fixed |= SMF__FIXED_JCMTSTATE;
      msgOutif( msglev, "", INDENT "Private RTS sequence. Fixing RTS_NUM.", status );
    }
  }

  /* work out if this is a fast flat observation taken before May 2010 */
  if (fitsvals.utdate > 20100218 && fitsvals.utdate < 20100501) {
    char buff[100];
    /* need to know whether this is a FASTFLAT */
    smf_getfitss( hdr, "SEQ_TYPE", buff, sizeof(buff), status );

    if (strcmp( buff, "FASTFLAT" ) == 0 ) {

      /* Fast flats had incorrect SHUTTER settings for one night */
      if (fitsvals.utdate == 20100223) {
        have_fixed |= SMF__FIXED_FITSHDR;
        smf_fits_updateD( hdr, "SHUTTER", 1.0, "shutter position 0-Closed 1-Open", status );
        msgOutif( msglev, "", INDENT "Shutter was open for fast flatfield ramp. Correcting.", status );
      }

      /* Need to fix up SC2_HEAT ramps */
      /* the problem is that the data were assumed to be taken with 3 measurements
         in each heater setting. What actually happened was that the first 5 were
         done at the reference setting and then the data were grouped in threes
         finishing with a single value at the reference setting again.

         For example the heater values and the actual values look something like

           Stored    1 1 1 2 2 2 3 3 3 4 4 4 5 5 5
           Actual    0 0 0 0 0 2 2 2 3 3 3 4 4 4 5

         So we can correct for this by starting at the end and copying in the value
         two slots further down until we get to position #4. Then replacing that with
         the PIXHEAT number.
      */

      {
        size_t i;
        int pixheat = 0;
        size_t nframes = hdr->nframes;
        smf_getfitsi( hdr, "PIXHEAT", &pixheat, status );

        /* shift everything up by 2 */
        for (i=nframes-1; i > 4; i--) {
          JCMTState * curstate = &((hdr->allState)[i]);
          JCMTState * prevstate = &((hdr->allState)[i-2]);
          curstate->sc2_heat = prevstate->sc2_heat;
        }

        /* fill in the first 5 slots with the same value */
        for (i=0; i<5; i++) {
          JCMTState * curstate = &((hdr->allState)[i]);
          curstate->sc2_heat = pixheat;
        }
        have_fixed |= SMF__FIXED_JCMTSTATE;
      }
    }
  }

  /* We always recalculate the WVM start and end tau values so that the header
     reflects something approximating the value that was actually used in the
     extinction correction.

     Note that smf_calc_smoothedwvm can do a better job because it has multiple
     subarrays to get all the values from. We just have to try with what we
     have from a single subarray. We do step into the time series until we
     find something good.

     The header values should mostly agree with the recalculated values if the
     WVM code at the time matches the code in SMURF for that date. This has not
     been true in cases where we have retrospectively realised that there has been
     a calibration error in the WVM. So that we do not have to keep track of those
     times explicitly we currently recalculate every time. If this recalculation
     becomes a problem (smf_calc_wvm has a cache now to minimize this) it should
     be possible to disable this recalculation if the file is less than, say,
     30 minutes old to indicate we are running in near realtime.

     As a special case we do not recalculate the headers for FOCUS observations
     as the WVM reading is somewhat irrelevant and simply slows things down.

  */

  if( *status == SAI__OK ){

    /* Have not parsed header yet to extract type so do it explicitly here */
    char obstype[100];
    smf_getfitss( hdr, "OBS_TYPE", obstype, sizeof(obstype), status );

    if (strcasecmp( obstype, "focus") != 0) {

      size_t i;
      size_t nframes = hdr->nframes;
      double starttau = VAL__BADD;
      double starttime = VAL__BADD;
      double endtau = VAL__BADD;
      double endtime = VAL__BADD;

      /* Create a TimeFrame that can be used to format MJD values into ISO
         date-time strings, including a "T" separator between time and date. */
      AstTimeFrame *tf = astTimeFrame( "Format=iso.0T" );

      for (i=0; i < nframes && *status == SAI__OK; i++) {
        smf__calc_wvm_index( hdr, "AMSTART", i, &starttau, &starttime, status );
        if (starttau != VAL__BADD) break;
        if (*status == SAI__ERROR) errAnnul( status );
      }

      /* if we did not get a start tau we are not going to get an end tau */
      if (starttau != VAL__BADD) {
        for (i=0; i < nframes && *status == SAI__OK; i++) {
          smf__calc_wvm_index( hdr, "AMEND", nframes - 1 - i, &endtau, &endtime, status );
          if (endtau != VAL__BADD) break;
          if (*status == SAI__ERROR) errAnnul( status );
        }
      }

      /* If we could not find any WVM readings then we have a bit of a problem.
         Do we clear the FITS headers or do we leave them untouched? Leave them
         alone for now. */
      if (starttau != VAL__BADD && starttime != VAL__BADD) {
        smf_fits_updateD( hdr, "WVMTAUST", starttau, "186GHz Tau from JCMT WVM at start", status );

        /* Convert starttime MJD to ISO format and update the value in the
           FITS header. */
        smf_fits_updateS( hdr, "WVMDATST", astFormat( tf, 1, starttime ),
                          "Time of WVMTAUST", status );
        have_fixed |= SMF__FIXED_FITSHDR;
      }

      if (endtau != VAL__BADD && endtime != VAL__BADD) {
        smf_fits_updateD( hdr, "WVMTAUEN", endtau, "186GHz Tau from JCMT WVM at end", status );

        /* Convert endtime MJD to ISO format and update the value in the
           FITS header. */
        smf_fits_updateS( hdr, "WVMDATEN", astFormat( tf, 1, endtime ),
                          "Time of WVMTAUEN", status );
        have_fixed |= SMF__FIXED_FITSHDR;
      }

      /* Free the TimeFrame. */
      tf = astAnnul( tf );

    }
  }


  /* SEQ_TYPE header turned up in 20091125. Before that date the SEQ_TYPE only
     had two values. If the shutter was open then SEQ_TYPE is just OBS_TYPE. In the
     dark only a FLATFIELD sometimes finished with a noise but in that case CALCFLAT
     doesn't care so we just call it a flatfield sequence anyhow. We could look at
     the OBSEND flag but I'm not sure it makes a difference. */
  if ( fitsvals.utdate < 20091125 ) {
    char obstype[100];
    char seqtype[100];
    double shutval = 0.0;
    /* need to know what type of observation this is */
    smf_getfitss( hdr, "OBS_TYPE", obstype, sizeof(obstype), status );
    /* and the shutter status */
    smf_fits_getD( hdr, "SHUTTER", &shutval, status );

    if (shutval == 0.0 && strcasecmp( obstype, "flatfield" ) != 0 ) {
      /* flatfield was the only non-noise observation in the dark */
      one_strlcpy( seqtype, "NOISE", sizeof(seqtype), status );
      msgOutif( msglev, "", INDENT "Setting sequence type to NOISE", status );
    } else {
      /* Shutter was open so SEQ_TYPE is just OBS_TYPE */
      one_strlcpy( seqtype, obstype, sizeof(seqtype), status );
      msgOutif( msglev, "",  INDENT "Setting sequence type to obs type", status);
    }
    smf_fits_updateS( hdr, "SEQ_TYPE", seqtype, "Type of sequence", status );
    have_fixed |= SMF__FIXED_FITSHDR;
  }

  /* The telescope goes crazy at the end of observation 56 on 20110530. Null
     the telescope data for subscans 30, 31 and 32 */
  if (fitsvals.utdate == 20110530) {
    char obsid[81];
    smf_getobsidss( hdr->fitshdr, obsid, sizeof(obsid), NULL, 0, status);

    if (strcmp(obsid, "scuba2_00056_20110530T135530") == 0 ) {
      int subscan;
      smf_getfitsi( hdr, "NSUBSCAN", &subscan, status );
      if (subscan == 30 || subscan == 31 || subscan == 32) {
        size_t nframes = hdr->nframes;
        JCMTState * curstate;
        size_t i;
        for ( i=0; i<nframes; i++ ) {
          curstate = &((hdr->allState)[i]);
          curstate->jos_drcontrol |= DRCNTRL__PTCS_BIT;
        }
        msgOutif( msglev, "", INDENT "Blanking telescope data due to extreme excursion", status );
        have_fixed |= SMF__FIXED_JCMTSTATE;
      }
    }
  }

  /* The second half of observation 14 on 20111215 (scuba2_00014_20111215T061536)
     has a elevation pointing shift */
  if (fitsvals.utdate == 20111215) {
    char obsid[81];
    const char fitskey[] = "FIXPCORR";
    smf_getobsidss( hdr->fitshdr, obsid, sizeof(obsid), NULL, 0, status);

    if (strcmp(obsid, "scuba2_00014_20111215T061536") == 0 ) {
      int seqcount;
      smf_getfitsi( hdr, "SEQCOUNT", &seqcount, status );
      if (seqcount == 5) {
        int have_fixed_pntg = 0;
        smf_fits_getL( hdr, fitskey, &have_fixed_pntg, status );
        if (*status == SMF__NOKWRD) {
          have_fixed = 0;
          errAnnul( status );
        }
        if (!have_fixed_pntg) {
          size_t nframes = hdr->nframes;
          size_t i;
          const double dlon = 0.0;
          const double dlat = -16.83; /* From making maps of each half */
          /* Correct the pointing */
          msgOutif( msglev, "", INDENT "Applying pointing anomaly correction", status );
          for (i=0;i<nframes;i++) {
            JCMTState * curstate = &((hdr->allState)[i]);
            /* This is an AZEL correction */
            smf_add_smu_pcorr( curstate, 1, dlon, dlat, status );
          }
          smf_fits_updateL(hdr, fitskey, 1, "Applied internal pointing correction", status);
          have_fixed |= SMF__FIXED_JCMTSTATE;
        }
      }
    }

  }

  /* For POL-2 data prior to 18-JAN-2013, the POL_CRD header was always
     "FPLANE" in reality, even if the POL_CRD value in JCMTSTATE said
     something else. */
  if ( fitsvals.utdate < 20130118 ) {
    char polcrd[80] = "<unset>";
    smf_getfitss( hdr, "POL_CRD", polcrd, sizeof(polcrd), status );
    if (*status == SMF__NOKWRD) {
       errAnnul( status );
    } else if( !strcmp( polcrd, "TRACKING" ) || !strcmp( polcrd, "AZEL" ) ) {
      msgOutiff( msglev, "",  INDENT "Changing POL_CRD from %s to FPLANE", status, polcrd);
      smf_fits_updateS( hdr, "POL_CRD", "FPLANE",
                        "Coordinate system of polarimeter", status );
      have_fixed |= SMF__FIXED_FITSHDR;
    }
  }


  return have_fixed;
}