/*
 * This file is part of the KMOS Library
 * Copyright (C) 2007-2008 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  02110-1301 USA
 */

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

#include "kmo_priv_stats.h"
#include "kmo_utils.h"
#include "kmo_debug.h"

/**
    @defgroup kmo_priv_stats_test   kmo_priv_stats unit tests

    @{
*/

void kmo_test_verbose_off();
void kmo_test_verbose_on();

/**
    @brief   Test for kmo_calc_stats_cube()
 */
void test_kmo_calc_stats_cube(void)
{
    float           tol     = 0.01;
    int             i       = 0,
                    j       = 0,
                    dummy   = 0;
    float           *pmask  = NULL;
    kmclipm_vector  *out    = NULL;
    cpl_imagelist   *data = NULL;
    cpl_image       *img    = NULL;

    kmo_test_verbose_off();

    cpl_test_null(out = kmo_calc_stats_cube(NULL, NULL, 0.0, 0.0, 0));
    cpl_error_reset();

    data = cpl_imagelist_new();
    img = cpl_image_new(10, 10, CPL_TYPE_FLOAT);
    cpl_image_fill_gaussian (img, 5, 5, 13, 3, 3);
    cpl_imagelist_set(data, img, 0);

    img = cpl_image_new(10, 10, CPL_TYPE_FLOAT);
    cpl_image_fill_gaussian (img, 4, 4, 7, 2, 2);
    cpl_imagelist_set(data, img, 1);

    img = cpl_image_new(10, 10, CPL_TYPE_FLOAT);
    cpl_image_fill_gaussian (img, 6, 6, 20, 7, 7);
    cpl_imagelist_set(data, img, 2);

    img = cpl_image_new(10, 10, CPL_TYPE_FLOAT);
    cpl_image_fill_gaussian (img, 6, 4, 50, 10, 10);
    cpl_imagelist_set(data, img, 3);

    /* rejection thresholds too small but iter == 0,
       so no rejection is performed */
    cpl_test_nonnull(out = kmo_calc_stats_cube(data, NULL, 0.0, 0.0, 0));
    cpl_test_abs(kmclipm_vector_get(out, 0, &dummy), 400, tol);
    cpl_test_abs(kmclipm_vector_get(out, 1, &dummy), 400, tol);
    cpl_test_abs(kmclipm_vector_get(out, 2, &dummy), 0.0744379, tol);
    cpl_test_abs(kmclipm_vector_get(out, 3, &dummy), 0.0504315, tol);
    cpl_test_abs(kmclipm_vector_get(out, 4, &dummy), 0.0744379, tol);
    cpl_test_abs(kmclipm_vector_get(out, 5, &dummy), 0.0504315, tol);
    cpl_test_abs(kmclipm_vector_get(out, 6, &dummy), 0.0659821, tol);
    cpl_test_abs(kmclipm_vector_get(out, 7, &dummy), 0.0633844, tol);
    cpl_test_abs(kmclipm_vector_get(out, 8, &dummy), 0.0155448, tol);
    cpl_test_abs(kmclipm_vector_get(out, 9, &dummy), 3.43772e-05, tol);
    cpl_test_abs(kmclipm_vector_get(out, 10, &dummy), 0.278521, tol);
    kmclipm_vector_delete(out);

    /* rejection thresholds too small,
       mean and stdev with rejection will be -1 */
    cpl_test_nonnull(out = kmo_calc_stats_cube(data, NULL, 0.0, 0.0, 3));
    cpl_test_abs(kmclipm_vector_get(out, 0, &dummy), 400, tol);
    cpl_test_abs(kmclipm_vector_get(out, 1, &dummy), 400, tol);
    cpl_test_abs(kmclipm_vector_get(out, 2, &dummy), 0.0744379, tol);
    cpl_test_abs(kmclipm_vector_get(out, 3, &dummy), 0.0504315, tol);
    cpl_test_abs(kmclipm_vector_get(out, 4, &dummy), 0.0744379, tol);
    cpl_test_abs(kmclipm_vector_get(out, 5, &dummy), 0.0504315, tol);
    cpl_test_abs(kmclipm_vector_get(out, 6, &dummy), 0.0659821, tol);
    cpl_test_abs(kmclipm_vector_get(out, 7, &dummy), 0.0633844, tol);
    cpl_test_abs(kmclipm_vector_get(out, 8, &dummy), 0.0155448, tol);
    cpl_test_abs(kmclipm_vector_get(out, 9, &dummy), 3.43772e-05, tol);
    cpl_test_abs(kmclipm_vector_get(out, 10, &dummy), 0.278521, tol);
    kmclipm_vector_delete(out);

    /* mask not of same size as data */
    cpl_image *mask = cpl_image_new(5, 5, CPL_TYPE_FLOAT);
    kmo_test_verbose_off();
    cpl_test_null(out = kmo_calc_stats_cube(data, mask, 1.0, 1.0, 3));
    cpl_error_reset();
    kmo_test_verbose_on();
    cpl_image_delete(mask);

    /* create striped mask, mode and noise won't converge */
    mask = cpl_image_new(10, 10, CPL_TYPE_FLOAT);
    pmask = cpl_image_get_data_float(mask);
    for (i = 0; i < 100; i++) {
        if (i % 2) {
            pmask[i] = 0.0;
        } else {
            pmask[i] = 1.0;
        }
    }

    kmo_test_verbose_off();
    cpl_test_nonnull(out = kmo_calc_stats_cube(data, mask, 1.0, 1.0, 3));
    kmo_test_verbose_on();
    cpl_test_abs(kmclipm_vector_get(out, 0, &dummy), 200, tol);
    cpl_test_abs(kmclipm_vector_get(out, 1, &dummy), 200, tol);
    cpl_test_abs(kmclipm_vector_get(out, 2, &dummy), 0.0749382, tol);
    cpl_test_abs(kmclipm_vector_get(out, 3, &dummy), 0.0499717, tol);
    cpl_test_abs(kmclipm_vector_get(out, 4, &dummy), 0.0669294, tol);
    cpl_test_abs(kmclipm_vector_get(out, 5, &dummy), 0.00693768, tol);
    cpl_test_abs(kmclipm_vector_get(out, 6, &dummy), 0.0674867, tol);
    cpl_test_abs(kmclipm_vector_get(out, 7, &dummy), 0.051289, tol);
    cpl_test_abs(kmclipm_vector_get(out, 8, &dummy), 0.0032501, tol);
    cpl_test_abs(kmclipm_vector_get(out, 9, &dummy), 0.000135945, tol);
    cpl_test_abs(kmclipm_vector_get(out, 10, &dummy), 0.245794, tol);
    kmclipm_vector_delete(out);
    cpl_image_delete(mask);

    /* create mask, with border removed */
    mask = cpl_image_new(10, 10, CPL_TYPE_FLOAT);
    pmask = cpl_image_get_data_float(mask);

    for (j = 0; j < 10; j++) {
        for (i = 0; i < 10; i++) {
            if ((i == 0) || (i == 9) || (j == 0) || (j == 9)) {
                pmask[i+j*10] = 0.0;
            } else {
                pmask[i+j*10] = 1.0;
            }
        }
    }

    kmo_test_verbose_off();
    cpl_test_nonnull(out = kmo_calc_stats_cube(data, mask, 1.0, 1.0, 3));
    kmo_test_verbose_on();
    cpl_test_abs(kmclipm_vector_get(out, 0, &dummy), 256, tol);
    cpl_test_abs(kmclipm_vector_get(out, 1, &dummy), 256, tol);
    cpl_test_abs(kmclipm_vector_get(out, 2, &dummy), 0.0886823, tol);
    cpl_test_abs(kmclipm_vector_get(out, 3, &dummy), 0.055239, tol);
    cpl_test_abs(kmclipm_vector_get(out, 4, &dummy), 0.0708096, tol);
    cpl_test_abs(kmclipm_vector_get(out, 5, &dummy), 0.00660312, tol);
    cpl_test_abs(kmclipm_vector_get(out, 6, &dummy), 0.0745695, tol);
    cpl_test_abs(kmclipm_vector_get(out, 7, &dummy), 0.0550391, tol);
    cpl_test_abs(kmclipm_vector_get(out, 8, &dummy), 0.00391701, tol);
    cpl_test_abs(kmclipm_vector_get(out, 9, &dummy), 0.000537672, tol);
    cpl_test_abs(kmclipm_vector_get(out, 10, &dummy), 0.278521, tol);

    kmclipm_vector_delete(out);
    cpl_image_delete(mask);
    cpl_imagelist_delete(data);

    kmo_test_verbose_on();
}

