/* $Id: casu_photcal_extinct.c,v 1.7 2015/09/30 08:33:06 jim Exp $
 *
 * This file is part of the CASU Pipeline utilities
 * Copyright (C) 2015 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * $Author: jim $
 * $Date: 2015/09/30 08:33:06 $
 * $Revision: 1.7 $
 * $Name:  $
 */

/* Includes */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define _XOPEN_SOURCE
#include <cpl.h>
#include <math.h>
#include <string.h>

#include "casu_mods.h"
#include "catalogue/casu_utils.h"
#include "casu_stats.h"
#include "catalogue/casu_fits.h"

typedef struct {
    char *filter_name;
    float atm_extcoef;
    float mag_offset;
    char **coleq_columns;
    char **coleq_errcols;
    float *coleq_coefs;
    float gal_extcoef;
    float default_zp;
    float default_zp_err;
    int ncolumns_coleq;
} photstrct;

#define INITALLOC 1024
#define SZBUF 1024
#define DEGRAD (CPL_MATH_PI/180.0)
#define RADARCSEC (3600.0*180.0/CPL_MATH_PI)

static double pixsize (cpl_propertylist *plist);
static int casu_phot_open(cpl_table *phottab, char *filt, photstrct *p);
static void casu_phot_close(photstrct *p);
static int extract_columns(cpl_table *tab, photstrct *p);
static int extract_coleq(cpl_table *tab, photstrct *p);
static void write_hdr_1(cpl_propertylist *p, int nresim, float med3, float sig3,
                        float lim3, float med5, float sig5, float lim5,
                        float extcoef, float extinct, float skybrt, int doqc);
static void modifytab(photstrct *p, cpl_table *intab, casu_fits *im, 
                      int minstars, cpl_image *schl_n, cpl_image *schl_s);
static void getextinct(cpl_image *schl_n, cpl_image *schl_s, double ra,
                       double dec, float *ebv);
static void radectolb(double ra, double dec, float epoch, double *gal_l, 
                      double *gal_b);
static void photdistort(casu_fits *im, cpl_table *outtab);
static void getproj_pixsize(cpl_wcs *wcs, double crv1, double tand, 
                            double secd, double x, double y, double *pixsize);

static float propertylist_get_float_or_default(const cpl_propertylist * list,
		const char * name, const float default_val);

/**@{*/

/* NB: This routine should be used only for a pawprint or a tile. Not for
   a whole night. */

