/* $Id: fors_image-test.c,v 1.20 2007-11-23 14:24:24 jmlarsen Exp $
 *
 * This file is part of the FORS Library
 * Copyright (C) 2002-2006 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
 */

/*
 * $Author: jmlarsen $
 * $Date: 2007-11-23 14:24:24 $
 * $Revision: 1.20 $
 * $Name: not supported by cvs2svn $
 */

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

#include <fors_image.h>
#include <fors_pfits.h>
#include <fors_saturation.h>
#include <test_simulate.h>
#include <test.h>

#include <fs_test.h> 

#include <fors_utils.h>

#include <math.h>
#include <string.h>

/**
 * @defgroup fors_image_test  Test of image module
 */

/**@{*/

//utility function to generate test images
static fors_image* get_test_image(double values, cpl_boolean bpm_on_img,
        cpl_boolean bpm_on_error);

static fors_image* get_test_image_simple(double values);

static cpl_boolean
check_images(fors_image* original, fors_image* twin);

static cpl_boolean
check_bpm(cpl_mask* original, cpl_mask* twin);

#undef cleanup
#define cleanup \
do { \
    fors_setting_delete(&setting); \
    delete_test_file(filename);\
} while(0)

/**
 * @brief  Test functions
 */
static void
test_image(void)
{
    const int nx = 200; /* Duplication here. Must be updated
                           in synchronization with nx and ny in ./test_simulate.c */
    const int ny = 200;
    const char *const filename = "fors_image.fits";

    cpl_frame *frame = cpl_frame_new();
    cpl_propertylist *header = cpl_propertylist_new();
    fors_setting *setting = NULL;
    cpl_image *data     = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    cpl_image *variance = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    fors_image *image;
    double saturated;

    cpl_image_add_scalar(variance, 1.0);
    cpl_image_set(data, 1, 1, 1); /* One non-saturated pixel */
    image   = fors_image_new(data, variance);
    
    /* save, load */
    {
        double exptime = 423;
        create_standard_keys(header, exptime);
    }

    fors_image_save(image, header, NULL, filename);
    fors_image_delete(&image);
    cpl_frame_set_filename(frame, filename);

    setting = fors_setting_new(frame);

    image = fors_image_load(frame);
    
    saturated = fors_saturation_img_satper(image);
    
    
    test_rel( saturated, 100 * (1 - 1.0 / (nx*ny)), 0.01 ); 
    test_eq( nx, fors_image_get_size_x(image) );
    test_eq( ny, fors_image_get_size_y(image) );

    cpl_frame_delete(frame);
    cpl_propertylist_delete(header);
    fors_image_delete(&image);


    /* Test croppping */
    data     = cpl_image_new(3, 2, FORS_IMAGE_TYPE);
    variance = cpl_image_new(3, 2, FORS_IMAGE_TYPE);

    {
        int x, y;
        for (y = 1; y <= 2; y++) {
            for (x = 1; x <= 3; x++) {
                cpl_image_set(data, x, y, x*y);
            }
        }
        /*
          Input data now:
            2 4 6
            1 2 3
        */
    }
    cpl_image_add_scalar(variance, 1.0);
    image = fors_image_new(data, variance);
    
    int xlo = 2;
    int xhi = 2;
    int ylo = 1;
    int yhi = 2;
    fors_image_crop(image, 
                    xlo, ylo, xhi, yhi);

    /*
         Should have now:
              4
              2
     */
    test_eq( fors_image_get_size_x(image), 1 );
    test_eq( fors_image_get_size_y(image), 2 );

    test_rel( fors_image_get_min(image), 2, 0.0001 );
    test_rel( fors_image_get_max(image), 4, 0.0001 );
    test_rel( fors_image_get_mean(image, NULL), 3, 0.0001 );
    test_rel( fors_image_get_stdev(image, NULL), sqrt(2), 0.0001 );

    fors_image_delete(&image);

    cleanup;
    return;
}

/**
 * @brief  Median filtering benchmark
 */