/**
    @brief   Test for kmo_calc_stats_img()
 */
void test_kmo_calc_stats_img(void)
{
    float           tol     = 0.01;
    int             i       = 0,
                    j       = 0,
                    dummy   = 0;
    float           *pmask  = NULL;
    kmclipm_vector  *out    = NULL;
    cpl_image       *img    = NULL;

    kmo_test_verbose_off();

    cpl_test_null(out = kmo_calc_stats_img(NULL, NULL, 0.0, 0.0, 0));
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    img = cpl_image_new(10, 10, CPL_TYPE_FLOAT);
    cpl_image_fill_gaussian (img, 5, 5, 13, 3, 3);

    /* rejection thresholds too small but iter == 0,
       so no rejection is performed */
    cpl_test_nonnull(out = kmo_calc_stats_img(img, NULL, 0.0, 0.0, 0));
    cpl_test_error(CPL_ERROR_NONE);
    cpl_test_abs(kmclipm_vector_get(out, 0, &dummy), 100, tol);
    cpl_test_abs(kmclipm_vector_get(out, 1, &dummy), 100, tol);
    cpl_test_abs(kmclipm_vector_get(out, 2, &dummy), 0.105616, tol);
    cpl_test_abs(kmclipm_vector_get(out, 3, &dummy), 0.0504315, tol);
    cpl_test_abs(kmclipm_vector_get(out, 4, &dummy), 0.105616, tol);
    cpl_test_abs(kmclipm_vector_get(out, 5, &dummy), 0.0504315, tol);
    cpl_test_abs(kmclipm_vector_get(out, 6, &dummy), 0.0894034, tol);
    cpl_test_abs(kmclipm_vector_get(out, 7, &dummy), 0.0633844, tol);
    cpl_test_abs(kmclipm_vector_get(out, 8, &dummy), 0.0395753, tol);
    cpl_test_abs(kmclipm_vector_get(out, 9, &dummy), 0.0142938, tol);
    cpl_test_abs(kmclipm_vector_get(out, 10, &dummy), 0.22989, tol);
    kmclipm_vector_delete(out);

    /* rejection thresholds too small,
       mean and stdev with rejection will be -1 */
    cpl_test_nonnull(out = kmo_calc_stats_img(img, NULL, 0.0, 0.0, 3));
    cpl_test_error(CPL_ERROR_NONE);
    cpl_test_abs(kmclipm_vector_get(out, 0, &dummy), 100, tol);
    cpl_test_abs(kmclipm_vector_get(out, 1, &dummy), 100, tol);
    cpl_test_abs(kmclipm_vector_get(out, 2, &dummy), 0.105616, tol);
    cpl_test_abs(kmclipm_vector_get(out, 3, &dummy), 0.0504315, tol);
    cpl_test_abs(kmclipm_vector_get(out, 4, &dummy), 0.105616, tol);
    cpl_test_abs(kmclipm_vector_get(out, 5, &dummy), 0.0504315, tol);
    cpl_test_abs(kmclipm_vector_get(out, 6, &dummy), 0.0894034, tol);
    cpl_test_abs(kmclipm_vector_get(out, 7, &dummy), 0.0633844, tol);
    cpl_test_abs(kmclipm_vector_get(out, 8, &dummy), 0.0395753, tol);
    cpl_test_abs(kmclipm_vector_get(out, 9, &dummy), 0.0142938, tol);
    cpl_test_abs(kmclipm_vector_get(out, 10, &dummy), 0.22989, tol);
    kmclipm_vector_delete(out);

    /* mask not of same size as data */
    cpl_image *mask = cpl_image_new(5, 5, CPL_TYPE_FLOAT);

    cpl_test_null(out = kmo_calc_stats_img(img, mask, 1.0, 1.0, 3));
    cpl_test_error(CPL_ERROR_ILLEGAL_INPUT);
    cpl_image_delete(mask);

    /* create striped mask, mode and noise won't converge */
    mask = cpl_image_new(10, 10, CPL_TYPE_FLOAT);
    pmask = cpl_image_get_data_float(mask);
    for (i = 0; i < 100; i++) {
        if (i % 2) {
            pmask[i] = 0.0;
        } else {
            pmask[i] = 1.0;
        }
    }

    cpl_test_nonnull(out = kmo_calc_stats_img(img, mask, 1.0, 1.0, 3));
    cpl_test_error(CPL_ERROR_NONE);

    cpl_test_abs(kmclipm_vector_get(out, 0, &dummy), 50, tol);
    cpl_test_abs(kmclipm_vector_get(out, 1, &dummy), 50, tol);
    cpl_test_abs(kmclipm_vector_get(out, 2, &dummy), 0.106697, tol);
    cpl_test_abs(kmclipm_vector_get(out, 3, &dummy), 0.0499717, tol);
    cpl_test_abs(kmclipm_vector_get(out, 4, &dummy), 0.0838177, tol);
    cpl_test_abs(kmclipm_vector_get(out, 5, &dummy), 0.00693768, tol);
    cpl_test_abs(kmclipm_vector_get(out, 6, &dummy), 0.0919571, tol);
    cpl_test_abs(kmclipm_vector_get(out, 7, &dummy), 0.051289, tol);
    cpl_test_abs(kmclipm_vector_get(out, 8, &dummy), 0.0032501, tol);
    cpl_test_abs(kmclipm_vector_get(out, 9, &dummy), 0.0235665, tol);
    cpl_test_abs(kmclipm_vector_get(out, 10, &dummy), 0.22989, tol);
    kmclipm_vector_delete(out);
    cpl_image_delete(mask);

    /* create mask, with border removed */
    mask = cpl_image_new(10, 10, CPL_TYPE_FLOAT);
    pmask = cpl_image_get_data_float(mask);

    for (j = 0; j < 10; j++) {
        for (i = 0; i < 10; i++) {
            if ((i == 0) || (i == 9) || (j == 0) || (j == 9)) {
                pmask[i+j*10] = 0.0;
            } else {
                pmask[i+j*10] = 1.0;
            }
        }
    }

    cpl_test_null(out = kmo_calc_stats_img(img, img, 1.0, 1.0, 3));
    cpl_test_error(CPL_ERROR_ILLEGAL_INPUT);

    kmclipm_vector_delete(out);
    cpl_image_delete(mask);
    cpl_image_delete(img);

    cpl_test_error(CPL_ERROR_NONE);

    kmo_test_verbose_on();
}

