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

/*
 * $Author: amodigli $
 * $Date: 2012-03-05 16:34:06 $
 * $Revision: 1.37 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include <cpl.h>
#include <sinfo_cpl_size.h>
#include <math.h>


#include "sinfo_distortion.h"
#include "sinfo_functions.h"
#include "sinfo_msg.h"
#include "sinfo_error.h"
#include "irplib_flat.h"
#include "sinfo_utils_wrappers.h"
//#include "sinfo_irplib_cpl_wrp.h"
#include "sinfo_utilities.h"
/*-----------------------------------------------------------------------------
                                   Define
 ----------------------------------------------------------------------------*/

#define ARC_NBSAMPLES       20
#define ARC_THRESHFACT      (1.0/3.0)
#define ARC_MINGOODPIX      100
#define ARC_MINARCLENFACT   1.19   /* 1.1-2 */
#define ARC_MINNBARCS       32     /* 4-32 */
#define ARC_RANGE_FACT      3.0
#define ARC_WINDOWSIZE      10  /* 32 */

#define TRESH_MEDIAN_MIN    0.0
#define TRESH_SIGMA_MAX     200.0

/**@{*/
/*---------------------------------------------------------------------------*/
/**
 * @defgroup sinfo_distortion       Distortion correction functions
 */
/*---------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------
                                Functions prototypes
 ----------------------------------------------------------------------------*/
static cpl_apertures *
sinfo_distortion_detect_arcs_new(cpl_image* ,cpl_image   **,
                                 int,int,double,int,int,int,int,double,int);

static
cpl_apertures * sinfo_distortion_detect_arcs(cpl_image *,
        cpl_image **, int, int, int, int, int, int) ;
static int
sinfo_distortion_fill_badzones(cpl_image *, int, int, int, int, double) ;
static int
sinfo_distortion_threshold1d(cpl_image *, double, cpl_image *, double) ;
static int
sinfo_distortion_purge_arcs(cpl_image *, cpl_apertures **,
                            cpl_image **, int, int, double) ;
static cpl_bivector **
sinfo_distortion_get_arc_positions(cpl_image *,
                                   cpl_image *,
                                   cpl_apertures *, int, double **) ;
static double sinfo_distortion_fine_pos(cpl_image *, cpl_image *, int, int) ;
static int sinfo_distortion_sub_hor_lowpass(cpl_image *, int) ;
static cpl_image * sinfo_distortion_remove_ramp(const cpl_image *) ;
static cpl_image *
sinfo_distortion_smooth(cpl_image* inp,const int r,const int d);




/*----------------------------------------------------------------------------
                                Functions code
 ----------------------------------------------------------------------------*/


/*---------------------------------------------------------------------------*/
/**
  @brief    Perform running mean filter of a given radius along a given
            direction
  @param    im image to be filtered
  @param    rad filter radius
  @param    direction: 0 (X) or 1 (Y)
  @return   smoothed image if succes, NULL else
*/

static cpl_image *
sinfo_distortion_smooth(cpl_image* inp,const int r,const int d)
{

  int sx=0;
  int sy=0;
  int i=0;
  int j=0;
  int z=0;

  float sum;
  cpl_image* out=NULL;
  float* pi=NULL;
  float* po=NULL;
  int min=0;

  cknull(inp,"Null input image!");
  check_nomsg(sx=cpl_image_get_size_x(inp));
  check_nomsg(sy=cpl_image_get_size_y(inp));
  check_nomsg(out=cpl_image_duplicate(inp));
  check_nomsg(pi=cpl_image_get_data_float(inp));
  check_nomsg(po=cpl_image_get_data_float(out));
  min = r/2;
  switch (d) {
  case 0:
    for(j=0;j<sy;j++) {
      for(i=min;i<sx-min;i++) {
    sum=0;
    for(z=i-min;z<i+min+1;z++) {
      sum+=pi[z+j*sx];
    }
        po[i+j*sx]=sum/r;
      }
    }
    break;

  case 1:
    for(i=0;i<sx;i++) {
      for(j=min;j<sy-min;j++) {
    sum=0;
    for(z=j-min;z<j+min+1;z++) {
      sum+=pi[i+z*sx];
    }
        po[i+j*sx]=sum;
      }
    }
    break;

  default:
    sinfo_msg_error("case not supported");
    goto cleanup;

  }
  check_nomsg(cpl_image_delete(inp));
  return out;
 cleanup:
  return NULL;
}


/*---------------------------------------------------------------------------*/
/**
  @brief    Estimate the distortion using vertical curved arcs
  @param    org     the input image
  @param    xmin
  @param    ymin    Define the zone to take into account
  @param    xmax
  @param    ymax
  @param    auto_ramp_sub   To automatically clean the image before detection
  @param    arc_sat Saturation threshold for the arcs
  @param    max_arc_width   Maximum arc width allowed in pixels
  @param    kappa   Kappa for arc detection (0.33 = good default)
  @param    degree  The degree of the fitted polynomial
  @param    arcs    The found arcs
  @return   The 2d polynomial describing the distortion or NULL in error case

  The arcs are expected to be vertical.

  If (Xi, yi) define positions on the curved arcs, and (xi, yi) the associated
  positions on the straight arcs, the created polynomial is Xi = P(xi, yi).

  This polynomial can be used as it is by the CPL warping function to correct
  the image.
 */
