/*                                                                           *
 *   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: 2013-09-30 12:14:02 $
 * $Revision: 1.213 $
 * $Name: not supported by cvs2svn $
 */

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

/*---------------------------------------------------------------------------*/
/**
 * @defgroup xsh_data_pre     Pre Format
 * @ingroup data_handling
 */
/*---------------------------------------------------------------------------*/

/**@{*/


/*-----------------------------------------------------------------------------
                                 Includes
 ----------------------------------------------------------------------------*/
#include <string.h>
#include <stdio.h>
#include <xsh_data_pre.h>
#include <xsh_irplib_mkmaster.h>
#include <xsh_dump.h>
#include <xsh_utils.h>
#include <xsh_error.h>
#include <xsh_msg.h>
#include <xsh_pfits.h>
#include <xsh_dfs.h>
#include <math.h>
#include <time.h>
#include <cpl.h>

/*----------------------------------------------------------------------------
                                 Function implementation
 ----------------------------------------------------------------------------*/
static cpl_error_code xsh_preoverscan_corr(cpl_frame* raw, const int corr_mode,
    double* cor_val) {

  const char* fname = NULL;
  cpl_image* raw_ima = NULL;
  cpl_image* tmp_ima = NULL;

  cpl_propertylist* header = NULL;
  int ovscx = 0;
  //int ovscy = 0;
  int prscx = 0;
  //int prscy = 0;
  int naxis1 = 0;
  int naxis2 = 0;
  double tmp_val = 0;
  cpl_vector* cor_val_vect=NULL;
  cpl_imagelist* iml=NULL;
  cpl_image* ima_ext=NULL;

  //double* praw=NULL;
  float* ptmp = NULL;
  int i = 0;
  int j = 0;
  char cor_fname[256];

  fname = cpl_frame_get_filename(raw);
  sprintf(cor_fname, "PREOVER_COR_%s", xsh_get_basename(fname));
  header = cpl_propertylist_load(fname, 0);

  raw_ima = cpl_image_load(fname, XSH_PRE_DATA_TYPE, 0, 0);
  prscx = xsh_pfits_get_prscx(header);
  ovscx = xsh_pfits_get_ovscx(header);

  naxis1 = xsh_pfits_get_naxis1(header);
  naxis2 = xsh_pfits_get_naxis2(header);
  tmp_ima = cpl_image_new(naxis1, naxis2, XSH_PRE_DATA_TYPE);
  ptmp = cpl_image_get_data_float(tmp_ima);

  switch (corr_mode) {


  case 0: {
    *cor_val = 0;
    break;
  }
  case 1: {
    /* constant using only overscan */

    ima_ext = cpl_image_extract(raw_ima, naxis1 - ovscx, 1, naxis1, naxis2);
    iml = cpl_imagelist_new();
    cpl_imagelist_set(iml, ima_ext,0);
    cor_val_vect = xsh_irplib_imagelist_get_clean_mean_levels(iml, 3, 5, 0.001);
    xsh_free_imagelist(&iml);

    *cor_val = cpl_vector_get(cor_val_vect, 0);
    xsh_free_vector(&cor_val_vect);

    break;
  }
  case 2: {
    /* constant using only prescan */

    ima_ext = cpl_image_extract(raw_ima, 1, 1, prscx, naxis2);
    iml = cpl_imagelist_new();
    cpl_imagelist_set(iml, ima_ext,0);
    cor_val_vect = xsh_irplib_imagelist_get_clean_mean_levels(iml, 3, 5, 0.001);
    xsh_free_imagelist(&iml);

    *cor_val = cpl_vector_get(cor_val_vect, 0);
    xsh_free_vector(&cor_val_vect);

    break;
  }
  case 3: {
    /* constant using both prescan and overscan */

    /* prescan contribute */
    ima_ext = cpl_image_extract(raw_ima, 1, 1, prscx, naxis2);
    iml = cpl_imagelist_new();
    cpl_imagelist_set(iml, ima_ext,0);
    cor_val_vect = xsh_irplib_imagelist_get_clean_mean_levels(iml, 3, 5, 0.001);
    xsh_free_imagelist(&iml);

    *cor_val = cpl_vector_get(cor_val_vect, 0);
    xsh_free_vector(&cor_val_vect);

    /* overscan contribute */
    ima_ext = cpl_image_extract(raw_ima, naxis1 - ovscx, 1, naxis1, naxis2);
    iml = cpl_imagelist_new();
    cpl_imagelist_set(iml, ima_ext,0);
    cor_val_vect = xsh_irplib_imagelist_get_clean_mean_levels(iml, 3, 5, 0.001);
    xsh_free_imagelist(&iml);

    *cor_val += cpl_vector_get(cor_val_vect, 0);
    xsh_free_vector(&cor_val_vect);

    /* mean of two contributes */
    *cor_val *= 0.5;

    break;
  }
  case 4: {
    /* image row by row using only overscan */
    /*
     xsh_msg("ok1 naxis1=%d ovscx=%d",naxis1,ovscx);
     xsh_msg("raw_ima sx=%d sy=%d",
     cpl_image_get_size_x(raw_ima),
     cpl_image_get_size_y(raw_ima));
     */
    for (j = 0; j < naxis2; j++) {
      tmp_val = cpl_image_get_median_window(raw_ima, naxis1 - ovscx, j + 1,
          naxis1, j + 1);
      for (i = 0; i < naxis1; i++) {
        ptmp[j * naxis1 + i] = tmp_val;
      }
    }
    break;
  }
  case 5: {
    /* image row by row using only prescan */
    for (j = 0; j < naxis2; j++) {
      tmp_val = cpl_image_get_median_window(raw_ima, 1, j + 1,prscx, j + 1);
      for (i = 0; i < naxis1; i++) {
        ptmp[j * naxis1 + i] = tmp_val;
      }
    }
    break;
  }
  case 6: {
    /* image row by row using both prescan and overscan */

    for (j = 0; j < naxis2; j++) {
       /* prescan contribute */
      tmp_val = cpl_image_get_median_window(raw_ima, 1, j + 1,prscx, j + 1);
      /* overscan contribute */
      tmp_val += cpl_image_get_median_window(raw_ima, naxis1 - ovscx, j + 1, naxis1, j + 1);

      /* mean of two contributes */
      tmp_val *= 0.5;
      for (i = 0; i < naxis1; i++) {
        ptmp[j * naxis1 + i] = tmp_val;
      }
    }
    break;
  }
  default:
    xsh_msg_error("Not supported");
    break;
  }

  if (corr_mode < 4) {
    cpl_image_subtract_scalar(raw_ima, *cor_val);
  } else {
    *cor_val = cpl_image_get_median_window(tmp_ima, 1, 1, naxis1, naxis2);
    cpl_image_subtract(raw_ima, tmp_ima);
  }

  xsh_msg("Apply pre-overscan correction %g", *cor_val);

  if (corr_mode > 0 && corr_mode < 4) {
    if (fabs(*cor_val) < 1.e-6) {
      xsh_msg_error(
          "The overscan correction is < 10^-6. Probably you have bad input");
      cpl_error_set(cpl_func, CPL_ERROR_ILLEGAL_INPUT);
      goto cleanup;
    }
  }

  xsh_pfits_set_bunit(header, XSH_BUNIT_FLUX_REL_C);
  check(cpl_image_save( raw_ima, cor_fname, CPL_BPP_IEEE_FLOAT, header,
      CPL_IO_DEFAULT));
  cpl_frame_set_filename(raw, cor_fname);
  //check(cpl_image_save(raw_ima,fname,CPL_BPP_IEEE_FLOAT,header,CPL_IO_DEFAULT));
  xsh_add_temporary_file(cor_fname);
  cleanup:

  xsh_free_vector(&cor_val_vect);
  xsh_free_imagelist(&iml);
  xsh_free_image(&raw_ima);
  xsh_free_image(&tmp_ima);
  xsh_free_propertylist(&header);

  return cpl_error_get_code();
}


double xsh_compute_ron_nir(const double dit)
{
   double ron=0;
   const int nsamples=10;
   int i=0;
   double dit_min=0;
   double dit_max=0;
   double ron_min=0;
   double ron_max=0;
   double slope=0;
   int i_min=0;
  

   static double dit_samples[10]={2,4,8,16,32,64,128,256,400,1024};

   //static double ron_samples[10]={25.5,27.2,27.5,25.1,20.0,14.8,12.5,8.8,8.5,8.0};
   static double ron_samples[10]={21.3,18.4,14.8,11.7,9.0,7.3,6.3,6.2,7.0,9.0};

   for(i=0;i<nsamples-1;i++){
      if(dit_samples[i] < dit) i_min=i; 
   }

   dit_min=dit_samples[i_min];
   ron_min=ron_samples[i_min];

   dit_max=dit_samples[i_min+1];
   ron_max=ron_samples[i_min+1];
   slope=(ron_max-ron_min)/(dit_max-dit_min);

   ron=ron_min+slope*(dit-dit_min);

   return ron;

}
/*---------------------------------------------------------------------------*/
/**
  @brief 
    Fill the XSH_PRE structure from FITS header file
  @param[in] pre
    The pre structure to fill
  @param[in] header
    The header of fits image
  @param[in] instrument arm setting

  @doc set:
       pre: header
            naxis1
            naxis2
            exptime
            ron
            conad
            gain
            binx
            biny
            ovscx
            ovscy
            prscx
            prscy
            prszx
            prszy
            cutx
            cuty
            cutmx
            cutmy

     instrument: arm
                 binx
                 biny

*/
/*---------------------------------------------------------------------------*/
static void xsh_pre_init(xsh_pre* pre, cpl_propertylist* header,
			 xsh_instrument * instrument)
{
  //XSH_ARM arm;
  double dit;
  /* check  parameters input */
  XSH_ASSURE_NOT_NULL( pre);
  XSH_ASSURE_NOT_NULL( header);
  XSH_ASSURE_NOT_NULL( pre->instrument);

  /* set header to pre */
  pre->data_header = header;
  
  /* put arm */
  //arm = xsh_pfits_get_arm( pre->data_header);
  if ( cpl_error_get_code() != CPL_ERROR_NONE){
    xsh_msg_warning("No ARM keyword found put default");
    xsh_error_reset();
    check( xsh_pfits_set_arm(pre->data_header, instrument));
  }
  pre->decode_bp = instrument->decode_bp;

  /* must be read */ 
  check( pre->naxis1 = xsh_pfits_get_naxis1(pre->data_header));
  check( pre->naxis2 = xsh_pfits_get_naxis2(pre->data_header));
  if(cpl_propertylist_has(pre->data_header,"EXPTIME")) {
    check( pre->exptime = xsh_pfits_get_exptime(pre->data_header));
  }
  if( xsh_instrument_get_arm(pre->instrument) == XSH_ARM_NIR){
     check(dit= xsh_pfits_get_dit(pre->data_header)) ;
    /*Temporarily hard-coded as missing in NIR FITS header */
    /*pre->ron=10; this should come from a curve: 22 (DIT=2s)- 6 (DIT=100s) */
    pre->ron=xsh_compute_ron_nir(dit);
    //xsh_msg("pre->ron=%g",pre->ron);
    pre->conad=2.12;
    pre->gain=1./pre->conad;
  }else {
    check(pre->ron = xsh_pfits_get_ron(pre->data_header));
    check(pre->conad = xsh_pfits_get_conad(pre->data_header));
    check(pre->gain = xsh_pfits_get_det_gain(pre->data_header));
  }
  if( xsh_instrument_get_arm(pre->instrument) == XSH_ARM_NIR){
    //int chip_ny;

    /* NO binning in NIR */ 
    pre->binx = 1;
    pre->biny = 1;
 
    check(pre->pszx = xsh_pfits_get_det_pxspace(
						pre->data_header));
  
    /* convert from meter to microns */
    pre->pszx = pre->pszx * 1000000 ;
    pre->pszy = pre->pszx;
  
    //check( chip_ny = xsh_pfits_get_chip_ny (pre->data_header));
    pre->cutx = XSH_NIR_DEFAULT_CUT;
    //AModigliani
    //PLUTO
    //pre->cuty = chip_ny / 2; //why this? Cut out half detector

    // Paolo would like this:
    pre->cuty =  XSH_NIR_DEFAULT_CUT_Y;
    pre->cutmx = XSH_NIR_DEFAULT_CUT_X;
#if 0
    pre->cutmy = (chip_ny / 2) - XSH_NIR_DEFAULT_CUT_Y ; //why this? Cut out half detector
#else
    /* Dont cut half detector, it's already cut in the raw frame */
    pre->cutmy = XSH_NIR_DEFAULT_CUT_Y ;
#endif

  }
  else {
    pre->binx = xsh_pfits_get_binx( pre->data_header);
    pre->biny = xsh_pfits_get_biny( pre->data_header);
    if ( cpl_error_get_code() != CPL_ERROR_NONE){
      xsh_msg("WARNING : No binning keywords found. Force binning to 1x1");
      pre->binx = 1;
      pre->biny = 1;
      xsh_error_reset();
    }

    check(pre->pszx = xsh_pfits_get_pszx(pre->data_header));
    check(pre->pszy = xsh_pfits_get_pszy(pre->data_header));
    /* compute size of a image pixel */
    pre->pszx = pre->pszx * pre->binx;
    pre->pszy = pre->pszy * pre->biny;

    /* could be read */
    /*
      ATENTION ! ATTENTION !
      With real data from VLT, pre and over scan should NOT be divided
      by binning
    */
    pre->cutmx = xsh_pfits_get_ovscx(pre->data_header);
    pre->cutx = xsh_pfits_get_prscx(pre->data_header);
    pre->cutmy = xsh_pfits_get_ovscy(pre->data_header);
    pre->cuty = xsh_pfits_get_prscy(pre->data_header);
    xsh_error_reset();
  }
  /* Add binning into instrument->config for later use */
  instrument->binx = pre->binx ;
  instrument->biny = pre->biny ;

  cleanup:

    return;
}

