Example #1
0
int smf_correct_extinction(ThrWorkForce *wf, smfData *data, smf_tausrc *thetausrc, smf_extmeth method,
                            AstKeyMap * extpars, double tau, double *allextcorr,
                            double **wvmtaucache, int *status) {

  /* Local variables */
  int allquick = 0;        /* Is the extinction for all bolometers the same? */
  double amstart = VAL__BADD; /* Airmass at start */
  double amend = VAL__BADD;   /* Airmass at end */
  double elstart = VAL__BADD; /* Elevation at start (radians) */
  double elend = VAL__BADD;/* Elevation at end (radians) */
  smfHead *hdr = NULL;     /* Pointer to full header struct */
  double *indata = NULL;   /* Pointer to data array */
  int isTordered;          /* data order of input data */
  int lbnd[2];             /* Lower bound */
  size_t ndims;            /* Number of dimensions in input data */
  dim_t nframes = 0;       /* Number of frames */
  dim_t npts = 0;          /* Number of data points */
  dim_t nx = 0;            /* # pixels in x-direction */
  dim_t ny = 0;            /* # pixels in y-direction */
  smf_tausrc tausrc;       /* Local copy of tausrc value */
  int ubnd[2];             /* Upper bound */
  double *vardata = NULL;  /* Pointer to variance array */
  double * wvmtau = NULL;  /* WVM tau (smoothed or not) for these data */
  int nw;                  /* Number of worker threads */
  int iw;                  /* Thread index */
  SmfCorrectExtinctionData *job_data = NULL;  /* Array of job descriptions */
  SmfCorrectExtinctionData *pdata;   /* Pointer to next job description */
  size_t framestep;         /* Number of frames per thread */

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

  /* If no correction requested, return */
  if( method==SMF__EXTMETH_NONE ) {
    msgOutif(MSG__VERB, "", FUNC_NAME ": Extinction method=none, returning",
             status );
    return allquick;
  }

  if ( ! thetausrc ) {
    *status = SAI__ERROR;
    errRep( "", FUNC_NAME ": Must supply a thetausrc argument. Possible programming error.",
            status );
    return allquick;
  }

  /* Use a local value for tausrc as we update it in this routine. In particular,
     CSOFIT becomes WVMRAW and this would be confusing to the caller */
  tausrc = *thetausrc;

  /* If no opacity monitor specified generate bad status */
  if( tausrc==SMF__TAUSRC_NULL ) {
    *status = SAI__ERROR;
    errRep( "", FUNC_NAME ": No source of opacity information could be determined",
            status );
    return allquick;
  }

  if( smf_history_check( data, FUNC_NAME, status) ) {
    /* If caller not requesting allextcorr fail here */
    if( !allextcorr ) {
      msgSetc("F", FUNC_NAME);
      msgOutif(MSG__VERB," ",
               "^F has already been run on these data, returning to caller",
               status);
      return allquick;
    }
  }

  /* Acquire the data order */
  isTordered = data->isTordered;

  /* make sure we have a header */
  hdr = data->hdr;
  if( hdr == NULL ) {
    *status = SAI__ERROR;
    errRep( FUNC_NAME, "Input data has no header", status);
    return allquick;
  }

  /* Do we have 2-D image data? */
  ndims = data->ndims;
  if (ndims == 2) {
    nframes = 1;
    nx = (data->dims)[0];
    ny = (data->dims)[1];
    npts = nx*ny;
  } else {
    /* this routine will also check for dimensionality */
    smf_get_dims( data, &nx, &ny, &npts, &nframes, NULL, NULL, NULL, status );
  }

  /* Tell user we're correcting for extinction */
  msgOutif(MSG__VERB," ",
           "Correcting for extinction.", status);

  /* Should check data type for double if not allextcorr case */
  if( !allextcorr ) {
    if (!smf_dtype_check_fatal( data, NULL, SMF__DOUBLE, status)) return allquick;
  }

  /* Check that we're not trying to use the WVM for 2-D data */
  if ( ndims == 2 && tausrc == SMF__TAUSRC_WVMRAW ) {
    if ( *status == SAI__OK ) {
      *status = SAI__ERROR;
      errRep( FUNC_NAME, "Method WVMRaw can not be used on 2-D image data", status );
      return allquick;
    }
  } else if (ndims == 2 && tausrc == SMF__TAUSRC_CSOFIT ) {
    /* This is CSOTAU mode with the value calculated from the fits. We have to either
       calculate the value here based on the FITS headers or we have to ensure that
       when this mode triggers we've been given the fallback tau derived in this manner.
       Revisit this as the code develops (we do not want to be reading fits multiple times).
    */
    if (*status == SAI__OK) {
      *status = SAI__ERROR;
      errRep( FUNC_NAME, "Method CSOFIT not yet supported on 2-D image data", status );
      return allquick;
    }
  } else if (ndims < 2 || ndims > 3) {
    if (*status == SAI__OK) {
      *status = SAI__ERROR;
      errRepf( FUNC_NAME, "Can not extinction correct data with %zd dimension(s)", status,
              ndims );
      return allquick;
    }
  }

  /* if we are WVMRAW, CSOFIT or AUTO and we have a cache we should always use it since
     we assume it was filled in properly the previous time. */
  if (wvmtaucache && *wvmtaucache &&
      (tausrc == SMF__TAUSRC_WVMRAW ||
       tausrc == SMF__TAUSRC_AUTO ||
       tausrc == SMF__TAUSRC_CSOFIT)) {
    wvmtau = *wvmtaucache;
    smf_smfFile_msg( data->file, "FILE", 1, "<unknown>");
    msgOutiff( MSG__VERB, "", "Using cached high resolution data for extinction correction of ^FILE",
               status);
    tausrc = SMF__TAUSRC_WVMRAW; /* We are now WVMRAW as we have the data */

    /* Assume that we only do not know the provenance if in AUTO mode */
    if (tausrc == SMF__TAUSRC_AUTO) *thetausrc = SMF__TAUSRC_CACHED;
  }

  if (!wvmtau && tausrc == SMF__TAUSRC_WVMRAW) {
    size_t ntotaltau = 0;
    size_t ngoodtau = 0;
    smf_calc_smoothedwvm( wf, NULL, data, extpars, &wvmtau, &ntotaltau,
                          &ngoodtau, status );
    smf_smfFile_msg( data->file, "FILE", 1, "<unknown>");
    msgOutiff( MSG__VERB, "", "Using WVM mode for extinction correction of ^FILE"
               " %.0f %% of WVM data are present", status,
               (double)(100.0*(double)ngoodtau/(double)ntotaltau) );
  }

  if (*status == SAI__OK && tausrc == SMF__TAUSRC_CSOFIT) {
    /* Calculate the fit but we can use the same cache that WVM uses */
    size_t nframes = 0;
    smf_calc_csofit( data, extpars, &wvmtau, &nframes, status );
    smf_smfFile_msg( data->file, "FILE", 1, "<unknown>");
    msgOutiff( MSG__QUIET, "", "Using CSO fits for extinction correction of ^FILE",
               status );
    /* Rebrand as WVM data from this point on */
    tausrc = SMF__TAUSRC_WVMRAW;
  }

  /* AUTO mode logic */
  /*
   * Default position is to use WVM data but we have two caveats
   *
   *  1. Was this observation done during a period where the WVM has been flagged as unreliable?
   *  2. If the WVM is nominally okay, do we have sufficient good data during this period?
   *
   * If the WVM should not be used we fallback to seeing if we have a fit available for this
   * night from the CSO data.
   *
   * If we do not have a reliable WVM or CSO fit then we fallback to using a fixed CSO number
   * from the header.
   *
   * This final fallback position is unfortunate as it is highly likely that this is not a reliable
   * number if we have fits for every night of observing (we have no information on whether a missing
   * fit indicates the CSO was too unstable to use or whether it means we simply haven't got to it
   * yet).
   *
   */

  /* Check auto mode */
  if (tausrc == SMF__TAUSRC_AUTO && *status == SAI__OK) {

    smf_smfFile_msg( data->file, "FILE", 1, "<unknown>" );

    if (ndims == 2) {
      /* have to use CSO mode */
      tausrc = SMF__TAUSRC_CSOTAU;
      *thetausrc = tausrc;
    } else if (ndims == 3) {
      /* We have already done the cache test so not needed here */

      /* Is the WVM nominally stable for this night? */
      if (smf_is_wvm_usable( data->hdr, status ) ) {

        /* Calculate the WVM tau data and see if we have enough good data */
        size_t ngoodtau = 0;
        size_t ntotaltau = 0;
        double percentgood = 0.0;
        smf_calc_smoothedwvm( wf, NULL, data, extpars, &wvmtau, &ntotaltau,
                              &ngoodtau, status );
        percentgood = 100.0 * ((double)ngoodtau / (double)ntotaltau);

        if ( percentgood > 80.0) {
          tausrc = SMF__TAUSRC_WVMRAW;
          msgOutiff( MSG__VERB, "", "Selecting WVM mode for extinction correction of ^FILE."
                     " %.0f %% of WVM data are present", status, percentgood );
          *thetausrc = tausrc;
        } else {
          tausrc = SMF__TAUSRC_AUTO; /* keep it AUTO (a no-op but make it clear) */
          if (wvmtau) wvmtau = astFree( wvmtau );
        }
      }

      /* at this point we either have WVM data handled or we still think we are AUTO.
         Do a CSO FIT check */
      if (tausrc == SMF__TAUSRC_AUTO && *status == SAI__OK) {
        size_t nframes = 0;
        smf_calc_csofit( data, extpars, &wvmtau, &nframes, status );
        if (*status == SAI__OK) {
          smf_smfFile_msg( data->file, "FILE", 1, "<unknown>");
          msgOutiff( MSG__QUIET, "", "Using CSO fits for extinction correction of ^FILE",
                     status );
          /* Rebrand as WVM data from this point on */
          tausrc = SMF__TAUSRC_WVMRAW;
          *thetausrc = SMF__TAUSRC_CSOFIT;
        } else if (*status == SMF__BADFIT) {
          /* No fit, carry on. */
          errAnnul( status );
        }
      }

      /* At this point if we are not WVMRAW then we have a serious issue. It means that
         WVM was unusable and we did not have a good CSO fit. We should not continue at this
         point as to continue implies that we know what we should do. The user should decide
         how much they trust the opacity for the night. There has to be a reason why there
         is no CSO fit for the night. */
      if (*status == SAI__OK && tausrc != SMF__TAUSRC_WVMRAW) {
        *status = SAI__ERROR;
        errRep("", "Unable to determine opacity data for this observation. Both WVM and CSO fits failed. Please investigate and if necessary use CSO mode explicitly but proceed with caution.", status );
      }
    }
  }

  /* If we have a CSO Tau then convert it to the current filter. This will also
     convert bad values to a value derived from the header if appropriate. */
  if ( tausrc == SMF__TAUSRC_CSOTAU ) {
    tau = smf_cso2filt_tau( hdr, tau, extpars, status );
    /* The tau source is now a real tau */
    tausrc = SMF__TAUSRC_TAU;
  }

  /* Find the airmass range for this data */
  smf_find_airmass_interval( hdr, &amstart, &amend, &elstart, &elend, status );
  if (*status == SAI__OK && (amstart == VAL__BADD || amend == VAL__BADD)) {
    *status = SAI__ERROR;
    errRep( "", "No good airmass values found in JCMTSTATE structure for these data",
            status );
  }

  /* if we are not doing WVM correction but are in adaptive mode we can determine
     whether or not we will have to use full or single mode just by looking at the
     airmass data. */
  if (ndims == 3 && tausrc != SMF__TAUSRC_WVMRAW && method == SMF__EXTMETH_ADAPT) {
    /* first and last is a good approximation given that most SCUBA-2 files will only
       be a minute duration. */
    double refel;
    double refam;

    /* only need to examine the largest airmass */
    if (amstart > amend) {
      refam = amstart;
      refel = elstart;
    } else {
      refam = amend;
      refel = elend;
    }

    /* and choose a correction method */
    if (is_large_delta_atau( refam, refel, tau, status) ) {
      method = SMF__EXTMETH_FULL;
      msgOutiff(MSG__DEBUG, " ",
               "Adaptive extinction algorithm selected per-bolometer airmass value "
               "per time slice (am=%g, tau=%g)", status, refam, tau);
    } else {
      msgOutiff(MSG__DEBUG, " ",
               "Adaptive extinction algorithm selected single airmass value per time slice"
               " (am=%g, tau=%g)", status, refam, tau);
      method = SMF__EXTMETH_SINGLE;
    }

  }

  /* Assign pointer to input data array if status is good */
  if ( *status == SAI__OK ) {
    indata = (data->pntr)[0];
    vardata = (data->pntr)[1];
  }

  /* Jump to the cleanup section if status is bad by this point
     since we need to free memory */
  if (*status != SAI__OK) goto CLEANUP;

  /* Array bounds for astTranGrid call */
  lbnd[0] = 1;
  lbnd[1] = 1;
  ubnd[0] = nx;
  ubnd[1] = ny;

  /* Unlock the AST objects in the smfData so that the worker threads can
     lock them. */
  smf_lock_data( data, 0, status );

  /* How many threads do we get to play with */
  nw = wf ? wf->nworker : 1;

  /* Find how many frames to process in each worker thread. */
  framestep = nframes/nw;
  if( framestep == 0 ) {
    framestep = 1;
    nw = nframes;
  }

  /* Allocate job data for threads, and store the range of frames to be
     processed by each one. Ensure that the last thread picks up any
     left-over frames. */
  job_data = astCalloc( nw, sizeof(*job_data) );
  if( *status == SAI__OK ) {
    for( iw = 0; iw < nw; iw++ ) {
      pdata = job_data + iw;
      pdata->f1 = iw*framestep;
      if( iw < nw - 1 ) {
        pdata->f2 = pdata->f1 + framestep - 1;
      } else {
        pdata->f2 = nframes - 1 ;
      }

      pdata->nframes = nframes;
      pdata->npts = npts;
      pdata->allextcorr = allextcorr;
      pdata->indata = indata;
      pdata->tau = tau;
      pdata->vardata = vardata;
      pdata->wvmtau = wvmtau;
      pdata->amstart = amstart;
      pdata->amfirst = amstart + ( amend - amstart )*pdata->f1/( nframes - 1 );
      pdata->lbnd = lbnd;
      pdata->ubnd = ubnd;
      pdata->isTordered = isTordered;
      pdata->ndims = ndims;
      pdata->data = data;
      pdata->hdr = hdr;
      pdata->method = method;
      pdata->tausrc = tausrc;

      /* Submit the job to the workforce. */
      thrAddJob( wf, 0, pdata, smf1_correct_extinction, 0, NULL, status );
    }

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

    /* Record if all time slices used a single air mass. */
    allquick = 1;
    for( iw = 0; iw < nw; iw++ ) {
      pdata = job_data + iw;
      if( ! pdata->allquick ) {
        allquick = 0;
        break;
      }
    }

    /* Free the job data. */
    job_data = astFree( job_data );
  }

  /* Lock the AST objects in the smfData for use by this thread. */
  smf_lock_data( data, 1, status );

  /* Add history entry if !allextcorr */
  if( (*status == SAI__OK) && !allextcorr ) {
    smf_history_add( data, FUNC_NAME, status);
  }

 CLEANUP:
  if (wvmtaucache) {
    if (!*wvmtaucache) {
      *wvmtaucache = wvmtau;
    }
  } else {
    wvmtau = astFree( wvmtau );
  }

  return allquick;
}
Example #2
0
void smf_calc_stareimage( smfData *data, const int naver, int *status) {

    double *avdata = NULL;           /* Pointer to averaged data */
    int dims[2];                     /* Dimensions of the stored image */
    smfHead *hdr;                    /* Header information */
    int j;                           /* Loop counter */
    size_t npts;                     /* Number of points in the averaged data */
    int numaver;                     /* Number of samples to average */
    int numimages;                   /* Number of output STARE images */
    int numsamples;                  /* Number of time slices (samples) */
    int remainder;                   /* Remainder from dividing no of timeslices
				      by number of frames to average */
    HDSLoc *scu2redloc = NULL;       /* Locator to SCU2RED extension */
    double steptime;                 /* Step time per sample, sec */
    double *zero = NULL;             /* Bolometer zero points */

    if ( *status != SAI__OK ) return;

    /* Check we have time-series data */
    if ( data->ndims != 3) {
        *status = SAI__ERROR;
        errRep(FUNC_NAME,
               "File does not contain time series data - unable to process",
               status);
        return;
    }

    /* Check we have flatfielded data */
    if ( !smf_history_check( data, "smf_flatfield", status) ) {
        if ( *status == SAI__OK ) {
            *status = SAI__ERROR;
            errRep(FUNC_NAME, "Data have not been flatfielded ", status);
            return;
        }
    }

    /* Do we have STARE data? */
    hdr = data->hdr;
    if ( hdr->obsmode == SMF__OBS_STARE ) {
        msgOutif(MSG__VERB," ", "Processing STARE data", status);

        if (smf_history_check( data, "smf_calc_stareimage", status) ) {
            msgOut(" ", "File contains STARE data which has already been processed: proceeding but this WILL OVERWRITE any STARE images already written into the SCU2RED extension",
                   status);
        }

        /* Number of output STARE images */
        numsamples = (data->dims)[2];

        /* If naver < 0 then re-calculate to give 1-second images */
        if ( naver < 0 ) {
            smf_fits_getD(hdr, "STEPTIME", &steptime, status);
            numaver = 1.0 / steptime;
        } else if ( naver > numsamples ) {
            msgOutif(MSG__NORM, "", "Warning: NAVER exceeds the number of samples - will average entire time stream to create a single image", status);
            numaver = numsamples;
        } else {
            numaver = naver;
        }

        numimages = numsamples / numaver;

        /* Warn user if the number to average is not a factor of the
           number of time slices  */
        remainder = numsamples % numaver;
        if ( remainder != 0 ) {
            msgSeti("R", remainder);
            msgSeti("N", numaver);
            msgSeti("T", numsamples);
            msgOutif(MSG__NORM, "", "Warning: NAVER (^N) is not a factor of the number of time slices (^T): final ^R samples will not be included in an image", status);
        }

        /* Helpful info for the user */
        msgSeti("N",numimages);
        if ( numimages == 1 ) {
            msgSetc("IM","image");
        } else {
            msgSetc("IM","images");
        }
        msgOutif( MSG__VERB, "", "Calculating ^N STARE ^IM", status );

        /* Obtain a locator for the extension where for the images will
           be stored */
        scu2redloc = smf_get_xloc(data, "SCU2RED", "SCUBA2_MAP_ARR", "WRITE",
                                  0, NULL, status);

        /* Set the dimensions of the output images - they are all the same
           so they can be safely set outside the loop below */
        dims[0] = (data->dims)[0];
        dims[1] = (data->dims)[1];

        /* Loop over the number of output images */
        for ( j=0; j<numimages; j++) {
            /* Average the time stream data over the desired interval */
            smf_average_dataD( data, j*numaver, numaver, 1, &avdata, &npts, status );

            /* Store the averaged data as an image */
            smf_store_image( data, scu2redloc, j, 2, dims, numaver, 0, 0, avdata, NULL,
                             status);

            avdata = astFree( avdata );
            zero = astFree( zero );
        }
        /* Add a history entry if everything's OK */
        smf_history_add(data, "smf_calc_stareimage",  status );

        /* Release SCU2RED locator */
        datAnnul( &scu2redloc, status );
    } else {
        msgOutif(MSG__NORM," ",
                 "Input file is not a STARE observation - ignoring", status);
    }

}
Example #3
0
int smf_correct_extinction(ThrWorkForce *wf, smfData *data, smf_tausrc tausrc, smf_extmeth method,
                            AstKeyMap * extpars, double tau, double *allextcorr,
                            double **wvmtaucache, int *status) {

  /* Local variables */
  int allquick = 0;        /* Is the extinction for all bolometers the same? */
  double amstart = VAL__BADD; /* Airmass at start */
  double amend = VAL__BADD;   /* Airmass at end */
  double elstart = VAL__BADD; /* Elevation at start (radians) */
  double elend = VAL__BADD;/* Elevation at end (radians) */
  smfHead *hdr = NULL;     /* Pointer to full header struct */
  double *indata = NULL;   /* Pointer to data array */
  int isTordered;          /* data order of input data */
  int lbnd[2];             /* Lower bound */
  size_t ndims;            /* Number of dimensions in input data */
  dim_t nframes = 0;       /* Number of frames */
  dim_t npts = 0;          /* Number of data points */
  dim_t nx = 0;            /* # pixels in x-direction */
  dim_t ny = 0;            /* # pixels in y-direction */
  int ubnd[2];             /* Upper bound */
  double *vardata = NULL;  /* Pointer to variance array */
  double * wvmtau = NULL;  /* WVM tau (smoothed or not) for these data */
  int nw;                  /* Number of worker threads */
  int iw;                  /* Thread index */
  SmfCorrectExtinctionData *job_data = NULL;  /* Array of job descriptions */
  SmfCorrectExtinctionData *pdata;   /* Pointer to next job description */
  size_t framestep;         /* Number of frames per thread */

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

  /* If no correction requested, return */
  if( method==SMF__EXTMETH_NONE ) {
    msgOutif(MSG__VERB, "", FUNC_NAME ": Extinction method=none, returning",
             status );
    return allquick;
  }

  /* If no opacity monitor specified generate bad status */
  if( tausrc==SMF__TAUSRC_NULL ) {
    *status = SAI__ERROR;
    errRep( "", FUNC_NAME ": No extinction monitor specified",
            status );
    return allquick;
  }

  if( smf_history_check( data, FUNC_NAME, status) ) {
    /* If caller not requesting allextcorr fail here */
    if( !allextcorr ) {
      msgSetc("F", FUNC_NAME);
      msgOutif(MSG__VERB," ",
               "^F has already been run on these data, returning to caller",
               status);
      return allquick;
    }
  }

  /* Acquire the data order */
  isTordered = data->isTordered;

  /* make sure we have a header */
  hdr = data->hdr;
  if( hdr == NULL ) {
    *status = SAI__ERROR;
    errRep( FUNC_NAME, "Input data has no header", status);
    return allquick;
  }

  /* Do we have 2-D image data? */
  ndims = data->ndims;
  if (ndims == 2) {
    nframes = 1;
    nx = (data->dims)[0];
    ny = (data->dims)[1];
    npts = nx*ny;
  } else {
    /* this routine will also check for dimensionality */
    smf_get_dims( data, &nx, &ny, &npts, &nframes, NULL, NULL, NULL, status );
  }

  /* Tell user we're correcting for extinction */
  msgOutif(MSG__VERB," ",
           "Correcting for extinction.", status);

  /* Should check data type for double if not allextcorr case */
  if( !allextcorr ) {
    if (!smf_dtype_check_fatal( data, NULL, SMF__DOUBLE, status)) return allquick;
  }

  /* Check that we're not trying to use the WVM for 2-D data */
  if ( ndims == 2 && tausrc == SMF__TAUSRC_WVMRAW ) {
    if ( *status == SAI__OK ) {
      *status = SAI__ERROR;
      errRep( FUNC_NAME, "Method WVMRaw can not be used on 2-D image data", status );
      return allquick;
    }
  } else if (ndims < 2 || ndims > 3) {
    if (*status == SAI__OK) {
      *status = SAI__ERROR;
      errRepf( FUNC_NAME, "Can not extinction correct data with %zd dimension(s)", status,
              ndims );
      return allquick;
    }
  }

  if (tausrc == SMF__TAUSRC_WVMRAW) {
    size_t ntotaltau = 0;
    size_t ngoodtau = 0;
    /* calculate WVM unless we have external values */
    if (wvmtaucache && *wvmtaucache) {
      wvmtau = *wvmtaucache;
      smf_smfFile_msg( data->file, "FILE", 1, "<unknown>");
      msgOutiff( MSG__VERB, "", "Using cached WVM data for extinction correction of ^FILE",
                 status);
    } else {
      smf_calc_smoothedwvm( wf, NULL, data, extpars, &wvmtau, &ntotaltau,
                            &ngoodtau, status );
      smf_smfFile_msg( data->file, "FILE", 1, "<unknown>");
      msgOutiff( MSG__VERB, "", "Using WVM mode for extinction correction of ^FILE"
                 " %.0f %% of WVM data are present", status,
                 (double)(100.0*(double)ngoodtau/(double)ntotaltau) );
    }
  }

  /* Check auto mode */
  if (tausrc == SMF__TAUSRC_AUTO && *status == SAI__OK) {
    smf_smfFile_msg( data->file, "FILE", 1, "<unknown>" );

    if (ndims == 2) {
      /* have to use CSO mode */
      tausrc = SMF__TAUSRC_CSOTAU;
    } else if (ndims == 3) {
      /* Calculate the WVM tau data and see if we have enough good data */
      size_t ngoodtau = 0;
      size_t ntotaltau = 0;
      double percentgood = 0.0;

      if (wvmtaucache && *wvmtaucache) {
        wvmtau = *wvmtaucache;
        tausrc = SMF__TAUSRC_WVMRAW;
        smf_smfFile_msg( data->file, "FILE", 1, "<unknown>");
        msgOutiff( MSG__VERB, "", "Using cached WVM data for extinction correction of ^FILE",
                   status );
      } else {
        smf_calc_smoothedwvm( wf, NULL, data, extpars, &wvmtau, &ntotaltau,
                              &ngoodtau, status );
        percentgood = 100.0 * ((double)ngoodtau / (double)ntotaltau);

        if ( percentgood > 80.0) {
          tausrc = SMF__TAUSRC_WVMRAW;
          msgOutiff( MSG__VERB, "", "Selecting WVM mode for extinction correction of ^FILE."
                     " %.0f %% of WVM data are present", status, percentgood );
        } else {
          tausrc = SMF__TAUSRC_CSOTAU;
          if (wvmtau) wvmtau = astFree( wvmtau );
        }
      }
    }
    if (tausrc == SMF__TAUSRC_CSOTAU) {
      msgOutiff( MSG__VERB, "", "Selecting CSO mode for extinction correction of ^FILE", status );
    } else if (tausrc == SMF__TAUSRC_WVMRAW) {
      /* Dealt with this above */
    } else {
      /* oops. Fall back position */
      tausrc = SMF__TAUSRC_CSOTAU;
      msgOutiff( MSG__VERB, "", "Selecting CSO mode as unexpected fallback for extinction correction of ^FILE", status );
    }
  }

  /* If we have a CSO Tau then convert it to the current filter. This will also
     convert bad values to a value derived from the header if appropriate. */
  if ( tausrc == SMF__TAUSRC_CSOTAU ) {
    tau = smf_cso2filt_tau( hdr, tau, extpars, status );
    /* The tau source is now a real tau */
    tausrc = SMF__TAUSRC_TAU;
  }

  /* Find the airmass range for this data */
  smf_find_airmass_interval( hdr, &amstart, &amend, &elstart, &elend, status );
  if (*status == SAI__OK && (amstart == VAL__BADD || amend == VAL__BADD)) {
    *status = SAI__ERROR;
    errRep( "", "No good airmass values found in JCMTSTATE structure for these data",
            status );
  }

  /* if we are not doing WVM correction but are in adaptive mode we can determine
     whether or not we will have to use full or single mode just by looking at the
     airmass data. */
  if (ndims == 3 && tausrc != SMF__TAUSRC_WVMRAW && method == SMF__EXTMETH_ADAPT) {
    /* first and last is a good approximation given that most SCUBA-2 files will only
       be a minute duration. */
    double refel;
    double refam;

    /* only need to examine the largest airmass */
    if (amstart > amend) {
      refam = amstart;
      refel = elstart;
    } else {
      refam = amend;
      refel = elend;
    }

    /* and choose a correction method */
    if (is_large_delta_atau( refam, refel, tau, status) ) {
      method = SMF__EXTMETH_FULL;
      msgOutiff(MSG__DEBUG, " ",
               "Adaptive extinction algorithm selected per-bolometer airmass value "
               "per time slice (am=%g, tau=%g)", status, refam, tau);
    } else {
      msgOutiff(MSG__DEBUG, " ",
               "Adaptive extinction algorithm selected single airmass value per time slice"
               " (am=%g, tau=%g)", status, refam, tau);
      method = SMF__EXTMETH_SINGLE;
    }

  }

  /* Assign pointer to input data array if status is good */
  if ( *status == SAI__OK ) {
    indata = (data->pntr)[0];
    vardata = (data->pntr)[1];
  }

  /* Jump to the cleanup section if status is bad by this point
     since we need to free memory */
  if (*status != SAI__OK) goto CLEANUP;

  /* Array bounds for astTranGrid call */
  lbnd[0] = 1;
  lbnd[1] = 1;
  ubnd[0] = nx;
  ubnd[1] = ny;

  /* Unlock the AST objects in the smfData so that the worker threads can
     lock them. */
  smf_lock_data( data, 0, status );

  /* How many threads do we get to play with */
  nw = wf ? wf->nworker : 1;

  /* Find how many frames to process in each worker thread. */
  framestep = nframes/nw;
  if( framestep == 0 ) {
    framestep = 1;
    nw = nframes;
  }

  /* Allocate job data for threads, and store the range of frames to be
     processed by each one. Ensure that the last thread picks up any
     left-over frames. */
  job_data = astCalloc( nw, sizeof(*job_data) );
  if( *status == SAI__OK ) {
    for( iw = 0; iw < nw; iw++ ) {
      pdata = job_data + iw;
      pdata->f1 = iw*framestep;
      if( iw < nw - 1 ) {
        pdata->f2 = pdata->f1 + framestep - 1;
      } else {
        pdata->f2 = nframes - 1 ;
      }

      pdata->nframes = nframes;
      pdata->npts = npts;
      pdata->allextcorr = allextcorr;
      pdata->indata = indata;
      pdata->tau = tau;
      pdata->vardata = vardata;
      pdata->wvmtau = wvmtau;
      pdata->amstart = amstart;
      pdata->amfirst = amstart + ( amend - amstart )*pdata->f1/( nframes - 1 );
      pdata->lbnd = lbnd;
      pdata->ubnd = ubnd;
      pdata->isTordered = isTordered;
      pdata->ndims = ndims;
      pdata->data = data;
      pdata->hdr = hdr;
      pdata->method = method;
      pdata->tausrc = tausrc;

      /* Submit the job to the workforce. */
      thrAddJob( wf, 0, pdata, smf1_correct_extinction, 0, NULL, status );
    }

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

    /* Record if all time slices used a single air mass. */
    allquick = 1;
    for( iw = 0; iw < nw; iw++ ) {
      pdata = job_data + iw;
      if( ! pdata->allquick ) {
        allquick = 0;
        break;
      }
    }

    /* Free the job data. */
    job_data = astFree( job_data );
  }

  /* Lock the AST objects in the smfData for use by this thread. */
  smf_lock_data( data, 1, status );

  /* Add history entry if !allextcorr */
  if( (*status == SAI__OK) && !allextcorr ) {
    smf_history_add( data, FUNC_NAME, status);
  }

 CLEANUP:
  if (wvmtaucache) {
    if (!*wvmtaucache) {
      *wvmtaucache = wvmtau;
    }
  } else {
    wvmtau = astFree( wvmtau );
  }

  return allquick;
}