static void
test_median_filter(void)
{
    //const int nx = 4000;
    //const int ny = 2000;
    const int nx = 400;
    const int ny = 200;

    const int xradius = 1;
    const int yradius = 1;
    const int xstart = 1;
    const int ystart = 1;
    const int xend = nx;
    const int yend = ny;
    const int xstep = 1;
    const int ystep = 1;

    cpl_image *data     = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    cpl_image *variance = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    
    fors_image *image;
    cpl_image *smooth;

    cpl_image_add_scalar(variance, 1.0);

    image = fors_image_new(data, variance);
    bool use_data = true;
    smooth = fors_image_filter_median_create(image,
                                             xradius, yradius,
                                             xstart, ystart,
                                             xend, yend,
                                             xstep, ystep,
                                             use_data);
    
    cpl_image_delete(smooth);
    fors_image_delete(&image);

    return;
}

#undef cleanup
#define cleanup \
do { \
    fors_image_delete(&left); \
    fors_image_delete(&right); \
} while(0)

/**
 * @brief   Test image subtraction
 */
static void
test_subtract(void)
{
    /* Simulate data */
    const int nx = 20;
    const int ny = 30;
    const double values  = 17;
    const double variance = 5;
    cpl_image *left_data     = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    cpl_image *left_variance = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    cpl_image *right_data;
    cpl_image *right_variance;
    fors_image *left, *right;
    double error_before, error_after;

    cpl_image_add_scalar(left_data, values);
    cpl_image_add_scalar(left_variance, variance);
    
    right_data    = cpl_image_multiply_scalar_create(left_data, 0.6);
    right_variance = cpl_image_duplicate(left_variance);

    left  = fors_image_new(left_data , left_variance);
    right = fors_image_new(right_data, right_variance);

    error_before = fors_image_get_error_mean(left, NULL);

    /* Call function */
    fors_image_subtract(left, right);

    /* Check results */
    error_after = fors_image_get_error_mean(left, NULL);
    test_rel( fors_image_get_mean(left, NULL), values*(1-0.6), 0.001);
    test_rel( error_after, error_before*sqrt(2.0), 0.001);
    
    cleanup;
    return;
}

#undef cleanup
#define cleanup \
do { \
    fors_image_delete(&image); \
} while(0)

/**
 * @brief   Test image exponentiation
 */

static void
test_exponential(void)
{
    const double values  = 17;
    /* Simulate data */
    fors_image *image = get_test_image_simple(values);
    double base = 10;
    double dbase = -1;


    /* Call function */
    fors_image_exponential(image, base, dbase);

    /* Check results */
    test_rel( fors_image_get_mean(image, NULL), pow(base, values), 0.001);
    
    cleanup;
    return;
}


#undef cleanup
#define cleanup \
do { \
    fors_image_delete(&image); \
} while(0)
/**
 * @brief   Test image division
 */
static void
test_divide(void)
{
    /* Simulate data */
    const int nx = 20;
    const int ny = 30;
    const double values  = 17;
    const double variance_value = 5;
    cpl_image *data     = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    cpl_image *variance = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    fors_image *image = NULL;
    double error_before, error_after;

    cpl_image_add_scalar(data    , values);
    cpl_image_add_scalar(variance, variance_value);
    
    image  = fors_image_new(data, variance);

    error_before = 
        fors_image_get_error_mean(image, NULL) /
        fors_image_get_mean      (image, NULL);

    /* Call function */
    fors_image_divide(image, image);

    /* Check results,
     * relative errors add in quadrature
     */
    error_after =
        fors_image_get_error_mean(image, NULL) /
        fors_image_get_mean      (image, NULL);
    test_rel( fors_image_get_mean(image, NULL), 1.0, 0.001 );
    test_rel( error_after, error_before*sqrt(2.0), 0.001 );

    cleanup;
    return;
}