/*---------------------------------------------------------------------------*/
/**
  @brief 
    Create a XSH_PRE from a raw frame
  @param[in] raw
    The raw frame
  @param[in] bpmap 
    The bad pixel map associated to the data or NULL if not
  @param[in] bias_data
    The bias data image or NULL if not
  @param[in] instr 
    The instrument to be attached
  @param[in] pre_overscan_corr 
   switch to use or not pre/overscan for bias estimation
  @return 
    A newly allocated XSH_PRE structure
  @retval
    NULL in case of error

  The data and errs units are initialized to zero.

*/
/*---------------------------------------------------------------------------*/
xsh_pre* xsh_pre_create(cpl_frame* raw, cpl_frame* bpmap, cpl_image* bias_data,
    xsh_instrument* instr, const int pre_overscan_corr,
    const bool flag_neg_and_thresh_pix) {
  xsh_pre* result = NULL;
  const char* framename = NULL;
  const char* frame_tag = NULL;
  int nextensions = 0;
  int mode_or=1;

  /*
  double median_le = 0.0, median_ri = 0.0, median_up = 0.0, median_do = 0.0;
  double stdev_le = 0.0, stdev_ri = 0.0, stdev_up = 0.0, stdev_do = 0.0;
  float* pdata = NULL;
  */

  cpl_image *image = NULL;
  float* errs = NULL;
  float* data = NULL;
  int i = 0;
  const char *bpfilename = NULL;
  cpl_propertylist* bplist = NULL, *data_header = NULL;
  int bpnaxis1 = 0, bpnaxis2 = 0;

  int nsat = 0;
  double frac_sat = 0;
  double cor_val = 0;

  /* check  parameters input */
  XSH_ASSURE_NOT_NULL(raw);
  XSH_ASSURE_NOT_NULL(instr);

  /* allocate memory */
  XSH_CALLOC(result, xsh_pre, 1);

  /* fill the structure */
  result->instrument = instr;
  result->decode_bp = instr->decode_bp;

  XSH_NEW_PROPERTYLIST(result->errs_header);
  check(xsh_pfits_set_extname(result->errs_header, "ERRS"));
  XSH_NEW_PROPERTYLIST(result->qual_header);
  check(xsh_pfits_set_extname(result->qual_header, "QUAL"));

  /* analyse  raw frame */
  check(framename = cpl_frame_get_filename(raw));
  check(result->group = cpl_frame_get_group(raw));
  check(frame_tag = cpl_frame_get_tag(raw));

  //XSH_ASSURE_NOT_MISMATCH(result->group == CPL_FRAME_GROUP_RAW);
  check(nextensions = cpl_frame_get_nextensions(raw));
  //xsh_msg("nextensions=%d",nextensions);

  XSH_ASSURE_NOT_ILLEGAL(nextensions == 0);

  /* load the property list */
  check_msg(data_header = cpl_propertylist_load(framename, 0),
      "Could not load header of file '%s'", framename);

  /* we start data manimpulation here */
  if (pre_overscan_corr > 0) {
    check(xsh_preoverscan_corr(raw, pre_overscan_corr, &cor_val));
    check(framename = cpl_frame_get_filename(raw));
  }

  check(xsh_pre_init(result, data_header, instr));
  /* special init for AFC frame */
  if (strstr(frame_tag, "AFC") != NULL) {
    check(xsh_pfits_set_dpr_tech(data_header, XSH_DPR_TECH_SINGLE_PINHOLE));
    if (xsh_instrument_get_arm(instr) != XSH_ARM_NIR) {
      result->cutmx = 0;
      result->cutmy = 0;
      result->cutx = 0;
      result->cuty = 0;
    }
  }

  /* compute image size */
  result->nx = result->naxis1 - result->cutmx - result->cutx;
  result->ny = result->naxis2 - result->cutmy - result->cuty;

  /* in NIR mode */
  int is_flat = 0;
  if(cpl_propertylist_has(result->data_header, XSH_DPR_TYPE)) {
      is_flat = xsh_pfits_is_flat(result->data_header);
  }

  if (xsh_instrument_get_arm(instr) == XSH_ARM_NIR) {

    XSH_ASSURE_NOT_ILLEGAL(result->nx > 0 && result->ny > 0);

    /* load image */
    check(image = cpl_image_load(framename, XSH_PRE_DATA_TYPE, 0, 0));

    /* compute data part */

    /* slice it in two equal part and apply a padding of XSH_NIR_DEFAULT_CUT
     on top,left and right borders */

    xsh_msg_dbg_medium("extracting region [%d,%d:%d,%d]", result->cutx + 1,
        result->cuty + 1, result->naxis1 - XSH_NIR_DEFAULT_CUT,
        result->naxis2 - result->cutmy - XSH_NIR_DEFAULT_CUT);

    check(
        result->data = cpl_image_extract(image, result->cutx + 1,
            result->cuty + 1, result->naxis1 - XSH_NIR_DEFAULT_CUT,
            result->naxis2 - result->cutmy - XSH_NIR_DEFAULT_CUT));
    /* rotate 90° clockwise the result */
    check(cpl_image_turn(result->data, 1));
    result->nx = cpl_image_get_size_x(result->data);
    result->ny = cpl_image_get_size_y(result->data);
    xsh_msg_dbg_low("***** NX,NY: %d,%d", result->nx, result->ny);
    /* compute ERRS part */
    check(result->errs = cpl_image_duplicate(result->data));
    check(errs = cpl_image_get_data_float(result->errs));
    check(data = cpl_image_get_data_float(result->data));
    double cdata = 0;

    double ron2 = result->ron * result->ron;
    int nx_ny = result->nx * result->ny;
    double ron_div_conad = result->ron / result->conad;
    double crpix1=1.;
    double crpix2=1.;
    double crval1=1.;
    double crval2=1.;
    double cdelt1=1.;
    double cdelt2=1.;
    for (i = 0; i < nx_ny; i++) {
      /* convert in e- to calculate and return in ADU */
      cdata = data[i];
      if (cdata > 0) {
        errs[i] = sqrt(cdata * result->conad + ron2) / result->conad;
      } else {
        errs[i] = ron_div_conad;
      }
    }

    /* to flag bad pixel we need the qual extension initialized */
       result->qual=cpl_image_new(result->nx,result->ny,XSH_PRE_QUAL_TYPE);
    /* Flag negative saturated and (NIR) extrapolated pixels */

    xsh_badpixelmap_flag_saturated_pixels(result, instr, cor_val,
        flag_neg_and_thresh_pix, is_flat, &nsat);
    frac_sat = (double) nsat / (nx_ny);

    xsh_pfits_set_wcs(result->data_header,crpix1,crval1,cdelt1,crpix2,crval2,cdelt2);
    xsh_pfits_set_wcs(result->errs_header,crpix1,crval1,cdelt1,crpix2,crval2,cdelt2);
    xsh_pfits_set_wcs(result->qual_header,crpix1,crval1,cdelt1,crpix2,crval2,cdelt2);
    check(xsh_pfits_set_nsat(result->data_header, nsat));
    check(xsh_pfits_set_frac_sat(result->data_header, frac_sat));

  }
  /* IN VIS and UVB mode */
  else {
    XSH_ASSURE_NOT_ILLEGAL(result->nx > 0 && result->ny > 0);
    /* load image */
    check(image = cpl_image_load(framename, XSH_PRE_DATA_TYPE, 0, 0));
    /* TODO: the following computations seems to record overscan/prescan levels
     * but are already done elsewhere and is not used later on.
     * Should we remove this? We have commented out it!

     //  computes mean and median for 4 windows
     if (result->cutx > 0) {
     check(median_le = cpl_image_get_median_window(image, 1, 1,
     result->cutx, result->naxis2));
     check(stdev_le = cpl_image_get_stdev_window(image, 1, 1,
     result->cutx, result->naxis2));
     }
     if (result->cuty > 0) {
     check(median_do = cpl_image_get_median_window(image, 1, 1,
     result->naxis1, result->cuty));
     check(stdev_do = cpl_image_get_stdev_window(image, 1, 1,
     result->naxis1, result->cuty));
     }
     if (result->cutmx > 0) {
     check(median_ri =cpl_image_get_median_window(image,
     result->naxis1 - result->cutmx + 1, 1, result->naxis1,
     result->naxis2));
     check(stdev_ri = cpl_image_get_stdev_window(image,
     result->naxis1 - result->cutmx + 1, 1, result->naxis1,
     result->naxis2));
     }
     if (result->cutmy > 0) {
     check(median_up =cpl_image_get_median_window(image, 1,
     result->naxis2 - result->cutmy + 1, result->naxis1, result->naxis2));
     check(stdev_ri = cpl_image_get_stdev_window(image, 1,
     result->naxis2 - result->cutmy + 1, result->naxis1, result->naxis2));
     }

     // update FITS header
     check(xsh_pfits_set_bias_left_median(result->data_header, median_le));
     check(xsh_pfits_set_bias_right_median(result->data_header, median_ri));
     check(xsh_pfits_set_bias_left_stdev(result->data_header, stdev_le));
     check(xsh_pfits_set_bias_right_stdev(result->data_header, stdev_ri));
     check(xsh_pfits_set_bias_up_median(result->data_header, median_up));
     check(xsh_pfits_set_bias_down_median(result->data_header, median_do));
     check(xsh_pfits_set_bias_up_stdev(result->data_header, stdev_up));
     check(xsh_pfits_set_bias_down_stdev(result->data_header, stdev_do));
     */

    /* compute data part */
    check(
        result->data = cpl_image_extract(image, result->cutx + 1,
            result->cuty + 1, result->naxis1 - result->cutmx,
            result->naxis2 - result->cutmy));

    if (xsh_instrument_get_arm(instr) == XSH_ARM_UVB) {
      /* rotate the image of 180 degrees */
      check(cpl_image_turn(result->data, 2));
    }

    /* to flag bad pixel we need the qual extension initialized */
    result->qual=cpl_image_new(result->nx,result->ny,XSH_PRE_QUAL_TYPE);

    /* flag saturated pixels for QC */
     check(xsh_badpixelmap_flag_saturated_pixels(result, instr, cor_val,
        flag_neg_and_thresh_pix, is_flat, &nsat));
    int nx_ny = result->nx * result->ny;
    frac_sat = (double) nsat / (nx_ny);

    check(xsh_pfits_set_nsat(result->data_header, nsat));
    check(xsh_pfits_set_frac_sat(result->data_header, frac_sat));
    if ( (xsh_instrument_get_arm(instr) == XSH_ARM_VIS) ) {
      /*
        && (strcmp(rec_id,"xsh_wavecal") == 0 ) ) {
      */
      check(xsh_badpixelmap_count_range_pixels(result, 4800,5200, cor_val,
					       &nsat,&frac_sat));
      check(xsh_pfits_set_n_range_pix(result->data_header, nsat));
      check(xsh_pfits_set_frac_range_pix(result->data_header, frac_sat));

    }
    double crpix1=1.;
    double crpix2=1.;
    double crval1=1.;
    double crval2=1.;
    double cdelt1=xsh_instrument_get_binx(instr);;
    double cdelt2=xsh_instrument_get_biny(instr);

    xsh_pfits_set_wcs(result->data_header,crpix1,crval1,cdelt1,crpix2,crval2,cdelt2);
    xsh_pfits_set_wcs(result->errs_header,crpix1,crval1,cdelt1,crpix2,crval2,cdelt2);
    xsh_pfits_set_wcs(result->qual_header,crpix1,crval1,cdelt1,crpix2,crval2,cdelt2);


    /* compute ERRS part */
    check(result->errs = cpl_image_duplicate(result->data));
    check(errs = cpl_image_get_data_float(result->errs));
    check(data = cpl_image_get_data_float(result->data));

    /* in case of BIAS frame */
    if ((strcmp(frame_tag, XSH_BIAS_UVB) == 0)
        || (strcmp(frame_tag, XSH_BIAS_VIS) == 0)
        || (strcmp(frame_tag, XSH_RAW_IMA_SLIT_UVB) == 0)
        || (strcmp(frame_tag, XSH_RAW_IMA_SLIT_VIS) == 0)) {

      /* RON (e-) / CONAD ==> ADU */
      float ron_div_conad = result->ron / result->conad;

      for (i = 0; i < nx_ny; i++) {
        errs[i] = ron_div_conad;
      }
    }

    /* in case of LINEARITY frame */
    else if ((strcmp(frame_tag, XSH_LINEARITY_UVB) == 0)
        || (strcmp(frame_tag, XSH_LINEARITY_VIS) == 0)
        || (strcmp(frame_tag, XSH_RAW_BP_MAP_NL_UVB) == 0)
        || (strcmp(frame_tag, XSH_RAW_BP_MAP_NL_VIS) == 0)
        || (strcmp(frame_tag, XSH_BP_MAP_NL_UVB) == 0)
        || (strcmp(frame_tag, XSH_BP_MAP_NL_VIS) == 0)) {

      for (i = 0; i < nx_ny; i++) {
        errs[i] = 0;
      }
    } else {
        /* usual input data case */
      int bias_x, bias_y;
      float* mbias_data = NULL;
      double ron2 = result->ron * result->ron;
      double cdata = 0;
      double ron_div_conad = result->ron / result->conad;

      int flag_err_comp = 0;
      if (bias_data != (cpl_image *)-1) {
        /* if a master bias is provided */

        xsh_msg("pre_overscan=%d",pre_overscan_corr);
        if (pre_overscan_corr == 0) {
          /* if no overscan correction */

          check(bias_x = cpl_image_get_size_x(bias_data));
          check(bias_y = cpl_image_get_size_y(bias_data));
          xsh_msg_dbg_medium(
              "bias_x=%d nx=%d bias_y=%d ny=%d", bias_x, result->nx, bias_y, result->ny);
          XSH_ASSURE_NOT_ILLEGAL_MSG(
              bias_x == result->nx,
              "Master bias X size different from raw frame X size. Input image frames must match in arm and bin!");
          XSH_ASSURE_NOT_ILLEGAL_MSG(
              bias_y == result->ny,
              "Master bias Y size different from raw frame Y size. Input image frames must match in arm and bin!");

          flag_err_comp = 1;
	        check( mbias_data = cpl_image_get_data_float( bias_data));
          for (i = 0; i < nx_ny; i++) {
            /* convert in e- to calculate and return in ADU */
            cdata = data[i] - mbias_data[i];
            if (cdata > 0) {
              errs[i] = sqrt(cdata * result->conad + ron2) / result->conad;
            } else {
              errs[i] = ron_div_conad;
            }
          }

        } /* end check on poverscan correction */
      } /* end check on mbias input provision */

      if (flag_err_comp == 0) {

        for (i = 0; i < nx_ny; i++) {
          /* convert in e- to calculate and return in ADU */
          cdata = data[i];
          if (cdata > 0) {
            errs[i] = sqrt(cdata * result->conad + ron2) / result->conad;
          } else {
            errs[i] = ron_div_conad;
          }
        }

      } /* end check on special case */

    } /* end case usual input data */

  } /* end check on arms */

  /* combine with reference bad pixels if provided */
  if (bpmap != NULL) {
    check(bpfilename = cpl_frame_get_filename(bpmap));
    xsh_msg("Use Bad pixel map file %s\n", bpfilename);
    check(bplist = cpl_propertylist_load(bpfilename, 0));
    /* read KW */
    check(bpnaxis1 = xsh_pfits_get_naxis1(bplist));
    check(bpnaxis2 = xsh_pfits_get_naxis2(bplist));
    assure(bpnaxis1 == result->nx && bpnaxis2 == result->ny,
        CPL_ERROR_ILLEGAL_INPUT,
        "qual image %d %d must have same size of data\
      image %d %d",
        bpnaxis1, bpnaxis2, result->nx, result->ny);
    cpl_image* ima_ref_bp = cpl_image_load(bpfilename, XSH_PRE_QUAL_TYPE, 0, 0);
    xsh_badpixelmap_image_coadd(&(result->qual), ima_ref_bp,mode_or);
    xsh_free_image(&ima_ref_bp);
  }
  /* Associate to data structure the flagged bad pixels after decoding using a 
   CPL standard bad pixel code in order to not use the in following
   operations like statistics etc.. */

  check(xsh_image_flag_bp(result->data, result->qual, instr));

  cleanup: if (cpl_error_get_code() != CPL_ERROR_NONE) {
    xsh_pre_free(&result);
    result = NULL;
  }
  xsh_free_image(&image);
  xsh_free_propertylist(&bplist);

  return result;
}

