/* $Id: test_simulate.c,v 1.34 2009-02-25 15:34:48 hlorch Exp $
 *
 * This file is part of the FORS Library
 * Copyright (C) 2002-2006 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

/*
 * $Author: hlorch $
 * $Date: 2009-02-25 15:34:48 $
 * $Revision: 1.34 $
 * $Name: not supported by cvs2svn $
 */

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

#include <test_simulate.h>

#include <fors_instrument.h>
#include <fors_image.h>
#include <fors_dfs.h>
#include <fors_data.h>
#include <fors_pfits.h>
#include <fors_utils.h>

#include <cpl.h>

#include <math.h>

/* FIXME:   - remove FORS_DATA_STD_* !!!
 *          - replace below in create_std_cat()
 *              1.) by creation of a standard star list, and
 *              2.) and a new function fors_std_cat_test_create_stetson()
 *                  in fors_std_cat.c
 */
const char *const FORS_DATA_STD_MAG[FORS_NUM_FILTER] =
{"U",
 "B",
 //"G",
 "V",  /* G uses V */
 "V",
 "R",
 "I",
 "Z"};

const char *const FORS_DATA_STD_DMAG[FORS_NUM_FILTER] =
{"ERR_U",
 "ERR_B",
 //"ERR_G",
 "ERR_V", /* G uses V */
 "ERR_V",
 "ERR_R",
 "ERR_I",
 "ERR_Z"};

const char *const FORS_DATA_STD_COL[FORS_NUM_FILTER] = 
{"U_B",
 "B_V",
 "B_V",
 "B_V",
 "V_R",
 "V_R",
 "?Z?"};

const char *const FORS_DATA_STD_RA   = "RA";
const char *const FORS_DATA_STD_DEC  = "DEC";
const char *const FORS_DATA_STD_NAME = "OBJECT";

/**
 * @defgroup test_simulate   Simulate data for unit testing
 *
 * Note: The images simulated here are not meant to be realistic.
 * The purpose is to test the software's behaviour on known input
 * before introducing the complexity of real world data.
 */

/**@{*/

static const int det_nx = 400;        /* Unbinned detector pixels */
static const int det_ny = 400;
static const int pres_nx = 5;
static const int binx = 2;
static const int biny = 2;
static const double ron   = 4.0;       /* ADU */
static const double conad = 0.78;   /* e- / ADU */

static const double bias_avg = 200; /* ADU */
static const double dark_avg = 50;  /* ADU */
static const char *const instrume = "fors2";
static const char *const chip_id = "Test chip 234";
static const char *const read_clock = "200Kps/2ports/low_gain";

/**
 * @brief   Frame constructor
 * @param   filename         frame filename
 * @param   tag              frame tag
 * @param   group            frame group
 * @return  newly callocated frame with the given contents
 */
static cpl_frame *
frame_new(const char *filename, const char *tag, cpl_frame_group group)
{
    cpl_frame *f = cpl_frame_new();

    cpl_frame_set_filename(f, filename);
    cpl_frame_set_tag     (f, tag);
    cpl_frame_set_group   (f, group);

    return f;
}

/**
 * @brief   Write FORS standard keywords to simulated header
 * @param   header          to update
 * @param   exptime         exposure time (s), possibly zero
 */