static cpl_boolean
check_images(fors_image* original, fors_image* twin)
{
    const char* func = "check_images";
    const float* p_orig = fors_image_get_data_const(original);
    const float* p_twin = fors_image_get_data_const(twin);

    cpl_size length;


	if(p_orig == p_twin)
	{
		cpl_error_set_message(func, CPL_ERROR_UNSPECIFIED,
				"Memory is shared");
		return CPL_FALSE;
	}

	if(fors_image_get_size_x(original) != fors_image_get_size_x(twin))
	{
		cpl_error_set_message(func, CPL_ERROR_UNSPECIFIED, "X dim !=");
		return CPL_FALSE;
	}

	if(fors_image_get_size_y(original) != fors_image_get_size_y(twin))
	{
		cpl_error_set_message(func, CPL_ERROR_UNSPECIFIED, "Y dim !=");
		return CPL_FALSE;
	}

	length = fors_image_get_size_x(original) *
			fors_image_get_size_y(original);

	if(memcmp(p_orig, p_twin, length * sizeof(float)))
	{
		cpl_error_set_message(func, CPL_ERROR_UNSPECIFIED,
				"Images are not ==");
		return CPL_FALSE;
	}


    if(original->variance == NULL && twin->variance == NULL) return CPL_TRUE;

    p_orig = cpl_image_get_data_float(original->variance);
    p_twin = cpl_image_get_data_float(twin->variance);

    if(p_orig == NULL && p_twin == NULL)
        return CPL_TRUE;

    cpl_boolean inconsistent_instantiation = p_orig == NULL && p_twin != NULL;
    inconsistent_instantiation |= p_orig != NULL && p_twin == NULL;

    if(inconsistent_instantiation)
    {
        cpl_error_set_message(func, CPL_ERROR_UNSPECIFIED,
                "Instantiation is inconsistent");
        return CPL_FALSE;
    }

    if(p_orig == p_twin)
    {
        cpl_error_set_message(func, CPL_ERROR_UNSPECIFIED,
                "Error memory is shared");
        return CPL_FALSE;
    }

    if(memcmp(p_orig, p_twin, length * sizeof(float)))
    {
        cpl_error_set_message(func, CPL_ERROR_UNSPECIFIED,
                "The two error images are not identical");
                    return CPL_FALSE;
    }

    return CPL_TRUE;
}

#undef cleanup
#define cleanup \
do\
{\
    fors_image_delete(&original);\
    fors_image_delete(&twin);\
}while(0);\



static void
test_duplicate()
{
    fors_image *original = get_test_image(17, CPL_FALSE, CPL_FALSE);
    fors_image *twin = fors_image_duplicate(original);

    //Case 1 - Image and error duplicated, no bpm in both
    assure(check_images(original, twin), return,
            "Test duplication: Case 1 - Error");

    cleanup;

    //Case 2 : image has bpm, but variance has not
    original = get_test_image(17, CPL_TRUE, CPL_FALSE);
    twin = fors_image_duplicate(original);
    assure(check_images(original, twin), return,
            "Test duplication: Case 2 - Error");


    cpl_mask* ori_mask = cpl_image_get_bpm(original->data);
    cpl_mask* twin_mask = cpl_image_get_bpm(twin->data);

    assure(check_bpm(ori_mask, twin_mask), return,
            "Test bpm duplication: Case 2 - Error");

    ori_mask = cpl_image_get_bpm(original->variance);
    twin_mask = cpl_image_get_bpm(twin->variance);

    assure(check_bpm(ori_mask, twin_mask), return,
                "Test bpm duplication for variance: Case 2 - Error");

    cleanup;

    //Case 3 : image and variance have bpms
    original = get_test_image(17, CPL_TRUE, CPL_TRUE);
    twin = fors_image_duplicate(original);
    assure(check_images(original, twin), return,
            "Test duplication: Case 3 - Error");


    ori_mask = cpl_image_get_bpm(original->data);
    twin_mask = cpl_image_get_bpm(twin->data);

    assure(check_bpm(ori_mask, twin_mask), return,
            "Test bpm duplication: Case 3 - Error");

    ori_mask = cpl_image_get_bpm(original->variance);
    twin_mask = cpl_image_get_bpm(twin->variance);

    assure(check_bpm(ori_mask, twin_mask), return,
                "Test bpm duplication for variance: Case 3 - Error");


    cleanup;

    //Case 4 : only variance has bpms
    original = get_test_image(17, CPL_FALSE, CPL_TRUE);
    twin = fors_image_duplicate(original);

    assure(check_images(original, twin), return,
                "Test duplication: Case 4 - Error");


    ori_mask = cpl_image_get_bpm(original->data);
    twin_mask = cpl_image_get_bpm(twin->data);

    assure(check_bpm(ori_mask, twin_mask), return,
            "Test bpm duplication: Case 4 - Error");

    ori_mask = cpl_image_get_bpm(original->variance);
    twin_mask = cpl_image_get_bpm(twin->variance);

    assure(check_bpm(ori_mask, twin_mask), return,
                "Test bpm duplication for variance: Case 4 - Error");

    cleanup;
}