/*---------------------------------------------------------------------------*/
cpl_polynomial * sinfo_distortion_estimate_new(
        const cpl_image *   org,
        int                 xmin,
        int                 ymin,
        int                 xmax,
        int                 ymax,
        int                 auto_ramp_sub,
        int                 arc_sat,
        int                 max_arc_width,
        double              kappa,
        double              arcs_min_arclen_factor,
        int                 arcs_window_size,
        int                 smooth_rad,
        int                 degree,
        double              offset,
        cpl_apertures   **  arcs)
{
    cpl_image       *   local_im ;
    cpl_image       *   label_image ;
    double              rightmost, leftmost ;
    cpl_bivector    **  arcs_pos ;
    double          *   parc_posx ;
    double          *   parc_posy ;
    double          *   lines_pos ;
    cpl_bivector    *   grid ;
    double          *   pgridx ;
    double          *   pgridy ;
    cpl_vector      *   values_to_fit ;
    double          *   pvalues_to_fit ;
    int                 min_arc_range ;
    int                 n_calib ;
    int                 n_arcs ;
    cpl_polynomial  *   poly2d ;
    int                 nx ;
    int                 i, j ;

    /* AMO added to use offset */
    cpl_vector    *     lines_pos_tmp ;
    cpl_bivector    *   grid_tmp ;
    cpl_vector* grid_tot=0;
    double* pgrid_tmp_x=NULL;
    //double* pgrid_tmp_y=NULL;
    double* pgrid_tot=NULL;
    double* plines_pos_tmp=NULL;
    int n_lines=0;
    int k=0;


    /* Check entries */
    if (org == NULL) return NULL ;
    if (kappa < 0.0) return NULL ;

    /* Initialise */
    n_calib = ARC_NBSAMPLES ;
    nx = cpl_image_get_size_x(org) ;

    if (auto_ramp_sub) {
        local_im = sinfo_distortion_remove_ramp(org) ;
    } else {
        /* Local copy of input image */
        local_im = cpl_image_duplicate(org) ;
    }
    if (local_im == NULL) {
        cpl_msg_error(cpl_func, "Cannot clean the image") ;
        return NULL ;
    }
    if(smooth_rad > 1) {
      local_im=sinfo_distortion_smooth(local_im,smooth_rad,1);
      //cpl_image_save(local_im,"out_local_im.fits",CPL_BPP_IEEE_FLOAT,
      //               NULL,CPL_IO_DEFAULT);
      //local_im=sinfo_distortion_image_restore(local_im,smooth_rad,1,2,0,2);
      //cpl_image_save(local_im,"out_local_im_post.fits",
      //               CPL_BPP_IEEE_FLOAT,NULL,CPL_IO_DEFAULT);

    }
    /* Detect the arcs in the input image */
    cpl_msg_info(cpl_func, "Detect arcs") ;
    if ((*arcs = sinfo_distortion_detect_arcs_new(local_im,
                    &label_image,
                    arc_sat, max_arc_width, kappa,
                    xmin, ymin, xmax, ymax,
                    arcs_min_arclen_factor,arcs_window_size)) == NULL) {
        cpl_image_delete(local_im) ;
        cpl_msg_error(cpl_func, "Cannot detect the arcs") ;
        return NULL ;
    }
    n_arcs = cpl_apertures_get_size(*arcs) ;
    cpl_msg_info(cpl_func, "%d detected arcs", n_arcs) ;

    /* Check that the arcs are not concentrated in the same zone */
    rightmost = leftmost = cpl_apertures_get_pos_x(*arcs, 1) ;
    for (i=1 ; i<n_arcs ; i++) {
        if (cpl_apertures_get_pos_x(*arcs, i+1) < leftmost)
            leftmost = cpl_apertures_get_pos_x(*arcs, i+1) ;
        if (cpl_apertures_get_pos_x(*arcs, i+1) > rightmost)
            rightmost = cpl_apertures_get_pos_x(*arcs, i+1) ;
    }
    min_arc_range = (int)(nx / ARC_RANGE_FACT) ;
    if ((int)(rightmost-leftmost) < min_arc_range) {
        cpl_msg_error(cpl_func, "too narrow range (%g-%g)<%d",
                rightmost, leftmost, min_arc_range) ;
        cpl_apertures_delete(*arcs) ;
        cpl_image_delete(local_im) ;
        cpl_image_delete(label_image) ;
        return NULL ;
    }

    /* Create a 2-D deformation grid with detected arcs */
    cpl_msg_info(cpl_func, "Create deformation grid") ;
    lines_pos = cpl_malloc(n_arcs * sizeof(double)) ;
    if ((arcs_pos = sinfo_distortion_get_arc_positions(local_im,
                    label_image, *arcs, n_calib, &lines_pos))==NULL){
        cpl_msg_error(cpl_func, "cannot get arcs positions") ;
        cpl_apertures_delete(*arcs) ;
        cpl_image_delete(local_im) ;
        cpl_free(lines_pos) ;
        cpl_image_delete(label_image) ;
        return NULL ;
    }
    cpl_image_delete(label_image) ;
    cpl_image_delete(local_im) ;

    /* Prepare the fitting */
    lines_pos_tmp=cpl_vector_new(n_arcs);
    plines_pos_tmp=cpl_vector_get_data(lines_pos_tmp);


    sinfo_msg("Fit the 2d polynomial") ;
    grid = cpl_bivector_new(n_arcs * n_calib) ;
    pgridx = cpl_bivector_get_x_data(grid) ;
    pgridy = cpl_bivector_get_y_data(grid) ;
    values_to_fit = cpl_vector_new(n_arcs * n_calib) ;
    pvalues_to_fit = cpl_vector_get_data(values_to_fit) ;

    for (i=0 ; i<n_arcs ; i++) {
        parc_posx = cpl_bivector_get_x_data(arcs_pos[i]) ;
        parc_posy = cpl_bivector_get_y_data(arcs_pos[i]) ;
        for (j=0 ; j<n_calib ; j++) {
            plines_pos_tmp[i]=lines_pos[i] ;
            pgridx[j+i*n_calib] = lines_pos[i] ;
            pgridy[j+i*n_calib] = parc_posy[j] ;
            pvalues_to_fit[j+i*n_calib] = parc_posx[j] ;
        }
    }
    /* AMO new to use offset */
    n_lines= n_arcs/32.0;
    if(n_lines < 1) {
      n_lines=1;
    }
    cpl_vector_sort(lines_pos_tmp,1);
    plines_pos_tmp=cpl_vector_get_data(lines_pos_tmp);
    grid_tmp=cpl_bivector_duplicate(grid);
    grid_tot=cpl_vector_new(n_calib);
    pgrid_tmp_x = cpl_bivector_get_x_data(grid_tmp) ;
    //pgrid_tmp_y = cpl_bivector_get_y_data(grid_tmp) ;
    pgrid_tot = cpl_vector_get_data(grid_tot);
    for(j=0;j<n_calib;j++) {
      pgrid_tot[j]=0;
      for(i=n_lines ;i<n_arcs;i=i+n_lines)
    {
      for(k=0;k<n_lines;k++) {
        pgrid_tot[j] += (plines_pos_tmp[i+k]-
                             plines_pos_tmp[k]);
        /*
            sinfo_msg("diff=%g",(plines_pos_tmp[i+k]-
                 plines_pos_tmp[k]));
        */
      }
    }
      /*
      sinfo_msg("j=%d pgrid_tot=%g",j,pgrid_tot[j]);
      */
    }

    for(j=0;j<n_calib;j++) {
      for (i=0 ; i<n_arcs ; i++) {
     pgridx[j+i*n_calib]=pgridx[j+i*n_calib]*
                             ((nx/32.0)*n_lines*(31*32/2))/pgrid_tot[j]-offset;
     /*
         sinfo_msg_error("AMo after corr grid[%d,%d]=%g",
                          i,k,pgridx[k+i*n_calib]);
     */
     pgrid_tmp_x[j+i*n_calib]=pgrid_tmp_x[j+i*n_calib]*
                                  ((nx/32.0)*n_lines*(31*32/2))/pgrid_tot[j]-
                                  offset;

      }
    }
    cpl_vector_delete(lines_pos_tmp);
    cpl_bivector_delete(grid_tmp);
    cpl_vector_delete(grid_tot);
    /* end AMO: to use the offset */


    for (i=0 ; i<n_arcs ; i++) cpl_bivector_delete(arcs_pos[i]) ;
    cpl_free(arcs_pos) ;
    cpl_free(lines_pos) ;

    /* Apply the fitting */
    if ((poly2d = sinfo_polynomial_fit_2d_create(grid, values_to_fit,
                    degree, NULL))==NULL) {
        cpl_msg_error(cpl_func, "cannot apply the 2d fit") ;
        cpl_bivector_delete(grid) ;
        cpl_vector_delete(values_to_fit) ;
        cpl_apertures_delete(*arcs) ;
        return NULL ;
    }

    /* Free and return */
    cpl_bivector_delete(grid) ;
    cpl_vector_delete(values_to_fit) ;
    return poly2d ;
}