void
create_standard_keys(cpl_propertylist *header, double exptime)
{
    int nx = det_nx / binx;
    int ny = det_ny / biny;

    cpl_propertylist_update_string(header, "ESO DPR TYPE", "some");
    cpl_propertylist_update_string(header, "ESO TPL ID", "tpl id.");
    cpl_propertylist_update_string(header, "ESO INS COLL NAME", "collimator name");
    cpl_propertylist_update_string(header, "ARCFILE", "archive filename");
    
    cpl_propertylist_update_string(header, FORS_PFITS_INSTRUME, instrume);
    cpl_propertylist_update_string(header, FORS_PFITS_FILTER_NAME, "R_SPECIAL");

    cpl_propertylist_update_double(header, FORS_PFITS_AIRMASS_START, 1.156);    
    cpl_propertylist_update_double(header, FORS_PFITS_AIRMASS_END  , 1.619);    
    
    cpl_propertylist_update_int   (header, "HIERARCH ESO DET CHIP1 NX", nx);
    cpl_propertylist_update_int   (header, "HIERARCH ESO DET CHIP1 NY", ny);
    cpl_propertylist_update_int   (header, "HIERARCH ESO DET OUT1 X", 1);
    cpl_propertylist_update_int   (header, "HIERARCH ESO DET OUT1 Y", 1);
    cpl_propertylist_update_double(header, "HIERARCH ESO DET CHIP1 PSZX", 15.0);
    cpl_propertylist_update_double(header, "HIERARCH ESO DET CHIP1 PSZY", 15.0);
    cpl_propertylist_update_int   (header, FORS_PFITS_DET_NY, ny);
    cpl_propertylist_update_int   (header, FORS_PFITS_DET_NX, nx);
    cpl_propertylist_update_int   (header, FORS_PFITS_DET_NY, ny);
    cpl_propertylist_update_int   (header, FORS_PFITS_BINX, binx);
    cpl_propertylist_update_int   (header, FORS_PFITS_BINY, biny);
    cpl_propertylist_update_int   (header, FORS_PFITS_OVERSCANX, 0);
    cpl_propertylist_update_int   (header, FORS_PFITS_OVERSCANY, 0);
    cpl_propertylist_update_int   (header, FORS_PFITS_PRESCANX, pres_nx);
    cpl_propertylist_update_int   (header, FORS_PFITS_PRESCANY, 0);

    cpl_propertylist_update_double(header, FORS_PFITS_PIXSCALE, 0.126);
    
    cpl_propertylist_update_int(header, FORS_PFITS_OUTPUTS, 1);
    cpl_propertylist_update_double(header, FORS_PFITS_CONAD[0], conad);
    cpl_propertylist_update_double(header, "HIERARCH ESO DET OUT1 GAIN", 1./conad);
    
    /* Convert RON to e- units */
    cpl_propertylist_update_double(header, FORS_PFITS_RON[0], ron*conad);
    cpl_propertylist_update_double(header, FORS_PFITS_EXPOSURE_TIME, exptime);

    cpl_propertylist_update_string(header, FORS_PFITS_CHIP_ID, chip_id);
    cpl_propertylist_update_string(header, FORS_PFITS_READ_CLOCK, read_clock);
    
    /* WCS info, fine tuned to match simulated catalogue */
    {
        struct {
            const char *name;
            double value;
        } data[] = {
            {"CRVAL1", 8.1368333333},
            {"CRVAL2", -46.9576388889},
            {"CRPIX1", 1},
            {"CRPIX2", 1},
//            {"CD1_1", 5.81347849634012E-21}, 
//            {"CD1_2", 9.49444444444444E-05},
//            {"CD2_1",-9.49444444444444E-05}, 
//            {"CD2_2",-5.81347849634012E-21},
            {"CD1_1", 5.81347849634012E-20}, 
            {"CD1_2", 9.49444444444444E-04},
            {"CD2_1",-9.49444444444444E-04}, 
            {"CD2_2",-5.81347849634012E-20},
            {"PV2_1", 1.0},
            {"PV2_2", 0.0},
            {"PV2_3", 42.0},
            {"PV2_4", 0.0}, 
            {"PV2_5", 0.0}
        };
        
        unsigned i;
        for (i = 0; i < sizeof(data) / sizeof(*data); i++) {
            cpl_propertylist_append_double(header, data[i].name, data[i].value);
        }
    }
    
    return;
}


#undef cleanup
#define cleanup \
do { \
    fors_image_delete(&bias); \
    cpl_propertylist_delete(header); \
} while(0)

/**
 * @brief   Simulate bias image
 * @param   filename        save to this file
 * @param   tag             output frame tag
 * @param   tag             output frame group
 * @return  bias frame
 *
 * Model: constant + gaussian noise, rounded to nearest integer
 */
