
/*********************************************************************
 * E.S.O. - VLT project
 *
 * "@(#) $Id: kmclipm_math.c,v 1.5 2013-04-23 07:40:04 aagudo Exp $"
 *
 * Functions that are implemented as recipes. They are also useedd in KMOS
 * data reduction pipeline.
 *
 * who       when        what
 * --------  ----------  ----------------------------------------------
 * agudo     2008-06-10  created
*/

/**
    @defgroup kmclipm_math Mathematic functions & definitions

    This module provides mathematical helper functions needed by the modules
    @ref kmclipm_rtd_image and the KMOS-pipeline

    @par Synopsis:
    @code
      #include <kmclipm_math.h>
    @endcode
    @{
 */

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


/*------------------------------------------------------------------------------
 *                                  Includes
 *----------------------------------------------------------------------------*/
#define _ISOC99_SOURCE

#include <math.h>

#include <cpl.h>

#include "kmclipm_math.h"
#include "kmclipm_functions.h"
#include "kmclipm_priv_error.h"

/*------------------------------------------------------------------------------
 *                                  Definitions
 *----------------------------------------------------------------------------*/
/**
    @brief  Calculates the minimum median value of an image.
    @param  img The image to process.
    @param  xpos Output x-position of minimum median value. If NULL nothing will
                 be returned
    @param  ypos Output y-position of minimum median value. If NULL nothing will
                 be returned
    @return The minimum median value, or DBL_MAX on error.

    A window of size MEDIAN_WINDOW_SIZE is moved across the whole image. Each
    time the median is calculated and the minimum value will be returned.

    Possible _cpl_error_code_ set in this function:

    @li CPL_ERROR_NULL_INPUT if input image is NULL
    @li CPL_ERROR_ILLEGAL_INPUT if size of image is less than 3x3, if there are
        only bad pixels in the window or if input isn't an image
*/
double kmclipm_median_min(const cpl_image *img, int *xpos, int *ypos)
{
    int     nx  = 0,
            ny  = 0,
            i       = 0,
            j       = 0;

    double  min     = DBL_MAX,
            val     = 0.0;

    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK_AUTOMSG(img != NULL,
            CPL_ERROR_NULL_INPUT);

        nx = cpl_image_get_size_x(img);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        ny = cpl_image_get_size_y(img);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        KMCLIPM_TRY_CHECK_AUTOMSG(nx >= MEDIAN_WINDOW_SIZE,
            CPL_ERROR_ILLEGAL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG(ny >= MEDIAN_WINDOW_SIZE,
            CPL_ERROR_ILLEGAL_INPUT);

        for (i = MEDIAN_WINDOW_SIZE - 1; i < nx; i++) {
            for (j = MEDIAN_WINDOW_SIZE - 1; j < ny; j++) {

                val = cpl_image_get_median_window(img, i-1, j-1, i+1, j+1);
                if (cpl_error_get_code() != CPL_ERROR_NONE) {
                    cpl_error_reset();
                } else {
                    if (min > val) {
                        min = val;
                        if (xpos != NULL) {
                            *xpos = i;
                        }
                        if (ypos != NULL) {
                            *ypos = j;
                        }
                    }
                }
            }
        }

        if ((xpos != NULL) && (ypos != NULL) && (*xpos == 0) && (*ypos == 0)) {
            cpl_image *dup = NULL;

            KMCLIPM_TRY_EXIT_IFN(
                dup = cpl_image_duplicate(img));
            KMCLIPM_TRY_EXIT_IFN(
                kmclipm_reject_nan(dup) == CPL_ERROR_NONE);

#if KMCLIPM_GET_INSTALLED_CPL_VERSION >= 6
            cpl_size nr_rej = cpl_image_count_rejected(img);
#else
            int nr_rej = cpl_image_count_rejected(img);
#endif
            KMCLIPM_TRY_CHECK_ERROR_STATE();
            if (nr_rej == nx*ny) {
                cpl_msg_error("", "All pixels in image are NaN! Check if calibration frames match the data!");
            } else {
                cpl_msg_error("", "xpos = 0 and ypos = 0");
            }

            KMCLIPM_TRY_CHECK_AUTOMSG(1==0,
                                      CPL_ERROR_ILLEGAL_INPUT);
        }
    }
    KMCLIPM_CATCH
    {
        min = DBL_MAX;
        if (xpos != NULL) {
            *xpos = -1;
        }
        if (ypos != NULL) {
            *ypos = -1;
        }
    }

    return min;
}