/*---------------------------------------------------------------------------*/
/**
  @brief    Detect the vertical arcs in an image
  @param    im          the input image
  @param    label_im    the output label image
  @param    arc_sat     Saturation threshold for the arcs
  @param    max_arc_width   Maximum arc width allowed
  @param    kappa           For arcs detection (0.33 is a good default)
  @param    xmin
  @param    ymin        Define the zone to take into account
  @param    xmax
  @param    ymax
  @return   The arcs or NULL in error case

  The arcs are expected to be vertical.
 */
/*---------------------------------------------------------------------------*/
static cpl_apertures * sinfo_distortion_detect_arcs_new(
        cpl_image   *   im,
        cpl_image   **  label_im,
        int             arc_sat,
        int             max_arc_width,
        double          kappa,
        int             xmin,
        int             ymin,
        int             xmax,
        int             ymax,
        double arcs_min_arclen_factor,
        int arcs_window_size)
{
    cpl_image       *   filt_im ;
    cpl_matrix      *   filter ;
    cpl_image       *   collapsed ;
    cpl_mask        *   bin_im ;
    double              threshold, fillval, median_val, sigma ;
    int                 min_arclen = 0 ;
    cpl_apertures   *   det ;
    cpl_size                 nobj ;
    int                 ngoodpix ;
    int                 ny ;

    ny = cpl_image_get_size_y(im) ;
    /* Default values for output parameters */
    *label_im = NULL ;

    /* Clear zones to be ignored (to avoid false detections) */
    median_val = cpl_image_get_median_dev(im, &sigma) ;
    fillval = median_val-sigma/2.0 ;
    if (sinfo_distortion_fill_badzones(im, xmin, ymin, xmax, ymax,
                fillval) == -1) {
        cpl_msg_error(cpl_func, "cannot fill bad zones") ;
        return NULL ;
    }
    /* Median vertical filter */
    filter = cpl_matrix_new(3, 1) ;
    cpl_matrix_fill(filter, 1.0) ;
    /* filt_im = cpl_image_filter_median(im, filter) ; */
    filt_im = cpl_image_duplicate(im) ;
    cpl_matrix_delete(filter) ;

    /* Subtract a low-pass */
    /* AMO: suppressed as may remove arcs */
    if (sinfo_distortion_sub_hor_lowpass(filt_im, arcs_window_size) == -1) {
        cpl_image_delete(filt_im) ;
        return NULL ;
    }
    //cpl_image_save(filt_im,"out_filt_im_lp.fits",CPL_BPP_IEEE_FLOAT,
    //               NULL,CPL_IO_DEFAULT);

    /* Get relevant stats for thresholding */
    median_val = cpl_image_get_median_dev(filt_im, &sigma) ;

    /* Correct median_val and sigma if necessary */
    if (median_val < TRESH_MEDIAN_MIN) median_val = TRESH_MEDIAN_MIN ;
    if (sigma > TRESH_SIGMA_MAX) sigma = TRESH_SIGMA_MAX ;

    /* Set the threshold */
    threshold = median_val + sigma * kappa ;

    /* Collapse the image */
    collapsed = cpl_image_collapse_median_create(filt_im, 0, 0, 0) ;

    /* Threshold to keep only the arcs - use of the collapsed image */
    if (sinfo_distortion_threshold1d(filt_im, median_val,
                                     collapsed, 0.0)==-1) {
        cpl_msg_error(cpl_func, "cannot threshold the filtered image") ;
        cpl_image_delete(filt_im) ;
        cpl_image_delete(collapsed) ;
        return NULL ;
    }
    cpl_image_delete(collapsed) ;

    /* Binarize the image */
    bin_im = cpl_mask_threshold_image_create(filt_im, threshold,
            SINFO_DBL_MAX);
    cpl_image_delete(filt_im) ;
    if (bin_im == NULL) {
        cpl_msg_error(cpl_func, "cannot binarise the image") ;
        return NULL ;
    }

    /* Test if there are enough good pixels */
    ngoodpix = cpl_mask_count(bin_im) ;
    if (ngoodpix < ARC_MINGOODPIX) {
        cpl_msg_error(cpl_func, "Too few (%d) white pixels", ngoodpix) ;
        cpl_mask_delete(bin_im) ;
        return NULL ;
    }

    /* Apply a morphological closing to clean the isolated pixels */
    filter = cpl_matrix_new(3, 3) ;
    cpl_matrix_fill(filter, 1.0) ;
    cpl_mask_closing(bin_im, filter) ;
    cpl_matrix_delete(filter) ;

    /* Labelize pixel map to a label image */
    *label_im = cpl_image_labelise_mask_create(bin_im, &nobj) ;
    cpl_mask_delete(bin_im) ;
    //cpl_image_save(*label_im,"out_label_im.fits",CPL_BPP_IEEE_FLOAT,
    //               NULL,CPL_IO_DEFAULT);

    /* Compute statistics on objects */
    if ((det = cpl_apertures_new_from_image(im, *label_im)) == NULL) {
        cpl_msg_error(cpl_func, "Cannot compute arcs stats") ;
        cpl_image_delete(*label_im) ;
        *label_im = NULL ;
        return NULL ;
    }
    /* Set min_arclen */
    min_arclen = (int)(ny /arcs_min_arclen_factor) ;
    //cpl_image_save(im,"out_im.fits",CPL_BPP_IEEE_FLOAT,NULL,CPL_IO_DEFAULT);

    /* Purge non-relevant arcs */
    /* cpl_apertures_dump(det,stdout); */
    if (sinfo_distortion_purge_arcs(im, &det, label_im, min_arclen,
                max_arc_width, arc_sat) == -1) {
        cpl_msg_error(cpl_func, "Cannot purge the arcs") ;
        cpl_image_delete(*label_im) ;
        *label_im = NULL ;
        cpl_apertures_delete(det) ;
        return NULL ;
    }
    /* cpl_apertures_dump(det,stdout); */
    if (cpl_apertures_get_size(det) < ARC_MINNBARCS) {
        cpl_msg_error(cpl_func, "Not enough valid arcs (%" 
                      CPL_SIZE_FORMAT " < %d)",
                cpl_apertures_get_size(det), ARC_MINNBARCS) ;
        cpl_image_delete(*label_im) ;
        *label_im = NULL ;
        cpl_apertures_delete(det) ;
        return NULL ;
    }

    /* Return  */
    return det ;
}



