/* $Id: kmo_priv_arithmetic.c,v 1.4 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.4 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include <string.h>

#include <cpl.h>

#include "kmo_debug.h"
#include "kmo_utils.h"
#include "kmo_dfs.h"
#include "kmo_cpl_extensions.h"
#include "kmo_error.h"
#include "kmo_priv_arithmetic.h"

/*----------------------------------------------------------------------------*/
/**
    @defgroup kmos_priv_arithmetic     Helper functions for recipe kmo_arithmetic.

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

/**
    @brief
        Applies an arithmetic operation on two vectors with or without noise.

    @param op1          The 1st operand.
    @param op2          The 2nd operand.
    @param op1_noise    (Optional) The noise of the 1st operand.
    @param op2_noise    (Optional) The noise of the 2nd operand.
    @param op           The operator ("+", "-", "*" or "/").

    @return CPL_ERROR_NONE in case of success.

    Possible cpl_error_code set in this function:

    The same as from cpl_image_add etc.
*/
cpl_error_code  kmo_arithmetic_1D_1D(
                            kmclipm_vector *op1,
                            const kmclipm_vector *op2,
                            kmclipm_vector *op1_noise,
                            const kmclipm_vector *op2_noise,
                            const char *op)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;
    kmclipm_vector  *op1_copy       = NULL,
                    *op2_copy       = NULL,
                    *op2_noise_copy = NULL;
    KMO_TRY
    {
        KMO_TRY_ASSURE((op1 != NULL) &&
                       (op2 != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_ASSURE(cpl_vector_get_size(op1->data) ==
                       cpl_vector_get_size(op2->data),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Input data isn't of same size!");

        if ((op1_noise != NULL) && (op2_noise != NULL)) {
            KMO_TRY_ASSURE((cpl_vector_get_size(op1->data) ==
                            cpl_vector_get_size(op1_noise->data)) &&
                           (cpl_vector_get_size(op1_noise->data) ==
                            cpl_vector_get_size(op2_noise->data)),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Input noise isn't of same size as data!");
        }

        KMO_TRY_ASSURE((strcmp(op, "+") == 0) ||
                       (strcmp(op, "-") == 0) ||
                       (strcmp(op, "*") == 0) ||
                       (strcmp(op, "/") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Illegal operator!");

        if ((op1_noise != NULL) && (op2_noise != NULL) &&
            ((strcmp(op, "*") == 0) || (strcmp(op, "/") == 0)))
        {
            KMO_TRY_EXIT_IF_NULL(
                op1_copy = kmclipm_vector_duplicate(op1));
        }

        //
        // process data
        //
        if (strcmp(op, "+") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_add(op1, op2));
        } else if (strcmp(op, "-") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_subtract(op1, op2));
        } else if (strcmp(op, "*") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_multiply(op1, op2));
        } else if (strcmp(op, "/") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_divide(op1, op2));
        }

        //
        // process noise
        //
        if ((op1_noise != NULL) && (op2_noise != NULL)) {
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_reject_from_mask(op1_noise, op2_noise->mask,
                                                TRUE));

            KMO_TRY_EXIT_IF_NULL(
                op2_noise_copy = kmclipm_vector_duplicate(op2_noise));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_power(op2_noise_copy, 2.0));

            if ((strcmp(op, "*") == 0) || (strcmp(op, "/") == 0)) {
                KMO_TRY_EXIT_IF_NULL(
                    op2_copy = kmclipm_vector_duplicate(op2));

                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_vector_power(op2_copy, 2.0));

                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_vector_divide(op2_noise_copy, op2_copy));
                kmclipm_vector_delete(op2_copy); op2_copy = NULL;
            }

            if ((strcmp(op, "+") == 0) || (strcmp(op, "-") == 0)) {
                /* sig_x = sqrt(sig_u^2 + sig_v^2) */
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_vector_power(op1_noise, 2.0));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_vector_add(op1_noise, op2_noise_copy));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_vector_power(op1_noise, 0.5));
            } else if ((strcmp(op, "*") == 0) || (strcmp(op, "/") == 0)) {
                /* sig_x = x * sqrt(sig_u^2/u^2 + sig_v^2/v^2) */
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_vector_power(op1_noise, 2.0));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_vector_power(op1_copy, 2.0));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_vector_divide(op1_noise, op1_copy));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_vector_add(op1_noise, op2_noise_copy));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_vector_power(op1_noise, 0.5));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_vector_multiply(op1_noise, op1));

                kmclipm_vector_delete(op1_copy); op1_copy = NULL;
            }

            kmclipm_vector_delete(op2_noise_copy); op2_noise_copy = NULL;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Applies a scalar arithmetic operation on a vector.

    @param op1          The 1st operand.
    @param op2          The 2nd operand (scalar).
    @param op1_noise    (Optional) The noise of the 1st operand.
    @param op           The operator ("+", "-", "*", "/" or "^").

    @return CPL_ERROR_NONE in case of success.

    Possible cpl_error_code set in this function:

    The same as from cpl_image_add etc.
