/* $Id: naco_img_detlin.c,v 1.41 2009-01-29 08:54:58 llundin Exp $
 *
 * This file is part of the NACO Pipeline
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02111-1307  USA
 */

/*
 * $Author: llundin $
 * $Date: 2009-01-29 08:54:58 $
 * $Revision: 1.41 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include "naco_recipe.h"

/*-----------------------------------------------------------------------------
                                Define
 -----------------------------------------------------------------------------*/

#define RECIPE_STRING "naco_img_detlin"

#define DETLIN_LIMIT        0.75 
#define DETLIN_STABILITY    1/100.0

#define DETLIN_MAX_GRAD 1.5  /* Maximum gradient */
#define DETLIN_MAX_RESI 10.0 /* Maximum residual */

/*-----------------------------------------------------------------------------
                            Private Functions prototypes
 -----------------------------------------------------------------------------*/

static cpl_error_code naco_img_detlin_load(cpl_imagelist *, cpl_vector *,
                                           const cpl_parameterlist *,
                                           const irplib_framelist *,
                                           const irplib_framelist *);

static cpl_image * naco_img_detlin_limit(const cpl_imagelist *);
static cpl_image * naco_img_detlin_bpm(const cpl_imagelist *,
                                       const cpl_vector *,
                                       const cpl_image *);

static cpl_error_code naco_img_detlin_dit_diff(cpl_vector *);

static cpl_error_code naco_img_detlin_save(cpl_frameset *,
                                           const cpl_parameterlist *,
                                           const cpl_propertylist *,
                                           const cpl_imagelist *,
                                           const cpl_image *,
                                           const cpl_image *);

static cpl_imagelist * naco_imagelist_fit_polynomial(const cpl_imagelist *,
                                                     const double *, int);

NACO_RECIPE_DEFINE(naco_img_detlin,
                   NACO_PARAM_FORCE,
                   "Detector linearity recipe",
                   RECIPE_STRING " -- NACO imaging detector linearity recipe.\n"
                   "The Set Of Frames (sof-file) must specify at least three "
                   "pairs of files which must be tagged\n"
                   "NACO-raw-file.fits " NACO_IMG_DETLIN_LAMP " and\n"
                   "NACO-raw-file.fits " NACO_IMG_DETLIN_DARK "\n");

/*----------------------------------------------------------------------------*/
/**
 * @defgroup naco_img_detlin   Detector Linearity
 */