cpl_frame *
create_bias(const char *filename, const char *tag, cpl_frame_group group)
{
    int nx = det_nx / binx + pres_nx;
    int ny = det_ny / biny;
    double exptime = 0.0;
    
    cpl_image *data     = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    cpl_image *variance = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    fors_image *bias = NULL;
    cpl_propertylist *header = cpl_propertylist_new();
    
    create_standard_keys(header, exptime);
    cpl_propertylist_erase(header, FORS_PFITS_FILTER_NAME); /* No filter */

    {
        int x, y;
        for (y = 1; y <= ny; y++)
            for (x = 1; x <= nx; x++) {
                cpl_image_set(data   , x, y, 
                              (int)(bias_avg + ron*fors_rand_gauss() + 0.5));
                cpl_image_set(variance, x, y, ron*ron);
            }
    }
    
    bias = fors_image_new(data, variance);
    fors_image_save(bias, header, NULL, filename);

    assure( !cpl_error_get_code(), return NULL,
            "Saving bias to %s failed", filename );

    cleanup;
    return frame_new(filename, tag, group);
}

/**
 * @brief   Simulate master bias image
 * @param   filename        save to this file
 * @param   tag             output frame tag
 * @param   tag             output frame group
 * @return  bias frame
 *
 * Model: constant + gaussian noise, rounded to nearest integer
 */
cpl_frame *
create_master_bias(const char *filename, const char *tag, cpl_frame_group group)
{
    int nx = det_nx / binx;
    int ny = det_ny / biny;
    double exptime = 0.0;
    
    cpl_image *data     = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    cpl_image *variance = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    fors_image *bias = NULL;
    cpl_propertylist *header = cpl_propertylist_new();
    
    create_standard_keys(header, exptime);
    cpl_propertylist_erase(header, FORS_PFITS_FILTER_NAME); /* No filter */
    cpl_propertylist_append_double(header, "ESO QC DET OUT1 RON",4.44); /* No filter */

    {
        int x, y;
        for (y = 1; y <= ny; y++)
            for (x = 1; x <= nx; x++) {
                cpl_image_set(data   , x, y, 
                              (int)(ron*fors_rand_gauss() + 0.5)); //Overscan already removed
                cpl_image_set(variance, x, y, ron*ron);
            }
    }
    
    bias = fors_image_new(data, variance);
    fors_image_save(bias, header, NULL, filename);

    assure( !cpl_error_get_code(), return NULL,
            "Saving master bias to %s failed", filename );

    cleanup;
    return frame_new(filename, tag, group);
}

#undef cleanup
#define cleanup \
do { \
    fors_image_delete(&dark); \
    cpl_propertylist_delete(header); \
} while(0)

/**
 * @brief   Simulate dark image
 * @param   filename        save to this file
 * @param   tag             output frame tag
 * @param   tag             output frame group
 * @return  dark frame
 *
 * Model:     bias   + dark(x,y)
 * Variance = ron**2 + dark(x,y)/conad
 */
cpl_frame *
create_dark(const char *filename, const char *tag, cpl_frame_group group)
{
    int nx = det_nx / binx + pres_nx;
    int ny = det_ny / biny;
    double exptime = 3.0;
    
    cpl_image *data     = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    cpl_image *variance = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    fors_image *dark = NULL;
    cpl_propertylist *header = cpl_propertylist_new();
    
    create_standard_keys(header, exptime);

    {
        int x, y;
        for (y = 1; y <= ny; y++)
            for (x = 1; x <= nx; x++) {
                double var = ron*ron + dark_avg/conad;
                
                cpl_image_set(data   , x, y, 
                              (int)(bias_avg + dark_avg + 
                                    sqrt(var)*fors_rand_gauss() 
                                    + 0.5));
                cpl_image_set(variance, x, y, var);
            }
        //Fill the prescan region
        for (y = 1; y <= ny; y++)
            for (x = 1; x <= pres_nx; x++)
            {
                cpl_image_set(data   , x, y, 
                              (int)(bias_avg + ron*fors_rand_gauss() + 0.5));
                cpl_image_set(variance, x, y, ron*ron);
            }
    }
    
    dark = fors_image_new(data, variance);
    fors_image_save(dark, header, NULL, filename);

    assure( !cpl_error_get_code(), return NULL, 
            "Saving dark to %s failed", filename );
    
    cleanup;
    return frame_new(filename, tag, group);
}


#undef cleanup
#define cleanup \
do { \
    fors_image_delete(&sflat); \
    cpl_propertylist_delete(header); \
} while(0)
/**
 * @brief   Simulate screen flat image
 * @param   filename        save to this file
 * @param   tag             output frame tag
 * @param   group           output frame group
 * @return  screen flat frame
 *
 * Model:     bias   + flat(x,y)
 * Variance = ron**2 + flat(x,y)/conad
 */
