/*                                                                           a
 *   This file is part of the ESO X-shooter Pipeline                         *
 *   Copyright (C) 2006 European Southern Observatory                        *
 *                                                                           *
 *   This library 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: 2012-12-18 14:15:45 $
 * $Revision: 1.42 $
 * $Name: not supported by cvs2svn $
 */
/*----------------------------------------------------------------------------
  Includes
  ----------------------------------------------------------------------------*/
#include <math.h>
#include <string.h>
#include <xsh_error.h>
#include <xsh_utils_ifu.h>
#include <xsh_utils_image.h>
#include <xsh_utils_imagelist.h>
#include <xsh_utils_wrappers.h>
#include <xsh_utils_table.h>
#include <xsh_pfits.h>
#include <xsh_model_io.h>
#include <xsh_data_rec.h>
#include <xsh_data_spectrum.h>
#include <xsh_dfs.h>
#include <xsh_drl.h>
#include <xsh_globals.h>

#define PI_NUMB     (3.1415926535897932384626433832795)

static double
xsh_interpol(double x,double x1,double x2,double y1,double y2)
{
    return y1+(y2-y1)/(x2-x1)*(x-x1);
}


static void
xsh_plist_set_coord1(cpl_propertylist** plist,
                     const double crpix1,
                     const double crval1,
                     const double cdelt1);
static void
xsh_plist_set_coord2(cpl_propertylist** plist,
                     const double crpix2,
                     const double crval2,
                     const double cdelt2);
static void
xsh_plist_set_coord3(cpl_propertylist** plist,
                     const int crpix3,
                     const double crval3,
                     const double cdelt3);


static void
xsh_plist_set_cd_matrix2(cpl_propertylist** plist,
                         const double cd1_1,
                         const double cd1_2,
                         const double cd2_1,
                         const double cd2_2);


static void
xsh_plist_set_cd_matrix3(cpl_propertylist** plist,
                         const double cd1_3,
                         const double cd2_3,
                         const double cd3_1,
                         const double cd3_2,
                         const double cd3_3);



/**
@brief set world coordinate system
@param plist input propertylist
@param crpix1 value of CRPIX1 (ref pixel axis 1 coord in pix coordinates)
@param crval1 value of CRVAL1 (ref pixel axis 1 coord in sky coordinates)
@param cdelt1 value of CDELT1 (ref pixel axis 1 size )
@return updated propertylist
 */
static void
xsh_plist_set_coord1(cpl_propertylist** plist,
                     const double crpix1,
                     const double crval1,
                     const double cdelt1)
{
    cpl_propertylist_erase_regexp(*plist, "^CTYPE1",0);
    cpl_propertylist_insert_after_string(*plist,"EXPTIME","CTYPE1","RA---TAN");
    cpl_propertylist_set_comment(*plist, "CTYPE1", "Projected Rectascension");
    cpl_propertylist_erase_regexp(*plist, "^CRPIX1",0);
    cpl_propertylist_insert_after_double(*plist,"CTYPE1","CRPIX1", crpix1) ;
    cpl_propertylist_set_comment(*plist, "CRPIX1","Reference pixel in RA" ) ;

    cpl_propertylist_erase_regexp(*plist, "^CRVAL1",0);
    cpl_propertylist_insert_after_double(*plist, "CRPIX1", "CRVAL1", crval1 ) ;
    cpl_propertylist_set_comment(*plist, "CRVAL1","Reference RA" ) ;

    cpl_propertylist_erase_regexp(*plist, "^CDELT1",0);
    cpl_propertylist_insert_after_double(*plist,"CRVAL1","CDELT1",cdelt1 ) ;
    cpl_propertylist_set_comment(*plist, "CDELT1","pixel scale" ) ;

    cpl_propertylist_erase_regexp(*plist, "^CUNIT1",0);
    cpl_propertylist_insert_after_string(*plist, "CDELT1",  "CUNIT1", "deg" ) ;
    cpl_propertylist_set_comment(*plist, "CUNIT1","RA-UNIT" ) ;

    return;
}


/**
@brief set world coordinate system
@param plist input propertylist
@param crpix2 value of CRPIX2 (ref pixel axis 2 coord in pix coordinates)
@param crval2 value of CRVAL2 (ref pixel axis 2 coord in sky coordinates)
@param cdelt2 value of CDELT2 (ref pixel axis 2 size )
@return updated propertylist
 */
static void
xsh_plist_set_coord2(cpl_propertylist** plist,
                     const double crpix2,
                     const double crval2,
                     const double cdelt2)
{
    cpl_propertylist_erase_regexp(*plist, "^CTYPE2",0);
    cpl_propertylist_insert_after_string(*plist,"CUNIT1","CTYPE2","DEC--TAN");
    cpl_propertylist_set_comment(*plist, "CTYPE2", "Projected Declination") ;

    cpl_propertylist_erase_regexp(*plist, "^CRPIX2",0);
    cpl_propertylist_insert_after_double(*plist,"CTYPE2","CRPIX2",crpix2 ) ;
    cpl_propertylist_set_comment(*plist, "CRPIX2", "Reference pixel in DEC") ;

    cpl_propertylist_erase_regexp(*plist,"^CRVAL2",0);
    cpl_propertylist_insert_after_double(*plist,"CRPIX2","CRVAL2",crval2) ;
    cpl_propertylist_set_comment(*plist,"CRVAL2","Reference DEC") ;

    cpl_propertylist_erase_regexp(*plist,"^CDELT2",0);
    cpl_propertylist_insert_after_double(*plist,"CRVAL2","CDELT2",cdelt2 ) ;
    cpl_propertylist_set_comment(*plist,"CDELT2","pixel scale") ;

    cpl_propertylist_erase_regexp(*plist,"^CUNIT2",0);
    cpl_propertylist_insert_after_string(*plist,"CDELT2","CUNIT2", "deg" ) ;
    cpl_propertylist_set_comment(*plist,"CUNIT2","DEC-UNIT") ;


}



/**
@brief set world coordinate system
@param plist input propertylist
@param crpix3 value of CRPIX3 (ref pixel axis 3 coord in pix coordinates)
@param crval3 value of CRVAL3 (ref pixel axis 3 coord in sky coordinates)
@param cdelt3 value of CDELT3 (ref pixel axis 3 size )
@return updated propertylist
 */
static void
xsh_plist_set_coord3(cpl_propertylist** plist,
                     const int crpix3,
                     const double crval3,
                     const double cdelt3)
{
    cpl_propertylist_erase_regexp(*plist, "^CTYPE3",0);
    cpl_propertylist_insert_after_string(*plist,"EXPTIME", "CTYPE3", "WAVE" ) ;
    cpl_propertylist_set_comment(*plist,"CTYPE3","wavelength axis in nm") ;


    cpl_propertylist_erase_regexp(*plist, "^CRPIX3",0);
    cpl_propertylist_insert_after_double(*plist, "CTYPE3", "CRPIX3", (double)crpix3 ) ;
    cpl_propertylist_set_comment(*plist, "CRPIX3", "Reference pixel in z") ;

    cpl_propertylist_erase_regexp(*plist, "^CRVAL3",0);
    cpl_propertylist_insert_after_double(*plist,"CRPIX3", "CRVAL3", crval3) ;
    cpl_propertylist_set_comment(*plist, "CRVAL3", "central wavelength") ;

    cpl_propertylist_erase_regexp(*plist, "^CDELT3",0);

    cpl_propertylist_insert_after_double(*plist,"CRVAL3","CDELT3",cdelt3) ;
    cpl_propertylist_set_comment(*plist, "CDELT3", "nm per pixel") ;

    cpl_propertylist_erase_regexp(*plist, "^CUNIT3",0);
    cpl_propertylist_insert_after_string(*plist,"CDELT3", "CUNIT3", "nm" ) ;
    cpl_propertylist_set_comment(*plist, "CUNIT3",  "spectral unit" ) ;

    cpl_propertylist_erase_regexp(*plist, "^SPECSYS",0);
    cpl_propertylist_insert_after_string(*plist,"CUNIT3", "SPECSYS", "TOPOCENT" ) ;
    cpl_propertylist_set_comment(*plist, "SPECSYS",  "Coordinate reference frame" ) ;

}


/**
@brief set world coordinate system: CD matrix
@param plist input propertylist
@param cd1_1 value of CD1_1
@param cd1_2 value of CD1_2
@param cd2_1 value of CD2_1
@param cd2_2 value of CD2_2
@return updated propertylist
 */

static void
xsh_plist_set_cd_matrix2(cpl_propertylist** plist,
                         const double cd1_1,
                         const double cd1_2,
                         const double cd2_1,
                         const double cd2_2)
{

    check(cpl_propertylist_erase_regexp(*plist, "^CD1_1",0));
    check(cpl_propertylist_insert_after_double(*plist,"EXPTIME",
                    "CD1_1", cd1_1 )) ;
    check(cpl_propertylist_set_comment(*plist, "CD1_1",
                    "CD rotation matrix" )) ;

    check(cpl_propertylist_erase_regexp(*plist, "^CD1_2",0));
    check(cpl_propertylist_insert_after_double(*plist, "CD1_1",
                    "CD1_2", cd1_2 )) ;
    check(cpl_propertylist_set_comment(*plist, "CD1_2",
                    "CD rotation matrix" )) ;

    check(cpl_propertylist_erase_regexp(*plist, "^CD2_1",0));
    check(cpl_propertylist_insert_after_double(*plist, "CD1_2",
                    "CD2_1", cd2_1 )) ;
    check(cpl_propertylist_set_comment(*plist, "CD2_1",
                    "CD rotation matrix" )) ;

    check(cpl_propertylist_erase_regexp(*plist, "^CD2_2",0));
    check(cpl_propertylist_insert_after_double(*plist, "CD2_1",
                    "CD2_2", cd2_2 )) ;
    check(cpl_propertylist_set_comment(*plist, "CD2_2",
                    "CD rotation matrix" )) ;

    cleanup:
    return;


}


/**
@brief set world coordinate system: CD matrix
@param plist input propertylist
@param cd1_3 value of CD1_3
@param cd2_3 value of CD2_3
@param cd3_1 value of CD3_1
@param cd3_2 value of CD3_2
@param cd3_3 value of CD3_3

@return updated propertylist
 */

static void
xsh_plist_set_cd_matrix3(cpl_propertylist** plist,
                         const double cd1_3,
                         const double cd2_3,
                         const double cd3_1,
                         const double cd3_2,
                         const double cd3_3)
{


    check(cpl_propertylist_erase_regexp(*plist, "^CD1_3",0));
    check(cpl_propertylist_insert_after_double(*plist,"EXPTIME",
                    "CD1_3", cd1_3 )) ;
    check(cpl_propertylist_set_comment(*plist, "CD1_3",
                    "CD rotation matrix" )) ;


    check(cpl_propertylist_erase_regexp(*plist, "^CD2_3",0));
    check(cpl_propertylist_insert_after_double(*plist,"CD1_3",
                    "CD2_3", cd2_3 )) ;
    check(cpl_propertylist_set_comment(*plist, "CD2_3",
                    "CD rotation matrix" )) ;



    check(cpl_propertylist_erase_regexp(*plist, "^CD3_1",0));
    check(cpl_propertylist_insert_after_double(*plist,"CD2_3",
                    "CD3_1", cd3_1 )) ;
    check(cpl_propertylist_set_comment(*plist, "CD3_1",
                    "CD rotation matrix" )) ;

    check(cpl_propertylist_erase_regexp(*plist, "^CD3_2",0));
    check(cpl_propertylist_insert_after_double(*plist, "CD3_1",
                    "CD3_2", cd3_2 )) ;
    check(cpl_propertylist_set_comment(*plist, "CD3_2",
                    "CD rotation matrix" )) ;

    check(cpl_propertylist_erase_regexp(*plist, "^CD3_3",0));
    check(cpl_propertylist_insert_after_double(*plist, "CD3_2",
                    "CD3_3", cd3_3 )) ;
    check(cpl_propertylist_set_comment(*plist, "CD3_3",
                    "CD rotation matrix" )) ;

    cleanup:
    return;


}

/**
@brief set world coordinate system for a cube
@name xsh_change_plist_cube
@param plist input propertylist
@param name file name
@param cenLambda central wawelength
@param dispersion   dispersion
@param cenpix central pixel
@param center_x   center x
@param center_y   center y
 */