*/
cpl_error_code  kmo_arithmetic_1D_scalar(
                            kmclipm_vector *op1,
                            double op2,
                            kmclipm_vector *op1_noise,
                            const char *op)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;
    kmclipm_vector  *op1_copy       = NULL;

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

        if (op1_noise != NULL) {
            KMO_TRY_ASSURE(cpl_vector_get_size(op1->data) ==
                           cpl_vector_get_size(op1_noise->data),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Input noise isn't of same size as data!");
        }

        KMO_TRY_ASSURE((strcmp(op, "+") == 0) ||
                       (strcmp(op, "-") == 0) ||
                       (strcmp(op, "*") == 0) ||
                       (strcmp(op, "/") == 0) ||
                       (strcmp(op, "^") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Illegal operator!");

        if ((op1_noise != NULL) && (strcmp(op, "^") == 0)) {
            KMO_TRY_EXIT_IF_NULL(
                op1_copy = kmclipm_vector_duplicate(op1));
        }

        //
        // process data
        //
        if (strcmp(op, "+") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_add_scalar(op1, op2));
        } else if (strcmp(op, "-") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_subtract_scalar(op1, op2));
        } else if (strcmp(op, "*") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_multiply_scalar(op1, op2));
        } else if (strcmp(op, "/") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_divide_scalar(op1, op2));
        } else if (strcmp(op, "^") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_power(op1, op2));
        }

        //
        // process noise
        //
        if ((op1_noise != NULL) && (strcmp(op, "^") == 0)) {
            // sig_x = scalar * x * sig_u / u
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_divide(op1_noise, op1_copy));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_multiply(op1_noise, op1));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_multiply_scalar(op1_noise, op2));
        }

        kmclipm_vector_delete(op1_copy); op1_copy = NULL;
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Applies an arithmetic operation on two images with or without noise.

    @param op1          The 1st operand.
    @param op2          The 2nd operand.
    @param op1_noise    (Optional) The noise of the 1st operand.
    @param op2_noise    (Optional) The noise of the 2nd operand.
    @param op           The operator ("+", "-", "*" or "/").

    @return CPL_ERROR_NONE in case of success.

    Possible cpl_error_code set in this function:

    The same as from cpl_image_add etc.