cpl_frame *
create_screen_flat(const char *filename, const char *tag, cpl_frame_group group)
{
    int nx = det_nx / binx + pres_nx;
    int ny = det_ny / biny;
    double exptime = 3.0;

    cpl_image *data     = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    cpl_image *variance = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    fors_image *sflat = NULL;
    cpl_propertylist *header = cpl_propertylist_new();
    
    create_standard_keys(header, exptime);

    {
        int x, y;
        for (y = 1; y <= ny; y++)
            for (x = 1; x <= nx; x++) {
                double medium_scale_structure = 1000*(2 + sin(x*30.0/nx + y*30.0/ny));
                double  large_scale_structure = 1000*(1 + sin(x*4.0/nx));
                double flat = 5000 + medium_scale_structure + large_scale_structure;
                double var = ron*ron + flat/conad;
                
                cpl_image_set(data   , x, y, 
                              (int)(bias_avg + flat + sqrt(var)*fors_rand_gauss() + 0.5));
                cpl_image_set(variance, x, y, var);
            }
        //Fill the prescan region
        for (y = 1; y <= ny; y++)
            for (x = 1; x <= pres_nx; x++)
            {
                cpl_image_set(data   , x, y, 
                              (int)(bias_avg + ron*fors_rand_gauss() + 0.5));
                cpl_image_set(variance, x, y, ron*ron);
            }
    }
    
    sflat = fors_image_new(data, variance);
    fors_image_save(sflat, header, NULL, filename);

    assure( !cpl_error_get_code(), return NULL, 
            "Saving screen flat to %s failed", filename );
    
    cleanup;
    return frame_new(filename, tag, group);
}

#undef cleanup
#define cleanup \
do { \
    fors_image_delete(&sflat); \
    cpl_propertylist_delete(header); \
} while(0)
/**
 * @brief   Simulate sky flat image
 * @param   filename        save to this file
 * @param   tag             output frame tag
 * @param   group           output frame group
 * @param   exptime         exposure time
 * @return  sky flat frame
 *
 * Model:     bias   + flat(x,y)
 * Variance = ron**2 + flat(x,y)/conad
 */
cpl_frame *
create_sky_flat(const char *filename, const char *tag, cpl_frame_group group,
                double exptime)
{
    int nx = det_nx / binx + pres_nx;
    int ny = det_ny / biny;

    cpl_image *data     = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    cpl_image *variance = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    fors_image *sflat = NULL;
    cpl_propertylist *header = cpl_propertylist_new();
    
    create_standard_keys(header, exptime);

    {
        int x, y;
        for (y = 1; y <= ny; y++)
            for (x = 1; x <= nx; x++) {
                double medium_scale_structure = 1000*(2 + sin(x*30.0/nx - y*10.0/ny));
                double  large_scale_structure = 1000*(1 + sin(x*4.0/nx));
                double flat = exptime*(5000 + 
                                       medium_scale_structure + 
                                       large_scale_structure);
                double var = ron*ron + flat/conad;
                
                cpl_image_set(data   , x, y, 
                              (int)(flat + sqrt(var)*fors_rand_gauss() + 0.5));//Overscan already removed
                cpl_image_set(variance, x, y, var);
            }
        //Fill the prescan region
        for (y = 1; y <= ny; y++)
            for (x = 1; x <= pres_nx; x++)
            {
                cpl_image_set(data   , x, y, 
                              (int)(bias_avg + ron*fors_rand_gauss() + 0.5));
                cpl_image_set(variance, x, y, ron*ron);
            }
    }
    
    sflat = fors_image_new(data, variance);
    fors_image_save(sflat, header, NULL, filename);

    assure( !cpl_error_get_code(), return NULL, 
            "Saving sky flat to %s failed", filename );

    cleanup;
    return frame_new(filename, tag, group);
}

/**
 * @brief   Simulate master sky flat image
 * @param   filename        save to this file
 * @param   tag             output frame tag
 * @param   group           output frame group
 * @param   exptime         exposure time
 * @return  sky flat frame
 *
 * Model:     bias   + flat(x,y)
 * Variance = ron**2 + flat(x,y)/conad
 */