/*---------------------------------------------------------------------------*/
/**
  @brief
    Free a xsh_pre structure
  @param pre The xsh_pre structure to free
 */
/*---------------------------------------------------------------------------*/
void xsh_pre_free(xsh_pre** pre)
{
  if (pre && *pre) {
    xsh_free_image(&((*pre)->data));
    xsh_free_propertylist(&((*pre)->data_header));
    xsh_free_image(&((*pre)->errs));
    xsh_free_propertylist(&((*pre)->errs_header));
    xsh_free_image(&((*pre)->qual));
    xsh_free_propertylist(&((*pre)->qual_header));
    cpl_free(*pre);
    *pre = NULL;
  }
}

/*---------------------------------------------------------------------------*/
/**
  @brief
    Load a xsh_pre structure from a frame
  @param[in] frame 
    The frame pointing on the file to load
  @param instr 
    The instrument to be attached
  @return  
    A newly allocated PRE structure
 */
/*---------------------------------------------------------------------------*/
xsh_pre* xsh_pre_load (cpl_frame * frame, xsh_instrument* instr)
{
  /* MUST BE DEALLOCATED in caller */
  xsh_pre * result = NULL;
  /* Others */
  const char *filename = NULL;
  int extension = 0;
  cpl_propertylist* data_header = NULL;

  
  /* check  parameters input */
  XSH_ASSURE_NOT_NULL(frame);
  XSH_ASSURE_NOT_NULL(instr);
  XSH_ASSURE_NOT_NULL(cpl_frame_get_tag (frame));
  check (filename = cpl_frame_get_filename (frame));
  /* allocate memory */
  XSH_CALLOC(result, xsh_pre,1);
  /* fill structure */
  result->instrument = instr;  
  check(result->group = cpl_frame_get_group(frame));

  /* get nextensions */
  XSH_ASSURE_NOT_ILLEGAL(cpl_frame_get_nextensions(frame) == 2);

  /* Load data, validate file structure */
  extension = 0;
  check_msg (data_header = cpl_propertylist_load (filename,extension),
    "Cannot read the FITS header from '%s' extension %d",
    filename, extension);
  check(xsh_pre_init(result,data_header, instr));

  result->nx = result->naxis1;
  result->ny = result->naxis2;
  result->decode_bp = instr->decode_bp;

  /* load data */
  check_msg (result->data = cpl_image_load (filename,XSH_PRE_DATA_TYPE,0,0),
    "Error loading image from %s extension 0", filename);
 
  /* load errs */
  check_msg (result->errs_header = cpl_propertylist_load (filename,1),
    "Cannot read the FITS header from '%s' extension 1",filename); 
  assure(strcmp(xsh_pfits_get_extname(result->errs_header),"ERRS") == 0,
    CPL_ERROR_ILLEGAL_INPUT,"extension 1 must be a errs extension");
  check_msg (result->errs = cpl_image_load (filename, XSH_PRE_ERRS_TYPE,0,1),
    "Error loading image from %s extension 1", filename);
  /* load qual */
  check_msg (result->qual_header = cpl_propertylist_load (filename,2),
    "Cannot read the FITS header from '%s' extension 2",filename);
  assure(strcmp("QUAL",xsh_pfits_get_extname (result->qual_header)) == 0,
    CPL_ERROR_ILLEGAL_INPUT,"extension 2 must be a qual extension");
  check_msg (result->qual = cpl_image_load (filename, XSH_PRE_QUAL_TYPE,0,2),
    "Error loading image from %s extension 2", filename);


  //AMo: Flip UVB images
  //AModigliani: for the moment commented out, we have to decide which
  //is most appropriate
  /*
  if(xsh_instrument_get_arm(instr) == XSH_ARM_UVB) {
    //flip the image around the horizontal
    check(cpl_image_flip(result->data,2));
    check(cpl_image_flip(result->errs,2));
    check(cpl_image_flip(result->qual,2));
    //flip the image around the vertical
    check(cpl_image_flip(result->data,0));
    check(cpl_image_flip(result->errs,0));
    check(cpl_image_flip(result->qual,0));
  }

  
  //PIPPO
  */
  //AMo: Flip NIR images 
  /*
  check(cpl_image_shift(result->data,1,(int)cpl_image_get_size_y(result->data)/2.));
  if(xsh_instrument_get_arm(instr) == XSH_ARM_NIR) {
    //turn the image 90 degree clockwise
    check(cpl_image_turn(result->data,1));
    check(cpl_image_turn(result->errs,1));
    check(cpl_image_turn(result->qual,1));
    //flip around the vertical
    check(cpl_image_flip(result->data,0));
    check(cpl_image_flip(result->errs,0));
    check(cpl_image_flip(result->qual,0));
  }
  
  */

  cleanup:
    if (cpl_error_get_code () != CPL_ERROR_NONE) {
      xsh_error_msg("can't load frame %s",cpl_frame_get_filename(frame));
      xsh_pre_free(&result);
    }
    return result;
}

/*--------------------------------------------------------------------------*/
/**
   @brief    Copy a PRE structure
   @param    pre The PRE to copy
   @return   Pointer to newly PRE structure
*/
/*--------------------------------------------------------------------------*/
xsh_pre * xsh_pre_duplicate (const xsh_pre * pre)
{
  xsh_pre *result = NULL;

  assure(pre != NULL, CPL_ERROR_NULL_INPUT, "Null PRE");

  /* allocate memory */
  result = (xsh_pre*)(cpl_malloc (sizeof (xsh_pre)));
  assure (result != NULL, CPL_ERROR_ILLEGAL_OUTPUT,
    "Memory allocation failed!");

  /* init */
  result->data = NULL;
  result->data_header = NULL;
  result->errs = NULL;
  result->errs_header = NULL;
  result->qual = NULL;
  result->qual_header = NULL;
  result->instrument = pre->instrument;

  /* copy attributes */
  check(result->nx = xsh_pre_get_nx(pre));
  check(result->ny = xsh_pre_get_ny(pre));
  check(result->group = xsh_pre_get_group(pre));
  result->pszx = pre->pszx;
  result->pszy = pre->pszy;
  result->gain = pre->gain;
  result->exptime = pre->exptime;
  /* copy data */
  check_msg(result->data_header = cpl_propertylist_duplicate(
    pre->data_header),"can't copy data header");
  check_msg(result->data = cpl_image_duplicate(pre->data),
    "can't copy data image");

  /* copy errs */
  check_msg(result->errs_header = cpl_propertylist_duplicate(
    pre->errs_header),"can't copy errs header");
  check_msg(result->errs = cpl_image_duplicate(pre->errs),
    "can't copy errs image");

  /* copy qual */
  check_msg(result->qual_header = cpl_propertylist_duplicate(
    pre->qual_header),"can't copy qual header");

  check_msg(result->qual = cpl_image_duplicate(pre->qual),
    "can't copy qual image");
  result->decode_bp = pre->decode_bp;

cleanup:
  if (cpl_error_get_code () != CPL_ERROR_NONE) {
    xsh_pre_free(&result);
    result = NULL;
  }
  return result;
}

