示例#1
0
文件: ndf.c 项目: timj/starlink-pyndf
static int
pyndf_cput_helper ( int ndfid, const char * comp, const char * value )
{
  int status = SAI__OK;
  errBegin(&status);
  if (value) {
    ndfCput(value, ndfid, comp, &status);
  } else {
    ndfReset( ndfid, comp, &status );
  }
  if (raiseNDFException(&status)) return -1;
  return 0;
}
示例#2
0
static void smf1_jsadicer( int indfo, int *olbnd, int *oubnd,
                           AstMapping *tile_map, AstFrame *tile_frm,
                           AstMapping *p2pmap, void *ipd, void *ipv,
                           unsigned char *ipq, int *status ){
/*
*  Name:
*     smf1_jsadicer

*  Purpose:
*     Copy one tile from the input NDF into a specified output NDF.

*  Language:
*     Starlink ANSI C

*  Type of Module:
*     C function

*  Invocation:
*     void smf1_jsadicer( int indfo, int *olbnd, int *oubnd,
*                         AstMapping *tile_map, AstFrame *tile_frm,
*                         AstMapping *p2pmap, void *ipd, void *ipv,
*                         unsigned char *ipq, int *status )

*  Arguments:
*     indfo = int (Given)
*        An identifier for the NDF in which the copied data is to be
*        stored. It's original pixel bounds are used as the bounds of the
*        ipd, ipv and ipq arrays.
*     olbnd = int * (Given)
*        The new lower pixel bounds required for the output NDF. The bounds
*        of the supplied NDF are changed to match these values.
*     oubnd = int * (Given)
*        The new upper pixel bounds required for the output NDF. The bounds
*        of the supplied NDF are changed to match these values.
*     tile_map = AstMapping * (Given)
*        The mapping from pixel coords in the output NDF to WCS coords.
*     tile_frm = AstMapping * (Given)
*        The WCS Frame for the output NDF.
*     p2pmap = AstMapping * (Given)
*        The mapping from pixel coords in the input NDF to pixel coords in
*        the output NDF.
*     ipd = void * (Given)
*        Pointer to the start of the input data array. If this is NULL,
*        the existing contents of the NDF are used as input.
*     ipv = void * (Given)
*        Pointer to the start of the input variance array. Should be NULL
*        if no variances are available.
*     ipq = unsigned char * (Given)
*        Pointer to the start of the input quality array. Should be NULL
*        if no quality is available.
*     status = int * (Given)
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   AstFrame *use_frm = NULL;
   AstFrameSet *owcs;
   AstMapping *use_map = NULL;
   AstMapping *use_p2pmap = NULL;
   AstShiftMap *sm;
   char type[ NDF__SZTYP + 1 ];
   double shifts[ 3 ];
   int axes[ 2 ];
   int axout[ NDF__MXDIM ];
   int free_arrays;
   int isreal;
   int lbnd_tile[ 3 ];
   int ndim;
   int nel;
   int nin;
   int there;
   int ubnd_tile[ 3 ];
   unsigned char *ipq_out = NULL;
   void *ipd_out = NULL;
   void *ipv_out = NULL;

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

/* Begin an AST context. */
   astBegin;

/* Get the NDF data type - _REAL or _DOUBLE. */
   ndfType( indfo, "Data", type, sizeof(type), status );
   isreal = !strcmp( type, "_REAL" );

/* Get the existing bounds of the NDF. */
   ndfBound( indfo, 3, lbnd_tile, ubnd_tile, &ndim, status );

/* If no data array has been supplied, take a copy of the original Data,
   Quality and Variance arrays and use these as the input arrays. */
   if( !ipd ) {
      free_arrays = 1;

      ndfMap( indfo, "Data", type, "Read", &ipd_out, &nel, status );
      ipd = astStore( NULL, ipd_out,
                      nel*(isreal?sizeof(float):sizeof(double)) );
      ndfUnmap( indfo, "Data", status );

      ndfState( indfo, "Variance", &there, status );
      if( there ) {
         ndfMap( indfo, "Variance", type, "Read", &ipv_out, &nel, status );
         ipv = astStore( NULL, ipv_out,
                         nel*(isreal?sizeof(float):sizeof(double)) );
         ndfUnmap( indfo, "Variance", status );
      } else {
         ipv = NULL;
      }

      ndfState( indfo, "Quality", &there, status );
      if( there ) {
         ndfMap( indfo, "Quality", "_UBYTE", "Read", (void **) &ipq_out,
                 &nel, status );
         ipq = astStore( NULL, ipq_out, nel*sizeof(*ipq) );
         ndfUnmap( indfo, "Quality", status );
      } else {
         ipq = NULL;
      }

   } else {
      free_arrays = 0;
   }

/* Set the bounds of the NDF to the required values. */
   ndfSbnd( ndim, olbnd, oubnd, indfo, status );

/* Erase the existing WCS FrameSet and then get the default WCS FrameSet. */
   ndfReset( indfo, "WCS", status );
   ndfGtwcs( indfo, &owcs, status );

/* If the supplied mapping and Frame have two many axes, strip some off.
   The orering of pixel axes in the output JSA tile is hardwired by SMURF
   as (ra,dec,spec). */
   nin = astGetI( tile_map, "Nin" );
   if( nin == 3 && ndim == 2 ) {
      axes[ 0 ] = 1;
      axes[ 1 ] = 2;
      astMapSplit( tile_map, 2, axes, axout, &use_map );
      if( use_map ) {
         use_frm = astPickAxes( tile_frm, 2, axout, NULL );
      } else if( *status == SAI__OK ) {
         *status = SAI__ERROR;
         errRepf( " ", "smf1_jsadicer: cannot split mapping (programming "
                  "error).", status );
      }

      astMapSplit( p2pmap, 2, axes, axout, &use_p2pmap );
      if( !use_p2pmap && *status == SAI__OK ) {
         *status = SAI__ERROR;
         errRepf( " ", "smf1_jsadicer: cannot split mapping (programming "
                  "error).", status );
      }

   } else if( nin == ndim ) {
      use_p2pmap = astClone( p2pmap );
      use_map = astClone( tile_map );
      use_frm = astClone( tile_frm );

   } else if( *status == SAI__OK ) {
      *status = SAI__ERROR;
      errRepf( " ", "smf1_jsadicer: unexpected combination of nin (%d) and "
               "ndim (%d) (programming error).", status, nin, ndim );
   }

/* Add the tile WCS Frame into the output NDF's WCS FrameSet, using "tilemap"
   to connect it to the PIXEL Frame (NDF ensure Frame 2 is the PIXEL
   Frame). */
   astAddFrame( owcs, 2, use_map, use_frm );

/* The astResample function is odd in that it assumes that pixel coords
   are defined such that the centre of pixel "I" has integral pixel
   coord "I" (rather than "I-0.5" as is usual in Starlink). So we need to
   use a half-pixel ShiftMap at start and end of the p2pmap Mapping to
   account for this. */
   shifts[ 0 ] = -0.5;
   shifts[ 1 ] = -0.5;
   shifts[ 2 ] = -0.5;
   sm = astShiftMap( ndim, shifts, " " );
   use_p2pmap = (AstMapping *) astCmpMap( sm, use_p2pmap, 1, " " );
   astInvert( sm );
   use_p2pmap = (AstMapping *) astCmpMap( use_p2pmap, sm, 1, " " );

/* Store this modified WCS FrameSet in the output NDF. */
   ndfPtwcs( owcs, indfo, status );

/* Map the required arrays of the output NDF. */
   ndfMap( indfo, "Data", type, "Write", &ipd_out, &nel, status );
   if( ipv ) ndfMap( indfo, "Variance", type, "Write", &ipv_out, &nel,
                     status );
   if( ipq ) ndfMap( indfo, "Quality", "_UBYTE", "Write",
                      (void **) &ipq_out, &nel, status );

/* Copy the input data values to the output, using nearest neighbour
   interpolation (the mapping should always map input pixel centres onto
   output pixel centres). We can set the "tol" argument non-zero (e.g. 0.1)
   without introducing any error because the the p2pmap mapping will be
   piecewise linear. This gives a factor of about 5 decrease in the time
   spent within astResample. */
   if( !strcmp( type, "_REAL" ) ) {
      (void) astResampleF( use_p2pmap, ndim, lbnd_tile, ubnd_tile, (float *) ipd,
                           (float *) ipv, AST__NEAREST, NULL, NULL,
                           AST__USEBAD, 0.1, 1000, VAL__BADR, ndim,
                           olbnd, oubnd, olbnd, oubnd,
                           (float *) ipd_out, (float *) ipv_out );
   } else {
      (void) astResampleD( use_p2pmap, ndim, lbnd_tile, ubnd_tile, (double *) ipd,
                           (double *) ipv, AST__NEAREST, NULL, NULL,
                           AST__USEBAD, 0.1, 1000, VAL__BADD, ndim,
                           olbnd, oubnd, olbnd, oubnd,
                           (double *) ipd_out, (double *) ipv_out );
   }

   if( ipq ) {
      (void) astResampleUB( use_p2pmap, ndim, lbnd_tile, ubnd_tile, ipq, NULL,
                            AST__NEAREST, NULL, NULL, 0, 0.1, 1000, 0,
                            ndim, olbnd, oubnd, olbnd, oubnd, ipq_out,
                            NULL );
   }

/* Unmap everything the output NDF. */
   ndfUnmap( indfo, "*", status );

/* Free the input arrays if they were allocated in this function. */
   if( free_arrays ) {
      ipd = astFree( ipd );
      ipv = astFree( ipv );
      ipq = astFree( ipq );
   }

/* End the AST context. */
   astEnd;
}
示例#3
0
void smf_calc_iqu( ThrWorkForce *wf, smfData *data, int block_start,
                  int block_end, int ipolcrd, int qplace, int uplace,
                  int iplace, NdgProvenance *oprov, AstFitsChan *fc,
                  int pasign, double paoff, double angrot, int submean,
                  int harmonic, int *status ){

/* Local Variables: */
   AstCmpMap *cm1;
   AstCmpMap *cm2;
   AstFrameSet *wcs;          /* WCS FrameSet for output NDFs */
   AstMapping *fpmap1;
   AstMapping *fpmap2;
   AstMapping *oskymap;
   AstMapping *totmap;
   AstSkyFrame *oskyfrm;
   AstWinMap *wm;             /* Mapping to reverse the X GRID axis */
   const JCMTState *state;    /* JCMTState info for current time slice */
   const char *usesys;        /* Used system string */
   dim_t itime;               /* Time slice index */
   dim_t nbolo;               /* No. of bolometers */
   dim_t ncol;                /* No. of columns of bolometers */
   dim_t nrow;                /* No. of rows of bolometers */
   dim_t ntime;               /* Time slices to check */
   dim_t ntslice;             /* Number of time-slices in data */
   double *ipi;               /* Pointer to output I array */
   double *ipiv;              /* Pointer to output I variance array */
   double *ipq;               /* Pointer to output Q array */
   double *ipqv;              /* Pointer to output Q variance array */
   double *ipu;               /* Pointer to output U array */
   double *ipuv;              /* Pointer to output U variance array */
   double *mean;
   double ang_data[2];
   double fox[2];
   double foy[2];
   double fpr0;
   double fprinc;
   double fx[2];
   double fy[2];
   double ina[ 2 ];           /* Bolometer coords at bottom left */
   double inb[ 2 ];           /* Bolometer coords at top right */
   double outa[ 2 ];          /* NDF GRID coords at bottom left */
   double outb[ 2 ];          /* NDF GRID coords at top right */
   int bstep;                 /* Bolometer step between threads */
   int el;                    /* Number of mapped array elements */
   int gotvar;                /* Were any output variances created? */
   int indfi;                 /* Identifier for NDF holding I values */
   int indfq;                 /* Identifier for NDF holding Q values */
   int indfu;                 /* Identifier for NDF holding Q values */
   int iworker;               /* Index of a worker thread */
   int lbnd[ 2 ];             /* Lower pixel bounds of output NDF */
   int moving;
   int nworker;               /* No. of worker threads */
   int old;                   /* Data has old-style POL_ANG values? */
   int tstep;                 /* Time slice step between threads */
   int ubnd[ 2 ];             /* Upper pixel bounds of output NDF */
   size_t bstride;            /* Stride between adjacent bolometer values */
   size_t tstride;            /* Stride between adjacent time slice values */
   smfCalcIQUJobData *job_data = NULL; /* Pointer to all job data */
   smfCalcIQUJobData *pdata = NULL;/* Pointer to next job data */
   smfHead *hdr;              /* Pointer to data header this time slice */

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

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

/* 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, &nrow, &ncol, &nbolo, &ntslice, NULL, &bstride,
                 &tstride, status );

/* Report an error if the block of time slices extends of either end. */
   if( block_start < 0 || block_end >= (int) ntslice ) {
      if( *status == SAI__OK ) {
         *status = SAI__ERROR;
         msgSeti( "S", block_start );
         msgSeti( "E", block_end );
         msgSeti( "N", ntslice );
         errRep( " ", "smf_calc_iqu: invalid block of time slices - ^S to "
                 "^E (^N time slices are available).", status );
      }
   }

/* Create the output NDFs. Each one is a 2D array with dimensions
   equal to the bolometer array. */
   lbnd[ 0 ] = 1;
   lbnd[ 1 ] = 1;
   ubnd[ 0 ] = ncol;
   ubnd[ 1 ] = nrow;
   ndfNew( "_DOUBLE", 2, lbnd, ubnd, &qplace, &indfq, status );
   ndfNew( "_DOUBLE", 2, lbnd, ubnd, &uplace, &indfu, status );
   if( iplace != NDF__NOPL ) {
      ndfNew( "_DOUBLE", 2, lbnd, ubnd, &iplace, &indfi, status );
   } else {
      indfi = NDF__NOID;
   }

/* Store any supplied provenance in all NDFs. */
   if( oprov ) {
      ndgWriteProv( oprov, indfq, 1, status );
      ndgWriteProv( oprov, indfu, 1, status );
      if( indfi != NDF__NOID ) ndgWriteProv( oprov, indfi, 1, status );
   }

/* Store any supplied FITS headers in all NDFs.*/
   if( fc && astGetI( fc, "NCard" ) > 0 ) {
      kpgPtfts( indfq, fc, status );
      kpgPtfts( indfu, fc, status );
      if( indfi != NDF__NOID )  kpgPtfts( indfi, fc, status );
   }

/* Store the WCS frameSet in all NDFs. First get the FrameSet for the
   central time slice in the block, and set its current Frame to the
   tracking frame. */
   smf_tslice_ast( data, ( block_start + block_end )/2, 1, NO_FTS, status);
   usesys = sc2ast_convert_system( (data->hdr->allState)[0].tcs_tr_sys,
                                    status );
   astSetC( hdr->wcs, "System", usesys );

/* Get the Mapping from focal plane coords to bolometer grid coords. This
   is the same for all time slices. sc2ast ensures that frame 3 is FPLANE. */
   fpmap1 = astGetMapping( hdr->wcs, 3, AST__BASE );

/* Take a copy and then reverse the X axis of the GRID Frame by remaping the
   base Frame using a WinMap. This produces a pixel grid such as you would
   see by looking up at the sky from underneath the array, rather than looking
   down at the ground from above the array. */
   wcs = astCopy( hdr->wcs );
   ina[ 0 ] = 1.0;
   inb[ 0 ] = ncol;
   ina[ 1 ] = 1.0;
   inb[ 1 ] = nrow;

   outa[ 0 ] = ncol;
   outb[ 0 ] = 1.0;
   outa[ 1 ] = 1.0;
   outb[ 1 ] = nrow;

   wm = astWinMap( 2, ina, inb, outa, outb, " " );
   astRemapFrame( wcs, AST__BASE, wm );
   wm = astAnnul( wm );

/* Get the Mapping from output grid coords to focal plane coords. */
   fpmap2 = astGetMapping( wcs, AST__BASE, 3 );

/* If the target is moving (assumed to be the case if the tracking
   system is AZEL or GAPPT), make the FrameSet current Frame represent
   offsets from the reference position (i.e. the moving target), and
   indicate that the offset coord system should be used for alignment. */
   if( !strcmp( usesys, "AZEL" ) || !strcmp( usesys, "GAPPT" ) ){
      astSet( wcs, "SkyRefIs=Origin,AlignOffset=1" );
      moving = 1;
   } else {
      moving = 0;
   }

/* Store the FrameSet in the output NDFs. */
   ndfPtwcs( wcs, indfq, status );
   ndfPtwcs( wcs, indfu, status );
   if( indfi != NDF__NOID ) ndfPtwcs( wcs, indfi, status );

/* Map the Data array in each NDF. */
   ndfMap( indfq, "Data", "_DOUBLE", "WRITE", (void **) &ipq, &el, status );
   ndfMap( indfu, "Data", "_DOUBLE", "WRITE", (void **) &ipu, &el, status );
   if( indfi != NDF__NOID ) {
      ndfMap( indfi, "Data", "_DOUBLE", "WRITE", (void **) &ipi, &el, status );
   } else {
      ipi = NULL;
   }

/* Map the Variance array in each NDF. */
   ndfMap( indfq, "Variance", "_DOUBLE", "WRITE", (void **) &ipqv, &el, status );
   ndfMap( indfu, "Variance", "_DOUBLE", "WRITE", (void **) &ipuv, &el, status );
   if( indfi != NDF__NOID ) {
      ndfMap( indfi, "Variance", "_DOUBLE", "WRITE", (void **) &ipiv, &el, status );
   } else {
      ipiv = NULL;
   }

/* If required, allocate memory to hold the mean bolometer value at each
   time slice. */
   mean = submean ? astMalloc( ntslice*sizeof( *mean ) ) : NULL;

/* 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;
         }
      }

/* If required, find the mean bolometer value at each time slice. */
      if( submean ) {

/* Determine which time-slices are to be processed by which threads. */
         tstep = ntslice/nworker;
         if( tstep < 1 ) tstep = 1;

         for( iworker = 0; iworker < nworker; iworker++ ) {
            pdata = job_data + iworker;
            pdata->block_start = iworker*tstep;
            if( iworker < nworker - 1 ) {
               pdata->block_end = pdata->block_start + tstep - 1;
            } else {
               pdata->block_end = ntslice - 1;
            }
         }

/* Store all the other info needed by the worker threads, and submit the
   jobs to calculate the Q and U 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->dat = data->pntr[0];
            pdata->nbolo = nbolo;
            pdata->qua = smf_select_qualpntr( data, NULL, status );;
            pdata->tstride = tstride;
            pdata->mean = mean;
            pdata->action = 1;

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

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

      }

/* Get the Frame representing absolute sky coords in the output NDF,
   and the Mapping from sky to grid in the output NDF. */
      oskyfrm = astCopy( astGetFrame( wcs, AST__CURRENT ) );
      astSet( oskyfrm, "SkyRefIs=Ignored" );
      oskymap = astGetMapping( wcs, AST__CURRENT, AST__BASE );
      wcs = astAnnul( wcs );

/* Find the first and last time slices, calculate the angle between the
   focal pane Y axis at the time slice, and the focal plane Y axis in
   the output NDF. For intervening time-slices, the angle is found by
   linear interpolation between the extreme time slices. */
      for( el = 0; el < 2; el++ ) {

/* Get the mapping from GRID coords in the input time slice to GRID
   coords in the output. */
         totmap = smf_rebin_totmap( data, el?ntslice-1:0, oskyfrm, oskymap,
                                    moving, NO_FTS, status );

/* Modify it to be the Mapping from focal plane coords in the input time
   slice to focal plane coords in the output. */
         cm1 = astCmpMap( fpmap1, totmap, 1, " " );
         cm2 = astCmpMap( cm1, fpmap2, 1, " " );

/* Use this Mapping to convert two points on the focal plane Y axis from
   the input to the output. */
         fx[0] = 0.0;
         fy[0] = 0.0;
         fx[1] = 0.0;
         fy[1] = 4.0;
         astTran2( cm2, 2, fx, fy, 1, fox, foy );

/* The angle from the focal plane Y axis in the output to the focal plane
   Y axis in the input time slice, measured positive in sense of rotation
   from Fy to Fx. */
         ang_data[ el ] = atan2( fox[1]-fox[0], foy[1]-foy[0] );

/* Free resources for this time slice. */
         totmap = astAnnul( totmap );
         cm1 = astAnnul( cm1 );
         cm2 = astAnnul( cm2 );
      }

/* Annul objects. */
      oskymap = astAnnul( oskymap );
      oskyfrm = astAnnul( oskyfrm );
      fpmap1 = astAnnul( fpmap1 );
      fpmap2 = astAnnul( fpmap2 );

/* Get the constants of the linear relationship between focal plane
   rotation and time slice index "fpr = fpr0 + itime*fprinc". */
      fpr0 = ang_data[ 0 ];
      fprinc = ( ang_data[ 1 ] - fpr0 )/( ntslice - 1 );

/* 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 Q and U 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->dat = data->pntr[0];;
         pdata->nbolo = nbolo;
         pdata->qua = smf_select_qualpntr( data, NULL, status );;
         pdata->tstride = tstride;
         pdata->allstates = hdr->allState;
         pdata->ipq = ipq;
         pdata->ipu = ipu;
         pdata->ipi = ipi;
         pdata->ipqv = ipqv;
         pdata->ipuv = ipuv;
         pdata->ipiv = ipiv;
         pdata->ipolcrd = ipolcrd;
         pdata->block_start = block_start;
         pdata->block_end = block_end;
         pdata->old = old;
         pdata->ncol = ncol;
         pdata->pasign = pasign ? +1: -1;
         pdata->paoff = paoff;
         pdata->angrot = angrot;
         pdata->fpr0 = fpr0;
         pdata->fprinc = fprinc;
         pdata->angfac = harmonic/4.0;
         pdata->action = 0;
         pdata->mean = mean;

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

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

/* See if any thread produced non-bad variance values. */
      gotvar = 0;
      for( iworker = 0; iworker < nworker; iworker++ ) {
         pdata = job_data + iworker;
         if( pdata->gotvar ) gotvar = 1;
      }

/* If no variances were created, erase the Variance component and tell
   the user. */
      ndfUnmap( indfq, "*", status );
      ndfUnmap( indfu, "*", status );
      if( ipi ) ndfUnmap( indfi, "*", status );

      if( !gotvar ) {
         ndfReset( indfq, "Variance", status );
         ndfReset( indfu, "Variance", status );
         if( ipi ) ndfReset( indfi, "Variance", status );
         msgOut( "", "Warning: Insufficient input data to produce variances",
                 status );
      }
   }

/* Add POLANAL Frames to the WCS FrameSet in each output NDF. This Frame
   is used by POLPACK to determine the reference direction of the Stokes
   vectors (focal plane Y in this case, i.e. zero-based axis 1 ). */
   smf_polext( indfq, 0, 0.0, "FPLANE", 1, status );
   smf_polext( indfu, 0, 0.0, "FPLANE", 1, status );
   if( ipi ) smf_polext( indfi, 0, 0.0, "FPLANE", 1, status );

/* Free the two output NDFs. */
   ndfAnnul( &indfq, status );
   ndfAnnul( &indfu, status );
   if( ipi ) ndfAnnul( &indfi, status );

/* Free other resources. */
   job_data = astFree( job_data );
   mean = astFree( mean );
}