/*----------------------------------------------------------------------------*/

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

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the command line options and execute the data reduction
  @param    framelist   the frames list
  @param    parlist     the parameters list
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static int naco_img_detlin(cpl_frameset            * framelist,
                           const cpl_parameterlist * parlist)
{
    irplib_framelist * allframes  = NULL;
    irplib_framelist * darkframes = NULL;
    irplib_framelist * lampframes = NULL;
    cpl_propertylist * qclist     = cpl_propertylist_new();
    const cpl_propertylist * reflist;
    cpl_imagelist    * iset = cpl_imagelist_new();
    cpl_image        * lin_limit = NULL;
    cpl_vector       * ditval = cpl_vector_new(4); /* 4 is minimum */
    cpl_imagelist    * fitres = NULL;
    cpl_image        * bpm = NULL;
    cpl_image        * residual = NULL;


    /* Identify the RAW and CALIB frames in the input frameset */
    skip_if (naco_dfs_set_groups(framelist));
 
    allframes = irplib_framelist_cast(framelist);
    skip_if(allframes == NULL);

    darkframes = irplib_framelist_extract(allframes, NACO_IMG_DETLIN_DARK);
    skip_if (darkframes == NULL);
   
    lampframes = irplib_framelist_extract(allframes, NACO_IMG_DETLIN_LAMP);
    skip_if (lampframes == NULL);

    /* Load the data and check the DIT consistency */
    cpl_msg_info(cpl_func, "Load the data");
    skip_if( irplib_framelist_load_propertylist(lampframes, 0, 0, "^("
                                                NACO_PFITS_REGEXP_DETLIN_FIRST
                                                ")$", CPL_FALSE));

    skip_if(irplib_framelist_load_propertylist_all(lampframes, 0, "^("
                                                   NACO_PFITS_REGEXP_DETLIN
                                                   ")$",
                                                   CPL_FALSE));

    skip_if(irplib_framelist_load_propertylist_all(darkframes, 0, "^("
                                                   NACO_PFITS_REGEXP_DETLIN
                                                   ")$", CPL_FALSE));

    skip_if (naco_img_detlin_load(iset, ditval, parlist, lampframes,
                                  darkframes));

    reflist = irplib_framelist_get_propertylist_const(lampframes, 0);
    bug_if (cpl_propertylist_copy_property_regexp(qclist, reflist, "^("
                                                  NACO_PFITS_REGEXP_DETLIN_COPY
                                                  ")$", 0));

    irplib_framelist_delete(lampframes);
    lampframes = NULL;
    irplib_framelist_delete(darkframes);
    darkframes = NULL;
   
    /* Compute the linearity limit */
    cpl_msg_info(cpl_func, "Compute the linearity limit");
    lin_limit = naco_img_detlin_limit(iset);
    skip_if (lin_limit == NULL);

    /* Compute the linearity coefficients */
    cpl_msg_info(cpl_func, "Compute the linearity coefficients");
#if defined USE_CPL_FIT_IMAGELIST_POLYNOMIAL
    residual = cpl_image_new(cpl_image_get_size_x(lin_limit),
                             cpl_image_get_size_y(lin_limit),
                             CPL_TYPE_FLOAT);
    bug_if(0);
    cpl_vector_dump(ditval, stderr);
    fitres = cpl_fit_imagelist_polynomial(ditval, iset, 1, 4, CPL_FALSE,
                                          CPL_TYPE_FLOAT, residual);
    skip_if (fitres == NULL);

    bug_if (cpl_imagelist_set(fitres, residual, 4));
    residual = NULL;
#else
    fitres = naco_imagelist_fit_polynomial(iset, cpl_vector_get_data(ditval), 4);
    skip_if (fitres == NULL);
#endif

    /* Compute the bad pixels map */
    cpl_msg_info(cpl_func, "Compute the bad pixels map");
    skip_if (naco_img_detlin_dit_diff(ditval));
    bpm = naco_img_detlin_bpm(iset, ditval, cpl_imagelist_get(fitres, 4));
    cpl_imagelist_delete(iset);
    iset = NULL;

    skip_if (bpm==NULL);

    /* Save the products */
    cpl_msg_info(cpl_func, "Save the products");
    skip_if (naco_img_detlin_save(framelist, parlist, qclist, fitres,
                                  lin_limit, bpm));

    end_skip;

    irplib_framelist_delete(allframes);
    irplib_framelist_delete(lampframes);
    irplib_framelist_delete(darkframes);
    cpl_image_delete(lin_limit);
    cpl_image_delete(residual);
    cpl_imagelist_delete(fitres);
    cpl_image_delete(bpm);
    cpl_imagelist_delete(iset);
    cpl_vector_delete(ditval);
    cpl_propertylist_delete(qclist);

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Load the data and check their consistency
  @param    lamplist The list of dark-subtracted images
  @param    ditval   The corresponding DIT values
  @param    parlist  The list of recipe parameters
  @param    lamps    the lamp frames
  @param    darks    the dark frames
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code naco_img_detlin_load(cpl_imagelist * lamplist,
                                           cpl_vector * ditval,
                                           const cpl_parameterlist * parlist,
                                           const irplib_framelist * lamps,
                                           const irplib_framelist * darks)
{
    cpl_image     * imlamp = NULL;
    cpl_image     * imdark = NULL;
    double          refmean, refdit;
    double          dit_min, dit_max;
    const int       nb_lamps = irplib_framelist_get_size(lamps);
    int             dit_var = 0;
    int             i;
    const cpl_boolean force = naco_parameterlist_get_bool(parlist, RECIPE_STRING,
                                                          NACO_PARAM_FORCE);


    bug_if (0);

    bug_if (lamplist == NULL);
    bug_if (darks    == NULL);
    bug_if (ditval   == NULL);

    skip_if_lt(nb_lamps, 3, "lamp images");

    /* Check that there are as many lamp as darks */
    skip_if (irplib_framelist_get_size(darks) != nb_lamps);

    /* Check out that they have consistent integration times */
    cpl_msg_info(cpl_func, "Checking DIT consistency");

    skip_if(irplib_framelist_contains(lamps, NACO_PFITS_DOUBLE_DIT,
                                      CPL_TYPE_DOUBLE, CPL_FALSE, 0.0));

    skip_if(irplib_framelist_contains(darks, NACO_PFITS_DOUBLE_DIT,
                                      CPL_TYPE_DOUBLE, CPL_FALSE, 0.0));

    /* Load the data and compute lamp-dark */
    cpl_msg_info(cpl_func, "Compute the differences lamp - dark");

    /* Check the lamp stability */
    cpl_msg_info(cpl_func, "Check the lamp stability");

    bug_if(cpl_vector_set_size(ditval, nb_lamps));
    refmean = refdit = dit_max = dit_min = 0.0; /* Avoid uninit warning */
    for (i=0 ; i < nb_lamps ; i++) {
        const char * lampname
            = cpl_frame_get_filename(irplib_framelist_get_const(lamps, i));
        const char * darkname
            = cpl_frame_get_filename(irplib_framelist_get_const(darks, i));

        /* DIT from LAMP */
        const cpl_propertylist * plist
            = irplib_framelist_get_propertylist_const(lamps, i);
        const double dit_lamp = (double)naco_pfits_get_dit(plist);
        double dit_dark;


        bug_if (0);

        /* DIT from DARK */
        plist = irplib_framelist_get_propertylist_const(darks, i);
        dit_dark = naco_pfits_get_dit(plist);
        bug_if (0);

        /* Check consistency */
        irplib_ensure(fabs(dit_dark-dit_lamp) <= 1e-3,
                      CPL_ERROR_INCOMPATIBLE_INPUT,
                      "LAMP-DIT=%g and DARK-DIT=%g differ by more than %g",
                       dit_lamp, dit_dark, 1e-3);

        irplib_check(imlamp = cpl_image_load(lampname, CPL_TYPE_FLOAT, 0, 0),
                     "Could not load FITS-image from %s", lampname);

        cpl_image_delete(imdark);
        irplib_check(imdark = cpl_image_load(darkname, CPL_TYPE_FLOAT, 0, 0),
                     "Could not load FITS-image from %s", darkname);

        bug_if (cpl_image_subtract(imlamp, imdark));

        /* Set selection */
        if (i > 0 && dit_lamp != refdit) {
            bug_if(cpl_imagelist_set(lamplist, imlamp, dit_var));
            imlamp = NULL;
            bug_if(cpl_vector_set(ditval, dit_var, dit_lamp));
            dit_var++;
            if (dit_lamp > dit_max) dit_max = dit_lamp;
            if (dit_lamp < dit_min) dit_min = dit_lamp;
        } else {
            const double mean = cpl_image_get_mean(imlamp);

            cpl_image_delete(imlamp);
            imlamp = NULL;

            bug_if(0);

            if (i == 0) {
                refmean = mean;
                dit_max = dit_min = refdit = dit_lamp;
            } else if ((mean - refmean) > refmean * (DETLIN_STABILITY)) {
                irplib_ensure(force, CPL_ERROR_INCOMPATIBLE_INPUT,
                              "Relative level difference # %d is too high:"
                               " (%g - %g)/%g > %g", i+1, mean, refmean,
                               refmean, (DETLIN_STABILITY));

                cpl_msg_warning(cpl_func, "Relative level difference # %d "
                                "is high: (%g - %g)/%g > %g", i+1, mean,
                                refmean, refmean, (DETLIN_STABILITY));
            }
        }
    }
    
    /* Check if there are enough DITs for stability check */
    skip_if_lt(i - dit_var, 2, "lamp frames for stability check");

    if (dit_min >= dit_max) {
        cpl_msg_error(cpl_func, "Too small DIT range: %g - %g = %g", dit_max,
                      dit_min, dit_max - dit_min);
        cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
        skip_if(1);
    }

    bug_if(cpl_vector_set_size(ditval, dit_var));

    end_skip;
    
    cpl_image_delete(imlamp);
    cpl_image_delete(imdark);

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the detector linearity limit
  @param    iset    the set of loaded images
  @return   the detector linearity limit image
 */
/*----------------------------------------------------------------------------*/
static cpl_image * naco_img_detlin_limit(const cpl_imagelist * iset)
{
    cpl_image       *   lin_limit = NULL;
    int                 size_x, size_y;
    float           *   plin_limit;
    const float     *   pim1;
    const float     *   pim2;
    float               ref_diff, cur_diff;
    int                 i, j;


    if (cpl_error_get_code()) return NULL;

    skip_if_lt(cpl_imagelist_get_size(iset), 3, "images to compute the "
               "detector linearity limit");

    /* Get image size */
    size_x = cpl_image_get_size_x(cpl_imagelist_get_const(iset, 0));
    size_y = cpl_image_get_size_y(cpl_imagelist_get_const(iset, 0));

    /* Create the output image */
    lin_limit = cpl_image_new(size_x, size_y, CPL_TYPE_FLOAT);

    skip_if (lin_limit == NULL);

    plin_limit = cpl_image_get_data_float(lin_limit);

    bug_if(0);

    /* Compute the limit for each pixel */
    for (i=0 ; i<size_x*size_y ; i++) {
        pim1 = cpl_image_get_data_float_const(cpl_imagelist_get_const(iset, 0));
        pim2 = cpl_image_get_data_float_const(cpl_imagelist_get_const(iset, 1));
        ref_diff = pim2[i] - pim1[i];
        for (j=1 ; j<cpl_imagelist_get_size(iset)-1 ; j++) {
            pim1 = cpl_image_get_data_float_const(
                                            cpl_imagelist_get_const(iset, j));
            pim2 = cpl_image_get_data_float_const(
                                            cpl_imagelist_get_const(iset, j+1));
            cur_diff = pim2[i] - pim1[i];
            if (cur_diff < DETLIN_LIMIT*ref_diff) {
                /* The limit is reached */
                plin_limit[i] = pim1[i];
                break;
            }
            /* The limit is undefined */
            if (j+1 == cpl_imagelist_get_size(iset)-1) 
                plin_limit[i] = (float)0.0;
        }
    }

    end_skip;

    return lin_limit;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the DIT differences
  @param    self  The DIT values
  @return   0 iff everything is ok
  @note Element 0 is unchanged, the rest contain the n-1 differences
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code naco_img_detlin_dit_diff(cpl_vector * self)
{

    double * dits;
    int i;

    bug_if(0);
    bug_if(self == NULL);

    dits = cpl_vector_get_data(self);

    for (i = cpl_vector_get_size(self)-1; i > 0 ; i--) {
        dits[i] -= dits[i-1];

        if (dits[i] == 0.0) break;

    }

    if (i > 0) {
        cpl_msg_error(cpl_func, "Consecutive (%d and %d) DITS are equal: "
                      "%g", i-1, i, dits[i-1]);

        cpl_error_set(cpl_func, CPL_ERROR_ILLEGAL_INPUT);
        skip_if(1);
    }

    end_skip;

    return cpl_error_get_code();

}


/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the bad pixels map
  @param    iset    the set of loaded images
  @param    ditdiff the DIT differences, starting at element 1
  @param    err_im  the fit error image
  @return   the bad pixels map
 */
/*----------------------------------------------------------------------------*/
static cpl_image * naco_img_detlin_bpm(const cpl_imagelist * iset,
                                       const cpl_vector    * ditdiff,
                                       const cpl_image     * err_im)
{
    cpl_image    * imdiff = NULL;
    cpl_image    * bpm = NULL;
    int          * pbpm;
    const double * perr_im;
    const float  * pim1;
    const float  * pim2;
    float        * pimdiff;
    const double * ditval = cpl_vector_get_data_const(ditdiff);
    float          dit_ref, curr_val;
    int            size_x, size_y;
    int            ii, i, j, k;


    bug_if(0);
    skip_if_lt(cpl_imagelist_get_size(iset), 3, "images to compute the "
               "bad pixel map");

    /* Get size */
    size_x = cpl_image_get_size_x(cpl_imagelist_get_const(iset, 0));
    size_y = cpl_image_get_size_y(cpl_imagelist_get_const(iset, 0));
    bug_if(0);

    /* Create the output image */
    /*  - with all pixels zero-valued - i.e. they are assumed to be good */
    bpm = cpl_image_new(size_x, size_y, CPL_TYPE_INT);
    bug_if(0);
    pbpm = cpl_image_get_data_int(bpm);

    imdiff = cpl_image_new(size_x, 1, CPL_TYPE_FLOAT);
    bug_if(0);
    pimdiff = cpl_image_get_data_float(imdiff);

    /* Compute the bad pixel value for each pixel */
    dit_ref = (float)ditval[1];
    perr_im = cpl_image_get_data_double_const(err_im);
    
    for (ii = 0, j=0 ; j < size_y ; j++) {
        pim1 = cpl_image_get_data_float_const(cpl_imagelist_get_const(iset, 1));
        pim2 = cpl_image_get_data_float_const(cpl_imagelist_get_const(iset, 0));

        for (i=0 ; i < size_x ; i++, ii++) {
            const float fdiff = pim1[ii] - pim2[ii];

            pimdiff[i] = fdiff;

            /* Second test */
            if (fdiff * dit_ref < 0.0) pbpm[ii] |= 2;

            /* Third test */
            if (perr_im[ii] > DETLIN_MAX_RESI) pbpm[ii] |= 4;
        }

        for (k=1 ; k < cpl_imagelist_get_size(iset)-1 ; k++) {
            const float dit_diff = ditval[k+1] * DETLIN_MAX_GRAD;

            pim2 = pim1;
            pim1 = cpl_image_get_data_float_const(
                                            cpl_imagelist_get_const(iset, k+1));

            ii -= size_x;
            for (i=0 ; i < size_x ; i++, ii++) {
                if ((pbpm[ii] & 1) == 0) {

                    const float fdiff = pimdiff[i];

                    curr_val = pim1[ii] - pim2[ii];
                    if (dit_diff * dit_ref < 0.0
                        ? (curr_val *  dit_ref < fdiff *  dit_diff)
                        : (curr_val *  dit_ref > fdiff *  dit_diff)) {
                        pbpm[ii] |= 1;
                    }
                }
            }
        }
    }

    end_skip;

    cpl_image_delete(imdiff);

    return bpm;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Save the different products of the recipe
  @param    set         the input frame set
  @param    parlist     the input list of parameters
  @param    qclist      List of QC parameters
  @param    fitres      the 4 fit coefficients images and the fit quality
  @param    lin_limit   the linearity limit image
  @param    bpm         the bad pixels map
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code naco_img_detlin_save(cpl_frameset            * set,
                                           const cpl_parameterlist * parlist,
                                           const cpl_propertylist  * qclist,
                                           const cpl_imagelist     * fitres,
                                           const cpl_image         * lin_limit,
                                           const cpl_image         * bpm)
{

    bug_if(0);

    /* Write the _A FITS FILE */
    skip_if (irplib_dfs_save_image(set, parlist, set,
                               cpl_imagelist_get_const(fitres, 0),
                               CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                               NACO_IMG_DETLIN_A, qclist, NULL,
                               naco_pipe_id,
                               RECIPE_STRING "_A" CPL_DFS_FITS));

    /* Write the _B FITS FILE */
    skip_if (irplib_dfs_save_image(set, parlist, set,
                               cpl_imagelist_get_const(fitres, 1),
                               CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                               NACO_IMG_DETLIN_B, qclist, NULL, naco_pipe_id,
                               RECIPE_STRING "_B" CPL_DFS_FITS));

    /* Write the _C FITS FILE */
    skip_if (irplib_dfs_save_image(set, parlist, set,
                               cpl_imagelist_get_const(fitres, 2),
                               CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                               NACO_IMG_DETLIN_C, qclist, NULL, naco_pipe_id,
                               RECIPE_STRING "_C" CPL_DFS_FITS));

    /* Write the _D FITS FILE */
    skip_if (irplib_dfs_save_image(set, parlist, set,
                               cpl_imagelist_get_const(fitres, 3),
                               CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                               NACO_IMG_DETLIN_D, qclist, NULL, naco_pipe_id,
                               RECIPE_STRING "_D" CPL_DFS_FITS));

    /* Write the _Q FITS FILE */
    skip_if (irplib_dfs_save_image(set, parlist, set,
                               cpl_imagelist_get_const(fitres, 4),
                               CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                               NACO_IMG_DETLIN_Q, qclist, NULL, naco_pipe_id,
                               RECIPE_STRING "_Q" CPL_DFS_FITS));

    /* Write the _limit FITS FILE */
    skip_if (irplib_dfs_save_image(set, parlist, set, lin_limit, CPL_BPP_IEEE_FLOAT,
                               RECIPE_STRING, NACO_IMG_DETLIN_LIM, qclist, NULL,
                               naco_pipe_id,
                               RECIPE_STRING "_limit" CPL_DFS_FITS));

    /* Write the _bpm FITS FILE */
    skip_if (irplib_dfs_save_image(set, parlist, set, bpm, CPL_BPP_8_UNSIGNED,
                               RECIPE_STRING, NACO_IMG_DETLIN_BPM, qclist, NULL,
                               naco_pipe_id,
                               RECIPE_STRING "_bpm" CPL_DFS_FITS));

    end_skip;

    return cpl_error_get_code();
}



/*----------------------------------------------------------------------------*/
/**
  @brief  Compute the non linearity of the detector
  @param  ilist   The list of exposures (images obtained with different DITs)
  @param  dit     The DIT values used
  @param  deg     The polynomial degree for the fit
  @note   dit _must_ point to (at least) as many doubles as there are images
          in the list, otherwise the behaviour of the function is undefined.
  @note   The created imagelist must be deallocated with cpl_imagelist_delete().
  @see cpl_imagelist_fit_polynomial 
  @return The image list with the fit coefficients or NULL on error.
  
  Image list can be CPL_TYPE_FLOAT or CPL_TYPE_DOUBLE.
  
  For each pixel, a polynomial representing the relation DIT=P(flux) is
  computed where:
  P(x) = a1*x + ... + an*x^n
  The returned image list contains deg+1 images.
  The deg first images contain the the deg first coefficients of the polynomial
  and the last image contains the error of the fit.
  
  Possible #_cpl_error_code_ set in this function:
  - CPL_ERROR_NULL_INPUT if (one of) the input pointer(s) is NULL
  - CPL_ERROR_ILLEGAL_INPUT if deg is not strictly positive
  - CPL_ERROR_TYPE_MISMATCH if the passed image list type is not supported
  - CPL_ERROR_INCOMPATIBLE_INPUT if deg exceeds the number of input images
  - CPL_ERROR_SINGULAR_MATRIX if there are no pixels with enough distinct
    values, in this case a retry with a reduced deg may work.
 */
/*----------------------------------------------------------------------------*/
static cpl_imagelist * naco_imagelist_fit_polynomial(
        const cpl_imagelist *   ilist,
        const double        *   dit,
        int                     deg)
{
    cpl_imagelist   * fitres;
    const cpl_image * image;
    int               ni, nx, ny;

    /* Check entries */
    cpl_ensure(ilist != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(dit   != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(deg > 0,       CPL_ERROR_ILLEGAL_INPUT, NULL);

    ni = cpl_imagelist_get_size(ilist);
    cpl_ensure(ni >= deg,     CPL_ERROR_INCOMPATIBLE_INPUT, NULL);

    image = cpl_imagelist_get_const(ilist, 0);

    cpl_ensure(cpl_image_get_type(image) == CPL_TYPE_FLOAT,
               CPL_ERROR_TYPE_MISMATCH, NULL);

    nx = cpl_image_get_size_x(image);
    ny = cpl_image_get_size_y(image);

    do {

        double       f, f_prod;
        double     * pimad;
        const float* pi;
        cpl_matrix * mv;
        cpl_matrix * mb;
        cpl_matrix * mx;
        double     * pv;
        const int    npix = nx * ny;
        int          nsing = 0;
        int          i, j, k;
        cpl_errorstate prestate = cpl_errorstate_get();


        /* Allocate deg+1 images to store the results */
        fitres = cpl_imagelist_new();
        for (i=0; i < deg+1; i++) {
            cpl_imagelist_set(fitres, cpl_image_new(nx, ny, CPL_TYPE_DOUBLE),
                              i);
        }

        /* Create matrices */
        pv = cpl_malloc(ni * deg * sizeof(double));
        mv = cpl_matrix_wrap(ni, deg, pv);

        /* Fill RHS matrix
           - mb is _not_ modified */
        mb = cpl_matrix_wrap(ni, 1, (double*)dit);

        /* Loop on pixels */
        for (i=0; i < npix; i++) {
            /* Fill Vandermonde matrix */
            for (j=0; j < ni; j++) {
                pi = cpl_image_get_data_const(cpl_imagelist_get_const(ilist,j));
                f_prod = f = (double)pi[i];
                pv[deg * j] = f;
                for (k=1; k < deg; k++) {
                    f_prod *= f;
                    pv[deg * j + k] = f_prod;
                }
            }

            /* Solve least-squares */
            /* FIXME: See cpl_polynomial_fit_1d_create() for how to
               reduce the solve time */
            mx = cpl_matrix_solve_normal(mv, mb);

            if (mx == NULL) {
                cpl_errorstate_set(prestate);
                for (k=0; k < deg+1; k++) {
                    pimad = 
                        cpl_image_get_data_double(cpl_imagelist_get(fitres,k));
                    pimad[i] = 0.0;
                }
                nsing++;
            } else {
                double err, sq_err;
                const double * px = cpl_matrix_get_data(mx);

                /* Store results */
                for (k=0; k < deg; k++) {
                    pimad = cpl_image_get_data_double(
                            cpl_imagelist_get(fitres, k));
                    pimad[i] = px[k];
                }

                /* Goodness of fit */
                sq_err=0.0;
                for (j=0; j < ni; j++) {
                    /* Error between model and reality */
                    err = dit[j];
                    /* Computed by model */
                    for (k=0; k < deg; k++)
                        err -= px[k] * pv[deg * j + k];
                    sq_err += err * err;
                }
                sq_err /= (double)ni;
                pimad=cpl_image_get_data_double(cpl_imagelist_get(fitres, deg));
                pimad[i] = sq_err;
                cpl_matrix_delete(mx);
            }
        }

        cpl_free(pv); /* Free explicitly - to avoid cppcheck false positive */
        (void)cpl_matrix_unwrap(mv);
        (void)cpl_matrix_unwrap(mb);

        if (nsing == npix) {
            cpl_imagelist_delete(fitres);
            cpl_ensure(0, CPL_ERROR_SINGULAR_MATRIX, NULL);
        }


    } while (0);


    return fitres;
}
