/*
 * This file is part of the UVES Pipeline
 * Copyright (C) 2002, 2003, 2004, 2005 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, 51 Franklin St, Fifth Floor, Boston, MA  02111-1307  USA
 */

/*
 * $Author: amodigli $
 * $Date: 2013-04-16 15:48:48 $
 * $Revision: 1.272 $
 * $Name: not supported by cvs2svn $
 *
 */

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

/*----------------------------------------------------------------------------*/
/**
 * @defgroup uves_dfs  DFS related functions
 *
 * This modules implements the interface to the DFS, such as file formats, 
 * file names, I/O and FITS header compliancy.
 */
/*----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
                                   Includes
 -----------------------------------------------------------------------------*/

#include <uves_dfs.h>

#include <uves_utils.h>
#include <uves_wavecal_utils.h>
#include <uves_pfits.h>
#include <uves_dump.h>
#include <uves_qclog.h>
#include <uves.h>
#include <uves_utils_wrappers.h>
#include <uves_error.h>
#include <uves_msg.h>

#include <irplib_utils.h>

#include <cpl.h>

#include <uves_time.h> /* iso time */

#include <float.h>
#include <string.h>
/*-----------------------------------------------------------------------------
                                   Defines
 -----------------------------------------------------------------------------*/
#define FITS_MAGIC_SZ      6

/*-----------------------------------------------------------------------------
                                   Prototypes
 -----------------------------------------------------------------------------*/

static polynomial *load_polynomial(const char* filename, int extension);
static char *int_to_string(int i);

static cpl_error_code
load_raw_image(const char *filename, 
           cpl_type type, bool flames, bool blue,
           cpl_image *raw_image[2],
           uves_propertylist *raw_header[2], 
           uves_propertylist *rotated_header[2]);

static int uves_is_fits_file(const char *filename);
/**@{*/

/**
  @brief    Check if an error has happened and returns error kind and location
  @param    val input value
  @return   0 if no error is detected,-1 else
 */
int uves_check_rec_status(const int val) {
   if(cpl_error_get_code() != CPL_ERROR_NONE) {
      uves_msg_error("error before %d",val);
      uves_msg_error("%s", (char* ) cpl_error_get_message());
      uves_msg_error("%s", (char* ) cpl_error_get_where());
      return -1;
    }
    return 0;
}

/*-------------------------------------------------------------------------*/
/**
   @name        uves_is_fits_file
   @memo        returns 1 if file is in FITS format, 0 else
   @param    filename name of the file to check
   @return    int 0, 1, or -1
   @doc

   Returns 1 if the file name corresponds to a valid FITS file. Returns
   0 else. If the file does not exist, returns -1.
*/
/*--------------------------------------------------------------------------*/