/*---------------------------------------------------------------------------*/
/**
   @brief    Save PRE on disk
   @param    pre to save
   @param    filename The file to save to
   @param    tag      The file pro catg 
   @param    temp Flag if 1 frame is temporary file
   @return   A frame pointing to the file that was saved
*/
/*----------------------------------------------------------------------------*/
cpl_frame* xsh_pre_save (const xsh_pre * pre, 
                         const char *filename, 
                         const char *tag, 
			 int temp )
{
  cpl_frame *product_frame = NULL;

  XSH_ASSURE_NOT_NULL(pre);
  XSH_ASSURE_NOT_NULL(filename);

  /* Save the file */
  xsh_pfits_set_bunit(pre->data_header,XSH_BUNIT_FLUX_REL_C);
  xsh_pfits_set_bunit(pre->errs_header,XSH_BUNIT_FLUX_REL_C);
  xsh_pfits_set_bunit(pre->qual_header,XSH_BUNIT_NONE_C);

  check( xsh_pfits_set_pcatg( pre->data_header,tag));
  check_msg (cpl_image_save (pre->data, filename, XSH_PRE_DATA_BPP,
    pre->data_header, CPL_IO_DEFAULT),
    "Could not save data to %s extension 0", filename);
  check_msg (cpl_image_save (pre->errs, filename, XSH_PRE_ERRS_BPP,
    pre->errs_header, CPL_IO_EXTEND),
    "Could not save errs to %s extension 1", filename);
  check_msg (cpl_image_save (pre->qual, filename, XSH_PRE_QUAL_BPP,
    pre->qual_header, CPL_IO_EXTEND),
    "Could not save qual to %s extension 2", filename);
  /* Create product frame */
  product_frame = cpl_frame_new ();
  XSH_ASSURE_NOT_NULL( product_frame);
  
  check ((cpl_frame_set_filename (product_frame, filename),
          cpl_frame_set_tag(product_frame,tag),
	  cpl_frame_set_type (product_frame, CPL_FRAME_TYPE_IMAGE),
	  cpl_frame_set_group (product_frame, xsh_pre_get_group(pre)) )) ;

 
  if ( temp != 0 ) {
    check( cpl_frame_set_level(product_frame,
			       CPL_FRAME_LEVEL_TEMPORARY));
    xsh_add_temporary_file( filename ) ;
  }
 
  
cleanup:
  if (cpl_error_get_code () != CPL_ERROR_NONE) {
    xsh_free_frame(&product_frame);
    product_frame = NULL;
  }
  return product_frame;
}

/*---------------------------------------------------------------------------*/
/**
   @brief Save PRE frame like a product on disk
   @param pre the pre image structure
   @param frame The frame describing the product
   @param frameset The initial frameset containing raw frames
   @param parameters The recipe parameters
   @param recipe_id The recipe id
   @param prefix Prefix used to build the filename

   @return A frame pointing to the file product
*/
/*----------------------------------------------------------------------------*/

cpl_frame* 
xsh_pre_save_product(xsh_pre *pre, 
                     cpl_frame* frame,
                     cpl_frameset* frameset,
                     const cpl_parameterlist* parameters,
                     const char* recipe_id, 
                     const char *prefix)
{
  cpl_frame* result = NULL;
  char filename[256];
  time_t now ;
  char* date = NULL;
  const char* tag=NULL;
  /* check input */
  XSH_ASSURE_NOT_NULL( pre);
  XSH_ASSURE_NOT_NULL( frame);
  XSH_ASSURE_NOT_NULL( parameters);
  XSH_ASSURE_NOT_NULL( recipe_id);
  XSH_ASSURE_NOT_NULL( pre->instrument);

  /* update header */
  check( cpl_frame_set_group (frame, CPL_FRAME_GROUP_PRODUCT));
  check (cpl_dfs_setup_product_header (pre->data_header,
				       frame, frameset, parameters, 
				       recipe_id, pre->instrument->pipeline_id,
				       pre->instrument->dictionary,NULL));

  /* save product */
  if ( prefix == NULL ) {
    sprintf(filename,"PRODUCT_%s",cpl_frame_get_filename (frame));
  }
  else if ( xsh_time_stamp_get() ) {
    time( &now ) ;
    date = xsh_sdate_utc(&now);
    sprintf( filename, "%s_%dx%d_%s.fits", prefix, pre->binx, pre->biny, 
	     date);
  }
  else
    sprintf( filename, "%s_%dx%d.fits", prefix, pre->binx, pre->biny );
  check(tag=xsh_pfits_get_pcatg(pre->data_header));
  check(result = xsh_pre_save(pre, filename, tag,0));
  /* update frame */
  cpl_frame_set_type (result, CPL_FRAME_TYPE_IMAGE),
  cpl_frame_set_group (result, CPL_FRAME_GROUP_PRODUCT),
  cpl_frame_set_level (result, CPL_FRAME_LEVEL_FINAL);
cleanup:
  if (cpl_error_get_code () != CPL_ERROR_NONE) {
    xsh_free_frame(&result);
    result = NULL;
  }
  XSH_FREE( date ) ;

  return result;
}

/*---------------------------------------------------------------------------*/
/**
  @brief 
    Create new PRE image
  @param nx
    The x-size
  @param ny
    The y-size
  @return
    The newly allocated PRE image

  The data and errs units are initialized to zero.  
*/
/*---------------------------------------------------------------------------*/
xsh_pre* xsh_pre_new (int nx, int ny)
{
  xsh_pre *result = NULL;

  assure (nx > 0 && ny > 0, CPL_ERROR_ILLEGAL_INPUT,
          "Illegal image size: %dx%d", nx, ny);

  XSH_CALLOC( result, xsh_pre, 1);

  result->nx = nx;
  result->ny = ny;

  check( result->data = cpl_image_new (nx, ny, XSH_PRE_DATA_TYPE));
  check( result->errs = cpl_image_new (nx, ny, XSH_PRE_ERRS_TYPE));
  check( result->qual = cpl_image_new (nx, ny, XSH_PRE_QUAL_TYPE));
  check( result->data_header = cpl_propertylist_new());
  check( result->errs_header = cpl_propertylist_new());
  check( result->qual_header = cpl_propertylist_new());

  cleanup:
    if (cpl_error_get_code () != CPL_ERROR_NONE) {
      xsh_pre_free ( &result);
      result = NULL;
    }
    return result;
}

/*---------------------------------------------------------------------------*/
/**
  @brief Get nx of pre structure
  @param pre the pre structure 
  @return the nx value
 */
/*---------------------------------------------------------------------------*/
int xsh_pre_get_nx (const xsh_pre * pre)
{
  int result = 0;

  XSH_ASSURE_NOT_NULL( pre );
  result = pre->nx;

  cleanup:
    return result;
}

/*---------------------------------------------------------------------------*/
/**
  @brief Get ny of pre structure
  @param pre the pre structure
  @return the ny value
 */
/*---------------------------------------------------------------------------*/
int xsh_pre_get_ny (const xsh_pre * pre)
{
  int result = 0;

  XSH_ASSURE_NOT_NULL( pre );
  result = pre->ny;

  cleanup:
    return result;
}


/*---------------------------------------------------------------------------*/
/**
  @brief Get binx of pre structure
  @param pre the pre structure 
  @return the binx value
 */
/*---------------------------------------------------------------------------*/
int xsh_pre_get_binx (const xsh_pre * pre)
{
  int result = 0;

  XSH_ASSURE_NOT_NULL( pre );
  result = pre->binx;

  cleanup:
    return result;
}

/*---------------------------------------------------------------------------*/
/**
  @brief Get biny of pre structure
  @param pre the pre structure
  @return the ny value
 */
/*---------------------------------------------------------------------------*/
int xsh_pre_get_biny (const xsh_pre * pre)
{
  int result = 0;

  XSH_ASSURE_NOT_NULL( pre );
  result = pre->biny;

  cleanup:
    return result;
}

/*---------------------------------------------------------------------------*/
/**
  @brief Get pszx of pre structure
  @param pre the pre structure
  @return the pszx value
 */
/*---------------------------------------------------------------------------*/
float xsh_pre_get_pszx (const xsh_pre * pre)
{
  float result = 0.0;
  assure (pre != NULL, CPL_ERROR_NULL_INPUT, "Null PRE");
  result = pre->pszx;

  cleanup:
    return result;
}

/*---------------------------------------------------------------------------*/
/**
  @brief Get x, y coordinates in image from RAW coordinates
  @param pre the pre structure
  @param raw_x Raw X pixel
  @param raw_y Raw Y pixel
  @param x Result X value
  @param y Result Y value
 */
/*---------------------------------------------------------------------------*/

void xsh_pre_from_raw_get(xsh_pre* pre, double raw_x, double raw_y, 
  double* x, double* y)
{
  /* check parameters input */
  XSH_ASSURE_NOT_NULL(pre);
  XSH_ASSURE_NOT_NULL(x);
  XSH_ASSURE_NOT_NULL(y);

  /* 0.5 for complience with the data model origin pixel 
     like ds9 
  */

  /* pixel in detector center at 0.5 ex : 0 => [0 1] */
  if( xsh_instrument_get_arm(pre->instrument) == XSH_ARM_NIR){
    /**x = raw_y - pre->cuty; */
    *x = raw_y ;
    *y = pre->ny + pre->cutx - raw_x;
  }
  else{
    *x = raw_x - pre->cutx;
    *y = raw_y - pre->cuty;
  }
 
 cleanup:
   return;
}

/*---------------------------------------------------------------------------*/
/**
  @brief Get pszy of pre structure
  @param pre the pre structure
  @return the pszy value
 */
/*---------------------------------------------------------------------------*/
float xsh_pre_get_pszy (const xsh_pre * pre)
{
  float result = 0.0;

  XSH_ASSURE_NOT_NULL(pre);
  result = pre->pszy;

  cleanup:
    return result;
}

/*---------------------------------------------------------------------------*/
/**
  @brief Get gain of pre structure
  @param pre the pre structure
  @return the gain value
 */
/*---------------------------------------------------------------------------*/
float xsh_pre_get_gain (const xsh_pre * pre)
{
  float result = 0.0;

  XSH_ASSURE_NOT_NULL(pre);
  result = pre->gain;

  cleanup:
    return result;
}
/*---------------------------------------------------------------------------*/
/**
  @brief Get ny of pre structure
  @param pre the pre structure
  @return the ny value
 */
/*---------------------------------------------------------------------------*/
cpl_frame_group xsh_pre_get_group (const xsh_pre * pre)
{
  cpl_frame_group result = CPL_FRAME_GROUP_NONE;

  XSH_ASSURE_NOT_NULL(pre);
  result = pre->group;

  cleanup:
    return result;
}

/**
  @brief Get bpmap of pre structure
  @param pre the pre structure
  @return the cpl_mask represented the bpmap
 */
/*---------------------------------------------------------------------------*/
cpl_mask* xsh_pre_get_bpmap(const xsh_pre * pre) {
  int* qual = NULL;
  cpl_mask* mask = NULL;
  cpl_binary* binary = NULL;
  int nx_ny = 0;
  int i = 0;

  XSH_ASSURE_NOT_NULL(pre);

  check(mask = cpl_image_get_bpm(pre->qual));
  check(qual = cpl_image_get_data_int(pre->qual));
  check(binary = cpl_mask_get_data(mask));
  nx_ny = pre->nx * pre->ny;
  for (i = 0; i < nx_ny; i++) {
    if ( (qual[i] & pre->decode_bp) > 0 ) {
      /* bad pix */
      binary[i] = CPL_BINARY_1;
    }
  }

  cleanup: return mask;
}