/*---------------------------------------------------------------------------*/
/**
    \ingroup casu_modules
    \brief Do photometric calibration 

    \par Name:
        casu_photcal_extinct
    \par Purpose:
        Do photometric calibration of a pawprint or tile
    \par Description:
        A photometric calibration calculation is done for either a 
        tile or a pawprint. The input matched standards catalogue will
        be modified to include information on galactic extinction. Fluxes
        will also be modified by the camera distortion implied by the
        WCS.
    \par Language:
        C
    \param images
        The input image list
    \param mstds
        The matched standards tables list
    \param cats
        The list of source catalogues
    \param nimages
        The number of input images
    \param filt
        The name of the filter for the observation
    \param phottab
        The relevant table for photometric calibration for a given standard
        source
    \param minstars
        The minimum number of stars for the solution
    \param schlf_n
        The Schlegel map for the northern hemisphere
    \param schlf_s
        The Schlegel map for the southern hemisphere
    \param expkey
        The FITS header keyword for the exposure time
    \param amkey
        The FITS header keyword for the airmass
    \param status
        An input/output status
    \retval CASU_OK
        if everything is ok
    \retval CASU_WARN
        if there are no images or images fail to load
    \par QC headers:
        - \b MAGZPT
            The zeropoint calculated through aperture 3 using only objects from
            the current image
        - \b MAGZERR
            The zeropoint sigma calculated through aperture 3 using only 
            objects from the current image.
        - \b MAGNZPT
            The number of objects used to calculate the zeropoint.
        - \b LIMITING_MAG
            The 5 sigma limiting magnitude through aperture 3.
        - \b SKYBRIGHT
            The sky brightness in mag/sq-arcsec
    \par DRS headers:
        The following DRS keywords are written to the infile extension header
        - \b MAGNZPTIM
            The number of objects used to calculate the zeropoint for the
            current image.
        - \b ZPIM1
            The zeropoint calculated through 1*rcore using only objects from
            the current image.
        - \b ZPSIGIM1
            The zeropoint sigma calculated through 1*rcore using only 
            objects from the current image.
        - \b LIMIT_MAG1
            The 5 sigma limiting magnitude for this image in 1*rcore
        - \b ZPIM2
            The zeropoint calculated through 2*rcore using only objects from
            the current image.
        - \b ZPSIGIM2
            The zeropoint sigma calculated through 2*rcore using only 
            objects from the current image.
        - \b LIMIT_MAG2
            The 5 sigma limiting magnitude for this image in 2*rcore
        - \b MAGNZPTALL
            The number of objects on all images used to calculate the 
            zeropoint.
        - \b ZPALL1
            The zeropoint calculated through 1*rcore using all objects from
            all images in the list.
        - \b ZPSIGALL1
            The zeropoint sigma calculated through 1*rcore using all objects
            from all images in the list
        - \b ZPALL2
            The zeropoint calculated through 2*rcore using all objects from
            all images in the list.
        - \b ZPSIGALL2
            The zeropoint sigma calculated through 2*rcore using all objects
            from all images in the list
        - \b MEDEBV
            The median colour excess calculated from the Schlegel maps
        - \b SIGDET1
            The detector-level dispersion in the 1*rcore aperture
        - \b SIGDET2
            The detector-level dispersion in the 2*rcore aperture
        - \b SKYBRIGHT
            The sky brightness in mag/sq-arcsec
        - \b EXTINCT
            The assumed atmospheric extinction
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

extern int casu_photcal_extinct(casu_fits **images, casu_tfits **mstds, 
                                casu_tfits **cats, int nimages, 
                                char *filt, cpl_table *phottab, int minstars,
                                cpl_frame *schlf_n, cpl_frame *schlf_s,
                                const char *expkey, const char *amkey,
                                float magerrcut, int *status) {
    float **stdmagptr,*resall3,*resall5,apcor3,apcor5,exptime,**magerrptr;
    float airmass,*catcore3,*catcore5,*resim3,*resim5,cf,fluxmag3,fluxmag5;
    float refmag,extinct,dm3,dm5,med3,mad,med5,sig3,sig5;
    float rcore,lim5,dx,skylev,skbrt,*ebmvall,*zps3,*zps5,medebv,med;
    float sig3det,sig5det,*ebmv,blankvalue=-99.0,sumskbr;
    float sknoise,sumsat;
    int nresall,nalloc_resall,i,j,k,ncat,nresim,nresimtot;
    int ngood,oops,nlim,isrubbish,*dodgy,crud;
    const char *fctid = "casu_photcal_extinct";
    casu_fits *im;
    cpl_propertylist *ehu_im,*ehu_cat,*phu_im,*phu_cat;
    cpl_table *stds;
    cpl_image *schl_n,*schl_s;
    photstrct p;

    /* Inherited status */

    if (*status != CASU_OK)
        return(*status);

    /* Check for nonsense errors */

    if (nimages <= 0) {
        cpl_msg_error(fctid,"No images included in photometric calibration");
        FATAL_ERROR
    }

    /* Get the Schlegel maps */

    if (schlf_n == NULL || schlf_s == NULL) {
        schl_n = NULL;
        schl_s = NULL;
    } else {
        schl_n = cpl_image_load(cpl_frame_get_filename(schlf_n),CPL_TYPE_FLOAT,
                                0,0);
        schl_s = cpl_image_load(cpl_frame_get_filename(schlf_s),CPL_TYPE_FLOAT,
                                0,0);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error(fctid,"Unable to read Schlegel maps");
            freeimage(schl_n);
            freeimage(schl_s);
            FATAL_ERROR
        }   
    }

    /* Set up the structure that will give us the colour equations for
       this filter later on */

    if (casu_phot_open(phottab,filt,&p) != CASU_OK)
        FATAL_ERROR

    /* Get a workspace to hold the pointers to the magnitude columns */

    stdmagptr = cpl_malloc(p.ncolumns_coleq*sizeof(float *));
    if (p.coleq_errcols != NULL)
        magerrptr = cpl_malloc(p.ncolumns_coleq*sizeof(float *));
    else
        magerrptr = NULL;

    /* Get some workspace to hold all the zeropoints for all the images
       in the input list. This is an initial allocation and more can be
       made available later if needed */

    resall3 = cpl_malloc(INITALLOC*sizeof(float));
    resall5 = cpl_malloc(INITALLOC*sizeof(float));
    ebmvall = cpl_malloc(INITALLOC*sizeof(float));
    nresall = 0;
    nalloc_resall = INITALLOC;
    zps3 = cpl_malloc(nimages*sizeof(float));
    zps5 = cpl_malloc(nimages*sizeof(float));
    ngood = 0;
    nresimtot = 0;
    dodgy = cpl_calloc(nimages,sizeof(int));

    /* Loop for the input images and catalogues. Create some shorthand
       variables. */

    nlim = 0;
    float sumlim = 0.0;
    sumsat = 0.0;
    sumskbr = 0.0;
    for (i = 0; i < nimages; i++) {
        im = images[i];
        phu_im = casu_fits_get_phu(im);
        ehu_im = casu_fits_get_ehu(im);
        stds = casu_tfits_get_table(mstds[i]);
        ehu_cat = casu_tfits_get_ehu(cats[i]);
        
        /* Is this image any good? */

        if (casu_fits_get_status(im) != CASU_OK) {
            cpl_msg_warning(fctid,"Dummy image %s",casu_fits_get_fullname(im));
            write_hdr_1(ehu_im,0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1);
            write_hdr_1(ehu_cat,0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0);
            dodgy[i] = 1;
            continue;
        }           

        /* Are there any stars? */

        ncat = (int)cpl_table_get_nrow(stds);
        if (mstds[i] == NULL || ncat < minstars) {
            cpl_msg_warning(fctid,
                            "Too few standards available in %" CPL_SIZE_FORMAT,
                            (cpl_size)(i+1));
            cpl_error_reset();
            write_hdr_1(ehu_im,ncat,0.0,1.0,0.0,0.0,1.0,0.0,p.atm_extcoef,0.0,0.0,1);
            write_hdr_1(ehu_cat,ncat,0.0,1.0,0.0,0.0,1.0,0.0,p.atm_extcoef,0.0,0.0,0);
            dodgy[i] = 1;
            continue;
        }

        /* Now for the input catalogues. Start by getting some useful info
           from the header */

        apcor3 = cpl_propertylist_get_float(ehu_cat,"APCOR3");
        apcor5 = cpl_propertylist_get_float(ehu_cat,"APCOR5");
        rcore = cpl_propertylist_get_float(ehu_cat,"ESO DRS RCORE");
        skylev = cpl_propertylist_get_float(ehu_cat,"ESO DRS SKYLEVEL");
        sknoise = cpl_propertylist_get_float(ehu_cat,"ESO DRS SKYNOISE");
        dx = pixsize(ehu_im);
        exptime = (float)cpl_propertylist_get_double(phu_im,expkey);
        airmass = (float)cpl_propertylist_get_double(phu_im,amkey);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error(fctid,"Unable to get header %s or %s info",expkey,
                amkey);
            cpl_error_reset();
            continue;
        }

        /* Add some extra info to the table */

        modifytab(&p,stds,im,minstars,schl_n,schl_s);

        /* Dereference some of the columns */

        ncat = (int)cpl_table_get_nrow(stds);
        catcore3 = cpl_table_get_data_float(stds,"Aper_flux_3");
        catcore5 = cpl_table_get_data_float(stds,"Aper_flux_5");
        ebmv = cpl_table_get_data_float(stds,"ebmv");
        for (j = 0; j < p.ncolumns_coleq; j++) {
            cpl_table_fill_invalid_float(stds,(p.coleq_columns)[j],blankvalue);
            stdmagptr[j] = cpl_table_get_data_float(stds,(p.coleq_columns)[j]);
            if (stdmagptr[j] == NULL)
            {
                cpl_msg_error(fctid,"Unable to find filter column %s in photometric calibration table", p.coleq_columns[j]);
                FATAL_ERROR
            }

            if (magerrptr != NULL) {
                cpl_table_fill_invalid_float(stds,(p.coleq_errcols)[j],blankvalue);
                magerrptr[j] = cpl_table_get_data_float(stds,(p.coleq_errcols)[j]);
            }
        }

        /* Get some workspace for the results arrays for this image */

        resim3 = cpl_malloc(ncat*sizeof(float));
        resim5 = cpl_malloc(ncat*sizeof(float));
        nresim = 0;

        /* Loop for all the standards */

        extinct = p.atm_extcoef*(airmass - 1.0);
        for (j = 0; j < ncat; j++) {
            
            /* Do core magnitude calculation */

            cf = catcore3[j]/exptime;
            if (cf < 1.0)
                cf = 1.0;
            fluxmag3 = 2.5*log10((double)cf) + apcor3;
            cpl_table_set_float(stds,"instmag3",j,fluxmag3);
            cf = catcore5[j]/exptime;
            if (cf < 1.0)
                cf = 1.0;
            fluxmag5 = 2.5*log10((double)cf) + apcor5;
            cpl_table_set_float(stds,"instmag5",j,fluxmag5);

            /* Work out a reference magnitude. Cut out stars where the
               magnitude error is too high */

            refmag = p.mag_offset;
            oops = 0;
            for (k = 0; k < p.ncolumns_coleq; k++) {
                if (stdmagptr[k][j] == blankvalue) {
                    oops = 1;
                    break;
                } else if (magerrptr != NULL) {
                    if (magerrptr[k][j] > magerrcut)  {
                        oops = 1;
                        break;
                    }
                }
                refmag += ((p.coleq_coefs)[k]*stdmagptr[k][j]);
            }
            if (oops) 
                continue;
            cpl_table_set_float(stds,"refmag",j,refmag);

            /* Work out zero points and store them away for later */

            dm3 = refmag + fluxmag3 + extinct;
            dm5 = refmag + fluxmag5 + extinct;
            cpl_table_set_float(stds,"dm3",j,dm3);
            cpl_table_set_float(stds,"dm5",j,dm5);
            resim3[nresim] = dm3;
            resim5[nresim++] = dm5;
            ebmvall[nresall] = ebmv[j];
            resall3[nresall] = dm3;
            resall5[nresall++] = dm5;
            if (nresall == nalloc_resall) {
                nalloc_resall += INITALLOC;
                resall3 = cpl_realloc(resall3,nalloc_resall*sizeof(float));
                resall5 = cpl_realloc(resall5,nalloc_resall*sizeof(float));
                ebmvall = cpl_realloc(ebmvall,nalloc_resall*sizeof(float));
            }
        }

        /* Ok, what is the mean zeropoint for this image? */

        if (nresim > 0) {
            (void)casu_medmad(resim3,NULL,(long)nresim,&med3,&mad);
            sig3 = mad*1.48;
            (void)casu_medmad(resim5,NULL,(long)nresim,&med5,&mad);
            sig5 = mad*1.48;
            zps3[ngood] = med3;
            zps5[ngood++] = med5;
            crud = 0;
        } else {
            med3 = 0.0;
            med5 = 0.0;
            sig3 = 1.0;
            sig5 = 1.0;
            crud = 1;
        }

        /* How many stars were cut from the original file? */

        nresimtot += cpl_table_get_nrow(stds);

        /* Delete some workspace */

        freespace(resim3);
        freespace(resim5);

        /* Calculate the limiting magnitudes */
        float lim3 = 0;
        if (! crud) {
            lim3 = casu_calculate_abmag_lim(med3 + 2.5 * log10(exptime) - extinct,
                                            sknoise, rcore, exptime, apcor3, extinct);
            lim5 = med5 - 2.5*log10((5.0*sknoise*2.0*rcore*sqrt(CPL_MATH_PI))/exptime) -
                apcor5 - extinct;
        } else {
            med3 = p.default_zp;
            med5 = p.default_zp;
            lim3 = casu_calculate_abmag_lim(med3 + 2.5 * log10(exptime) - extinct,
                                            sknoise, rcore, exptime, apcor3, extinct);
            lim5 = med5 - 2.5*log10((5.0*sknoise*2.0*rcore*sqrt(CPL_MATH_PI))/exptime) -
                apcor5 - extinct;
        }

        /* Calculate sky brightness */

        skbrt = med3 - 2.5*log10(skylev/(exptime*dx*dx)) - extinct;

        /* Calculate the saturation magnitude */
        const float psf_fwhm =  propertylist_get_float_or_default(ehu_cat,"PSF_FWHM", 1.0f);
        const float mean_sky =  propertylist_get_float_or_default(ehu_cat,"ESO QC MEAN_SKY", 1.0f);
        /*Using image header because HAWKI does not have pixelscale in catalog*/
        const float pixel_scale = propertylist_get_float_or_default(ehu_im,"ESO QC WCS_SCALE", 1.0f);

        const float satmag = casu_calculate_abmag_sat(med3 + 2.5 * log10(exptime) - extinct,
        		65e3f, mean_sky, psf_fwhm, pixel_scale, exptime);

        if (!crud) {
            sumlim += lim3;
            sumsat += satmag;
            sumskbr += skbrt;
            nlim++;
        }

        /* Write out header stuff. First for the image and then for the
           catalogue. NB: QC is not allowed to appear in the catalogue */

        write_hdr_1(ehu_im,nresim,med3,sig3,lim3,med5,sig5,lim5,p.atm_extcoef,
                    extinct,skbrt,0);
        write_hdr_1(ehu_cat,nresim,med3,sig3,lim3,med5,sig5,lim5,p.atm_extcoef,
                    extinct,skbrt,0);
        cpl_propertylist_update_double(ehu_im,"ABMAGSAT",satmag);
        cpl_propertylist_set_comment(ehu_im,"ABMAGSAT",
                                     "Saturation limit for point sources");
        cpl_propertylist_update_double(ehu_cat,"ABMAGSAT",satmag);
        cpl_propertylist_set_comment(ehu_cat,"ABMAGSAT",
                                     "Saturation limit for point sources");
    }

    /* Ok, what is the mean zeropoint for all images? */

    if (nresall > 0) {
        (void)casu_medmad(resall3,NULL,(long)nresall,&med3,&mad);
        sig3 = mad*1.48;
        (void)casu_medmad(resall5,NULL,(long)nresall,&med5,&mad);
        sig5 = mad*1.48;
        (void)casu_medmad(ebmvall,NULL,(long)nresall,&medebv,&mad);
        isrubbish = 0;
    } else {
        med3 = p.default_zp;
        sig3 = p.default_zp_err;
        med5 = 0.0;
        sig5 = 1.0;
        medebv = -1.0;
        isrubbish = 1;
    }
    char const * const fluxcal = isrubbish ? "UNCALIBRATED" : "ABSOLUTE";

    /* If there were good images, then calculate the RMS of the detector
       level zeropoints in both apertures */

    if (ngood > 1) {
        (void)casu_medmad(zps3,NULL,(long)ngood,&med,&mad);
        sig3det = mad*1.48;
        (void)casu_medmad(zps5,NULL,(long)ngood,&med,&mad);
        sig5det = mad*1.48;
    } else {
        sig3det = sig3;
        sig5det = sig5;
    }
    if (nlim > 0) {
        sumlim /= (float)nlim;
        sumskbr /= (float)nlim;
        sumsat /= (float)nlim;
    }

    /* Delete some workspace */

    freespace(resall3);
    freespace(resall5);
    freespace(ebmvall);
    freespace(stdmagptr);
    freespace(magerrptr);
    freespace(zps3);
    freespace(zps5);
    freeimage(schl_n);
    freeimage(schl_s);
    casu_phot_close(&p);

    /* Write these results to the header of the images and the catalogues. 
       First the images */

    for (i = 0; i < nimages; i++) {
        im = images[i];
        ehu_im = casu_fits_get_ehu(im);
        ehu_cat = casu_tfits_get_ehu(cats[i]);
        phu_im = casu_fits_get_phu(im);
        phu_cat = casu_tfits_get_phu(cats[i]);

        cpl_propertylist_update_string(phu_im,"FLUXCAL",fluxcal);
        cpl_propertylist_set_comment(phu_im,"FLUXCAL",
                                     "Certifies the validity of PHOTZP");
        cpl_propertylist_update_string(phu_cat,"FLUXCAL",fluxcal);
        cpl_propertylist_set_comment(phu_cat,"FLUXCAL",
                                     "Certifies the validity of PHOTZP");

        /*According to the PHASE 3 standard PHOTZP has to absorb the exposure
         * time and the extinciton*/
        exptime = 1.0; /* Assumption in case of missing keywords */
        extinct = 0.0; /* Assumption in case of missing keywords */
        exptime = (float)cpl_propertylist_get_double(phu_im,expkey);
        extinct = cpl_propertylist_get_float(ehu_im,"ESO DRS EXTINCT");
        double photzp = 0.0;
        if(med3 != 0.0)
            photzp = med3 + 2.5*log10(exptime) - extinct;

        cpl_propertylist_update_int(ehu_im,"ESO DRS MAGNZPTALL",nresall);
        cpl_propertylist_set_comment(ehu_im,"ESO DRS MAGNZPTALL",
                                     "number of stars in all magzpt calc");
        cpl_propertylist_update_double(ehu_im,"ESO DRS ZPALL1",med3);
        cpl_propertylist_set_comment(ehu_im,"ESO DRS ZPALL1",
                                     "[mag] zeropoint 1*rcore all images");
        cpl_propertylist_update_double(ehu_im,"ESO DRS ZPSIGALL1",sig3);
        cpl_propertylist_set_comment(ehu_im,"ESO DRS ZPSIGALL1",
                                     "[mag] zeropoint sigma 1*rcore all images");
        cpl_propertylist_update_double(ehu_im,"ESO DRS ZPALL2",med5);
        cpl_propertylist_set_comment(ehu_im,"ESO DRS ZPALL2",
                                     "[mag] zeropoint 2*rcore all images");
        cpl_propertylist_update_double(ehu_im,"ESO DRS ZPSIGALL2",sig5);
        cpl_propertylist_set_comment(ehu_im,"ESO DRS ZPSIGALL2",
                                     "[mag] zeropoint sigma 2*rcore all images");
        cpl_propertylist_update_double(ehu_im,"ESO DRS MEDEBV",medebv);
        cpl_propertylist_set_comment(ehu_im,"ESO DRS MEDEBV",
                                     "[mag] median galactic colour excess");
        cpl_propertylist_update_double(ehu_im,"ESO DRS SIGDET1",sig3det);
        cpl_propertylist_set_comment(ehu_im,"ESO DRS SIGDET1",
                                     "[mag] sigma det-level zpt 1*rcore");
        cpl_propertylist_update_double(ehu_im,"ESO DRS SIGDET2",sig5det);
        cpl_propertylist_set_comment(ehu_im,"ESO DRS SIGDET2",
                                     "[mag] sigma det-level zpt 2*rcore");
        cpl_propertylist_update_double(ehu_im,"ESO QC MAGZPT",med3);
        cpl_propertylist_set_comment(ehu_im,"ESO QC MAGZPT",
                                     "[mag] photometric zeropoint");
        cpl_propertylist_update_double(ehu_im,"ESO QC MAGZERR",sig3det);
        cpl_propertylist_set_comment(ehu_im,"ESO QC MAGZERR",
                                     "[mag] photometric zeropoint error");
        cpl_propertylist_update_double(ehu_im,"ESO QC SKYBRIGHT",sumskbr);
        cpl_propertylist_set_comment(ehu_im,"ESO QC SKYBRIGHT",
                                     "[mag/arcsec**2] sky brightness");
        cpl_propertylist_update_double(ehu_im,"PHOTZP",photzp);
        cpl_propertylist_set_comment(ehu_im,"PHOTZP",
                                     "[mag] photometric zeropoint");
        cpl_propertylist_update_double(ehu_im,"PHOTZPER",sig3det);
        cpl_propertylist_set_comment(ehu_im,"PHOTZPER",
                                     "[mag] uncertainty on PHOTZP");
        cpl_propertylist_update_int(ehu_im,"ESO QC MAGNZPT",nresimtot);
        cpl_propertylist_set_comment(ehu_im,"ESO QC MAGNZPT",
                                     "number of stars in magzpt calc");
        cpl_propertylist_update_bool(ehu_im,"ZPFUDGED",isrubbish);
        cpl_propertylist_set_comment(ehu_im,"ZPFUDGED",
                                     "TRUE if the ZP not derived from stds");
        if (dodgy[i]) {
            cpl_propertylist_update_double(ehu_im,"ABMAGSAT",sumsat);
            cpl_propertylist_set_comment(ehu_im,"ABMAGSAT",
                                         "Saturation limit for point sources");
            cpl_propertylist_update_double(ehu_im,"ABMAGLIM",sumlim);
            cpl_propertylist_set_comment(ehu_im,"ABMAGLIM",
                                         "[mag] 5 sigma limiting mag");
            cpl_propertylist_update_double(ehu_im,"ESO QC LIMITING_MAG",sumlim);
            cpl_propertylist_set_comment(ehu_im,"ESO QC LIMITING_MAG",
                                         "[mag] 5 sigma limiting mag.");
        }
        
        /* Now the catalogues */

        cpl_propertylist_update_int(ehu_cat,"ESO DRS MAGNZPTALL",nresall);
        cpl_propertylist_set_comment(ehu_cat,"ESO DRS MAGNZPTALL",
                                     "number of stars in all magzpt calc");
        cpl_propertylist_update_double(ehu_cat,"ESO DRS ZPALL1",med3);
        cpl_propertylist_set_comment(ehu_cat,"ESO DRS ZPALL1",
                                     "[mag] zeropoint 1*rcore all group images");
        cpl_propertylist_update_double(ehu_cat,"ESO DRS ZPSIGALL1",sig3);
        cpl_propertylist_set_comment(ehu_cat,"ESO DRS ZPSIGALL1",
                                     "[mag] zeropoint sigma 1*rcore all group images");
        cpl_propertylist_update_double(ehu_cat,"ESO DRS ZPALL2",med5);
        cpl_propertylist_set_comment(ehu_cat,"ESO DRS ZPALL2",
                                     "[mag] zeropoint 2*rcore all group images");
        cpl_propertylist_update_double(ehu_cat,"ESO DRS ZPSIGALL2",sig5);
        cpl_propertylist_set_comment(ehu_cat,"ESO DRS ZPSIGALL2",
                                     "[mag] zeropoint sigma 2*rcore all group images");
        cpl_propertylist_update_double(ehu_cat,"ESO DRS MEDEBV",medebv);
        cpl_propertylist_set_comment(ehu_cat,"ESO DRS MEDEBV",
                                     "[mag] median galactic colour excess");
        cpl_propertylist_update_double(ehu_cat,"ESO DRS SIGDET1",sig3det);
        cpl_propertylist_set_comment(ehu_cat,"ESO DRS SIGDET1",
                                     "[mag] sigma det-level zpt 1*rcore");
        cpl_propertylist_update_double(ehu_cat,"ESO DRS SIGDET2",sig5det);
        cpl_propertylist_set_comment(ehu_cat,"ESO DRS SIGDET2",
                                     "[mag] sigma det-level zpt 2*rcore");
        cpl_propertylist_update_double(ehu_cat,"ESO QC MAGZPT",med3);
        cpl_propertylist_set_comment(ehu_cat,"ESO QC MAGZPT",
                                     "[mag] photometric zeropoint");
        cpl_propertylist_update_double(ehu_cat,"ESO QC MAGZERR",sig3det);
        cpl_propertylist_set_comment(ehu_cat,"ESO QC MAGZERR",
                                     "[mag] photometric zeropoint error");
        cpl_propertylist_update_double(ehu_cat,"ESO QC SKYBRIGHT",sumskbr);
        cpl_propertylist_set_comment(ehu_cat,"ESO QC SKYBRIGHT",
                                     "[mag/arcsec**2] sky brightness");
        cpl_propertylist_update_double(ehu_cat,"PHOTZP",photzp);
        cpl_propertylist_set_comment(ehu_cat,"PHOTZP",
                                     "[mag] photometric zeropoint");
        cpl_propertylist_update_double(ehu_cat,"PHOTZPER",sig3det);
        cpl_propertylist_set_comment(ehu_cat,"PHOTZPER",
                                     "[mag] uncertainty on PHOTZP");
        cpl_propertylist_update_int(ehu_cat,"ESO QC MAGNZPT",nresimtot);
        cpl_propertylist_set_comment(ehu_cat,"ESO QC MAGNZPT",
                                     "number of stars in magzpt calc");
        cpl_propertylist_update_bool(ehu_cat,"ZPFUDGED",isrubbish);
        cpl_propertylist_set_comment(ehu_cat,"ZPFUDGED",
                                     "TRUE if the ZP not derived from stds");
        if (dodgy[i]) {
            cpl_propertylist_update_double(ehu_cat,"ABMAGSAT",sumsat);
            cpl_propertylist_set_comment(ehu_cat,"ABMAGSAT",
                                         "Saturation limit for point sources");
            cpl_propertylist_update_double(ehu_cat,"ABMAGLIM",sumlim);
            cpl_propertylist_set_comment(ehu_cat,"ABMAGLIM",
                                         "[mag] 5 sigma limiting mag");
            cpl_propertylist_update_double(ehu_cat,"ESO QC LIMITING_MAG",sumlim);
            cpl_propertylist_set_comment(ehu_cat,"ESO QC LIMITING_MAG",
                                         "[mag] 5 sigma limiting mag.");
        }

        /* Update the matched standards headers */

        if (mstds[i] != NULL) {
            phu_cat = casu_tfits_get_phu(mstds[i]);
            cpl_propertylist_update_string(phu_cat,"FLUXCAL",fluxcal);
            cpl_propertylist_set_comment(phu_cat,"FLUXCAL",
                                         "Certifies the validity of PHOTZP");

            ehu_cat = casu_tfits_get_ehu(mstds[i]);
            cpl_propertylist_update_int(ehu_cat,"ESO DRS MAGNZPTALL",nresall);
            cpl_propertylist_set_comment(ehu_cat,"ESO DRS MAGNZPTALL",
                                         "number of stars in all magzpt calc");
            cpl_propertylist_update_double(ehu_cat,"ESO DRS ZPALL1",med3);
            cpl_propertylist_set_comment(ehu_cat,"ESO DRS ZPALL1",
                                         "[mag] zeropoint 1*rcore all group images");
            cpl_propertylist_update_double(ehu_cat,"ESO DRS ZPSIGALL1",sig3);
            cpl_propertylist_set_comment(ehu_cat,"ESO DRS ZPSIGALL1",
                                         "[mag] zeropoint sigma 1*rcore all group images");
            cpl_propertylist_update_double(ehu_cat,"ESO DRS ZPALL2",med5);
            cpl_propertylist_set_comment(ehu_cat,"ESO DRS ZPALL2",
                                         "[mag] zeropoint 2*rcore all group images");
            cpl_propertylist_update_double(ehu_cat,"ESO DRS ZPSIGALL2",sig5);
            cpl_propertylist_set_comment(ehu_cat,"ESO DRS ZPSIGALL2",
                                         "[mag] zeropoint sigma 2*rcore all group images");
            cpl_propertylist_update_double(ehu_cat,"ESO DRS MEDEBV",medebv);
            cpl_propertylist_set_comment(ehu_cat,"ESO DRS MEDEBV",
                                         "[mag] median galactic colour excess");
            cpl_propertylist_update_double(ehu_cat,"ESO DRS SIGDET1",sig3det);
            cpl_propertylist_set_comment(ehu_cat,"ESO DRS SIGDET1",
                                         "[mag] sigma det-level zpt 1*rcore");
            cpl_propertylist_update_double(ehu_cat,"ESO DRS SIGDET2",sig5det);
            cpl_propertylist_set_comment(ehu_cat,"ESO DRS SIGDET2",
                                         "[mag] sigma det-level zpt 2*rcore");
            cpl_propertylist_update_double(ehu_cat,"ESO QC MAGZPT",med3);
            cpl_propertylist_set_comment(ehu_cat,"ESO QC MAGZPT",
                                         "[mag] photometric zeropoint");
            cpl_propertylist_update_double(ehu_cat,"ESO QC MAGZERR",sig3det);
            cpl_propertylist_set_comment(ehu_cat,"ESO QC MAGZERR",
                                         "[mag] photometric zeropoint error");
            cpl_propertylist_update_double(ehu_cat,"ESO QC SKYBRIGHT",sumskbr);
            cpl_propertylist_set_comment(ehu_cat,"ESO QC SKYBRIGHT",
                                         "[mag/arcsec**2] sky brightness");
            cpl_propertylist_update_double(ehu_cat,"PHOTZP",photzp);
            cpl_propertylist_set_comment(ehu_cat,"PHOTZP",
                                         "[mag] photometric zeropoint");
            cpl_propertylist_update_double(ehu_cat,"PHOTZPER",sig3det);
            cpl_propertylist_set_comment(ehu_cat,"PHOTZPER",
                                         "[mag] uncertainty on PHOTZP");
            cpl_propertylist_update_int(ehu_cat,"ESO QC MAGNZPT",nresimtot);
            cpl_propertylist_set_comment(ehu_cat,"ESO QC MAGNZPT",
                                         "number of stars in magzpt calc");
            cpl_propertylist_update_bool(ehu_cat,"ZPFUDGED",isrubbish);
            cpl_propertylist_set_comment(ehu_cat,"ZPFUDGED",
                                         "TRUE if the ZP not derived from stds");
        }
    }
    freespace(dodgy);

    /* Get out of here */

    GOOD_STATUS

}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_remove_mag_outside_range
    \par Purpose:
        Remove from mstds the stars having magnitude outside low_mag and high_mag
    \par Description:
        Remove from mstds the stars having magnitude outside low_mag and high_mag
    \par Language:
        C
    \param mstds
        The matched standards tables list
    \param nimages
        Number of elements in the matched standards tables list
    \param filt
        The name of the filter for the observation
    \param phottab
        The relevant table for photometric calibration for a given standard
        source
    \param low_mag
        The lower magnitude bound. Stars brigher than this are removed
    \param high_mag
        The upper magnitude bound. Stars fainter than this are removed
    \return The number of removed stars, -1 in case of error
    \author
        Matteo Salmistraro, ESO