cpl_error_code
xsh_cube_set_wcs(cpl_propertylist * plist,
                 float cenLambda,
                 float disp_x,
                 float disp_y,
                 float disp_z,
                 float center_x,
                 float center_y,
                 int   center_z)
{


    double ra ;
    double dec ;
    double angle ;
    float radangle ;
    double cd1_1, cd1_2, cd2_1, cd2_2 ;
    int sign_swap = -1;


    double cdelt1=disp_x;
    double cdelt2=disp_y;
    double cdelt3=disp_z;

    double crpix1=center_x;
    double crpix2=center_y;
    int crpix3=center_z;

    double crval1=0;
    double crval2=0;
    double crval3=cenLambda;

    ra = xsh_pfits_get_ra(plist) ;
    dec = xsh_pfits_get_dec(plist) ;

    //get better coordinate values
    ra=xsh_pfits_get_tel_targ_alpha(plist);
    dec=xsh_pfits_get_tel_targ_delta(plist);

    //xsh_msg("ra=%f",ra);
    //xsh_msg("dec=%f",dec);
    ra=xsh_hms2deg(ra);
    dec=xsh_sess2deg(dec);
    //xsh_msg("ra=%f",ra);
    //xsh_msg("dec=%f",dec);

    crval1=ra;
    crval2=dec;

    angle = xsh_pfits_get_posangle(plist) ;
    /* in PUPIL data there is not posangle info: we reset the error */
    if(cpl_error_get_code() != CPL_ERROR_NONE) {
        cpl_error_reset();
    }
    cdelt1=sign_swap*disp_x / 3600.;
    cdelt2=         +disp_y / 3600.;


    radangle = angle * PI_NUMB / 180. ;
    cd1_1 = +cdelt1*cos(radangle);
    cd1_2 = -cdelt2*sin(radangle);
    cd2_1 = +cdelt1*sin(radangle);
    cd2_2 = +cdelt2*cos(radangle);


    xsh_plist_set_coord1(&plist,crpix1,crval1,cdelt1);
    xsh_plist_set_coord2(&plist,crpix2,crval2,cdelt2);

    xsh_plist_set_coord3(&plist,crpix3,crval3,cdelt3);
    xsh_plist_set_cd_matrix2(&plist,cd1_1,cd1_2,cd2_1,cd2_2);
    xsh_plist_set_cd_matrix3(&plist,0,0,0,0,disp_z);

    return cpl_error_get_code();
}







/**@{*/

/*---------------------------------------------------------------------------*/
/**
   @brief check edge values (llx,urx) fit in image size (1,nx)
   @param px [IN] point position
   @param nx [IN] x image size
   @param rad_x [IN] x radii
   @param llx [OUT] lower left X
   @param urx [OUT] upper right X

   @return void
 */
/*---------------------------------------------------------------------------*/


void
xsh_edge_check(const int px,const int nx,const int rad_x,
               int* llx,int* urx)
{


    *llx=px-rad_x;
    *urx=px+rad_x;
    *llx=(*llx>1) ? *llx : 1;
    *urx=(*urx<nx) ? *urx : nx;
    return;

}


/*---------------------------------------------------------------------------*/
/**
   @brief convert X-Y coordinates to Wavelengt Slit space
   @param x_centroid  centrod position on X axis 
   @param p_obj_cen [IN] pointer to central object array
   @param p_slit [IN] pointer to slit array
   @param p_wave [IN] pointer to wave array
   @param lly   [IN] Lower left X
   @param nx  [IN] number of X points (vector size) l
   @param row [IN] detector row position(Y)
   @param p_obj_cen_s [OUT] pointer to object s position
   @param p_obj_cen_w [OUT] pointer to object s position

   @return void
 */
/*---------------------------------------------------------------------------*/

void
xsh_convert_xy_to_ws(double x_centroid, 
                     double* p_obj_cen,
                     double* p_slit,
                     double* p_wave,
                     const int lly,
                     const int nx,
                     const int row,
                     double* p_obj_cen_s,
                     double* p_obj_cen_w)
{
    int x_ceil=0;
    int x_floor=0;
    double s_ceil=0;
    double s_floor=0;
    double w_ceil=0;
    double w_floor=0;

    p_obj_cen[row]=x_centroid;
    x_ceil=ceil(x_centroid);
    x_floor=floor(x_centroid);

    s_ceil =p_slit[lly*nx+x_ceil];
    s_floor=p_slit[lly*nx+x_floor];

    w_ceil =p_wave[lly*nx+x_ceil];
    w_floor=p_wave[lly*nx+x_floor];

    p_obj_cen_s[row]=xsh_interpol(x_centroid,x_floor,x_ceil,s_floor,s_ceil);
    p_obj_cen_w[row]=xsh_interpol(x_centroid,x_floor,x_ceil,w_floor,w_ceil);

    return;
}


/*---------------------------------------------------------------------------*/
/**
   @brief swap lower and upper edge columns
   @param tab [INOUT] pointer to input/output table

   @return if no error, CPL_ERROR_NONE
 */
/*---------------------------------------------------------------------------*/

cpl_error_code
xsh_table_edges_swap_low_upp(cpl_table** tab)
{
    /* swap LOW/UPP in case of VIS */
    cpl_table_duplicate_column(*tab,"OBJ_LOW_S_TMP",*tab,"OBJ_LOW_S");
    cpl_table_duplicate_column(*tab,"OBJ_UPP_S_TMP",*tab,"OBJ_UPP_S");

    cpl_table_erase_column(*tab,"OBJ_LOW_S");
    cpl_table_erase_column(*tab,"OBJ_UPP_S");

    cpl_table_duplicate_column(*tab,"OBJ_UPP_S",*tab,"OBJ_LOW_S_TMP");
    cpl_table_duplicate_column(*tab,"OBJ_LOW_S",*tab,"OBJ_UPP_S_TMP");

    cpl_table_erase_column(*tab,"OBJ_LOW_S_TMP");
    cpl_table_erase_column(*tab,"OBJ_UPP_S_TMP");

    cpl_table_duplicate_column(*tab,"OBJ_LOW_W_TMP",*tab,"OBJ_LOW_W");
    cpl_table_duplicate_column(*tab,"OBJ_UPP_W_TMP",*tab,"OBJ_UPP_W");

    cpl_table_erase_column(*tab,"OBJ_LOW_W");
    cpl_table_erase_column(*tab,"OBJ_UPP_W");

    cpl_table_duplicate_column(*tab,"OBJ_UPP_W",*tab,"OBJ_LOW_W_TMP");
    cpl_table_duplicate_column(*tab,"OBJ_LOW_W",*tab,"OBJ_UPP_W_TMP");

    cpl_table_erase_column(*tab,"OBJ_LOW_W_TMP");
    cpl_table_erase_column(*tab,"OBJ_UPP_W_TMP");


    return cpl_error_get_code();
}



/*---------------------------------------------------------------------------*/
/**
   @brief Add to edges table columns to store LOW/CEN/UPP object positions
   @param name [IN] input file name

   @return corrected table
 */
/*---------------------------------------------------------------------------*/

cpl_table*
xsh_table_edge_prepare(const char* name)
{
    cpl_table* tab=NULL;
    int nrow=0;

    check(tab=cpl_table_load(name,2,0));

    nrow=cpl_table_get_nrow(tab);

    cpl_table_new_column(tab,"OBJ_LOW_X",CPL_TYPE_DOUBLE);
    cpl_table_new_column(tab,"OBJ_CEN_X",CPL_TYPE_DOUBLE);
    cpl_table_new_column(tab,"OBJ_UPP_X",CPL_TYPE_DOUBLE);

    cpl_table_fill_column_window(tab,"OBJ_LOW_X",0,nrow,-1);
    cpl_table_fill_column_window(tab,"OBJ_CEN_X",0,nrow,-1);
    cpl_table_fill_column_window(tab,"OBJ_UPP_X",0,nrow,-1);

    cpl_table_new_column(tab,"OBJ_LOW_S",CPL_TYPE_DOUBLE);
    cpl_table_new_column(tab,"OBJ_LOW_W",CPL_TYPE_DOUBLE);
    cpl_table_new_column(tab,"OBJ_CEN_S",CPL_TYPE_DOUBLE);
    cpl_table_new_column(tab,"OBJ_CEN_W",CPL_TYPE_DOUBLE);
    cpl_table_new_column(tab,"OBJ_UPP_S",CPL_TYPE_DOUBLE);
    cpl_table_new_column(tab,"OBJ_UPP_W",CPL_TYPE_DOUBLE);

    cpl_table_fill_column_window(tab,"OBJ_LOW_S",0,nrow,-1);
    cpl_table_fill_column_window(tab,"OBJ_LOW_W",0,nrow,-1);
    cpl_table_fill_column_window(tab,"OBJ_CEN_S",0,nrow,-1);
    cpl_table_fill_column_window(tab,"OBJ_CEN_W",0,nrow,-1);
    cpl_table_fill_column_window(tab,"OBJ_UPP_S",0,nrow,-1);
    cpl_table_fill_column_window(tab,"OBJ_UPP_W",0,nrow,-1);

    cleanup:
    return tab;

}

/* Not used
static cpl_error_code
xsh_tab_clean_traces(cpl_table** tab,
                     const double fit_wmin,
                     const double fit_wmax,
                     const char* wave_col_name,
                     const char* trace_col_name,
                     const char* fit_col_name,
		     const char* res_col_name,
		     const char* flag_col_name,
                     const int niter,
		     const double kappa)
{

  double* pdata=NULL;
  double* pwave=NULL;
  double* pres=NULL;
  double* pfit=NULL;
  int* pflag=NULL;

  cpl_polynomial* p_fit=NULL;
  cpl_vector* vdata=NULL;
  cpl_vector* vwave=NULL;
  int fit_pows[2];
  double fit_off=0;
  double fit_slope=0;
  int row=0;
  int nrow=0;
  double mse_fit=0;
  double res_stdev;
  double res_max=1;
  int i=0;
  cpl_table* ext=NULL;
  int next=0;

  check(nrow=cpl_table_get_nrow(*tab));
  cpl_table_and_selected_double(*tab,wave_col_name,CPL_GREATER_THAN,fit_wmin);
  cpl_table_and_selected_double(*tab,wave_col_name,CPL_LESS_THAN,fit_wmax);
  ext=cpl_table_extract_selected(*tab);
  check(next=cpl_table_get_nrow(ext));


  pdata=cpl_table_get_data_double(ext,trace_col_name);
  pwave=cpl_table_get_data_double(ext,wave_col_name);
  vdata=cpl_vector_wrap(next,pdata);
  vwave=cpl_vector_wrap(next,pwave);
  p_fit=xsh_polynomial_fit_1d_create(vwave,vdata,1,&mse_fit);

  cpl_polynomial_dump(p_fit,stdout);
  fit_pows[0]=0;
  check(fit_off=cpl_polynomial_get_coeff(p_fit,fit_pows));
  fit_pows[0]=1;
  check(fit_slope=cpl_polynomial_get_coeff(p_fit,fit_pows));



  cpl_table_new_column(*tab,fit_col_name,CPL_TYPE_DOUBLE);
  cpl_table_fill_column_window(*tab,fit_col_name,0,nrow,-1);
  pfit=cpl_table_get_data_double(*tab,fit_col_name);

  cpl_table_new_column(*tab,res_col_name,CPL_TYPE_DOUBLE);
  cpl_table_fill_column_window(*tab,res_col_name,0,nrow,-1);
  pres=cpl_table_get_data_double(*tab,res_col_name);

  cpl_table_new_column(*tab,flag_col_name,CPL_TYPE_INT);
  cpl_table_fill_column_window(*tab,flag_col_name,0,nrow,-1);
  pflag=cpl_table_get_data_int(*tab,flag_col_name);



  pdata=cpl_table_get_data_double(*tab,trace_col_name);
  pwave=cpl_table_get_data_double(*tab,wave_col_name);

  for(row=0;row<nrow;row++) {
    pfit[row]=fit_off+pwave[row]*fit_slope;
    pres[row]=pfit[row]-pdata[row];
  }


  // kappa-sigma-clip upper trace
  for(i=0;i<niter;i++) {
    res_stdev=cpl_table_get_column_stdev(*tab,res_col_name);
    xsh_msg("res_stdev=%g",res_stdev);
    for(row=0;row<nrow;row++) {
      if((fabs(pres[row])>kappa*res_stdev) || 
         (fabs(pres[row])>res_max)) {
        //xsh_msg("set invalid %d res=%g",row,pres[row]);
	cpl_table_set_invalid(*tab,res_col_name,row);
      } else {
	pflag[row]=1;
      }
    }
  }

  cpl_table_erase_invalid(*tab);


 cleanup:
  return cpl_error_get_code();

}
 */