static fors_image* get_test_image_simple(double values)
{
    return get_test_image(values, CPL_FALSE, CPL_FALSE);
}

static fors_image* get_test_image(double values, cpl_boolean bpm_on_img,
        cpl_boolean bpm_on_error)
{
    const int nx = 20;
    const int ny = 30;
    const double var_val = 5;
    cpl_image *data     = NULL;
    cpl_image *variance = NULL;

    cpl_mask* error_mask = NULL;

    data = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    cpl_image_add_scalar(data, values);

    if(bpm_on_img)
    {
        cpl_mask* img_mask = cpl_mask_new(nx,ny);
        cpl_mask_set(img_mask, 1, 2, CPL_BINARY_1);

        cpl_image_set_bpm(data, img_mask);
    }

    variance = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    cpl_image_add_scalar(variance, var_val);

    if(bpm_on_error)
    {
        error_mask = cpl_mask_new(nx,ny);
        cpl_mask_set(error_mask, 2, 1, CPL_BINARY_1);

        cpl_image_set_bpm(variance, error_mask);
    }


    return fors_image_new(data, variance);
}

static cpl_boolean
check_bpm(cpl_mask* original, cpl_mask* twin)
{
    const char* func = "check_bpm";

    if(original == twin && twin == NULL) return CPL_TRUE;

    if(original != twin && (twin == NULL || original == NULL))
    {
        cpl_error_set_message(func, CPL_ERROR_UNSPECIFIED,
                        "One of the two bpms is NULL, the other is not");
                            return CPL_FALSE;
    }

    if(cpl_mask_get_size_x(original) != cpl_mask_get_size_x(twin))
    {
        cpl_error_set_message(func, CPL_ERROR_UNSPECIFIED,
                "The two bpm have different dimensions");
                    return CPL_FALSE;
    }

    if(cpl_mask_get_size_y(original) != cpl_mask_get_size_y(twin))
    {
        cpl_error_set_message(func, CPL_ERROR_UNSPECIFIED,
                    "The two bpm have different dimensions");
                        return CPL_FALSE;
    }

    const cpl_binary* p_orig = cpl_mask_get_data_const(original);
    const cpl_binary* p_twin = cpl_mask_get_data_const(twin);

    if(p_orig == p_twin)
    {
        cpl_error_set_message(func, CPL_ERROR_UNSPECIFIED, "Memory is shared");
        return CPL_FALSE;
    }

    if(memcmp(p_orig, p_twin, sizeof(cpl_binary) *
            cpl_mask_get_size_y(original) * cpl_mask_get_size_x(original)))
    {

        cpl_error_set_message(func, CPL_ERROR_UNSPECIFIED,
                "The two error images are not identical");
                    return CPL_FALSE;
    }

    return CPL_TRUE;

}

/**
 * @brief   Test of image module
 */
int main(void)
{
    TEST_INIT;

    test_image();
    
    test_median_filter();

    test_subtract();

    test_divide();

    test_exponential();

    test_duplicate();

    TEST_END;
}

/**@}*/