/*---------------------------------------------------------------------------*/
/**
  @brief  Add two PRE images
  @param  self The first operand modified by the add operation
  @param  right The second image to add

  The input images must have same size.

  The data units are added:
     data = data1 + data2

  The error images are propagated using
     sigma^2 = sigma1^2 + sigma2^2

  The quality maps are or'ed
     qual  = qual1 U qual2

  fixme: Verify that this is a sensible way of propagating the
  bad pixel maps.
 */
/*---------------------------------------------------------------------------*/

void xsh_pre_add (xsh_pre* self, const xsh_pre * right)
{
  float* errs1 = NULL;
  float* errs2 = NULL;
  int i = 0;
  //cpl_mask* mask = NULL;
  //cpl_binary* binary =NULL;
  //int* qual = NULL;

  XSH_ASSURE_NOT_NULL(self);
  XSH_ASSURE_NOT_NULL(right);

  /* Check that sizes match */
  assure (xsh_pre_get_nx (self) == xsh_pre_get_nx (right) &&
          xsh_pre_get_ny (self) == xsh_pre_get_ny (right),
          CPL_ERROR_INCOMPATIBLE_INPUT,
          "Image sizes don't match: %dx%d and %dx%d",
          xsh_pre_get_nx (self), xsh_pre_get_ny (self),
          xsh_pre_get_nx (right), xsh_pre_get_ny (right));

  /* Handle data unit + plist */
  check (cpl_image_add(self->data, right->data));

  /* Handle errs unit + plist */
  check(errs1 = cpl_image_get_data_float(self->errs));
  check(errs2 = cpl_image_get_data_float(right->errs));

  for(i=0;i< self->nx*self->ny; i++){
    errs1[i] = sqrt(pow(errs1[i],2)+pow(errs2[i],2));
  }
    
  /* QUAL computation: combine the two QUAL codes */

  /* IF there is a CALIB
  if( (xsh_pre_get_group(right) == CPL_FRAME_GROUP_CALIB) ){ 
    // PUT QFLAG CALIB DEFECT
    check( mask = xsh_pre_get_bpmap(right));
    check(binary = cpl_mask_get_data(mask));
    check(qual = cpl_image_get_data_int(self->qual));
    for(i=0;i<self->nx*self->ny;i++){
      if (binary[i]){
        qual[i] |= QFLAG_CALIB_FILE_DEFECT;
      }
    } 
  }
  else {
    // Need a function to "OR" 2 images
    xsh_badpixelmap_or( self, right ) ;
  }
  */
  xsh_badpixelmap_or( self, right ) ;
cleanup:
  return;
}


/** 
  @brief 
    Subtract 2 frames (in XSH_PRE format) 
    Just loads the 2 frames, subtract (xsh_pre_subtract) and save resulting
    frame.
  @param set 
    Frameset to be corrected
  @param sub
    pre frame to be subtracted
  @param spec 
    specifier for Name of the saved subtracted file
  @param instr 
    Pointer to the instrument description structure
  
  @return The result frame (one - two)
*/
cpl_frameset* xsh_pre_frameset_subtract_frame( cpl_frameset *set,
                                            cpl_frame *sub,
                                               const char* spec,
                                            xsh_instrument *instr )
{
   int i=0;
   int size=0;

   cpl_frame* frm=NULL;
   cpl_frame* cor=NULL;
   cpl_frameset* result=NULL;
   char filename[256];
   const char * name=NULL;
   size=cpl_frameset_get_size(set);
   result=cpl_frameset_new();
   for(i=0;i<size;i++) {
      frm=cpl_frameset_get_frame(set,i);
      name=cpl_frame_get_filename(frm);
      sprintf(filename,"SUB_%s_%d_%s",spec,i,name);
      cor=xsh_pre_frame_subtract(frm,sub,filename,instr,1);
      cpl_frameset_insert(result,cor);
   }

   return result;

}


/** 
  @brief 
    Subtract 2 frames (in XSH_PRE format) 
    Just loads the 2 frames, subtract (xsh_pre_subtract) and save resulting
    frame.
  @param one 
    First pre frame
  @param two 
    Second pre frame
  @param filename 
    Name of the saved subtracted file
  @param instr 
    Pointer to the instrument description structure
  
  @return The result frame (one - two)
*/
cpl_frame* 
xsh_pre_frame_subtract( cpl_frame *one,
                        cpl_frame *two,
                        const char *filename,
                        xsh_instrument *instr,
                        const int clean_tmp)
{
  xsh_pre * pre1 = NULL ;
  xsh_pre * pre2 = NULL ;
  xsh_pre * pre_result = NULL ;
  cpl_frame * result = NULL ;
  const char* tag=NULL;

  /* Check input */
  XSH_ASSURE_NOT_NULL ( one);
  XSH_ASSURE_NOT_NULL ( two);
  XSH_ASSURE_NOT_NULL ( filename);
  XSH_ASSURE_NOT_NULL ( instr);

  /* Load the 2 pre frames */
  check ( pre1 = xsh_pre_load( one, instr));
  check ( pre2 = xsh_pre_load( two, instr));

  /* subtract two from one */
  check( pre_result = xsh_pre_duplicate( pre1));
  check ( xsh_pre_subtract( pre_result, pre2));

  check(tag=cpl_frame_get_tag(one));
  /* Save the pre result */
  check( result = xsh_pre_save( pre_result, filename,tag,0));
  
  /* Set the result frame tags, etc */
  check ((cpl_frame_set_filename (result, filename),
    cpl_frame_set_type (result, CPL_FRAME_TYPE_IMAGE),
    cpl_frame_set_group (result, xsh_pre_get_group(pre_result)),
    cpl_frame_set_level (result, CPL_FRAME_LEVEL_TEMPORARY),
    cpl_frame_set_tag( result,tag)));
  if(clean_tmp) {
     xsh_add_temporary_file( filename ) ;
  }
  cleanup:
    xsh_pre_free( &pre1);
    xsh_pre_free( &pre2);
    xsh_pre_free( &pre_result);
    return result;
}

/*----------------------------------------------------------------------------*/
/**
  @brief   
    Subtract one PRE image from another
    The data units are subtracted
      data = data1 - data2
    The error images are propagated using
      sigma^2 = sigma1^2 + sigma2^2
    The quality maps are or'e
      qual  = qual1 U qual2
  @param[in] self The first operand modified by the subtract operation
  @param[in] right The image to subtract
*/
/*----------------------------------------------------------------------------*/

void xsh_pre_subtract (xsh_pre* self, const xsh_pre * right)
{

  float* errs1 = NULL;
  float* errs2 = NULL;
  int i = 0;
  //cpl_mask* mask = NULL;
  //cpl_binary* binary =NULL;
  //int* qual = NULL;

  /* Check input */
  XSH_ASSURE_NOT_NULL( self);
  XSH_ASSURE_NOT_NULL( right);

  /* Check that sizes match */
  assure (xsh_pre_get_nx (self) == xsh_pre_get_nx (right) &&
          xsh_pre_get_ny (self) == xsh_pre_get_ny (right),
          CPL_ERROR_INCOMPATIBLE_INPUT,
          "Image sizes don't match: %dx%d and %dx%d",
          xsh_pre_get_nx (self), xsh_pre_get_ny (self),
          xsh_pre_get_nx (right), xsh_pre_get_ny (right));

  /* Handle data unit + plist */
  check ( cpl_image_subtract( self->data, right->data));

  /* Handle errs unit + plist */
  check( errs1 = cpl_image_get_data_float( self->errs));
  check( errs2 = cpl_image_get_data_float( right->errs));

  for(i=0;i< self->nx*self->ny; i++){
    errs1[i] = sqrt(pow(errs1[i],2)+pow(errs2[i],2));
  }

  /* Handle qual unit + plist
  if( (xsh_pre_get_group(right) == CPL_FRAME_GROUP_CALIB) ){
    check( mask = xsh_pre_get_bpmap(right));
    check(binary = cpl_mask_get_data(mask));
    check(qual = cpl_image_get_data_int(self->qual));
    for(i=0;i<self->nx*self->ny;i++){
      if ( (binary[i] & self->decode_bp) > 0 ){
        // this is a bad pixel
        qual[i] |= QFLAG_CALIB_FILE_DEFECT;
      }
    }
  }
  else {
    // In case the inputs are RAW frames
    xsh_badpixelmap_or( self, right ) ;
  }
  */
  xsh_badpixelmap_or( self, right ) ;
  cleanup:
    return ;
}
/*----------------------------------------------------------------------------*/
/**
  @brief   Subsample a PRE image of a bin factor binx X biny
  @param   self  The first operand modified by the divide operation
  @param   binx bin factor along X
  @param   biny bin factor along Y
  @param   inst instrument setting
  @return  subsampled image

  The pixel intensities are summed over the integration bin size:
     data = sum_ij (data_i+data_j) for 0<i<binx; 0<j<biny;

  The error images are propagated with sum squares

     sigma = sqrt{ sum_ij[sigma_i^2 + sigma_j^2 ) ]}

  The quality maps are or'ed
     qual  = qual1 U qual2

 */
/*----------------------------------------------------------------------------*/
xsh_pre* xsh_pre_subsample (xsh_pre* self, const int  binx, const int biny, const int rescale,xsh_instrument* inst)
{

  xsh_pre* result=NULL;
  float *errsi = NULL;
  float *errso = NULL;
  float *datai = NULL;
  float *datao = NULL;
  int *quali = NULL;
  int *qualo = NULL;
  int i = 0;
  int j = 0;
  int nx_ny=0;
  int nx=0;
  int ny=0;
  int m=0;
  int k=0;
  int sx=0;
  int sy=0;

  int pixeli=0;
  int pixelo=0;

  int j_biny=0;
  int j_nx=0;
  int nx_binx=0;
  float inv_binx_biny=1;
  float binx_biny=1;

  assure (self != NULL, CPL_ERROR_NULL_INPUT, "Null image!");
  assure (binx >0, CPL_ERROR_ILLEGAL_INPUT, "binx <=0!");
  assure (biny >0, CPL_ERROR_ILLEGAL_INPUT, "biny <=0!");

  /* Handle data and errs unit */
  check(datai = cpl_image_get_data_float(self->data));
  check(errsi = cpl_image_get_data_float(self->errs));
  check(quali = cpl_image_get_data_int(self->qual));

  sx=self->nx;
  sy=self->ny;
  nx=sx/binx;
  ny=sy/biny;


  result=xsh_pre_new(nx,ny);
  check(datao = cpl_image_get_data_float(result->data));
  check(errso = cpl_image_get_data_float(result->errs));
  check(qualo = cpl_image_get_data_int(result->qual));

  nx_binx=sx;
  int cpix=0;
  int cpix_k=0;

  //Loop over output image
   for (j = 0; j < ny; j++) {
    j_biny = j*biny;
    j_nx = j*nx;
     for (i = 0; i < nx; i++) {
       pixeli = j_biny*nx_binx + (i*binx);
       pixelo = j_nx + i;

       //Loop over pixels to be binned into one pixel in the input image
       for (m = 0; m < biny; m++) {
         cpix = pixeli +m*nx_binx;
         for (k = 0; k < binx;k++) {

            cpix_k = cpix + k;
            datao[pixelo] += datai[cpix_k];
            errso[pixelo] += errsi[cpix_k] * errsi[cpix_k];
            qualo[pixelo] |= quali[cpix_k];

         }
       }
       errso[pixelo] = sqrt(errso[pixelo]);
    }
  }

  if (rescale > 0) {
    nx_ny=nx*ny;
    inv_binx_biny=1./(binx*biny);
    /* HERE ONE SHOULD RE-SCALE */
    for (i = 0; i < nx_ny; i++) {
      datao[i] *= inv_binx_biny;
      errso[i] *= inv_binx_biny;
    }
  }

  if (rescale < 0) {
    nx_ny=nx*ny;
    binx_biny=(binx*biny);
    /* HERE ONE SHOULD RE-SCALE */
    for (i = 0; i < nx_ny; i++) {
      datao[i] *= binx_biny;
      errso[i] *= binx_biny;
    }
  }
  cleanup:
    return result;
}