/*---------------------------------------------------------------------------*/
/**
   @brief Function to calibrate object traces in IFU mode
   @param ifu_object_ff_name name of input object frame (flat field corrected)
   @param order_tab_edges_ifu_name name of input edge order table
   @param slit_map_name slit map filename
   @param wave_map_name wave map filename
   @return cpl error code
 */
/*---------------------------------------------------------------------------*/

cpl_error_code
xsh_ifu_trace_object_calibrate(const char* ifu_object_ff_name,
                               const char* order_tab_edges_ifu_name,
                               const char* slit_map_name,
                               const char* wave_map_name)
{
    cpl_image* ifu_object_ff_ima=NULL;
    cpl_image* wave_map_ima=NULL;
    cpl_image* slit_map_ima=NULL;
    cpl_image* ratio_ima=NULL;
    cpl_table* tab=NULL;

    //int sx=0;
    //int sy=0;

    int nrow=0;

    double* p_edge_lo_x=NULL;
    double* p_edge_up_x=NULL;

    double* p_slice_lo_x=NULL;
    double* p_slice_up_x=NULL;

    double* p_center_y=NULL;

    double* p_obj_low=NULL;
    double* p_obj_cen=NULL;
    double* p_obj_upp=NULL;

    double* p_obj_low_s=NULL;
    double* p_obj_low_w=NULL;
    double* p_obj_cen_s=NULL;
    double* p_obj_cen_w=NULL;
    double* p_obj_upp_s=NULL;
    double* p_obj_upp_w=NULL;

    double* p_slit=NULL;
    double* p_wave=NULL;

    double x_centroid=0;

    int llx=0;
    int lly=0;
    int urx=0;
    int ury=0;

    int row=0;

    //double range1_w_min=0;
    //double range1_w_max=0;
    //double range2_w_min=0;
    //double range2_w_max=0;

    //double range_ks_w_min=0;
    //double range_ks_w_max=0;

    int nx=0;
    int ny=0;
    cpl_propertylist* plist=NULL;
    XSH_ARM arm=XSH_ARM_UNDEFINED;
    const char* pcatg=NULL;
    char tag[25];
    char name[256];
    cpl_size px=0;
    cpl_size py=0;

    int rad_x=10;
    /*
  int niter=2;
  int kappa=2.0;
     */
    int biny=1;

    typedef enum {centroid, gaussian} xsh_fit_method;
    xsh_fit_method fit_method = centroid;


    check(ifu_object_ff_ima=cpl_image_load(ifu_object_ff_name,CPL_TYPE_DOUBLE,0,0));
    plist=cpl_propertylist_load(ifu_object_ff_name,0);
    pcatg=xsh_pfits_get_pcatg(plist);

    xsh_msg("pcatg=%s",pcatg);

    if(strstr(pcatg,"UVB") != NULL) {
        arm=XSH_ARM_UVB;
        //range1_w_min=300;
        //range1_w_max=500;
        //range_ks_w_min=380;
        //range_ks_w_max=550;
        biny=xsh_pfits_get_biny(plist);


    } else if(strstr(pcatg,"VIS") != NULL) {
        arm=XSH_ARM_VIS;
        //range1_w_min=600;
        //range1_w_max=950;

        //range_ks_w_min=600;
        //range_ks_w_max=900;
        biny=xsh_pfits_get_biny(plist);

    } else if(strstr(pcatg,"NIR") != NULL) {
        arm=XSH_ARM_NIR;
        //range1_w_min=1050;
        //range1_w_max=1250;
        //range2_w_min=2000;
        //range2_w_max=2200;

        //range_ks_w_min=1100;
        //range_ks_w_max=2200;


    }

    check(slit_map_ima=cpl_image_load(slit_map_name,CPL_TYPE_DOUBLE,0,0));
    check(wave_map_ima=cpl_image_load(wave_map_name,CPL_TYPE_DOUBLE,0,0));
    //sx=cpl_image_get_size_x(ifu_object_ff_ima);
    //sy=cpl_image_get_size_y(ifu_object_ff_ima);

    check(tab=xsh_table_edge_prepare(order_tab_edges_ifu_name));
    nrow=cpl_table_get_nrow(tab);

    p_edge_lo_x=cpl_table_get_data_double(tab,"EDG_LO_X");
    p_slice_lo_x=cpl_table_get_data_double(tab,"SLIC_LO_X");
    p_obj_low=cpl_table_get_data_double(tab,"OBJ_LOW_X");
    p_obj_low_s=cpl_table_get_data_double(tab,"OBJ_LOW_S");
    p_obj_low_w=cpl_table_get_data_double(tab,"OBJ_LOW_W");

    p_edge_up_x=cpl_table_get_data_double(tab,"EDG_UP_X");
    p_slice_up_x=cpl_table_get_data_double(tab,"SLIC_UP_X");
    p_obj_upp=cpl_table_get_data_double(tab,"OBJ_UPP_X");
    p_obj_upp_s=cpl_table_get_data_double(tab,"OBJ_UPP_S");
    p_obj_upp_w=cpl_table_get_data_double(tab,"OBJ_UPP_W");

    p_center_y=cpl_table_get_data_double(tab,"CENTER_Y");
    p_obj_cen=cpl_table_get_data_double(tab,"OBJ_CEN_X");
    p_obj_cen_s=cpl_table_get_data_double(tab,"OBJ_CEN_S");
    p_obj_cen_w=cpl_table_get_data_double(tab,"OBJ_CEN_W");

    p_slit=cpl_image_get_data_double(slit_map_ima);
    p_wave=cpl_image_get_data_double(wave_map_ima);
    nx=cpl_image_get_size_x(wave_map_ima);
    ny=cpl_image_get_size_y(wave_map_ima);

    for(row=0;row<nrow;row++) {

        lly=(int)p_center_y[row]/biny;
        ury=(int)p_center_y[row]/biny;

        //xsh_msg("lly=%d ury=%d nx=%d ny=%d",lly,ury,nx,ny);
        if((llx<nx) && (ury<ny) ) {

            /* CEN IFU slice obj position */
            llx=floor(p_slice_lo_x[row]);
            urx=ceil(p_slice_up_x[row]);

            check(cpl_image_get_maxpos_window(ifu_object_ff_ima,
                            llx,lly,urx,ury,&px,&py));

            xsh_edge_check(px,nx,rad_x,&llx,&urx);
            if(fit_method==centroid) {
                x_centroid=cpl_image_get_centroid_x_window(ifu_object_ff_ima,
                                llx,lly,urx,ury);

            } else {
                check(x_centroid=xsh_image_fit_gaussian_max_pos_x_window(ifu_object_ff_ima,
                                llx,urx,lly));
            }


            p_obj_cen[row]=x_centroid;
            /* now measure position in arcsecs and wavelength */
            if(urx<nx) {
                check(xsh_convert_xy_to_ws(x_centroid,p_obj_cen,p_slit,p_wave,
                                lly,nx,row,p_obj_cen_s,p_obj_cen_w));
            }

            /* LOW IFU slice obj position */
            llx=floor(p_edge_lo_x[row]);
            urx=ceil(p_slice_lo_x[row]);
            cpl_image_get_maxpos_window(ifu_object_ff_ima,
                                        llx,lly,urx,ury,&px,&py);

            xsh_edge_check(px,nx,rad_x,&llx,&urx);
            if(fit_method==centroid) {
                check(x_centroid=cpl_image_get_centroid_x_window(ifu_object_ff_ima,
                                llx,lly,urx,ury));
            } else {
                x_centroid=xsh_image_fit_gaussian_max_pos_x_window(ifu_object_ff_ima,
                                llx,urx,lly);
            }


            /* now measure position in arcsecs and wavelength */
            if(urx<nx) {
                check(xsh_convert_xy_to_ws(x_centroid,p_obj_low,p_slit,p_wave,
                                lly,nx,row,p_obj_low_s,p_obj_low_w));
            }

            /* UPP IFU slice obj position */
            llx=floor(p_slice_up_x[row]);
            urx=ceil(p_edge_up_x[row]);
            cpl_image_get_maxpos_window(ifu_object_ff_ima,
                                        llx,lly,urx,ury,&px,&py);

            xsh_edge_check(px,nx,rad_x,&llx,&urx);
            if(fit_method==centroid) {
                x_centroid=cpl_image_get_centroid_x_window(ifu_object_ff_ima,
                                llx,lly,urx,ury);
            } else {
                x_centroid=xsh_image_fit_gaussian_max_pos_x_window(ifu_object_ff_ima,
                                llx,urx,lly);
            }

            p_obj_upp[row]=x_centroid;
            /* now measure position in arcsecs and wavelength */
            if(urx<nx) {
                check(xsh_convert_xy_to_ws(x_centroid,p_obj_upp,p_slit,p_wave,
                                lly,nx,row,p_obj_upp_s,p_obj_upp_w));
            }
        }
    }

    if(arm==XSH_ARM_VIS) {
        xsh_table_edges_swap_low_upp(&tab);
    }
    cpl_table_duplicate_column(tab,"OBJ_CEN_PLUS_UPP_S",tab,"OBJ_CEN_S");

    cpl_table_duplicate_column(tab,"OBJ_CEN_PLUS_LOW_S",tab,"OBJ_CEN_S");

    cpl_table_add_columns(tab,"OBJ_CEN_PLUS_UPP_S","OBJ_UPP_S");
    cpl_table_add_columns(tab,"OBJ_CEN_PLUS_LOW_S","OBJ_LOW_S");



    /* we now clean results:
     -fit linear to OBJ_CEN_PLUS_UPP/LOW_S
     -compute residuals fit-value
     -compute stdev residuals
     -apply kappa-sigma clip (2 times) flagging as null clipped values
     */

    /*
  check(xsh_tab_clean_traces(&tab,range_ks_w_min,range_ks_w_max,
			     "OBJ_CEN_W","OBJ_CEN_PLUS_UPP_S",
			     "CEN_UPP_S_FIT","CEN_UPP_S_RES",
			     "CEN_UPP_S_FLAG",niter,kappa));

  check(xsh_tab_clean_traces(&tab,range_ks_w_min,range_ks_w_max,
                             "OBJ_CEN_W","OBJ_CEN_PLUS_LOW_S",
			     "CEN_LOW_S_FIT","CEN_LOW_S_RES",
			     "CEN_LOW_S_FLAG",niter,kappa));

     */
    /* save result */
    sprintf(tag,"TRACE_OBJ_%s",xsh_arm_tostring(arm));
    sprintf(name,"%s.fits",tag);
    xsh_pfits_set_pcatg(plist,tag);
    check(cpl_table_save(tab,plist,NULL,name,CPL_IO_DEFAULT));



    cleanup:
    xsh_free_image(&ifu_object_ff_ima);
    xsh_free_image(&slit_map_ima);
    xsh_free_image(&wave_map_ima);
    xsh_free_image(&ratio_ima);
    xsh_free_propertylist(&plist);
    return cpl_error_get_code();

}


/*---------------------------------------------------------------------------*/
/**
   @brief Check if a model configuration frame has been corrected for flexures
   @param model_config_frame model configuration frame
   @param sci_frame science frame
   @param instrument instrument setting
   @return cpl error code
 */
/*---------------------------------------------------------------------------*/