/**@}*/

/*---------------------------------------------------------------------------*/
/**
  @brief    Detect the vertical arcs in an image
  @param    im          the input image
  @param    label_im    the output label image
  @param    arc_sat     Saturation threshold for the arcs
  @param    max_arc_width   Maximum arc width allowed
  @param    xmin
  @param    ymin        Define the zone to take into account
  @param    xmax
  @param    ymax
  @return   The arcs or NULL in error case

  The arcs are expected to be vertical.
 */
/*---------------------------------------------------------------------------*/
static cpl_apertures * sinfo_distortion_detect_arcs(
        cpl_image   *   im,
        cpl_image   **  label_im,
        int             arc_sat,
        int             max_arc_width,
        int             xmin,
        int             ymin,
        int             xmax,
        int             ymax)
{

    cpl_image       *   filt_im ;
    cpl_matrix      *   filter ;
    cpl_image       *   collapsed ;
    cpl_mask        *   bin_im ;
    double              threshold, fillval, median_val, sigma ;
    int                 min_arclen = 0 ;
    cpl_apertures   *   det ;
    cpl_size                 nobj ;
    int                 ngoodpix ;
    int                 ny ;

    ny = cpl_image_get_size_y(im) ;

    /* Default values for output parameters */
    *label_im = NULL ;

    /* Clear zones to be ignored (to avoid false detections) */
    median_val = cpl_image_get_median_dev(im, &sigma) ;
    fillval = median_val-sigma/2.0 ;
    if (sinfo_distortion_fill_badzones(im, xmin, ymin, xmax, ymax,
                fillval) == -1) {
        cpl_msg_error(cpl_func, "cannot fill bad zones") ;
        return NULL ;
    }

    /* Median vertical filter */
    filter = cpl_matrix_new(3, 1) ;
    cpl_matrix_fill(filter, 1.0) ;
    /* filt_im = cpl_image_filter_median(im, filter) ; */
    filt_im = cpl_image_duplicate(im) ;
    cpl_matrix_delete(filter) ;

    /* Subtract a low-pass */
    if (sinfo_distortion_sub_hor_lowpass(filt_im, ARC_WINDOWSIZE) == -1) {
        cpl_image_delete(filt_im) ;
        return NULL ;
    }

    /* Get relevant stats for thresholding */
    median_val = cpl_image_get_median_dev(filt_im, &sigma) ;

    /* Correct median_val and sigma if necessary */
    if (median_val < TRESH_MEDIAN_MIN) median_val = TRESH_MEDIAN_MIN ;
    if (sigma > TRESH_SIGMA_MAX) sigma = TRESH_SIGMA_MAX ;

    /* Set the threshold */
    threshold = median_val + sigma * ARC_THRESHFACT ;

    /* Collapse the image */
    collapsed = cpl_image_collapse_median_create(filt_im, 0, 0, 0) ;

    /* Threshold to keep only the arcs - use of the collapsed image */
    if (sinfo_distortion_threshold1d(filt_im, median_val,
                                     collapsed, 0.0)==-1) {
        cpl_msg_error(cpl_func, "cannot threshold the filtered image") ;
        cpl_image_delete(filt_im) ;
        cpl_image_delete(collapsed) ;
        return NULL ;
    }
    cpl_image_delete(collapsed) ;

    /* Binarize the image */
    bin_im = cpl_mask_threshold_image_create(filt_im, threshold,
            SINFO_DBL_MAX);
    cpl_image_delete(filt_im) ;
    if (bin_im == NULL) {
        cpl_msg_error(cpl_func, "cannot binarise the image") ;
        return NULL ;
    }

    /* Test if there are enough good pixels */
    ngoodpix = cpl_mask_count(bin_im) ;
    if (ngoodpix < ARC_MINGOODPIX) {
        cpl_msg_error(cpl_func, "Too few (%d) white pixels", ngoodpix) ;
        cpl_mask_delete(bin_im) ;
        return NULL ;
    }

    /* Apply a morphological closing to clean the isolated pixels */
    filter = cpl_matrix_new(3, 3) ;
    cpl_matrix_fill(filter, 1.0) ;
    cpl_mask_closing(bin_im, filter) ;
    cpl_matrix_delete(filter) ;

    /* Labelize pixel map to a label image */
    *label_im = cpl_image_labelise_mask_create(bin_im, &nobj) ;
    cpl_mask_delete(bin_im) ;

    /* Compute statistics on objects */
    if ((det = cpl_apertures_new_from_image(im, *label_im)) == NULL) {
        cpl_msg_error(cpl_func, "Cannot compute arcs stats") ;
        cpl_image_delete(*label_im) ;
        *label_im = NULL ;
        return NULL ;
    }

    /* Set min_arclen */
    min_arclen = (int)(ny / ARC_MINARCLENFACT) ;

    /* Purge non-relevant arcs */
    if (sinfo_distortion_purge_arcs(im, &det, label_im, min_arclen,
                max_arc_width, arc_sat) == -1) {
        cpl_msg_error(cpl_func, "Cannot purge the arcs") ;
        cpl_image_delete(*label_im) ;
        *label_im = NULL ;
        cpl_apertures_delete(det) ;
        return NULL ;
    }
    if (cpl_apertures_get_size(det) < ARC_MINNBARCS) {
        cpl_msg_error(cpl_func, "Not enough valid arcs (%"
                      CPL_SIZE_FORMAT " < %d)",
                cpl_apertures_get_size(det), ARC_MINNBARCS) ;
        cpl_image_delete(*label_im) ;
        *label_im = NULL ;
        cpl_apertures_delete(det) ;
        return NULL ;
    }

    /* Return  */
    return det ;
}