/*----------------------------------------------------------------------------*/
/**
  @brief   Divide a PRE image from another
  @param   self  The first operand modified by the divide operation
  @param   right The image to divide with
  @param   threshold minimal value to divide

  The input images must have same size.

  The data units are divided:
     data = data1 / data2

  The error images are propagated using
 
     sigma = abs(1/ data2) * sqrt(sigma1^2 + data1^2 * sigma2^2 / data2^2)
 
  The quality maps are or'ed
     qual  = qual1 U qual2

 */
/*----------------------------------------------------------------------------*/
void xsh_pre_divide (xsh_pre* self, const xsh_pre * right, const int is_flat, double threshold)
{

  float *errs1 = NULL;
  float *errs2 = NULL;
  float *data1 = NULL;
  float *data2 = NULL;
  int *qual1 = NULL;
  int *qual2 = NULL;
  int i = 0;
  int nx_ny=0;
  assure (self != NULL, CPL_ERROR_NULL_INPUT, "Null image!");
  assure (right != NULL, CPL_ERROR_NULL_INPUT, "Null image!");

  /* Check that sizes match */
  assure (xsh_pre_get_nx (self) == xsh_pre_get_nx (right) &&
          xsh_pre_get_ny (self) == xsh_pre_get_ny (right),
          CPL_ERROR_INCOMPATIBLE_INPUT,
          "Image sizes don't match: %dx%d and %dx%d",
          xsh_pre_get_nx (self), xsh_pre_get_ny (self),
          xsh_pre_get_nx (right), xsh_pre_get_ny (right));

  /* Handle data and errs unit */

  check(data1 = cpl_image_get_data_float(self->data));
  check(data2 = cpl_image_get_data_float(right->data));
  check(errs1 = cpl_image_get_data_float(self->errs));
  check(errs2 = cpl_image_get_data_float(right->errs));
  check(qual1 = cpl_image_get_data_int(self->qual));
  check(qual2 = cpl_image_get_data_int(right->qual));
  nx_ny=self->nx*self->ny;

  for(i=0;i< nx_ny; i++){

    if ( (qual2[i] & self->decode_bp) > 0 ) {
      // pixel quality
      /*
      if( (xsh_pre_get_group(right) == CPL_FRAME_GROUP_CALIB) ){
        qual1[i] |= QFLAG_CALIB_FILE_DEFECT;
      }
      else {
        qual1[i] |= qual2[i];
      }
      */
      qual1[i] |= qual2[i];
      if ( (is_flat) &&
    	   (qual2[i] & (QFLAG_SATURATED_DATA > 0) ) &&
		   (qual1[i] & (QFLAG_SATURATED_DATA == 0) )
          ) {
    	  /* if division was by a saturated flat remove
    	   * saturated pixel quality
    	   */

    	  qual1[i] -= QFLAG_SATURATED_DATA;

      }
      if (qual1[i] > 0 ) {
    	  /* as QFLAG_SATURATED_DATA was removed the qual1[i] > 0 only
    	   * if the pixel was bad for other reasons. In this case flag this
    	   * with QFLAG_CALIB_FILE_DEFECT
    	   */
         qual1[i] |= QFLAG_CALIB_FILE_DEFECT;
      }
    }
    if ( fabs(data2[i]) < threshold){
      qual1[i] |= QFLAG_DIVISOR_ZERO;
      errs1[i] = 1;
      data1[i] = 0;
    }
    else {
      double d1 = 0.0, d2 = 0.0;
      double e1 = 0.0, e2 =0.0;
      double error = 0.0;

      d1 = data1[i];
      d2 = data2[i];
      e1 = errs1[i];
      e2 = errs2[i];
      error = fabs( 1.0/d2) * sqrt( (e1*e1) + (e2*e2*d1*d1)/(d2*d2) );
      errs1[i] = (float) error;
      data1[i] = (float)(d1/d2);
    }
  }

  cleanup:
    return;
}

/*----------------------------------------------------------------------------*/
/**
  @brief   Multiply a PRE image from another
  @param   self  The first operand modified by the multiply operation
  @param   right The image to multiply with
  @param   threshold minimal value to multiply

  The input images must have same size.

  The data units are divided:
     data = data1 * data2

  The error images are propagated using
        sigma = sqrt( data2^2 * sigma1^2 + data1^2 sigma2^2 )
   
  The quality maps are or'ed
     qual  = qual1 U qual2

 */
/*----------------------------------------------------------------------------*/
void xsh_pre_multiply (xsh_pre* self, const xsh_pre * right, double threshold)
{

  float *errs1 = NULL;
  float *errs2 = NULL;
  float *data1 = NULL;
  float *data2 = NULL;
  int *qual1 = NULL;
  int *qual2 = NULL;
  int i = 0;
  int nx_ny=0;
  assure (self != NULL, CPL_ERROR_NULL_INPUT, "Null image!");
  assure (right != NULL, CPL_ERROR_NULL_INPUT, "Null image!");

  /* Check that sizes match */
  assure (xsh_pre_get_nx (self) == xsh_pre_get_nx (right) &&
          xsh_pre_get_ny (self) == xsh_pre_get_ny (right),
          CPL_ERROR_INCOMPATIBLE_INPUT,
          "Image sizes don't match: %dx%d and %dx%d",
          xsh_pre_get_nx (self), xsh_pre_get_ny (self),
          xsh_pre_get_nx (right), xsh_pre_get_ny (right));

  /* Handle data and errs unit */

  check(data1 = cpl_image_get_data_float(self->data));
  check(data2 = cpl_image_get_data_float(right->data));
  check(errs1 = cpl_image_get_data_float(self->errs));
  check(errs2 = cpl_image_get_data_float(right->errs));
  check(qual1 = cpl_image_get_data_int(self->qual));
  check(qual2 = cpl_image_get_data_int(right->qual));
  nx_ny=self->nx*self->ny;
  for(i=0;i< nx_ny; i++){

    if ( (qual2[i] & self->decode_bp) > 0 ) {
      // pixel quality
      /*
      if( (xsh_pre_get_group(right) == CPL_FRAME_GROUP_CALIB) ){
        qual1[i] |= QFLAG_CALIB_FILE_DEFECT;
      }
      else {
        qual1[i] |= qual2[i];
      }
      */
      qual1[i] |= qual2[i];
    }
    else if ( fabs(data2[i]) > threshold){
      qual1[i] |= QFLAG_OUTSIDE_DATA_RANGE;
      errs1[i] = 1;
      data1[i] = 0;
    }
    else {
      double d1 = 0.0, d2 = 0.0;
      double e1 = 0.0, e2 =0.0;
      double error = 0.0;

      d1 = data1[i];
      d2 = data2[i];
      e1 = errs1[i];
      e2 = errs2[i];
      error = sqrt( d2*d2*e1*e1 + d1*d1*e2*e2 );
      errs1[i] = (float) error;
      data1[i] = (float)(d1*d2);
    }
  }

  cleanup:
    return;
}


/*----------------------------------------------------------------------------*/
/**
*  @brief  Normalize data flux
*  @param  self The PRE image to normalize
*/
/*----------------------------------------------------------------------------*/
void xsh_pre_normalize( xsh_pre* self)
{
  double val1, ct1;
  int i, rej, i1;

  XSH_ASSURE_NOT_NULL(self);
  i = 0;

  /* get the 1st index where the data is not a bad pixel and not 0 */
  do {
    i++;
    check(val1 = cpl_image_get(self->data, i, 1, &rej));
  }
  while (val1 == 0 || rej == 1);
  i1 = i;
  
  /* normalise image data by the mean of the whole frame */
  check(cpl_image_normalise( self->data, CPL_NORM_MEAN));
  
  /* obtain the scale factor */
  check(ct1 = val1/ cpl_image_get(self->data, i1, 1, &rej));

  xsh_msg("normalize estimate pixel (%d,1) : %f",i1,ct1);
  
  /* apply scale factor to errors */
  check( cpl_image_divide_scalar( self->errs, ct1));

  cleanup:
    return;
}

/** 
 * Calculate average, median, stdev taking into account the bad pixels (
 * from the bad pixel map frame->qual)
 * 
 * @param preFrame XSH_PRE strucure
 * @param mean Average value
 * @param median Median value
 * @param stdev Standard deviation (output)
 */
void xsh_pre_median_mean_stdev( xsh_pre * preFrame, double * mean,
				double * median, double * stdev )
{
  int i, ng, nx, ny ;
  float * ppix = NULL;
  int * pbad = NULL;
  cpl_vector * ptemp = NULL;
  int nx_ny=0;
  XSH_ASSURE_NOT_NULL( preFrame ) ;
  XSH_ASSURE_NOT_NULL( mean ) ;
  XSH_ASSURE_NOT_NULL( median ) ;
  XSH_ASSURE_NOT_NULL( stdev ) ;

  nx = xsh_pre_get_nx (preFrame);
  assure (nx != 0, cpl_error_get_code (), "Cant get X size");
  ny = xsh_pre_get_ny (preFrame);
  assure (ny != 0, cpl_error_get_code (), "Cant get Y size");

  check( ptemp = cpl_vector_new( nx*ny )) ;
  check( ppix = cpl_image_get_data_float( preFrame->data ) ) ;
  check( pbad = cpl_image_get_data_int( preFrame->qual ) ) ;
  nx_ny=nx*ny;
  for( ng = 0, i = 0 ; i < nx_ny ; i++ ) {
    if ( (pbad[i] & preFrame->decode_bp) == 0 ){
      /* good pixel */
      check( cpl_vector_set( ptemp, ng, (double)(ppix[i]) )) ;
      ng++ ;
    }
  }

  /* Now calculate median and Avg */
  check( cpl_vector_set_size( ptemp, ng ) );
  check( *mean = cpl_vector_get_mean( ptemp ) );
  check( *stdev = cpl_vector_get_stdev( ptemp ) );
  check( *median = cpl_vector_get_median( ptemp ) );

 cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE){
      *mean = 0.0;
      *stdev = 0.0;
      *median = 0.0;
    }
    xsh_free_vector( &ptemp ) ;
    return;
}

/** 
 * Calculate average, median, stdev taking into account the bad pixels (
 * from the bad pixel map frame->qual) in a given window
 * 
 * @param preFrame XSH_PRE strucure
 * @param mean Average value
 * @param median Median value
 * @param stdev standard deviation value
 * @param llx lower left x value
 * @param lly lower left x value
 * @param urx upper right x value
 * @param ury upper right y value
 */