cpl_error_code 
xsh_frame_check_model_cfg_is_proper_for_sci(cpl_frame* model_config_frame,
                                            cpl_frame* sci_frame,
                                            xsh_instrument* instrument)
{

    cpl_propertylist* afc_plist=NULL;
    cpl_propertylist* sci_plist=NULL;
    const char* name=NULL;
    const char* sci_obs_targ_name=NULL;
    const char* afc_obs_targ_name=NULL;
    const char* afc_slit_value=NULL;
    //const char* sci_slit_value=NULL;
    int sci_obs_id=0;
    int afc_obs_id=0;

    check(name=cpl_frame_get_filename(model_config_frame));
    check(afc_plist=cpl_propertylist_load(name,0));

    check(name=cpl_frame_get_filename(sci_frame));
    check(sci_plist=cpl_propertylist_load(name,0));

    check(afc_slit_value=xsh_pfits_get_slit_value(afc_plist,instrument ));

    //check(sci_slit_value=xsh_pfits_get_slit_value(sci_plist,instrument ));

    if(strstr(afc_slit_value,"Pin_0.5") == NULL) {
        xsh_msg_error("You have used uncorrect AFC corrected model cfg frame");
        xsh_msg_error("IFU AFC corrected model CFG must have INS.OPTIi.NAME='Pin_0.5'");
        cpl_error_set(cpl_func,CPL_ERROR_ILLEGAL_INPUT);
    }


    check(sci_obs_targ_name=xsh_pfits_get_obs_targ_name(sci_plist));
    check(afc_obs_targ_name=xsh_pfits_get_obs_targ_name(afc_plist));
    if(strcmp(sci_obs_targ_name,afc_obs_targ_name) != 0) {
        xsh_msg_error("Improper AFC corrected model cfg frame to reduce sci frame");
        xsh_msg_error("Their OBS.TARG.NAME values must match");
        cpl_error_set(cpl_func,CPL_ERROR_ILLEGAL_INPUT);

    }

    check(sci_obs_id=xsh_pfits_get_obs_id(sci_plist));
    check(afc_obs_id=xsh_pfits_get_obs_id(afc_plist));

    if(sci_obs_id != afc_obs_id) {
        xsh_msg_error("Improper AFC corrected model cfg frame to reduce sci frame");
        xsh_msg_error("Their OBS.ID values must match");
        cpl_error_set(cpl_func,CPL_ERROR_ILLEGAL_INPUT);
    }

    cleanup:

    xsh_free_propertylist(&sci_plist);
    xsh_free_propertylist(&afc_plist);

    return cpl_error_get_code();
}


/*---------------------------------------------------------------------------*/
/**
   @brief utility to check if a frame has been corrected for flexures
   @param model_config_frame  input frame

   @return corrected table
 */
/*---------------------------------------------------------------------------*/

cpl_error_code 
xsh_frame_check_model_cfg_is_afc_corrected(cpl_frame* model_config_frame){

    cpl_propertylist* plist=NULL;
    const char* name=NULL;
    const char* raw1_catg=NULL;

    check(name=cpl_frame_get_filename(model_config_frame));
    check(plist=cpl_propertylist_load(name,0));
    check(raw1_catg=xsh_pfits_get_raw1catg(plist));
    if(strstr(raw1_catg,"AFC_ATT") == NULL) {
        xsh_msg_error("model cfg frame seems not to be AFC corrected");
        xsh_msg_error("Their PRO.REC1.RAW1..NAME values must contain AFC_ATT");
        cpl_error_set(cpl_func,CPL_ERROR_ILLEGAL_INPUT);
    }

    cleanup:

    xsh_free_propertylist(&plist);

    return cpl_error_get_code();

}

static cpl_error_code
xsh_add_correct_coeff(cpl_table** table, cpl_propertylist* plist,
                      const char* prefix, const int row,
                      const char* col12, const char* col32)
{
    double coeff_t1=0;
    double coeff_t2=0;
    double coeff_t3=0;
    double diff_c12=0;
    double diff_c32=0;
    char key_name[40];


    sprintf(key_name,"%s_%s",prefix,"T1");
    check(coeff_t1=cpl_propertylist_get_double(plist,key_name));

    sprintf(key_name,"%s_%s",prefix,"T2");
    coeff_t2=cpl_propertylist_get_double(plist,key_name);

    sprintf(key_name,"%s_%s",prefix,"T3");
    coeff_t3=cpl_propertylist_get_double(plist,key_name);
    diff_c12=coeff_t1-coeff_t2;
    diff_c32=coeff_t3-coeff_t2;

    cpl_table_set_double(*table,col12 ,row, diff_c12);
    cpl_table_set_double(*table,col32 ,row, diff_c32);

    cleanup:

    return cpl_error_get_code();

}

static cpl_frame* 
xsh_crea_correct_coeff(cpl_frame* qc_trace_merged_frame,xsh_instrument* inst)
{

    cpl_propertylist* plist=NULL;
    const char* fname=NULL;
    cpl_table* table=NULL;
    cpl_frame* result=NULL;
    char pname[256];
    char ptag[40];

    fname=cpl_frame_get_filename(qc_trace_merged_frame);
    plist=cpl_propertylist_load(fname,0);

    table=cpl_table_new(3);
    check(cpl_table_new_column(table,"DIFF_T12", CPL_TYPE_DOUBLE));
    check(cpl_table_new_column(table,"DIFF_T32", CPL_TYPE_DOUBLE));


    check(xsh_add_correct_coeff(&table,plist,XSH_QC_TRACE_FIT_C0,0,
                    "DIFF_T12","DIFF_T32"));

    check(xsh_add_correct_coeff(&table,plist,XSH_QC_TRACE_FIT_C1,1,
                    "DIFF_T12","DIFF_T32"));

    check(xsh_add_correct_coeff(&table,plist,XSH_QC_TRACE_FIT_C2,2,
                    "DIFF_T12","DIFF_T32"));

    sprintf(ptag,"IFU_CFG_COR_%s",xsh_instrument_arm_tostring(inst));
    sprintf(pname,"%s.fits",ptag);
    xsh_msg("tag=%s name=%s",ptag,pname);

    check(cpl_table_save(table,plist,NULL,pname,CPL_IO_DEFAULT));
    result=xsh_frame_product(pname,ptag,CPL_FRAME_TYPE_TABLE,
                    CPL_FRAME_GROUP_PRODUCT,CPL_FRAME_LEVEL_FINAL);


    cleanup:

    xsh_free_propertylist(&plist);
    xsh_free_table(&table);

    return result;
}

static cpl_frame*
xsh_frame_build_sky_area(cpl_frame* slitmap_frame, const char* tag) {

    char name_o[256];
    cpl_frame* result = NULL;

    cpl_image* ima_slit = NULL;
    cpl_image* ima_sky = NULL;
    float* pslit = NULL;
    float* psky = NULL;

    const char* name = NULL;
    cpl_propertylist* plist = NULL;
    int sx = 0;
    int sy = 0;
    int i = 0;
    int j = 0;

    check(name = cpl_frame_get_filename(slitmap_frame));
    cpl_frame_dump(slitmap_frame, stdout);
    check(ima_slit = cpl_image_load(name, CPL_TYPE_FLOAT, 0, 0));
    check(plist = cpl_propertylist_load(name, 0));
    pslit = cpl_image_get_data_float(ima_slit);

    sx = cpl_image_get_size_x(ima_slit);
    sy = cpl_image_get_size_y(ima_slit);
    ima_sky = cpl_image_new(sx, sy, CPL_TYPE_FLOAT);
    psky = cpl_image_get_data_float(ima_sky);
    for (j = 1; j < sy - 1; j++) {
        for (i = 1; i < sx - 1; i++) {

            psky[j * sx + i] = 0.25 * (pslit[j * sx + i + 1] - pslit[j * sx + i - 1])
                          * (pslit[(j + 1) * sx + i] - pslit[(j - 1) * sx + i]);

        }
    }

    sprintf(name_o, "%s.fits", tag);
    check(cpl_image_save(ima_sky, name_o, XSH_PRE_DATA_BPP, plist, CPL_IO_DEFAULT));

    result = cpl_frame_duplicate(slitmap_frame);
    cpl_frame_set_filename(result, name_o);
    cpl_frame_set_tag(result, tag);

    cleanup: xsh_free_propertylist(&plist);
    xsh_free_image(&ima_slit);
    xsh_free_image(&ima_sky);
    return result;

}

static cpl_frame*
xsh_frame_build_sky_map(cpl_frame* slitmap_frame,const double value,const char* tag)
{

    char name_o[256];
    cpl_frame* result=NULL;

    cpl_image* ima = NULL;
    const char* name = NULL;
    cpl_propertylist* plist = NULL;

    check(name = cpl_frame_get_filename(slitmap_frame));
    check(ima = cpl_image_load(name, XSH_PRE_DATA_TYPE, 0, 0));
    check(plist = cpl_propertylist_load(name, 0));

    check(cpl_image_add_scalar(ima,value));
    sprintf(name_o,"%s.fits",tag);
    check(cpl_image_save(ima, name_o, XSH_PRE_DATA_BPP, plist, CPL_IO_DEFAULT));

    result = cpl_frame_duplicate(slitmap_frame);
    cpl_frame_set_filename(result, name_o);
    cpl_frame_set_tag(result, tag);
    xsh_add_temporary_file(name_o);

    cleanup:
    xsh_free_propertylist(&plist);
    xsh_free_image(&ima);

    return result;

}
/*---------------------------------------------------------------------------*/
/**
   @brief Reconstruct IFU cube
   @param[in] div_frame input frame flat field corrected
   @param[in] wavemap_frame slit map frame
   @param[in] slitmap_frame wave map frame
   @param[in] model_config_frame model configuration frame
   @param[in] instrument instrument (arm) setting
   @return corrected table
 */
/*---------------------------------------------------------------------------*/
cpl_frame*
xsh_build_ifu_map(cpl_frame* div_frame,
                  cpl_frame* wavemap_frame,
                  cpl_frame* slitmap_frame,
                  xsh_instrument* instrument)
{

    cpl_frame* map = NULL;
    cpl_frame* ra_map = NULL;
    cpl_frame* dec_map = NULL;
    cpl_frame* sky_area = NULL;

    char name_o[256];
    char tag_o[256];

    cpl_image* ima = NULL;
    const char* name = NULL;
    cpl_propertylist* plist = NULL;
    double RA=0;
    double DEC=0;

    name = cpl_frame_get_filename(div_frame);
    plist = cpl_propertylist_load(name, 0);

    sprintf(tag_o,"%s_%s",XSH_IFU_MAP_SKY,xsh_instrument_arm_tostring(instrument));
    sprintf(name_o,"%s.fits",tag_o);
    /*
  map=cpl_frame_duplicate(div_frame);
  cpl_frame_set_filename(map,name_o);
  cpl_frame_set_tag(map,tag_o);
     */
    xsh_frame_image_save2ext(div_frame,name_o,0,0);
    xsh_frame_image_save2ext(wavemap_frame,name_o,0,1);
    xsh_frame_image_save2ext(slitmap_frame,name_o,0,2);
    xsh_frame_image_save2ext(slitmap_frame,name_o,1,3);

    /*
  RA=xsh_pfits_get_ra(plist);
  DEC=xsh_pfits_get_dec(plist);
  get better coordinate values
     */
    RA=xsh_pfits_get_tel_targ_alpha(plist);
    DEC=xsh_pfits_get_tel_targ_delta(plist);

    /* converts from hours-min-sec to degree */
    RA=xsh_hms2deg(RA);
    /* converts from sessagesimal angles to degree */
    DEC=xsh_sess2deg(DEC);

    /* converts to arcsecs */
    RA*=3600;
    DEC*=3600;

    /* prepare RA map */
    sprintf(tag_o,"%s_%s","IFU_MAP_SKY_RA",xsh_instrument_arm_tostring(instrument));
    check(ra_map=xsh_frame_build_sky_map(slitmap_frame,RA,tag_o));
    check(xsh_frame_image_save2ext(ra_map,name_o,0,4));

    /* prepare DEC map */
    sprintf(tag_o,"%s_%s","IFU_MAP_SKY_DEC",xsh_instrument_arm_tostring(instrument));
    check(dec_map=xsh_frame_build_sky_map(slitmap_frame,DEC,tag_o));
    check(xsh_frame_image_save2ext(dec_map,name_o,0,5));

    /* prepare DEC map */
    sprintf(tag_o,"%s_%s","IFU_MAP_SKY_AREA",xsh_instrument_arm_tostring(instrument));
    check(sky_area=xsh_frame_build_sky_area(slitmap_frame,tag_o));
    check(xsh_frame_image_save2ext(sky_area,name_o,0,6));
    xsh_add_temporary_file(name_o);
    /*
  xsh_frame_image_save2ext(slice_map,name_o,6);
     */
    //sprintf(tag_o,"%s_%s",XSH_IFU_MAP_SKY,xsh_instrument_arm_tostring(instrument));

    map = xsh_frame_product(name_o, tag_o, CPL_FRAME_TYPE_IMAGE,
                    CPL_FRAME_GROUP_PRODUCT, CPL_FRAME_LEVEL_FINAL);

    cleanup:
    xsh_free_propertylist(&plist);
    xsh_free_image(&ima);

    return map;

}