static int sinfo_distortion_fill_badzones(
        cpl_image   *   im,
        int             xmin,
        int             ymin,
        int             xmax,
        int             ymax,
        double          fillval)
{
    float       *   pfi ;
    int             nx, ny ;
    int             i, j ;

    /* Check entries */
    if (im == NULL) return -1 ;
    if (cpl_image_get_type(im) != CPL_TYPE_FLOAT) return -1 ;

    /* Get the data */
    pfi = cpl_image_get_data_float(im) ;
    nx = cpl_image_get_size_x(im) ;
    ny = cpl_image_get_size_y(im) ;

    /* Fill the zone */
    for (i=0 ; i<nx ; i++) {
        for (j=0 ; j<ny ; j++) {
            if ((i<xmin-1) || (i>xmax-1) || (j<ymin-1) || (j>ymax-1)) {
                pfi[i+j*nx] = (float)fillval ;
            }
        }
    }
    return 0 ;
}

static int sinfo_distortion_threshold1d(
        cpl_image   *   im,
        double          threshold,
        cpl_image   *   im1d,
        double          newval)
{
    float       *   pim ;
    float       *   pim1d ;
    int             nx, ny ;
    int             i, j ;

    /* Check entries */
    if (im == NULL) return -1 ;
    if (im1d == NULL) return -1 ;
    if (cpl_image_get_type(im) != CPL_TYPE_FLOAT) return -1 ;
    if (cpl_image_get_type(im1d) != CPL_TYPE_FLOAT) return -1 ;

    /* Get access to the im / im1d data */
    pim = cpl_image_get_data_float(im) ;
    pim1d = cpl_image_get_data_float(im1d) ;
    nx = cpl_image_get_size_x(im) ;
    ny = cpl_image_get_size_y(im) ;

    /* Apply the thresholding */
    for (i=0 ; i<nx ; i++)
        if (pim1d[i] < threshold) {
            for (j=0 ; j<ny ; j++) pim[i+j*nx] = (float)newval ;
        }

    /* Return */
    return 0 ;
}