*/
/*---------------------------------------------------------------------------*/
extern int casu_remove_mag_outside_range(casu_tfits **mstds, const int nimages,
                                        char *filt, cpl_table *phottab,
                                        const float low_mag, const float high_mag)
{

    photstrct p;

    if (casu_phot_open(phottab,filt,&p) != CASU_OK)
        return -1;

    int i, j;
    int num_erased = 0;
    for (i = 0; i < nimages; i++) {
        casu_tfits * ctfits = mstds[i];
        cpl_table * stds = casu_tfits_get_table(ctfits);
        cpl_table_unselect_all(stds);
        for (j = 0; j < p.ncolumns_coleq; j++) {
            const char * clnm_name = (p.coleq_columns)[j];
            if(!cpl_table_has_column(stds, clnm_name))
            {
                casu_phot_close(&p);
                return -1;
            }
            cpl_table_or_selected_float(stds, clnm_name, CPL_LESS_THAN, low_mag);
            cpl_table_or_selected_float(stds, clnm_name, CPL_GREATER_THAN, high_mag);
        }
        num_erased += cpl_table_count_selected(stds);
        cpl_table_erase_selected(stds);
        cpl_table_select_all(stds);
    }

    casu_phot_close(&p);
    return num_erased;
}


/*---------------------------------------------------------------------------*/
/**
    \par Name:
        pixsize
    \par Purpose:
        Work out a rough pixel size
    \par Description:
        Given the values of the X elements of the CD matrix in an input
        FITS header, work out a rough pixel size in arcseconds.
    \par Language:
        C
    \param plist
        The input propertylist representing an image extension FITS header
    \return The value of the pixel size in arcseconds
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static double pixsize (cpl_propertylist *plist) {
    cpl_wcs *wcs;
    double *cd,pix;

    wcs = cpl_wcs_new_from_propertylist(plist);
    cd = cpl_matrix_get_data((cpl_matrix *)cpl_wcs_get_cd(wcs));
    pix = 3600.0*sqrt(fabs(cd[0]*cd[3] - cd[1]*cd[2]));
    cpl_wcs_delete(wcs);
    return(pix);
}
    
/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_phot_open
    \par Purpose:
        Open and parse the photcal table
    \par Description:
        Check the validity of the input photcal table. Select the row that
        represents the input filter. Parse the values in that row and fill
        in the relevant parts of the data structure.
    \par Language:
        C
    \param phottab
        The input input photometric calibration table.
    \param filt
        The input filter name
    \param p
        The photometric data structure
    \retval CASU_OK 
        if all went ok.
    \retval CASU_FATAL 
        if there were any errors in the table
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static int casu_phot_open(cpl_table *phottab, char *filt, photstrct *p) {
    const char *fctid = "casu_phot_open";
    int ns,nerr,null,nr;
    cpl_table *subset;
    char **filts;
    const char *req_cols[9] = {"filter_name","atm_extcoef","mag_offset",
                               "coleq_columns","coleq_errcols","coleq_coefs",
                               "gal_extcoef","default_zp","default_zp_err"};

    /* Initialise a few things */

    p->coleq_coefs = NULL;
    p->coleq_columns = NULL;
    p->coleq_errcols = NULL;
    p->ncolumns_coleq = 0;
    p->mag_offset = 0.0;

    /* Check the table and make sure it has all the required columns */

    nerr = 0;
    for (ns = 0; ns < 9; ns++) {
        if (! cpl_table_has_column(phottab,req_cols[ns])) {
            cpl_msg_error(fctid,"Photometry table missing column %s",
                          req_cols[ns]);
            nerr++;
        }
    }
    if (nerr > 0)
        return(CASU_FATAL);

    /* Search the table and find the rows that matches the filter. NB: we
       can't use cpl_table_and_selected_string because it matches by regular
       expression rather than exact matching! So just loop through and choose
       the first one that matches exactly. There shouldn't be more than one! */

    filts = cpl_table_get_data_string(phottab,"filter_name");
    nr = cpl_table_get_nrow(phottab);
    nerr = 1;
    for (ns = 0; ns < nr; ns++) {
        if (strncmp(filts[ns],filt,16) == 0) {
            nerr = 0;
            break;
        }
    }
    if (nerr == 1) {
        cpl_msg_error(fctid,"Unable to match photometry table to filter %s",
                      filt);
        return(CASU_FATAL);
    }
    cpl_table_and_selected_window(phottab,ns,1);

    /* Read the information from the selected row */

    subset = cpl_table_extract_selected(phottab);
    p->filter_name = (char *)cpl_table_get_string(subset,"filter_name",0);
    p->atm_extcoef = cpl_table_get_float(subset,"atm_extcoef",0,&null);
    p->mag_offset = cpl_table_get_float(subset,"mag_offset",0,&null);
    p->gal_extcoef = cpl_table_get_float(subset,"gal_extcoef",0,&null);
    p->default_zp = cpl_table_get_float(subset,"default_zp",0,&null);
    p->default_zp_err = cpl_table_get_float(subset,"default_zp_err",0,&null);
    if (extract_columns(subset,p) != CASU_OK) {
        freetable(subset);
        return(CASU_FATAL);
    }
    if (extract_coleq(subset,p) != CASU_OK) {
        freetable(subset);
        return(CASU_FATAL);
    }
    freetable(subset);

    /* Get out of here */

    return(CASU_OK);
}
    