static cpl_error_code
xsh_monitor_spectrum3D_flux_qc(cpl_imagelist* data,
                               cpl_imagelist* errs,
                               cpl_imagelist* qual,
                               cpl_propertylist* phead,
                               const double wave_s,
                               const double wave_e,
                               const int index)
{

    int zstart=0;
    int zend=0;

    double flux=0;
    double err=0;
    double sn=0;
    double ws=wave_s;
    double we=wave_e;

    char comment[40];
    char qc_key[20];

    double cdelt3=0;
    double crval3=0;
    int naxis3=0;
    int j=0;
    cpl_imagelist* data_swap=NULL;
    cpl_imagelist* errs_swap=NULL;
    //cpl_imagelist* qual_swap=NULL;
    cpl_image* data_mean_ima=NULL;
    cpl_image* errs_mean_ima=NULL;
    //cpl_image* qual_mean_ima=NULL;

    /* for the cube case we need to get size, wave start, wave step */
    check(crval3=xsh_pfits_get_crval3(phead));
    check(cdelt3=xsh_pfits_get_cdelt3(phead));
    //check(naxis3=xsh_pfits_get_naxis3(phead));
    naxis3=(int)cpl_imagelist_get_size(data);

    /* special case: index=0 is used to use full range for statistics */
    if (index != 0) {
        zstart=((ws - crval3)/cdelt3+0.5);
        zend=  ((we - crval3)/cdelt3-0.5);
    } else {
        zstart=1;
        zend=naxis3;
        ws=crval3;
        we=crval3+(naxis3-1.)*cdelt3;
    }


    /* make consistency check */
    zstart=(zstart < naxis3) ? zstart: naxis3;
    zend=(zend < naxis3) ? zend: naxis3;
    zstart=(zstart < zend) ? zstart: zend-1;

    /* swap Y with Z axis to be able to collapse over Y (spatial direction) */
    data_swap=cpl_imagelist_swap_axis_create(data,CPL_SWAP_AXIS_YZ);
    errs_swap=cpl_imagelist_swap_axis_create(errs,CPL_SWAP_AXIS_YZ);

    /* average extraction of IFU spectrum (average over original Y direction) */
    data_mean_ima=cpl_imagelist_collapse_create(data_swap);
    errs_mean_ima=cpl_imagelist_collapse_create(errs_swap);
    xsh_free_imagelist(&data_swap);
    xsh_free_imagelist(&errs_swap);
    /* loop over each IFU slice */
    for(j=1;j<=3;j++) {

        /* collapse the cube along direction cross to the ifu slices j */
        check(flux=cpl_image_get_mean_window(data_mean_ima,j,zstart,j,zend));
        if (index != 0) {
            sprintf(qc_key,"%s%d%d VAL",XSH_QC_FLUX,j,index);
        } else {
            sprintf(qc_key,"%s%d VAL",XSH_QC_FLUX,j);
        }
        sprintf(comment,"Flux in slic %d, %4.0f-%4.0f nm",j,ws,we);

        check(cpl_propertylist_append_double(phead,qc_key,flux));
        check(cpl_propertylist_set_comment(phead,qc_key,comment));
        err=cpl_image_get_mean_window(errs_mean_ima,j,zstart,j,zend);
        if (index != 0) {
            sprintf(qc_key,"%s%d%d ERR",XSH_QC_FLUX,j,index);
        } else {
            sprintf(qc_key,"%s%d ERR",XSH_QC_FLUX,j);
        }
        sprintf(comment,"Err Flux in slic %d, %4.0f-%4.0f nm",j,ws,we);
        cpl_propertylist_update_double(phead,qc_key,err);
        cpl_propertylist_set_comment(phead,qc_key,comment);
        if (index != 0) {
            sprintf(qc_key,"%s%d%d SN",XSH_QC_FLUX,j,index);
        } else {
            sprintf(qc_key,"%s%d SN",XSH_QC_FLUX,j);
        }
        sprintf(comment,"SNR in slic %d, %4.0f-%4.0f nm",j,ws,we);

        sn = (fabs(err) > 1.e-37) ? flux/err : -999;

        cpl_propertylist_append_double(phead,qc_key,sn);
        cpl_propertylist_set_comment(phead,qc_key,comment);

    }
    xsh_free_image(&data_mean_ima);
    xsh_free_image(&errs_mean_ima);
    cleanup:
    return cpl_error_get_code();

}

/*---------------------------------------------------------------------------*/
/**
   @brief Computes flux, err, SNR on IFU cube over full range and sub intervals

   @param[in] data cube
   @param[in] errs cube
   @param[in] qual cube
   @param[in] instrument to specify detector's arm
   @param[in/out] phead primary header to be updated

   @note wavelength is expected to increase along the cube Z axis
   @return cpl_error_code
 */
/*---------------------------------------------------------------------------*/

static cpl_error_code
xsh_monitor_spectrum3D_flux(cpl_imagelist* data,
                            cpl_imagelist* errs,
                            cpl_imagelist* qual,
                            xsh_instrument* instrument,
                            cpl_propertylist* phead)
{


    cpl_ensure(data != NULL, CPL_ERROR_NULL_INPUT,CPL_ERROR_NULL_INPUT);
    cpl_ensure(errs != NULL, CPL_ERROR_NULL_INPUT,CPL_ERROR_NULL_INPUT);
    cpl_ensure(qual != NULL, CPL_ERROR_NULL_INPUT,CPL_ERROR_NULL_INPUT);
    cpl_ensure(instrument != NULL, CPL_ERROR_NULL_INPUT,CPL_ERROR_NULL_INPUT);
    cpl_ensure(phead != NULL, CPL_ERROR_NULL_INPUT,CPL_ERROR_NULL_INPUT);

    if ( xsh_instrument_get_arm(instrument) == XSH_ARM_UVB){
        check( xsh_monitor_spectrum3D_flux_qc(data,errs,qual,phead,450,550,0));
        check( xsh_monitor_spectrum3D_flux_qc(data,errs,qual,phead,450,470,1));
        check( xsh_monitor_spectrum3D_flux_qc(data,errs,qual,phead,510,530,2));
    }
    else if ( xsh_instrument_get_arm(instrument) == XSH_ARM_VIS){
        xsh_monitor_spectrum3D_flux_qc(data,errs,qual,phead,600,999,0);
        xsh_monitor_spectrum3D_flux_qc(data,errs,qual,phead,672,680,1);
        xsh_monitor_spectrum3D_flux_qc(data,errs,qual,phead,745,756,2);
        xsh_monitor_spectrum3D_flux_qc(data,errs,qual,phead,992,999,3);

    }
    else if ( xsh_instrument_get_arm(instrument) == XSH_ARM_NIR){
        xsh_monitor_spectrum3D_flux_qc(data,errs,qual,phead,1100,2450,0);
        check(xsh_monitor_spectrum3D_flux_qc(data,errs,qual,phead,1514,1548,1));
        /* TODO support JH
        if(!xsh_instrument_nir_is_JH(in_frm,instrument)) {
            check(xsh_monitor_spectrum3D_flux_qc(data,errs,qual,phead,2214,2243,2));
        }
        */
    }
    
    cleanup:

    return cpl_error_get_code();
}





/*---------------------------------------------------------------------------*/
/**
   @brief Reconstruct IFU cube
   @param[in] div_frame input frame flat field corrected
   @param[in] ifu_cfg_tab_frame  IFU configuration frame
   @param[in] ifu_cfg_cor_frame  IFU configuration frame
   @param[in] spectral_format_frame spectral format frame
   @param[in] model_config_frame model configuration frame
   @param[in] wavesol_frame  wavelength solution frame
   @param[in] instrument instrument (arm) setting
   @param[in] frameset input frame set
   @param[in] parameters input parameter list
   @param[in] rectify_par input parameters to specify rectification
   @param[in] recipe_id input recipe id
   @param[in] rec_prefix input recipe prefix
   @param[in] frame_is_object flag to specify if input frame is object or sky
   @return corrected table
 */