void xsh_pre_median_mean_stdev_window( xsh_pre * preFrame, double * mean,
                                       double * median, double * stdev,
                                       const int llx, const int lly,
                                       const int urx, const int ury )
{
  int i=0, ng=0, nx=0, ny=0 ;
  int vx=0,vy=0;
  float * ppix = NULL;
  int * pbad = NULL;
  cpl_vector * ptemp = NULL;
  int j=0;

  XSH_ASSURE_NOT_NULL( preFrame ) ;
  XSH_ASSURE_NOT_NULL( mean ) ;
  XSH_ASSURE_NOT_NULL( median ) ;
  XSH_ASSURE_NOT_NULL( stdev ) ;

  nx = xsh_pre_get_nx (preFrame);
  assure (nx != 0, cpl_error_get_code (), "Cant get X size");
  ny = xsh_pre_get_ny (preFrame);
  assure (ny != 0, cpl_error_get_code (), "Cant get Y size");

  vx=urx-llx+1;
  vy=ury-lly+1;

  check( ptemp = cpl_vector_new( vx*vy )) ;
  check( ppix = cpl_image_get_data_float( preFrame->data ) ) ;
  check( pbad = cpl_image_get_data_int( preFrame->qual ) ) ;

  ng=0;
  //we need to decrese of 1 as vectors start from 0 while images from 1
  for( j = lly-1 ; j < ury ; j++ ) { 
     for( i = llx-1 ; i < urx ; i++ ) {
       //xsh_msg("pbad=%d",pbad[i]);
       if ( (pbad[i] & preFrame->decode_bp) == 0) {
         /* good pixel */
           check( cpl_vector_set( ptemp, ng, (double)(ppix[j*nx+i]) )) ;
           ng++ ;
       }
     }
  }

  cpl_table* tab=NULL;
  cpl_table* ext=NULL;
  int kappa=5;
  int niter1=2;
  int niter2=4;

  /* Now calculate median and Avg */
  if(ng>0) {
     tab=cpl_table_new(ng);
     cpl_table_wrap_double(tab,cpl_vector_get_data(ptemp),"VAL");

     /* first iteration comparing to median */
     *median=cpl_table_get_column_median(tab,"VAL");
     *mean=cpl_table_get_column_mean(tab,"VAL");
     *stdev=cpl_table_get_column_stdev(tab,"VAL");
     for(i=0;i<niter1;i++) {

        cpl_table_and_selected_double(tab,"VAL",CPL_LESS_THAN,
                                      *median+kappa*(*stdev));
        cpl_table_and_selected_double(tab,"VAL",CPL_GREATER_THAN,
                                      *median-kappa*(*stdev));
        xsh_free_table(&ext);    
        ext=cpl_table_extract_selected(tab);
        *median=cpl_table_get_column_median(ext,"VAL");
        *mean=cpl_table_get_column_mean(ext,"VAL");
        *stdev=cpl_table_get_column_stdev(ext,"VAL");
        //xsh_msg("mean=%g median=%g stdev=%g",*mean,*median,*stdev);
     }

     /* then compare to mean */
     for(i=0;i<niter2;i++) {
        cpl_table_and_selected_double(tab,"VAL",CPL_LESS_THAN,
                                      *mean+kappa*(*stdev));
        cpl_table_and_selected_double(tab,"VAL",CPL_GREATER_THAN,
                                      *mean-kappa*(*stdev));
        xsh_free_table(&ext);    
        ext=cpl_table_extract_selected(tab);
        *median=cpl_table_get_column_median(ext,"VAL");
        *mean=cpl_table_get_column_mean(ext,"VAL");
        *stdev=cpl_table_get_column_stdev(ext,"VAL");
        //xsh_msg("mean=%g median=%g stdev=%g",*mean,*median,*stdev);
     }
     xsh_free_table(&ext);    
     ext=cpl_table_extract_selected(tab);
     //check(cpl_table_save(ext,NULL,NULL,"pippo.fits",CPL_IO_DEFAULT));
     cpl_table_unwrap(tab,"VAL");
     *median=cpl_table_get_column_median(ext,"VAL");
     *mean=cpl_table_get_column_mean(ext,"VAL");
     *stdev=cpl_table_get_column_stdev(ext,"VAL");
  } else {
     *mean = -999.0;
     *stdev = -999.0;
     *median = -999.0;
  }


 cleanup:
    if (cpl_error_get_code() != CPL_ERROR_NONE){
      *mean = 0.0;
      *stdev = 0.0;
      *median = 0.0;
    }
    xsh_free_vector( &ptemp ) ;
    xsh_free_table(&tab);
    xsh_free_table(&ext);
    return;
}

/*---------------------------------------------------------------------------*/
/**
  @brief
    Search pixel position of best running median flux in the search window
  @param[in]  pre 
    The PRE image
  @param[in]  xcen
    The position in pixel along X axis defining the center of the search 
    window
  @param[in] ycen
    The position in pixel along Y axis defining the center of the search 
    window
  @param[in] search_window_hsize
    THe half size of search box
  @param[in] running_median_hsize
    The half size of running median box
  @param[out] xadj
    The finding pixel position along X
  @param[out] yadj
    The finding pixel position along Y
*/
/*---------------------------------------------------------------------------*/
int xsh_pre_window_best_median_flux_pos( xsh_pre* pre, int xcen, int ycen,
  int search_window_hsize, int running_median_hsize,  int* xadj, int* yadj)
{
  int search_win_xmin, search_win_xmax, search_win_ymin, search_win_ymax;
  //float* data = NULL;
  int* qual = NULL;  
  double* median_tab = NULL;
  int median_box_size = 0;
  double max_flux = -99999;
  int ibox, jbox;
  int xpos = -1, ypos = -1;
  int result= 0;
  int status=0;
  XSH_ASSURE_NOT_NULL( pre);
  XSH_ASSURE_NOT_NULL( xadj);
  XSH_ASSURE_NOT_NULL( yadj);
  XSH_CMP_INT( xcen, >=, 0, "Check central x position",);
  XSH_CMP_INT( xcen, <, pre->nx, "Check central x position",);
  XSH_CMP_INT( ycen, >=, 0, "Check central x position",);
  XSH_CMP_INT( ycen, <, pre->ny, "Check central x position",);

  median_box_size = 1+running_median_hsize*2;

  search_win_xmin = xcen-search_window_hsize;
  search_win_xmax = xcen+search_window_hsize;
  search_win_ymin = ycen-search_window_hsize;
  search_win_ymax = ycen+search_window_hsize;

  if ( search_win_xmin  < 0){
    search_win_xmin = 0;
  }
  if ( search_win_xmax >= pre->nx){
    search_win_xmax = pre->nx-1;
  }
  if ( search_win_ymin  < 0){
    search_win_ymin = 0;
  }
  if ( search_win_ymax >= pre->ny){
    search_win_ymax = pre->ny-1;
  }

  //check( data = cpl_image_get_data_float( pre->data));
  check( qual = cpl_image_get_data_int( pre->qual));
  XSH_CALLOC(median_tab, double,  median_box_size* median_box_size);

  int xc,yc;
  double med_flux = 0.0;
  /* We search for approx pixel position within a box */
  for (jbox=search_win_ymin; jbox <= (search_win_ymax+1-median_box_size); 
    jbox++){
    yc = jbox+running_median_hsize;
    int array_shift=yc*pre->nx;
    for (ibox=search_win_xmin; ibox <= (search_win_xmax+1-median_box_size); 
      ibox++){

      med_flux = xsh_pre_data_window_median_flux_pa( pre,
        ibox, jbox, median_box_size, median_box_size, median_tab,&status);

      if ( cpl_error_get_code() == CPL_ERROR_NONE){

        if (max_flux < med_flux) {
          xc = ibox+running_median_hsize;
          if ( (qual[xc+array_shift] & pre->decode_bp ) == 0) {

           /* good pixel */
           max_flux = med_flux;
           xpos = xc;
           ypos = yc;

         }

        }

      }
      else{
        xsh_error_reset();
      }
    }
  }
  if (xpos < 0 || ypos < 0){
    xsh_msg_dbg_high( "No valid pixels in the search box");
    result = 1;
  }
  else{
    *xadj = xpos;
    *yadj = ypos;
  }

  cleanup:
    XSH_FREE( median_tab);
    return result; 
}



/*----------------------------------------------------------------------------*/
/**
*  @brief  compute median sample value
*          on the data window with preallocated memory tab. Only good pixels
*          codes are used.
* 
*  @param  pre the PRE image
*  @param  x coordinate along X axis of window first pixel
*  @param  y coordinate along Y axis of window first pixel
*  @param  size_x size of the window along X axis
*  @param  size_y size of the window along Y axis
*  @param  tab preallocated memory of sizex * sizey
*  @return the sample value of the window data
*/
/*----------------------------------------------------------------------------*/
double xsh_pre_data_window_median_flux_pa(xsh_pre* pre, int x, int y,
					  int size_x, int size_y, double* tab,int* status)
{

  cpl_vector* v = NULL;
  float* data = NULL;
  int* qual = NULL;
  int ix = 0, iy = 0;
  int size = 0;
  *status=0;
  double flux = 0.0;
  /* check parameters */
  XSH_ASSURE_NOT_NULL(pre);
  XSH_ASSURE_NOT_ILLEGAL(x >=1 && x <= pre->nx);
  XSH_ASSURE_NOT_ILLEGAL(y >= 1 && y <= pre->ny);
  XSH_ASSURE_NOT_ILLEGAL(size_x > 0);
  XSH_ASSURE_NOT_ILLEGAL(size_y > 0);
  XSH_ASSURE_NOT_NULL(tab);

  x = x-1;
  y = y-1;

  if ( (x+size_x) >= pre->nx){
    size_x = pre->nx-x;
  }
  if ( (y+size_y) >= pre->ny){
    size_y = pre->ny-y;
  }

  /* get data */
  check(data  = cpl_image_get_data_float(pre->data));
  /* get qual */
  check(qual = cpl_image_get_data_int(pre->qual));

  /*Extract good pixels from box*/
  int offset=0;
  int x_plus_size_x = x + size_x;
  int y_plus_size_y = y + size_y;
  int ix_plus_offset = 0;
  for( iy=y; iy< y_plus_size_y; iy++){
     offset=iy*pre->nx;
     for( ix=x; ix<x_plus_size_x;ix++){
       ix_plus_offset = ix + offset;
       if ( (qual[ix_plus_offset] & pre->decode_bp) == 0 ) {
         /* good pix */
          tab[size] = data[ix_plus_offset];
          size++;
       }
     }
  }


  if (size==0) {
    /* If no good pixels, then return flux = 0.0 */
    *status=1;
  } else if (size==1) {
    /* If one good pixel, then return flux = pix value */
    flux = tab[0];
  } else if (size>1) {
    /* if found more than one good pixel, compute median */
    check(v = cpl_vector_wrap(size, tab));
    check( flux = cpl_vector_get_median(v));
  }
  cleanup:
    xsh_unwrap_vector(&v);
    return flux;
}