cpl_frame *
create_master_sky_flat(const char *filename, 
                       const char *tag, cpl_frame_group group,
                       double exptime)
{
    int nx = det_nx / binx;
    int ny = det_ny / biny;

    cpl_image *data     = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    cpl_image *variance = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    fors_image *sflat = NULL;
    cpl_propertylist *header = cpl_propertylist_new();
    
    create_standard_keys(header, exptime);

    {
        int x, y;
        for (y = 1; y <= ny; y++)
            for (x = 1; x <= nx; x++) {
                double medium_scale_structure = 1000*(2 + sin(x*30.0/nx - y*10.0/ny));
                double  large_scale_structure = 1000*(1 + sin(x*4.0/nx));
                double flat = exptime*(5000 + 
                                       medium_scale_structure + 
                                       large_scale_structure);
                double var = ron*ron + flat/conad;
                
                cpl_image_set(data   , x, y, 
                              (int)(flat + sqrt(var)*fors_rand_gauss() + 0.5));//Overscan already removed
                cpl_image_set(variance, x, y, var);
            }
    }
    
    sflat = fors_image_new(data, variance);
    fors_image_save(sflat, header, NULL, filename);

    assure( !cpl_error_get_code(), return NULL, 
            "Saving sky flat to %s failed", filename );

    cleanup;
    return frame_new(filename, tag, group);
}

/**
 * @brief   Create standard star image
 * @param   filename        save to this file
 * @param   tag             output frame tag
 * @param   group           output frame group
 * @return  standard field frame
 */
cpl_frame *
create_standard(const char *filename, const char *tag, cpl_frame_group group)
{
    // fixme: add stars
    double exptime = 1.0;
    return create_sky_flat(filename, tag, group, exptime);
}


#undef cleanup
#define cleanup \
do { \
    cpl_table_delete(t); \
} while(0)
/**
 * @brief   Create standard star catalogue
 * @param   filename        save to this file
 * @param   tag             output frame tag
 * @param   group           output frame group
 * @return  catalogue
 *
 */
cpl_frame *
create_std_cat(const char *filename, const char *tag, cpl_frame_group group)
{
    cpl_table *t;
    struct {
        double ra, dec;
        double magnitude, dmagnitude;
        double col;
        const char *name;
    } 
    data[] =  {
        {8.15958, -47.0347, 15.824000, 0.001, 0.8, "object 1"},
        {8.14792, -46.9664, 12.895000, 0.002, -0.2, ""},
        {8.15083, -47.0092, 12.861000, 0.003, -0.3, " dff bject 1"},
        {8.15583, -47.0222, 16.540001, 0.001, 0.7, "-9"},
        {8.17167, -47.10  , 11.970000, 0.005, 0.12, NULL},
        {8.14833, -47.0567, 13.861000, 0.003, -0.2, ""},
        {8.1475 , -47.0411, 13.903000, 0.001, -0.8, "dddddddobject 1"},
        {7.92542,  2.62917, 15.446000, 0.002, -0.2, "start 1"},
    };

    int N = sizeof(data) / sizeof(*data);
    int i;

    t = cpl_table_new(N);
    cpl_table_new_column(t, FORS_DATA_STD_RA , CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, FORS_DATA_STD_DEC, CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, FORS_DATA_STD_NAME, CPL_TYPE_STRING);
    
    for (i = 0; i < FORS_NUM_FILTER; i++) {
        if (!cpl_table_has_column(t, FORS_DATA_STD_MAG[i])) {
	    cpl_table_new_column(t, FORS_DATA_STD_MAG[i], CPL_TYPE_FLOAT);
	}
        if (!cpl_table_has_column(t, FORS_DATA_STD_DMAG[i])) {
	    cpl_table_new_column(t, FORS_DATA_STD_DMAG[i], CPL_TYPE_FLOAT);
	}
        if (!cpl_table_has_column(t, FORS_DATA_STD_COL[i])) {
            cpl_table_new_column(t, FORS_DATA_STD_COL[i], CPL_TYPE_FLOAT);
        }
    }

    for (i = 0; i < N; i++) {
        int j;
        
        cpl_table_set_double(t, FORS_DATA_STD_RA  , i, data[i].ra);
        cpl_table_set_double(t, FORS_DATA_STD_DEC , i, data[i].dec);
        cpl_table_set_string(t, FORS_DATA_STD_NAME, i, data[i].name);
        
        for (j = 0; j < FORS_NUM_FILTER; j++) {
            cpl_table_set_float (t, FORS_DATA_STD_MAG[j], i, data[i].magnitude);
            cpl_table_set_float (t, FORS_DATA_STD_DMAG[j], i, data[i].dmagnitude);
            cpl_table_set_float (t, FORS_DATA_STD_COL[j], i, data[i].col);
        }
    }

    cpl_table_save(t, NULL, NULL, filename, CPL_IO_DEFAULT);
    assure( !cpl_error_get_code(), return NULL, 
            "Failed to save standard catalogue to %s", filename );
    
    cleanup;
    return frame_new(filename, tag, group);
}