/*---------------------------------------------------------------------------*/
cpl_error_code
xsh_build_ifu_cube(cpl_frame* div_frame, 
                   cpl_frame* ifu_cfg_tab_frame,
                   cpl_frame* ifu_cfg_cor_frame,
                   cpl_frame* spectral_format_frame,
                   cpl_frame* model_config_frame,
                   cpl_frame* wavesol_frame,
                   xsh_instrument* instrument,
                   cpl_frameset* frameset,
                   cpl_parameterlist* parameters,
                   xsh_rectify_param * rectify_par,
                   const char* recipe_id,
                   const char* rec_prefix,
                   const int frame_is_object)
{


    /* Parameters */

    cpl_image* data_img=NULL;
    cpl_image* errs_img=NULL;
    cpl_image* qual_img=NULL;

    cpl_image* data_tmp=NULL;
    cpl_image* errs_tmp=NULL;
    cpl_image* qual_tmp=NULL;
    cpl_image* mask_tmp=NULL;
    cpl_frame* qc_trace_frame=NULL;
    cpl_frame* qc_trace_merged_frame=NULL;
    cpl_frame* frame_data_cube=NULL;
    cpl_frame* frame_errs_cube=NULL;
    cpl_frame* frame_qual_cube=NULL;
    cpl_frame* frame_merged_cube=NULL;


    /* Intermediate frames */

    /* variables */
    XSH_ARM arm;
    xsh_xs_3 model_config ;
    xsh_rec_list * rec_list = NULL ;

    cpl_table* ifu_cfg_tab=NULL;
    cpl_table* sp_fmt_tab=NULL;
    cpl_vector* profile=NULL;
    cpl_imagelist* data_cube=NULL;
    cpl_imagelist* errs_cube=NULL;
    cpl_imagelist* qual_cube=NULL;

    cpl_imagelist* data_cube_merge=NULL;
    cpl_imagelist* errs_cube_merge=NULL;
    cpl_imagelist* qual_cube_merge=NULL;
    cpl_imagelist* mask_data_merge=NULL;
    cpl_imagelist* mask_errs_merge=NULL;
    cpl_imagelist* mask_qual_merge=NULL;


    float* pima=NULL;
    float* perr=NULL;
    int* pqua=NULL;
    int* pmsk=NULL;
    int nord=0;
    int save_size_uvb=4;
    int save_size_vis=2;
    int save_size_nir=2;
    int save_size=0;
    const int peack_search_hsize=5;
    int method=0; /* Gaussian */



    cpl_propertylist* data_plist=NULL;
    cpl_propertylist* errs_plist=NULL;
    cpl_propertylist* qual_plist=NULL;


    const char* sci_name=NULL;
    const char* sp_fmt_name=NULL;
    const char* ifu_cfg_name=NULL;

    char name[256];
    char data_extid[40];
    char errs_extid[40];
    char qual_extid[40];

    char qualifier[10];
    int binx=1;
    int biny=1;

    double flux_upp=0;
    double flux_cen=0;
    double flux_low=0;


    double errs_upp=0;
    double errs_cen=0;
    double errs_low=0;
    double cube_wave_min=0;
    double cube_wave_max=0;

    //double cube_wmin=550;
    //double cube_wmax=1000;
    double cube_wstep=0;
    double cube_sstep=0;

    int is=0;

    int ord=0;
    int ord_min=0;
    int ord_max=0;

    double wave=0;
    double wave_min=0;
    double wave_max=0;
    double wave_step=0;

    double wave_min_old=0;
    //double wave_max_old=0;

    int nstep_off=0;

    double s=0;
    double s_min=-2;
    double s_max=2;
    double s_step=0;

    double x=0;
    double y=0;
    double s_upp=0;
    double s_low=0;


    int radius=rectify_par->rectif_radius;

    double confidence=0;
    int naxis1=0;
    int naxis2=0;
    int naxis3=0;
    int ik=0;
    //double wave_cen=0;

    /*
    double stupid_offset_uvb=0.145;
    double stupid_offset_vis=0.09;
    double stupid_offset_nir=0.16;
     */
    double w_low_coeff1=0;
    double w_low_coeff2=0;

    double w_upp_coeff1=0;
    double w_upp_coeff2=0;

    double w_low_coeff1_uvb=-0.002972;     /*-0.00085*/
    double w_low_coeff2_uvb=2.26497e-6+0.2e-6;/*-2.26e-6;*/
    double w_upp_coeff1_uvb=8.3355331e-5;     /*-0.00085*/
    double w_upp_coeff2_uvb=-1.0682e-6;/*-2.26e-6;*/



    double w_low_coeff1_vis=-0.0016549569;
    double w_low_coeff2_vis=1.183805e-6+0.04e-6;
    double w_upp_coeff1_vis=-0.0016610719;
    double w_upp_coeff2_vis=1.0823013e-6+0.02e-6;

    double w_low_coeff1_nir=-0.00010;
    double w_low_coeff2_nir=-5.0e-9;

    double w_upp_coeff1_nir=0;
    double w_upp_coeff2_nir=0;


    double s_upp_off_uvb=4.0514429+0.16;/*1.91319135+stupid_offset_uvb;*/
    double s_upp_off_vis=5.2504895+0.16-0.703;/*+0.05; */
    double s_upp_off_nir=3.5452+0.31;
    double s_upp_off=0;

    double s_low_off_uvb=-3.4636662+0.16;/*-2.1704691+stupid_offset_uvb;*/

    double s_low_off_vis=-2.9428071+0.16-0.645;/*-0.07; */
    double s_low_off_nir=-4.451682+0.21+0.31;

    double s_low_off=0;

    int w_step_fct=1;

    int status=0;
    int mk=0;
    char tag[256];
    xsh_wavesol *wavesol=NULL;
    cpl_frame* trace_corr_tab=NULL;
    double s_upp_d0=0;
    double s_low_d0=0;
    double s_upp_d1=0;
    double s_low_d1=0;
    double s_upp_d2=0;
    double s_low_d2=0;
    int cut_uvb_spectrum=0;



    if(frame_is_object) {
        sprintf(qualifier,"OBJ");
    } else {
        sprintf(qualifier,"SKY");
    }
    /* NEW */
    /* Find STD star centroids (for each IFU slice):
     input STD star frame
     input order centre traces
     input order edges  traces
     input slit map
     output STD star centroids traces table+ QC on centroid measure
     */

    /* Find STD star centroids traces slices interdistance
     (each measured at the same wavelength)
     Find STD star centroids distances to IFU edges traces
     (each measured at the same wavelength)
     input STD star centroid traces table
     input order edges/IFU slices traces table
     input slit map (orders are spatially over-sized)
     input wave map (orders are spatially over-sized)
     output table with STD star centroids traces slices interdistance
     output corresponding QC(statistics)
     output table with STD star distances to IFU edges traces
     output corresponding QC(statistics)
     ? output "measured" slit map
     ? output "measured" wave map
     */


    check(sci_name=cpl_frame_get_filename(div_frame));
    check(data_plist=cpl_propertylist_load(sci_name,0));
    errs_plist=cpl_propertylist_duplicate(data_plist);
    qual_plist=cpl_propertylist_duplicate(data_plist);

    arm=instrument->arm;


    cube_wstep=rectify_par->rectif_bin_lambda;
    cube_sstep=rectify_par->rectif_bin_space;

    if(arm == XSH_ARM_UVB) {
        //cube_wmin=300;
        //cube_wmax=550;

        binx=xsh_pfits_get_binx(data_plist);
        biny=xsh_pfits_get_biny(data_plist);

        s_low_off=s_low_off_uvb;
        s_upp_off=s_upp_off_uvb;
        w_low_coeff1=w_low_coeff1_uvb;
        w_low_coeff2=w_low_coeff2_uvb;

        w_upp_coeff1=w_upp_coeff1_uvb;
        w_upp_coeff2=w_upp_coeff2_uvb;
        save_size=save_size_uvb;
        cut_uvb_spectrum=xsh_parameters_cut_uvb_spectrum_get(recipe_id,
                        parameters);

        //model_config.temper= xsh_pfits_get_temp2(data_plist);
    } else if(arm == XSH_ARM_VIS) {
        //cube_wmin=550;
        //cube_wmax=1000;

        //model_config.temper= xsh_pfits_get_temp5(data_plist);
        binx=xsh_pfits_get_binx(data_plist);
        biny=xsh_pfits_get_biny(data_plist);

        s_low_off=s_low_off_vis;
        s_upp_off=s_upp_off_vis;

        w_low_coeff1=w_low_coeff1_vis;
        w_low_coeff2=w_low_coeff2_vis;

        w_upp_coeff1=w_upp_coeff1_vis;
        w_upp_coeff2=w_upp_coeff2_vis;
        save_size=save_size_vis;


    } else if(arm == XSH_ARM_NIR) {
        //cube_wmin=1000;
        //cube_wmax=2500;

        //model_config.temper= xsh_pfits_get_temp82(data_plist);

        s_low_off=s_low_off_nir;
        s_upp_off=s_upp_off_nir;


        w_low_coeff1=w_low_coeff1_nir;
        w_low_coeff2=w_low_coeff2_nir;

        w_upp_coeff1=w_upp_coeff1_nir;
        w_upp_coeff2=w_upp_coeff2_nir;
        save_size=save_size_nir;

    }



    if(ifu_cfg_tab_frame) {

        ifu_cfg_name=cpl_frame_get_filename(ifu_cfg_tab_frame);
        ifu_cfg_tab=cpl_table_load(ifu_cfg_name,1,0);
        s_upp_off=cpl_table_get_double(ifu_cfg_tab,"S_UPP_OFF",arm,&status);
        s_low_off=cpl_table_get_double(ifu_cfg_tab,"S_LOW_OFF",arm,&status);

        w_upp_coeff1=cpl_table_get_double(ifu_cfg_tab,"W_UPP_COEF1",arm,&status);
        w_low_coeff1=cpl_table_get_double(ifu_cfg_tab,"W_LOW_COEF1",arm,&status);
        w_upp_coeff2=cpl_table_get_double(ifu_cfg_tab,"W_UPP_COEF2",arm,&status);
        w_low_coeff2=cpl_table_get_double(ifu_cfg_tab,"W_LOW_COEF2",arm,&status);

        w_step_fct=cpl_table_get_int(ifu_cfg_tab,"W_STEP_FCT",arm,&status);
        xsh_msg("s_upp_off=%10.8g",s_upp_off);
        xsh_msg("s_low_off=%10.8g",s_low_off);


        xsh_msg("w_upp_coeff1=%10.8g",w_upp_coeff1);
        xsh_msg("w_upp_coeff2=%10.8g",w_upp_coeff2);
        xsh_msg("w_low_coeff1=%10.8g",w_low_coeff1);
        xsh_msg("w_low_coeff2=%10.8g",w_low_coeff2);

        xsh_msg("w_step_fct=%d",w_step_fct);

    }

    if(ifu_cfg_cor_frame) {
        ifu_cfg_name=cpl_frame_get_filename(ifu_cfg_cor_frame);
        ifu_cfg_tab=cpl_table_load(ifu_cfg_name,1,0);
        s_upp_d0=cpl_table_get_double(ifu_cfg_tab,"DIFF_T12",0,&status);
        s_low_d0=cpl_table_get_double(ifu_cfg_tab,"DIFF_T32",0,&status);
        s_upp_d1=cpl_table_get_double(ifu_cfg_tab,"DIFF_T12",1,&status);
        s_low_d1=cpl_table_get_double(ifu_cfg_tab,"DIFF_T32",1,&status);
        s_upp_d2=cpl_table_get_double(ifu_cfg_tab,"DIFF_T12",2,&status);
        s_low_d2=cpl_table_get_double(ifu_cfg_tab,"DIFF_T32",2,&status);
        xsh_msg("upp_d0=%g upp_d1=%g upp_d2=%g low_d0=%g low_d1=%g low_d2=%g",
                s_upp_d0,s_upp_d1,s_upp_d2,s_low_d0,s_low_d1,s_low_d2);

    }

    cube_wstep*=w_step_fct;
    if(model_config_frame!=NULL) {
        check( xsh_model_config_load_best( model_config_frame, &model_config));
        xsh_model_binxy(&model_config,binx,biny);
    } else {
        check( wavesol = xsh_wavesol_load(  wavesol_frame, instrument));
    }
    check( rec_list = xsh_rec_list_create( instrument));
    check( profile = cpl_vector_new( CPL_KERNEL_DEF_SAMPLES));
    check(cpl_vector_fill_kernel_profile( profile,rectify_par->kernel_type,
                    rectify_par->rectif_radius));

    check(sci_name=cpl_frame_get_filename(div_frame));
    check(data_img=cpl_image_load(sci_name,XSH_PRE_DATA_TYPE,0,0));
    check(errs_img=cpl_image_load(sci_name,XSH_PRE_ERRS_TYPE,0,1));
    check(qual_img=cpl_image_load(sci_name,XSH_PRE_QUAL_TYPE,0,2));
    int nx=cpl_image_get_size_x(data_img);
    int ny=cpl_image_get_size_y(data_img);
    int* pqual_img=cpl_image_get_data_int(qual_img);
    cpl_image* var_img=cpl_image_duplicate(data_img);
    cpl_image_power(var_img,2.);
    cpl_vector* profile2=cpl_vector_duplicate(profile);
    cpl_vector_power(profile2,2.);
    check(sp_fmt_name=cpl_frame_get_filename(spectral_format_frame));
    check(sp_fmt_tab=cpl_table_load(sp_fmt_name,1,0));
    check(ord_min=cpl_table_get_column_min(sp_fmt_tab,"ORDER"));
    check(ord_max=cpl_table_get_column_max(sp_fmt_tab,"ORDER"));
    nord=ord_max-ord_min+1;

    check(cube_wave_min=cpl_table_get(sp_fmt_tab,"WLMIN",nord-1,&status));
    check(cube_wave_max=cpl_table_get(sp_fmt_tab,"WLMAX",0,&status));


    xsh_msg_debug("cube_wave_min=%10.8g cube_wave_max=%10.8g",cube_wave_min,cube_wave_max);

    /* on X we have the different IFU slices */
    naxis1=3;
    xsh_msg_debug("ord_min=%d ord_max=%d",ord_min,ord_max);
    wave_step= cube_wstep;
    s_step= cube_sstep;
    naxis2=(int)((s_max-s_min)/s_step+0.5)+1;


    xsh_free_imagelist(&data_cube_merge);
    xsh_free_imagelist(&errs_cube_merge);
    xsh_free_imagelist(&qual_cube_merge);
    xsh_free_imagelist(&mask_data_merge);
    xsh_free_imagelist(&mask_errs_merge);
    xsh_free_imagelist(&mask_qual_merge);

    data_cube_merge=cpl_imagelist_new();
    errs_cube_merge=cpl_imagelist_new();
    qual_cube_merge=cpl_imagelist_new();
    mask_data_merge=cpl_imagelist_new();
    mask_errs_merge=cpl_imagelist_new();
    mask_qual_merge=cpl_imagelist_new();


    xsh_free_image(&data_tmp);
    xsh_free_image(&errs_tmp);
    xsh_free_image(&qual_tmp);
    xsh_free_image(&mask_tmp);
    //double var_low=0, var_cen=0, var_upp=0;
    mk=0;

    for( ord = ord_max; ord >= ord_min; ord-- ) {

        xsh_free_imagelist(&data_cube);
        xsh_free_imagelist(&errs_cube);
        xsh_free_imagelist(&qual_cube);

        data_cube=cpl_imagelist_new();
        errs_cube=cpl_imagelist_new();
        qual_cube=cpl_imagelist_new();


        data_tmp=cpl_image_new(naxis1,naxis2,XSH_PRE_DATA_TYPE);
        errs_tmp=cpl_image_new(naxis1,naxis2,XSH_PRE_ERRS_TYPE);
        qual_tmp=cpl_image_new(naxis1,naxis2,XSH_PRE_QUAL_TYPE);
        mask_tmp=cpl_image_new(naxis1,naxis2,XSH_PRE_QUAL_TYPE);

        check(pima=cpl_image_get_data_float(data_tmp));
        check(perr=cpl_image_get_data_float(errs_tmp));
        check(pqua=cpl_image_get_data_int(qual_tmp));
        check(pmsk=cpl_image_get_data_int(mask_tmp));



        check(wave_min=cpl_table_get(sp_fmt_tab,"WLMIN",ord-ord_min,&status));
        check(wave_max=cpl_table_get(sp_fmt_tab,"WLMAX",ord-ord_min,&status));
        //cube_wave_min=wave_min;
        naxis3=(wave_max-wave_min)/wave_step+1;
        //wave_cen=wave_min+naxis3/2*wave_step;
        xsh_msg_debug("order=%d naxis1=%d,naxis2=%d naxis3=%d, wave_min=%g wave_max=%g",
                      ord,naxis1,naxis2,naxis3,wave_min,wave_max);

        /* in case we go from ord_max to ord_min */
        if(ord<ord_max) {
            nstep_off=(int)((wave_min-wave_min_old)/wave_step+0.5);
            wave_min=wave_min_old+nstep_off*wave_step;
        }

        ik=0;
        for( wave = wave_min; wave <= wave_max; wave+=wave_step) {
            is=0;
            for( s = s_min; s <= s_max; s+=s_step ) {
                int qual_upp=0;
                int qual_cen=0;
                int qual_low=0;
                /* upper slice */
                s_upp=-s+s_upp_off+w_upp_coeff1*wave+w_upp_coeff2*wave*wave;
                s_upp-=(s_upp_d0+s_upp_d1*wave+s_upp_d2*wave*wave)*s_step;
                if(wavesol!=NULL) {
                    check( x= xsh_wavesol_eval_polx( wavesol,wave, ord,s_upp));
                    check( y= xsh_wavesol_eval_poly( wavesol,wave, ord,s_upp));
                } else {

                    check(xsh_model_get_xy(&model_config,instrument,wave,ord,s_upp,
                                    &x,&y));
                }
                //y+=s_upp_d0+s_upp_d1*wave+s_upp_d2*wave*wave;

                //xsh_msg("is=%d s_min=%g s_max=%g s=%g",is,s_min,s_max,s);
                check(flux_upp=cpl_image_get_interpolated(data_img,x,y,
                                profile,radius,
                                profile,radius,
                                &confidence));
                
                if (confidence <= 0) {
                    pima[is*naxis1+0]=0;
                    perr[is*naxis1+0]=1;
                    pqua[is*naxis1+0]=QFLAG_MISSING_DATA;
                    pmsk[is*naxis1+0]=1;
                    /* If flux interpolation ok */
                } else {

                    check(errs_upp=cpl_image_get_interpolated(errs_img,x,y,
                                    profile,radius,
                                    profile,radius,
                                    &confidence));
                    /*
                    if(confidence > 0 && var_upp>0) {
                    errs_upp=sqrt(var_upp);
                    } else {
                        errs_upp=0;
                    }
                    */
                    /* Now propagate flags */
                    int ix=(int)(x+0.5);
                    int iy=(int)(x+0.5);
                    int jj_nx=0;
                    int jj_min= ( iy - radius >= 0 ) ? iy - radius: 0;
                    int jj_max= ( iy + radius < ny ) ? iy + radius: 0;
                    //int ii_min= ( ix - radius >= 0 ) ? ix - radius: 0;
                    //int ii_max= ( ix + radius < nx ) ? ix + radius: 0;
                    for(int jj=jj_min;jj<jj_max;jj++) {
                        jj_nx=jj*nx;
                        for(int ii=ix-radius;ii<ix+radius-1;ii++) {
                            qual_upp |= pqual_img[jj_nx+ii];         //UMR
                        }
                    }

                    pima[is*naxis1+0]=flux_upp;
                    perr[is*naxis1+0]=errs_upp;
                    pqua[is*naxis1+0]=qual_upp;
                    pmsk[is*naxis1+0]=1;
                }
                /* central slice */
                if(wavesol!=NULL) {
                    check( x= xsh_wavesol_eval_polx( wavesol,wave, ord,s));
                    check( y= xsh_wavesol_eval_poly( wavesol,wave, ord,s));
                } else {
                    check(xsh_model_get_xy(&model_config,instrument,wave,ord,s,
                                    &x,&y));
                }


                check(flux_cen=cpl_image_get_interpolated(data_img,x,y,
                                               profile,radius,
                                               profile,radius,
                                               &confidence));
              
                if (confidence <= 0) {
                    pima[is*naxis1+1]=0;
                    perr[is*naxis1+1]=1;
                    pqua[is*naxis1+1]=QFLAG_MISSING_DATA;
                    pmsk[is*naxis1+1]=1;
                    /* If flux interpolation ok */
                } else {

                    check(errs_cen=cpl_image_get_interpolated(errs_img,x,y,
                                    profile,radius,
                                    profile,radius,
                                    &confidence));
                    /*
                    if(confidence > 0 && var_cen>0) {
                    errs_cen=sqrt(var_cen);
                    } else {
                        errs_cen=0;
                    }
		    */
                   
                    /* Now propagate flags */
                    int ix=(int)(x+0.5);
                    int iy=(int)(x+0.5);
                    int jj_nx=0;
                    int jj_min= ( iy - radius >= 0 ) ? iy - radius: 0;
                    int jj_max= ( iy + radius < ny ) ? iy + radius: 0;
                    //int ii_min= ( ix - radius >= 0 ) ? ix - radius: 0;
                    //int ii_max= ( ix + radius < nx ) ? ix + radius: 0;
                    for(int jj=jj_min;jj<jj_max;jj++) {
                        jj_nx=jj*nx;
                        for(int ii=ix-radius;ii<ix+radius-1;ii++) {
                            qual_cen |= pqual_img[jj_nx+ii];         //UMR
                        }
                    }

                    pima[is*naxis1+1]=flux_cen;
                    perr[is*naxis1+1]=errs_cen;
                    pqua[is*naxis1+1]=qual_cen;
                    pmsk[is*naxis1+1]=1;
                }
                /* lower slice */
                s_low=-s+s_low_off+w_low_coeff1*wave+w_low_coeff2*wave*wave;
                s_low-=(s_low_d0+s_low_d1*wave+s_low_d2*wave*wave)*s_step;
                if(wavesol!=NULL) {
                    check( x= xsh_wavesol_eval_polx( wavesol,wave, ord,s_low));
                    check( y= xsh_wavesol_eval_poly( wavesol,wave, ord,s_low));
                } else {

                    check(xsh_model_get_xy(&model_config,instrument,wave,ord,s_low,
                                    &x,&y));
                }

                check(flux_low=cpl_image_get_interpolated(data_img,x,y,
                                profile,radius,
                                profile,radius,
                                &confidence));
               
                if (confidence <= 0 ) {
                     pima[is*naxis1+2]=0;
                     perr[is*naxis1+2]=1;
                     pqua[is*naxis1+2]=QFLAG_MISSING_DATA;
                     pmsk[is*naxis1+2]=1;
                     /* If flux interpolation ok */
                } else {

                    check(errs_low=cpl_image_get_interpolated(errs_img,x,y,
                                    profile,radius,
                                    profile,radius,
                                    &confidence));
                    /*
                    if(var_low>0 ) {
                    errs_low=sqrt(confidence > 0 && var_low);
                    } else {
                        errs_low=0;
                    }
		    */
                    
                    /* Now propagate flags */
                    int ix=(int)(x+0.5);
                    int iy=(int)(x+0.5);
                    int jj_nx=0;
                    int jj_min= ( iy - radius >= 0 ) ? iy - radius: 0;
                    int jj_max= ( iy + radius < ny ) ? iy + radius: 0;
                    //int ii_min= ( ix - radius >= 0 ) ? ix - radius: 0;
                    //int ii_max= ( ix + radius < nx ) ? ix + radius: 0;
                    for(int jj=jj_min;jj<jj_max;jj++) {
                        jj_nx=jj*nx;
                        for(int ii=ix-radius;ii<ix+radius-1;ii++) {
                            qual_low |= pqual_img[jj_nx+ii];         //UMR
                        }
                    }

                    pima[is*naxis1+2]=flux_low;
                    perr[is*naxis1+2]=errs_low;
                    pqua[is*naxis1+2]=qual_low;
                    pmsk[is*naxis1+2]=1;
                }

                is++;
            } /* end loop on s */
            //wave_max_old=wave_max;
            wave_min_old=wave_min;

            check(cpl_imagelist_set(data_cube,cpl_image_duplicate(data_tmp),ik));
            check(cpl_imagelist_set(errs_cube,cpl_image_duplicate(errs_tmp),ik));
            check(cpl_imagelist_set(qual_cube,cpl_image_duplicate(qual_tmp),ik));

            /*
            check(xsh_iml_merge_avg(&data_cube_merge,&mask_data_merge,
                            data_tmp,mask_tmp,mk));
            check(xsh_iml_merge_avg(&errs_cube_merge,&mask_errs_merge,
                            errs_tmp,mask_tmp,mk));
            check(xsh_iml_merge_avg(&qual_cube_merge,&mask_qual_merge,
                            qual_tmp,mask_tmp,mk));
            */
            check(xsh_iml_merge_wgt(&data_cube_merge, &errs_cube_merge, &qual_cube_merge,
                              data_tmp, errs_tmp, qual_tmp, mk,
                              instrument->decode_bp));
            ik++;
            if(ord==ord_max) {
                mk++;
            } else {
                mk=(int)((wave-cube_wave_min)/wave_step+0.5);
            }
        } /* end loop on wave */

        sprintf(tag,"%s_ORDER3D_DATA_%s_%s",rec_prefix,qualifier,
                xsh_arm_tostring(arm));
        sprintf(name,"%s.fits",tag);
        if(frame_is_object==0) {
            xsh_add_temporary_file(name);
        }
        xsh_cube_set_wcs(data_plist,wave_min,
                         0.6,s_step,wave_step,
                         1,naxis2/2.,0);

        xsh_cube_set_wcs(errs_plist,wave_min,
                         0.6,s_step,wave_step,
                         1,naxis2/2.,0);

        xsh_cube_set_wcs(qual_plist,wave_min,
                         0.6,s_step,wave_step,
                         1,naxis2/2.,0);

        sprintf(data_extid,"ORD%2.2d_FLUX",ord);
        xsh_pfits_set_extname (data_plist, data_extid);
        sprintf(errs_extid,"ORD%2.2d_ERRS",ord);
        xsh_pfits_set_extname (errs_plist, errs_extid);
        sprintf(qual_extid,"ORD%2.2d_QUAL",ord);
        xsh_pfits_set_extname (qual_plist, qual_extid);

        xsh_plist_set_extra_keys(data_plist,"IMAGE","DATA","RMSE",
                                 data_extid,errs_extid,qual_extid,0);

        xsh_plist_set_extra_keys(errs_plist,"IMAGE","ERROR","RMSE",
                                 data_extid,errs_extid,qual_extid,1);

        xsh_plist_set_extra_keys(qual_plist,"IMAGE","QUALITY","FLAG32BIT",
                                 data_extid,errs_extid,qual_extid,2);



        if(ord==ord_max) {
            xsh_pfits_set_pcatg(data_plist,tag);
            frame_data_cube=xsh_frame_product(name,tag,CPL_FRAME_TYPE_IMAGE,CPL_FRAME_GROUP_PRODUCT,CPL_FRAME_LEVEL_FINAL);
            cpl_imagelist_save(data_cube,name,XSH_SPECTRUM_DATA_BPP,data_plist,CPL_IO_DEFAULT);
            cpl_imagelist_save(errs_cube,name,XSH_SPECTRUM_ERRS_BPP,errs_plist,CPL_IO_EXTEND);
            cpl_imagelist_save(qual_cube,name,XSH_SPECTRUM_QUAL_BPP,qual_plist,CPL_IO_EXTEND);
        } else {
            cpl_imagelist_save(data_cube,name,XSH_SPECTRUM_DATA_BPP,data_plist,CPL_IO_EXTEND);
            cpl_imagelist_save(errs_cube,name,XSH_SPECTRUM_ERRS_BPP,errs_plist,CPL_IO_EXTEND);
            cpl_imagelist_save(qual_cube,name,XSH_SPECTRUM_QUAL_BPP,qual_plist,CPL_IO_EXTEND);
        }


        sprintf(name,"QC%s_%2.2d.fits",tag,ord);

        xsh_free_image(&data_tmp);
        xsh_free_image(&errs_tmp);
        xsh_free_image(&qual_tmp);
        xsh_free_image(&mask_tmp);

    } /* end loop on ord */

    sprintf(tag,"%s_MERGE3D_DATA_%s_%s",rec_prefix,qualifier,
            xsh_arm_tostring(arm));
    sprintf(name,"%s.fits",tag);
    xsh_cube_set_wcs(data_plist,cube_wave_min,
                     0.6,s_step,wave_step,
                     1,naxis2/2.,0);

    xsh_cube_set_wcs(errs_plist,cube_wave_min,
                     0.6,s_step,wave_step,
                     1,naxis2/2.,0);

    xsh_cube_set_wcs(qual_plist,cube_wave_min,
                     0.6,s_step,wave_step,
                     1,naxis2/2.,0);

    sprintf(data_extid,"FLUX");
    xsh_pfits_set_extname (data_plist, data_extid);
    sprintf(errs_extid,"ERRS");
    xsh_pfits_set_extname (errs_plist, errs_extid);
    sprintf(qual_extid,"QUAL");
    xsh_pfits_set_extname (qual_plist, qual_extid);

    xsh_plist_set_extra_keys(data_plist,"IMAGE","DATA","RMSE",
                             data_extid,errs_extid,qual_extid,0);
    xsh_plist_set_extra_keys(errs_plist,"IMAGE","ERROR","RMSE",
                             data_extid,errs_extid,qual_extid,1);
    xsh_plist_set_extra_keys(qual_plist,"IMAGE","QUALITY","FLAG32BIT",
                             data_extid,errs_extid,qual_extid,2);
    xsh_pfits_set_pcatg(data_plist,tag);

    if(cut_uvb_spectrum) {
       check(xsh_imagelist_cut_dichroic_uvb(data_cube_merge,errs_cube_merge,
                       qual_cube_merge,data_plist));
     }
   
    if( model_config_frame != NULL) {

     xsh_monitor_spectrum3D_flux(data_cube_merge,errs_cube_merge,
                                qual_cube_merge,instrument,data_plist);
    }
    
    cpl_imagelist_save(data_cube_merge,name,XSH_SPECTRUM_DATA_BPP,data_plist,CPL_IO_DEFAULT);

    cpl_imagelist_save(errs_cube_merge,name,XSH_SPECTRUM_ERRS_BPP,errs_plist,CPL_IO_EXTEND);

    cpl_imagelist_save(qual_cube_merge,name,XSH_SPECTRUM_QUAL_BPP,qual_plist,CPL_IO_EXTEND);

    xsh_msg_debug("merge cube size=%" CPL_SIZE_FORMAT "",cpl_imagelist_get_size(data_cube_merge));

    frame_merged_cube=xsh_frame_product(name,tag,CPL_FRAME_TYPE_IMAGE,
                    CPL_FRAME_GROUP_PRODUCT,CPL_FRAME_LEVEL_FINAL);


    check(xsh_add_product_imagelist(frame_merged_cube,frameset,parameters,
                    recipe_id,instrument,NULL));


    if(frame_is_object) {

        sprintf(tag,"%s_MERGE3D_%s",rec_prefix,qualifier);

        check(qc_trace_merged_frame=xsh_cube_qc_trace_window(frame_merged_cube,
                        instrument,
                        "MERGE3D",rec_prefix,
                        save_size+1,
                        naxis2-save_size,
                        peack_search_hsize,
                        method,1));

        trace_corr_tab=xsh_crea_correct_coeff(qc_trace_merged_frame,instrument);

        check( xsh_add_product_table( qc_trace_merged_frame, frameset,
                        parameters, recipe_id, instrument,
                        NULL));

        sprintf(tag,"%s_ORDER3D_DATA_%s",rec_prefix,qualifier);

        check(xsh_add_product_imagelist(frame_data_cube,frameset,parameters,
                        recipe_id,instrument,tag));
        /*
    check(xsh_add_product_imagelist(frame_errs_cube,frameset,parameters,
				    recipe_id,instrument,NULL));

    check(xsh_add_product_imagelist(frame_qual_cube,frameset,parameters,
				    recipe_id,instrument,NULL));
         */
    }

    if(trace_corr_tab) {
        xsh_add_product_table( trace_corr_tab, frameset,
                        parameters, recipe_id, instrument,NULL);
    }

    cleanup:

    xsh_free_propertylist(&data_plist);
    xsh_free_propertylist(&errs_plist);
    xsh_free_propertylist(&qual_plist);
    xsh_free_vector(&profile);

    xsh_free_image(&data_tmp);
    xsh_free_image(&errs_tmp);
    xsh_free_image(&qual_tmp);
    xsh_free_image(&mask_tmp);

    xsh_free_image(&data_img);
    xsh_free_image(&errs_img);
    xsh_free_image(&qual_img);

    xsh_free_image(&var_img);
    xsh_free_vector(&profile2);
    xsh_free_table(&sp_fmt_tab);

    xsh_free_imagelist(&data_cube);
    xsh_free_imagelist(&errs_cube);
    xsh_free_imagelist(&qual_cube);


    xsh_free_imagelist(&data_cube_merge);
    xsh_free_imagelist(&errs_cube_merge);
    xsh_free_imagelist(&qual_cube_merge);
    xsh_free_imagelist(&mask_data_merge);
    xsh_free_imagelist(&mask_errs_merge);
    xsh_free_imagelist(&mask_qual_merge);

    xsh_rec_list_free(&rec_list);
    xsh_free_frame(&qc_trace_frame);
    xsh_free_frame(&qc_trace_merged_frame);
    xsh_free_frame(&trace_corr_tab);
    xsh_free_frame(&frame_data_cube);
    xsh_free_frame(&frame_errs_cube);
    xsh_free_frame(&frame_qual_cube);
    xsh_free_frame(&frame_merged_cube);
    /*
     xsh_free_frame(&spectral_format_frame);
     xsh_free_frame(&model_config_frame);
     */

    return cpl_error_get_code();

}