/*----------------------------------------------------------------------------*/
/**
   @brief    Get header
   @param    pre              The PRE image
   @return   Pointer to existing PRE image header.
*/
/*----------------------------------------------------------------------------*/
const cpl_propertylist *
xsh_pre_get_header_const (const xsh_pre * pre)
{
  cpl_propertylist *header = NULL;

  XSH_ASSURE_NOT_NULL(pre);
  header = pre->data_header;

  cleanup:
    return header;
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Get header
   @param    pre              The PRE image
   @return   Pointer to existing PRE image header.
*/
/*----------------------------------------------------------------------------*/
cpl_propertylist *
xsh_pre_get_header (xsh_pre * pre)
{
  XSH_ASSURE_NOT_NULL(pre);

  cleanup:
    return (cpl_propertylist *) xsh_pre_get_header_const (pre);
}


/*----------------------------------------------------------------------------*/
/**
   @brief    Get data
   @param    pre              The PRE image
   @return   Pointer to existing data image
*/
/*----------------------------------------------------------------------------*/
const cpl_image* xsh_pre_get_data_const (const xsh_pre * pre)
{
  cpl_image *data = NULL;

  XSH_ASSURE_NOT_NULL(pre);
  data = pre->data;

  cleanup:
    return data;
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Get data
   @param    pre              The PRE image
   @return   Pointer to existing data image
*/
/*----------------------------------------------------------------------------*/
cpl_image* xsh_pre_get_data (xsh_pre * pre)
{
    return (cpl_image *) xsh_pre_get_data_const (pre);
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Get errs
   @param    pre              The PRE image
   @return   Pointer to existing errs image
*/
/*----------------------------------------------------------------------------*/
const cpl_image * xsh_pre_get_errs_const (const xsh_pre * pre)
{
  cpl_image *errs = NULL;

  XSH_ASSURE_NOT_NULL(pre);
  errs = pre->errs;

  cleanup:
    return errs;
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Get errs
   @param    pre              The PRE image
   @return   Pointer to existing errs image
*/
/*----------------------------------------------------------------------------*/
cpl_image* xsh_pre_get_errs (xsh_pre * pre)
{
    return (cpl_image *) xsh_pre_get_errs_const (pre);
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Get QUAL
   @param    pre              The PRE image
   @return   Pointer to existing qual image
*/
/*----------------------------------------------------------------------------*/
const cpl_image* xsh_pre_get_qual_const (const xsh_pre * pre)
{
  cpl_image *qual = NULL;

  XSH_ASSURE_NOT_NULL(pre);
  qual = pre->qual;

  cleanup:
    return qual;
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Get qual
   @param    pre              The PRE image
   @return   Pointer to existing qual image
*/
cpl_image* xsh_pre_get_qual (xsh_pre * pre)
{
  return (cpl_image *) xsh_pre_get_qual_const (pre);
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Get data buffer
   @param    pre              The PRE image
   @return   Pointer to existing data buffer
*/
/*----------------------------------------------------------------------------*/
const double* xsh_pre_get_data_buffer_const (const xsh_pre * pre)
{
  double *buffer = NULL;

  XSH_ASSURE_NOT_NULL(pre);
  /* Before casting from (void *) to (double *), make sure
     the CPL image has the expected type
   */
  passure (cpl_image_get_type (pre->data) == CPL_TYPE_DOUBLE, " ");
  buffer = (double *) cpl_image_get_data (pre->data);

  cleanup:
    return buffer;
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Get data buffer
   @param    pre              The PRE image
   @return   Pointer to existing data buffer
*/
/*----------------------------------------------------------------------------*/
double* xsh_pre_get_data_buffer (xsh_pre * pre)
{
    return (double *) xsh_pre_get_data_buffer_const (pre);
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Get errs buffer
   @param    pre              The PRE image
   @return   Pointer to existing errs buffer
*/
/*----------------------------------------------------------------------------*/
const double* xsh_pre_get_errs_buffer_const (const xsh_pre * pre)
{
  double *buffer = NULL;

  XSH_ASSURE_NOT_NULL(pre);
  passure (cpl_image_get_type (pre->errs) == CPL_TYPE_DOUBLE, " ");
  buffer = (double *) cpl_image_get_data (pre->errs);

  cleanup:
    return buffer;
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Get errs buffer
   @param    pre              The PRE image
   @return   Pointer to existing errs buffer
*/
/*----------------------------------------------------------------------------*/
double* xsh_pre_get_errs_buffer (xsh_pre * pre)
{
  return (double *) xsh_pre_get_errs_buffer_const(pre);
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Dump image to stream
   @param    pre              The PRE image to dump
   @param    stream           The stream to dump to

   This function might be used for debugging purposes.
*/
/*----------------------------------------------------------------------------*/
void
xsh_pre_dump (const xsh_pre * pre, FILE * stream)
{
  cpl_stats *stats = NULL;

  if (pre == NULL) {
    fprintf (stream, "Null");
  }
  else {
    /* Dump data image stats */
    stats = cpl_stats_new_from_image (pre->data, CPL_STATS_ALL);

    cpl_stats_dump (stats, CPL_STATS_ALL, stream);

    //fixme: print whatever relevant information

    fflush (stream);
  }

  xsh_free_stats (&stats);
  return;
}


/*----------------------------------------------------------------------------*/
/**
 @brief multiply a frame in PRE format by a scalar
 @param pre frame in PRE format
 @param x scalar
 */
void xsh_pre_multiply_scalar (const xsh_pre * pre, double x)
{

  XSH_ASSURE_NOT_NULL( pre);

  /* Handle data unit + plist */
  check (cpl_image_multiply_scalar(pre->data, x));

  /* Handle errs unit + plist */
  check (cpl_image_multiply_scalar(pre->errs, fabs(x)));

  cleanup:
    return;
}

/**
 @brief divides a frame in PRE format by a scalar
 @param pre frame in PRE format
 @param x scalar
 */
void xsh_pre_divide_scalar (const xsh_pre * pre, double x)
{

  XSH_ASSURE_NOT_NULL( pre);
  XSH_ASSURE_NOT_ILLEGAL( x != 0);

  /* Handle data unit + plist */
  check (cpl_image_divide_scalar(pre->data, x));

  /* Handle errs unit + plist */
  check (cpl_image_divide_scalar(pre->errs, fabs(x)));

  cleanup:
    return;
}

/**
 @brief add a scalar to a frame in PRE format
 @param pre frame in PRE format
 @param x scalar
 */

void xsh_pre_add_scalar (const xsh_pre * pre, double x)
{

  XSH_ASSURE_NOT_NULL( pre);
  XSH_ASSURE_NOT_ILLEGAL( x != 0);

  /* Handle data unit + plist */
  check (cpl_image_add_scalar(pre->data, x));

  cleanup:
    return;
}


/**
 @brief subtract a scalar from a frame in PRE format
 @param pre frame in PRE format
 @param x scalar
 */
void xsh_pre_subtract_scalar (const xsh_pre * pre, double x)
{

  XSH_ASSURE_NOT_NULL( pre);
  XSH_ASSURE_NOT_ILLEGAL( x != 0);

  /* Handle data unit + plist */
  check (cpl_image_subtract_scalar(pre->data, x));

  cleanup:
    return;
}

/**
 @brief multiply a frame in PRE format by an image
 @param pre frame in PRE format
 @param img  image
 */

void xsh_pre_multiply_image(const xsh_pre *pre, cpl_image* img)
{
  cpl_image *abs = NULL;

  XSH_ASSURE_NOT_NULL( pre);
  XSH_ASSURE_NOT_NULL( img);

  /* Handle data unit + plist */
  check ( cpl_image_multiply(pre->data, img));
  check( abs = cpl_image_abs_create( img));
  check ( cpl_image_multiply(pre->errs, abs));

  cleanup:
    xsh_free_image( &abs);
    return;
}

/*----------------------------------------------------------------------------*/
/**
 @brief computes absolute value of a frame in PRE format
 @param pre frame in PRE format
 @return image = |pre|
 */

cpl_image* xsh_pre_abs (const xsh_pre * pre)
{

  cpl_image *result = NULL;
  int *result_data = NULL;
  float *data = NULL;
  int i, size;

  XSH_ASSURE_NOT_NULL( pre);

  /* Handle data unit + plist */
  check ( data = cpl_image_get_data_float( pre->data));
  check( result = cpl_image_new( pre->nx, pre->ny, CPL_TYPE_INT));
  check( result_data = cpl_image_get_data_int( result));

  size = pre->nx*pre->ny;

  for(i =0; i < size; i++){
    if ( data[i] < 0){
      result_data[i] = -1;
      data[i] = -data[i];
    }
    else{
      result_data[i] = 1;
    }
  }

  cleanup:
    return result;
}

/*---------------------------------------------------------------------------*/
/**
  @brief Average set of frames in PRE format 
  @param set the input frameset
  @param instr the instrument setting
  @param tag the output frame tag

  @return the frame product
 */
/*---------------------------------------------------------------------------*/

cpl_frame* 
xsh_frameset_average_pre(cpl_frameset *set, 
                         xsh_instrument* instr, 
                         const char* tag)
{
   cpl_frame* result=NULL;
   cpl_frame* frame=NULL;
   cpl_image* data=NULL;
   cpl_image* errs=NULL;
   cpl_image* qual=NULL;

   xsh_pre* pre=NULL;
   cpl_imagelist* iml_data=NULL;
   cpl_imagelist* iml_errs=NULL;
   cpl_imagelist* iml_qual=NULL;

   cpl_propertylist* plist=NULL;
   char name_o[256];
   const char* name=NULL;
   cpl_frame* frm_tmp=NULL;
   int i=0;
   int size=0;
   //int nx=0;
   //int ny=0;

   size=cpl_frameset_get_size(set);
   iml_data=cpl_imagelist_new();
   iml_errs=cpl_imagelist_new();
   iml_qual=cpl_imagelist_new();

   for(i=0;i<size;i++) {
      frame=cpl_frameset_get_frame(set,i);
      pre=xsh_pre_load(frame,instr);
      //nx=pre->nx;
      //ny=pre->ny;
      cpl_imagelist_set(iml_data,cpl_image_duplicate(pre->data),i);
      cpl_imagelist_set(iml_errs,cpl_image_duplicate(pre->errs),i);
      cpl_imagelist_set(iml_qual,cpl_image_duplicate(pre->qual),i);
      xsh_pre_free(&pre);
   }

   //pre=xsh_pre_new(nx,ny);
   pre=xsh_pre_load(cpl_frameset_get_frame(set,0),instr);
   xsh_free_image(&(pre->data));
   xsh_free_image(&(pre->errs));
   xsh_free_image(&(pre->qual));
   pre->data=cpl_imagelist_collapse_create(iml_data);
   pre->errs=cpl_imagelist_collapse_create(iml_errs);
   pre->qual=cpl_imagelist_collapse_create(iml_qual);

   cpl_image_divide_scalar(pre->data,size);
   cpl_image_divide_scalar(pre->errs,size);
   cpl_image_divide_scalar(pre->qual,size);

   frame=cpl_frameset_get_frame(set,0);
   name=cpl_frame_get_filename(frame);
   plist=cpl_propertylist_load(name,0);

   sprintf(name_o,"%s.fits",tag);
   check(frm_tmp=xsh_pre_save(pre,name_o,tag,0));

   result=xsh_frame_product(name_o,tag,CPL_FRAME_TYPE_IMAGE, 
                            CPL_FRAME_GROUP_PRODUCT,CPL_FRAME_LEVEL_FINAL);

  cleanup:
   xsh_free_frame(&frm_tmp);
   xsh_free_image(&data);
   xsh_free_image(&errs);
   xsh_free_image(&qual);
   
   xsh_free_imagelist(&iml_data);
   xsh_free_imagelist(&iml_errs);
   xsh_free_imagelist(&iml_qual);

   xsh_pre_free(&pre);
   xsh_free_propertylist(&plist);

   return result;
}

/** 
 * Rotate an image by an integer number of times 90 degrees (positive is
 * counterclockwise). Uses cpl_image_turn.
 * 
 * @param pre Image to turn (PRE format)
 * @param rot Number of 90 degrees to turn (counterclockwise)
 */
void xsh_pre_turn( xsh_pre * pre, int rot )
{
  cpl_image * img = NULL ;

  XSH_ASSURE_NOT_NULL( pre ) ;

  check( img = xsh_pre_get_data( pre ) ) ;
  check( cpl_image_turn( img, rot ) ) ;

  check( img = xsh_pre_get_errs( pre ) ) ;
  check( cpl_image_turn( img, rot ) ) ;

  check( img = xsh_pre_get_qual( pre ) ) ;
  check( cpl_image_turn( img, rot ) ) ;

 cleanup:
  return ;
}

/** 
  @brief
    Create a sub pre image 
  @param pre 
    The image to cut (PRE format)
  @param xmin 
    x lower left point of the result image (FITS coordinate)
  @param ymin 
    y lower left point of the result image (FITS coordinate)
  @param xmax 
    x upper left point of the result image (FITS coordinate)
  @param ymax 
    y upper left point of the result image (FITS coordinate)
 */
void xsh_pre_extract( xsh_pre *pre, int xmin, int ymin, int xmax, int ymax)
{
  cpl_image * img = NULL ;
  cpl_image * ext_img = NULL ;

  XSH_ASSURE_NOT_NULL( pre);

  check( img = xsh_pre_get_data( pre));

  XSH_ASSURE_NOT_NULL( img);

  check( ext_img = cpl_image_extract( img, xmin, ymin, xmax, ymax));
  xsh_free_image( &img);
  pre->data = ext_img;

  check( img = xsh_pre_get_errs( pre));
  check( ext_img = cpl_image_extract( img, xmin, ymin, xmax, ymax));
  xsh_free_image( &img);
  pre->errs = ext_img;

  check( img = xsh_pre_get_qual( pre));
  check( ext_img = cpl_image_extract( img, xmin, ymin, xmax, ymax));
  xsh_free_image( &img);
  pre->qual = ext_img;

  cleanup:
    return;
}
/** 
 *  @brief
 *    Flip an image on a given miror line 
 *  @param pre 
 *    Image to turn (PRE format)
 *  @param angle
 *    mirror line in polar coord. is theta = (PI/4) * angle    
 **/
void xsh_pre_flip( xsh_pre * pre, int angle )
{
  cpl_image * img = NULL ;

  XSH_ASSURE_NOT_NULL( pre ) ;

  check( img = xsh_pre_get_data( pre ) ) ;
  check( cpl_image_flip( img, angle ) ) ;

  check( img = xsh_pre_get_errs( pre ) ) ;
  check( cpl_image_flip( img, angle ) ) ;

  check( img = xsh_pre_get_qual( pre ) ) ;
  check( cpl_image_flip( img, angle ) ) ;

 cleanup:
  return ;
}
/**@}*/