/**
    @brief   Test for kmo_calc_stats_vec()
 */
void test_kmo_calc_stats_vec(void)
{
    float           tol     = 0.01;
    int             dummy   = 0;
    kmclipm_vector  *in     = NULL,
                    *mask   = NULL;
    kmclipm_vector  *out    = NULL;

    kmo_test_verbose_off();

    cpl_test_null(out = kmo_calc_stats_vec(NULL, NULL, 0.0, 0.0, 0));
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    in = kmclipm_vector_new(5);
    kmclipm_vector_set(in, 0, 1.1);
    kmclipm_vector_set(in, 1, 0.1);
    kmclipm_vector_set(in, 2, 1.9);
    kmclipm_vector_set(in, 3, 0.3);
    kmclipm_vector_set(in, 4, 0.01);

    // rejection thresholds too small but iter == 0,
    // so no rejection is performed
    cpl_test_nonnull(out = kmo_calc_stats_vec(in, NULL, 0.0, 0.0, 0));
    cpl_test_error(CPL_ERROR_NONE);
    cpl_test_abs(kmclipm_vector_get(out, 0, &dummy), 5, tol);
    cpl_test_abs(kmclipm_vector_get(out, 1, &dummy), 5, tol);
    cpl_test_abs(kmclipm_vector_get(out, 2, &dummy), 0.682, tol);
    cpl_test_abs(kmclipm_vector_get(out, 3, &dummy), 0.805369, tol);
    cpl_test_abs(kmclipm_vector_get(out, 4, &dummy), 0.682, tol);
    cpl_test_abs(kmclipm_vector_get(out, 5, &dummy), 0.805369, tol);
    cpl_test_abs(kmclipm_vector_get(out, 6, &dummy), 0.3, tol);
    cpl_test_abs(kmclipm_vector_get(out, 7, &dummy), 0.955, tol);
    cpl_test_abs(kmclipm_vector_get(out, 8, &dummy), 0.553856, tol);
    cpl_test_abs(kmclipm_vector_get(out, 9, &dummy), 0.0142938, tol);
    cpl_test_abs(kmclipm_vector_get(out, 10, &dummy), 1.9, tol);
    kmclipm_vector_delete(out);

    // rejection thresholds too small,
    // mean and stdev with rejection will be -1
    cpl_test_nonnull(out = kmo_calc_stats_vec(in, NULL, 0.0, 0.0, 3));
    cpl_test_error(CPL_ERROR_NONE);
    cpl_test_abs(kmclipm_vector_get(out, 0, &dummy), 5, tol);
    cpl_test_abs(kmclipm_vector_get(out, 1, &dummy), 5, tol);
    cpl_test_abs(kmclipm_vector_get(out, 2, &dummy), 0.682, tol);
    cpl_test_abs(kmclipm_vector_get(out, 3, &dummy), 0.805369, tol);
    cpl_test_abs(kmclipm_vector_get(out, 4, &dummy), 0.682, tol);
    cpl_test_abs(kmclipm_vector_get(out, 5, &dummy), 0.805369, tol);
    cpl_test_abs(kmclipm_vector_get(out, 6, &dummy), 0.3, tol);
    cpl_test_abs(kmclipm_vector_get(out, 7, &dummy), 0.955, tol);
    cpl_test_abs(kmclipm_vector_get(out, 8, &dummy), 0.553856, tol);
    cpl_test_abs(kmclipm_vector_get(out, 9, &dummy), 0.0142938, tol);
    cpl_test_abs(kmclipm_vector_get(out, 10, &dummy), 1.9, tol);
    kmclipm_vector_delete(out);

    // data contains NaN
    kmclipm_vector_set(in, 3, 0./0.);
    kmclipm_vector_set(in, 4, 0./0.);
    cpl_test_nonnull(out = kmo_calc_stats_vec(in, NULL, 0.0, 0.0, 0));
    cpl_test_error(CPL_ERROR_NONE);
    cpl_test_abs(kmclipm_vector_get(out, 0, &dummy), 5, tol);
    cpl_test_abs(kmclipm_vector_get(out, 1, &dummy), 3, tol);
    cpl_test_abs(kmclipm_vector_get(out, 2, &dummy), 1.03333, tol);
    kmclipm_vector_delete(out);

    // with mask wrong size
    mask = kmclipm_vector_new(4);
    cpl_test_null(out = kmo_calc_stats_vec(in, mask, 0.0, 0.0, 0));
    cpl_test_error(CPL_ERROR_ILLEGAL_INPUT);
    kmclipm_vector_delete(mask);

    // with mask
    mask = kmclipm_vector_new(5);
    kmclipm_vector_set(mask, 0, 0./0.);
    kmclipm_vector_set(mask, 1, 0);
    kmclipm_vector_set(mask, 2, 1);
    kmclipm_vector_set(mask, 3, 0);
    kmclipm_vector_set(mask, 4, 1);

    cpl_test_nonnull(out = kmo_calc_stats_vec(in, mask, 0.0, 0.0, 0));

    cpl_test_error(CPL_ERROR_NONE);
    cpl_test_abs(kmclipm_vector_get(out, 0, &dummy), 2, tol);
    cpl_test_abs(kmclipm_vector_get(out, 1, &dummy), 1, tol);
    cpl_test_abs(kmclipm_vector_get(out, 2, &dummy), 1.9, tol);
    cpl_test_eq(kmclipm_vector_is_rejected(out, 7), 1);
    cpl_test_eq(kmclipm_vector_is_rejected(out, 8), 1);
    kmclipm_vector_delete(out);

    kmclipm_vector_delete(in);
    kmclipm_vector_delete(mask);
    cpl_test_error(CPL_ERROR_NONE);
    kmo_test_verbose_on();
}