/*---------------------------------------------------------------------------*/
/**
   @brief Do a wavelet decomposition using atrous from IDL
   @param[in] spec spectrum
   @param[in] nscales number os scales
   @return matrix of wavelet decomposition
 */
cpl_matrix * xsh_atrous( cpl_vector *spec, int nscales){

    cpl_matrix *decomp = NULL;
    cpl_vector *filter = NULL;
    double orig_filter[3] = {3./8., 1./4., 1./16.};
    double *new_filter = NULL;
    int filter_size = 3;

    int spec_size;
    cpl_vector *sp = NULL;
    cpl_vector *smooth = NULL;
    int i,k;
    cpl_vector *tmp_filter = NULL;

    /* Check parameters */
    XSH_ASSURE_NOT_NULL( spec);
    XSH_ASSURE_NOT_ILLEGAL( nscales >= 1);

    /* Init */
    check( spec_size = cpl_vector_get_size( spec));
    check( decomp = cpl_matrix_new( nscales+1, spec_size));
    sp = cpl_vector_duplicate( spec);
    smooth = cpl_vector_duplicate( spec);
    check( filter = cpl_vector_wrap( filter_size, orig_filter));


    for(k=0; k< nscales; k++){
        xsh_msg_dbg_medium("scale %d use filter with size %d", k, filter_size);
        /* smooth by convolution filter */
        check( cpl_wlcalib_xc_convolve( smooth, filter));

        /* fill result matrix */
        for(i=0; i<spec_size; i++){
            double sp_val, smooth_val;

            check( sp_val = cpl_vector_get( sp, i));
            check( smooth_val = cpl_vector_get( smooth, i));
            check( cpl_matrix_set( decomp, nscales-k, i, sp_val-smooth_val));
        }

        /* sp = smooth */
        xsh_free_vector( &sp);
        check( sp = cpl_vector_duplicate(smooth));

        /* generate new filter */
        check( tmp_filter = cpl_vector_duplicate( filter));
        XSH_FREE( new_filter);
        filter_size = cpl_vector_get_size( tmp_filter);
        XSH_CALLOC( new_filter, double , filter_size*2-1);

        for(i=0; i<filter_size; i++){
            new_filter[2*i] = cpl_vector_get( tmp_filter, i);
        }
        xsh_free_vector( &tmp_filter);
        xsh_unwrap_vector( &filter);
        check( filter = cpl_vector_wrap( filter_size*2-1, new_filter));
        filter_size = filter_size*2-1;
    }


    /* decomp[0,*] = smooth */
    for(i=0; i<spec_size; i++){
        double val;

        check( val = cpl_vector_get( sp, i));
        check( cpl_matrix_set( decomp, 0, i, val));
    }

    cleanup:
    if ( cpl_error_get_code() != CPL_ERROR_NONE){
        xsh_free_matrix( &decomp);
    }
    xsh_unwrap_vector( &filter);
    xsh_free_vector( &sp);
    xsh_free_vector( &smooth);
    XSH_FREE( new_filter);
    return decomp;
}
/*---------------------------------------------------------------------------*/