static int uves_is_fits_file(const char *filename)
{
   FILE    *fp ;
   char    *magic ;
   int        isfits ;

   if ((fp = fopen(filename, "r"))==NULL) {
      uves_msg_error("cannot open file [%s]", filename) ;
      return -1 ;
   }

   magic = cpl_calloc(FITS_MAGIC_SZ+1, sizeof(char)) ;
   (void)fread(magic, 1, FITS_MAGIC_SZ, fp) ;
   (void)fclose(fp) ;
   magic[FITS_MAGIC_SZ] = (char)0 ;
   if (strstr(magic, "SIMPLE")!=NULL)
      isfits = 1 ;
   else
      isfits = 0 ;
   cpl_free(magic) ;
   return isfits ;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief
 *   Check if all SOF files exist
 *
 * @param frameset The input set-of-frames
 *
 * @return 1 if not all files exist, 0 if they all exist.
 *
 */
/*----------------------------------------------------------------------------*/
static int 
uves_dfs_files_dont_exist(cpl_frameset *frameset)
{
    const char *func = "dfs_files_dont_exist";

    if (frameset == NULL) {
        cpl_error_set(func, CPL_ERROR_NULL_INPUT);
        return 1;
    }

    if (cpl_frameset_is_empty(frameset)) {
        return 0;
    }

    cpl_frameset_iterator* it = cpl_frameset_iterator_new(frameset);
    cpl_frame* frame = cpl_frameset_iterator_get(it);

    while (frame) {
        if (access(cpl_frame_get_filename(frame), F_OK)) {
            cpl_msg_error(func, "File %s (%s) was not found", 
                          cpl_frame_get_filename(frame), 
                          cpl_frame_get_tag(frame));
            cpl_error_set(func, CPL_ERROR_FILE_NOT_FOUND);
        }

        cpl_frameset_iterator_advance(it, 1);
        frame = cpl_frameset_iterator_get(it);

    }
    cpl_frameset_iterator_delete(it);
    if (cpl_error_get_code())
        return 1;

    return 0;
}



/*----------------------------------------------------------------------------*/
/**
  @brief    extract subset of frames with a given tag
  @param    sof     input frameset
  @param    raw     output frameset
  @param    type    input tag 
  @return   0

*/
/*----------------------------------------------------------------------------*/

int 
uves_contains_frames_kind(cpl_frameset * sof, 
                               cpl_frameset* raw,
                               const char*         type)
{





   int nsof=0;
   int i=0;
   nsof = cpl_frameset_get_size(sof);
   for (i=0 ; i<nsof ; i++) {
      cpl_frame* frame = cpl_frameset_get_frame(sof,i);
      char* name= (char*) cpl_frame_get_filename(frame);
      if(uves_is_fits_file(name) == 1) {
         /* to go on the file must exist */
         if(cpl_frame_get_tag(frame) != NULL) {
            /* If the frame has a tag we process it. Else it is an object */ 
            char* tag= (char*) cpl_frame_get_tag(frame);
            /* uves_msg("name=%s tag=%s type=%s\n",name,tag,type); */
            if(strstr(tag,type) != NULL) {
               /* uves_msg("Match name=%s tag=%s type=%s\n",name,tag,type); */
               cpl_frame* frame_dup = cpl_frame_duplicate(frame);
               cpl_frameset_insert(raw,frame_dup);
               /* uves_msg("inserted\n"); */ 
            }
         }
      }
   }
   return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Read a polynomial produced by REGRESSION/POLYNOMIAL (MIDAS)
  @param    plist               The property list where the polynomial is stored
  @param    regression_name     Name of the regression
  @param    regression_name_id  Extra index in regression name (FIBER mode) 
  @return   The polynomial stored in the property list, which must be deallocated
            with @c uves_polynomial_delete(), or NULL on error.

  This function reads a polynomial produced by the MIDAS command REGRESSION/POLYNOMIAL. 
  If the specified regression name is e.g. "COEFF", the function will read the
  information stored in the descriptors "COEFFI" and "COEFFD". The polynomial 
  must be 2d, otherwise an error is set.

  @note This function serves no purpose (i.e. can be removed) in a purely CPL 
  based pipeline.
*/
/*----------------------------------------------------------------------------*/
polynomial *
uves_polynomial_convert_from_plist_midas(const uves_propertylist *plist, 
					 const char *regression_name,
                                         const int index)
{
    polynomial *result = NULL;
    cpl_polynomial *pol = NULL;
    int N = strlen(regression_name);
    const char *coeffi_name = NULL;
    cpl_type type;
    int length;
    int *coeffi = NULL;
    int degree1 = -1;
    int degree2 = -1; 
    bool found = false;
    const long int plist_size = uves_propertylist_get_size(plist);
    int i;
     
    char cind=' ';

    if (index == -1) {
      coeffi_name = cpl_sprintf("%sI", regression_name);
    }
    else {

     switch(index) {

     case 1: cind='1'; break;
     case 2: cind='2'; break;
     case 3: cind='3'; break;
     case 4: cind='4'; break;
     case 5: cind='5'; break;
     case 6: cind='6'; break;
     case 7: cind='7'; break;
     case 8: cind='8'; break;
     case 9: cind='9'; break;
     default: 
       assure( false, CPL_ERROR_ILLEGAL_INPUT, 
             "Illegal index %d, 1-9 expected", index);
       break;
     }


      coeffi_name = cpl_sprintf("%sI%d", regression_name, index);
    }

    check_nomsg( coeffi = uves_read_midas_array(plist, coeffi_name, &length, &type, NULL));


    assure( type == CPL_TYPE_INT, CPL_ERROR_TYPE_MISMATCH,
	    "Type of array %s is %s, integer expected",
	    coeffi_name, uves_tostring_cpl_type(type));
    /*
    assure( length == 7, CPL_ERROR_ILLEGAL_INPUT,
	    "Wrong array length = %d, 7 expected",
	    length);
    */
    /* ignore OUTPUTI(1)- N,no.of data, */
    
    /* OUTPUTI(2)- M,no.of ind.var. */
    
    assure( coeffi[1] == 2, CPL_ERROR_UNSUPPORTED_MODE,
	    "Regressions is %d-dimensional (2D expected)", 
	    coeffi[1]);

    /* ignore OUTPUTI(3-5) (column number of variables)
       (3)- col.no. of dep.var.
       (4)- col.no. of indep.var.
       (5)-
    */
    
    /* Read degree of first and second variable 
       (6)- degree (ND) */

    degree1 = coeffi[5];
    degree2 = coeffi[6];
    
    uves_msg_debug("Degree of 2D regression %s is (%d, %d)",
                   regression_name, degree1, degree2);

    /* The degree of the regression is now known. Next, read the coefficients */

    pol = cpl_polynomial_new(2);

    /* Search for <regression_name>D */
    found = false;
    for (i = 0; !found && i < plist_size; i++){
    const cpl_property *p = uves_propertylist_get_const(plist, i);
    const char *name = cpl_property_get_name(p);
    
    if (strcmp(name, "HISTORY") == 0) {
        const char *value;
        check( value = cpl_property_get_string(p),
           "Error reading property value");
        
        /* match the string  "'<regression_name>D'"  */

        if (
         
	    (((index < 0) &&
	    (int)strlen(value) >= 1+N+2 &&
             value[0]     == '\'' &&
             value[1+N]   == 'D' && 
	     value[1+N+1] == '\'') 

                || 

	    ((index > 0) &&
	    (int)strlen(value) >= 1+N+3 &&
             value[0]     == '\'' &&
             value[1+N]   == 'D' && 
             value[1+N+1] == cind && 
	     value[1+N+2] == '\'') ) 

                 &&
 
        strncmp(value+1, regression_name, N) == 0
        ) {
        double coeff;
        char *next;
        cpl_size power[2];
        int j = i; /* points to the property currently being read */

        power[0] = 0;  /* Current degree */
        power[1] = 0;

        found = true;
        value = "dummy"; /* This will make strtod fail the first time */
        
        while (power[1] <= degree2){
            /* Read coefficient */
            coeff = strtod(value, &next);
            
            if (next != value) {
            /* A prefix of the string was successfully converted to double */
            cpl_polynomial_set_coeff(pol, power, coeff);
            uves_msg_debug("Polynomial coefficient of order (%" CPL_SIZE_FORMAT ", %" CPL_SIZE_FORMAT ") is %e",
                       power[0], power[1], coeff);
            
            power[0]++;
            if (power[0] > degree1){
                power[0] = 0;
                power[1]++;
            }
            value = next;
            }
            else {
            /* No more doubles could be read from the string,
               so move to the next property in the plist */
            j = j + 1;
            
            assure(j < plist_size, CPL_ERROR_ILLEGAL_INPUT,
                   "Missing header data");
            
            p = uves_propertylist_get_const(plist, j);
            assure(       cpl_property_get_type(p)             == CPL_TYPE_STRING &&
                   strcmp(cpl_property_get_name(p), "HISTORY") == 0, 
                      CPL_ERROR_ILLEGAL_INPUT, "Error parsing polynomial");
            
            value = cpl_property_get_string(p);

            
            uves_msg_debug("Parsing string '%s'", value);
            }
        } /* Read coefficients */
        } /* string was "'...D'" */
    } /* Keyword was HISTORY */
    }/* for i... */
    
    assure( found, CPL_ERROR_ILLEGAL_INPUT, "Could not find '%sD' in property list", 
        regression_name);

    /* Create a new polynomial from the cpl_polynomial */
    result = uves_polynomial_new(pol);
    
  cleanup:
    uves_free_int(&coeffi);
    uves_free_string_const(&coeffi_name);
    uves_free_polynomial(&pol);
    if (cpl_error_get_code() != CPL_ERROR_NONE) 
    {
        uves_polynomial_delete(&result);
    }

    return result;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    merge framesets 
  @param    set1     The input and destination frameset
  @param    set2     The input frameset
  @return   CPL_ERROR_NONE iff ok
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_frameset_merge(cpl_frameset * set1, const cpl_frameset* set2)
{

    const cpl_frame* frm_tmp=NULL;
    cpl_frame* frm_dup=NULL;
    int i=0;
    int nfrm;
    passure(set1 != NULL, "Wrong input set");
    passure(set2 != NULL, "Wrong input set");
    nfrm=cpl_frameset_get_size(set2);
    for (i = 0; i < nfrm; i++ ) {
        frm_tmp=cpl_frameset_get_frame(set2,i);
        frm_dup = cpl_frame_duplicate(frm_tmp);
        cpl_frameset_insert(set1, frm_dup);
    }

    cleanup:
    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Extracts frames of a given type 
  @param    set     The input frameset
  @param    ext     The output frameset
  @param    type    The frame type
  @return   CPL_ERROR_NONE iff ok
 */
/*----------------------------------------------------------------------------*/

cpl_error_code
uves_extract_frames_group_type(const cpl_frameset * set, cpl_frameset** ext, cpl_frame_group type)
{

  cpl_frame* frm_dup=NULL;
  cpl_frame_group g;

  check_nomsg(*ext = cpl_frameset_new());
  cpl_frameset_iterator* it = cpl_frameset_iterator_new(set);
  const cpl_frame *frm_tmp = cpl_frameset_iterator_get_const(it);

  while (frm_tmp != NULL)
    {
      g=cpl_frame_get_group(frm_tmp);
      if(g == type) {
        frm_dup=cpl_frame_duplicate(frm_tmp);
        cpl_frameset_insert(*ext,frm_dup);
        uves_msg_debug("group %d insert file %s ",type,cpl_frame_get_filename(frm_dup));
      }
      cpl_frameset_iterator_advance(it, 1);
      frm_tmp = cpl_frameset_iterator_get_const(it);
    }
  cpl_frameset_iterator_delete(it);
  cleanup:
    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Counts how many sflats we have 
  @param    set     The input frameset
  @param    enc     The output different start/end encoders table
  @param    nset     The input frameset
  @return   CPL_ERROR_NONE iff ok
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_sflats_get_encoder_steps(const cpl_frameset * set, cpl_table** enc, int* nset)
{
  /* Input */
    const cpl_frame* frm=NULL;
  int x1enc=0;
  int x2enc=0;
  int ref_x1enc=0;
  int ref_x2enc=0;
  int i=0;
  int ndata=0;
  const int threshold=5;
  int status=0;
  uves_propertylist* plist=NULL;
  cpl_table* encoder_tbl=NULL;
  ndata = cpl_frameset_get_size(set);
  encoder_tbl=cpl_table_new(ndata);
  cpl_table_new_column(encoder_tbl,"x1enc",CPL_TYPE_INT);
  cpl_table_new_column(encoder_tbl,"x2enc",CPL_TYPE_INT);
  cpl_table_new_column(encoder_tbl,"flag",CPL_TYPE_INT);
 
  for(i=0;i<cpl_frameset_get_size(set);i++)
    {
    check_nomsg(frm=cpl_frameset_get_frame_const(set,i));
    check_nomsg(plist=uves_propertylist_load(cpl_frame_get_filename(frm),0));
    check_nomsg(x1enc=uves_pfits_get_slit3_x1encoder(plist));
    check_nomsg(x2enc=uves_pfits_get_slit3_x2encoder(plist));
    check_nomsg(cpl_table_set_int(encoder_tbl,"x1enc",i,x1enc));
    check_nomsg(cpl_table_set_int(encoder_tbl,"x2enc",i,x2enc));
    uves_free_propertylist(&plist);
    }
 
  check_nomsg(uves_sort_table_2(encoder_tbl,"x1enc","x2enc",false,true));

  check_nomsg(ref_x1enc=cpl_table_get_int(encoder_tbl,"x1enc",0,&status));
  check_nomsg(ref_x2enc=cpl_table_get_int(encoder_tbl,"x2enc",0,&status));
  *nset=1;
  *enc=cpl_table_new(1);
  cpl_table_new_column(*enc,"x1enc",CPL_TYPE_INT);
  cpl_table_new_column(*enc,"x2enc",CPL_TYPE_INT);
  check_nomsg(cpl_table_set_int(*enc,"x1enc",0,ref_x1enc));
  check_nomsg(cpl_table_set_int(*enc,"x2enc",0,ref_x2enc));

  for(i=1;i<cpl_table_get_nrow(encoder_tbl);i++) {
     check_nomsg(x1enc=cpl_table_get_int(encoder_tbl,"x1enc",i,&status));
     check_nomsg(x2enc=cpl_table_get_int(encoder_tbl,"x2enc",i,&status));
     if( (fabs(ref_x1enc -x1enc) > threshold) || 
         (fabs(ref_x2enc -x2enc) > threshold) ) {
  
       ref_x1enc = x1enc;
       ref_x2enc = x2enc;
       cpl_table_set_size(*enc,(*nset+1));
       check_nomsg(cpl_table_set_int(*enc,"x1enc",*nset,ref_x1enc));
       check_nomsg(cpl_table_set_int(*enc,"x2enc",*nset,ref_x2enc));
       *nset=*nset+1;

     }
  }
  uves_msg("Number of sets = %d",*nset);

  cleanup:
   uves_free_table(&encoder_tbl);
    uves_free_propertylist(&plist);
    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Set the group as RAW or CALIB in a frameset
  @param    set     The input frameset
  @return   CPL_ERROR_NONE iff ok
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_dfs_set_groups(cpl_frameset * set)
{
    cpl_frame   *   cur_frame ;
    int             nframes ;
    
    /* Check entries */
    assure(set != NULL, CPL_ERROR_NULL_INPUT, "Null input"); 
    
    /* Initialize */
    check( nframes = cpl_frameset_get_size(set), "Could not read frameset size");
    
    /* Loop on frames */
    int i=0;
    for (i = 0; i< nframes;i++)
    {
        cur_frame=cpl_frameset_get_frame(set,i);
        bool is_raw   = false;
        bool is_calib = false;
        bool is_recognized = false;
        bool blue;
        enum uves_chip chip;
        const char  *   tag = cpl_frame_get_tag(cur_frame);
        
        assure( tag != NULL && strcmp(tag, "") != 0, CPL_ERROR_ILLEGAL_INPUT,
            "Frame has no tag!");
        
        blue = false;
        do {
        bool flames = false;
        do {
            /* RAW frames */
            is_raw   = is_raw   || 
            (strcmp(tag, UVES_ORDER_FLAT  (flames,blue)) == 0 ||
             strcmp(tag, UVES_BIAS        (blue)) == 0 ||
             strcmp(tag, UVES_DARK        (blue)) == 0 ||
             strcmp(tag, UVES_PDARK       (blue)) == 0 ||
             strcmp(tag, UVES_FLAT        (blue)) == 0 ||
             strcmp(tag, UVES_IFLAT       (blue)) == 0 ||
             strcmp(tag, UVES_DFLAT       (blue)) == 0 ||
             strcmp(tag, UVES_SFLAT       (blue)) == 0 ||
             strcmp(tag, UVES_TFLAT       (blue)) == 0 ||
             strcmp(tag, UVES_SCREEN_FLAT (blue)) == 0 ||
             strcmp(tag, UVES_CD_ALIGN    (blue)) == 0 ||
             strcmp(tag, UVES_FORMATCHECK (flames,blue)) == 0 ||
             strcmp(tag, UVES_STD_STAR    (blue)) == 0 ||
             strcmp(tag, UVES_SCIENCE     (blue)) == 0 ||
             strcmp(tag, UVES_SCI_EXTND   (blue)) == 0 ||
             strcmp(tag, UVES_SCI_POINT   (blue)) == 0 ||
             strcmp(tag, UVES_SCI_SLICER  (blue)) == 0 ||
             strcmp(tag, UVES_ARC_LAMP    (flames,blue)) == 0 ||
             strcmp(tag, UVES_ECH_ARC_LAMP(blue)) == 0 ||
             strcmp(tag, RAW_IMA) == 0 ||
             strcmp(tag, FLAMES_SCI_RED) == 0 ||
             strcmp(tag, FLAMES_SCI_SIM_RED) == 0 ||
             strcmp(tag, FLAMES_SCI_COM_RED) == 0 ||
             strcmp(tag, FLAMES_FIB_FF_ODD) == 0 ||
             strcmp(tag, FLAMES_FIB_FF_EVEN) == 0 ||
             strcmp(tag, FLAMES_FIB_FF_ALL) == 0);
            
            /* CALIB frames */
            
            /* Loop through all (1 or 2) blue or red chips */
            for (chip = uves_chip_get_first(blue);
             chip != UVES_CHIP_INVALID; 
             chip = uves_chip_get_next(chip))
            {
                int window;
                
                is_calib = is_calib || 
                (strcmp(tag, UVES_DRS_SETUP(flames, chip)) == 0 ||
                 strcmp(tag, UVES_ORDER_TABLE(flames, chip)) == 0 ||
                 strcmp(tag, UVES_GUESS_ORDER_TABLE(flames,chip)) == 0 ||
                 strcmp(tag, UVES_MASTER_BIAS   (chip)) == 0 ||
                 strcmp(tag, UVES_MASTER_DARK   (chip)) == 0 ||
                 strcmp(tag, UVES_MASTER_PDARK  (chip)) == 0 ||
                 strcmp(tag, UVES_MASTER_FLAT   (chip)) == 0 ||
                 strcmp(tag, UVES_MASTER_DFLAT  (chip)) == 0 ||
                 strcmp(tag, UVES_MASTER_SFLAT  (chip)) == 0 ||
                 strcmp(tag, UVES_MASTER_IFLAT  (chip)) == 0 ||
                 strcmp(tag, UVES_MASTER_TFLAT  (chip)) == 0 ||
                 strcmp(tag, UVES_REF_TFLAT     (chip)) == 0 ||
                 strcmp(tag, UVES_ORD_TAB(flames,chip)) == 0 ||
                 strcmp(tag, UVES_MASTER_SCREEN_FLAT(chip)) == 0 ||
                 strcmp(tag, UVES_MASTER_ARC_FORM(chip)) == 0 ||
                 strcmp(tag, UVES_WEIGHTS(chip))        == 0 ||
                 strcmp(tag, UVES_LINE_TABLE(flames,chip)) == 0 ||
                 strcmp(tag, UVES_GUESS_LINE_TABLE(flames,chip)) == 0 ||
                 strcmp(tag, UVES_INSTR_RESPONSE(chip)) == 0 ||
                 strcmp(tag, UVES_MASTER_RESPONSE(chip)) == 0 ||
                 strcmp(tag, UVES_LINE_REFER_TABLE    ) == 0 ||
                 strcmp(tag, UVES_LINE_INTMON_TABLE   ) == 0 ||
                 strcmp(tag, UVES_FLUX_STD_TABLE      ) == 0 ||
                 strcmp(tag, UVES_EXTCOEFF_TABLE      ) == 0 ||
                 strcmp(tag, FLAMES_LINE_TABLE(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_DT1(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_DT2(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_DT3(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_DTC(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_BP1(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_BP2(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_BP3(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_BPC(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_BN1(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_BN2(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_BN3(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_BNC(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_SG1(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_SG2(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_SG3(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_SGC(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_COM(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_NOR(chip)) == 0 ||
                 strcmp(tag, FLAMES_SLIT_FF_NSG(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_DT1(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_DT2(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_DT3(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_DTC(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_BP1(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_BP2(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_BP3(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_BPC(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_BN1(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_BN2(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_BN3(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_BNC(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_SG1(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_SG2(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_SG3(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_SGC(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_COM(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_NOR(chip)) == 0 ||
                 strcmp(tag, FLAMES_FIB_FF_NSG(chip)) == 0 ||
                 strcmp(tag, FLAMES_ORDEF(flames,chip)) == 0 ||
                 strcmp(tag, FLAMES_CORVEL_MASK) == 0);
                
                for (window = 1; window <= 3; window++)
                {
                    is_calib = is_calib || 
                    strcmp(tag, UVES_LINE_TABLE_MIDAS(chip, window)) == 0;
                }
                
                if (!flames && strcmp(tag, UVES_BACKGR_TABLE(chip)) == 0)
                {
                    uves_msg_warning("Background table %s has been deprecated. "
                             "Inter-order positions will be inferred "
                             "from the order table %s. "
                             "Use recipe parameters to define "
                             "measuring method ",
                             UVES_BACKGR_TABLE(chip), 
                             UVES_ORDER_TABLE(flames, chip));
                    
                    is_recognized = true;
                }
                
                if (strcmp(tag, UVES_DRS_SETUP(flames, chip)) == 0)
                {
                    uves_msg_warning("DRS setup table %s has been deprecated. "
                             "Use recipe parameters "
                             "to define data reduction parameters ",
                             UVES_DRS_SETUP(flames, chip));
                    
                    is_recognized = true;
                }
            }
            flames = !flames;
        } while (flames);
        blue = !blue;
        }
        while (blue);
        
        is_recognized = is_recognized || is_raw || is_calib;

        if (is_raw)
        {
            cpl_frame_set_group(cur_frame, CPL_FRAME_GROUP_RAW) ;
        }
        else if (is_calib)
        {
            cpl_frame_set_group(cur_frame, CPL_FRAME_GROUP_CALIB) ;
        }
        else if (!is_recognized)
        {
            uves_msg_warning("Unrecognized tag %s", tag);
        }
    }

    uves_dfs_files_dont_exist(set);


  cleanup:
    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
   @brief    Remove pre- and overscan keywords
   @param    pl                  Header containing keywords to be removed

   Every possible incarnation of these keywords (3 chips, new/old format)
   will be removed if present.

*/
/*----------------------------------------------------------------------------*/
static void
remove_pre_over_scan(uves_propertylist *pl)
{
    bool blue, new_format;
    enum uves_chip chip;
    
    new_format = false;
    do {
    blue = false;
    do {
        for (chip = uves_chip_get_first(blue); 
         chip != UVES_CHIP_INVALID;
         chip = uves_chip_get_next(chip))
        {
            int n_erase_px = 0;   /* Number of erased properties */
            int n_erase_py = 0;
            int n_erase_ox = 0;
            int n_erase_oy = 0;
            
            do {
            /* This function erases only one property at a time,
             *  therefore call it until it returns 0
             */
            check( n_erase_px = 
                   uves_propertylist_erase(pl, UVES_PRESCANX(new_format, chip)),
                   "Error erasing keyword '%s'", UVES_PRESCANX(new_format, chip));
            
            check( n_erase_py = 
                   uves_propertylist_erase(pl, UVES_PRESCANY(new_format, chip)),
                   "Error erasing keyword '%s'", UVES_PRESCANY(new_format, chip));
            
            check( n_erase_ox =
                   uves_propertylist_erase(pl, UVES_OVRSCANX(new_format, chip)),
                   "Error erasing keyword '%s'", UVES_OVRSCANX(new_format, chip));
            
            check( n_erase_oy =
                   uves_propertylist_erase(pl, UVES_OVRSCANY(new_format, chip)),
                   "Error erasing keyword '%s'", UVES_OVRSCANY(new_format, chip));
            }
            while (n_erase_px > 0 ||
               n_erase_py > 0 ||
               n_erase_ox > 0 ||
               n_erase_oy > 0);
        }
        blue = !blue;
    }
    while (blue);
    
    new_format = !new_format;
    }
    while (new_format);

  cleanup:
    return;
}


/*----------------------------------------------------------------------------*/
/**
   @brief    Propagate keyword if possible
   @param    to       copy to this header
   @param    from     from from this header
   @param    name     property name

   The property is copied only if it exists, and if it wouldn't overwrite
   an existing property
*/
/*----------------------------------------------------------------------------*/

void
uves_copy_if_possible(uves_propertylist *to, const uves_propertylist *from,
         const char *name)
{
    if (!uves_propertylist_contains(to, name) &&
    uves_propertylist_contains(from, name))
    {
        uves_msg_debug("Propagating keyword %s", name);

        check_nomsg( uves_propertylist_copy_property(to, from, name) );
    }
    else
    {
        uves_msg_debug("Keyword %s not propagated", name);
    }
    
  cleanup:
    return;
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Insert an image or table frame into a frame set
   @param    frames       The frame set
   @param    object       The image or table to insert
   @param    group        Frame group (e.g. CPL_FRAME_GROUP_PRODUCT)
   @param    type         Frame type (CPL_FRAME_TYPE_TABLE or CPL_FRAME_TYPE_IMAGE)
   @param    level        Frame level (e.g. CPL_FRAME_LEVEL_FINAL)
   @param    filename     Filename of frame
   @param    tag          Frame tag
   @param    raw_header   Header of input frame. ESO.DET.* keywords (which are not
                          in the primary extension of the first input frame for
              new format inputs) will 
              be copied from this header.
   @param    primary_header Primary product header
   @param    table_header Header of table extension. Ignored if @em object is an image
   @param    parameters   Recipe parameters
   @param    recipe       Recipe ID (e.g. make_str(UVES_MBIAS_ID))
   @param    pipeline     Pipeline ID (e.g. "UVES-PIPELINE")
   @param    qc           A NULL-terminated array of tables of QC parameters 
                          for this product. One paf file is created from each
              QC table. 
   @param    start_time   Time when recipe execution started
   @param    dump_paf     whether to create a PAF file from
                          a provided qc table
   @param    stats_mask   bitmask of image statistics keywords to write,
                          for example CPL_STATS_MIN | CPL_STATS_MAX
   @return   CPL_ERROR_NONE iff OK

   This function should be called as a last step of a recipe.
   It creates a new frame from an image or a table, 
   adds the frame to the frameset and saves the image/table to disk.

   The function takes care of adding the mandatory DFS keywords
   (produced by @c cpl_dfs_setup_product_header() ) 
   to the product header before saving.

   @note This function uses a void pointer, so it is the duty of
   the calling function to make sure that the parameters @em object
   and @em type match. If @em type is CPL_FRAME_TYPE_TABLE, then @em object must 
   point to a @c cpl_table.
   If @em type is CPL_FRAME_TYPE_IMAGE, then @em object must point to a @c cpl_image.

*/
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_frameset_insert(cpl_frameset *frames, 
                     void *object, 
                     cpl_frame_group group, 
                     cpl_frame_type type, 
                     cpl_frame_level level,
                     const char *filename, 
                     const char *tag, 
                     const uves_propertylist *raw_header,
                     const uves_propertylist *primary_header, 
                     const uves_propertylist *table_header, 
                     const cpl_parameterlist *parameters, 
                     const char *recipe, 
                     const char *pipeline,
                     cpl_table **qc,
                     const char *start_time,
                     bool dump_paf,
                     unsigned stats_mask)
{
    cpl_frame *f = NULL;
    uves_propertylist *pl = NULL;
    const char *origin = "";

    passure( !(type == CPL_FRAME_TYPE_IMAGE && table_header != NULL), " ");
    passure( raw_header != NULL, " ");
    passure( primary_header != NULL, " ");

    assure( type == CPL_FRAME_TYPE_IMAGE || stats_mask == 0,
        CPL_ERROR_INCOMPATIBLE_INPUT,
        "Cannot compute image statistics on table product" );

    /* Insert the object (image or table) into frameset */
    check(( f = cpl_frame_new(),
            cpl_frame_set_filename(f, filename),    /* local filename */
            cpl_frame_set_tag     (f, tag),         /* e.g. ORDER_TABLE_BLUE */
            cpl_frame_set_type    (f, type),        /* e.g. table */
            cpl_frame_set_group   (f, group),       /* e.g. raw/product */
            cpl_frame_set_level   (f, level),       /* e.g. temporary/final */
            cpl_frameset_insert(frames, f)), "Could not insert frame into frameset");
    
    /* Pipeline id format is <PACKAGE "/" PACKAGE_VERSION>; */
    if (strchr(pipeline, '/') == NULL)
    {
        uves_msg_warning("Pipeline ID '%s' is not of format: "
                 "Pipeline-name/version", pipeline);
    }

    /* Copy provided keywords in 'primary_header' to 'pl' */
    pl = uves_propertylist_new();
 if(uves_propertylist_contains(primary_header,UVES_BSCALE)) {
                                       uves_msg("inside has bscale");
                                    }
    if (!uves_propertylist_is_empty(primary_header))
    {
        if (0)
                /* This takes (n*m) time */
                {
                    /* The regexp "" matches any string (because any string has
                       the empty string as a sub-string),
                       except on Mac, where it is an illegal regexp (for whatever reason).
                       Therefore, use ".*" to match any string */
                    
                    check( uves_propertylist_copy_property_regexp(pl, primary_header, ".*", 0),
                           "Could not copy keywords");
                }
            else
                check( uves_propertylist_append(pl, primary_header),
                       "Could not copy keywords");
    }
    
    /* Propagate/create DFS keywords */
    UVES_TIME_START("cpl_dfs_setup_product_header");
    check( uves_dfs_setup_product_header(pl,
                    f,
                    frames,
                    parameters,
                    recipe,
                    pipeline,
                    DICTIONARY),
       "Error setting up product header");
    UVES_TIME_END;
    
    /* Change origin to 'ESO' if it says 'ESO-MIDAS'
     * NOST-Definition: "The value field shall contain a character string
     *                   identifying the organization or institution responsible 
     *                   for creating the FITS file."
     */
    
    check( uves_get_property_value(pl, "ORIGIN", CPL_TYPE_STRING, &origin),
       "Error reading ORIGIN from product header");

    if (strcmp(origin, "ESO-MIDAS") == 0) 
    {
        uves_propertylist_set_string(pl, "ORIGIN", "ESO");
    }
    
    /* Add statistics keywords */
    if (type == CPL_FRAME_TYPE_IMAGE && stats_mask != 0)
    {
        check( uves_dfs_write_statistics((cpl_image *) object, pl, stats_mask),
           "Error adding image statistics keywords");
    }
    
    /* Propagate ESO.DET keywords from 'raw_header',
     * This is necessary because cpl_dfs_setup_product_header() copies
     * only from the primary extension of the first input frames
     */
    check( uves_propertylist_copy_property_regexp(pl, raw_header, "^ESO DET ", 0),
       "Could not propagate 'ESO DET*' keywords");

    /* But remove prescan, overscan keywords. 
       (Since these areas are not present in any products.) */
    check( remove_pre_over_scan(pl), 
       "Error removing pre-, overscan keywords from product header");

    /* Propagate certain keywords from 'raw_header' 
       (only if available and if not already present in product header) */
    check_nomsg( uves_copy_if_possible(pl, raw_header, UVES_AIRMASS) );
    check_nomsg( uves_copy_if_possible(pl, raw_header, UVES_IMAGETYP) );
    check_nomsg( uves_copy_if_possible(pl, raw_header, UVES_UT) );
    check_nomsg( uves_copy_if_possible(pl, raw_header, UVES_ST) );
    check_nomsg( uves_copy_if_possible(pl, raw_header, UVES_EXPTIME) );
    check_nomsg( uves_copy_if_possible(pl, raw_header, UVES_EXTNAME) );
    check_nomsg( uves_copy_if_possible(pl, raw_header, UVES_DATE) );
    check_nomsg( uves_copy_if_possible(pl, raw_header, UVES_DATAMEAN) );
    check_nomsg( uves_copy_if_possible(pl, raw_header, UVES_DATAMED) );
    check_nomsg( uves_copy_if_possible(pl, raw_header, UVES_DATARMS) );
    check_nomsg( uves_copy_if_possible(pl, raw_header, UVES_OS_EXPOI) );

    /* MIDAS internal(?): check_nomsg( uves_copy_if_possible(pl, raw_header, UVES_TMSTART) ); */
        {
            check( uves_propertylist_copy_property_regexp(
                       pl, raw_header, "^((GRAT|FILTER|WLEN)[0-9]*)$", 0),
                   "Could not propagate GRATi, FILTERi and WLENi keywords");
        }

    /* If RA,DEC do not exist, invent them and set to zero, like MIDAS */
    if ( !uves_propertylist_contains(pl, UVES_RA) )
    {
        uves_pfits_set_ra(pl, 0);
    }
    if ( !uves_propertylist_contains(pl, UVES_DEC) )
    {
        uves_pfits_set_dec(pl, 0);
    }

    /* 
     * REDLEVEL and STATUS have been deprecated, so delete them
     * along with inherited MIDAS specific keywords
     */
    {
        bool invert = false;
        uves_propertylist_erase_regexp(pl, "^("
                                       "ESO PRO (REDLEVEL|REC[0-9]+ STATUS)|"
                                       "TM-START|MIDASFTP|FILENAME)$", invert);
    }

    if( (strcmp(recipe,"uves_obs_scired") == 0) ||
        (strcmp(recipe,"uves_cal_response") == 0) ) {
        /* scired products have Angstrom units */
        if(cpl_propertylist_has(pl,"WLEN1")) {
           double wlen1=uves_pfits_get_wlen1(pl);
           wlen1 *=10.;
           uves_pfits_set_wlen1(pl,wlen1);
        }
    }



    check( uves_pfits_set_starttime(pl, start_time),
       "Could not write recipe start time");

    check( uves_pfits_set_stoptime(pl, uves_get_datetime_iso8601()),
       "Could not write recipe stop time");

    /* Create paf file from each QC table, and transfer
       all QC parameters to product header
    */
    if (qc != NULL)
    {
        int i;
        for (i = 0; qc[i] != NULL; i++)
        {
            uves_pfits_put_qc(pl, qc[i]);
                        
            if (dump_paf)
            {
                /* Exception! This is a hack */
                if (strcmp(recipe, make_str(UVES_TFLAT_ID)) == 0 && i == 1)
                {
                    /* Don't dump the science QC again */
                }
            }
        } 
    }

    UVES_TIME_START("save product");

    /* Now save with the correct header */
    if (type == CPL_FRAME_TYPE_IMAGE)
    {
        bool use_bitpix16_for_int = (strcmp(recipe, make_str(FLAMES_CAL_ORDERPOS)) == 0);
        
        check( uves_save_image((cpl_image *) object, filename, pl, 
                               use_bitpix16_for_int, true), 
           "Error saving image to file %s", filename);
    }
    else if (type == CPL_FRAME_TYPE_TABLE)                           /* Table */
    {
        check( uves_table_save((cpl_table *) object,
                  pl,                                /* Primary header */
                  table_header,                      /* Table header */
                  filename,
                  CPL_IO_DEFAULT),                   /* Create new file */
           "Error saving table to file '%s'", filename);
    }
    else
    {
        assure(false, CPL_ERROR_UNSUPPORTED_MODE, "Unsupported frame type");
    }

    UVES_TIME_END;
    
  cleanup:
    uves_free_propertylist(&pl);
    
    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
   @brief    Add image statistics keywords to header
   @param    image            the product image
   @param    header           the header to write to
   @param    stats_mask       bitmask of statistics to compute
   @return   CPL_ERROR_NONE iff OK

*/
/*----------------------------------------------------------------------------*/
void
uves_dfs_write_statistics(const cpl_image *image, uves_propertylist *header,
         unsigned stats_mask)
{
    cpl_stats *stats = NULL;

    /* Only these bits are supported, all others must be zero */
    assure( (stats_mask & (CPL_STATS_MEAN | CPL_STATS_STDEV | CPL_STATS_MEDIAN |
              CPL_STATS_MIN  | CPL_STATS_MAX)) == stats_mask,
        CPL_ERROR_UNSUPPORTED_MODE, "Cannot compute mask %d",
        stats_mask );

    UVES_TIME_START("calculate stats");

    check( stats = cpl_stats_new_from_image(
           image, stats_mask),
       "Error reading image statistics");
    
    UVES_TIME_END;
    
    if (stats_mask & CPL_STATS_MEDIAN)
    {
        check( uves_pfits_set_data_median (header, cpl_stats_get_median(stats) ), 
           "Could not write median flux");
    }
    if (stats_mask & CPL_STATS_MEAN)
    {
        check( uves_pfits_set_data_average(header, cpl_stats_get_mean  (stats) ), 
           "Could not write average flux");
    }
    if (stats_mask & CPL_STATS_STDEV)
    {
        check( uves_pfits_set_data_stddev (header, cpl_stats_get_stdev (stats) ), 
           "Could not write flux stdev");
    }
    if (stats_mask & CPL_STATS_MIN)
    {
        check( uves_pfits_set_data_min    (header, cpl_stats_get_min   (stats) ), 
           "Could not write min flux");
    }
    if (stats_mask & CPL_STATS_MIN)
    {
        check( uves_pfits_set_data_max    (header, cpl_stats_get_max   (stats) ), 
           "Could not write max flux");
    }

  cleanup:
    uves_free_stats(&stats);
    return;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Read an array written by MIDAS in HISTORY keywords
  @param    plist       Property list to read from
  @param    name        Name of array
  @param    length      (output) Length of returned array
  @param    type        (output) type of returned array
  @param    ncards      (output) if non-NULL, number of HISTORY entries used 
                        by this descriptor
  @return   The array, which must be deallocated using cpl_free, or NULL on error.

  The format of the FITS header is assumed to be

  HISTORY 'name','t...
  HISTORY  value(1) value(2) value(3) ...
  :
  :
  HISTORY  ... value(n-2) value(n-1) value(n)
  HISTORY

  where the type is determined from 't'
  R*8: (CPL_TYPE_DOUBLE)
  R*4: (CPL_TYPE_FLOAT)
  I: (CPL_TYPE_INT)
  C: (CPL_TYPE_STRING)

  For string type only 1 value is allowed, i.e. the format must match

  HISTORY 'name','C...
  HISTORY value
  HISTORY

  and any blanks will be removed from 'value'
  AMO: Why this? Temporally suppressed this feature
 */
/*----------------------------------------------------------------------------*/
void *
uves_read_midas_array(const uves_propertylist *plist, const char *name, 
                      int *length, cpl_type *type, int *ncards)
{
    void *result = NULL;
    unsigned result_size;
    int N = strlen(name);
    bool found = false;
    const char *value;
    int size;
    int i;
    const long int plist_size = uves_propertylist_get_size(plist);
   
    assure_nomsg( length != NULL, CPL_ERROR_NULL_INPUT );
    assure_nomsg(   type != NULL, CPL_ERROR_NULL_INPUT );
    for (i = 0; !found && i < plist_size; i++)
    {
      const cpl_property *p = uves_propertylist_get_const(plist, i);
      value = cpl_property_get_name(p);
      
      if (strcmp(value, "HISTORY") == 0)
        {
          
          check( value = cpl_property_get_string(p),
		 "Error reading property value");
          
          /* match the string  "'<name>','t"  */
          
          if ((int)strlen(value) >= 1+N+4 &&
	      value[0]     == '\'' &&
	      value[N+1]   == '\'' && 
	      value[N+2]   == ','  && 
	      value[N+3]   == '\'' && 
	      strncmp(value+1, name, N) == 0
	      )
	    { 
	      switch(value[N+4]) {
	      case 'R':
		/* Distinguish between 
		   "'<name>','R*4'" and
		   "'<name>','R*8'"
		*/
		*type = CPL_TYPE_DOUBLE;

		if ((int)strlen(value) >= 1+N+4+2 && value[N+4+1] == '*')
		  {
		    switch(value[N+4+2]) {
		    case '4': *type = CPL_TYPE_FLOAT; break;
		    case '8': *type = CPL_TYPE_DOUBLE; break; 
		    default:
		      assure( false, CPL_ERROR_ILLEGAL_INPUT,
			      "Unrecognized MIDAS type: 'R*%c'",
			      value[N+4+2]);
		      break;
		    }
		  }
		break;
	      case 'I': *type = CPL_TYPE_INT   ; size = sizeof(int);    break;
	      case 'C': *type = CPL_TYPE_STRING; size = sizeof(char);   break;
	      default:
		assure( false, CPL_ERROR_UNSUPPORTED_MODE,
			"Unrecognized type '%c'", value[N+4]);
		break;
	      }
	      found = true;
	    }
        }
    }
    
    assure( found, CPL_ERROR_ILLEGAL_INPUT, "Could not find '%s' in property list", name);
    
    /* 'i' is now the row immediately after first occurence of 'HISTORY   '<name>...  */
    result_size = sizeof(double) * 100;  /* realloc when/if out of memory */
    result = cpl_malloc(result_size);

    *length = 0;
    if (ncards != NULL) *ncards = 2; /* First HISTORY entry + termination HISTORY entry */
    do {
      const cpl_property *p;

      if (ncards != NULL) *ncards += 1;

      assure(i < plist_size, 
	     CPL_ERROR_ILLEGAL_INPUT, "Missing header data");
      p = uves_propertylist_get_const(plist, i);
      assure(       cpl_property_get_type(p)             == CPL_TYPE_STRING &&
		    strcmp(cpl_property_get_name(p), "HISTORY") == 0, 
		    CPL_ERROR_ILLEGAL_INPUT, "Error parsing array");
      value = cpl_property_get_string(uves_propertylist_get_const(plist, i));
    
      uves_msg_debug("Parsing '%s'", value);

      if (*type == CPL_TYPE_STRING)
	{
	  assure( strlen(value) < 100, CPL_ERROR_UNSUPPORTED_MODE, 
		  "String too long. Max size is 100");

	  /* Remove any blanks from the string
	     (e.g. convert "0 1 2" to "012")
	  */
	  {
	    int len = strlen(value);
	    int j = 0;
	    int k;
	    for (k = 0; k <= len; k++)  /* including final '\0' */
	      {
		//if (value[k] != ' '){
		((char*)result)[j] = value[k];
		j++;
		//      }
	      }
	    *length = j-1;
	  }

	  uves_msg_debug("Converted '%s' to '%s'",
			 value, (char*)result);
                
	  /* done parsing */
	  value = "";
	}
        
      else { /* numerical types */
	if (strcmp(value, "") != 0) {
	  double numberd = -1; /* suppres warning */
	  int numberi = -1;
	  float numberf = -1;
	  const int base = 10;
	  char *next = (char *) value;

	  do {
            /* ignore OUTPUTI(1)- N,no.of data, */
	    switch(*type) {
	    case CPL_TYPE_DOUBLE:
	      numberd = strtod(value, &next);
	      uves_msg_debug("Got %g, remaining: '%s'", numberd, next);
	      break;
	    case CPL_TYPE_FLOAT:
	      numberf = strtod(value, &next); // C99: strtof(value, &next);
	      uves_msg_debug("Got %g, remaining: '%s'", numberf, next);
	      break;
	    case CPL_TYPE_INT:
	      numberi = strtol(value, &next, base);
	      uves_msg_debug("Got %d, remaining: '%s'", numberi, next);
	      break;
	    default:
	      passure(false, " ");
	    }
                    
            if (next != value)
	      {
                /* A prefix of the string could be converted */
                (*length)++;
		if (*length * sizeof(double) > result_size)
		  {
		    result_size *= 2;
		    result = cpl_realloc(result, result_size);
		  }

		switch(*type) {
		case CPL_TYPE_DOUBLE:
		  ((double *)result)[*length-1] = numberd;
		  break;
		case CPL_TYPE_FLOAT:
		  ((float *)result)[*length-1] = numberf;
		  break;
		case CPL_TYPE_INT:
		  ((int    *)result)[*length-1] = numberi;
		  break;
		default:
		  passure(false, " ");
		}

                value = next;
                            
		switch(*type) {
		case CPL_TYPE_DOUBLE:
		  numberd = strtod(value, &next);
		  uves_msg_debug("Got %g, remaining: '%s'", numberd, next);
		  break;
		case CPL_TYPE_FLOAT:
		  numberf = strtod(value, &next); // C99: strtof(value, &next);
		  uves_msg_debug("Got %g, remaining: '%s'", numberf, next);
		  break;
		case CPL_TYPE_INT:
		  numberi = strtol(value, &next, base);
		  uves_msg_debug("Got %d, remaining: '%s'", numberi, next);
		  break;
		default:
		  passure(false, " ");
		}
	      }
	  } while (next != value);
        }
      }/* if numerical type */
        
      i++;

      assure( strcmp(value, "") == 0, CPL_ERROR_ILLEGAL_INPUT,
	      "Cannot parse %s descriptor %s, remaining string: '%s'", 
	      uves_tostring_cpl_type(*type), name, value);
    
      /* Find out if we can continue parsing the next HISTORY keyword */
      if (i < plist_size)
        {
	  p = uves_propertylist_get_const(plist, i);
	  if (cpl_property_get_type(p) == CPL_TYPE_STRING &&
	      strcmp(cpl_property_get_name(p), "HISTORY") == 0)
            {
	      value = cpl_property_get_string(
					      uves_propertylist_get_const(plist, i));

	      if (*type == CPL_TYPE_STRING)
		{
		  if (strcmp(value, "") != 0) {
		    uves_msg_debug("String array %s with length > 1 found. Ignoring remaining values", name);
		    while (strcmp(value, "") != 0 && i+1 < plist_size) {
		      i++;
		      p = uves_propertylist_get_const(plist, i);
		      value = cpl_property_get_string(
						      uves_propertylist_get_const(plist, i));
		      if (ncards != NULL) *ncards += 1;
		    }
		  }
		}
	    }
	}
      
    } while (strcmp(value, "") != 0);
    
 cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE)
      {
	cpl_free(result); result = NULL;
      }
    return result;
}


/*----------------------------------------------------------------------------*/
    /**
       @brief    Save a table to disk
       @param    description    Textual description of table, used only for messaging.
       Ignored if NULL.
       @param    filename_prefix Filename prefix. The filename suffix is determined by the
       @em chip id, @em trace and @em window
   @param    table          Table to save
   @param    chip           CCD chip
   @param    trace          The trace number. Set to -1 if not applicable.
   @param    window         The window number. Set to -1 if not applicable.
   @param    pheader        Primary header
   @param    eheader        Table extension header
   @return   CPL_ERROR_NONE iff OK

   This function can be used to save a table without using the frame set infrastructure.

*/
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_save_table_local(const char *description, const char *filename_prefix,
              const cpl_table *table, 
              enum uves_chip chip, int trace, int window,
              const uves_propertylist *pheader, const uves_propertylist *eheader)
{
    char *filename = NULL;

    check( filename = uves_local_filename(filename_prefix, chip, trace, window),
       "Error getting filename");

    check( uves_table_save(table, pheader, eheader, filename, CPL_IO_DEFAULT), 
       "Error saving table to file '%s'", filename);
    
    if (description != NULL) uves_msg("%s saved to '%s'", description, filename);
    
  cleanup:
    cpl_free(filename);
    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Save an image to disk
   @param    description    Textual description of image, used for messaging only.
                             Ignored if NULL.
   @param    filename_prefix Filename prefix. The filename suffix is determined by the
                            @em chip id, @em trace and @em window
   @param    image          Image to save
   @param    chip           CCD chip
   @param    trace          The trace number. Set to -1 if not applicable.
   @param    window         The window number. Set to -1 if not applicable.
   @param    plist          Image header
   @param    use_bitpix16_for_int   @see uves_save_image
   @return   CPL_ERROR_NONE iff OK

   This function can be used to save an image to the local directory 
   without using the frame set infrastructure.

   Also see @c uves_save_image().   

*/
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_save_image_local(const char *description, const char *filename_prefix, 
                      const cpl_image *image, 
                      enum uves_chip chip, int trace, int window,
                      const uves_propertylist *plist,
                      bool use_bitpix16_for_int)
{
    char *filename = NULL;
    
    check( filename = uves_local_filename(filename_prefix, chip, trace, window),
       "Error getting filename");
    
    check( uves_save_image(image, filename, plist, use_bitpix16_for_int, true),
           "Error saving image to file '%s'", filename);
    if (description != NULL) uves_msg("%s saved to '%s'", description, filename);
    
  cleanup:
    cpl_free(filename);
    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
   @brief    Load image and header
   @param    f              frame with image
   @param    plane          image plane
   @param    extension      FITS extension
   @param    header         (output) If non-NULL, FITS header of specified
                            extension
   @return   newly allocated image of automatically determined CPL type
*/
/*----------------------------------------------------------------------------*/
cpl_image *uves_load_image(const cpl_frame *f,
               int plane,
               int extension,
               uves_propertylist **header)
{
    cpl_image *image = NULL;
    uves_propertylist *plist = NULL;
    const char *filename;
    int bitpix;
    cpl_type type;
    int naxis=0;
    cpl_vector * vector=NULL;

    
    assure_nomsg( f != NULL, CPL_ERROR_NULL_INPUT );
 
    assure( cpl_frame_get_type(f) == CPL_FRAME_TYPE_IMAGE,
        CPL_ERROR_TYPE_MISMATCH, "Wrong type: %s",
        uves_tostring_cpl_frame_type(cpl_frame_get_type(f)));

    filename = cpl_frame_get_filename(f);

    check( plist = uves_propertylist_load(filename, extension),
       "Could not load header from %s extension %d", 
       filename, extension);
    
    check( bitpix = uves_pfits_get_bitpix(plist),
       "Could not read BITPIX from %s extension %d",
       filename, extension);
    
    if      (bitpix == -32) type = CPL_TYPE_FLOAT;
    else if (bitpix == -64) type = CPL_TYPE_DOUBLE;
    else if (bitpix ==  32) type = CPL_TYPE_INT;
    else if (bitpix ==  16) type = CPL_TYPE_INT;
    else
    {
        assure( false, CPL_ERROR_UNSUPPORTED_MODE,
            "No CPL type to represent BITPIX = %d", bitpix);
    }

    check( naxis = uves_pfits_get_naxis(plist),
           "could not get NAXIS" );

    if( naxis == 1) {

      check( vector = cpl_vector_load(filename,extension),
             "Could not load vector from extension %d of file '%s' ",
             extension, filename);
      cknull(image=uves_vector_to_image(vector,type),
	     "could not convert vector to image");
    } else {


      check( image = cpl_image_load(filename,
				    type,
				    plane,
				    extension),
	     "Could not load image from extension %d of file '%s' ", 
	     extension, filename);

    }

    if (header != NULL)
    {
        *header = uves_propertylist_duplicate(plist);
    }

  cleanup:
    uves_free_vector(&vector);
    uves_free_propertylist(&plist);
    return image;
}
/*----------------------------------------------------------------------------*/
/**
   @See  uves_load_image()
*/
/*----------------------------------------------------------------------------*/

cpl_image *uves_load_image_file(const char *filename,
                                int plane,
                                int extension,
                                uves_propertylist **header)
{
    cpl_image *i;
    cpl_frame *f = cpl_frame_new();
    cpl_frame_set_filename(f, filename);
    cpl_frame_set_type(f, CPL_FRAME_TYPE_IMAGE);

    i = uves_load_image(f, plane, extension, header);
    
    uves_free_frame(&f);

    return i;
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Save an image to disk
   @param    image          image to save, or NULL to create an empty data unit
   @param    filename       filename
   @param    plist          image header or NULL
   @param    use_bitpix16_for_int  if true, an integer image is saved
                                   as 16 bit unsigned, otherwise 32 bit signed.
                                   This flag only affects integer images
   @param    save1d                If true the image is saved with NAXIS=1
                                   if the height is 1
   @return   CPL_ERROR_NONE iff OK

   This function is a wrapper of @c cpl_image_save().
   
   It uses CPL_IO_DEFAULT as output mode (i.e. create a new file or overwrite 
   existing file). The pixel depth (bpp) is automatically determined from the
   image type.

   If the mandatory FITS keywords (BITPIX, NAXIS, ...) are missing from the 
   input header, they will be added by @c cpl_image_save()

   Also see @c uves_frameset_insert().   

*/
/*----------------------------------------------------------------------------*/
void
uves_save_image(const cpl_image *image, const char *filename, const uves_propertylist *plist,
                bool use_bitpix16_for_int, bool save1d)
{
    cpl_type_bpp bpp;
    cpl_type t;
    const cpl_vector *image_1d = NULL;
    uves_propertylist *header = NULL;
    cpl_image *thresholded = NULL;
    cpl_image *thresholded_double = NULL;
    
    if (image == NULL) {
        check( uves_image_save(image, filename, CPL_BPP_IEEE_FLOAT, plist, CPL_IO_DEFAULT), 
               "Error saving NULL image to file '%s'", filename);
    }
    else {
        check( t = cpl_image_get_type(image), "Error reading image type");
        if      (t == CPL_TYPE_FLOAT ) bpp = CPL_BPP_IEEE_FLOAT;
        else if (t == CPL_TYPE_DOUBLE) bpp = CPL_BPP_IEEE_FLOAT;
        /* Internal computations in double precision,
           save as single precision */

        /* Some FLAMES images are BITPIX=16 (ORDEF), 
           some are 32 SLIT_FF_COM_REDL
        */
        else if (t == CPL_TYPE_INT   ) {
            if (use_bitpix16_for_int) bpp = CPL_BPP_16_UNSIGNED;
            else bpp = CPL_BPP_32_SIGNED;
        }
        else assure(false, CPL_ERROR_UNSUPPORTED_MODE,
                    "Unsupported image type '%s'", uves_tostring_cpl_type(t));


        thresholded = cpl_image_duplicate(image);
        assure_mem( thresholded );

        if (t == CPL_TYPE_DOUBLE)
            {
                passure( bpp == CPL_BPP_IEEE_FLOAT, "%d", bpp);

                /* Avoid infinities that would happen when casting
                   double -> float
                   by thresholding the image to +-FLT_MAX (or, better
                   a little less than FLT_MAX just to be sure).
        
                   (This is not a really nice solution because it solves the
                   problem (too large/small values) after it is introduced
                   (rather than avoiding it), but a general solution of the
                   problem would probably mean guarding every arithmetic
                   operation with range checks.)
                */
        
                check_nomsg( cpl_image_threshold(thresholded,
                                                 -FLT_MAX, FLT_MAX,
                                                 -FLT_MAX, FLT_MAX) );

                /* Also get rid of NaN, set to zero (what else?) */
                {
                    double *data = cpl_image_get_data_double(thresholded);
                    int nx = cpl_image_get_size_x(thresholded);
                    int ny = cpl_image_get_size_y(thresholded);
                    int x, y;
        
                    for (y = 0; y < ny; y++)
                        for (x = 0; x < nx; x++)
                            {
                                if (irplib_isnan(data[x + y*nx]))
                                    {
                                        data[x + y*nx] = 0;
                                    }
                            }
                }
            }

        if (save1d && 
            cpl_image_get_size_y(thresholded) == 1 &&
            (t == CPL_TYPE_DOUBLE ||
             t == CPL_TYPE_FLOAT)) {
            
            bool invert = false;
            if (plist != NULL)
                {
                    header = uves_propertylist_duplicate(plist);
            
                    uves_propertylist_erase_regexp(header, "^CDELT2$", invert);
                    uves_propertylist_erase_regexp(header, "^CRPIX2$", invert);
                    uves_propertylist_erase_regexp(header, "^CRVAL2$", invert);
                    uves_propertylist_erase_regexp(header, "^CTYPE2$", invert);
                    if(uves_propertylist_has(plist,"CDELT1")) {
                      double cdelt1=uves_pfits_get_cdelt1(header);
                      uves_pfits_set_cd11(header,cdelt1);
                    }
                }
            else
                {
                    header = NULL;
                }
        
            /* Image type must be double, before wrapping it
               in a vector */
            if (t == CPL_TYPE_FLOAT) {
                thresholded_double = cpl_image_cast(thresholded, CPL_TYPE_DOUBLE);
            }
            else {
                thresholded_double = cpl_image_duplicate(thresholded);
            }
        
            passure( cpl_image_get_type(thresholded_double) == CPL_TYPE_DOUBLE, "%d",
                     cpl_image_get_type(thresholded_double));
        
            image_1d = cpl_vector_wrap(
                cpl_image_get_size_x(thresholded_double),
                cpl_image_get_data_double(thresholded_double));

            check( uves_vector_save(image_1d, filename, bpp, header, CPL_IO_DEFAULT),
                   "Error saving vector to file '%s'", filename );
        }
        else
            {

            if (plist != NULL) {
                if(uves_propertylist_has(plist,"CDELT1")) {
                  double cdelt1=uves_pfits_get_cdelt1(plist);
                  uves_pfits_set_cd11(plist,cdelt1);
                    uves_pfits_set_cd12(plist,0);
                }
                if(uves_propertylist_has(plist,"CDELT2")) {
                  double cdelt2=uves_pfits_get_cdelt2(plist);
                  uves_pfits_set_cd21(plist,0);
                  uves_pfits_set_cd22(plist,cdelt2);
                }
            }


                check( uves_image_save(thresholded, filename, bpp, plist, CPL_IO_DEFAULT), 
                       "Error saving image to file '%s'", filename);
            }
    }
    
  cleanup:
    uves_unwrap_vector_const(&image_1d);
    uves_free_propertylist(&header);
    uves_free_image(&thresholded);
    uves_free_image(&thresholded_double);
    
    return;
}


/*----------------------------------------------------------------------------*/
/**
   @brief    Save an image to disk
   @param    iml          imagelist to save
   @param    filename       filename
   @param    plist          image header or NULL
   @return   CPL_ERROR_NONE iff OK

   This function is a wrapper of @c cpl_imagelist_save().
   
   It uses CPL_IO_DEFAULT as output mode (i.e. create a new file or overwrite 
   existing file). The pixel depth (bpp) is automatically determined from the
   image type.

   If the mandatory FITS keywords (BITPIX, NAXIS, ...) are missing from the 
   input header, they will be added by @c cpl_image_save()

   Also see @c uves_frameset_insert().   

*/
/*----------------------------------------------------------------------------*/
void
uves_save_imagelist(const cpl_imagelist *iml, const char *filename, const uves_propertylist *plist)
{
    const cpl_image* img=NULL;
    cpl_type_bpp bpp;
    cpl_type t;
    const cpl_vector *image_1d = NULL;
    uves_propertylist *header = NULL;
    cpl_imagelist *thresholded = NULL;

    int nx = 0;
    int ny = 0;
    int nz = 0;

    
    cknull(iml,"Null input image");
    check(img=cpl_imagelist_get_const(iml,0),"error reading image");

    check_nomsg( nx = cpl_image_get_size_x(img));
    check_nomsg( ny = cpl_image_get_size_y(img));
    check_nomsg( nz = cpl_imagelist_get_size(iml));

    check( t = cpl_image_get_type(img), "Error reading image type");
    if      (t == CPL_TYPE_FLOAT ) bpp = CPL_BPP_IEEE_FLOAT;
    else if (t == CPL_TYPE_DOUBLE) bpp = CPL_BPP_IEEE_FLOAT;
    /* Internal computations in double precision,
       save as single precision */
    else if (t == CPL_TYPE_INT   ) bpp = CPL_BPP_16_UNSIGNED;
    else assure(false, CPL_ERROR_UNSUPPORTED_MODE,
        "Unsupported image type '%s'", uves_tostring_cpl_type(t));


    thresholded = cpl_imagelist_duplicate(iml);
    assure_mem( thresholded );

    if (t == CPL_TYPE_DOUBLE)
    {
        passure( bpp == CPL_BPP_IEEE_FLOAT, "%d", bpp);

        /* Avoid infinities that would happen when casting
               double -> float
           by thresholding the image to +-FLT_MAX (or, better
           a little less than FLT_MAX just to be sure).
        
           (This is not a really nice solution because it solves the
           problem (too large/small values) after it is introduced
           (rather than avoiding it), but a general solution of the
           problem would probably mean guarding every arithmetic
           operation with range checks.)
        */
        
        check_nomsg( cpl_imagelist_threshold(thresholded,
                         -FLT_MAX, FLT_MAX,
                         -FLT_MAX, FLT_MAX) );



        /* Also get rid of NaN, set to zero (what else?) */
        {
        int x, y, z;


        for (z = 0; z < nz; z++) {
          cpl_image* ima=cpl_imagelist_get(thresholded,z);
          double* data = cpl_image_get_data_double(ima);

          for (y = 0; y < ny; y++) {
            for (x = 0; x < nx; x++) {
              if (irplib_isnan(data[x + y*nx])) {
            data[x + y*nx] = 0;    
              }
            }
          }
        }
        }
    }
    if (nz == 1 && t == CPL_TYPE_DOUBLE)
    /* To support other types (float, int) we would
       need to convert to double first */
    {

        if (plist != NULL)
        {
            header = uves_propertylist_duplicate(plist);
            bool invert = false;
            uves_propertylist_erase_regexp(header, "^CDELT3$", invert);
            uves_propertylist_erase_regexp(header, "^CRPIX3$", invert);
            uves_propertylist_erase_regexp(header, "^CRVAL3$", invert);
            uves_propertylist_erase_regexp(header, "^CTYPE3$", invert);
        }
        else
        {
            header = NULL;
        }
        /*
        image_1d = cpl_vector_wrap(nx,
                        cpl_image_get_data_double_const(thresholded));
        
        check( uves_vector_save(image_1d, filename, bpp, header, CPL_IO_DEFAULT),
           "Error saving vector to file '%s'", filename );
        */
        
    }
    else
    {
        check( uves_imagelist_save(thresholded, filename, bpp, plist, CPL_IO_DEFAULT), 
           "Error saving image to file '%s'", filename);
    }
    
  cleanup:
    uves_unwrap_vector_const(&image_1d);
    uves_free_propertylist(&header);
    uves_free_imagelist(&thresholded);

    return;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Save a polynomial to a table extension
  @param    p          The polynomial to save
  @param    filename   Name of file to save to
  @param    header     Header of table extension (may be NULL)
  @return   CPL_ERROR_NONE iff OK

  The file must already exist.

  A new table extension containing a representation of the polynomial
  is added to the file.

 */
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_save_polynomial(polynomial *p, const char *filename, const uves_propertylist *header)
{
    cpl_table *t = NULL;

    check( t = uves_polynomial_convert_to_table(p), "Error converting polynomial to table");
    
    check( uves_table_save(t, 
              NULL,                       /* Primary header, ignored when 
                             mode = CPL_IO_EXTEND */
              header,                     /* Table header */
              filename,
              CPL_IO_EXTEND),             /* Append to existing file */
       "Error saving table to file '%s'", filename);
    
  cleanup:
    uves_free_table(&t);
    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Load a polynomial from a table extension
  @param    filename   Name of FITS file containg the polynomial
  @param    extension  The polynomial is read from this table extension
  @return   The polynomial, or NULL on error.

 */
/*----------------------------------------------------------------------------*/
static polynomial *
load_polynomial(const char* filename, int extension)
{
    polynomial *p = NULL;  /* Result */
    cpl_table  *t = NULL;
    
    check(t = cpl_table_load(filename,
                 extension,
                 1),                   /* Mark identified 
                              invalid null values (1=yes) */
      "Error loading polynomial from extension %d of file '%s'", extension, filename);

    assure( uves_erase_invalid_table_rows(t, NULL) == 0, 
        CPL_ERROR_ILLEGAL_INPUT, "Table contains invalid rows");
    
    check(p = uves_polynomial_convert_from_table(t), "Error converting table to polynomial");

  cleanup:
    uves_free_table(&t);
    if (cpl_error_get_code() != CPL_ERROR_NONE)
    uves_polynomial_delete(&p);
    return p;
}
/*----------------------------------------------------------------------------*/
/**
 @brief    Identify red/blue chip
   @param    frames        The input frame set
   @param    blue_tag      Tag of blue frames
   @param    red_tag       Tag of red frames
   @param    blue          (output) Flag indicating if the identified raw frame 
                           is blue (true) or red (false)
   
   @return   The one of @em blue_tag and @em red_tag that is present in the frameset,
             or NULL on error.

   The function checks that exactly one of @em blue_tag and @em red_tag is
   present in the frame set, and reports which one. 
*/
/*----------------------------------------------------------------------------*/
static const char *
identify_arm(const cpl_frameset *frames, const char *blue_tag, const char *red_tag,
         bool *blue)
{
    const char *tag = NULL; /* Result */
    
    const cpl_frame *frame = NULL;
    
    passure( frames != NULL, "");
    assure (!cpl_frameset_is_empty(frames), CPL_ERROR_ILLEGAL_INPUT, "No input frames");
    
    /* Identify blue/red arm */
    frame = cpl_frameset_find_const(frames, blue_tag);
    *blue = (frame != NULL);
    
    if (frame == NULL)
    {
        frame = cpl_frameset_find_const(frames, red_tag);
    }
    
    assure( frame != NULL, CPL_ERROR_ILLEGAL_INPUT, 
        "No valid input frames "
        "('%s' or '%s') in frame set",
        blue_tag, red_tag);
    
    assure( cpl_frameset_find_const(frames, blue_tag) == NULL ||
        cpl_frameset_find_const(frames, red_tag)  == NULL,
        CPL_ERROR_INCOMPATIBLE_INPUT,
        "Multiple types of input frames ('%s' and '%s') in frame set",
        blue_tag, red_tag);
    
    tag = cpl_frame_get_tag(frame);
    
    uves_msg("Input frames are '%s'", tag);
    

  cleanup:
    return tag;
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Crop and rotate an UVES image
   @param    image         image to process
   @param    header        image header
   @param    chip          CCD chip
   @param    redl_header   FITS header for lower chip (needed for CRVAL computation)
   @param    new_format    flat indicating if one image contains both red chips 
                           (false, old format),
                           or if there is one chip per image (true, new format)
   @param    out_header    (output) FITS header containing the proper
                           CDELT, CUNIT et al. keywords
   @return   pre-processed image or NULL on error

   The function reads the pre- and overscan keywords from the provided header. The image
   is then extracted and rotated into standard orientation, so that order numbers
   increase from bottom to top and wavelengths increase from left to right.
*/
/*----------------------------------------------------------------------------*/
cpl_image *
uves_crop_and_rotate(const cpl_image *image, const uves_propertylist *header,
             enum uves_chip chip,
             const uves_propertylist *redl_header, 
             bool new_format, uves_propertylist **out_header)
{
    cpl_image *result = NULL;
    int prescanx, ovrscanx;
    cpl_size nx, ny;
    int x_0, y_0, x_1, y_1; /* Extracted area (inclusive) in 
                   FITS convention (i.e. counting from 1) */

    const char *ctype1, *ctype2; /* Geometry */
    const char *cunit1, *cunit2; /* Units */
    const char *bunit;
    double bscale=0;
    double crval1, crval2;
    double crpix1, crpix2;
    double cdelt1, cdelt2;


    passure( image != NULL, " ");
    passure( header != NULL, " ");
    passure( out_header != NULL, " ");
    
    nx = cpl_image_get_size_x(image);
    ny = cpl_image_get_size_y(image);


    /* Determine pre- and overscan areas */
    check( prescanx = uves_pfits_get_prescanx(header, chip), "Could not read x-prescan info" );
    check( ovrscanx = uves_pfits_get_ovrscanx(header, chip), "Could not read x-overscan info");
  
    /* Don't try to read the y pre- and overscan regions, which should be zero for UVES.
       The keywords are not present in older UVES data. */

    /* Read geometry */
    check( ctype1 = uves_pfits_get_ctype1(header), "Error reading keyword");
    check( ctype2 = uves_pfits_get_ctype2(header), "Error reading keyword");
    check( crval1 = uves_pfits_get_crval1(header), "Error reading keyword");
    check( crval2 = uves_pfits_get_crval2(header), "Error reading keyword");
    check( crpix1 = uves_pfits_get_crpix1(header), "Error reading keyword");
    check( crpix2 = uves_pfits_get_crpix2(header), "Error reading keyword");
    check( cdelt1 = uves_pfits_get_cdelt1(header), "Error reading keyword");
    check( cdelt2 = uves_pfits_get_cdelt2(header), "Error reading keyword");
    if (uves_propertylist_contains(header, UVES_BUNIT))
    {
        bunit = uves_pfits_get_bunit(header);
    }
    else
    {
        bunit = " ";
    }
    if (uves_propertylist_contains(header, UVES_BSCALE))
    {
        bscale = uves_pfits_get_bscale(header);
    }
    else
    {
       bscale = 0;
    }

    if (uves_propertylist_contains(header, UVES_CUNIT1))
    {
        cunit1 = uves_pfits_get_cunit1(header);
    }
    else
    {
        cunit1 = " ";
    }
    if (uves_propertylist_contains(header, UVES_CUNIT2))
    {
        cunit2 = uves_pfits_get_cunit2(header);
    }
    else
    {
        cunit2 = " ";
    }
    

    /* Crop the image */
    {
    y_0 = 1;
    y_1 = ny;
    if (new_format || chip == UVES_CHIP_BLUE)
        {
        x_0 = prescanx + 1;
        x_1 = nx - ovrscanx;
        }
    else /* red, old format */
        {
        if (chip == UVES_CHIP_REDU)
            {
            x_0 = prescanx + 1;
            x_1 = nx/2 - ovrscanx;
            }
        else
            { /* lower */
            x_0 = nx/2 + prescanx + 1;
            x_1 = nx - ovrscanx;
            }
        }
    check( result = cpl_image_extract(image, x_0, y_0, x_1, y_1), "Could not crop image");
    crpix1 = crpix1 - (x_0 - 1);
    crpix2 = crpix2 - (y_0 - 1);
    nx = (x_1 - x_0) + 1;
    ny = (y_1 - y_0) + 1;
    }

    UVES_TIME_START("Rotation");
    /* ... is a bit slow, and there's probably nothing to
       do about as it involves moving data between remote
       places in memory.
    */

    /* Rotate the image into standard orientation */
    {
    int crpix1_old = crpix1;
    int crpix2_old = crpix2;
    int crval1_old = crval1;
    int crval2_old = crval2;
    int cdelt1_old = cdelt1;
    int cdelt2_old = cdelt2;
    const char *ctype1_old = ctype1;
    const char *ctype2_old = ctype2;

    if (chip == UVES_CHIP_BLUE)
        {
        /* 90 deg counterclockwise rotation */
        check( cpl_image_turn(result, -1), "Could not turn image");
        
        crpix1 = ny - (crpix2_old - 1); /* Note: old value of ny */
        crpix2 = crpix1_old;
        crval1 = crval2_old;
        crval2 = crval1_old;
        }
    else 
        {
        /* Red */
        /* Flip image around y=-x */
        check( cpl_image_flip(result, 3), "Could not flip image");

        crpix1 = ny - (crpix2_old - 1); /* Note: old value of nx, ny */
        crpix2 = nx - (crpix1_old - 1);
        crval1 = crval2_old;
        crval2 = crval1_old;
        }


    /* Always swap these ones */
    ctype1 = ctype2_old;
    ctype2 = ctype1_old;
    cdelt1 = cdelt2_old;
    cdelt2 = cdelt1_old;
    }

    UVES_TIME_END;

    /* Here we should use the CROTAi keywords to 
       properly describe the new rotation */
    
    /* Instead, redefine CRVAL as in the following, on request from DFO */

    crpix1 = 1;
    crpix2 = 1;
    if (chip == UVES_CHIP_BLUE || chip == UVES_CHIP_REDL)
    {
        crval1 = 1;
        crval2 = 1;
    }
    else 
    {
            int physical_gap_between_chips = 64; /* Pixels. Unbinned. Hardcoded. */


        passure( chip == UVES_CHIP_REDU , "%d", chip );
        
        crval1 = 1;
        
        /* Set CRVAL2 = REDL_height - REDL_overscan - REDL_prescan + gap. */
        if (new_format)
        {

            check( crval2 = 1 +
               (uves_pfits_get_naxis1(redl_header) -
                uves_pfits_get_ovrscanx(redl_header, UVES_CHIP_REDL) -
                uves_pfits_get_prescanx(redl_header, UVES_CHIP_REDL)) *
               uves_pfits_get_cdelt1(redl_header) +
                           physical_gap_between_chips,
               "Error reading REDL chip geometry");

            uves_msg_debug("Setting CRVAL2 = 1 + (%d - %d - %d) * %f + %d = %f",
                   uves_pfits_get_naxis1(redl_header),
                   uves_pfits_get_ovrscanx(redl_header, UVES_CHIP_REDL),
                   uves_pfits_get_prescanx(redl_header, UVES_CHIP_REDL),
                   uves_pfits_get_cdelt1(redl_header),
                                   physical_gap_between_chips, crval2);
        }
        else
        {

            /* old format */
            check( crval2 = 1 +
               (uves_pfits_get_naxis1(header)/2 -
                uves_pfits_get_ovrscanx(redl_header, UVES_CHIP_REDL) -
                uves_pfits_get_prescanx(redl_header, UVES_CHIP_REDL)) *
               uves_pfits_get_cdelt1(redl_header) +
                           physical_gap_between_chips,
               "Error reading REDL chip geometry");

            uves_msg_debug("Setting CRVAL2 = 1 + (%d - %d - %d) * %f + %d = %f",
                   uves_pfits_get_naxis1(header)/2, 
                   uves_pfits_get_ovrscanx(redl_header, UVES_CHIP_REDL),
                   uves_pfits_get_prescanx(redl_header, UVES_CHIP_REDL),
                   uves_pfits_get_cdelt1(redl_header),
                                   physical_gap_between_chips, crval2);
        }

    }


    /* Update header with new geometry */
    check( *out_header = uves_initialize_image_header(ctype1, ctype2, 
                                                      cunit1, cunit2,
                                                      bunit,bscale,
                              crval1, crval2,
                              crpix1, crpix2,
                              cdelt1, cdelt2),
       "Error initializing header");

    //check(uves_propertylist_copy_property_regexp(*out_header, header,
    //						"^ESO ", 0),
    //   "Error copying hieararch keys");


    uves_msg("Raw image cropped and rotated from %" CPL_SIZE_FORMAT "x%" CPL_SIZE_FORMAT " to %" CPL_SIZE_FORMAT "x%" CPL_SIZE_FORMAT "",
         nx, ny,
         cpl_image_get_size_x(result),
         cpl_image_get_size_y(result));     

  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE)
    {
        uves_free_image(&result);
        if (out_header != NULL)
        {
            uves_free_propertylist(out_header);
        }
    }

    return result;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Print warning if calibration/input chip names don't match
  @param    calib_header     calibration FITS header
  @param    raw_chip_name    chip name from raw FITS header
  @param    chip             CCD chip
  @return   CPL_ERROR_NONE iff OK

  The UVES blue chip has was replaced at least one time. As a minimum we
  shouldn't blindly process science data for the new chip with calibration data for
  the old chip. (But a general solution to this problem (mismatching raw/calib
  data) is outside the scope of recipes and better left to the caller (Data
  Organizer, ...))
 */
/*----------------------------------------------------------------------------*/
void
uves_warn_if_chip_names_dont_match(const uves_propertylist *calib_header, 
                  const char *raw_chip_name, enum uves_chip chip)
{
    const char *calib_chip_name;
    bool mismatch = false;

    check( calib_chip_name = uves_pfits_get_chipid(calib_header, chip),
       "Could not read chip name of calibration data");


    /* Ignore leading/trailing blanks when comparing name strings.
     * (The following is O(n^2) where n is the string length, 
     * but that's ok because the strings stored in a FITS card are short).
     */
    {
    unsigned int calib_first, calib_last;  /* inclusive */
    unsigned int raw_first, raw_last;
    
    calib_first = 0;
    raw_first = 0;
    while (calib_first < strlen(calib_chip_name) - 1 && calib_chip_name[calib_first] == ' ' )
        {
        calib_first++;
        }
    while (raw_first < strlen(raw_chip_name) - 1 && raw_chip_name[raw_first] == ' ' )
        {
        raw_first++;
        }

    calib_last = strlen(calib_chip_name) - 1;
    raw_last = strlen(raw_chip_name) - 1;
    while (calib_chip_name[calib_last] == ' ' && calib_last > 0)
        {
        calib_last--;
        }
    while (raw_chip_name[raw_last] == ' ' && raw_last > 0)
        {
        raw_last--;
        }

    /* Compare substrings */
    if (calib_last - calib_first != raw_last - raw_first)
        {
        mismatch = true;
        }
    else
        {
        unsigned int i;
        
        for (i = 0; i <= (calib_last - calib_first); i++)
            {
            if (raw_chip_name[raw_first + i] != 
                calib_chip_name[calib_first + i])
                {
                mismatch = true;
                }
            }
        }
    }


    if (mismatch)
    {
        uves_msg_warning("Calibration frame chip ID '%s' does "
                 "not match raw frame chip ID '%s'",
                 calib_chip_name, raw_chip_name);
    }

  cleanup:
    return;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Loads a UVES raw image
  @param    filename       Filename of raw frame
  @param    type           Output type
  @param    flames         load a FLAMES frame?
  @param    blue           Flag indicating if the raw frame is blue (true) or red (false)
  @param    type           Load the image as this type
  @param    raw_image      (output) The loaded image(s)
  @param    raw_header     (output) The raw image header(s)
  @param    rotated_header (output) Header(s) containing the proper CDELT, CRPIX... 
                           keywords for the rotated frame (and nothing else)
  @return   CPL_ERROR_NONE iff OK

  The function loads a raw blue or red image from the specified file.
  Results are returned through the arrays @em raw_image and @em raw_header,
  which must be size 2 arrays. If red, array index 0 is REDL and array index
  1 is REDU. If blue, array index 1 is not used.

  The loaded images are rotated into standard orientation, and pre- and
  overscan areas are removed (see @c uves_crop_and_rotate() ).  
 */
/*----------------------------------------------------------------------------*/

static cpl_error_code
load_raw_image(const char *filename, 
               cpl_type type,
               bool flames,
               bool blue,
               cpl_image *raw_image[2],
               uves_propertylist *raw_header[2], 
               uves_propertylist *rotated_header[2])
{

 
    cpl_image *image = NULL;
    uves_propertylist *primary_header = NULL;
    uves_propertylist *ext_header = NULL;
    int extension, nextensions;
    bool new_format;
    int plane = 0;   /* Only one plane in FLAMES/UVES raw files */

    cpl_image* image1=NULL;
    cpl_image* image2=NULL;
    int sx=0;
    int sy=0;


    /* Initialize parameters */
    raw_image[0] = NULL;
    raw_image[1] = NULL;
    raw_header[0] = NULL;
    raw_header[1] = NULL;
    rotated_header[0] = NULL;
    rotated_header[1] = NULL;

    check( nextensions = uves_get_nextensions(filename),
       "Error reading number of extensions of file '%s'", filename);

    /* Find out if new/old format */
    extension = 0;
    check( primary_header = uves_propertylist_load(filename,
                          extension),
       "Could not load header from extension %d of file '%s'", 
       extension, filename);

    check( new_format = uves_format_is_new(primary_header),
       "Error determining new/old format of file %s", filename);
 
    uves_msg_low("Raw frame is %s, %s format, file '%s' has %d extensions", 
		 (blue) ? "blue" : "red", (new_format) ? "new" : "old", 
		 filename, nextensions);

    /* If the raw frame is blue, or if it's an old format red frame */
    if (blue || !new_format)
    {
        enum uves_chip chip;
        
        uves_msg_debug("Frame is blue or old format");

        assure( nextensions == 0 || 
                (blue   && nextensions == 2) ||
                (flames && nextensions == 2),
                CPL_ERROR_ILLEGAL_INPUT,
                "Unrecognized format of file '%s'. %d extensions expected. %d found.",
                filename,
                ((flames||blue) && (nextensions ==2)) ? 2 : 0, nextensions);

        /* FLAMES: the 2 extensions contain OzPoz table and FLAMES FIBRE table */

       /* Load the header */
        check( raw_header[0] = uves_propertylist_load(filename,
                             extension),
           "Could not load header from extension %d of file '%s'", 
           extension, filename);


        extension = 0;
        if(blue && nextensions == 2) {
           extension = 1;
           check( raw_header[1] = uves_propertylist_load(filename,
                                                         extension),
                  "Could not load header from extension %d of file '%s'",
                  extension, filename);
           check( uves_propertylist_append(raw_header[0],raw_header[1]),
                  "Could not collate header from extension 1 to 0 of file '%s'",filename);
           uves_free_propertylist(&raw_header[1]);
 
           check( image1 = cpl_image_load(filename,
                                          type,
                                          plane,   
                                          extension
                     ), "Could not load image from extension %d of file '%s' ", 
                  extension, filename);
           cpl_image_save(image1, "ima1.fits", CPL_BPP_IEEE_FLOAT,
                          NULL,CPL_IO_DEFAULT);

           extension = 2;
           check( image2 = cpl_image_load(filename,
                                          type,
                                          plane,   
                                          extension
                     ), "Could not load image from extension %d of file '%s' ", 
                  extension, filename);
           check_nomsg(sx=cpl_image_get_size_x(image1));
           check_nomsg(sy=cpl_image_get_size_y(image1));

           check_nomsg(image=cpl_image_new(2*sx,sy,type));
           check_nomsg(cpl_image_copy(image,image1,1,1));
           check_nomsg(cpl_image_copy(image,image2,1+sx,1));

           
           uves_free_image(&image1);
           uves_free_image(&image2);

           extension = 1;



        } else {


        check( image = cpl_image_load(filename,
                      type,
                      plane,   
                      extension
               ), "Could not load image from extension %d of file '%s' ", 
           extension, filename);
        }
 
        /* Get blue (or lower red) chip */
        chip = (blue) ? UVES_CHIP_BLUE : UVES_CHIP_REDL;
        check( raw_image[0] = uves_crop_and_rotate(image, raw_header[0], 
                               chip, raw_header[0],
                               new_format, 
                               &rotated_header[0]),
           "Error splitting image");
        
        if (!blue)
        {
            const uves_propertylist *redl_header;

            /* Upper red chip, use again the primary header */
            check( raw_header[1] = uves_propertylist_duplicate(raw_header[0]),
               "Error duplicating FITS header");
            
            /* Get upper red chip */
            chip = UVES_CHIP_REDU;
            redl_header = raw_header[0];
            check( raw_image[1] = uves_crop_and_rotate(image, raw_header[1],
                                   chip, redl_header,
                                   new_format,
                                   &rotated_header[1]),
               "Error splitting red image");
        }
        else
        {
            raw_image[1] = NULL;
            raw_header[1] = NULL;
            rotated_header[1] = NULL;
        }
    }
    else
    /* New red format. UVES must have 2 extensions,
     * FLAMES must have 2 or more extensions
     */
    {
        uves_msg_debug("Frame is red, new format");
        
        assure( nextensions >= 2, CPL_ERROR_UNSUPPORTED_MODE,
            "File '%s' (red frame) has %d extensions. 2+ extensions expected "
                    "for new format",
            filename, nextensions);
        
        uves_msg_debug("New red format, %s frame",
               (nextensions > 2) ? "FLAMES" : "FLAMES/UVES");
        

        /* Images always in extension 1 and 2. First load just the headers */
        for (extension = 1; extension <= 2; extension++)
        {
            /* In the FITS file, REDU is stored
               in extension 1, and REDL is stored in
               extension 2 */
            enum uves_chip chip = (extension == 1) ? UVES_CHIP_REDU : UVES_CHIP_REDL;
            int indx = uves_chip_get_index(chip);

            /* Load the extension header */
            uves_free_propertylist(&ext_header);
            check( ext_header = uves_propertylist_load(filename,
                                  extension),
               "Could not load header from extension %d of file '%s'", 
               extension, filename);
            
            /* Merge with primary header */
            check( raw_header[indx] = uves_propertylist_duplicate(primary_header),
               "Error cloning primary header");
            
            if (!uves_propertylist_is_empty(ext_header))
            {
                if(cpl_propertylist_has(ext_header,"TM-START") && 
                   cpl_propertylist_has(primary_header,"TM-START")  ) {
                   uves_propertylist_erase(ext_header,"TM-START");
                }
                if(cpl_propertylist_has(ext_header,"DATE-OBS") &&
                   cpl_propertylist_has(primary_header,"DATE-OBS") ) {
                   uves_propertylist_erase(ext_header,"DATE-OBS");
                }
                if(cpl_propertylist_has(ext_header,"EXPTIME") &&
                   cpl_propertylist_has(ext_header,"EXPTIME")) {
                   uves_propertylist_erase(ext_header,"EXPTIME");
                }
                check( uves_propertylist_copy_property_regexp(raw_header[indx],
                                     ext_header, ".*", 0),
                   "Error merging primary header with extension %d header", 
                   extension);
            }
        }


        /* Remove pre-, overscan areas (we needed to load both image headers for this) */
        for (extension = 1; extension <= 2; extension++)
        {
            enum uves_chip chip = (extension == 1) ? UVES_CHIP_REDU : UVES_CHIP_REDL;
            int indx      = uves_chip_get_index(chip);
            int indx_redl = uves_chip_get_index(UVES_CHIP_REDL);
            
            const uves_propertylist *redl_header = raw_header[indx_redl];
            
            uves_free_image(&image);
            check( image = cpl_image_load(filename,
                          type,
                          plane,               
                          extension),
               "Could not load image from extension %d of file '%s' ", 
               extension, filename);
            
            check( raw_image[indx] = uves_crop_and_rotate(image, 
                                  raw_header[indx],
                                  chip, redl_header,
                                  new_format,
                                  &rotated_header[indx]),
               "Error splitting red image");
        }

        
    }/* if new format */
 

  cleanup:
    uves_free_image(&image);
    uves_free_image(&image1);
    uves_free_image(&image2);

    uves_free_propertylist(&primary_header);
    uves_free_propertylist(&ext_header);

    if (cpl_error_get_code() != CPL_ERROR_NONE)
    {
        uves_free_image       (&raw_image[0]);
        uves_free_image       (&raw_image[1]);
        uves_free_propertylist(&raw_header[0]);
        uves_free_propertylist(&raw_header[1]);
        uves_free_propertylist(&rotated_header[0]);
        uves_free_propertylist(&rotated_header[1]);
    }
    
    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
   @brief    Loads all raw images of same tag
   @param    frames          The input frame set
   @param    flames          FLAMES frames?
   @param    blue_tag        Tag of blue frames
   @param    red_tag         Tag of red frames
   @param    type            Images are converted to this type when loading
   @param    images          (output) Resulting image list(s) (one for blue; two for red chips)
   @param    raw_headers     (output) The image headers.
   @param    rotated_header  (output) Header(s) containing keywords to describe
                             the output rotated frame(s). Only one per image list is
                 computed, since the format of all input frames is the same
                 (which is checked).
   @param    blue            (output) Flag indicating if the raw 
                             frames are blue (true) or red (false)
   @return   CPL_ERROR_NONE iff OK

   Only one of @em blue_tag and @em red_tag must be present in the frameset.
   All frames with this tag are loaded. They are returned as one (if blue) or two
   (if red) image lists. The headers of the raw images are returned as one or two arrays
   of property lists.

   It is checked that the image sizes are identical for all loaded images.

   It is also checked that the central wavelength (if present) of
   the images is the same.
*/
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_load_raw_imagelist(const cpl_frameset *frames,
            bool flames,
            const char *blue_tag, const char *red_tag, cpl_type type, 
            cpl_imagelist *images[2],
            uves_propertylist **raw_headers[2], uves_propertylist *rotated_header[2],
            bool *blue)
{
    const char *tag           = NULL;
    const cpl_frame *frame    = NULL;
    cpl_image *temp_image[2]  = {NULL, NULL};
    uves_propertylist *temp_header[2] = {NULL, NULL};
    cpl_size number_of_frames = 0;
    int frameset_size = 0;   /* Keeps track of number of raw_header pointers allocated */
    int nchips;
    int chip;
    
    raw_headers[0] = NULL;
    raw_headers[1] = NULL;

    check( frameset_size = cpl_frameset_get_size(frames),
       "Error reading frameset size");

    check( tag = identify_arm(frames, blue_tag, red_tag, blue),
       "Could not identify chip type");
    
    nchips = (*blue) ? 1 : 2;
    for(chip = 0; chip < nchips; chip++)
    {
        images[chip] = NULL;
        rotated_header[chip] = NULL;
        
        images[chip] = cpl_imagelist_new();
        raw_headers[chip] = cpl_calloc(frameset_size, sizeof(uves_propertylist *));
    }

    /* Load all input images with correct tag,
       split,
       insert into image list(s) */  

    number_of_frames = 0;
    int i=0;
    int nfrm;
    nfrm=cpl_frameset_get_size(frames);
    for(i=0;i<nfrm;i++)
    {
        frame=cpl_frameset_get_frame_const(frames,i);
        /* If match */
        if ( strcmp(cpl_frame_get_tag(frame), tag) == 0)
        {
            const char *filename = cpl_frame_get_filename(frame);
            
            /* Load image + header */
            uves_free_propertylist(&rotated_header[0]);
            uves_free_propertylist(&rotated_header[1]);
            
            check( load_raw_image(filename,
                      type,
                      flames,
                      *blue,
                      temp_image,
                      temp_header,
                      rotated_header),
               "Could not load image from file '%s'", filename);
            
            /* Append to image lists */
            for(chip = 0; chip < nchips; chip++)
            {
                raw_headers[chip][number_of_frames] = temp_header[chip];
                temp_header[chip] = NULL;
                
                check( cpl_imagelist_set(images[chip],
                             temp_image[chip],
                             /* Position */
                             cpl_imagelist_get_size(images[chip])
                       ),
                   "Could not insert image into image list");
                
                /* Don't deallocate image or header */
                temp_image[chip] = NULL;
            }
            
            number_of_frames += 1;
        }
    }

    /* Check that image sizes are identical */
    
    for(chip = 0; chip < nchips; chip++)
    {
        /* This function returns zero iff the list is uniform */
        assure (cpl_imagelist_is_uniform(images[chip]) == 0,
            CPL_ERROR_INCOMPATIBLE_INPUT, 
            "Input images are not of same size and type");
        
        passure( cpl_imagelist_get_size(images[chip]) == number_of_frames, 
             "%" CPL_SIZE_FORMAT " %" CPL_SIZE_FORMAT"", cpl_imagelist_get_size(images[0]), number_of_frames);

    }

    
    /* Check central wavelengths (not bias/dark) */
    if ( strcmp(UVES_BIAS (*blue), tag) != 0 &&
     strcmp(UVES_DARK (*blue), tag) != 0 &&
     strcmp(UVES_PDARK(*blue), tag) != 0) {
    enum uves_chip chip_id;
    int i;
    double wlen = 0;
    
    for (chip_id = uves_chip_get_first(*blue); 
         chip_id != UVES_CHIP_INVALID;
         chip_id = uves_chip_get_next(chip_id)) {
        for (i = 0; i < number_of_frames; i++) {
        if (i == 0) {
            check( wlen = uves_pfits_get_gratwlen(
                   raw_headers[uves_chip_get_index(chip_id)][i], chip_id),
               "Error reading central wavelength of input frame number %d", i+1);
        }
        else {
            double w;
            
            check( w = uves_pfits_get_gratwlen(
                   raw_headers[uves_chip_get_index(chip_id)][i], chip_id),
               "Error reading central wavelength of input frame number %d", i+1);
            
            assure( fabs((w-wlen)/wlen) < 0.01, CPL_ERROR_INCOMPATIBLE_INPUT,
                "Mis-matching input frame central wavelengths: "
                "%e (frame 1) != %e (frame %d)", wlen, w, i+1);
        }
        }
    }
    }
    
  cleanup:
    uves_free_image(&temp_image[0]);
    uves_free_image(&temp_image[1]);
    uves_free_propertylist(&temp_header[0]);
    uves_free_propertylist(&temp_header[1]);
    
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
    if (raw_headers[0] != NULL) {
        int i;
        for (i = 0; i < frameset_size; i++)    {
        if (raw_headers[0] != NULL) uves_free_propertylist(&raw_headers[0][i]);
        if (raw_headers[1] != NULL) uves_free_propertylist(&raw_headers[1][i]);
        }
    }
    cpl_free(raw_headers[0]); raw_headers[0] = NULL;
    cpl_free(raw_headers[1]); raw_headers[1] = NULL;
    
    uves_free_imagelist(&images[0]);
    uves_free_imagelist(&images[1]);
    
    uves_free_propertylist(&rotated_header[0]);
    uves_free_propertylist(&rotated_header[1]);
    }

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Loads a raw frame of a type used by the order position recipe
  @param    frames        The input frame set
  @param    flames        expect FLAMES frames?
  @param    raw_filename  (output) Filename of raw frame
  @param    raw_image     (output) The loaded image(s)
  @param    raw_header    (output) The raw image header(s)
  @param    rotated_header (output) Header(s) containing keywords to describe
                           the output rotated frame(s)
  @param    blue          (output) Flag indicating if the raw frame is blue 
                          (true) or red (false)
                       
  @return   CPL_ERROR_NONE iff OK

  The function searches the frame set for an order definition frame
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_load_orderpos(const cpl_frameset *frames,
           bool flames,
           const char **raw_filename,
           cpl_image *raw_image[2],
           uves_propertylist *raw_header[2], 
           uves_propertylist *rotated_header[2], bool *blue)
{
    const char *tags[4];

    int number_of_tags = sizeof(tags) / sizeof(char *);
    int indx;

    /* Warning: Duplicate logic. The number of tags must match the size of the
       tags array defined above */
    tags[0] = UVES_ORDER_FLAT(flames, false); /* red */
    tags[1] = UVES_ORDER_FLAT(flames, true);  /* blue */
    tags[2] = UVES_STD_STAR(false);
    tags[3] = UVES_STD_STAR(true);

    if (flames)
    {
        *blue = false;
        number_of_tags = 1;

        check( *raw_filename = uves_find_frame(frames, tags, number_of_tags, &indx,
                           NULL),
           "Could not find raw frame (%s) in SOF", 
           tags[0]);
        
    }
    else
    {    
        check( *raw_filename = uves_find_frame(frames, tags, number_of_tags, &indx,
                           NULL),
           "Could not find raw frame (%s, %s, %s, or %s) in SOF", 
           tags[0], tags[1], tags[2], tags[3]);
        
        *blue = (indx == 1) || (indx == 3);
    }

    /* Load the image */
    check( load_raw_image(*raw_filename,
              CPL_TYPE_DOUBLE,
              flames,
              *blue,
              raw_image,
              raw_header,
              rotated_header),
       "Error loading image from file '%s'", *raw_filename);
    
    passure( !flames || !(*blue), "%d %d",
         flames, *blue );

  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE)
    {
        *raw_filename = NULL;
    }
    
    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Loads a format check frame
  @param    frames        The input frame set
  @param    flames        load FLAMES or UVES frame?
  @param    raw_filename  (output) Filename of raw frame
  @param    raw_image     (output) The loaded image(s)
  @param    raw_header    (output) The raw image header(s)
  @param    rotated_header (output) Header(s) containing keywords to describe
                           the output rotated frame(s)
  @param    blue          (output) Flag indicating if the raw frame is blue (true) or red (false)
  @return   CPL_ERROR_NONE iff OK

  The function searches the frame set for an ARC_LAMP_FORM_BLUE or ARC_LAMP_FORM_RED frame,
  and loads the frame.
**/
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_load_formatcheck(const cpl_frameset *frames,
              bool flames,
              const char **raw_filename,
              cpl_image *raw_image[2],
              uves_propertylist *raw_header[2], 
              uves_propertylist *rotated_header[2], bool *blue)
{
    const char *tags[2];
    int number_of_tags = sizeof(tags) / sizeof(char *);
    int indx;

    tags[0] = UVES_FORMATCHECK(flames, false);   /* red */
    tags[1] = UVES_FORMATCHECK(flames, true);    /* blue */
    if (flames)
    {
        *blue = false;
        number_of_tags = 1;

        check( *raw_filename = uves_find_frame(frames, tags, number_of_tags, &indx, NULL),
           "Could not find raw frame (%s) in SOF",
           tags[0]);
    }
    else
    {
        check( *raw_filename = uves_find_frame(frames, tags, number_of_tags, &indx, NULL),
           "Could not find raw frame (%s or %s) in SOF", 
           tags[0], tags[1]);
        
        *blue = (indx == 1);
    }

    /* Load the image */
    check( load_raw_image(*raw_filename,
              CPL_TYPE_DOUBLE,
              flames,
              *blue,
              raw_image,
              raw_header,
              rotated_header),
       "Error loading image from file '%s'", *raw_filename);
   
  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) 
    {
        *raw_filename = NULL;
    }
    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Load exactly 2 CD alignment frames
  @param    frames          The input frame set
  @param    raw_filename1   (output) Filename of raw frame
  @param    raw_filename2   (output) Filename of raw frame
  @param    raw_image1      (output) The loaded image(s)
  @param    raw_image2      (output) The loaded image(s)
  @param    raw_header1     (output) The raw image header(s)
  @param    raw_header2     (output) The raw image header(s)
  @param    rotated_header1 (output) Header(s) containing keywords to describe
                            the output rotated frame(s)
  @param    rotated_header2 (output) Header(s) containing keywords to describe
                           the output rotated frame(s)
  @param    blue          (output) Flag indicating if the raw frame is blue (true)
                          or red (false)
  @return   CPL_ERROR_NONE iff OK

**/
/*----------------------------------------------------------------------------*/
void uves_load_cd_align(const cpl_frameset *frames,
            const char **raw_filename1,
            const char **raw_filename2,
            cpl_image *raw_image1[2],
            cpl_image *raw_image2[2],
            uves_propertylist *raw_header1[2], 
            uves_propertylist *raw_header2[2], 
            uves_propertylist *rotated_header1[2], 
            uves_propertylist *rotated_header2[2], 
            bool *blue)
{
    const char *tags[2];
    int number_of_tags = sizeof(tags) / sizeof(char *);
    int indx;
    bool flames = false;
    const cpl_frame *frame;

    tags[0] = UVES_CD_ALIGN(false);   /* red */
    tags[1] = UVES_CD_ALIGN(true);    /* blue */

    check( *raw_filename1 = uves_find_frame(frames, tags, number_of_tags, &indx, NULL),
       "Could not find raw frame (%s or %s) in SOF", 
       tags[0], tags[1]);
    
    *blue = (indx == 1);

    assure( cpl_frameset_count_tags(frames, tags[indx]) == 2,
        CPL_ERROR_ILLEGAL_INPUT,
        "%d %s frames found. Exactly 2 required",
        cpl_frameset_count_tags(frames, tags[indx]), tags[indx] );

    /* Load the two frames */
    {
    int n = 1;
    int i=0;
    int nfrm=cpl_frameset_get_size(frames);
    for (i = 0;i < nfrm;i++)
        {
        frame=cpl_frameset_get_frame_const(frames,i);
        if (strcmp(cpl_frame_get_tag(frame), tags[indx]) == 0)
            {
            if (n == 1)
                {
                *raw_filename1 = cpl_frame_get_filename(frame);
                }
            else
                {
                *raw_filename2 = cpl_frame_get_filename(frame);
                }
            
            check( load_raw_image(n == 1 ? 
                          *raw_filename1 :
                          *raw_filename2,
                          CPL_TYPE_DOUBLE,
                          flames,
                          *blue,
                          n == 1 ?
                          raw_image1 :
                          raw_image2,
                          n == 1 ?
                          raw_header1 :
                          raw_header2,
                          n == 1 ?
                          rotated_header1 :
                          rotated_header2),
                   "Error loading image from file '%s'",
                   n == 1 ? *raw_filename1 : *raw_filename2);

            n++;
            }
        }
    }
    
  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) 
    {
        *raw_filename1 = NULL;
        *raw_filename2 = NULL;
    }
    
    return;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Load a RAW arclamp frame
  @param    frames        The input frameset
  @param    flames        Load FLAMES or UVES frame?
  @param    raw_filename  (output) Filename of raw frame
  @param    raw_image     (output) The loaded image(s)
  @param    raw_header    (output) The raw image header(s)
  @param    rotated_header (output) Header(s) containing keywords to describe
                           the output rotated frame(s)
  @param    blue          (output) Flag indicating if the raw frame is blue
                          (true) or red (false)
  @param    sim_cal       (output) for FLAMES: whether the input frame is
                          a sim(ultaneous) cal(ibration) frame
  @return   CPL_ERROR_NONE iff OK

  The function searches the frame set for an arclamp frame.
  Results are returned through the parameters @em raw_filename, 
  @em raw_image and @em raw_header (NULL on error).

 */
/*----------------------------------------------------------------------------*/
void
uves_load_arclamp(const cpl_frameset *frames,
          bool flames,
          const char **raw_filename, 
          cpl_image *raw_image[2], uves_propertylist *raw_header[2],
          uves_propertylist *rotated_header[2], bool *blue,
          bool *sim_cal)
{
    const char *tags[4];

    int number_of_tags = sizeof(tags) / sizeof(char *);
    int indx;

    /* Warning: duplicate logic. Array size above must match */
    if (flames)
    {
        assure_nomsg( sim_cal != NULL, CPL_ERROR_NULL_INPUT );

        tags[0] = UVES_ARC_LAMP(flames, true);  /* blue flag not used */
        tags[1] = FLAMES_FIB_SCI_SIM;

        number_of_tags = 2;
        *blue = false;

        check( *raw_filename = uves_find_frame(frames, tags, number_of_tags, &indx, NULL), 
           "Could not find raw frame (%s or %s) in SOF", 
           tags[0], tags[1]);
        
        *sim_cal = (indx == 1);
    }
    else
    {
        tags[0] = UVES_ARC_LAMP(flames, true);
        tags[1] = UVES_ARC_LAMP(flames, false);
        tags[2] = UVES_ECH_ARC_LAMP(true);
        tags[3] = UVES_ECH_ARC_LAMP(false);

        check( *raw_filename = uves_find_frame(frames, tags, number_of_tags, &indx, NULL), 
           "Could not find raw frame (%s, %s, %s or %s) in SOF", 
           tags[0], tags[1], tags[2], tags[3]);
        
        *blue = (indx == 0 || indx == 2);
    }
    
    /* Load the image */
    check( load_raw_image(*raw_filename,
              CPL_TYPE_DOUBLE,
              flames,
              *blue,
              raw_image,
              raw_header,
              rotated_header),
       "Error loading image from file '%s'", *raw_filename);

  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
    *raw_filename = NULL;
    uves_free_image       (raw_image);
    uves_free_propertylist(raw_header);
    }
    return;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Load a science frame
  @param    frames        The input frameset
  @param    raw_filename  (output) Filename of raw frame
  @param    raw_image     (output) The loaded image(s)
  @param    raw_header    (output) The raw image header(s)
  @param    rotated_header (output) Header(s) containing keywords to describe
                           the output rotated frame(s)
  @param    blue          (output) Flag indicating if the raw frame is blue (true) or red (false)
  @param    sci_type      (output) the type of science frame found (SCIENCE, SCI_EXTND,
                          SCI_POINT or SCI_SLICER)
  @return   CPL_ERROR_NONE iff OK

 */
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_load_science(const cpl_frameset *frames, const char **raw_filename, 
          cpl_image *raw_image[2], 
          uves_propertylist *raw_header[2], 
          uves_propertylist *rotated_header[2], 
          bool *blue,
          const char **sci_type)
{
    /* Note: the two following arrays must match */
    const char *tags[] = 
    { 
        UVES_SCIENCE(true), UVES_SCIENCE(false),
        UVES_SCI_EXTND(true), UVES_SCI_EXTND(false),
        UVES_SCI_POINT(true), UVES_SCI_POINT(false),
        UVES_SCI_SLICER(true), UVES_SCI_SLICER(false),
        UVES_TFLAT(true), UVES_TFLAT(false) 
    };

    const char *type[] = 
    {
        "SCIENCE", "SCIENCE",
        "SCI_EXTND", "SCI_EXTND",
        "SCI_POINT", "SCI_POINT",
        "SCI_SLICER", "SCI_SLICER",
        "TFLAT", "TFLAT",
    };

    int number_of_tags = sizeof(tags) / sizeof(char *);
    int indx;
    bool flames = false;

    check( *raw_filename = uves_find_frame(frames, tags, number_of_tags, &indx, NULL), 
       "No science frame (%s, %s, %s, %s, %s, %s, %s, %s, %s or %s) in SOF", 
       tags[0], tags[1], tags[2], tags[3], 
       tags[4], tags[5], tags[6], tags[7], tags[7], tags[8]);

    *blue = (indx % 2 == 0);
    *sci_type = type[indx];
    
    /* Load the image */
    check( load_raw_image(*raw_filename,
              CPL_TYPE_DOUBLE,
              flames,
              *blue,
              raw_image,
              raw_header,
              rotated_header),
       "Error loading image from file '%s'", *raw_filename);
  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE)
    {
        *raw_filename = NULL;
        uves_free_image       (raw_image);
        uves_free_propertylist(raw_header);
    }
    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Load a standard star frame
  @param    frames        The input frameset
  @param    raw_filename  (output) Filename of raw frame
  @param    raw_image     (output) The loaded image(s)
  @param    raw_header    (output) The raw image header(s)
  @param    rotated_header (output) Header(s) containing keywords to describe
                           the output rotated frame(s)
  @param    blue          (output) Flag indicating if the raw frame is blue (true) or red (false)
  @return   CPL_ERROR_NONE iff OK

  The function searches the frame set for a standard star frame.
  Results are returned through the parameters @em raw_filename, 
  @em raw_image and @em raw_header (NULL on error).

 */
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_load_standard(const cpl_frameset *frames, const char **raw_filename, 
           cpl_image *raw_image[2],
           uves_propertylist *raw_header[2], 
           uves_propertylist *rotated_header[2], bool *blue)
{
    const char *tags[] = { UVES_STD_STAR(true), UVES_STD_STAR(false) };
    int number_of_tags = sizeof(tags) / sizeof(char *);
    int indx;
    bool flames = false;
    
    check( *raw_filename = uves_find_frame(frames, tags, number_of_tags, &indx, NULL), 
       "Could not identify raw frame (%s or %s) in SOF", tags[0], tags[1]);

    *blue = (indx == 0);
    
    /* Load the image */
    check( load_raw_image(*raw_filename,
              CPL_TYPE_DOUBLE,
              flames,
              *blue,
              raw_image,
              raw_header,
              rotated_header),
       "Error loading image from file '%s'", *raw_filename);

  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE)
    {
        *raw_filename = NULL;
        uves_free_image       (raw_image);
        uves_free_propertylist(raw_header);
    }
    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Load the DRS guess table
   @param    frames        the input frame set
   @param    flames        FLAMES mode?
   @param    chip_name     CCD chip name
   @param    drs_filename  Filename of DRS frame
   @param    drs_header    The table extension header
   @param    chip           CCD chip
   @return   CPL_ERROR_NONE iff OK

   The function searches the frame set for a guess table.
   Results are returned through the parameters @em drs_filename and 
   @em drs_header (NULL on error).

*/
/*----------------------------------------------------------------------------*/

cpl_error_code
uves_load_drs(const cpl_frameset *frames, 
          bool flames,
          const char *chip_name,
          const char **drs_filename, 
          uves_propertylist **drs_header,
          enum uves_chip chip)
{
    const char *tags[1];
    int number_of_tags = sizeof(tags) / sizeof(char *);
    int extension;
    int indx;
    
    *drs_header = NULL;
    tags[0]   = UVES_DRS_SETUP(flames, chip);
    extension = UVES_DRS_SETUP_EXTENSION(chip);

    check( *drs_filename = uves_find_frame(frames, tags, number_of_tags, &indx, NULL), 
       "Could not find DRS table (%s) in SOF", tags[0]);
    
    /* Load the header */
    check( *drs_header = uves_propertylist_load(*drs_filename,
                           extension),
       "Could not load header from extension %d of file '%s'", extension, *drs_filename);

    check_nomsg( uves_warn_if_chip_names_dont_match(*drs_header, chip_name, chip) );

  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
    *drs_filename = NULL;
    uves_free_propertylist(drs_header);
    }
    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Load weight map
   @param    frames               The input frame set
   @param    weights_filename     (output) Filename of weight map
   @param    chip                 CCD chip
   @return weight map
*/
/*----------------------------------------------------------------------------*/
cpl_image *
uves_load_weights(const cpl_frameset *frames, const char **weights_filename,
                  enum uves_chip chip)
{
    cpl_image *weights = NULL;
    const char *tags[1];
    int number_of_tags = sizeof(tags) / sizeof(char *);
    int extension = 0;
    int indx;

    assure( weights_filename != NULL, CPL_ERROR_NULL_INPUT, "Null filename");
    
    tags[0]   = UVES_WEIGHTS(chip);

    check( *weights_filename = uves_find_frame(frames, 
                                               tags, number_of_tags, &indx, NULL), 
           "Could not find '%s' in frame set", tags[0]);
    
    check( weights = cpl_image_load(*weights_filename,
                                    CPL_TYPE_DOUBLE,           /* Convert to this type */
                                    0,                         /* plane number */
                                    extension                  /* Extension number */
               ),
           "Could not load master bias from extension %d of file '%s'", 
           extension, *weights_filename);

  cleanup:
    return weights;
}


/*----------------------------------------------------------------------------*/
/**
   @brief    Load the master bias frame
   @param    frames               The input frame set
   @param    chip_name            CCD chip name
   @param    mbias_filename       (output) Filename of master bias frame
   @param    mbias                (output) The master bias image
   @param    mbias_header         (output) FITS header of master bias frame
   @param    chip                 CCD chip
   @return   CPL_ERROR_NONE iff OK

   The function searches the frame set for a master bias frame.
   Results are returned through the parameters @em mbias_filename,
   @em mbias and @em mbias_header (NULL on error).

*/
/*----------------------------------------------------------------------------*/

cpl_error_code
uves_load_mbias(const cpl_frameset *frames, const char *chip_name,
        const char **mbias_filename, 
        cpl_image **mbias, uves_propertylist **mbias_header, enum uves_chip chip)
{
    const char *tags[1];
    int number_of_tags = sizeof(tags) / sizeof(char *);
    int extension;
    int indx;
    
    *mbias        = NULL;
    *mbias_header = NULL;

    tags[0]   = UVES_MASTER_BIAS          (chip);
    extension = UVES_MASTER_BIAS_EXTENSION(chip);
    
    check( *mbias_filename = uves_find_frame(frames, tags, number_of_tags, &indx, NULL), 
       "Could not find '%s' in frame set", tags[0]);
    
    /* Load the mbias image */
    check( *mbias = cpl_image_load(*mbias_filename,
                   CPL_TYPE_DOUBLE,           /* Convert to this type */
                   0,                         /* plane number */
                   extension                  /* Extension number */
           ),
       "Could not load master bias from extension %d of file '%s'", 
       extension, *mbias_filename);

    /* Load the header */
    check( *mbias_header = uves_propertylist_load(*mbias_filename,
                         extension),
       "Could not load header from extension %d of file '%s'", 
       extension, *mbias_filename);

    check_nomsg( uves_warn_if_chip_names_dont_match(*mbias_header, chip_name, chip) );

  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) 
    {
        *mbias_filename = NULL;
        uves_free_image(mbias);
        uves_free_propertylist(mbias_header);
    }
    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
   @brief    Load the master formatcheck frame
   @param    frames               The input frame set
   @param    chip_name            CCD chip name
   @param    mform_filename       Filename of master formatcheck frame (returned)
   @param    mform                The master formatcheck image (returned)
   @param    mform_header         FITS header of master formatcheck frame (returned)
   @param    chip                 CCD chip
   @return   CPL_ERROR_NONE iff OK

   The function searches the frame set for a master formatcheck frame.
   Results are returned through the parameters @em mform_filename,
   @em mform and @em mform_header (NULL on error).

*/
/*----------------------------------------------------------------------------*/

cpl_error_code
uves_load_master_formatcheck(const cpl_frameset *frames, const char *chip_name,
        const char **mform_filename, 
        cpl_image **mform, uves_propertylist **mform_header, enum uves_chip chip)
{
    const char *tags[1];
    int number_of_tags = sizeof(tags) / sizeof(char *);
    int extension;
    int indx;
    
    *mform        = NULL;
    *mform_header = NULL;

    tags[0]   = UVES_MASTER_ARC_FORM          (chip);
    extension = UVES_MASTER_ARC_FORM_EXTENSION(chip);
    
    check( *mform_filename = uves_find_frame(frames, tags, number_of_tags, &indx, NULL), 
       "Could not find '%s' in frame set", tags[0]);
    
    /* Load the mbias image */
    check( *mform = cpl_image_load(*mform_filename,
                   CPL_TYPE_DOUBLE,           /* Convert to this type */
                   0,                         /* plane number */
                   extension                  /* Extension number */
           ),
       "Could not load master formatcheck from extension %d of file '%s'", 
       extension, *mform_filename);

    /* Load the header */
    
    check( *mform_header = uves_propertylist_load(*mform_filename,
                         extension),
       "Could not load header from extension %d of file '%s'", 
       extension, *mform_filename);

    check_nomsg( uves_warn_if_chip_names_dont_match(*mform_header, chip_name, chip) );

  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) 
    {
        *mform_filename = NULL;
        uves_free_image(mform);
        uves_free_propertylist(mform_header);
    }
    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Load the master dark frame
   @param    frames               The input frame set
   @param    chip_name              CCD chip name
   @param    mdark_filename       Filename of master dark frame (returned)
   @param    mdark                The master dark image (returned)
   @param    mdark_header         FITS header of master dark frame (returned)
   @param    chip                 CCD chip
   @return   CPL_ERROR_NONE iff OK

   The function searches the frame set for a master dark frame.
   Results are returned through the parameters @em mdark_filename,
   @em mdark and @em mdark_header (NULL on error).

*/
/*----------------------------------------------------------------------------*/

cpl_error_code
uves_load_mdark(const cpl_frameset *frames, const char *chip_name,
        const char **mdark_filename, cpl_image **mdark,
        uves_propertylist **mdark_header, enum uves_chip chip)
{
    const char *tags[2];
    int number_of_tags = sizeof(tags) / sizeof(char *);
    int extension;
    int indx;
    
    *mdark        = NULL;
    *mdark_header = NULL;

    tags[0]   = UVES_MASTER_DARK          (chip);
    tags[1]   = UVES_MASTER_PDARK         (chip);
    extension = UVES_MASTER_DARK_EXTENSION(chip);
    
    check( *mdark_filename = uves_find_frame(frames, tags, number_of_tags, &indx, NULL), 
       "Could not find %s or %s in frame set", tags[0], tags[1]);
    
    /* Load the mdark image */
    check( *mdark = cpl_image_load(*mdark_filename,
                   CPL_TYPE_DOUBLE,           /* Convert to this type */
                   0,                         /* plane number */
                   extension                  /* Extension number */
           ),
       "Could not load master dark from extension %d of file '%s'", 
       extension, *mdark_filename);

    /* Load the header */
    check( *mdark_header = uves_propertylist_load(*mdark_filename,
                         extension),
       "Could not load header from extension %d of file '%s'", 
       extension, *mdark_filename);

    check_nomsg( uves_warn_if_chip_names_dont_match(*mdark_header, chip_name, chip) );

  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) 
    {
        *mdark_filename = NULL;
        uves_free_image(mdark);
        uves_free_propertylist(mdark_header);
    }
    return cpl_error_get_code();
}
/*----------------------------------------------------------------------------*/
/**
   @brief    Load the reference flat-field
   @param    frames               The input frame set
   @param    chip_name              CCD chip name
   @param    mdark_filename       Filename of master dark frame (returned)
   @param    mdark                The master dark image (returned)
   @param    mdark_header         FITS header of master dark frame (returned)
   @param    chip                 CCD chip
   @return   CPL_ERROR_NONE iff OK

   The function searches the frame set for a master dark frame.
   Results are returned through the parameters @em mdark_filename,
   @em mdark and @em mdark_header (NULL on error).

*/
/*----------------------------------------------------------------------------*/
void
uves_load_ref_flat(const cpl_frameset *frames, const char *chip_name,
           const char **filename, cpl_image **rflat,
           uves_propertylist **rflat_header, enum uves_chip chip)
{
    const char *tags[1];
    int number_of_tags = sizeof(tags) / sizeof(char *);
    int extension;
    int indx;
    
    *rflat        = NULL;
    *rflat_header = NULL;

    tags[0]   = UVES_REF_TFLAT(chip);
    extension = UVES_MASTER_FLAT_EXTENSION(chip);

    check( *filename = uves_find_frame(frames, tags, number_of_tags, &indx, NULL), 
       "Could not find %s in frame set", tags[0]);
    
    check( *rflat = cpl_image_load(*filename,
                   CPL_TYPE_DOUBLE,           /* Convert to this type */
                   0,                         /* plane number */
                   extension                  /* Extension number */
           ),
       "Could not load reference dark from extension %d of file '%s'", 
       extension, *filename);

    check( *rflat_header = uves_propertylist_load(*filename,
                         extension),
       "Could not load header from extension %d of file '%s'", 
       extension, *filename);

    check_nomsg( uves_warn_if_chip_names_dont_match(*rflat_header, chip_name, chip) );

  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) 
    {
        *filename = NULL;
        uves_free_image(rflat);
        uves_free_propertylist(rflat_header);
    }

    return;
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Load the master flat frame
   @param    frames               The input frame set
   @param    chip_name            CCD chip name
   @param    mflat_filename       (output) Filename of master flat frame
   @param    mflat                (output) The master flat image
   @param    mflat_header         (output) FITS header of master flat frame
   @param    chip                 CCD chip
   @param    mflat_frame          (output) if non-NULL, this will be set
                                  to point to the loaded frame (which is still
                                  owned by the frame set and therefore should
                                  not be deallocated)
   @return   CPL_ERROR_NONE iff OK

*/
/*----------------------------------------------------------------------------*/

cpl_error_code
uves_load_mflat_const(const cpl_frameset *frames, const char *chip_name,
              const char **mflat_filename, 
              cpl_image **mflat, uves_propertylist **mflat_header, 
              enum uves_chip chip,
              const cpl_frame **mflat_frame)
{
    const char *tags[6];
    int number_of_tags = sizeof(tags) / sizeof(char *);
    int extension;
    int indx;
    
    *mflat        = NULL;
    *mflat_header = NULL;

    tags[0]   = UVES_REF_TFLAT            (chip);   /* Use REF TFLAT, rather than MASTER_TFLAT */
    tags[1]   = UVES_MASTER_FLAT          (chip);
    tags[2]   = UVES_MASTER_DFLAT         (chip);
    tags[3]   = UVES_MASTER_IFLAT         (chip);
    tags[4]   = UVES_MASTER_TFLAT         (chip);
    tags[5]   = UVES_MASTER_SCREEN_FLAT   (chip);
    extension = UVES_MASTER_FLAT_EXTENSION(chip);
    
    check( *mflat_filename = uves_find_frame(frames, tags, number_of_tags, &indx,
                         mflat_frame), 
       "Could not find '%s', '%s', '%s', '%s' or '%s' in frame set", 
       tags[0], tags[1], tags[2], tags[3], tags[4]);
    
    /* Load the mflat image */
    check( *mflat = cpl_image_load(*mflat_filename,
                   CPL_TYPE_DOUBLE,           /* Convert to this type */
                   0,                         /* plane number */
                   extension                  /* Extension number */
           ),
       "Could not load master flat from extension %d of file '%s'", 
       extension, *mflat_filename);

    /* Load the header */
    check( *mflat_header = uves_propertylist_load(*mflat_filename,
                         extension),
       "Could not load header from extension %d of file '%s'", 
       extension, *mflat_filename);

    check_nomsg( uves_warn_if_chip_names_dont_match(*mflat_header, chip_name, chip) );

  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) 
    {
        *mflat_filename = NULL;
        uves_free_image(mflat);
        uves_free_propertylist(mflat_header);
    }
    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Load the master flat frame
   @param    frames               See @c uves_load_mflat_const
   @param    chip_name            See @c uves_load_mflat_const
   @param    mflat_filename       See @c uves_load_mflat_const
   @param    mflat                See @c uves_load_mflat_const
   @param    mflat_header         See @c uves_load_mflat_const
   @param    chip                 See @c uves_load_mflat_const
   @param    mflat_frame          See @c uves_load_mflat_const
   @return   CPL_ERROR_NONE iff OK

   This function is the same as uves_load_mflat_const, except it provides a non-const
   pointer (last argument) to a non-const frameset (first argument)
*/
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_load_mflat(cpl_frameset *frames, const char *chip_name,
        const char **mflat_filename, 
        cpl_image **mflat, uves_propertylist **mflat_header, enum uves_chip chip,
        cpl_frame **mflat_frame)
{
    return uves_load_mflat_const((const cpl_frameset *)frames,
                 chip_name,
                 mflat_filename,
                 mflat, mflat_header, chip,
                 (const cpl_frame **) mflat_frame);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Load an order table
  @param    frames               The input frameset
  @param    flames               load FLAMES order table?
  @param    chip_name            CCD chip name
  @param    ordertable_filename  File containing order table
  @param    ordertable           (output) Loaded order table
  @param    ordertable_header    (output) Primary header of order table
  @param    ordertable_xheader    (output) 1st extension header of order table
  @param    order_locations      (output) The bivariate polynomial defining
                                 the order locations, not loaded if NULL
  @param    traces               (output) Table that defines the fibre traces,
                                 not loaded if NULL
  @param    tab_in_out_oshift    (output) If non-NULL the TAB_IN_OUT_OSHIFT value
  @param    tab_in_out_yshift    (output) If non-NULL the TAB_IN_OUT_YSHIFT value
  @param    fibre_mask    (output) If non-NULL the FIBREMASK value
  @param    fibre_pos     (output) If non-NULL the FIBREPOS value
  @param    chip                 CCD chip
  @param    guess_table          if true, load UVES_ORDER_GUESS_TAB_xxxx,
                                 if false load UVES_ORDER_TABLE_xxxx

  @return   CPL_ERROR_NONE iff OK

  The function searches the frame set for an order table of the type (blue, redu, redl)
  indicated by the parameter @em chip.

  Results are returned through the parameters @em ordertable_filename, @em ordertable,
  @em ordertable_header,
  @em order_locations and @em traces (NULL on error).

 */
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_load_ordertable(const cpl_frameset *frames, 
                     bool flames,
                     const char *chip_name,
                     const char **ordertable_filename, 
                     cpl_table **ordertable, 
                     uves_propertylist **ordertable_header, 
                     uves_propertylist **ordertable_xheader, 
                     polynomial **order_locations, 
                     cpl_table **traces, 
                     int *tab_in_out_oshift,
                     double *tab_in_out_yshift,
                     int ** fib_msk,
                     double ** fib_pos,
                     enum uves_chip chip,
                     bool guess_table)
{
    uves_propertylist *midas_header = NULL;      /* Table header if midas format */
    uves_propertylist *prime_header = NULL;      /* Prime header if flames  */
    const char *tags[1];
    int number_of_tags = sizeof(tags) / sizeof(char *);
    bool format_is_midas;
    int *tioo = NULL;
    double *tioy = NULL;
    int indx;

    double *fibre_pos = NULL;
    int *fibre_mask = NULL;

    if (guess_table)
    {
        tags[0] = UVES_GUESS_ORDER_TABLE(flames, chip);
    }
    else
    {
        tags[0] = UVES_ORDER_TABLE(flames, chip);
    }

    check( *ordertable_filename = uves_find_frame(frames, tags, number_of_tags, &indx, NULL), 
       "No order table (%s) found in SOF", tags[0]);
    
    check( *ordertable = cpl_table_load(*ordertable_filename,
                    UVES_ORDER_TABLE_EXTENSION,
                    1),                /* Mark identified 
                                  invalid values? (1=yes) */
       "Error loading order table from extension %d of file '%s'", 
       UVES_ORDER_TABLE_EXTENSION, *ordertable_filename);
    
    assure(ordertable_header != NULL,CPL_ERROR_NULL_INPUT,
           "NULL primary header uves_propertylist variable header");
    check( *ordertable_header = uves_propertylist_load(*ordertable_filename, 0),
       "Could not load header from extension 0 of '%s'", *ordertable_filename);

    if(ordertable_xheader != NULL) {

    check( *ordertable_xheader = uves_propertylist_load(*ordertable_filename, 1),
       "Could not load header from extension 1 of '%s'", *ordertable_filename);



    }
    check_nomsg( uves_warn_if_chip_names_dont_match(*ordertable_header, chip_name, chip) );
    
    check(uves_check_if_format_is_midas(*ordertable_header,&format_is_midas),
	  "Error getting FITS format");


    if (!format_is_midas && !flames)
    {
            /* The format check and order position recipes create order tables
               with different column names. Rename if necessary. 

               This is a workaround for the problem that different recipes
               create the same products (order tables and line tables).
               The true solution would be to remove the format check recipe from
               the recution cascade, and use the theoretical physical model to
               bootstrap the order definition and wavelength calibration.
            */
            if (cpl_table_has_column(*ordertable, "ORDER"))
                {
                    cpl_table_name_column(*ordertable, "ORDER", "Order");
                }
            if (cpl_table_has_column(*ordertable, "YFIT"))
                {
                    cpl_table_name_column(*ordertable, "YFIT", "Yfit");
                }

        if (order_locations != NULL)
        {
            check( *order_locations = 
               load_polynomial(*ordertable_filename, UVES_ORDER_TABLE_EXTENSION_POLY),
               "Could not read polynomial from extension %d of file '%s'",
               UVES_ORDER_TABLE_EXTENSION_POLY, *ordertable_filename);
        }

        if (traces != NULL)
        {
            check( *traces = cpl_table_load(*ordertable_filename,
                            UVES_ORDER_TABLE_EXTENSION_FIBRE,
                            1),    /* Mark identified 
                                  invalid values? (1=yes) */
               "Error loading fibre table from extension %d of file '%s'", 
               UVES_ORDER_TABLE_EXTENSION_FIBRE, *ordertable_filename);
        }
    }
    else
        /* MIDAS format, or FLAMES */
    {
        /* Rename */
        check(( cpl_table_cast_column (*ordertable, "ORDER", "Order", CPL_TYPE_INT),
                cpl_table_erase_column(*ordertable, "ORDER")),
              "Error casting and renaming column 'ORDER'");
        
        check( cpl_table_name_column(*ordertable, "YFIT", "Yfit"),
               "Error renaming column 'YFIT'");
                
        //check( midas_header = uves_propertylist_load(*ordertable_filename, 1),
	//      "Could not load header from extension 1 of '%s'", 
	//     *ordertable_filename);
	  check(midas_header = uves_propertylist_load(*ordertable_filename, 1),
		 "Could not load header from extension 1 of '%s'",
		 *ordertable_filename);

	if(flames) {
          check(prime_header = uves_propertylist_load(*ordertable_filename, 0),
		 "Could not load header from extension 0 of '%s'",
		 *ordertable_filename);
	  check_nomsg(uves_propertylist_append(midas_header,prime_header));
        }

        /* Load polynomial named 'COEFF' from descriptors in extension 1 */
        if (order_locations != NULL)
        {
            check( *order_locations = 
		   uves_polynomial_convert_from_plist_midas(midas_header, "COEFF",-1),
                   "Error reading polynomial from %s", *ordertable_filename);
        }


        if (flames && tab_in_out_oshift != NULL )
        {
            /* Get tab_in_out_oshift */
            int tioo_length;
                    cpl_type tioo_type;

            check( tioo = uves_read_midas_array(
                   midas_header, "TAB_IN_OUT_OSHIFT", &tioo_length,
                               &tioo_type, NULL),
               "Error reading TAB_IN_OUT_OSHIFT from MIDAS header");

                    assure( tioo_type == CPL_TYPE_INT, CPL_ERROR_TYPE_MISMATCH,
                            "Type of TAB_IN_OUT_OSHIFT is %s, double expected",
                            uves_tostring_cpl_type(tioo_type));

            if (tioo_length != 1)
            {
                uves_msg_warning("Length of TAB_IN_OUT_OSHIFT array is %d; "
                         "%d expected", tioo_length, 1);
            }

            *tab_in_out_oshift = tioo[0];
            
            uves_msg_debug("TAB_IN_OUT_OSHIFT = %d", *tab_in_out_oshift);

        }
        
        if (flames && tab_in_out_yshift != NULL)
        {
            /* Get tab_in_out_yshift */
            int tioy_length;
                    cpl_type tioy_type;

            check( tioy = uves_read_midas_array(
                   midas_header, "TAB_IN_OUT_YSHIFT", &tioy_length,
                               &tioy_type, NULL),
               "Error reading TAB_IN_OUT_YSHIFT from MIDAS header");

                    assure( tioy_type == CPL_TYPE_DOUBLE, CPL_ERROR_TYPE_MISMATCH,
                            "Type of TAB_IN_OUT_YSHIFT is %s, double expected",
                            uves_tostring_cpl_type(tioy_type));

            if (tioy_length != 1)
            {
                uves_msg_warning("Length of TAB_IN_OUT_YSHIFT array is %d; "
                         "%d expected", tioy_length, 1);
            }

            *tab_in_out_yshift = tioy[0];

            uves_msg_debug("TAB_IN_OUT_YSHIFT = %f", *tab_in_out_yshift);
        }
        
        if (traces != NULL)
        {
            *traces = uves_ordertable_traces_new();
            
            if (!flames)
            /* UVES: one trace with zero offset */
            {
                int fibre_ID = 0;
                double fibre_offset = 0.0;
                int fibre_msk = 1;
                uves_ordertable_traces_add(*traces, 
                               fibre_ID,
                               fibre_offset,
                               fibre_msk);
            }
            else
            /* FLAMES */
            {

                int fibre_pos_length;
                int fibre_mask_length;
                            cpl_type fibre_pos_type;
                            cpl_type fibre_mask_type;
                int fibre_ID;

                check( fibre_pos = uves_read_midas_array(
                       midas_header, "FIBREPOS", &fibre_pos_length,
                                       &fibre_pos_type, NULL),
                   "Error reading FIBREPOS from MIDAS header");

                            assure( fibre_pos_type == CPL_TYPE_DOUBLE, CPL_ERROR_TYPE_MISMATCH,
                                    "Type of FIBREPOS is %s, double expected",
                                    uves_tostring_cpl_type(fibre_pos_type));

                check( fibre_mask = uves_read_midas_array(
                       midas_header, "FIBREMASK", &fibre_mask_length,
                                       &fibre_mask_type, NULL),
                   "Error reading FIBREMASK from MIDAS header");

                            assure( fibre_mask_type == CPL_TYPE_INT, CPL_ERROR_TYPE_MISMATCH,
                                    "Type of FIBREMASK is %s, double expected",
                                    uves_tostring_cpl_type(fibre_mask_type));
                            
                assure( fibre_pos_length == fibre_mask_length,
                    CPL_ERROR_INCOMPATIBLE_INPUT,
                    "FIBREMASK has length %d, but "
                    "FIBREPOS has length %d",
                    fibre_mask_length, fibre_pos_length );
            
                *fib_pos= cpl_malloc(sizeof(double) * fibre_pos_length);
                *fib_msk= cpl_malloc(sizeof(int) * fibre_mask_length);

                for (fibre_ID = 0; fibre_ID < fibre_mask_length; fibre_ID++)
                {
                    uves_msg_debug("Found trace %d, position %f (%s)",
                           fibre_ID, fibre_pos[fibre_ID],
                           fibre_mask[fibre_ID] ? 
                           "enabled" : "disabled");
                    uves_ordertable_traces_add(*traces, 
                                   fibre_ID,
                                   fibre_pos[fibre_ID],
                                   fibre_mask[fibre_ID]);
                    (*fib_pos)[fibre_ID]=fibre_pos[fibre_ID];
                    (*fib_msk)[fibre_ID]=fibre_mask[fibre_ID];
                }
            }
        }
    }

  cleanup:
    uves_free_propertylist(&midas_header);
    uves_free_double(&fibre_pos);
    uves_free_int(&fibre_mask);
    uves_free_int(&tioo);
    uves_free_double(&tioy);
    uves_free_propertylist(&prime_header);

    if (cpl_error_get_code() != CPL_ERROR_NONE) 
    {
        *ordertable_filename = NULL;
        uves_free_table       (ordertable);
        uves_free_propertylist(ordertable_header);
        if (order_locations != NULL) uves_polynomial_delete(order_locations);
        if (traces != NULL)          uves_free_table       (traces);
    }
    return cpl_error_get_code();
}



/*--------------------------------------------------------------------------*/
/**
  @brief    Check if data are generated by MIDAS
  @param    header input frame's FITS header
  @param    format_is_midas result of check

  @return   CPL_ERROR_NONE iff okay

 */
/*--------------------------------------------------------------------------*/


cpl_error_code
uves_check_if_format_is_midas(uves_propertylist* header, bool* format_is_midas)
{
 
  /* Determine format of order table and read the polynomial */
  if (uves_propertylist_contains(header, UVES_DRS_ID)) {


    const char* drs_id=NULL;

    check( drs_id = uves_pfits_get_drs_id(header), "Error reading DRS ID");
    if (strstr(drs_id, "CPL") != NULL || 
	strstr(drs_id, "cpl") != NULL) {
      *format_is_midas = false;
      uves_msg_debug("Order table was written by CPL");
    } else if (strstr(drs_id, "MIDAS") != NULL || 
               strstr(drs_id, "midas") != NULL) {
      *format_is_midas = true;
      uves_msg_low("Order table was written by MIDAS");
    } else {
      assure ( false, CPL_ERROR_ILLEGAL_INPUT, 
	       "Unrecognized order table format, DRS_ID = '%s'", drs_id);
    }
  } else {

    *format_is_midas = true;
    uves_msg_debug("No '%s' keyword found. Assuming MIDAS format", UVES_DRS_ID);
  }
 
 cleanup:
  return cpl_error_get_code();

}

/*--------------------------------------------------------------------------*/
/**
  @brief    Add column 'Pixelsize' to the line table
  @param    linetable         The line table
  @return   CPL_ERROR_NONE iff okay

  This function exists to support reading MIDAS line tables, which
  don't have a 'Pixelsize' column.

 */
/*--------------------------------------------------------------------------*/

static cpl_error_code
create_column_pixelsize(cpl_table *linetable)
{
    polynomial *p = NULL;
    cpl_table *t = NULL;
    double d1, d2;
    int i;
    int degree = 3;
    
    /* Remove rows with Ident = 0 (unidentified lines) */
    check( t = uves_extract_table_rows(linetable, "Ident", CPL_GREATER_THAN, 0.1),
       "Error deleting rows with Ident=0");
    
    /* Create column Aux := Ident * Order  */
    check(( cpl_table_duplicate_column(t, "Aux", t, "Ident"),
        cpl_table_multiply_columns(t, "Aux", "Order")),
      "Error creating 'Aux' column");
    
    check( p = uves_polynomial_regression_1d(t, 
                         "X", "Aux", NULL,
                         degree,
                         NULL, NULL,
                         NULL,
                         -1),
       "Regression failed");
    
    check( d1 = uves_polynomial_get_coeff_1d(p, 1),
       "Error reading polynomial coefficient");
    
    check( d2 = uves_polynomial_get_coeff_1d(p, 2),
       "Error reading polynomial coefficient");
    
    cpl_table_new_column(linetable, LINETAB_PIXELSIZE, CPL_TYPE_DOUBLE);
    
    for (i = 0; i < cpl_table_get_nrow(linetable); i++)
    {
        int x;
        int order;
        double pixelsize;
        double ident;
        
        check(( x     = cpl_table_get_double(linetable, "X", i, NULL),
            order = cpl_table_get_int   (linetable, "Order", i, NULL),
            ident = cpl_table_get_double(linetable, "Ident", i, NULL)),
          "Error reading line table");
        
        assure( order != 0, CPL_ERROR_ILLEGAL_INPUT, "Illegal order number: %d", order);
        
        /* 
         * MIDAS approximates
         * d(lambda m)/dx (x,m)   =  d1 + 2*d2*x
         *
         * where the polynomial itself is ... + d1*x + d2*x^2 + ...
         */
        pixelsize = (d1 + 2*d2* x) / order;
//        pixelsize = uves_polynomial_derivative_2d(dispersion_relation, x, order, 1)/order;
        
        if (ident > 0.01)
        {
            cpl_table_set_double(linetable, LINETAB_PIXELSIZE, i, pixelsize);
        }
        else
        {
            cpl_table_set_invalid(linetable, LINETAB_PIXELSIZE, i);
        }
    }
    
  cleanup:
    uves_free_table(&t);
    uves_polynomial_delete(&p);
    return cpl_error_get_code();
}



/*----------------------------------------------------------------------------*/
/**
  @brief    Align line table with order table (necessary if the line table
            was created using another order table, inconsistent with the
            current order table)
  @param    linetable          The line table to correct
  @param    absolute_order     The map m = f(x, y) (from line table)
  @param    linetable_header   If non-NULL, the line table header to correct
  @param    order_locations    The reference order polynomial
  @param    minorder           Order table minimum order number
  @param    maxorder           Order table maximum order number
  @return   CPL_ERROR_NONE iff okay

  On-the-fly correction of line table order numbering if it is 
  inconsistent with input order table (DFS02694). 

  Two corrections are needed:
  - line table header (if provided),
  - 'Y' column if the input is a linetable, but not if
  it is a guess linetable in which case 'Y' is the absolute order
  number, not the relative order number (sigh...).
  
  (The best solution would
  be writing the absolute order number to the order table, but for historical 
  reasons... The ESO archive already contains many years of order tables with
  only relative order numbering)
*/
/*----------------------------------------------------------------------------*/
static void
align_order_line_table(cpl_table *linetable, const polynomial *absolute_order,
               uves_propertylist **linetable_header,
               const polynomial *order_locations, int minorder, int maxorder)
{
    polynomial *absord = NULL;

    assure ( order_locations  != NULL, CPL_ERROR_NULL_INPUT, 
             "Null order locations polynomial!");

    assure ( absolute_order  != NULL, CPL_ERROR_NULL_INPUT, 
             "Null absolute order pllynomial!");
    assure( cpl_table_has_column(linetable, "X"   ), CPL_ERROR_DATA_NOT_FOUND, 
        "Missing line table column 'X'");
    assure( cpl_table_has_column(linetable, "Ynew"), CPL_ERROR_DATA_NOT_FOUND, 
        "Missing line table column 'Ynew'");
    assure( cpl_table_has_column(linetable, "Order"), CPL_ERROR_DATA_NOT_FOUND, 
        "Missing line table column 'Order'");
    
    assure( cpl_table_get_column_type(linetable, "X") == CPL_TYPE_DOUBLE,
        CPL_ERROR_TYPE_MISMATCH, "Line table column 'X' has type %s (double expected))",
        uves_tostring_cpl_type(cpl_table_get_column_type(linetable, "X")) );
    
    assure( cpl_table_get_column_type(linetable, "Ynew") == CPL_TYPE_DOUBLE,
        CPL_ERROR_TYPE_MISMATCH, "Line table column 'Ynew' has type %s (double expected))",
        uves_tostring_cpl_type(cpl_table_get_column_type(linetable, "Ynew")) );
    
    assure( cpl_table_get_column_type(linetable, "Y") == CPL_TYPE_INT,
        CPL_ERROR_TYPE_MISMATCH, "Line table column 'Y' has type %s (integer expected))",
        uves_tostring_cpl_type(cpl_table_get_column_type(linetable, "Y")) );


    if (linetable_header != NULL)
    /* then correct first/abs order keywords */
    {
        int line_first, line_last;
        int ord_first, ord_last;
            {

                int maxx;
                int minx;
                int x, y, order, absorder;  /* At chip center */
                int coeff;
                

                maxx = uves_round_double(cpl_table_get_column_max(linetable, "X"));

                minx = uves_round_double(cpl_table_get_column_min(linetable, "X"));
                
                assure( 1 <= minx && minx <= maxx, CPL_ERROR_ILLEGAL_INPUT,
                        "Illegal min/max line x positions: %d/%d, must be > 1", 
                        minx, maxx);
        
                /* Center of chip */
                x = (minx + maxx) / 2;
                order = (minorder + maxorder) / 2;

                y = uves_polynomial_evaluate_2d(order_locations, x, order);
                if (uves_polynomial_derivative_2d(absolute_order, x, y, 2) > 0) {
                    coeff = +1;
                }
                else {
                    coeff = -1;
                } 

    assure ( order_locations  != NULL, CPL_ERROR_NULL_INPUT, 
             "Null order locations polynomial!");


                absorder = uves_round_double(uves_polynomial_evaluate_2d(absolute_order, x, y));


                uves_msg_debug("Absolute order polynomial at (%d, %d) = %f, "
                               "rounding to %d", x, y, 
                               uves_polynomial_evaluate_2d(absolute_order, x, y), absorder);

                ord_first = absorder + (minorder - order) * coeff;
                ord_last  = absorder + (maxorder - order) * coeff;
            }

            check( line_first =
           uves_pfits_get_firstabsorder(*linetable_header),
           "Could not read order number from line table header");
        
        check( line_last  =
           uves_pfits_get_lastabsorder (*linetable_header),
           "Could not read order number from line table header");
        
        uves_msg_debug("Order table range: %d - %d. Line table range: %d - %d",
               ord_first, ord_last, line_first, line_last);
        
        if (line_first != ord_first ||
        line_last  != ord_last)
        {
            uves_msg_warning("Provided line and order tables are incompatible. "
                     "Line table contains orders %d - %d. "
                     "Order table contains orders %d - %d. "
                     "Correcting on the fly",
                     line_first, line_last, ord_first, ord_last);
            
            check( uves_pfits_set_firstabsorder(*linetable_header,
                            ord_first),
               "Could not write corrected first absolute order number");
            check( uves_pfits_set_lastabsorder(*linetable_header,
                               ord_last),
               "Could not write corrected first absolute order number");

            uves_msg_debug("Setting line table order range = %d - %d",
                   ord_first, ord_last);
        }
    }
    /* This 'Y' column is the relative order number in linetables
       but the absolute order number (and therefore equal to
       the 'order' column) in line guess tables (!!) 
    */

    {
    double epsilon = 0.01; /* Must be larger than machine precision but
                  less than the typical difference between
                  absolute/relative numbering (~100) 
                   */

    if (fabs(cpl_table_get_column_median(linetable, "Y") - 
         cpl_table_get_column_median(linetable, "Order")) > epsilon)

        /* If column 'Y' is different from 'Order', 
           then 'Y' is the relative order number and
           should be corrected (if there is an inconsistency).

           For now, simply delete the 'Y' column because it is
           not used later. If the 'Y' column will be used later,
           it must be corrected at this place.
        */
        {
        uves_msg_debug("Removing line table column 'Y'");
        cpl_table_erase_column(linetable, "Y");
        }
    }
    
  cleanup:
    uves_polynomial_delete(&absord);
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Load a line table
  @param    frames               The input frameset
  @param    flames               load flames line table?
  @param    chip_name            CCD chip name
  @param    order_locations      Order definition solution. This is used
                                 to check the consistency of order and line
                 tables and, if the order numbering is inconsistent,
                 make a on-the-fly correction of the line table
  @param    minorder             Minimum order number from order table
  @param    maxorder             Maximum order number from order table
  @param    linetable_filename   File containing line table
  @param    linetable            Loaded line table
  @param    linetable_header     If non-NULL, this is the header
                                 of the extension containing the line table.
  @param    dispersion_relation  If non-NULL, the loaded dispersion relation  m.lambda=f(x,m)
  @param    absolute_order       If non-NULL, the polynomial defining the absolute
                                 order numbers abs_order=f(x,y)
  @param    chip                 CCD chip
  @param    trace_id             Load line table for this trace ID
  @param    window               Load line table for this window number. If set to -1,
                                 the first found line table for the specified 
                 trace will be loaded.

  @return   CPL_ERROR_NONE iff OK

  The function searches the frame set for a line table of the type (BLUE, REDU, REDL)
  indicated by the parameter @em chip.

  Results are returned through the parameters @em linetable_filename, @em linetable,
  @em dispersion_relation and @em absolute_order (all NULL on error).

  All columns other than 'Ident', 'Order' and 'X' are removed from the linetable.

  If the parameters @em dispersion_relation or @em absolute_order are NULL, 
  the respective polynomials are not loaded.

 */
/*----------------------------------------------------------------------------*/
void
uves_load_linetable(const cpl_frameset *frames, 
                    bool flames,
                    const char *chip_name,
                    const polynomial *order_locations, int minorder, int maxorder,
                    const char **linetable_filename,
                    cpl_table **linetable,
                    uves_propertylist **linetable_header,
                    polynomial **dispersion_relation,
                    polynomial **absolute_order,
                    enum uves_chip chip, int trace_id, int window)
{
    uves_propertylist *primary_header = NULL;
    uves_propertylist *header         = NULL;
    uves_propertylist *midas_header   = NULL;       /* MIDAS extension header */
    int *absorders                   = NULL;       /* Absolute order numbers */
    cpl_table *temp                  = NULL;
    polynomial *absolute_order_local = NULL;
    const char *tags[3];
    int number_of_tags = sizeof(tags) / sizeof(char *);
    const char *drs_id;
    bool format_is_midas;               /* Was file written by CPL or MIDAS? */
    int base_extension;                 /* Last extension (e.g. 0) before 
                       extension with line table */
    int indx;

    if (flames)
    {
        tags[0] = UVES_GUESS_LINE_TABLE(flames, chip);
        tags[1] = UVES_LINE_TABLE(flames, chip);
        tags[2] = UVES_LINE_TABLE(flames, chip);
        number_of_tags = 3;
        
        check( *linetable_filename = uves_find_frame(frames, tags, number_of_tags, &indx, NULL),
           "No line table (%s, %s or %s) found in SOF", tags[0], tags[1], tags[2]);
    }
    else
    {
        tags[0] = UVES_LINE_TABLE(flames, chip);
        tags[1] = UVES_LINE_TABLE(flames, chip);
        tags[2] = UVES_GUESS_LINE_TABLE(flames, chip);
        
        /* For backwards compatibility with MIDAS, 
           also look for LINE_TABLE_chip%d */
        if (cpl_frameset_find_const(frames, tags[0]) == NULL &&
            cpl_frameset_find_const(frames, tags[1]) == NULL &&
            cpl_frameset_find_const(frames, tags[2]) == NULL)
        {
            uves_msg_debug("No %s", tags[0]);
            
            if (window >= 1)
            {
                /* Look for LINE_TABLE_BLUEwindow */
                
                tags[0] = UVES_LINE_TABLE_MIDAS(chip, window);
                tags[1] = UVES_LINE_TABLE_MIDAS(chip, window);
                tags[2] = UVES_LINE_TABLE_MIDAS(chip, window);
                
                uves_msg_debug("Trying %s", tags[0]);
            }
            if (window <= 0)
            {
                /* Look for any LINE_TABLE_BLUEi */
                tags[0] = UVES_LINE_TABLE_MIDAS(chip, 1);
                tags[1] = UVES_LINE_TABLE_MIDAS(chip, 2);
                tags[2] = UVES_LINE_TABLE_MIDAS(chip, 3);
                
                uves_msg_debug("Trying %s, %s or %s", tags[0], tags[1], tags[2]);
            }
        }
        
        check( *linetable_filename = uves_find_frame(frames, tags, number_of_tags, &indx, NULL),
           "No line table (%s, %s or %s) found in SOF", tags[0], tags[1], tags[2]);
    }
    
    /* Read primary header */
    check( primary_header = uves_propertylist_load(*linetable_filename, 0),
       "Could not load primary header of '%s'", *linetable_filename);  

    check_nomsg( uves_warn_if_chip_names_dont_match(primary_header, chip_name, chip) );
    
    /* Determine format of line table */
    if (uves_propertylist_contains(primary_header, UVES_DRS_ID))
    {
        check( drs_id = uves_pfits_get_drs_id(primary_header), "Error reading DRS ID");
        if (strstr(drs_id, "CPL") != NULL || strstr(drs_id, "cpl") != NULL)
        {
            format_is_midas = false;
            uves_msg_debug("Line table was written by CPL");
        }
        else if (strstr(drs_id, "MIDAS") != NULL || strstr(drs_id, "midas") != NULL)
        {
            format_is_midas = true;
            uves_msg_debug("Line table was written by MIDAS");
        }
        else
        {
            assure ( false,
                 CPL_ERROR_ILLEGAL_INPUT,
                 "Unrecognized line table format, DRS_ID = '%s'", drs_id);
        }
    }
    else
    {
        format_is_midas = true;
        uves_msg_debug("No '%s' keyword found. Assuming MIDAS format", UVES_DRS_ID);
    }

    if (format_is_midas || flames)
    {
        if (!flames)
        {
            assure( trace_id == 0 && (window == -1 || (1 <= window && window <= 3)), 
                CPL_ERROR_UNSUPPORTED_MODE,
                "Cannot read (fibre, window) = (%d, %d) from MIDAS line table", 
                trace_id, window);
            
            base_extension = 0;
        }
        else
        {

	  if(trace_id > 0) {

            assure( ((1<= trace_id && trace_id <= 9) && (window == -1)), 
                CPL_ERROR_UNSUPPORTED_MODE,
                "Cannot read (fibre, window) = (%d, %d) from MIDAS line table", 
                trace_id, window);
            
            base_extension = 0;


	  } else {

            uves_msg_warning("Assuming line table is guess table");
            base_extension = 0;
	  }
        }
    }
    else
    /* Find table extension containing the line table for the specified trace and window */
    {
        int nextensions;
        bool found;
        
        check( nextensions = uves_get_nextensions(*linetable_filename),
           "Error reading number of extensions of file '%s'", *linetable_filename);
        header = NULL;
        found = false;

            uves_msg_debug("Number of extensions = %d", nextensions);

        for (base_extension = 1; base_extension < nextensions && !found; base_extension++)
        {
            int header_trace;
            int header_window;
            
            /* Read header trace & window info */
            check(( uves_free_propertylist(&header),
               header = uves_propertylist_load(*linetable_filename, base_extension)),
               "Could not header of extension %d of '%s'", 
              base_extension, *linetable_filename);
            
            check( header_trace  = uves_pfits_get_traceid     (header),
               "Error reading trace ID from header of extension %d of '%s'",
               base_extension, *linetable_filename);
            
            check( header_window = uves_pfits_get_windownumber(header),
               "Error reading window number from header of extension %d of '%s'",
               base_extension, *linetable_filename);
            
                    uves_msg_debug("Found (trace, window) = (%d, %d), need (%d, %d)",
                                   header_trace, header_window,
                                   trace_id, window);

            found = ( (trace_id == header_trace) && 
                  (window == -1 || window == header_window) );
        }
        
        assure( found,
            CPL_ERROR_ILLEGAL_INPUT,
            "Line table (trace, window) = (%d, %d) is not present in file '%s'",
            trace_id, window, *linetable_filename);

        /* Let 'base_extension' be the first extension before 
           the proper extension was found (0, 3, 6, ...) */
        base_extension -= 2;
        /* ...and incremented in for-loop */
    }

    check( *linetable = cpl_table_load(*linetable_filename,
                       base_extension + UVES_LINE_TABLE_EXTENSION,
                       1),              /* Mark identified 
                               invalid values? (1=yes) */
       "Error loading line table from extension %d of file '%s'", 
       base_extension + UVES_LINE_TABLE_EXTENSION, *linetable_filename);

    /* Read header of table extension if requested */
    if (linetable_header != NULL)
    {
        check( *linetable_header = 
           uves_propertylist_load(*linetable_filename, 
                     base_extension + UVES_LINE_TABLE_EXTENSION),
           "Could not load header of extension %d of '%s'", 
           base_extension + UVES_LINE_TABLE_EXTENSION, *linetable_filename);

        if (format_is_midas)
        {
            int size = 0;
                    cpl_type type;
            absorders = uves_read_midas_array(*linetable_header, "ORDER", &size,
                                                      &type, NULL);
                    
                    assure( type == CPL_TYPE_INT, CPL_ERROR_TYPE_MISMATCH,
                            "Type of ORDER is %s, int expected",
                            uves_tostring_cpl_type(type));

            assure( size == 2, 
                CPL_ERROR_ILLEGAL_INPUT,
                "'ORDER' array has size %d. Size 2 expected.", size);
            check(( uves_pfits_set_firstabsorder(*linetable_header, absorders[0]),
                uves_pfits_set_lastabsorder(*linetable_header, absorders[1])),
              "Error updating table header");
        }        
    }
    
    /* Read the polynomials if requested */
    if (format_is_midas)
    {
        /* Rename & cast order/ident/X columns */
        check(( cpl_table_cast_column(*linetable, "X", "xxxx", CPL_TYPE_DOUBLE),
            cpl_table_erase_column(*linetable, "X"),
            cpl_table_name_column(*linetable, "xxxx", "X")),
          "Error casting and renaming column 'X'");
        
        check(( cpl_table_cast_column(*linetable, "YNEW", "xxxx", CPL_TYPE_DOUBLE),
            cpl_table_erase_column(*linetable, "YNEW"),
            cpl_table_name_column(*linetable, "xxxx", "Ynew")),
          "Error casting and renaming column 'YNEW'");
        
        check(( cpl_table_cast_column(*linetable, "Y", "xxxx", CPL_TYPE_INT),
            cpl_table_erase_column(*linetable, "Y"),
            cpl_table_name_column(*linetable, "xxxx", "Y")),
           "Error casting and renaming column 'Y'");

        check(( cpl_table_cast_column(*linetable, "ORDER", "Order", CPL_TYPE_INT),
            cpl_table_erase_column(*linetable, "ORDER")),
           "Error casting and renaming column 'ORDER'");
           
        check( cpl_table_name_column(*linetable, "IDENT", "Ident"),
           "Error renaming column 'IDENT'");

        check( midas_header = uves_propertylist_load(
               *linetable_filename, 
               base_extension + UVES_LINE_TABLE_EXTENSION),
           "Could not load header of extension %d of '%s'",
           base_extension + UVES_LINE_TABLE_EXTENSION, *linetable_filename);
        
        if (dispersion_relation != NULL) {
            if (trace_id > 0) {
                check( *dispersion_relation = 
                       uves_polynomial_convert_from_plist_midas(midas_header,
                                                                "REGR", trace_id),
                       "Error reading polynomial 'REGR%d' from '%s'",
                       trace_id,
                       *linetable_filename);
            }
            else {
                check( *dispersion_relation = 
                       uves_polynomial_convert_from_plist_midas(midas_header,
                                                                "REGR", -1),
                       "Error reading polynomial 'REGR' from '%s'",
                       *linetable_filename);
            }
        }
        
    
      check( absolute_order_local = 
             uves_polynomial_convert_from_plist_midas(midas_header, "RORD",-1),
             "Error reading polynomial 'RORD' from '%s'", *linetable_filename);
      
      /* For FLAMES data, it seems that the polynomial is half an order shifted
         (for unknown reasons) */
        if (flames)
            {
                check_nomsg( uves_polynomial_shift(absolute_order_local, 0, 0.5) );
            }
    }
    else
    /* CPL format */
    {
        /* physmod + wavecal recipes use different naming conventions,
           workaround for this:
        */
        if (cpl_table_has_column(*linetable, "YNEW"))
        {
            cpl_table_name_column(*linetable, "YNEW", "Ynew");
        }

        if (dispersion_relation != NULL)
        {
            check( *dispersion_relation = load_polynomial(
                   *linetable_filename,
                   base_extension + UVES_LINE_TABLE_EXTENSION_DISPERSION),
               "Could not read polynomial from extension %d of file '%s'", 
               base_extension + UVES_LINE_TABLE_EXTENSION_DISPERSION,
               *linetable_filename);
        }

        check( absolute_order_local =
           load_polynomial(*linetable_filename, 
                   base_extension + UVES_LINE_TABLE_EXTENSION_ABSORDER),
           "Could not read polynomial from extension %d of file '%s'",
           base_extension + UVES_LINE_TABLE_EXTENSION_ABSORDER, *linetable_filename);
    }
        
    if (absolute_order != NULL)
    {
        *absolute_order = uves_polynomial_duplicate(absolute_order_local);
    }
    

    check( align_order_line_table(
           *linetable, absolute_order_local, linetable_header, 
           order_locations, minorder, maxorder),
       "Error while aligning line/order tables");


    /* Remove all other columns than 'Ident', 'Order', 'X', 'Pixelsize' */
    {
    const char *colname;

    /* Loop through all columns */

    /* It is undefined behaviour (for a reason!) to loop through
       columns while deleting some of them. Therefore, copy the
       structure of the linetable to another (empty) table */
    
    uves_free_table(&temp);
    check(( temp = cpl_table_new(0),
        cpl_table_copy_structure(temp, *linetable)),
           "Error duplicating line table column structure");
    
    colname = cpl_table_get_column_name(temp);
    while (colname != NULL)
        {
        if (!(strcmp(colname, "X"        ) == 0 ||
              strcmp(colname, "Order"    ) == 0 ||
              strcmp(colname, "Ident"    ) == 0 ||
	      strcmp(colname, "FIBRE"    ) == 0 ||
	      strcmp(colname, "Fibre"    ) == 0 ||
              strcmp(colname, LINETAB_PIXELSIZE) == 0))
            {
            cpl_table_erase_column(*linetable, colname);
            uves_msg_debug("Removing unused column '%s'", colname);
            }
        
        /* Call with NULL argument to get the next column name */
        colname = cpl_table_get_column_name(NULL);
        }
    }
    
    /* support MIDAS
     * Calculate 'Pixel' column (lower case) for MIDAS tables 
     */
    if ( !cpl_table_has_column(*linetable, LINETAB_PIXELSIZE) )
    {
        check( create_column_pixelsize(*linetable),
           "Error adding 'Pixelsize' column");
    }

    /* Remove un-identified lines (where Ident = invalid or Ident = zero) ... */
    check( uves_erase_invalid_table_rows(*linetable, "Ident"),
       "Error deleting rows with illegal 'Ident' value");

    check( uves_erase_table_rows(*linetable, "Ident", CPL_LESS_THAN, 0.01),
       "Error deleting rows with illegal 'Ident' value");
    
    /* Check for any other invalid value */
    assure( uves_erase_invalid_table_rows(*linetable, NULL) == 0, CPL_ERROR_ILLEGAL_INPUT,
        "After deleting rows with invalid 'Ident' values, "
        "the table in extension %d of file '%s' still contains invalid rows",
        base_extension + UVES_LINE_TABLE_EXTENSION, *linetable_filename);
    
    /* Sort line table by 'Order' (ascending), then 'X' (ascending) */
    check( uves_sort_table_2(*linetable, "Order", "X", false, false), "Error sorting line table");
    
  cleanup:
    uves_free_propertylist(&primary_header);
    uves_free_propertylist(&header);
    uves_free_propertylist(&midas_header);
    uves_free_table(&temp);
    uves_polynomial_delete(&absolute_order_local);
    cpl_free(absorders);
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
    *linetable_filename = NULL;
    uves_free_table(linetable);
    if (dispersion_relation != NULL) uves_polynomial_delete(dispersion_relation);
    if (absolute_order      != NULL) uves_polynomial_delete(absolute_order);
    }
    return;
}

/*----------------------------------------------------------------------------*/
/**
   @see uves_load_linetable
*/
/*----------------------------------------------------------------------------*/
void
uves_load_linetable_const(const cpl_frameset *frames, 
                          bool flames,
                          const char *chip_name,
                          const polynomial *order_locations, int minorder, int maxorder,
                          const char **linetable_filename,
                          const cpl_table **linetable,
                          const uves_propertylist **linetable_header,
                          const polynomial **dispersion_relation,
                          polynomial **absolute_order,
                          enum uves_chip chip, int trace_id, int window)
{
    uves_load_linetable(frames, flames, chip_name, order_locations, 
                        minorder, maxorder, 
                        linetable_filename, 
                        (cpl_table **)linetable, 
                        (uves_propertylist **)linetable_header,
                        (polynomial **)dispersion_relation, 
                        absolute_order,
                        chip, trace_id, window);
}



/*----------------------------------------------------------------------------*/
/**
   @brief    Load the response curve (image or table)
   @param    frames               The input frame set
   @param    chip_name            CCD chip name
   @param    response_filename    (output) Filename of response curve
   @param    response_curve       (output) The response curve (1d image)
   @param    master_response      (output) The response curve table
   @param    response_header      (output) FITS header of response curve
   @param    chip                 CCD chip
   @return   CPL_ERROR_NONE iff OK

   On succes, exactly one of response_curve and master_response will be
   non-NULL
*/
/*----------------------------------------------------------------------------*/

cpl_error_code
uves_load_response_curve(const cpl_frameset *frames, const char *chip_name,
             const char **response_filename, 
             cpl_image **response_curve,
             cpl_table **master_response,
             uves_propertylist **response_header, enum uves_chip chip)
{
    const char *tags[2];
    int number_of_tags = sizeof(tags) / sizeof(char *);
    int extension;
    int indx;
    
    *response_curve  = NULL;
    *response_header = NULL;
    *master_response = NULL;
    
    tags[0]   = UVES_INSTR_RESPONSE (chip);
    tags[1]   = UVES_MASTER_RESPONSE(chip);
    
    check( *response_filename = uves_find_frame(frames, tags, number_of_tags, &indx, 
                        NULL), 
       "Could not find '%s' in frame set", tags[0]);

 
    if (indx == 0)
    {
        extension = UVES_INSTR_RESPONSE_EXTENSION(chip);
        
        /* Load the response image
           
        Note: Even if the response curve was saved as
        a FITS file with NAXIS=1, cpl_image_load() will
        create an image of size nx1, which is just
        what we want
        */
        check( *response_curve = uves_load_image_file(*response_filename,
                                                      /* CPL_TYPE_DOUBLE,  Convert to this type */
                            0,               /* plane number */
                                                      extension,        /* Extension number */
                            
response_header
               ),
           "Could not load response curve from extension %d of file '%s'", 
           extension, *response_filename);

        /* Load the header */
/*
        check( *response_header = uves_propertylist_load(*response_filename,
                                extension),
           "Could not load header from extension %d of file '%s'", 
           extension, *response_filename);
*/
        check_nomsg( uves_warn_if_chip_names_dont_match(*response_header, chip_name, chip) );
    }
    else
    /* Master response */
    {
        extension = UVES_MASTER_RESPONSE_EXTENSION(chip);
        
        check( *master_response = cpl_table_load(*response_filename,
                             UVES_LINE_INTMON_TABLE_EXTENSION,
                             1),   /* Mark identified 
                                  invalid values? (1=yes) */
           "Error master response curve from extension %d of file '%s'", 
           extension, *response_filename);           

        /* Convert columns to double */
        check(( cpl_table_cast_column(*master_response, "LAMBDA", "LAMBDA_double", 
                      CPL_TYPE_DOUBLE),
            cpl_table_erase_column(*master_response, "LAMBDA"),
            cpl_table_name_column(*master_response, "LAMBDA_double", "LAMBDA")),
          "Could not cast column 'LAMBDA'");

        check(( cpl_table_cast_column(*master_response, "FLUX_CONV", "FLUX_CONV_double", 
                      CPL_TYPE_DOUBLE),
            cpl_table_erase_column(*master_response, "FLUX_CONV"),
            cpl_table_name_column(*master_response, "FLUX_CONV_double", "FLUX_CONV")),
          "Could not cast column 'FLUX_CONV'");

        /* Do not need the header, which also does not contain 
           keywords needed for uves_warn_if_chip_names_dont_match() */
    }
    
  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) 
    {
        *response_filename = NULL;
        uves_free_image(response_curve);
        uves_free_propertylist(response_header);
    }
    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Load wavelength line intensity table
  @param    frames                the input frameset
  @param    line_intmon_filename  (output) file containing the table
  @param    line_intmon           (output) loaded table

  @return   CPL_ERROR_NONE iff OK

 */
/*----------------------------------------------------------------------------*/
cpl_error_code uves_load_lineintmon(const cpl_frameset *frames, 
                    const char **line_intmon_filename, 
                    cpl_table **line_intmon)
{
    const char *tags[1] = {UVES_LINE_INTMON_TABLE};    

    int number_of_tags = sizeof(tags) / sizeof(char *);
    int indx;
    
    /* Get filename */
    check( *line_intmon_filename = uves_find_frame(frames, tags, number_of_tags, 
                           &indx, NULL),
       "No line intensity table (%s) found in SOF", tags[0]);
    
    /* Load table */
    check( *line_intmon = cpl_table_load(*line_intmon_filename,
                     UVES_LINE_INTMON_TABLE_EXTENSION,
                     1),         /* Mark identified 
                            invalid values? (1=yes) */
       "Error loading line reference table from extension %d of file '%s'", 
       UVES_LINE_INTMON_TABLE_EXTENSION, *line_intmon_filename);

    check(( cpl_table_cast_column(*line_intmon, "WAVE", "Wave", CPL_TYPE_DOUBLE),
        cpl_table_erase_column(*line_intmon, "WAVE")),
      "Could not cast and rename column");
    
    /* Sort table by 'Wave' (ascending) */
    check(  uves_sort_table_1(*line_intmon, "Wave", false), "Error sorting table");
    
  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) 
    {
        *line_intmon_filename = NULL;
        uves_free_table(line_intmon);
    }
    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Load velocity correction table (FLAMES)
  @param    frames                the input frameset
  @param    corvel                (output) velocity correction table
  @param    corvel_header         (output) If non-NULL, velocity correction 
                                  table primary header
  @param    corvel_filename       (output) filename

  @return   CPL_ERROR_NONE iff OK

 */
/*----------------------------------------------------------------------------*/
void
uves_load_corvel(const cpl_frameset *frames,
         cpl_table **corvel,
         uves_propertylist **corvel_header,
         const char **corvel_filename)
{
    const char *tags[1];
    int number_of_tags = sizeof(tags) / sizeof(char *);
    int indx;
    int extension;

    tags[0] = FLAMES_CORVEL_MASK;

    assure_nomsg( corvel != NULL, CPL_ERROR_NULL_INPUT );
    assure_nomsg( corvel_filename != NULL, CPL_ERROR_NULL_INPUT );

    /* Get filename */
    check( *corvel_filename = uves_find_frame(frames, tags, number_of_tags, 
                          &indx, NULL),
       "No velocity correction table (%s) found in SOF", tags[0]);
    
    /* Load table */
    extension = 1;
    check( *corvel = cpl_table_load(*corvel_filename,
                    extension,
                    1),         /* Mark identified 
                           invalid values? (1=yes) */
       "Error loading line reference table from extension %d of file '%s'",
       extension, *corvel_filename);

    /* Load header */
    if (corvel_header != NULL)
    {
        extension = 0;
        check( *corvel_header = uves_propertylist_load(*corvel_filename,
                              extension),
           "Could not load header from extension %d of file %s",
           extension, *corvel_filename);

    }
    
  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) 
    {
        *corvel_filename = NULL;
        uves_free_table(corvel);
    }
    return;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Load the wavelength catalogue
  @param    frames               The input frameset
  @param    line_refer_filename  File containing the table
  @param    line_refer           Loaded reference table
  @param    line_refer_header    Loaded header

  @return   CPL_ERROR_NONE iff OK

  The function searches the frame for a line reference catalogue and returns
  the results through the parameters @em line_refer_filename, @em line_refer
  and @em line_refer_header (NULL on error).

  If the parameter @em linetable_header is NULL, the header is not loaded.
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_load_linerefertable(const cpl_frameset *frames, 
             const char **line_refer_filename, 
             cpl_table **line_refer, uves_propertylist **line_refer_header)
{
    const char *tags[1] = {UVES_LINE_REFER_TABLE};    

    int number_of_tags = sizeof(tags) / sizeof(char *);
    int indx;
    
    /* Get filename */
    check( *line_refer_filename = uves_find_frame(frames, tags, number_of_tags, 
                          &indx, NULL),
       "No line reference table (%s) found in SOF", tags[0]);
    
    /* Load table */
    check( *line_refer = cpl_table_load(*line_refer_filename,
                       UVES_LINE_REFER_TABLE_EXTENSION,
                       1),         /* Mark identified 
                              invalid values? (1=yes) */
       "Error loading line reference table from extension %d of file '%s'", 
       UVES_LINE_REFER_TABLE_EXTENSION, *line_refer_filename);

    /* Load header if requested */
    if (line_refer_header != NULL)
    {
        check( *line_refer_header = uves_propertylist_load(*line_refer_filename, 0),
           "Could not load header of line_refer table in '%s'", *line_refer_filename);
    }

    assure( uves_erase_invalid_table_rows(*line_refer, NULL) == 0, CPL_ERROR_ILLEGAL_INPUT,
        "Table in extension %d of file '%s' contains invalid rows", 
        UVES_LINE_REFER_TABLE_EXTENSION, *line_refer_filename);

    check(( cpl_table_cast_column(*line_refer, "WAVE", "Wave", CPL_TYPE_DOUBLE),
        cpl_table_erase_column(*line_refer, "WAVE")),
       "Could not cast and rename column");
    
    /* Write uncertainties of wavelengths.
       The value 0.002 is finetuned/retro-fitted to get a chi_sq ~ 1 when
       using the new catalogue from

       M. T. Murphy, P. Tzanavaris, J. K. Webb, C. Lovis
       "Selection of ThAr lines for wavelength calibration of echelle
       spectra and implications for variations in the fine-structure constant",
       Submitted to MNRAS
    */

#if 0
     check(( cpl_table_duplicate_column(*line_refer, "dWave", *line_refer, "Wave"),
           cpl_table_divide_scalar   (*line_refer, "dWave", 300000*10)),
       "Error writing wavelength uncertainties");
#else
     /* we should do this */
    check(( cpl_table_new_column(*line_refer, "dWave", CPL_TYPE_DOUBLE),
            cpl_table_fill_column_window(*line_refer,
                                         "dWave",
                                         0,
                                         cpl_table_get_nrow(*line_refer), 0.002)),
          "Error writing wavelength uncertainties");
#endif
    
    /* Sort table by 'Wave' (ascending) */
    check(  uves_sort_table_1(*line_refer, "Wave", false), "Error sorting table");
    
  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
    *line_refer_filename = NULL;
    uves_free_table       (line_refer);
    if (line_refer_header != NULL) uves_free_propertylist(line_refer_header);
    }
    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Load the table of standard star fluxes
  @param    frames               The input frameset
  @param    flux_table_filename  (out) File containing the table
  @param    flux_table           (out) Loaded reference table

  @return   CPL_ERROR_NONE iff OK

  The function searches the frame for a standard star flux table and returns
  the results through the parameters @em flux_table_filename and @em flux_table
  (NULL on error).

 */
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_load_flux_table(const cpl_frameset *frames, const char **flux_table_filename, 
             cpl_table **flux_table)
{
    const char *tags[1] = {UVES_FLUX_STD_TABLE};

    int number_of_tags = sizeof(tags) / sizeof(char *);
    int indx;
    
    /* Get filename */
    check( *flux_table_filename = uves_find_frame(frames, tags, number_of_tags, 
                          &indx, NULL), 
       "No standard star flux table (%s) in SOF", tags[0]);

    /* Load table */
    check( *flux_table = cpl_table_load(*flux_table_filename,
                    UVES_FLUX_STD_TABLE_EXTENSION,
                    1),         /* Mark identified 
                               invalid values? (1=yes) */
       "Error loading flux table from extension %d of file '%s'",
       UVES_FLUX_STD_TABLE_EXTENSION, *flux_table_filename);

    if (false)
        /* Don't do this, it will remove one std (LTT2415) from the table which has TYPE = NULL.
           Instead, set type to "NULL" (this is only used for messages) 
        */
        {
            if (uves_erase_invalid_table_rows(*flux_table, NULL) != 0)
                {
                    uves_msg_warning("Table in extension %d of file '%s' contains null values",
                                     UVES_FLUX_STD_TABLE_EXTENSION, *flux_table_filename);
                }
        }
    else
        {
            int i;
            for (i = 0; i < cpl_table_get_nrow(*flux_table); i++)
                {
                    if (cpl_table_get_string(*flux_table, "TYPE", i) == NULL)
                        {
                            cpl_table_set_string(*flux_table, "TYPE", i, "NULL");
                        }
                }
        }
    
        
  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) 
    {
        *flux_table_filename = NULL;
        uves_free_table(flux_table);
    }
    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Load the atmospheric extinction table
  @param    frames                 The input frameset
  @param    atmext_table_filename (output) File containing the table
  @param    atmext_table          (output) Loaded table

  @return   CPL_ERROR_NONE iff OK

  The function searches the frame for an extinction table and returns
  the results through the parameters @em atmext_filename and @em atmext_table
  (NULL on error).

 */
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_load_atmo_ext(const cpl_frameset *frames, const char **atmext_table_filename, 
           cpl_table **atmext_table)
{
    const char *tags[1] = {UVES_EXTCOEFF_TABLE};
    
    int number_of_tags = sizeof(tags) / sizeof(char *);
    int indx;
    
    /* Get filename */
    check( *atmext_table_filename = uves_find_frame(frames, tags, number_of_tags, 
                            &indx, NULL), 
       "No atmospheric extinction table (%s) found in SOF", tags[0]);

    /* Load table */
    check( *atmext_table = cpl_table_load(*atmext_table_filename,
                      UVES_EXTCOEFF_TABLE_EXTENSION,
                      1),          /* Mark identified 
                              invalid values? (1=yes) */
       "Error loading atmospheric extinction table from extension %d of file '%s'",
       UVES_EXTCOEFF_TABLE_EXTENSION, *atmext_table_filename);
    
    assure( uves_erase_invalid_table_rows(*atmext_table, NULL) == 0, CPL_ERROR_ILLEGAL_INPUT,
        "Table in extension %d of file '%s' contains invalid rows",
        UVES_EXTCOEFF_TABLE_EXTENSION, *atmext_table_filename);
    
    check( uves_sort_table_1(*atmext_table, "LAMBDA", false),
       "Error sorting table");
    
    /* Convert columns to double */
    check(( cpl_table_cast_column(*atmext_table, "LAMBDA", "LAMBDA_double", CPL_TYPE_DOUBLE),
        cpl_table_erase_column(*atmext_table, "LAMBDA"),
        cpl_table_name_column(*atmext_table, "LAMBDA_double", "LAMBDA")),
      "Could not cast column 'LAMBDA'");
    
    check(( cpl_table_cast_column(*atmext_table, "LA_SILLA", "LA_SILLA_double", CPL_TYPE_DOUBLE),
        cpl_table_erase_column(*atmext_table, "LA_SILLA"),
        cpl_table_name_column(*atmext_table, "LA_SILLA_double", "LA_SILLA")),
      "Could not cast column 'LA_SILLA'");
    
  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE) 
    {
        *atmext_table_filename = NULL;
        uves_free_table(atmext_table);
    }
    return cpl_error_get_code();
}
/*----------------------------------------------------------------------------*/
/**
  @brief    Get the order guess table filename
  @param    chip                 CCD chip

  @return   The dynamically allocated order table filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_guess_order_table_filename(enum uves_chip chip) 
{
    return uves_local_filename("orderguesstable", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the order table filename
  @param    chip                 CCD chip

  @return   The dynamically allocated order table filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_order_table_filename(enum uves_chip chip) 
{
    return uves_local_filename("ordertable", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the order definition frame filename (FLAMES)
  @param    chip                 CCD chip

  @return   The dynamically allocated order table filename, or NULL on error
 */
/*----------------------------------------------------------------------------*/
char *uves_ordef_filename(enum uves_chip chip)
{
    return uves_local_filename("order_def", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get master dark filename
  @param    chip                 CCD chip

  @return   The dynamically allocated master flat filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_masterdark_filename(enum uves_chip chip) 
{
    return uves_local_filename("masterdark", chip, -1, -1);
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Get master flat ratio filename
  @param    chip                 CCD chip
  @return   The dynamically allocated master flat filename, or NULL on error
 */
/*----------------------------------------------------------------------------*/
char *
uves_flat_ratio_filename(enum uves_chip chip) 
{
    return uves_local_filename("ratio", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get CD align table filename
  @param    chip              CCD chip
  @return   The filename

 */
/*----------------------------------------------------------------------------*/
char *uves_cd_align_filename(enum uves_chip chip)
{
    return uves_local_filename("cd_align", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get master flat filename
  @param    chip                 CCD chip

  @return   The dynamically allocated master flat filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_masterflat_filename(enum uves_chip chip) 
{
    return uves_local_filename("masterflat", chip, -1, -1);
}
/*----------------------------------------------------------------------------*/
/**
  @brief    Get master flat background filename
  @param    chip                 CCD chip

  @return   The dynamically allocated filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_masterflat_bkg_filename(enum uves_chip chip) 
{
    return uves_local_filename("masterflat_bkg", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get master bias filename
  @param    chip                 CCD chip

  @return   The dynamically allocated master bias filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_masterbias_filename(enum uves_chip chip) 
{
    return uves_local_filename("masterbias", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the guess line table filename
  @param    chip                 CCD chip

  @return   The dynamically allocated line table filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_guess_line_table_filename(enum uves_chip chip)
{
    return uves_local_filename("lineguesstable", chip, -1, -1);
}
/*----------------------------------------------------------------------------*/
/**
  @brief    Get the line table filename
  @param    chip                 CCD chip

  @return   The dynamically allocated line table filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_line_table_filename(enum uves_chip chip)
{
    return uves_local_filename("linetable", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the line table PAF filename
  @param    chip                 CCD chip

  @return   The dynamically allocated line table filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_line_table_filename_paf(enum uves_chip chip)
{
    return uves_local_filename("linetable_paf", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get filename of the response curve
  @param    chip                 CCD chip

  @return   The dynamically allocated filename, or NULL on error
  
*/
/*----------------------------------------------------------------------------*/
char *
uves_response_curve_filename(enum uves_chip chip)
{
    return uves_local_filename("response", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get filename of the 2d response curve
  @param    chip                 CCD chip

  @return   The dynamically allocated filename, or NULL on error
  
*/
/*----------------------------------------------------------------------------*/
char *
uves_response_curve_2d_filename(enum uves_chip chip)
{
    return uves_local_filename("response_2d", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get filename of the reduced standard star
  @param    chip                 CCD chip

  @return   The dynamically allocated filename, or NULL on error
  
*/
/*----------------------------------------------------------------------------*/
char *
uves_response_red_standard_filename(enum uves_chip chip)
{
    return uves_local_filename("red_std", chip, -1, -1);
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Get filename of the reduced (noappend) standard star
  @param    chip                 CCD chip

  @return   The dynamically allocated filename, or NULL on error
  
*/
/*----------------------------------------------------------------------------*/
char *
uves_response_red_noappend_standard_filename(enum uves_chip chip)
{
    return uves_local_filename("red_nonmerged", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get filename of the standard star background
  @param    chip                 CCD chip

  @return   The dynamically allocated filename, or NULL on error
  
*/
/*----------------------------------------------------------------------------*/
char *
uves_response_bkg_standard_filename(enum uves_chip chip)
{
    return uves_local_filename("bkg_std", chip, -1, -1);
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Get filename of the extraction quality table
  @param    chip                 CCD chip

  @return   The dynamically allocated filename, or NULL on error
  
*/
/*----------------------------------------------------------------------------*/
char *
uves_order_extract_qc_standard_filename(enum uves_chip chip)
{
    return uves_local_filename("order_extract_qc", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get filename of the efficiency table
  @param    chip                 CCD chip

  @return   The dynamically allocated filename, or NULL on error
  
*/
/*----------------------------------------------------------------------------*/
char *
uves_response_efficiency_filename(enum uves_chip chip)
{
    return uves_local_filename("efficiency", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the reduced 2d science spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated spectrum filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/

char *
uves_scired_red_2d_science_filename(enum uves_chip chip)
{
    return uves_local_filename("red_2d_science", chip, -1, -1);
}

/**
  @brief    Get the reduced science spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated spectrum filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/



char *
uves_scired_red_science_filename(enum uves_chip chip)
{
    return uves_local_filename("red_science", chip, -1, -1);
}
/**
  @brief    Get the reduced (noappend) science spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated spectrum filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/



char *
uves_scired_red_noappend_science_filename(enum uves_chip chip)
{
    return uves_local_filename("red_nonmerged_science", chip, -1, -1);
}
/*----------------------------------------------------------------------------*/
/**
  @brief    Get the reduced science spectrum error filename
  @param    chip                 CCD chip

  @return   The dynamically allocated spectrum error filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_scired_red_error_filename(enum uves_chip chip)
{
    return uves_local_filename("error_red_science", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the reduced science (noappend) spectrum error filename
  @param    chip                 CCD chip

  @return   The dynamically allocated spectrum error filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_scired_red_noappend_error_filename(enum uves_chip chip)
{
    return uves_local_filename("error_red_nonmerged_science", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the reduced science spectrum error filename
  @param    chip                 CCD chip

  @return   The dynamically allocated spectrum error filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_scired_red_2d_error_filename(enum uves_chip chip)
{
    return uves_local_filename("error_2d_science", chip, -1, -1);
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Get the reduced science spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated spectrum filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_scired_fluxcal_science_filename(enum uves_chip chip)
{
    return uves_local_filename("fluxcal_science", chip, -1, -1);
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Get the reduced science spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated spectrum filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_scired_fluxcal_science_noappend_filename(enum uves_chip chip)
{
    return uves_local_filename("fluxcal_nonmerged_science", chip, -1, -1);
}
/*----------------------------------------------------------------------------*/
/**
  @brief    Get the reduced science spectrum error filename
  @param    chip                 CCD chip

  @return   The dynamically allocated spectrum error filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_scired_fluxcal_error_filename(enum uves_chip chip)
{
    return uves_local_filename("fluxcal_error_science", chip, -1, -1);
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Get the reduced science spectrum error filename
  @param    chip                 CCD chip

  @return   The dynamically allocated spectrum error filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_scired_fluxcal_error_noappend_filename(enum uves_chip chip)
{
    return uves_local_filename("fluxcal_error_nonmerged_science", chip, -1, -1);
}



/**
  @brief    Get the reduced 2d science spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated spectrum filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_scired_fluxcal_science_2d_filename(enum uves_chip chip)
{
    return uves_local_filename("fluxcal_2d_science", chip, -1, -1);
}
/*----------------------------------------------------------------------------*/
/**
  @brief    Get the reduced 2d science spectrum error filename
  @param    chip                 CCD chip

  @return   The dynamically allocated spectrum error filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_scired_fluxcal_error_2d_filename(enum uves_chip chip)
{
    return uves_local_filename("fluxcal_error_2d_science", chip, -1, -1);
}
/*----------------------------------------------------------------------------*/
/**
  @brief    Get the reduced science spectrum variance filename
  @param    chip                 CCD chip

  @return   The dynamically allocated spectrum variance filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_scired_ff_variance_filename(enum uves_chip chip)
{
    return uves_local_filename("variance_ff_science", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the reduced science spectrum variance filename
  @param    chip                 CCD chip

  @return   The dynamically allocated spectrum variance filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_scired_ff_variance_2d_filename(enum uves_chip chip)
{
    return uves_local_filename("variance_ff_2d_science", chip, -1, -1);
}

/*----------------------------------------------------------------------------*//**
  @brief    Get the merged 2d science spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated merged spectrum filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_scired_merged_2d_science_filename(enum uves_chip chip)
{
    return uves_local_filename("merged_2d_science", chip, -1, -1);
}

/**
  @brief    Get the merged science spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated merged spectrum filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_scired_merged_science_filename(enum uves_chip chip)
{
    return uves_local_filename("merged_science", chip, -1, -1);
}
/*----------------------------------------------------------------------------*/
/**
  @brief    Get the merged sky spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated merged spectrum filename, or NULL on error

 */
/*----------------------------------------------------------------------------*/
char *
uves_scired_merged_sky_filename(enum uves_chip chip)
{
    return uves_local_filename("merged_sky", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the background image filename
  @param    chip                 CCD chip

  @return   The dynamically allocated background filename, or NULL on error

**/
/*----------------------------------------------------------------------------*/
char *
uves_scired_background_filename(enum uves_chip chip)
{
    return uves_local_filename("background", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the resampled science spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated resampled science spectrum filename, or NULL on error

**/
/*----------------------------------------------------------------------------*/
char *
uves_scired_resampled_filename(enum uves_chip chip)
{
    return uves_local_filename("resampled_science", chip, -1, -1);
}



/*----------------------------------------------------------------------------*/
/**
  @brief    Get the resampled 2d science spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated resampled 2d science spectrum filename, or NULL on error

**/
/*----------------------------------------------------------------------------*/
char *
uves_scired_resampled_2d_filename(enum uves_chip chip)
{
    return uves_local_filename("resampled_2d_science", chip, -1, -1);
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Get the resampled master flat spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated resampled science spectrum filename, or NULL on error

**/
/*----------------------------------------------------------------------------*/
char *
uves_scired_resampledmf_filename(enum uves_chip chip)
{
    return uves_local_filename("resampled_mflat", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the resampled, flat-fielded science spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated resampled, flat-fielded science 
            spectrum filename, or NULL on error

**/
/*----------------------------------------------------------------------------*/
char *
uves_scired_rebinned_filename(enum uves_chip chip)
{
    return uves_local_filename("resampled_ff_science", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the resampled, flat-fielded science spectrum error filename
  @param    chip                 CCD chip

  @return   The dynamically allocated resampled, flat-fielded science
            spectrum filename, or NULL on error

**/
/*----------------------------------------------------------------------------*/
char *
uves_scired_rebinned_error_filename(enum uves_chip chip)
{
    return uves_local_filename("resampled_error_ff_science", chip, -1, -1);
}


/**
  @brief    Get the resampled, flat-fielded 2d science spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated resampled, flat-fielded science 
            spectrum filename, or NULL on error

**/
/*----------------------------------------------------------------------------*/
char *
uves_scired_rebinned_2d_filename(enum uves_chip chip)
{
    return uves_local_filename("resampled_ff_2d_science", chip, -1, -1);
}

/**
  @brief    Get the resampled, flat-fielded 2d science spectrum error filename
  @param    chip                 CCD chip

  @return   The dynamically allocated resampled, flat-fielded science
            spectrum filename, or NULL on error

**/
/*----------------------------------------------------------------------------*/
char *
uves_scired_rebinned_2d_error_filename(enum uves_chip chip)
{
    return uves_local_filename("resampled_error_ff_2d_science", chip, -1, -1);
}
/*----------------------------------------------------------------------------*/
/**
  @brief    Get the science ordertrace table filename
  @param    chip                 CCD chip

  @return   The dynamically allocated science ordertrace filename, or NULL on error

**/
/*----------------------------------------------------------------------------*/
char *
uves_scired_ordertrace_filename(enum uves_chip chip)
{
    return uves_local_filename("ordertrace", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the cosmic ray mask filename
  @param    chip                 CCD chip

  @return   The dynamically allocated cosmic ray mask filename, or NULL on error

**/
/*----------------------------------------------------------------------------*/
char *
uves_scired_crmask_filename(enum uves_chip chip)
{
    return uves_local_filename("cr_mask", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the wave map filename
  @param    chip                 CCD chip

  @return   The dynamically allocated wave map filename, or NULL on error

**/
/*----------------------------------------------------------------------------*/
char *
uves_scired_wmap_filename(enum uves_chip chip)
{
    return uves_local_filename("wave_map", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the 2d extracted spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated filename, or NULL on error

**/
/*----------------------------------------------------------------------------*/
char *uves_scired_ext2d_filename(enum uves_chip chip)
{
    return uves_local_filename("ext_2d_science", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the 2d extracted + flat-fielded spectrum filename
  @param    chip                 CCD chip

  @return   The dynamically allocated filename, or NULL on error

**/
/*----------------------------------------------------------------------------*/
char *uves_scired_ff2d_filename(enum uves_chip chip)
{
    return uves_local_filename("ff_2d_science", chip, -1, -1);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get local filename
  @param    prefix        Filename prefix
  @param    chip          CCD chip
  @param    trace         The trace number (counting from zero), or -1
  @param    window        The positive window number, or -1

  @return   A pointer to a dynamically allocated filename, or NULL on error

  For example calling @em uves_local_filename("linetable", false, true, 7, 3)
  would yield @em "linetable_redu_7_3.fits" .

  Specifying negative window and trace number  gives an empty trace number substring
  and an empty window number substring, e.g.
  @em "ordertable_blue.fits" .

  Specifying negative trace number gives an empty trace number substring, e.g.
  @em "errfxb_2_blue.fits" .

 */
/*----------------------------------------------------------------------------*/
char *
uves_local_filename(const char *prefix, enum uves_chip chip, int trace, int window)
{
    char *result = NULL;
    const char *chip_string;
    const char *suffix = ".fits";     /* Always */
    char *t = NULL;
    char *w = NULL;

    assure( (trace < 0 && window < 0) ||           /* Empty suffix          */
        (trace < 0 && window > 0) ||           /* Window only suffix    */
        (trace >= 0 && window > 0),            /* Trace & window suffix */
        CPL_ERROR_ILLEGAL_INPUT, "Illegal trace and window numbers: (%d, %d)", 
        trace, window);

    /* Chip */
    chip_string = uves_chip_tostring_lower(chip);
    
    /* Trace and window number (possibly empty string) */
    check(( t = int_to_string(trace),
        w = int_to_string(window)),
          "Error creating substrings");

/* old code:
    result = cpl_calloc(strlen(prefix) + 1 + 
            strlen(chip_string) + strlen(t) + strlen(w) + strlen(suffix) + 1,
            sizeof(char));
    
    assure_mem( result );
    
    strcpy(result, prefix);
    strcat(result, "_");
    strcat(result, chip_string);
    strcat(result, t);
    strcat(result, w);
    strcat(result, suffix);
*/
    result = uves_sprintf("%s_%s%s%s%s", prefix, chip_string, t, w, suffix);
    assure_mem( result );

  cleanup:
    cpl_free(t);
    cpl_free(w);
    if (cpl_error_get_code() != CPL_ERROR_NONE)
    {
        cpl_free(result); result = NULL;
    }
    return result;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Convert integer to string
  @param    i            The integer to convert
  @return   A dynamically allocated string containing the converted integer, or NULL on error

  The integer is prefixed with the underscore character. For example calling @em int_to_string(2) 
  results in "_2".
  @em i must be -1 or non-negative. If @em i is -1, an empty string is returned.

 */
/*----------------------------------------------------------------------------*/
static char *
int_to_string(int i)
{
    char *result = NULL;

    assure( -1 <= i, CPL_ERROR_ILLEGAL_INPUT, "Illegal number (%d)", i);

    if (i == -1)
    {
        /* Empty string */
        result = cpl_calloc(1, sizeof(char));
        assure_mem( result );
    }
    else
    {
        result = uves_sprintf("_%d", i);
    }
    
  cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE){
    cpl_free(result); result = NULL;
    }
    return result;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Convert a vector to a 1d image
  @param    vector to convert       
  @param    type   of image       

  @return   a new allocated 1d image whose elements are the same as the vector
  @em The image need to be allocated .

 */
/*----------------------------------------------------------------------------*/

cpl_image*
uves_vector_to_image(const cpl_vector* vector,cpl_type type)
{
  int i=0;
  cpl_image* image=NULL;
  int size=0;
  const double* pv=NULL;
  int* pi=NULL;
  float* pf=NULL;
  double* pd=NULL;


      size=cpl_vector_get_size(vector);
      image=cpl_image_new(size,1,type);
      pv=cpl_vector_get_data_const(vector);
      if(type == CPL_TYPE_INT) {
        pi=cpl_image_get_data_int(image);
        for(i=0;i<size;i++) {
	  pi[i]=pv[i];
	}
      } else if (type == CPL_TYPE_FLOAT) {
        pf=cpl_image_get_data_float(image);
        for(i=0;i<size;i++) {
	  pf[i]=pv[i];
	}
      } else if (type == CPL_TYPE_DOUBLE) {
        pd=cpl_image_get_data_double(image);
        for(i=0;i<size;i++) {
	  pd[i]=pv[i];
	}
      } else {
        assure( false, CPL_ERROR_INVALID_TYPE,
            "No CPL type to represent BITPIX = %d", type);
      }

 cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE){
      uves_free_image(&image);
    }

    return image;

}
/**@}*/