static int sinfo_distortion_sub_hor_lowpass(
        cpl_image   *   im,
        int             filt_size)
{
    cpl_vector  *   linehi ;
    cpl_vector  *   linelo ;
    cpl_vector  *   avglinehi ;
    cpl_vector  *   avglinelo ;
    double      *   pavglinehi ;
    float       *   pim ;
    int             lopos, hipos, nx, ny ;
    int             i, j ;

    /* Test entries */
    if (im == NULL) return -1 ;
    if (filt_size <= 0) return -1 ;

    /* Initialise */
    nx = cpl_image_get_size_x(im) ;
    ny = cpl_image_get_size_y(im) ;
    lopos = (int)(ny/4) ;
    hipos = (int)(3*ny/4) ;

    /* Get the vectors out of the image */
    if ((linehi = cpl_vector_new_from_image_row(im, hipos)) == NULL) {
        return -1 ;
    }
    if ((linelo = cpl_vector_new_from_image_row(im, lopos)) == NULL) {
        cpl_vector_delete(linehi) ;
        return -1 ;
    }

    /* Filter the vectors */
    if ((avglinehi = cpl_vector_filter_median_create(linehi,
                    filt_size)) == NULL) {
        cpl_vector_delete(linehi) ;
        cpl_vector_delete(linelo) ;
        return -1 ;
    }
    cpl_vector_delete(linehi) ;

    if ((avglinelo = cpl_vector_filter_median_create(linelo,
                    filt_size)) == NULL) {
        cpl_vector_delete(linelo) ;
        cpl_vector_delete(avglinehi) ;
        return -1 ;
    }
    cpl_vector_delete(linelo) ;

    /* Average the filtered vectors to get the low freq signal */
    cpl_vector_add(avglinehi, avglinelo) ;
    cpl_vector_delete(avglinelo) ;
    cpl_vector_divide_scalar(avglinehi, 2.0) ;

    /* Subtract the low frequency signal */
    pavglinehi = cpl_vector_get_data(avglinehi) ;
    pim = cpl_image_get_data_float(im) ;
    for (i=0 ; i<nx ; i++) {
        for (j=0 ; j<ny ; j++) {
            pim[i+j*nx] -= pavglinehi[i] ;
        }
    }
    cpl_vector_delete(avglinehi) ;

    return 0 ;
}








static int sinfo_distortion_purge_arcs(
        cpl_image       *   im,
        cpl_apertures   **  arcs,
        cpl_image       **  lab_im,
        int                 min_arclen,
        int                 max_arcwidth,
        double              arc_sat)
{

    int             nb_arcs ;



    int         *   plabim ;
    cpl_mask    *   bin_im ;
    int             nx, ny ;
    int             i, j ;

    /* Check entries */
    if (arcs == NULL) return -1 ;
    if (*arcs == NULL) return -1 ;
    if (*lab_im == NULL) return -1 ;

    /* Get number of arcs */
    nb_arcs = cpl_apertures_get_size(*arcs) ;
    nx = cpl_image_get_size_x(*lab_im) ;
    ny = cpl_image_get_size_y(*lab_im) ;

    /* Allocate selection array */
    int* selection = cpl_malloc(nb_arcs * sizeof(int)) ;
    /* Loop on the different arcs candidates */
    /* sinfo_msg("min_arclen=%d max_arcwidth=%d",min_arclen,max_arcwidth); */
    for (i=0 ; i<nb_arcs ; i++) {
        int arclen = cpl_apertures_get_top(*arcs, i+1) -
            cpl_apertures_get_bottom(*arcs, i+1) + 1 ;
        int arcwidth = cpl_apertures_get_right(*arcs, i+1) -
            cpl_apertures_get_left(*arcs, i+1) + 1 ;
        int edge = cpl_apertures_get_left_y(*arcs, i+1) ;
        double mean = cpl_apertures_get_mean(*arcs, i+1) ;

        /* Test if the current object is a valid arc */

        if (
            (arclen>min_arclen) &&
        (arcwidth<max_arcwidth) &&
            (edge>0) &&
            (mean < arc_sat)) {
      /*
        sinfo_msg_warning("Take Pos=%5.4d len=%d width=%d edge=%d mean=%f ",
    (cpl_apertures_get_right(*arcs, i+1)+cpl_apertures_get_left(*arcs, i+1))/2,
     arclen,arcwidth,edge,mean);
      */
            selection[i] = 1 ;
        } else {
      /*
    sinfo_msg_warning("Rej Pos=%5.4d len=%d width=%d edge=%d mean=%f i=%d",
         (cpl_apertures_get_right(*arcs, i+1)+
          cpl_apertures_get_left(*arcs, i+1))/2,arclen,arcwidth,edge,mean,i);
      */
            selection[i] = 0 ;
        }
    }

    /* Update the labelised image by erasing non valid arcs */
    for (i=0 ; i<nb_arcs ; i++) {
        if (selection[i] == 0) {
            plabim = cpl_image_get_data_int(*lab_im) ;
            for (j=0 ; j<nx*ny ; j++) {
                if (plabim[j] == i+1) plabim[j] = 0 ;
            }
        }
    }
    cpl_free(selection) ;

    /* Reset the labels to have consecutive ones */
    bin_im = cpl_mask_threshold_image_create(*lab_im, 0.5, SINFO_DBL_MAX) ;
    cpl_image_delete(*lab_im) ;
    *lab_im = cpl_image_labelise_mask_create(bin_im, NULL) ;
    cpl_mask_delete(bin_im) ;

    /* Purge the bad arcs */
    cpl_apertures_delete(*arcs) ;
    *arcs = cpl_apertures_new_from_image(im, *lab_im) ;

    /* Check if there are some valid arcs */
    if (cpl_apertures_get_size(*arcs) <= 0) {
        cpl_msg_error(cpl_func, "No valid arc found") ;
        return -1 ;
    }
    /* Return  */
    return 0 ;
}