*/
cpl_error_code kmo_arithmetic_2D_2D(cpl_image *op1,
                                    const cpl_image *op2,
                                    cpl_image *op1_noise,
                                    const cpl_image *op2_noise,
                                    const char *op)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;
    cpl_image       *op1_copy       = NULL,
                    *op2_copy       = NULL,
                    *op2_noise_copy = NULL;

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

        KMO_TRY_ASSURE((cpl_image_get_size_x(op1) ==
                        cpl_image_get_size_x(op2)) &&
                       (cpl_image_get_size_y(op1) ==
                        cpl_image_get_size_y(op2)) ,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Input data isn't of same size!");

        if ((op1_noise != NULL) && (op2_noise != NULL)) {
            KMO_TRY_ASSURE((cpl_image_get_size_x(op1_noise) ==
                            cpl_image_get_size_x(op2_noise)) &&
                           (cpl_image_get_size_y(op1_noise) ==
                            cpl_image_get_size_y(op2_noise)) ,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Input noise isn't of same size!");

            KMO_TRY_ASSURE((cpl_image_get_size_x(op1) ==
                            cpl_image_get_size_x(op1_noise)) &&
                           (cpl_image_get_size_y(op1) ==
                            cpl_image_get_size_y(op1_noise)) ,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Input data and noise isn't of same sconstize!");
        }

        KMO_TRY_ASSURE((strcmp(op, "+") == 0) ||
                       (strcmp(op, "-") == 0) ||
                       (strcmp(op, "*") == 0) ||
                       (strcmp(op, "/") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Illegal operator!");

        if ((op1_noise != NULL) && (op2_noise != NULL) &&
            ((strcmp(op, "*") == 0) || (strcmp(op, "/") == 0)))
        {
            KMO_TRY_EXIT_IF_NULL(
                op1_copy = cpl_image_duplicate(op1));
        }

        //
        // process data
        //
        if (strcmp(op, "+") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                cpl_image_add(op1, op2));
        } else if (strcmp(op, "-") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                cpl_image_subtract(op1, op2));
        } else if (strcmp(op, "*") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                cpl_image_multiply(op1, op2));
        } else if (strcmp(op, "/") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_image_divide(op1, op2));
        }

        //
        // process noise
        //
        if ((op1_noise != NULL) && (op2_noise != NULL)) {
            if ((strcmp(op, "+") == 0) ||
                (strcmp(op, "-") == 0))
            {
                // sig_x = sqrt(sig_u^2 + sig_v^2)
                KMO_TRY_EXIT_IF_NULL(
                    op2_noise_copy = cpl_image_power_create(op2_noise, 2.0));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_image_power(op1_noise, 2.0));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_image_add(op1_noise, op2_noise_copy));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_image_power(op1_noise, 0.5));
            } else if ((strcmp(op, "*") == 0) ||
                        (strcmp(op, "/") == 0))
            {
                // sig_x = x * sqrt(sig_u^2/u^2 + sig_v^2/v^2)
                KMO_TRY_EXIT_IF_NULL(
                    op2_noise_copy = cpl_image_power_create(op2_noise, 2.0));
                KMO_TRY_EXIT_IF_NULL(
                    op2_copy = cpl_image_power_create(op2, 2.0));
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_image_divide(op2_noise_copy, op2_copy));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_image_power(op1_noise, 2.0));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_image_power(op1_copy, 2.0));
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_image_divide(op1_noise, op1_copy));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_image_add(op1_noise, op2_noise_copy));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_image_power(op1_noise, 0.5));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_image_multiply(op1_noise, op1));
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    cpl_image_delete(op1_copy); op1_copy = NULL;
    cpl_image_delete(op2_noise_copy); op2_noise_copy = NULL;
    cpl_image_delete(op2_copy); op2_copy = NULL;

    return ret_error;
}