#undef cleanup
#define cleanup \
do { \
    cpl_table_delete(t); \
} while(0)
/**
 * @brief   Create photometry table
 * @param   filename        save to this file
 * @param   tag             output frame tag
 * @param   group           output frame group
 * @return  photometry table
 *
 */
cpl_frame *
create_phot_table(const char *filename, const char *tag, cpl_frame_group group)
{
    cpl_table *t;
    struct {
        char    band;
        double ext_coeff, dext_coeff;
        double color_term, dcolor_term;
        double expected_zeropoint, dexpected_zeropoint;
    } 
    data[FORS_NUM_FILTER] = {
        {'U', 0.4  , 0.01, -0.076, 0.001, 20, 0.2},
        {'B', 0.05 , 0.01,  0.033, 0.001, 21.123456, 0.2},
        {'G', 0.1  , 0.01,  0.01 , 0.001, 22, 0.2},
        {'V', 0.09 , 0.01, -0.02 , 0.001, -18, 0.2},
        {'R', 0.2  , 0.01,  0.03 , 0.001, 0, 0.2},
        {'I', 0.000, 0.01, -0.04 , 0.001, 1.0, 0.2},
    };
    
    int N = fors_instrument_known_filters_get_number();
    int i;
    
    t = cpl_table_new(N);
    cpl_table_new_column(t, FORS_DATA_PHOT_FILTER   , CPL_TYPE_STRING);
    cpl_table_new_column(t, FORS_DATA_PHOT_EXTCOEFF , CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, FORS_DATA_PHOT_DEXTCOEFF , CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, FORS_DATA_PHOT_COLORTERM, CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, FORS_DATA_PHOT_DCOLORTERM, CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, FORS_DATA_PHOT_ZEROPOINT, CPL_TYPE_DOUBLE);
    cpl_table_new_column(t, FORS_DATA_PHOT_DZEROPOINT, CPL_TYPE_DOUBLE);

    /* For each filtername (e.g. "U_BESS") find the matching filter (e.g. FILTER_U) */
    for (i = 0; i < N; i++) {
        cpl_table_set_string(       t,
                                    FORS_DATA_PHOT_FILTER,
                                    i,
                                    fors_instrument_known_filters_get_name(i));

        unsigned j;
        for (j = 0; j < FORS_NUM_FILTER; j++) {
            if (fors_instrument_known_filters_get_band(i) == data[j].band)
            {
                cpl_table_set_double(t, FORS_DATA_PHOT_EXTCOEFF , i, data[j].ext_coeff);
                cpl_table_set_double(t, FORS_DATA_PHOT_DEXTCOEFF , i, data[j].dext_coeff);
                cpl_table_set_double(t, FORS_DATA_PHOT_COLORTERM, i, data[j].color_term);
                cpl_table_set_double(t, FORS_DATA_PHOT_DCOLORTERM, i, data[j].dcolor_term);
                cpl_table_set_double(t, FORS_DATA_PHOT_ZEROPOINT, i, data[j].expected_zeropoint);
                cpl_table_set_double(t, FORS_DATA_PHOT_DZEROPOINT, i, data[j].dexpected_zeropoint);
            }
        }
    }

    cpl_table_save(t, NULL, NULL, filename, CPL_IO_DEFAULT);
    assure( !cpl_error_get_code(), return NULL, 
            "Failed to save photometry table to %s", filename );
    
    cleanup;
    return frame_new(filename, tag, group);
}
/**@}*/