/**
    @brief   Test for kmo_count_masked_pixels()
 */
void test_kmo_count_masked_pixels(void)
{
    cpl_image       *img        = NULL;


    /* test kmo_count_masked_pixels */
    cpl_test_eq(0, kmo_count_masked_pixels(NULL));

    float val1[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
    img = cpl_image_wrap_float (2, 3, val1);
    cpl_test_eq(0, kmo_count_masked_pixels(img));
    cpl_image_unwrap(img);

    float val2[6] = {0.0, 0.0, 3.0, 0.0, 5.0, 0.0};
    img = cpl_image_wrap_float (2, 3, val2);
    cpl_test_eq(4, kmo_count_masked_pixels(img));
    cpl_image_unwrap(img);
}

/**
    @brief   Test for kmo_vector_identify_infinite()
 */
void test_kmo_vector_identify_infinite(void)
{
    float           tol         = 0.01;
    cpl_vector      *vec1       = NULL,
                    *vec2       = NULL,
                    *inf_vec    = NULL;

    /* no infinite values*/
    double val1[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};   // -inf
    vec1 = cpl_vector_wrap(6, val1);

    /* infinite values*/
    double val4[6] = {1.0, 2.0, 3.0, 4.0, 5.0, -6.0/0.0};   // -inf
    vec2 = cpl_vector_wrap(6, val4);

    /* test kmo_vector_identify_infinite */
    inf_vec = kmo_vector_identify_infinite(vec1);
    cpl_test_abs(cpl_vector_get_mean(inf_vec), 0.0, tol);
    cpl_vector_delete(inf_vec); inf_vec = NULL;

    inf_vec = kmo_vector_identify_infinite(vec2);
    cpl_test_abs(cpl_vector_get_mean(inf_vec), 0.166667, tol);
    cpl_vector_delete(inf_vec); inf_vec = NULL;

    cpl_vector_unwrap(vec1);
    cpl_vector_unwrap(vec2);
}

/**
    @brief   Test for kmo_imagelist_to_vector()
 */
void test_kmo_imagelist_to_vector(void)
{
    float           tol         = 0.01;
    kmclipm_vector  *vec        = NULL;

    cpl_image       *img        = NULL,
                    *mask       = NULL;

    cpl_imagelist   *cube1      = cpl_imagelist_new(),
                    *cube2      = cpl_imagelist_new();

    int             nr_masked   = 0;

    /* no infinite values*/
    float val1[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};   // -inf
    float val2[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};    // nan
    float val3[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};    // inf

    img = cpl_image_wrap_float (2, 3, val1);
    cpl_imagelist_set(cube1, cpl_image_duplicate(img), 0);
    cpl_image_unwrap(img);

    img = cpl_image_wrap_float (2, 3, val2);
    cpl_imagelist_set(cube1, cpl_image_duplicate(img), 1);
    cpl_image_unwrap(img);

    img = cpl_image_wrap_float (2, 3, val3);
    cpl_imagelist_set(cube1, cpl_image_duplicate(img), 2);
    cpl_image_unwrap(img);

    /* infinite values*/
     float val4[6] = {1.0, 2.0, 3.0, 4.0, 5.0, -6.0/0.0};   // -inf
     float val5[6] = {1.0, 0.0/0.0, 3.0, 4.0, 5.0, 6.0};    // nan
     float val6[6] = {1.0, 2.0/0.0, 3.0/0.0, 4.0, 5.0, 6.0};    // inf

    img = cpl_image_wrap_float (2, 3, val4);
    cpl_imagelist_set(cube2, cpl_image_duplicate(img), 0);
    cpl_image_unwrap(img);

    img = cpl_image_wrap_float (2, 3, val5);
    cpl_imagelist_set(cube2, cpl_image_duplicate(img), 1);
    cpl_image_unwrap(img);

    img = cpl_image_wrap_float (2, 3, val6);
    cpl_imagelist_set(cube2, cpl_image_duplicate(img), 2);
    cpl_image_unwrap(img);

    /* mask */
    float val7[6] = {0.0, 2.0, 0.0, 0.0, 0.0, 0.0};
    mask = cpl_image_wrap_float (2, 3, val7);

    /* test kmo_imagelist_to_vector */
    cpl_test_nonnull(vec = kmo_imagelist_to_vector(cube1, NULL, &nr_masked));
    cpl_test_eq(0, kmclipm_vector_count_rejected(vec));
    cpl_test_abs(kmclipm_vector_get_mean(vec), 3.5, tol);
    cpl_test_eq(0, nr_masked);
    cpl_test_eq(18, kmclipm_vector_get_size(vec));
    kmclipm_vector_delete(vec); vec = NULL;

    cpl_test_nonnull(vec = kmo_imagelist_to_vector(cube2, NULL, &nr_masked));
    cpl_test_eq(4, kmclipm_vector_count_rejected(vec));
    cpl_test_abs(kmclipm_vector_get_mean(vec), 3.57143, tol);
    cpl_test_eq(0, nr_masked);
    cpl_test_eq(18, kmclipm_vector_get_size(vec));
    cpl_test_eq(14, kmclipm_vector_count_non_rejected(vec));
    kmclipm_vector_delete(vec); vec = NULL;

    cpl_test_nonnull(vec = kmo_imagelist_to_vector(cube1, mask, &nr_masked));
    cpl_test_eq(0, kmclipm_vector_count_rejected(vec));
    cpl_test_abs(kmclipm_vector_get_mean(vec), 2.0, tol);
    cpl_test_eq(5, nr_masked);
    cpl_test_eq(3, kmclipm_vector_get_size(vec));
    kmclipm_vector_delete(vec); vec = NULL;

    cpl_test_nonnull(vec = kmo_imagelist_to_vector(cube2, mask, &nr_masked));
    cpl_test_eq(2, kmclipm_vector_count_rejected(vec));
    cpl_test_abs(kmclipm_vector_get_mean(vec), 2.0, tol);
    cpl_test_eq(5, nr_masked);
    cpl_test_eq(3, kmclipm_vector_get_size(vec));
    cpl_test_eq(1, kmclipm_vector_count_non_rejected(vec));
    kmclipm_vector_delete(vec); vec = NULL;

    cpl_imagelist_delete(cube1);
    cpl_imagelist_delete(cube2);
    cpl_image_unwrap(mask);
}

/**
    @brief   Test for kmo_image_to_vector()
 */
void test_kmo_image_to_vector(void)
{
    cpl_image       *img1 = NULL,
                    *img2 = NULL,
                    *mask = NULL;
    kmclipm_vector  *vec  = NULL;
    int             nr_masked = 0;
    double          tol     = 0.0;

    /* no infinite values*/
    float val1[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};   // -inf
    img1 = cpl_image_wrap_float(2, 3, val1);

    /* infinite values*/
    float val2[6] = {1.0, 2.0, 3.0, 4.0, 5.0, -6.0/0.0};   // -inf
    img2 = cpl_image_wrap_float(2, 3, val2);

    /* mask */
    float val7[6] = {0.0, 2.0, 0.0, 0.0, 0.0, 0.0};
    mask = cpl_image_wrap_float(2, 3, val7);

    cpl_test_nonnull(vec = kmo_image_to_vector(img1, NULL, &nr_masked));
    cpl_test_eq(0, kmclipm_vector_count_rejected(vec));
    cpl_test_abs(kmclipm_vector_get_mean(vec), 3.5, tol);
    cpl_test_eq(6, kmclipm_vector_get_size(vec));
    cpl_test_eq(0, nr_masked);
    kmclipm_vector_delete(vec);

    cpl_test_nonnull(vec = kmo_image_to_vector(img1, mask, &nr_masked));
    cpl_test_eq(0, kmclipm_vector_count_rejected(vec));
    cpl_test_abs(kmclipm_vector_get_mean(vec), 2.0, tol);
    cpl_test_eq(1, kmclipm_vector_get_size(vec));
    cpl_test_eq(5, nr_masked);
    kmclipm_vector_delete(vec);

    cpl_test_nonnull(vec = kmo_image_to_vector(img2, NULL, &nr_masked));
    cpl_test_eq(1, kmclipm_vector_count_rejected(vec));
    cpl_test_abs(kmclipm_vector_get_mean(vec), 3.0, tol);
    cpl_test_eq(6, kmclipm_vector_get_size(vec));
    cpl_test_eq(5, kmclipm_vector_count_non_rejected(vec));
    cpl_test_eq(0, nr_masked);
    kmclipm_vector_delete(vec);

    cpl_test_nonnull(vec = kmo_image_to_vector(img2, mask, &nr_masked));
    cpl_test_eq(0, kmclipm_vector_count_rejected(vec));
    cpl_test_abs(kmclipm_vector_get_mean(vec), 2.0, tol);
    cpl_test_eq(1, kmclipm_vector_get_size(vec));
    cpl_test_eq(1, kmclipm_vector_count_non_rejected(vec));
    cpl_test_eq(5, nr_masked);
    kmclipm_vector_delete(vec);

    cpl_image_unwrap(img1);
    cpl_image_unwrap(img2);
    cpl_image_unwrap(mask);
}

/**
    @brief   Test for kmo_calc_mode()
 */
void test_kmo_calc_mode(void)
{
    float          tol          = 0.01;
    double         mode         = -1.0,
                   noise        = -1.0;
    kmclipm_vector *data_in_vec = NULL;
    int            nr_mask_pix  = 0;

    cpl_imagelist *data = cpl_imagelist_new();
    cpl_image *img = NULL;

    img = cpl_image_new(10, 10, CPL_TYPE_FLOAT);
    cpl_image_fill_gaussian (img, 5, 5, 13, 3, 3);
    cpl_imagelist_set(data, img, 0);

    img = cpl_image_new(10, 10, CPL_TYPE_FLOAT);
    cpl_image_fill_gaussian (img, 4, 4, 7, 2, 2);
    cpl_imagelist_set(data, img, 1);

    img = cpl_image_new(10, 10, CPL_TYPE_FLOAT);
    cpl_image_fill_gaussian (img, 6, 6, 20, 7, 7);
    cpl_imagelist_set(data, img, 2);

    img = cpl_image_new(10, 10, CPL_TYPE_FLOAT);
    cpl_image_fill_gaussian (img, 6, 4, 50, 10, 10);
    cpl_imagelist_set(data, img, 3);

    kmo_test_verbose_off();
    cpl_test_eq(CPL_ERROR_NULL_INPUT, kmo_calc_mode(data_in_vec, &mode, &noise,
                                                    3, 3, 3));
    cpl_error_reset();
    kmo_test_verbose_on();

    data_in_vec = kmo_imagelist_to_vector(data, NULL, &nr_mask_pix);

    cpl_test_eq(CPL_ERROR_NONE, kmo_calc_mode(data_in_vec, &mode, &noise, 3, 3, 3));

    cpl_test_abs(mode, 0.0633844, tol);
    cpl_test_abs(noise, 0.0155448, tol);

    kmclipm_vector_delete(data_in_vec);
    cpl_imagelist_delete(data);

    /* test all the same values */
    data_in_vec = kmclipm_vector_new(108);
    kmclipm_vector_fill(data_in_vec, 1.123456);
    kmclipm_vector_set(data_in_vec, 10, 1e6);
    kmclipm_vector_set(data_in_vec, 11, 1e6);
    kmclipm_vector_set(data_in_vec, 12, 1e6);
    kmclipm_vector_set(data_in_vec, 100, 1e3);
    kmclipm_vector_set(data_in_vec, 101, 1e3);
    kmclipm_vector_set(data_in_vec, 102, 1e3);

    mode = 0;
    noise = 0;
    cpl_test_eq(CPL_ERROR_NONE, kmo_calc_mode(data_in_vec, &mode, &noise, 3, 3, 3));
    cpl_test_abs(mode, 0., tol);
    cpl_test_abs(noise, 0., tol);

    kmclipm_vector_delete(data_in_vec);
}

/**
    @brief   Test for kmo_reject_sigma()
 */
void test_kmo_reject_sigma(void)
{
    float       tol     = 0.01;
    double      sigma   = 1.0;
    cpl_vector  *out    = NULL,
                *index  = NULL;

    cpl_vector *d = cpl_vector_new(5);
    cpl_vector_set(d, 0, 1.0);
    cpl_vector_set(d, 1, 111.1);
    cpl_vector_set(d, 2, 1.9);
    cpl_vector_set(d, 3, 5.0);
    cpl_vector_set(d, 4, 1.1);

    kmo_test_verbose_off();
    cpl_test_null(out = kmo_reject_sigma(NULL, 0.0, 0.0, 0.0, 0.0, NULL));
    cpl_error_reset();
    kmo_test_verbose_on();

    cpl_test_null(out = kmo_reject_sigma(d, 0.0, 0.0, 0.0, 0.0, NULL));
    cpl_test_null(out = kmo_reject_sigma(d, 0.0, 0.0, 0.0, 0.0, &index));
    cpl_test_eq(cpl_vector_get_size(d), cpl_vector_get_size(index));
    cpl_test_abs(cpl_vector_get_mean(index), 0.0, tol);
    cpl_vector_delete(index);

    cpl_test_nonnull(
        out = kmo_reject_sigma(d, cpl_vector_get_mean(d),
                               sigma, sigma, cpl_vector_get_stdev(d),
                               NULL));
    cpl_test_eq(4, cpl_vector_get_size(out));
    cpl_test_abs(cpl_vector_get_mean(out), 2.25, tol);
    cpl_vector_delete(out);

    cpl_test_nonnull(
        out = kmo_reject_sigma(d, cpl_vector_get_mean(d),
                               sigma, sigma, cpl_vector_get_stdev(d),
                               &index));
    cpl_test_eq(4, cpl_vector_get_size(out));
    cpl_test_abs(cpl_vector_get_mean(out), 2.25, tol);
    cpl_test_eq(5, cpl_vector_get_size(index));
    cpl_test_abs(cpl_vector_get_mean(index), 0.8, tol);

    cpl_vector_delete(d);
    cpl_vector_delete(out);
    cpl_vector_delete(index);
}

/**
    @brief   Test for kmo_calc_remaining()
 */
void test_kmo_calc_remaining(void)
{
    kmclipm_vector  *vec1   = NULL,
                    *out    = NULL;
    double          tol     = 0.01;

    /* no infinite values*/
    vec1 = kmclipm_vector_new(6);
    kmclipm_vector_set(vec1, 0, 1.0);
    kmclipm_vector_set(vec1, 1, 2.0);
    kmclipm_vector_set(vec1, 2, 3.0);
    kmclipm_vector_set(vec1, 3, 4.0);
    kmclipm_vector_set(vec1, 4, 5.0);
    kmclipm_vector_set(vec1, 5, 6.0);
    out = kmclipm_vector_new(11);
    kmclipm_vector_fill(out, 0);

    kmo_test_verbose_off();

    kmo_calc_remaining(vec1, out, 0.0, 0.0, 0, 6);
    cpl_test_error(CPL_ERROR_NONE);
    cpl_test_abs(kmclipm_vector_get_mean(out), 2.38244, tol);

    kmo_calc_remaining(vec1, out, 3.0, 3.0, 3, 6);
    cpl_test_error(CPL_ERROR_NONE);
    cpl_test_abs(kmclipm_vector_get_mean(out), 2.38244, tol);

    kmo_test_verbose_on();

    kmclipm_vector_delete(out);
    kmclipm_vector_delete(vec1);
}

/**
    @brief   Test of helper functions for kmo_stats
*/
int main(void)
{
    cpl_test_init("<usd-help@eso.org>", CPL_MSG_WARNING);

    test_kmo_calc_stats_cube();
    test_kmo_calc_stats_img();
    test_kmo_calc_stats_vec();
    test_kmo_count_masked_pixels();
    test_kmo_vector_identify_infinite();
    test_kmo_imagelist_to_vector();
    test_kmo_image_to_vector();
    test_kmo_calc_mode();
    test_kmo_reject_sigma();
    test_kmo_calc_remaining();

    return cpl_test_end(0);
}

/** @} */