/**
    @brief
        Applies a scalar arithmetic operation on an image.

    @param op1          The 1st operand.
    @param op2          The 2nd operand (scalar).
    @param op1_noise    (Optional) The noise of the 1st operand.
    @param op           The operator ("+", "-", "*", "/" or "^").

    @return CPL_ERROR_NONE in case of success.

    Possible cpl_error_code set in this function:

    The same as from cpl_image_add etc.
*/
cpl_error_code kmo_arithmetic_2D_scalar(cpl_image *op1,
                                        double op2,
                                        cpl_image *op1_noise,
                                        const char *op)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;
    cpl_image       *op1_copy       = NULL;

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

        if (op1_noise != NULL) {
            KMO_TRY_ASSURE((cpl_image_get_size_x(op1) ==
                            cpl_image_get_size_x(op1_noise)) &&
                           (cpl_image_get_size_y(op1) ==
                            cpl_image_get_size_y(op1_noise)) ,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Data and noise isn't of same size!");
        }

        KMO_TRY_ASSURE((strcmp(op, "+") == 0) ||
                       (strcmp(op, "-") == 0) ||
                       (strcmp(op, "*") == 0) ||
                       (strcmp(op, "/") == 0) ||
                       (strcmp(op, "^") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Illegal operator!");

        if ((op1_noise != NULL) && (strcmp(op, "^") == 0)) {
            KMO_TRY_EXIT_IF_NULL(
                op1_copy = cpl_image_duplicate(op1));
        }

        //
        // process data
        //
        if (strcmp(op, "+") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                cpl_image_add_scalar(op1, op2));
        } else if (strcmp(op, "-") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                cpl_image_subtract_scalar(op1, op2));
        } else if (strcmp(op, "*") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                cpl_image_multiply_scalar(op1, op2));
        } else if (strcmp(op, "/") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_image_divide_scalar(op1, op2));
        } else if (strcmp(op, "^") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_image_power(op1, op2));
        }

        //
        // process noise
        //
        if ((op1_noise != NULL) && (strcmp(op, "^") == 0)) {
            // sig_x = scalar * x * sig_u / u
            KMO_TRY_EXIT_IF_ERROR(
                kmo_image_divide(op1_noise, op1_copy));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_image_multiply(op1_noise, op1));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_image_multiply_scalar(op1_noise, op2));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    cpl_image_delete(op1_copy); op1_copy = NULL;

    return ret_error;
}