/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_phot_close
    \par Purpose:
        Free the workspace associated with the photcal table
    \par Description:
        Free the workspace associated with the photcal table
    \par Language:
        C
    \param p
        The photometric data structure
    \return Nothing
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static void casu_phot_close(photstrct *p) {
    int j;

    for (j = 0; j < p->ncolumns_coleq; j++) {
        freespace((p->coleq_columns)[j]);
        freespace((p->coleq_errcols)[j]);
    }
    freespace(p->coleq_columns);
    freespace(p->coleq_errcols);
    freespace(p->coleq_coefs);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        extract_columns
    \par Purpose:
        Read the 'columns' column from the table and parse.
    \par Description:
        Read the 'columns' column from the table and parse.
    \par Language:
        C
    \param tab
        The input photcal table
    \param p
        The photometric data structure
    \retval CASU_OK 
        Always
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static int extract_columns(cpl_table *tab, photstrct *p) {
    int nv,i,j,k;
    char *v,*w,**z;
    const char *cols[2] = {"coleq_columns","coleq_errcols"};

    /* Loop for the columns */

    for (k = 0; k < 2; k++) {

        /* Get the relevant value from the table */

        v = cpl_strdup(cpl_table_get_string(tab,cols[k],0));
        
        /* Count the number of commas in the string to see how many 
           columns there are */

        nv = 1;
        j = strlen(v);
        for (i = 0; i < j; i++)
            if (v[i] == ',')
                nv++;
        if (k == 0) {
            p->ncolumns_coleq = nv;
        } else {
            if (nv == 0 || nv != p->ncolumns_coleq) {
                p->coleq_errcols = NULL;
                freespace(v);
                return(CASU_OK);
            }
        }
        
        /* Now parse the string into the column names */

        z = cpl_malloc(nv*sizeof(char *));
        char *saveptr = NULL;
        for (i = 0; i < nv; i++) {
            if (i == 0) 
                w = strtok_r(v,",",&saveptr);
            else
                w = strtok_r(NULL,",",&saveptr);
            z[i] = cpl_strdup(w);
        }
        if (k == 0) 
            p->coleq_columns = z;    
        else
            p->coleq_errcols = z;
        freespace(v);
    }
    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        extract_coleq
    \par Purpose:
        Extract the colour equation coefficients
    \par Description:
        Extract the colour equation coefficients
    \par Language:
        C
    \param tab
        The input photcal table
    \param p
        The photometric data structure
    \retval CASU_OK 
        Always
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static int extract_coleq(cpl_table *tab, photstrct *p) {
    int nv,i,j;
    char *v,*w;
    float *z;

    /* Get the relevant value from the table */

    v = cpl_strdup(cpl_table_get_string(tab,"coleq_coefs",0));

    /* Count the number of commas in the string to see how many 
       columns there are */

    nv = 1;
    j = strlen(v);
    for (i = 0; i < j; i++)
        if (v[i] == ',')
            nv++;

    /* Now parse the string into the colour equation values */

    z = cpl_malloc(nv*sizeof(float));
    char *saveptr1 = NULL;
    for (i = 0; i < nv; i++) {
        if (i == 0) 
            w = strtok_r(v,",", &saveptr1);
        else
            w = strtok_r(NULL,",", &saveptr1);
        z[i] = (float)atof(w);
    }
    p->coleq_coefs = z;
    freespace(v);
    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        write_hdr_1
    \par Purpose:
        Write photometric calibration info to a header
    \par Description:
        Write the results of the photometric calibration to the propertylist
        that corresponds to the extension header of an image
    \par Language:
        C
    \param pl
        The input propertylist
    \param nresim
        The number of stars used in the photometric solution
    \param med3
        The median zeropoint through the third aperture (1x rcore)
    \param sig3
        The sigma zeropoint through the third aperture (1x rcore)
    \param lim3
        The limiting magnitude through the third aperture (1x rcore)
    \param med5
        The median zeropoint through the fifth aperture (2x rcore)
    \param sig5
        The sigma zeropoint through the fifth aperture (2x rcore)
    \param lim5
        The limiting magnitude through the fifth aperture (2x rcore)
    \param extcoef
        The atmospheric extinction coefficient
    \param extinct
        The atmospheric extinction value used
    \param skybrt
        The sky brightness estimate
    \param doqc
        If set, then some of these results are written to ESO QC
        header values
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static void write_hdr_1(cpl_propertylist *pl, int nresim, float med3, 
                        float sig3, float lim3, float med5, float sig5, 
                        float lim5, float extcoef, float extinct, float skybrt,
                        int doqc) {

    /* Write these results to the header of the image and the catalogue.
       First the QC headers for the image. These are not allowed to be
       copied into the catalogue headers...*/

    if (doqc) {
        cpl_propertylist_update_double(pl,"ESO QC MAGZPT",med3);
        cpl_propertylist_set_comment(pl,"ESO QC MAGZPT",
                                     "[mag] photometric zeropoint");
        cpl_propertylist_update_double(pl,"ESO QC MAGZERR",sig3);
        cpl_propertylist_set_comment(pl,"ESO QC MAGZERR",
                                     "[mag] photometric zeropoint error");
        cpl_propertylist_update_int(pl,"ESO QC MAGNZPT",nresim);
        cpl_propertylist_set_comment(pl,"ESO QC MAGNZPT",
                                     "number of stars in magzpt calc");
        cpl_propertylist_update_double(pl,"ESO QC SKYBRIGHT",skybrt);
        cpl_propertylist_set_comment(pl,"ESO QC SKYBRIGHT",
                                     "[mag/arcsec**2] sky brightness");
    }

    /* Now the DRS headers for the image */

    cpl_propertylist_update_int(pl,"ESO DRS MAGNZPTIM",nresim);
    cpl_propertylist_set_comment(pl,"ESO DRS MAGNZPTIM",
                                 "number of stars in image magzpt calc");

    cpl_propertylist_update_double(pl,"ESO DRS ZPIM1",med3);
    cpl_propertylist_set_comment(pl,"ESO DRS ZPIM1",
                                 "[mag] zeropoint 1*rcore this image only");
    cpl_propertylist_update_double(pl,"ESO DRS ZPSIGIM1",sig3);
    cpl_propertylist_set_comment(pl,"ESO DRS ZPSIGIM1",
                                 "[mag] zeropoint sigma 1*rcore this image only");
    cpl_propertylist_update_double(pl,"ESO DRS LIMIT_MAG1",lim3);
    cpl_propertylist_set_comment(pl,"ESO DRS LIMIT_MAG1",
                                 "[mag] 5 sigma limiting mag 1*rcore.");
    cpl_propertylist_update_double(pl,"ABMAGLIM",lim3);
    cpl_propertylist_set_comment(pl,"ABMAGLIM",
                                 "[mag] 5 sigma limiting mag");
    cpl_propertylist_update_double(pl,"ESO QC LIMITING_MAG",lim3);
    cpl_propertylist_set_comment(pl,"ESO QC LIMITING_MAG",
                                 "[mag] 5 sigma limiting mag.");
    cpl_propertylist_update_double(pl,"ESO DRS ZPIM2",med5);
    cpl_propertylist_set_comment(pl,"ESO DRS ZPIM2",
                                 "[mag] zeropoint 2*rcore this image only");
    cpl_propertylist_update_double(pl,"ESO DRS ZPSIGIM2",sig5);
    cpl_propertylist_set_comment(pl,"ESO DRS ZPSIGIM2",
                                 "[mag] zeropoint sigma 2*rcore this image only");
    cpl_propertylist_update_double(pl,"ESO DRS LIMIT_MAG2",lim5);
    cpl_propertylist_set_comment(pl,"ESO DRS LIMIT_MAG2",
                                 "[mag] 5 sigma limiting mag core5.");
    cpl_propertylist_update_double(pl,"ESO DRS EXTCOEF",extcoef);
    cpl_propertylist_set_comment(pl,"ESO DRS EXTCOEF",
                                 "[mag] Assumed extinction coefficient");
    cpl_propertylist_update_double(pl,"ESO DRS EXTINCT",extinct);
    cpl_propertylist_set_comment(pl,"ESO DRS EXTINCT",
                                 "[mag] Assumed extinction");
    cpl_propertylist_update_double(pl,"ESO DRS SKYBRIGHT",skybrt);
    cpl_propertylist_set_comment(pl,"ESO DRS SKYBRIGHT",
                                 "[mag/arcsec**2] sky brightness");
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        modifytab
    \par Purpose:
        Modify the matched standards table
    \par Description:
        Modify the matched standards table to add some extra information
        about the galactic reddening and absorption. Also modify the fluxes
        in the table to reflect the distortion within the camera optics implied
        by the world coordinate system. Finally add some columns to help
        with diagnostics.
    \par Language:
        C
    \param p
        A structure holding the colour equation information
    \param intab
        The input matched standards table
    \param im
        The input image 
    \param minstars
        The minimum number of stars for a solutino
    \param schl_n
        The northern Schlegel map image
    \param schl_s
        The southern Schlegel map image
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static void modifytab(photstrct *p, cpl_table *intab, casu_fits *im, 
                      int minstars, cpl_image *schl_n, cpl_image *schl_s) {
    float *ebmv,ebv,*galex;
    float *ra = NULL, *dec = NULL;
    double *rra = NULL, *ddec = NULL;
    int i,nrows;
    const char *fctid = "modifytab";

    /* Count how many we've got. If there are too few left, then get out of
       here and signal an error */

    nrows = (int)cpl_table_get_nrow(intab);
    if (nrows < minstars) {
        cpl_msg_error(fctid,"Matched standards has too few stars");
        return;
    }

    /* Now create two more columns to include the estimated colour excess
       as E(B-V) from the Schlegel maps, and an estimate of the absorption
       in the relevant band for the observations */

    if (! cpl_table_has_column(intab,"ebmv"))
        cpl_table_duplicate_column(intab,"ebmv",intab,"Peak_height");
    if (! cpl_table_has_column(intab,"galextinct"))
        cpl_table_duplicate_column(intab,"galextinct",intab,"Peak_height");

    /* Add some columns for diagnostics now */

    if (! cpl_table_has_column(intab,"instmag3")) {
        cpl_table_duplicate_column(intab,"instmag3",intab,"Peak_height");
        cpl_table_set_column_invalid(intab,"instmag3",0,nrows);
    }
    if (! cpl_table_has_column(intab,"instmag5")) {
        cpl_table_duplicate_column(intab,"instmag5",intab,"Peak_height");
        cpl_table_set_column_invalid(intab,"instmag5",0,nrows);
    }
    if (! cpl_table_has_column(intab,"refmag")) {
        cpl_table_duplicate_column(intab,"refmag",intab,"Peak_height");
        cpl_table_set_column_invalid(intab,"refmag",0,nrows);
    }
    if (! cpl_table_has_column(intab,"dm3")) {
        cpl_table_duplicate_column(intab,"dm3",intab,"Peak_height");
        cpl_table_set_column_invalid(intab,"dm3",0,nrows);
    }
    if (! cpl_table_has_column(intab,"dm5")) {
        cpl_table_duplicate_column(intab,"dm5",intab,"Peak_height");
        cpl_table_set_column_invalid(intab,"dm5",0,nrows);
    }

    /* Get the data arrays for these new columns and use the Schlegel maps
       to work out values for them */

    ebmv = cpl_table_get_data_float(intab,"ebmv");
    galex = cpl_table_get_data_float(intab,"galextinct");
    if (cpl_table_get_column_type(intab,"RA") == CPL_TYPE_FLOAT) {
        ra = cpl_table_get_data_float(intab,"RA");
        dec = cpl_table_get_data_float(intab,"Dec");
    } else {
        rra = cpl_table_get_data_double(intab,"RA");
        ddec = cpl_table_get_data_double(intab,"Dec");
    }
    for (i = 0; i < nrows; i++) {
        if (schl_n == NULL || schl_s == NULL) {
            ebv = 0.0;
        } else {
            if (cpl_table_get_column_type(intab,"RA") == CPL_TYPE_FLOAT && ra != NULL && dec != NULL) {
                getextinct(schl_n,schl_s,(double)(ra[i]),(double)(dec[i]),&ebv);
            } else if (cpl_table_get_column_type(intab,"RA") == CPL_TYPE_DOUBLE && rra != NULL && ddec != NULL){
                getextinct(schl_n,schl_s,rra[i],ddec[i],&ebv);
            } else {
                ebv = 0.0;
            }
        }
        ebmv[i] = ebv;
        galex[i] = p->gal_extcoef*ebv;
    }

    /* Do the photometric distortion of the input fluxes */

    photdistort(im,intab);

    /* Get out of here */

    return;
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        photdistort
    \par Purpose:
        Modify the fluxes of the stars to take into account photometric 
        distortion
    \par Description:
        Modify the fluxes of the stars in the matched standards catalogue
        to take into account the variable pixel size that comes from the
        optical distortion in the camera.
    \par Language:
        C
    \param im
        The input image 
    \param outtab
        The input/output table from the matched standards catalogue
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static void photdistort(casu_fits *im, cpl_table *outtab) {
    cpl_wcs *wcs;
    float *flux1,*flux2,*x,*y;
    int i,nrows,isnull;
    const cpl_array *crv;
    double crv1,tand,secd,distcor,ps1,ps2,psc;

    /* Get the WCS descriptor and work out a few convenience variables */

    wcs = cpl_wcs_new_from_propertylist(casu_fits_get_ehu(im));
    crv = cpl_wcs_get_crval(wcs);
    crv1 = DEGRAD*cpl_array_get(crv,0,&isnull);
    tand = tan(DEGRAD*cpl_array_get(crv,1,&isnull));
    secd = sqrt(1.0 + tand*tand);

    /* Get the pixel size (sq arcsec) at the central position */

    psc = pixsize(casu_fits_get_ehu(im));
    ps2 = psc*psc;

    /* Dereference the table data */

    flux1 = cpl_table_get_data_float(outtab,"Aper_flux_3");
    flux2 = cpl_table_get_data_float(outtab,"Aper_flux_5");
    x = cpl_table_get_data_float(outtab,"X_coordinate");
    y = cpl_table_get_data_float(outtab,"Y_coordinate");
    nrows = (int)cpl_table_get_nrow(outtab);

    /* Now work out the distortion for each object */

    for (i = 0; i < nrows; i++) {
        getproj_pixsize(wcs,crv1,tand,secd,(double)x[i],(double)y[i],&ps1);
        distcor = ps1/ps2;
        flux1[i] *= distcor;
        flux2[i] *= distcor;
    }
    freewcs(wcs);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        propertylist_get_float_or_default
    \par Purpose:
        Get the property of the default value
    \par Description:
        Get the property of the default value
    \par Language:
        C
    \param list
        The input propertylist
    \param name
        Tag name
    \param default_val
        Default value to be returned if the propertylist has no value
	\retval the corresponding value in the propertylist or default if not
			available.
	\author
        Matteo Salmistraro, ESO
*/
/*---------------------------------------------------------------------------*/
static float propertylist_get_float_or_default(const cpl_propertylist * list, const char * name,
		const float default_val){

	cpl_boolean value_missing_or_mismatch = !cpl_propertylist_has(list, name);
	value_missing_or_mismatch = value_missing_or_mismatch || (cpl_propertylist_get_type(list, name) != CPL_TYPE_DOUBLE
			&& cpl_propertylist_get_type(list, name) != CPL_TYPE_FLOAT);
	if(value_missing_or_mismatch) {
		cpl_msg_warning(cpl_func, "Unable to extract %s, fallback to %f", name, default_val);
		return default_val;
	}

	return cpl_propertylist_get_float(list, name);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        getproj_pixsize
    \par Purpose:
        Get an approximation of the projected pixel size in the field
    \par Description:
        Work out a pixel size for a location within a distorted field.
    \par Language:
        C
    \param wcs
        The input wcs descriptor
    \param crv1
        The value of CRVAL1 in radians
    \param tand
        The tangent of the central declination
    \param secd
        The secant of the central declination
    \param x
        The X position of the location in question
    \param y
        The Y position of the location in question
    \param ps
        The output pixel size in arcsec**2
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static void getproj_pixsize(cpl_wcs *wcs, double crv1, double tand, 
                            double secd, double x, double y, double *ps) {
    cpl_matrix *xy,*rd;
    cpl_array *stat;
    cpl_vector *xi,*eta;
    int i;
    double offx,offy,ra,dec,xiv,etav,bigd,dx,dy,*xira,*etara,dx1,dx2,dy1,dy2;

    /* First, load up the corners of the input pixel */

    xy = cpl_matrix_new(4,2);
    for (i = 0; i < 4; i++) {
        offx = (i < 2 ? -0.5 : 0.5);
        offy = ((i == 0 || i == 3) ? -0.5 : 0.5);
        cpl_matrix_set(xy,i,0,x+offx);
        cpl_matrix_set(xy,i,1,y+offy);
    }

    /* Do the conversion to RA, Dec */

    cpl_wcs_convert(wcs,xy,&rd,&stat,CPL_WCS_PHYS2WORLD);
    cpl_array_delete(stat);
    cpl_matrix_delete(xy);

    /* Loop for each of the rows */

    xi = cpl_vector_new(4);
    eta = cpl_vector_new(4);
    for (i = 0; i < 4; i++) {
        ra = cpl_matrix_get(rd,i,0);
        dec = cpl_matrix_get(rd,i,1);
        ra *= DEGRAD;
        dec *= DEGRAD;
        ra -= crv1;
        bigd = tan(dec)*tand + cos(ra);
        xiv = RADARCSEC*secd*sin(ra)/bigd;
        etav = RADARCSEC*(tan(dec) - tand*cos(ra))/bigd;
        cpl_vector_set(xi,i,xiv);
        cpl_vector_set(eta,i,etav);
    }   
    cpl_matrix_delete(rd);

    /* Now form the average size of the two sides and work out
       the pixel size */

    xira = cpl_vector_get_data(xi);
    etara = cpl_vector_get_data(eta);
    dx1 = xira[1] - xira[0];
    dx2 = xira[3] - xira[2];
    dy1 = etara[1] - etara[0];
    dy2 = etara[3] - etara[2];
    dy = 0.5*(sqrt(dx1*dx1 + dy1*dy1) + sqrt(dx2*dx2 + dy2*dy2));
    dx1 = xira[1] - xira[2];
    dx2 = xira[0] - xira[3];
    dy1 = etara[1] - etara[2];
    dy2 = etara[0] - etara[3];
    dx = 0.5*(sqrt(dx1*dx1 + dy1*dy1) + sqrt(dx2*dx2 + dy2*dy2));
    *ps = dx*dy;

    /* Tidy and exit */

    cpl_vector_delete(xi);
    cpl_vector_delete(eta);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        getextinct
    \par Purpose:
        Get colour excess estimate from the Schlegel maps
    \par Description:
        Use the Schlegel maps to estimate the colour excess in (B-V) for
        a star at a particular position
    \par Language:
        C
    \param schl_n
        The northern Schlegel map image
    \param schl_s
        The souther Schlegel map image
    \param ra
        The input RA
    \param dec
        The input Declination
    \param ebv
        The output colour excess estimate
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static void getextinct(cpl_image *schl_n, cpl_image *schl_s, double ra,
                       double dec, float *ebv) {
    double gal_l,gal_b,sin_b,cos_l,sin_l;
    cpl_image *schl;
    float x,y,*schl_data,xx,yy,delx,dely;
    int ix,iy,iarg1,iarg2,iarg3,iarg4,nx,ny;

    /* Get the galactic coordinates (radians) for this ra and dec */

    radectolb(ra,dec,2000.0,&gal_l,&gal_b);

    /* Get the relevant Schlegel data */

    schl = (gal_b >= 0.0 ? schl_n : schl_s);
    schl_data = cpl_image_get_data_float(schl);

    /* Get the x,y coordinates for this position. This formula is taken
       from the original Schlegel article */

    sin_b = sin(gal_b);
    cos_l = cos(gal_l);
    sin_l = sin(gal_l);
    if (gal_b >= 0.0) {
        x = 2048.0*sqrt(1.0 - sin_b)*cos_l + 2047.5;
        y = -2048.0*sqrt(1.0 - sin_b)*sin_l + 2047.5;
    } else {
        x = 2048.0*sqrt(1.0 + sin_b)*cos_l + 2047.5;
        y = 2048.0*sqrt(1.0 + sin_b)*sin_l + 2047.5;
    }

    /* Get the pixel and the ones next to it */

    nx = (int)cpl_image_get_size_x(schl);
    ny = (int)cpl_image_get_size_y(schl);
    ix = min(nx-2,max(0,(int)x));
    iy = min(ny-2,max(0,(int)y));
    delx = x - (int)x;
    dely = y - (int)y;
    iarg1 = iy*nx + ix;
    iarg2 = iarg1 + 1;
    iarg3 = iarg1 + nx;
    iarg4 = iarg2 + nx;

    /* Get the values and do the interpolation. */

    xx = (1.0 - delx)*schl_data[iarg1] + delx*schl_data[iarg2];
    yy = (1.0 - delx)*schl_data[iarg3] + delx*schl_data[iarg4];
    *ebv = (1.0 - dely)*xx + dely*yy;
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        radectolb
    \par Purpose:
        Convert (RA,Dec) => (l,b)
    \par Description:
        Convert equatorial coordinates to galactic coordinates
    \par Language:
        C
    \param ra
        The input RA
    \param dec
        The input Declination
    \param epoch
        The equatorial coordinate epoch
    \param gal_l
        The output galactic longitude
    \param gal_b
        The output galactic latitude
    \author
        Jim Lewis, CASU
*/
/*---------------------------------------------------------------------------*/

static void radectolb(double ra, double dec, float epoch, double *gal_l, 
                      double *gal_b) {
    double ti,tf,s,z,h,arcsecrad,ra2,dec2,cosdec1,sindec1,cosra1s,sinra1s;
    double cos_h,sin_h,x1,x2,sin_b,cosdec2,sindec2,costheta,sintheta;
    double cosdra,sindra,ra1,dec1,sinra2,cosra2;
    double a0=282.25,theta=62.6,gal_l0=33.0;
    float equin=1950;

    /* Start by precessing coordinates to 1950 using IAU 1976 precession 
       quuantities */

    ti = (double)(epoch - 2000.0)/100.0;
    tf = (double)(equin - 2000.0)/100.0 - ti;
    s = (2306.2181 - 1.39656*ti - 0.000139*ti*ti)*tf +
        (0.30188 - 0.000344*ti)*tf*tf + 0.17998*tf*tf*tf;
    z = s + (0.79280 + 0.000410*ti)*tf*tf + 0.000205*tf*tf*tf;
    h = (2004.3109 - 0.85330*ti - 0.000217*ti*ti)*tf - 
        (0.42665 + 0.000217*ti)*tf*tf - 0.041833*tf*tf*tf;
    
    /* Convert to radians */

    arcsecrad = 1.0/(CPL_MATH_DEG_RAD*3600.0);
    s *= arcsecrad;
    z *= arcsecrad;
    h *= arcsecrad;
    ra1 = ra/CPL_MATH_DEG_RAD;
    dec1 = dec/CPL_MATH_DEG_RAD;

    /* Apply precession to give new RA and Dec in radians */

    cosdec1 = cos(dec1);
    sindec1 = sin(dec1);
    cosra1s = cos(ra1+s);
    sinra1s = sin(ra1+s);
    cos_h = cos(h);
    sin_h = sin(h);
    dec2 = asin(cosdec1*cosra1s*sin_h + sindec1*cos_h);
    cosdec2 = cos(dec2);
    sinra2 = cosdec1*sinra1s/cosdec2;
    cosra2 = (cosdec1*cosra1s*cos_h - sindec1*sin_h)/cosdec2;
    ra2 = atan2(sinra2,cosra2) + z;

    /* Check to make sure the new RA isn't negative */

    if (ra2 < 0.0)
        ra2 += CPL_MATH_2PI;

    /* Convert galactic reference poles to radians */
    
    a0 /= CPL_MATH_DEG_RAD;
    theta /= CPL_MATH_DEG_RAD;
    gal_l0 /= CPL_MATH_DEG_RAD;

    /* Do the conversion now */

    sindec2 = sin(dec2);
    costheta = cos(theta);
    sintheta = sin(theta);
    cosdra = cos(ra2 - a0);
    sindra = sin(ra2 - a0);
    x1 = cosdec2*cosdra;
    x2 = cosdec2*sindra*costheta + sindec2*sintheta;
    *gal_l = atan2(x2,x1) + gal_l0;
    if (*gal_l < 0)
        *gal_l += CPL_MATH_2PI;
    sin_b = sindec2*costheta - cosdec2*sindra*sintheta;
    *gal_b = asin(sin_b);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_calculate_abmag_lim
    \par Purpose:
        Calculate ABMAGLIM
    \par Description:
        Calculate ABMAGLIM
    \par Language:
        C
    \param zeropoint
        The zeropoint
    \param skynoise
        The sky noise
    \param r_core
        The radius of the core
    \param eff_expt
        The effective exposure time
    \param apcor3
        The aperture correction
	\param extinction
        The extintion
    \author
        Matteo Salmistraro, ESO
*/
/*---------------------------------------------------------------------------*/
extern float
casu_calculate_abmag_lim(const float zeropoint, const float skynoise,
		const float r_core, const float eff_expt, const float apcor3, const float extinction){
	return zeropoint - 2.5f * log10f(5.0f * skynoise * r_core * sqrt(CPL_MATH_PI) / eff_expt) - apcor3 - extinction;
}

extern float
casu_calculate_abmag_sat(const float zeropoint, const float satlev,
		const float mean_sky, const float psf_fwhm, const float pixel_scale,
		const float exptime){
	const float val = (CPL_MATH_PI_4 * CPL_MATH_LN2) * (satlev - mean_sky) * powf((psf_fwhm / pixel_scale), 2.0) / exptime;
	return	zeropoint - 2.5f*log10f(val);
}

/**@}*/

/*

$Log: casu_photcal_extinct.c,v $
Revision 1.7  2015/09/30 08:33:06  jim
superficial changes

Revision 1.6  2015/09/14 18:50:07  jim
Added missing ABMAGSAT for when calibration fails

Revision 1.5  2015/09/11 09:13:41  jim
Fixed bug in photometric distortion where pixel size was being calculated
incorrectly

Revision 1.4  2015/08/07 13:06:54  jim
Fixed copyright to ESO

Revision 1.3  2015/08/03 12:45:00  jim
fixed bug in sizing of schlegel maps

Revision 1.2  2015/06/30 17:47:19  jim
Added ability to save default zeropoint and error to header if no fit is
possible

Revision 1.1.1.1  2015/06/12 10:44:32  jim
Initial import

Revision 1.11  2015/06/03 13:10:57  jim
Fixed to remove homegrown WCS routines from distortion correction

Revision 1.10  2015/05/13 11:45:35  jim
Now adds some extra columns into the matched standards catalogue which
might be useful if it is saved

Revision 1.9  2015/02/17 11:22:51  jim
Fixed to test for existence of extra columns

Revision 1.8  2015/02/14 12:33:15  jim
matched standards is now a casu_tfits structure with header info

Revision 1.7  2015/01/29 11:53:14  jim
modified comments and removed extraneous code

Revision 1.6  2014/12/11 12:23:33  jim
new version

Revision 1.5  2014/03/26 15:56:23  jim
Removed globals

Revision 1.4  2013/11/21 09:38:14  jim
detabbed

Revision 1.3  2013-10-24 09:25:54  jim
traps for dummy input extensions

Revision 1.2  2013-09-30 18:11:35  jim
Fixed memory error

Revision 1.1.1.1  2013-08-27 12:07:48  jim
Imported


*/