static cpl_bivector **
sinfo_distortion_get_arc_positions(
        cpl_image       *   in,
        cpl_image       *   label_im,
        cpl_apertures   *   det,
        int                 nb_samples,
        double          **  lines_pos)
{

    int                 n_arcs ;
    cpl_image       *   filt_img ;
    cpl_matrix      *   kernel ;
    cpl_bivector    **  pos ;
    double          *   biv_x ;
    double          *   biv_y ;
    double              x_finepos ;
    int             *   plabel_im ;
    int             *   arcs_samples_y ;
    int             *   computed ;

    int                 use_this_arc ;
    int                 obj ;
    int                 nx, ny ;
    int                 i, j, k ;
    cpl_mask*          mask=NULL;

    /* Check entries */

    /* Initialise */
    n_arcs = cpl_apertures_get_size(det) ;
    nx = cpl_image_get_size_x(label_im) ;
    ny = cpl_image_get_size_y(label_im) ;

    /* Allocate positions (pos. of n_arcs*nb_samples pts on the arcs) */
    pos = cpl_calloc(n_arcs, sizeof(cpl_bivector*)) ;
    for (i=0 ; i<n_arcs ; i++) pos[i] = cpl_bivector_new(nb_samples) ;

    /* Median filter on input image */
    kernel = cpl_matrix_new(3, 3) ;
    cpl_matrix_fill(kernel, 1.0) ;
    filt_img=cpl_image_duplicate(in);
    mask=cpl_mask_new(3,3);
    cpl_mask_not(mask);
    cpl_image_filter_mask(filt_img,in,mask,CPL_FILTER_MEDIAN,CPL_BORDER_FILTER);
    cpl_mask_delete(mask);

    cpl_matrix_delete(kernel) ;

    /* Measured Arcs coordinates along curvature */
    arcs_samples_y = cpl_malloc(n_arcs * nb_samples * sizeof(int)) ;
    computed = cpl_calloc(n_arcs*nb_samples, sizeof(int)) ;

    /* Find out the Y coordinates along the arcs  */
    for (j=0 ; j<n_arcs ; j++) {
        double arclen = cpl_apertures_get_top(det,j+1) -
            cpl_apertures_get_bottom(det,j+1) + 1 ;
        for (i=0 ; i<nb_samples ; i++) {
            arcs_samples_y[i+j*nb_samples] =
                (int)(cpl_apertures_get_bottom(det, j+1) +
                      (arclen * (i + 0.5)) / (double)nb_samples) ;
        }
    }

    /* Find out the X coord. at nb_samples Y positions on all arcs */
    plabel_im = cpl_image_get_data_int(label_im) ;
    for (i=0 ; i<nx ; i++) {
        for (j=0 ; j<ny ; j++) {
            /* use_this_arc is set to 1 if we are on the arc at a y */
            /* coordinate where the x coord should be found */
            obj = plabel_im[i + j * nx] ;
            /* Handle background */
            if (obj==0) continue ;
            /* Decrease by one to index the array from 0 */
            else obj-- ;

            use_this_arc = 0 ;
            for (k=0 ; k<nb_samples ; k++) {
                if (arcs_samples_y[k+obj*nb_samples] == j) {
                    use_this_arc = 1 ;
                    break ;
                }
            }
            if ((use_this_arc)  && (computed[k+obj*nb_samples] == 0)) {
                /* Find x coordinate of obj at the Y coord. */
                if ((x_finepos = sinfo_distortion_fine_pos(filt_img,
                                label_im, i, j)) < 0.0) {
                    cpl_msg_error(cpl_func, "cannot find fine arc position") ;
                    cpl_image_delete(filt_img) ;
                    cpl_free(arcs_samples_y);
                    cpl_free(computed) ;
                    for (i=0 ; i<n_arcs ; i++) cpl_bivector_delete(pos[i]);
                    cpl_free(pos) ;
                    return NULL ;
                } else {
                    biv_x = cpl_bivector_get_x_data(pos[obj]) ;
                    biv_y = cpl_bivector_get_y_data(pos[obj]) ;
                    biv_x[k] = x_finepos ;
                    biv_y[k] = j ;
                    (*lines_pos)[obj] = cpl_apertures_get_centroid_x(det,obj+1);
                    computed[k+obj*nb_samples] = 1 ;
                }
            }
        }
    }

    /* Free and return */
    cpl_image_delete(filt_img) ;
    cpl_free(arcs_samples_y) ;
    cpl_free(computed) ;
    return pos ;
}

static double
sinfo_distortion_fine_pos(
        cpl_image   *   im,
        cpl_image   *   label_im,
        int             x,
        int             y)
{
    float   *   pim ;
    int     *   plabel_im ;
    int         objnum ;
    int         curr_obj ;
    int         start_pos ;
    double      grav_c ;
    double      sum ;
    double      max ;

    int         maxpos ;
    //int         im_extrem ;
    double      arc_pos ;
    int         nx ;

    /* Initialize */
    nx = cpl_image_get_size_x(im) ;
    grav_c = 0.0 ;
    sum    = 0.0 ;
    start_pos = x ;
    maxpos = start_pos ;
    pim = cpl_image_get_data_float(im) ;
    max    = (double)pim[start_pos + y * nx] ;
    plabel_im = cpl_image_get_data_int(label_im) ;
    objnum = plabel_im[start_pos + y * nx] ;
    //im_extrem = nx ;

    /* While we stay in the same object... */
    do {
        double val = (double)pim[start_pos + y * nx] ;
        if (start_pos == 0) grav_c = 0.0 ;
        else grav_c += start_pos * val ;
        sum += val ;
        if (val > max) {
            max = val ;
            maxpos = start_pos ;
        }

        /* Next point */
        start_pos++ ;

        curr_obj = plabel_im[start_pos + y * nx] ;
    } while (curr_obj == objnum) ;

    /* Returned position is the gravity center or the max in bad cases */
    if ((fabs(grav_c) < 1.0e-40) || (fabs(sum) < 1.0e-40)) {
        arc_pos = maxpos ;
    } else {
        arc_pos = grav_c / sum ;
        if (fabs(arc_pos) >= start_pos) arc_pos = maxpos ;
    }

    /* Return */
    return arc_pos ;
}

/*---------------------------------------------------------------------------*/
/**
  @brief    Detect and remove a dark ramp in an image
  @param    in  input image
  @return   a newly allocated clean image
 */