/**
    @brief
        Applies an arithmetic operation on two cubes.

    @param op1          The 1st operand.
    @param op2          The 2nd operand.
    @param op1_noise    (Optional) The noise of the 1st operand.
    @param op2_noise    (Optional) The noise of the 2nd operand.
    @param op           The operator ("+", "-", "*" or "/").

    @return CPL_ERROR_NONE in case of success.

    Possible cpl_error_code set in this function:

    The same as from cpl_image_add etc.
*/
cpl_error_code kmo_arithmetic_3D_3D(cpl_imagelist *op1,
                                    const cpl_imagelist *op2,
                                    cpl_imagelist *op1_noise,
                                    const cpl_imagelist *op2_noise,
                                    const char *op)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;
    int             i               = 0;
    cpl_image       *img1           = NULL,
                    *img1_noise     = NULL;
    const cpl_image *img2           = NULL,
                    *img2_noise     = NULL;

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


        if ((op1_noise != NULL) && (op2_noise != NULL)) {
            KMO_TRY_ASSURE((cpl_imagelist_get_size(op1) ==
                            cpl_imagelist_get_size(op2)) &&
                           (cpl_imagelist_get_size(op1) ==
                            cpl_imagelist_get_size(op1_noise)) &&
                           (cpl_imagelist_get_size(op1) ==
                            cpl_imagelist_get_size(op2_noise)),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Input data isn't of same size!");
        }

        KMO_TRY_ASSURE((strcmp(op, "+") == 0) ||
                       (strcmp(op, "-") == 0) ||
                       (strcmp(op, "*") == 0) ||
                       (strcmp(op, "/") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Illegal operator!");

        for (i = 0; i < cpl_imagelist_get_size(op1); i++) {
            KMO_TRY_EXIT_IF_NULL(
                img1 = cpl_imagelist_get(op1, i));

            KMO_TRY_EXIT_IF_NULL(
                img2 = cpl_imagelist_get_const(op2, i));

            if ((op1_noise != NULL) && (op2_noise != NULL)) {
                KMO_TRY_EXIT_IF_NULL(
                    img1_noise = cpl_imagelist_get(op1_noise, i));

                KMO_TRY_EXIT_IF_NULL(
                    img2_noise = cpl_imagelist_get_const(op2_noise, i));
            }

            KMO_TRY_EXIT_IF_ERROR(
                kmo_arithmetic_2D_2D(img1, img2, img1_noise, img2_noise, op));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Applies an arithmetic operation on a cube and an image.

    @param op1          The 1st operand.
    @param op2          The 2nd operand.
    @param op1_noise    (Optional) The noise of the 1st operand.
    @param op2_noise    (Optional) The noise of the 2nd operand.
    @param op           The operator ("+", "-", "*" or "/").

    @return CPL_ERROR_NONE in case of success.

    Possible cpl_error_code set in this function:

    The same as from cpl_image_add etc.
*/
cpl_error_code kmo_arithmetic_3D_2D(cpl_imagelist *op1,
                                    const cpl_image *op2,
                                    cpl_imagelist *op1_noise,
                                    const cpl_image *op2_noise,
                                    const char *op)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;
    int             i               = 0;
    cpl_image       *img           = NULL,
                    *img_noise     = NULL;

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

        KMO_TRY_EXIT_IF_NULL(
            img = cpl_imagelist_get(op1, 0));

        KMO_TRY_ASSURE((cpl_image_get_size_x(img) ==
                        cpl_image_get_size_x(op2)) &&
                       (cpl_image_get_size_y(img) ==
                        cpl_image_get_size_y(op2)) ,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Input data isn't of same size!");

        if ((op1_noise != NULL) && (op2_noise != NULL)) {
            KMO_TRY_EXIT_IF_NULL(
                img = cpl_imagelist_get(op1_noise, 0));

            KMO_TRY_ASSURE((cpl_image_get_size_x(img) ==
                            cpl_image_get_size_x(op2_noise)) &&
                           (cpl_image_get_size_y(img) ==
                            cpl_image_get_size_y(op2_noise)) ,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Input noise isn't of same size!");

            KMO_TRY_ASSURE((cpl_image_get_size_x(op2) ==
                            cpl_image_get_size_x(op2_noise)) &&
                           (cpl_image_get_size_y(op2) ==
                            cpl_image_get_size_y(op2_noise)) ,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Input data and noise isn't of same size!");
        }

        KMO_TRY_ASSURE((strcmp(op, "+") == 0) ||
                       (strcmp(op, "-") == 0) ||
                       (strcmp(op, "*") == 0) ||
                       (strcmp(op, "/") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Illegal operator!");

        for (i = 0; i < cpl_imagelist_get_size(op1); i++) {
            KMO_TRY_EXIT_IF_NULL(
                img = cpl_imagelist_get(op1, i));

            if ((op1_noise != NULL)) {
                KMO_TRY_EXIT_IF_NULL(
                    img_noise = cpl_imagelist_get(op1_noise, i));
            }

            KMO_TRY_EXIT_IF_ERROR(
                kmo_arithmetic_2D_2D(img, op2, img_noise, op2_noise, op));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Applies an arithmetic operation on a cube and a vector with.

    @param op1          The 1st operand.
    @param op2          The 2nd operand.
    @param op1_noise    (Optional) The noise of the 1st operand.
    @param op2_noise    (Optional) The noise of the 2nd operand.
    @param op           The operator ("+", "-", "*" or "/").

    @return CPL_ERROR_NONE in case of success.

    Possible cpl_error_code set in this function:

    The same as from cpl_image_add etc.
*/
cpl_error_code kmo_arithmetic_3D_1D(cpl_imagelist *op1,
                                    const kmclipm_vector *op2,
                                    cpl_imagelist *op1_noise,
                                    const kmclipm_vector *op2_noise,
                                    const char *op)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;
    cpl_image       *tmp_img        = NULL,
                    *tmp_img1       = NULL,
                    *tmp_img2       = NULL,
                    *tmp_img1_noise = NULL;
    double          *pop2_noise_d   = NULL,
                    *pop2_noise_m   = NULL;
    const double    *pop2_d         = NULL,
                    *pop2_m         = NULL;
    int             i               = 0,
                    ix              = 0,
                    iy              = 0;

    cpl_imagelist   *op1_copy       = NULL;
    kmclipm_vector  *op2_copy       = NULL,
                    *op2_noise_copy = NULL;

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

        KMO_TRY_ASSURE(cpl_imagelist_get_size(op1) ==
                       cpl_vector_get_size(op2->data),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Input data isn't of same size!");

        if ((op1_noise != NULL) && (op2_noise != NULL)) {
            KMO_TRY_ASSURE((cpl_imagelist_get_size(op1) ==
                            cpl_vector_get_size(op2_noise->data)) &&
                           (cpl_imagelist_get_size(op1) ==
                            cpl_imagelist_get_size(op1_noise)),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Input noise isn't of same size as data!");
        }

        KMO_TRY_ASSURE((strcmp(op, "+") == 0) ||
                       (strcmp(op, "-") == 0) ||
                       (strcmp(op, "*") == 0) ||
                       (strcmp(op, "/") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Illegal operator!");

        if ((op1_noise != NULL) && (op2_noise != NULL) &&
            ((strcmp(op, "*") == 0) || (strcmp(op, "/") == 0)))
        {
            KMO_TRY_EXIT_IF_NULL(
                op1_copy = cpl_imagelist_duplicate(op1));
        }

        //
        // process data
        //
        pop2_d = cpl_vector_get_data_const(op2->data);
        pop2_m = cpl_vector_get_data_const(op2->mask);

        for (i = 0; i < cpl_imagelist_get_size(op1); i++) {
            KMO_TRY_EXIT_IF_NULL(
                tmp_img = cpl_imagelist_get(op1, i));

            if (pop2_m[i] < 0.5) {
                // whole image rejected
                for (ix = 1; ix <= cpl_image_get_size_x(tmp_img); ix++) {
                    for (iy = 1; iy <= cpl_image_get_size_y(tmp_img); iy++) {
                        cpl_image_reject(tmp_img, ix, iy);
                    }
                }
            } else {
                if (strcmp(op, "+") == 0) {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_image_add_scalar(tmp_img, (float)pop2_d[i]));
                } else if (strcmp(op, "-") == 0) {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_image_subtract_scalar(tmp_img,
                                                    (float)pop2_d[i]));
                } else if (strcmp(op, "*") == 0) {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_image_multiply_scalar(tmp_img,
                                                    (float)pop2_d[i]));
                } else if (strcmp(op, "/") == 0) {
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_image_divide_scalar(tmp_img,
                                                (float)pop2_d[i]));
                }
// überflüssig ?!?
//                KMO_TRY_EXIT_IF_ERROR(
//                    cpl_imagelist_set(op1, tmp_img, i));
            }
        }

        //
        // process noise
        //
        if ((op1_noise != NULL) && (op2_noise != NULL)) {
            if ((strcmp(op, "+") == 0) ||
                (strcmp(op, "-") == 0)) {

                KMO_TRY_EXIT_IF_NULL(
                    op2_noise_copy = kmclipm_vector_duplicate(op2_noise));

                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_vector_power(op2_noise_copy, 2.0));

            } else if ((strcmp(op, "*") == 0) ||
                        (strcmp(op, "/") == 0)) {

                KMO_TRY_EXIT_IF_NULL(
                    op2_noise_copy = kmclipm_vector_duplicate(op2_noise));

                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_vector_power(op2_noise_copy, 2.0));

                KMO_TRY_EXIT_IF_NULL(
                    op2_copy = kmclipm_vector_duplicate(op2));

                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_vector_power(op2_copy, 2.0));

                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_vector_divide(op2_noise_copy, op2_copy));
            }

            pop2_noise_d = cpl_vector_get_data(op2_noise_copy->data);
            pop2_noise_m = cpl_vector_get_data(op2_noise_copy->mask);

            for (i = 0; i < cpl_imagelist_get_size(op1); i++) {
                KMO_TRY_EXIT_IF_NULL(
                    tmp_img1_noise = cpl_imagelist_get(op1_noise, i));

                if (pop2_noise_m[i] < 0.5) {
                    // is rejected: reject whole image
                    for (ix = 1; ix <= cpl_image_get_size_x(tmp_img1_noise);ix++) {
                        for (iy = 1; iy <= cpl_image_get_size_y(tmp_img1_noise); iy++) {
                            cpl_image_reject(tmp_img1_noise, ix, iy);
                        }
                    }
                } else {
                    KMO_TRY_EXIT_IF_NULL(
                        tmp_img2 = cpl_imagelist_get(op1, i));

                    if ((strcmp(op, "+") == 0) ||
                        (strcmp(op, "-") == 0)) {

                        /* sig_x = sqrt(sig_u^2 + sig_v^2) */
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_image_power(tmp_img1_noise, 2.0));

                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_image_add_scalar(tmp_img1_noise,
                                                    pop2_noise_d[i]));
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_image_power(tmp_img1_noise, 0.5));

                    } else if ((strcmp(op, "*") == 0) ||
                                (strcmp(op, "/") == 0)) {

                        /* sig_x = x * sqrt(sig_u^2/u^2 + sig_v^2/v^2) */
                        KMO_TRY_EXIT_IF_NULL(
                            tmp_img1 = cpl_imagelist_get(op1_copy, i));

                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_image_power(tmp_img1_noise, 2.0));

                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_image_power(tmp_img1, 2.0));

                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_image_divide(tmp_img1_noise, tmp_img1));

                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_image_add_scalar(tmp_img1_noise,
                                                    pop2_noise_d[i]));
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_image_power(tmp_img1_noise, 0.5));

                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_image_multiply(tmp_img1_noise, tmp_img2));
                    }