/**
    @brief  Calculates the maximum median value of an image.
    @param  img The image to process.
    @param  xpos Output x-position of maximum median value. If NULL nothing will
                 be returned
    @param  ypos Output y-position of maximum median value. If NULL nothing will
                 be returned
    @return The maximum median value, or -1 on error.

    A window of size MEDIAN_WINDOW_SIZE is moved across the whole image. Each
    time the median is calculated and the maximum value will be returned.

    Possible _cpl_error_code_ set in this function:

    @li CPL_ERROR_NULL_INPUT if input image is NULL
    @li CPL_ERROR_ILLEGAL_INPUT if size of image is less than 3x3, if there are
        only bad pixels in the window or if input isn't an image
*/
double kmclipm_median_max(const cpl_image* img, int *xpos, int *ypos)
{
    int     nx  = 0,
            ny  = 0,
            i       = 0,
            j       = 0;

    double  max     = -DBL_MAX,
            val     = 0.0;

    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK_AUTOMSG(img != NULL,
                                  CPL_ERROR_NULL_INPUT);

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

        KMCLIPM_TRY_CHECK_AUTOMSG((nx >= MEDIAN_WINDOW_SIZE) &&
                                  (ny >= MEDIAN_WINDOW_SIZE),
                                  CPL_ERROR_ILLEGAL_INPUT);

        for (i = MEDIAN_WINDOW_SIZE - 1; i < nx; i++) {
            for (j = MEDIAN_WINDOW_SIZE - 1; j < ny; j++) {

                val = cpl_image_get_median_window(img, i-1, j-1, i+1, j+1);
                if (cpl_error_get_code() != CPL_ERROR_NONE) {
                    cpl_error_reset();
                } else {
                    if (((i == MEDIAN_WINDOW_SIZE - 1) &&
                         (j == MEDIAN_WINDOW_SIZE - 1)) ||
                        (max < val))
                    {
                        max = val;
                        if (xpos != NULL) {
                            *xpos = i;
                        }
                        if (ypos != NULL) {
                            *ypos = j;
                        }
                    }
                }
            }
        }

        if ((xpos != NULL) && (ypos != NULL) && (*xpos == 0) && (*ypos == 0)) {
            cpl_image *dup = NULL;

            KMCLIPM_TRY_EXIT_IFN(
                dup = cpl_image_duplicate(img));
            KMCLIPM_TRY_EXIT_IFN(
                kmclipm_reject_nan(dup) == CPL_ERROR_NONE);

#if KMCLIPM_GET_INSTALLED_CPL_VERSION >= 6
            cpl_size nr_rej = cpl_image_count_rejected(img);
#else
            int nr_rej = cpl_image_count_rejected(img);
#endif
            KMCLIPM_TRY_CHECK_ERROR_STATE();
            if (nr_rej == nx*ny) {
                cpl_msg_error("", "All pixels in image are NaN! Check if calibration frames match the data!");
            } else {
                cpl_msg_error("", "xpos = 0 and ypos = 0");
            }

            KMCLIPM_TRY_CHECK_AUTOMSG(1==0,
                                      CPL_ERROR_ILLEGAL_INPUT);
        }
    }
    KMCLIPM_CATCH
    {
        max = -DBL_MAX;
        if (xpos != NULL) {
            *xpos = -1;
        }
        if (ypos != NULL) {
            *ypos = -1;
        }
    }

    return max;
}

/**
    @brief
        Checks if a value is nan, inf or -inf.

    @param A    The value to check.

    @return     TRUE if the value is nan, inf or -inf, FALSE otherwise.
*/
int kmclipm_is_nan_or_inf(double A)
{
    return (isnan(A) || (kmclipm_is_inf(A)==1) || (kmclipm_is_inf(A)==-1));
}

/**
    @brief
        Checks if a value is inf.

    Use this function instead of isinf!! Under Linux the behavior of this function
    and isinf() is the same. But on MacOSX isinf() returns +1 for -inf values!!

    @param a    The value to check.

    @return     1 if the value is inf, -1 if it is -inf, 0 otherwise.

*/
int kmclipm_is_inf(double a) {
    int ret = isinf(a);
    if (ret == 1){
        /* double check in case we are running on MacOSX */
        if (isgreater(a, 0))
            return 1;
        else
            return -1;
    }
    return ret;
}

/** @} */