/*---------------------------------------------------------------------------*/
#define IS_NB_TESTPOINTS    8
#define IS_MIN_SLOPE        0.01
#define IS_MAX_SLOPE_DIF    0.075
#define IS_MAX_FIT_EDGE_DIF 0.05
#define IS_MIN_RAMP         10.0
#define IS_MAX_MNERR        13.0
#define IS_MAX_MNERR_DIF    8.0
#define IS_MAX_INTER_DIF    20.0
#define IS_SKIPZONE         2.5
#define SQR(x) ((x)*(x))
static cpl_image * sinfo_distortion_remove_ramp(const cpl_image * in)
{

    int                 ramp_present ;
    int                 nx, ny ;
    int                 yhi, ylo;

    cpl_bivector    *   testpointlo ;
    double          *   testpointlo_x ;
    double          *   testpointlo_y ;
    cpl_bivector    *   testpointhi ;
    double          *   testpointhi_x ;
    double          *   testpointhi_y ;
    int                 spacing;
    double              rampdif, fitslope;
    double          *   pol_coefhi,
                    *   pol_coeflo ;
    cpl_vector      *   median ;
    double          *   median_data ;
    double              medianerrlo, medianerrhi;
    double              slope ;
    cpl_image       *   out ;
    float           *   pout ;



    /* Initialise */
    nx = cpl_image_get_size_x(in) ;
    ny = cpl_image_get_size_y(in) ;

    /* Check entries */
    if (in==NULL) return NULL ;

    if (ny<IS_SKIPZONE*IS_NB_TESTPOINTS){
        cpl_msg_error(cpl_func, "image has %d lines, min=%d ",
                ny, (int)(IS_SKIPZONE*IS_NB_TESTPOINTS*2));
        return NULL ;
    }

    slope=0.0 ;
    spacing= ny / (IS_SKIPZONE*IS_NB_TESTPOINTS) ;
    yhi = (int)(ny/2) ;
    ylo = yhi - 1 ;
    /* Fill the vectors */
    testpointhi = cpl_bivector_new(IS_NB_TESTPOINTS) ;
    testpointhi_x = cpl_bivector_get_x_data(testpointhi) ;
    testpointhi_y = cpl_bivector_get_y_data(testpointhi) ;
    testpointlo = cpl_bivector_new(IS_NB_TESTPOINTS) ;
    testpointlo_x = cpl_bivector_get_x_data(testpointlo) ;
    testpointlo_y = cpl_bivector_get_y_data(testpointlo) ;
    int i;
    for (i=0 ; i<IS_NB_TESTPOINTS ; i++) {
        int y = yhi + i * spacing;
        cpl_vector* tmp_vector = cpl_vector_new_from_image_row(in, y+1) ;
        testpointhi_x[i] = y - ny / 2;
        testpointhi_y[i] = cpl_vector_get_median_const(tmp_vector) ;
        cpl_vector_delete(tmp_vector) ;
        y = ylo - i * spacing;
        tmp_vector = cpl_vector_new_from_image_row(in, y+1) ;
        testpointlo_x[IS_NB_TESTPOINTS-i-1] = y ;
        testpointlo_y[IS_NB_TESTPOINTS-i-1]=
	  cpl_vector_get_median_const(tmp_vector);
        cpl_vector_delete(tmp_vector) ;
    }

    /* Apply the fit */
    pol_coefhi = irplib_flat_fit_slope_robust(testpointhi_x,
            testpointhi_y, IS_NB_TESTPOINTS) ;
    pol_coeflo = irplib_flat_fit_slope_robust(testpointlo_x,
            testpointlo_y, IS_NB_TESTPOINTS) ;

    /* Compute the errors */
    median = cpl_vector_new(IS_NB_TESTPOINTS) ;
    median_data = cpl_vector_get_data(median) ;
    for (i=0 ; i<IS_NB_TESTPOINTS ; i++) {
        median_data[i]=SQR(testpointhi_y[i]
                - pol_coefhi[0] - pol_coefhi[1] * testpointhi_x[i]);
    }
    medianerrhi = cpl_vector_get_median_const(median) ;
    for (i=0; i<IS_NB_TESTPOINTS; i++) {
        median_data[i]=SQR(testpointlo_y[i]
                - pol_coeflo[0] - pol_coeflo[1] * testpointlo_x[i]);
    }
    medianerrlo = cpl_vector_get_median_const(median) ;
    cpl_vector_delete(median) ;
    rampdif = testpointlo_y[IS_NB_TESTPOINTS-1] - testpointhi_y[0];
    slope = rampdif / (ny/2.0) ;
    fitslope = (pol_coefhi[1] + pol_coeflo[1]) / 2.0 ;

    cpl_bivector_delete(testpointlo);
    cpl_bivector_delete(testpointhi);

    /* Decide if there is a ramp or not  */
    if (fabs(rampdif)<IS_MIN_RAMP ||
            fabs(pol_coefhi[1]) < IS_MIN_SLOPE ||
            fabs(pol_coeflo[1]) < IS_MIN_SLOPE ||
            pol_coefhi[1]/pol_coeflo[1]<0.5 ||
            pol_coefhi[1]/pol_coeflo[1]>2.0 ||
            fabs(pol_coefhi[1]-pol_coeflo[1])>IS_MAX_SLOPE_DIF ||
            fabs(pol_coefhi[0]-pol_coeflo[0]) > IS_MAX_INTER_DIF ||
            medianerrlo> IS_MAX_MNERR ||
            medianerrhi> IS_MAX_MNERR ||
            fabs(medianerrlo-medianerrhi) >IS_MAX_MNERR_DIF ||
            fabs(slope-fitslope) > IS_MAX_FIT_EDGE_DIF ||
            slope/fitslope<0.5 ||
            slope/fitslope>2.0) ramp_present = 0 ;
    else ramp_present = 1 ;

    cpl_free(pol_coeflo) ;
    cpl_free(pol_coefhi) ;

    /* Correct the ramp if it is there */
    out = cpl_image_duplicate(in) ;
    pout = cpl_image_get_data_float(out) ;
    if (ramp_present == 1) {
        int j;
        float val;
        for (j=0 ; j<ny/2 ; j++) {
            val = slope * (j-ny/2) ;
            for (i=0 ; i<nx ; i++)
                pout[i+j*nx] -= val ;
        }
        for (j=ny/2 ; j<ny ; j++) {
            val = slope * (j-ny) ;
            for (i=0 ; i<nx ; i++)
                pout[i+j*nx] -= val ;
        }

    }

    return out ;
}

/**@}*/