//     überflüssig ?!?
//                    KMO_TRY_EXIT_IF_ERROR(
//                        cpl_imagelist_set(op1_noise, tmp_img1_noise, i));
                }
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    cpl_imagelist_delete(op1_copy); op1_copy = NULL;
    kmclipm_vector_delete(op2_copy); op2_copy = NULL;
    kmclipm_vector_delete(op2_noise_copy); op2_noise_copy = NULL;

    return ret_error;
}

/**
    @brief
        Applies a scalar arithmetic operation on a cube with.

    @param op1          The 1st operand.
    @param op2          The 2nd operand (scalar).
    @param op1_noise    (Optional) The noise of the 1st operand.
    @param op           The operator ("+", "-", "*", "/" or "^").

    @return CPL_ERROR_NONE in case of success.

    Possible cpl_error_code set in this function:

    The same as from cpl_image_add etc.
*/
cpl_error_code kmo_arithmetic_3D_scalar(cpl_imagelist *op1,
                                        double op2,
                                        cpl_imagelist *op1_noise,
                                        const char *op)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;
    int             i               = 0;
    cpl_image       *img            = NULL,
                    *img_noise      = NULL;

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

        if  (op1_noise != NULL) {
            KMO_TRY_ASSURE(cpl_imagelist_get_size(op1) ==
                           cpl_imagelist_get_size(op1_noise),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Data and noise isn't of same size!");
        }

        KMO_TRY_ASSURE((strcmp(op, "+") == 0) ||
                       (strcmp(op, "-") == 0) ||
                       (strcmp(op, "*") == 0) ||
                       (strcmp(op, "/") == 0) ||
                       (strcmp(op, "^") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Illegal operator!");

        for (i = 0; i < cpl_imagelist_get_size(op1); i++) {
            KMO_TRY_EXIT_IF_NULL(
                img = cpl_imagelist_get(op1, i));

            if  (op1_noise != NULL) {
                KMO_TRY_EXIT_IF_NULL(
                    img_noise = cpl_imagelist_get(op1_noise, i));
            }

            KMO_TRY_EXIT_IF_ERROR(
                kmo_arithmetic_2D_scalar(img, op2, img_noise, op));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/** @} */
