/* $Id: kmo_cpl_extensions.c,v 1.8 2013-06-17 07:52:26 aagudo Exp $
 *
 * This file is part of the KMOS 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * $Author: aagudo $
 * $Date: 2013-06-17 07:52:26 $
 * $Revision: 1.8 $
 * $Name: not supported by cvs2svn $
 */

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

/*-----------------------------------------------------------------------------
 *                                 Includes
 *----------------------------------------------------------------------------*/
#include <math.h>
#include <cpl.h>

#include "kmclipm_math.h"
#include "kmclipm_vector.h"

#include "kmo_cpl_extensions.h"
#include "kmo_priv_stats.h"
#include "kmo_error.h"
#include "kmo_constants.h"
#include "kmo_utils.h"

/*----------------------------------------------------------------------------*/
/**
    @defgroup kmos_cpl_extensions     Missing or modified CPL-functions.

    @{
 */
/*----------------------------------------------------------------------------*/

/**
    @brief
        Computes maximum pixel value and position over a vector.

    @param vec Input vector.
    @param x   Pointer to the coordinate of the maximum position.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c vec is NULL.

    @deprecated Use kmclipm_vector instead.
*/
cpl_error_code kmo_vector_get_maxpos_old(const cpl_vector *vec, int *x)
{
    int             j       = 0;

    const double    *pvec   = NULL;

    double          val     = 0.0;

    cpl_error_code  ret_error   = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE(vec != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        KMO_TRY_EXIT_IF_NULL(
            pvec = cpl_vector_get_data_const(vec));

        for (j = 0; j < cpl_vector_get_size(vec); j++) {
            if (val < pvec[j]) {
                val = pvec[j];
                *x = j;
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Computes minimum pixel value and position over a vector.

    @param vec Input vector.
    @param x   Pointer to the coordinate of the minimum position.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c vec is NULL.

    @deprecated Use kmclipm_vector instead.
*/
cpl_error_code kmo_vector_get_minpos_old(const cpl_vector *vec, int *x)
{
    int         j       = 0;

    const double    *pvec   = NULL;

    double          val     = DBL_MAX;

    cpl_error_code  ret_error   = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE(vec != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        KMO_TRY_EXIT_IF_NULL(
            pvec = cpl_vector_get_data_const(vec));

        for (j = 0; j < cpl_vector_get_size(vec); j++) {
            if (val > pvec[j]) {
                val = pvec[j];
                *x = j;
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Calculates the histogram of a vector.

    This implementation follows the one used in IDL. This means that in the
    highest bin can only be found values corresponding to the maximum value in
    the input vector.

    @param d        Input vector.
    @param nbins    The number of bins to produce.

    @return
        A vector of size @c nbins containing the histogram of input data @c d .

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c d is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c nbis is <= 0.

*/
cpl_vector* kmo_vector_histogram_old(const cpl_vector *d, int nbins)
{
    int             j       = 0,
                    pos     = 0;

    const double    *pd   = NULL;

    double          *ph     = NULL,
                    binwidth    = 0.0,
                    hmin        = 0.0,
                    hmax        = 0.0;

    cpl_vector      *h      = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(d != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        KMO_TRY_ASSURE(nbins > 0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Nr. of bins must be positive!");

        hmin = cpl_vector_get_min(d);
        hmax = cpl_vector_get_max(d);
        KMO_TRY_CHECK_ERROR_STATE();

        binwidth = (hmax - hmin) / (nbins - 1);
        if (fabs(binwidth) < 0.00001) {
            binwidth = 1;
        }

        KMO_TRY_EXIT_IF_NULL(
            pd = cpl_vector_get_data_const(d));

        KMO_TRY_EXIT_IF_NULL(
            h = cpl_vector_new(nbins));

        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_fill(h, 0.0));

        KMO_TRY_EXIT_IF_NULL(
            ph = cpl_vector_get_data(h));

        for (j = 0; j < cpl_vector_get_size(d); j++) {
            pos = floor((pd[j] - hmin) / binwidth);
            ph[pos]++;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(h); h = NULL;
    }

    return h;
}

/**
  @brief    Get the maximum and its position of the cpl_vector

  @param    v    const cpl_vector
  @param    pos  The position of the vector

  @return   the maximum value of the vector or undefined on error
*/
double kmo_vector_get_max_old(const cpl_vector *v, int *pos)
{
    double          max = 0.0;
    const double    *pv = NULL;
    int             i   = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE((v != NULL) &&
                       (pos != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_EXIT_IF_NULL(
            pv = cpl_vector_get_data_const(v));

        *pos = -1;
        max = pv[0];
        for (i=1; i < cpl_vector_get_size(v); i++) {
            if (pv[i] > max) {
                max = pv[i];
                *pos = i;
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        max = -1.0;
        *pos = -1;
    }

    return max;
}

/**
    @brief
        Flip the values of a vector.

    @param data    The vector.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    The vector is flipped elementwise from both sides.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c data is NULL.
*/
cpl_error_code kmo_vector_flip_old(cpl_vector* data)
{
    cpl_error_code  ret_error  = CPL_ERROR_NONE;

    double          *pdata      = NULL,
                    tmp_dbl     = 0;

    int             i           = 0,
                    half_size   = 0,
                    size        = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(data != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_vector_get_data(data));

        size = cpl_vector_get_size(data);
        half_size = floor(size / 2);

        for (i = 0; i < half_size;i++) {
            tmp_dbl = pdata[i];
            pdata[i] = pdata[size-1-i];
            pdata[size-1-i] = tmp_dbl;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Calculate the mean of a vector.

    NaN, +/-Inf values are ignored.

    @param vec   The vector.

    @return
        The function returns the mean of the vector on success or 0.0
        otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c vec is NULL.
*/
double kmo_vector_get_mean_old(const cpl_vector *vec)
{
    double          ret_val     = 0.0;

    const double    *pvec       = NULL;

    int             nr_valid    = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(vec != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        KMO_TRY_EXIT_IF_NULL(
            pvec = cpl_vector_get_data_const(vec));

        int i = 0;
        for (i = 0; i < cpl_vector_get_size(vec); i++) {
            if (kmclipm_is_nan_or_inf(pvec[i]) == FALSE) {
                ret_val += pvec[i];
                nr_valid++;
            }
        }

        ret_val = ret_val / nr_valid;
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        ret_val = 0.0;
    }

    return ret_val;
}

/**
    @brief
        Calculates the histogram of an image.

    @param d        Input image.
    @param nbins    The number of bins to produce.

    @return
        A vector of size @c nbins containing the histogram of input data @c d .

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c d is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c nbis is <= 0.

*/
cpl_vector* kmo_image_histogram(const cpl_image *d, int nbins)
{
    int             j           = 0,
                    pos         = 0;

    const float     *pd         = NULL;

    double          *ph         = NULL,
                    binwidth    = 0.0,
                    hmin        = 0.0,
                    hmax        = 0.0;

    cpl_vector      *h          = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(d != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        KMO_TRY_ASSURE(nbins > 0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Nr. of bins must be positive!");

        hmin = cpl_image_get_min(d);
        hmax = cpl_image_get_max(d);
        KMO_TRY_CHECK_ERROR_STATE();

        binwidth = (hmax - hmin) / (nbins - 1);

        KMO_TRY_EXIT_IF_NULL(
            pd = cpl_image_get_data_float_const(d));

        KMO_TRY_EXIT_IF_NULL(
            h = cpl_vector_new(nbins));

        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_fill(h, 0.0));

        KMO_TRY_EXIT_IF_NULL(
            ph = cpl_vector_get_data(h));

        for (j = 0; j < cpl_image_get_size_x(d) * cpl_image_get_size_y(d); j++)
        {
            pos = floor((pd[j] - hmin) / binwidth);
            ph[pos]++;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(h); h = NULL;
    }

    return h;
}

#define KMO_FLOAT_SWAP(a,b) { register float t=(a);(a)=(b);(b)=t; }

/**
  @brief    Sort a float array

  @param    pix_arr     the array to sort
  @param    n           the array size

  @return   the #_cpl_error_code_ or CPL_ERROR_NONE

  Possible #_cpl_error_code_ set in this function:
  - CPL_ERROR_NULL_INPUT
  - CPL_ERROR_ILLEGAL_INPUT
*/
cpl_error_code kmo_sort_float(float *pix_arr, int n)
{
    int         i, ir, j, k, l, CPL_PIX_STACK_SIZE=50;
    int         i_stack[CPL_PIX_STACK_SIZE] ;
    int         j_stack ;
    float      a ;

    /* Check entries */
    cpl_ensure(pix_arr, CPL_ERROR_NULL_INPUT,
            CPL_ERROR_NULL_INPUT) ;

    ir = n ;
    l = 1 ;
    j_stack = 0 ;
    for (;;) {
        if (ir-l < 7) {
            for (j=l+1 ; j<=ir ; j++) {
                a = pix_arr[j-1];
                for (i=j-1 ; i>=1 ; i--) {
                    if (pix_arr[i-1] <= a) break;
                    pix_arr[i] = pix_arr[i-1];
                }
                pix_arr[i] = a;
            }
            if (j_stack == 0) break;
            ir = i_stack[j_stack-- -1];
            l  = i_stack[j_stack-- -1];
        } else {
            k = (l+ir) >> 1;
            KMO_FLOAT_SWAP(pix_arr[k-1], pix_arr[l])
            if (pix_arr[l] > pix_arr[ir-1]) {
                KMO_FLOAT_SWAP(pix_arr[l], pix_arr[ir-1])
            }
            if (pix_arr[l-1] > pix_arr[ir-1]) {
                KMO_FLOAT_SWAP(pix_arr[l-1], pix_arr[ir-1])
            }
            if (pix_arr[l] > pix_arr[l-1]) {
                KMO_FLOAT_SWAP(pix_arr[l], pix_arr[l-1])
            }
            i = l+1;
            j = ir;
            a = pix_arr[l-1];
            for (;;) {
                do i++; while (pix_arr[i-1] < a);
                do j--; while (pix_arr[j-1] > a);
                if (j < i) break;
                KMO_FLOAT_SWAP(pix_arr[i-1], pix_arr[j-1]);
            }
            pix_arr[l-1] = pix_arr[j-1];
            pix_arr[j-1] = a;
            j_stack += 2;
            if (j_stack > CPL_PIX_STACK_SIZE) {
                /* Should never reach here */
                return CPL_ERROR_ILLEGAL_INPUT ;
            }
            if (ir-i+1 >= j-l) {
                i_stack[j_stack-1] = ir;
                i_stack[j_stack-2] = i;
                ir = j-1;
            } else {
                i_stack[j_stack-1] = j-1;
                i_stack[j_stack-2] = l;
                l = i;
            }
        }
    }
    return CPL_ERROR_NONE ;
}

#undef KMO_FLOAT_SWAP

#define KMO_DOUBLE_SWAP(a,b) { register double t=(a);(a)=(b);(b)=t; }

/**
  @brief    Sort a double array

  @param    pix_arr     the array to sort
  @param    n           the array size

  @return   the #_cpl_error_code_ or CPL_ERROR_NONE

  Possible #_cpl_error_code_ set in this function:
  - CPL_ERROR_NULL_INPUT
  - CPL_ERROR_ILLEGAL_INPUT
*/
cpl_error_code kmo_sort_double(double *pix_arr, int n)
{
    int         i, ir, j, k, l, CPL_PIX_STACK_SIZE=50;
    int         i_stack[CPL_PIX_STACK_SIZE] ;
    int         j_stack ;
    double      a;

    /* Check entries */
    cpl_ensure(pix_arr, CPL_ERROR_NULL_INPUT,
            CPL_ERROR_NULL_INPUT) ;

    ir = n ;
    l = 1 ;
    j_stack = 0 ;
    for (;;) {
        if (ir-l < 7) {
            for (j=l+1 ; j<=ir ; j++) {
                a = pix_arr[j-1];
                for (i=j-1 ; i>=1 ; i--) {
                    if (pix_arr[i-1] <= a) break;
                    pix_arr[i] = pix_arr[i-1];
                }
                pix_arr[i] = a;
            }
            if (j_stack == 0) break;
            ir = i_stack[j_stack-- -1];
            l  = i_stack[j_stack-- -1];
        } else {
            k = (l+ir) >> 1;
            KMO_DOUBLE_SWAP(pix_arr[k-1], pix_arr[l])
            if (pix_arr[l] > pix_arr[ir-1]) {
                KMO_DOUBLE_SWAP(pix_arr[l], pix_arr[ir-1])
            }
            if (pix_arr[l-1] > pix_arr[ir-1]) {
                KMO_DOUBLE_SWAP(pix_arr[l-1], pix_arr[ir-1])
            }
            if (pix_arr[l] > pix_arr[l-1]) {
                KMO_DOUBLE_SWAP(pix_arr[l], pix_arr[l-1])
            }
            i = l+1;
            j = ir;
            a = pix_arr[l-1];
            for (;;) {
                do i++; while (pix_arr[i-1] < a);
                do j--; while (pix_arr[j-1] > a);
                if (j < i) break;
                KMO_DOUBLE_SWAP(pix_arr[i-1], pix_arr[j-1]);
            }
            pix_arr[l-1] = pix_arr[j-1];
            pix_arr[j-1] = a;
            j_stack += 2;
            if (j_stack > CPL_PIX_STACK_SIZE) {
                /* Should never reach here */
                return CPL_ERROR_ILLEGAL_INPUT ;
            }
            if (ir-i+1 >= j-l) {
                i_stack[j_stack-1] = ir;
                i_stack[j_stack-2] = i;
                ir = j-1;
            } else {
                i_stack[j_stack-1] = j-1;
                i_stack[j_stack-2] = l;
                l = i;
            }
        }
    }
    return CPL_ERROR_NONE ;
}

#undef KMO_DOUBLE_SWAP

/**
    @brief
        Sorts an image and returns it as a vector (omitting rejected values).

    @param data        Input image.

    @return
        A sorted vector.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c d is NULL.
*/
cpl_vector* kmo_image_sort(const cpl_image *data)
{
    int             size        = 0,
                    nx          = 0,
                    ny          = 0,
                    cnt         = 0;

    cpl_vector      *vec        = NULL;

    double          *pvec       = NULL;

    const float     *pdata      = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(data != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        // calculate size of output vector without rejected values
        nx = cpl_image_get_size_x(data);
        ny = cpl_image_get_size_y(data);

        size = nx*ny - cpl_image_count_rejected(data);
        KMO_TRY_CHECK_ERROR_STATE();

        // create vector and fill it with non-rejected values
        KMO_TRY_EXIT_IF_NULL(
            vec = cpl_vector_new(size));
        KMO_TRY_EXIT_IF_NULL(
            pvec = cpl_vector_get_data(vec));
        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_image_get_data_float_const(data));

        int iy = 0, ix = 0;
        for (iy = 0; iy < ny; iy++) {
            for (ix = 0; ix < nx; ix++) {
                if (!cpl_image_is_rejected(data, ix+1, iy+1)) {
                    pvec[cnt++] = pdata[ix+iy*nx];
                }
            }
        }

        // sort now
        cpl_vector_sort(vec, CPL_SORT_ASCENDING);
        KMO_TRY_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(vec); vec = NULL;
    }

    return vec;
}

/**
    @brief
        Fill an image with a given value.

    @param img   The image to initialise.
    @param value   The value to initialise with.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c img is NULL.
*/
cpl_error_code kmo_image_fill(cpl_image *img, double value)
{
    float          *pimg       = NULL;

    int             i           = 0;

    cpl_error_code  ret_error   = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE(img != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_EXIT_IF_NULL(
            pimg = cpl_image_get_data_float(img));

        for (i = 0;
             i < cpl_image_get_size_x(img) * cpl_image_get_size_y(img);
             i++) {
            pimg[i] = value;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Computes the sum of pixel values over an image ignoring NaN values.

    @param image   The image.

    @return
        The flux value.

    NaN, Inf, -Inf values are ignored.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c image is NULL. In this case 0.0 is
                                returned.
*/
double kmo_image_get_flux(const cpl_image *img) {
    int             nx      = 0,
                    ny      = 0,
                    i       = 0,
                    j       = 0;

    double          flux    = 0.0;

    const float     *pimg   = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(img != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        nx = cpl_image_get_size_x(img);
        ny = cpl_image_get_size_y(img);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_NULL(
            pimg = cpl_image_get_data_float_const(img));

        for (j = 0; j < ny; j++) {
            for (i = 0; i < nx; i++) {
                if (kmclipm_is_nan_or_inf(pimg[i+j*nx]) == FALSE) {
                    flux += pimg[i+j*nx];
                }
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        flux = 0.0;
    }

    return flux;
}

/**
    @brief
        Set the bad pixels in an image as defined in a map-image.

    @param img	The input image
    @param map	The mask defining the bad pixels

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if any input is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if the image and the map have different sizes
*/
cpl_error_code kmo_image_reject_from_mask(cpl_image *img, const cpl_image *map)
{
    int             nx      = 0,
                    ny      = 0;

    const float     *pmap  = NULL;

    cpl_error_code  ret_err = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE((img != NULL) &&
                       (map != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        nx = cpl_image_get_size_x(img);
        ny = cpl_image_get_size_y(img);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((nx == cpl_image_get_size_x(map)) &&
                       (ny == cpl_image_get_size_y(map)),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "img and map don't have the same dimensions!");

        KMO_TRY_EXIT_IF_NULL(
            pmap = cpl_image_get_data_float_const(map));

        int iy = 0, ix = 0;
        for (iy = 0; iy < ny; iy++) {
            for (ix = 0; ix < nx; ix++) {
                if (pmap[ix+iy*nx] < 0.5) {
                    // bad pixel should be 0, good pixel 1
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_image_reject(img, ix+1, iy+1));
                }
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_err = cpl_error_get_code();
    }

    return ret_err;
}

/**
    @brief
        Count the number of rejected pixels in an image.

    @param img	The input image

    @return
        The number of rejected pixels.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if any input is NULL.
*/
int kmo_image_get_rejected(const cpl_image *img)
{
    int             rej     = 0,
                    nx      = 0,
                    ny      = 0;
    cpl_size        iy      = 0,
                    ix      = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(img != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        nx = cpl_image_get_size_x(img);
        ny = cpl_image_get_size_y(img);
        KMO_TRY_CHECK_ERROR_STATE();

        for (iy = 1; iy <= ny; iy++) {
            for (ix = 1; ix <= nx; ix++) {
                if (cpl_image_is_rejected(img, ix, iy)) {
                    rej++;
                }
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        rej = -1;
    }

    return rej;
}

/**
    @brief
        Elementwise raising to a power of the @c imglist with a scalar.

    @param imglist   Imagelist to be modified in place.
    @param exponent  Scalar exponent.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c imglist is NULL.
*/
cpl_error_code kmo_imagelist_power(cpl_imagelist *imglist, double exponent)
{
    cpl_image   *img    = NULL;
    int         j       = 0;
    cpl_error_code  ret_error   = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE(imglist != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        for (j = 0; j < cpl_imagelist_get_size(imglist); j++) {
            KMO_TRY_EXIT_IF_NULL(
                img = cpl_imagelist_get(imglist, j));

            KMO_TRY_EXIT_IF_ERROR(
                kmo_image_power(img, exponent));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Calculate the mean of an imagelist.

    NaN, +/-Inf values are ignored.

    @param cube   The imagelist.

    @return
        The function returns the mean of the imagelist on success or 0.0
        otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c cube is NULL.
*/
double kmo_imagelist_get_mean(const cpl_imagelist *cube)
{
    int             size    = 0;

    const cpl_image *img    = NULL;

    double          ret_val = 0.0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(cube != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        size = cpl_imagelist_get_size(cube);

        KMO_TRY_EXIT_IF_NULL(
            img = cpl_imagelist_get_const(cube, 0));


        ret_val = kmo_imagelist_get_flux(cube);

        ret_val /= size*cpl_image_get_size_x(img)*cpl_image_get_size_y(img);
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        ret_val = 0.0;
    }

    return ret_val;
}

/**
    @brief
        Computes the sum of pixel values over an imagelist.

    @param cube   The imagelist.

    @return
        The flux value.

    NaN, Inf, -Inf values are ignored.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c cube is NULL. In this case 0.0 is returned.
*/
double kmo_imagelist_get_flux(const cpl_imagelist *cube)
{
    int             nz      = 0;
    const cpl_image *img    = NULL;
    double          flux    = 0.0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(cube != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        nz = cpl_imagelist_get_size(cube);

        int iz = 0;
        for (iz = 0; iz < nz; iz++) {
            KMO_TRY_EXIT_IF_NULL(
                img = cpl_imagelist_get_const(cube, iz));

            flux += kmo_image_get_flux(img);
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        flux = 0.0;
    }

    return flux;
}

/**
    @brief
        Computes the mode of an imagelist.

    @param cube   The imagelist.

    @return
        The mode value.

    NaN, Inf, -Inf values are ignored.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c cube is NULL. In this case 0.0 is returned.
*/
double kmo_imagelist_get_mode(const cpl_imagelist *cube,
                              double *noise)
{
    int             nx          = 0,
                    ny          = 0,
                    nr_mask_pix = 0;
    const cpl_image *img        = NULL;
    cpl_image       *mask       = NULL;
    double          mode        = 0.0;
    kmclipm_vector* datavec     = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((cube != NULL) &&
                       (noise != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        // create mask in x,y (all active, set to 1)
        KMO_TRY_EXIT_IF_NULL(
            img = cpl_imagelist_get_const(cube, 0));
        nx = cpl_image_get_size_x(img);
        ny = cpl_image_get_size_y(img);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_NULL(
            mask = cpl_image_new(nx, ny, CPL_TYPE_FLOAT));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_image_fill(mask, 1.));

        KMO_TRY_EXIT_IF_NULL(
            datavec = kmo_imagelist_to_vector(cube, mask, &nr_mask_pix));

        KMO_TRY_EXIT_IF_ERROR(
            kmo_calc_mode(datavec, &mode, noise,
                          DEF_POS_REJ_THRES, //cpos_rej,
                          DEF_NEG_REJ_THRES, //cneg_rej,
                          DEF_ITERATIONS)); //citer));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        mode = 0.0;
    }

    kmclipm_vector_delete(datavec); datavec = NULL;
    cpl_image_delete(mask); mask = NULL;

    return mode;
}

/**
    @brief
        Rotate an imagelist by a multiple of 90 degrees counterclockwise.

    @param cube   The imagelist to rotate inplace.
    @param rot    The multiple: -1 is a rotation of 90 deg counterclockwise.

    @return
        CPL_ERROR_NONE on success, otherwise the relevant cpl_error_code

    rot may be any integer value, its modulo 4 determines the rotation:
    - -3 to turn 270 degrees counterclockwise.
    - -2 to turn 180 degrees counterclockwise.
    - -1 to turn 90 degrees counterclockwise.
    - 0 to not turn
    - +1 to turn 90 degrees clockwise (same as -3)
    - +2 to turn 180 degrees clockwise (same as -2).
    - +3 to turn 270 degrees clockwise (same as -1).

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c cube is NULL. In this case 0.0 is returned.
*/
cpl_error_code      kmo_imagelist_turn(cpl_imagelist *cube, int rot) {
    int             j           = 0,
                    size        = 0;

    cpl_image       *img        = NULL;

    cpl_error_code  ret_error   = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE(cube != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        size = cpl_imagelist_get_size(cube);

        for (j = 0; j < size; j++) {
            KMO_TRY_EXIT_IF_NULL(
                img = cpl_imagelist_get(cube, j));

            cpl_image_turn(img, rot);
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Shift an imagelist by integer offsets.

    @param cube   The imagelist to shift inplace.
    @param dx 	  The shift in X
    @param dy 	  The shift in Y

    @return
        CPL_ERROR_NONE on success, otherwise the relevant cpl_error_code

    The new zones (in the result image) where no new value is computed are set
    to 0 and flagged as bad pixels. The shift values have to be valid:
    -nx < dx < nx and -ny < dy < ny

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c cube is NULL. In this case 0.0 is returned.
    @li CPL_ERROR_ILLEGAL_INPUT if the requested shift is bigger than the
    imagelist size

*/
cpl_error_code      kmo_imagelist_shift(cpl_imagelist *cube, int dx, int dy) {
    int             j           = 0,
                    size        = 0;

    cpl_image       *img        = NULL;

    cpl_error_code  ret_error   = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE(cube != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        size = cpl_imagelist_get_size(cube);

        for (j = 0; j < size; j++) {
            KMO_TRY_EXIT_IF_NULL(
                img = cpl_imagelist_get(cube, j));

            KMO_TRY_EXIT_IF_ERROR(
                cpl_image_shift(img, dx, dy));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Fill an array with a given value.

    @param arr     The array to initialise.
    @param value   The value to initialise with.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c arr is NULL.
*/
cpl_error_code kmo_array_fill_int(cpl_array *arr, int value)
{
    cpl_error_code  ret_error   = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE(arr != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        int i = 0;
        for (i = 0; i < cpl_array_get_size(arr); i++) {
            cpl_array_set_int(arr, i, value);
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Divide two vectors element-wise.

    @param vec1   First cpl_vector.
    @param vec2   Second cpl_vector.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Unlike cpl_vector_divide(), this function allows division by zero. In this
    case inf, nan or -inf is generated (and not just 0 like in cpl_vector())

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c vec1 or @c vec2 is NULL.
    @li CPL_ERROR_INCOMPATIBLE_INPUT if @c vec1 and @c vec2 have different sizes
*/
cpl_error_code kmo_vector_divide(cpl_vector *vec1, cpl_vector *vec2)
{
    double          *dd1        = NULL,
                    *dd2        = NULL;
    int             size        = 0,
                    i           = 0;
    cpl_error_code  ret_error   = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE((vec1 != NULL) && (vec2 != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        size = cpl_vector_get_size(vec1);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE(size == cpl_vector_get_size(vec2),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "First and second vector don't have the same size!");

        KMO_TRY_EXIT_IF_NULL(
            dd1 = cpl_vector_get_data(vec1));
        KMO_TRY_EXIT_IF_NULL(
            dd2 = cpl_vector_get_data(vec2));

        for (i = 0; i < size; i++) {
            dd1[i] /= dd2[i];
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Elementwise division of an image with a scalar

    @param img       Image to be modified in place.
    @param divisor   Number to divide with.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.
    Unlike cpl_image_divide_scalar(), this function allows division by zero. In
    this case inf, nan or -inf is generated (and not just 0 like in
    cpl_image_divide_scalar())

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c img is NULL.
    @li CPL_ERROR_TYPE_MISMATCH if the passed image type is not supported
*/
cpl_error_code kmo_image_divide_scalar(cpl_image *img, float divisor)
{
    float           *df         = NULL;
    int             x           = 0,
                    y           = 0,
                    i           = 0;
    cpl_error_code  ret_error   = CPL_ERROR_NONE;
/* not supported yet    double  *dd = NULL;
    int     *di = NULL;
*/

    KMO_TRY
    {
        KMO_TRY_ASSURE(img != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        x = cpl_image_get_size_x(img);
        y = cpl_image_get_size_y(img);
        KMO_TRY_CHECK_ERROR_STATE();

        switch (cpl_image_get_type(img)) {
            case CPL_TYPE_FLOAT:
                KMO_TRY_EXIT_IF_NULL(
                    df = (float*)cpl_image_get_data(img));

                for (i = 0; i < x * y; i++) {
                    df[i] /= divisor;
                }
                break;
// not supported yet            case CPL_TYPE_DOUBLE:
//                dd = (double*)cpl_image_get_data(img);
//                for (i = 0; i < x*y; i++) {
//                    dd[i] /= divisor;
//                }
//                break;
//            case CPL_TYPE_INT:
//                printf(">>> int\n");
//                break;
            default:
                KMO_TRY_SET_ERROR(CPL_ERROR_TYPE_MISMATCH);
        }
        KMO_TRY_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Divide two images, store the result in the first image

    @param img1       Image to be modified in place.
    @param img2      Image to divide with.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Unlike cpl_image_divide(), this function allows division by zero. In
    this case inf, nan or -inf is generated (and not just 0 like in
    cpl_image_divide())

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c img1 or @c img2 is NULL.
    @li CPL_ERROR_TYPE_MISMATCH if the passed image type is not supported
*/
cpl_error_code kmo_image_divide(cpl_image *img1, const cpl_image *img2)
{
    float           *df1        = NULL,
                    *df2        = NULL;
    int             x1          = 0,
                    y1          = 0,
                    x2          = 0,
                    y2          = 0,
                    i           = 0;
    cpl_error_code  ret_error   = CPL_ERROR_NONE;
/* not supported yet    double  *dd1 = NULL, double  *dd2 = NULL;
    int     *di = NULL;
*/

    KMO_TRY
    {
        KMO_TRY_ASSURE((img1 != NULL) && (img2 != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        x1 = cpl_image_get_size_x(img1);
        y1 = cpl_image_get_size_y(img1);
        x2 = cpl_image_get_size_x(img2);
        y2 = cpl_image_get_size_y(img2);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((x1 == x2) && (y1 == y2),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "First and second image don't have the same size!");

        KMO_TRY_ASSURE(cpl_image_get_type(img1) == cpl_image_get_type(img2),
                       CPL_ERROR_TYPE_MISMATCH,
                       "First and second image don't have the same type!");

        switch (cpl_image_get_type(img1)) {
            case CPL_TYPE_FLOAT:
                KMO_TRY_EXIT_IF_NULL(
                    df1 = (float*)cpl_image_get_data(img1));
                KMO_TRY_EXIT_IF_NULL(
                    df2 = (float*)cpl_image_get_data_const(img2));

                for (i = 0; i < x1 * y1; i++) {
                    df1[i] /= df2[i];
                }
                break;
// not supported yet            case CPL_TYPE_DOUBLE:
//                dd1 = (double*)cpl_image_get_data(img1);
//                dd2 = (double*)cpl_image_get_data(img2);
//                for (i = 0; i < x1 * y1; i++) {
//                    dd1[i] /= dd2[i];
//                }
//                break;
//            case CPL_TYPE_INT:
//                printf(">>> int\n");
//                break;
            default:
                KMO_TRY_SET_ERROR(CPL_ERROR_TYPE_MISMATCH);
        }
        KMO_TRY_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Compute the elementwise power of the image.

    @param img      Image to be modified in place.
    @param scalar   Exponent.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Unlike cpl_image_power(), this function returns always an image filled with
    ones when the exponent is zero.

    With a zero image and an exponent of zero cpl_image_power() returns
    a zero image, which is definitely wrong!
    a^0 = 1, so for a = 0
    And for exponents < 0 values of 0 are returned ?!?

    (Strange enough: Sometimes the error "division by zero" occured when the
    image contained a zero value ?!?)

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c img is NULL.
    Otherwise the same as cpl_image_power() generates.
*/
cpl_error_code kmo_image_power(cpl_image *img,
                               double exponent)
{
    cpl_error_code  ret_error   = CPL_ERROR_NONE;
    cpl_size        iy          = 0,
                    ix          = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(img != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");
        if (exponent < 0) {
            /* do it manually (0^-1 is NOT 0!!)*/
            float *pimg = cpl_image_get_data_float(img);
            cpl_size xs = cpl_image_get_size_x(img),
                     ys = cpl_image_get_size_y(img);
            for (iy = 0; iy < ys; iy++) {
                for (ix = 0; ix < xs; ix++) {
                    pimg[ix+iy*xs] = powf(pimg[ix+iy*xs], exponent);
                    if (kmclipm_is_nan_or_inf(pimg[ix+iy*xs])) {
                        cpl_image_reject(img, ix+1, iy+1);
                    }
                }
            }
        }
        else if (exponent == 0.0) {
            /* create image filled with ones */
            KMO_TRY_EXIT_IF_ERROR(
                cpl_image_multiply_scalar(img, 0.0));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_image_add_scalar(img, 1.0));
        } else {
            /* use default cpl-function */
            KMO_TRY_EXIT_IF_ERROR(
                cpl_image_power(img, exponent));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Elementwise division of each image in the imlist with a scalar.

    @param imglist   Imagelist to be modified in place.
    @param divisor   Number to divide with.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Unlike cpl_imagelist_divide_scalar(), this function allows division by
    zero. In this case inf, nan or -inf is generated (and not just 0 like in
    cpl_imagelist_divide_scalar())

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c imglist is NULL.
*/
cpl_error_code kmo_imagelist_divide_scalar(cpl_imagelist *imglist,
                                           double divisor)
{
    cpl_image       *img        = NULL;
    int             j           = 0;
    cpl_error_code  ret_error   = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE(imglist != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        for (j = 0; j < cpl_imagelist_get_size(imglist); j++) {
            KMO_TRY_EXIT_IF_NULL(
                img = cpl_imagelist_get(imglist, j));
            KMO_TRY_EXIT_IF_ERROR(
                kmo_image_divide_scalar(img, divisor));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
       Divide two image lists, the first one is replaced by the result.

    @param imglist      Image to be modified in place.
    @param divisor      Image to divide with.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Unlike cpl_imagelist_divide(), this function allows division by zero. In
    this case inf, nan or -inf is generated (and not just 0 like in
    cpl_imagelist_divide())

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c imglist or @c divisor is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c imglist and @c divisor don't have the
                                same size.
    @li CPL_ERROR_TYPE_MISMATCH if the passed image type is not supported.
*/
cpl_error_code kmo_imagelist_divide(cpl_imagelist *imglist,
                                    cpl_imagelist *divisor)
{
    cpl_image       *img1       = NULL,
                    *img2       = NULL;
    int             j           = 0;
    cpl_error_code  ret_error   = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE((imglist != NULL) &&
                       (divisor != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        KMO_TRY_ASSURE(cpl_imagelist_get_size(imglist) ==
                                                cpl_imagelist_get_size(divisor),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Input data hasn't same size!");

        for (j = 0; j < cpl_imagelist_get_size(imglist); j++) {
            KMO_TRY_EXIT_IF_NULL(
                img1 = cpl_imagelist_get(imglist, j));

            KMO_TRY_EXIT_IF_NULL(
                img2 = cpl_imagelist_get(divisor, j));

            KMO_TRY_EXIT_IF_ERROR(
                kmo_image_divide(img1, img2));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/** @} */