/*---------------------------------------------------------------------------*/
/**
   @brief Do a wavelet decomposition using atrous from IDL
   @param[in] spec spectrum
   @param[in] nscales number os scales
   @return matrix of wavelet decomposition
 */
cpl_frameset * xsh_shift_offsettab( cpl_frameset *shiftifu_frameset,
                                    double offset_low, double offset_up)
{
    cpl_frameset *result = NULL;
    cpl_frame *lo_frame = NULL;
    const char *lo_name = NULL;
    cpl_table *lo_table = NULL;
    double *lo_data = NULL;
    cpl_frame *up_frame = NULL;
    const char *up_name = NULL;
    cpl_table *up_table = NULL;
    double *up_data = NULL;
    int i, size;
    cpl_propertylist *lo_header = NULL;
    cpl_propertylist *up_header = NULL;
    cpl_frame *prod_frame = NULL;

    lo_frame = cpl_frameset_get_frame(shiftifu_frameset,0);
    lo_name = cpl_frame_get_filename(lo_frame);
    xsh_msg("Name %s", lo_name);
    XSH_TABLE_LOAD( lo_table, lo_name);
    check( lo_data = cpl_table_get_data_double( lo_table,
                    XSH_SHIFTIFU_COLNAME_SHIFTSLIT));

    up_frame = cpl_frameset_get_frame(shiftifu_frameset,2);
    up_name = cpl_frame_get_filename(up_frame);
    xsh_msg("Name %s", up_name);
    XSH_TABLE_LOAD( up_table, up_name);
    check( up_data = cpl_table_get_data_double( up_table,
                    XSH_SHIFTIFU_COLNAME_SHIFTSLIT));

    size = cpl_table_get_nrow( lo_table);

    for( i=0; i<size; i++){
        lo_data[i] += offset_low;
        up_data[i] += offset_up;
    }

    lo_header = cpl_propertylist_load( lo_name,0);
    check( cpl_table_save( lo_table, lo_header, NULL, "tmp_OFFSET_TAB_LOW.fits", CPL_IO_DEFAULT));
    up_header = cpl_propertylist_load( up_name,0);
    check( cpl_table_save( up_table, up_header, NULL, "tmp_OFFSET_TAB_UP.fits", CPL_IO_DEFAULT));

    result = cpl_frameset_new();
    check(prod_frame = xsh_frame_product( "tmp_OFFSET_TAB_LOW.fits",
                    "OFFSET_TAB",
                    CPL_FRAME_TYPE_TABLE,
                    CPL_FRAME_GROUP_PRODUCT,
                    CPL_FRAME_LEVEL_TEMPORARY));
    cpl_frameset_insert( result, prod_frame);

    check(prod_frame = cpl_frame_duplicate( cpl_frameset_get_frame(shiftifu_frameset,1)));
    cpl_frameset_insert( result, prod_frame);

    check(prod_frame = xsh_frame_product( "tmp_OFFSET_TAB_UP.fits",
                    "OFFSET_TAB",
                    CPL_FRAME_TYPE_TABLE,
                    CPL_FRAME_GROUP_PRODUCT,
                    CPL_FRAME_LEVEL_TEMPORARY));
    cpl_frameset_insert( result, prod_frame);

    cleanup:
    if ( cpl_error_get_code() != CPL_ERROR_NONE){
        xsh_free_frameset( &result);
    }
    XSH_TABLE_FREE( lo_table);
    XSH_TABLE_FREE( up_table);
    xsh_free_propertylist( &lo_header);
    xsh_free_propertylist( &up_header);
    return result;
}
/*---------------------------------------------------------------------------*/
/**@}*/
