/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set sw=2 sts=2 et cin: */
/*
 * This file is part of the MUSE Instrument Pipeline
 * Copyright (C) 2005-2015 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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

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

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

#include "muse_image.h"

#include "muse_quadrants.h"
#include "muse_quality.h"
#include "muse_pfits.h"
#include "muse_utils.h"
#include "muse_wcs.h"

/*----------------------------------------------------------------------------*/
/**
 * @defgroup muse_image        Image handling for the MUSE format
 *
 * These functions wrap the respective CPL functions to easily handle
 * the three-extension muse_image structure.
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*---------------------------------------------------------------------------*/
/**
  @brief    Allocate memory for a new <tt><b>muse_image</b></tt> object.
  @return   a new <tt><b>muse_image *</b></tt> or @c NULL on error
  @remark   The returned object has to be deallocated using
            <tt><b>muse_image_delete()</b></tt>.
  @remark   This function does not allocate the contents of the four elements,
            these have to be allocated with @c cpl_image_new() or
            @c cpl_propertylist_new(), respectively, or equivalent functions.

  Simply allocate memory to store the pointers of the <tt><b>muse_image</b></tt>
  structure.
 */
/*---------------------------------------------------------------------------*/
muse_image *
muse_image_new(void)
{
  /* calloc automatically NULLs out the four components of the structure */
  muse_image *image = (muse_image *)cpl_calloc(1, sizeof(muse_image));
  return image;
} /* muse_image_new() */

/*---------------------------------------------------------------------------*/
/**
  @brief    Deallocate memory associated to a muse_image object.
  @param    aImage   input MUSE image

  Just calls @c cpl_image_delete() and @c cpl_propertylist_delete() for the
  four components of a <tt><b>muse_image</b></tt>, and frees memory for the
  aImage pointer. As a safeguard, it checks if a valid pointer was passed,
  so that crashes cannot occur.
 */
/*---------------------------------------------------------------------------*/
void
muse_image_delete(muse_image *aImage)
{
  /* if image does not exists at all, we don't need to do anything */
  if (!aImage) {
    return;
  }

  /* checks for the existence of the sub-images *
   * are done in the CPL functions              */
  cpl_image_delete(aImage->data);
  aImage->data = NULL;
  cpl_image_delete(aImage->dq);
  aImage->dq = NULL;
  cpl_image_delete(aImage->stat);
  aImage->stat = NULL;

  /* delete the FITS header, too */
  cpl_propertylist_delete(aImage->header);
  aImage->header = NULL;
  cpl_free(aImage);
} /* muse_image_delete() */

/*---------------------------------------------------------------------------*/
/**
  @brief    Load the three extensions and the FITS headers of a MUSE image.
  @param    aFilename      name of the file to load
  @param    aIFU           IFU number to search for, or 0 if loading directly.
  @param    aID            function name to use for messages
  @return   a muse_image or NULL on error
 */
/*---------------------------------------------------------------------------*/
static muse_image *
muse_image_load_internal(const char *aFilename, unsigned char aIFU,
                         const char *aID)
{
  muse_image *image = muse_image_new();

  /* Load the primary FITS header first. This is not critical, but *
   * a good first test to see, if the file is really there.        */
  image->header = cpl_propertylist_load(aFilename, 0);
  if (!image->header) {
    cpl_error_set_message(aID, cpl_error_get_code(), "Loading primary FITS "
                          "header of \"%s\" did not succeed", aFilename);
    muse_image_delete(image);
    return NULL;
  }

  /* now load the image data from the there extensions */
  char extname[KEYWORD_LENGTH];
  if (aIFU) {
    /* first set the EXTNAME keyword in the output header */
    snprintf(extname, KEYWORD_LENGTH, "CHAN%02hhu", aIFU);
    cpl_propertylist_update_string(image->header, "EXTNAME", extname);
    /* then set up the extension name to search for */
    snprintf(extname, KEYWORD_LENGTH, "CHAN%02hhu.%s", aIFU, EXTNAME_DATA);
  } else {
    snprintf(extname, KEYWORD_LENGTH, "%s", EXTNAME_DATA);
  }
  int extension = cpl_fits_find_extension(aFilename, extname);
  image->data = cpl_image_load(aFilename, CPL_TYPE_FLOAT, 0, extension);
  if (!image->data) {
    cpl_error_set_message(aID, MUSE_ERROR_READ_DATA, "Could not load extension "
                          "%s from \"%s\"", extname, aFilename);
    muse_image_delete(image);
    return NULL;
  }
  /* get BUNIT back from the header of this data extension */
  cpl_propertylist *hdata = cpl_propertylist_load(aFilename, extension);
  if (cpl_propertylist_has(hdata, "BUNIT")) { /* backward compatibility */
    cpl_propertylist_append_string(image->header, "BUNIT",
                                   muse_pfits_get_bunit(hdata));
    cpl_propertylist_set_comment(image->header, "BUNIT",
                                 cpl_propertylist_get_comment(hdata, "BUNIT"));
  } else {
    cpl_msg_warning(aID, "No BUNIT given in extension %d [%s] of \"%s\"!",
                    extension, extname, aFilename);
  }
  /* When reading from extensions, we also need to merge in all     *
   * HIERARCH keywords from the data extension and add the EXTNAME. */
  if (aIFU) {
    cpl_propertylist_erase_regexp(hdata, "^ESO ", 1);
    cpl_propertylist_append(image->header, hdata);
  }
  cpl_propertylist_delete(hdata);

  if (aIFU) {
    snprintf(extname, KEYWORD_LENGTH, "CHAN%02hhu.%s", aIFU, EXTNAME_DQ);
  } else {
    snprintf(extname, KEYWORD_LENGTH, "%s", EXTNAME_DQ);
  }
  extension = cpl_fits_find_extension(aFilename, extname);
  image->dq = cpl_image_load(aFilename, CPL_TYPE_INT, 0, extension);
  if (!image->dq) {
    cpl_error_set_message(aID, MUSE_ERROR_READ_DQ, "Could not load extension "
                          "%s from \"%s\"", extname, aFilename);
    muse_image_delete(image);
    return NULL;
  }

  if (aIFU) {
    snprintf(extname, KEYWORD_LENGTH, "CHAN%02hhu.%s", aIFU, EXTNAME_STAT);
  } else {
    snprintf(extname, KEYWORD_LENGTH, "%s", EXTNAME_STAT);
  }
  extension = cpl_fits_find_extension(aFilename, extname);
  image->stat = cpl_image_load(aFilename, CPL_TYPE_FLOAT, 0, extension);
  if (!image->stat) {
    cpl_error_set_message(aID, MUSE_ERROR_READ_STAT, "Could not load extension "
                          "%s from \"%s\"", extname, aFilename);
    muse_image_delete(image);
    return NULL;
  }

  return image;
} /* muse_image_load_internal() */

/*---------------------------------------------------------------------------*/
/**
  @brief    Load the three extensions and the FITS headers of a MUSE image
            from a file.
  @param    aFilename      name of the file to load
  @return   a new <tt><b>muse_image *</b></tt> or NULL on error
  @remark   The new image has to be deallocated using
            <tt><b>muse_image_delete()</b></tt>.
  @remark   Only FITS keywords from the primary FITS header will be loaded.

  <tt><b>muse_image_new()</b></tt> is used to create a new
  <tt><b>muse_image *</b></tt>. The primary FITS header is loaded into the
  header element using @c cpl_propertylist_load().  The other three extensions
  are then loaded from the FITS extensions with the expected EXTNAMEs, using
  @c cpl_image_load() to allocate and load the three image components of the
  <tt><b>muse_image</b></tt>.

  This function does not display any error messsage, but the user can take the
  error state to print one, if necessary.

  @error{return NULL\, propagate CPL error code,
         cpl_propertylist_load() fails (this handles the case of an invalid or empty filename) }
  @error{set MUSE_ERROR_READ_DATA\, MUSE_ERROR_READ_DQ\, or MUSE_ERROR_READ_STAT depending on failed extension\, clean up allocations\, and return NULL,
         failure to load any extension}
 */
/*---------------------------------------------------------------------------*/
muse_image *
muse_image_load(const char *aFilename)
{
  return muse_image_load_internal(aFilename, 0, __func__);
} /* muse_image_load() */

/*---------------------------------------------------------------------------*/
/**
  @brief    Load the three extensions and the FITS headers of a MUSE image
            from extensions of a merged file.
  @param    aFilename      name of the file to load
  @param    aIFU           IFU number to search for
  @return   a new <tt><b>muse_image *</b></tt> or NULL on error
  @remark   The new image has to be deallocated using
            <tt><b>muse_image_delete()</b></tt>.
  @remark   The FITS keywords of the primary FITS header will be merged with
            the HIERARCH keywords of the CHANnn.DATA extension.

  <tt><b>muse_image_new()</b></tt> is used to create a new <tt><b>muse_image
  *</b></tt>. The primary FITS header is loaded into the header element using @c
  cpl_propertylist_load().  The other three extensions are then loaded from the
  FITS extensions with the expected EXTNAMEs (CHANnn.NAME, where NAME is one of
  DATA, DQ, and STAT), using @c cpl_image_load() to allocate and load the three
  image components of the <tt><b>muse_image</b></tt>.

  This function does not display any error messsage, but the user can take the
  error state to print one, if necessary.

  @error{return NULL\, propagate CPL error code,
         cpl_propertylist_load() fails (this handles the case of an invalid or empty filename) }
  @error{set MUSE_ERROR_READ_DATA\, MUSE_ERROR_READ_DQ\, or MUSE_ERROR_READ_STAT depending on failed extension\, clean up allocations\, and return NULL,
         failure to load any extension}
 */
/*---------------------------------------------------------------------------*/
muse_image *
muse_image_load_from_extensions(const char *aFilename, unsigned char aIFU)
{
  return muse_image_load_internal(aFilename, aIFU, __func__);
} /* muse_image_load_from_extensions() */

/*---------------------------------------------------------------------------*/
/**
  @brief    Load raw image into the data extension of a MUSE image
  @param    aFilename    name of the file to load
  @param    aExtension   the FITS extension number
  @return   a new muse_image * or NULL on error
  @remark   The new image has to be deallocated using muse_image_delete().

  This function loads the specified extension from a file. The dq image is
  filled with 0 (no flaws) and the stat image is created (also filled with 0.0);
  this requires the caller of this function to fill in a correct estimate of
  the variance later!

  The FITS header of the output image are created by merging the primary header
  with the extension header of the given extension.

  Since the returned data is not corrected for the gain, the BUNIT in the header
  is set to "adu".

  @error{return NULL and propagate CPL error code, cpl_image_load() fails}
  @error{return NULL but set MUSE_ERROR_CHIP_NOT_LIVE,
         cpl_image_load() fails but chip is known to be dead}
 */
/*---------------------------------------------------------------------------*/
muse_image *
muse_image_load_from_raw(const char *aFilename, int aExtension)
{
  muse_image *mImage = muse_image_new();

  cpl_errorstate prestate = cpl_errorstate_get();
  /* Raw images are always integer.                   *
   * The cast to float happens implicitely on loading */
  mImage->data = cpl_image_load(aFilename, CPL_TYPE_FLOAT, 0, aExtension);
  char *channel = NULL;
  if (!mImage->data) {
    muse_image_delete(mImage);

    cpl_propertylist *header = cpl_propertylist_load(aFilename, aExtension);
    if (!header) {
      cpl_msg_error(__func__, "Image \"%s\" (extension %d) could not be read: %s",
                    aFilename, aExtension, cpl_error_get_message());
      return NULL;
    }
    /* check if the header contains DET.CHIP.LIVE=F; if so, *
     * set a special error to not fail the whole recipe     */
    cpl_boolean live = muse_pfits_get_chip_live(header);
    channel = cpl_strdup(muse_pfits_get_extname(header));
    cpl_propertylist_delete(header);
    if (live) { /* something is wrong with the file! */
      cpl_msg_error(__func__, "Image \"%s[%s]\" (extension %d) could not be read "
                    "although chip is alive: %s", aFilename, channel, aExtension,
                    cpl_error_get_message());
      cpl_free(channel);
      return NULL;
    }
    cpl_msg_warning(__func__, "Image \"%s[%s]\" (extension %d) could not be read,"
                    " but chip is dead: %s", aFilename, channel, aExtension,
                    cpl_error_get_message());
    cpl_errorstate_set(prestate);
    cpl_error_set_message(__func__, MUSE_ERROR_CHIP_NOT_LIVE, "Image \"%s[%s]\" "
                          "(extension %d) is dead", aFilename, channel,
                          aExtension);
    cpl_free(channel);
    return NULL;
  } /* if data not loaded */

  /* Create an empty DQ extension, signifying a CCD without flaws. *
   * As a first guess this should be adequate.                     */
  mImage->dq = cpl_image_new(cpl_image_get_size_x(mImage->data),
                             cpl_image_get_size_y(mImage->data),
                             CPL_TYPE_INT); /* is initialized to 0 */

  /* create STAT extension filled with zeros; this must be replaced later *
   * by the caller of this function                                       */
  mImage->stat = cpl_image_new(cpl_image_get_size_x(mImage->data),
                               cpl_image_get_size_y(mImage->data),
                               CPL_TYPE_FLOAT); /* is initialized to 0 */

  /* load and merge primary and extension header */
  mImage->header = cpl_propertylist_load(aFilename, 0);
  if (aExtension > 0) {
    cpl_propertylist *extHeader = cpl_propertylist_load(aFilename, aExtension);
    /* leave EXTNAME in place but remove other stuff related to extensions: */
    cpl_propertylist_copy_property_regexp(mImage->header, extHeader,
                                          "^XTENSION$|"
                                          "^CHECKSUM$|"
                                          "^DATASUM$", 1);
    cpl_propertylist_delete(extHeader);
  }
  prestate = cpl_errorstate_get();
  channel = cpl_strdup(muse_pfits_get_extname(mImage->header));
  if (!cpl_errorstate_is_equal(prestate)) { /* ignore missing EXTNAME */
    cpl_errorstate_set(prestate);
  }
  /* make sure that the unit is correct */
  cpl_propertylist_update_string(mImage->header, "BUNIT", "adu");
  cpl_propertylist_set_comment(mImage->header, "BUNIT",
                               "DATA is in analog-to-digital units");
  cpl_msg_info(__func__, "loaded \"%s[%s]\" (extension %d)", aFilename,
               channel ? channel : "0", aExtension);
  cpl_free(channel);

  return mImage;
} /* muse_image_load_from_raw() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Save the three image extensions and the FITS headers of a MUSE
            image to a file.
  @param    aImage         input MUSE image
  @param    aFilename      name of the output file
  @return   CPL_ERROR_NONE or the relevant cpl_error_code on error
  @remark   The primary headers of the output file will be constructed from
            the header member of the <tt><b>muse_image</b></tt> structure.
  @remark   The extension headers will only contain the minimal keywords,
            plus EXTNAMEs that advertise their function (DATA, DQ, STAT) and a
            comment explaining their purpose.

  Just calls @c cpl_image_save() for the three components of a
  <tt><b>muse_image</b></tt>, using the keywords in the header element of the
  <tt><b>muse_image</b></tt> structure for construction of the primary header.

  This function uses @ref muse_utils_set_hduclass() to add the special FITS
  headers to support the ESO format.

  The data unit ("BUNIT") of the DATA and STAT extensions are set to be the
  incoming BUNIT (from the header component) and (BUNIT)**2, respectively.
  BUNIT and WCS keys are removed from the primary header before saving.

  @error{return CPL_ERROR_NULL_INPUT,
         aImage\, its data component\, or aFilename are NULL}
  @error{return CPL_ERROR_INCOMPATIBLE_INPUT,
         the header component of aImage does not contain a BUNIT}
  @error{return CPL error code,
         failure to save any of the three components\, if the respective component is non-NULL}
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
muse_image_save(muse_image *aImage, const char *aFilename)
{
  cpl_ensure_code(aImage && aImage->data && aFilename, CPL_ERROR_NULL_INPUT);
  cpl_ensure_code(cpl_propertylist_has(aImage->header, "BUNIT"),
                  CPL_ERROR_INCOMPATIBLE_INPUT);

  /* BUNIT should only be saved in the image extension(s), *
   * so save a header without it                           */
  cpl_propertylist *header = cpl_propertylist_duplicate(aImage->header);
  cpl_propertylist_erase(header, "BUNIT");
  /* do not save the WCS keywords, either */
  cpl_propertylist_erase_regexp(header, MUSE_WCS_KEYS, 0);
  /* and remove ESO format headers, if they exist */
  cpl_propertylist_erase_regexp(header, ESO_HDU_HEADERS_REGEXP, 0);

  /* Save the FITS header first, as the primary header of the file. *
   * The extensions will contain only minimal image headers.        */
  cpl_error_code error = cpl_propertylist_save(header, aFilename, CPL_IO_CREATE);
  cpl_propertylist_delete(header);
  if (error != CPL_ERROR_NONE) {
    cpl_msg_error(__func__, "Could not save header: %s",
                  cpl_error_get_message());
    return error;
  }

  /* minimal header information for the extensions to add *
   * extension name information                           */
  cpl_propertylist *extheader = cpl_propertylist_new();
  cpl_propertylist_append_bool(extheader, "INHERIT", 1);

  /* copy WCS information to minimal extension header */
  cpl_propertylist_copy_property_regexp(extheader, aImage->header,
                                        MUSE_WCS_KEYS, 0);

  cpl_propertylist_append_string(extheader, "EXTNAME", EXTNAME_DATA);
  cpl_propertylist_set_comment(extheader, "EXTNAME", EXTNAME_DATA_COMMENT);
  const char *unit = muse_pfits_get_bunit(aImage->header),
             *ucomment = cpl_propertylist_get_comment(aImage->header, "BUNIT");
  cpl_propertylist_append_string(extheader, "BUNIT", unit);
  cpl_propertylist_set_comment(extheader, "BUNIT", ucomment);
  muse_utils_set_hduclass(extheader, "DATA", EXTNAME_DATA,
                          aImage->dq ? EXTNAME_DQ : NULL,
                          aImage->stat ? EXTNAME_STAT : NULL);
  error = cpl_image_save(aImage->data, aFilename, CPL_TYPE_FLOAT,
                         extheader, CPL_IO_EXTEND);
  if (error != CPL_ERROR_NONE) {
    cpl_msg_error(__func__, "Could not append data image: %s",
                  cpl_error_get_message());
    cpl_propertylist_delete(extheader);
    return error;
  } /* if error */

  if (aImage->dq) {
    cpl_propertylist_set_string(extheader, "EXTNAME", EXTNAME_DQ);
    cpl_propertylist_set_comment(extheader, "EXTNAME", EXTNAME_DQ_COMMENT);
    cpl_propertylist_erase(extheader, "BUNIT"); /* no unit for data quality */
    muse_utils_set_hduclass(extheader, "QUALITY", EXTNAME_DATA, EXTNAME_DQ,
                            aImage->stat ? EXTNAME_STAT : NULL);
    error = cpl_image_save(aImage->dq, aFilename, CPL_TYPE_INT,
                           extheader, CPL_IO_EXTEND);
    if (error != CPL_ERROR_NONE) {
      cpl_msg_error(__func__, "Could not append dq image: %s",
                    cpl_error_get_message());
      cpl_propertylist_delete(extheader);
      return error;
    } /* if error */
  } /* if dq */

  if (aImage->stat) {
    cpl_propertylist_set_string(extheader, "EXTNAME", EXTNAME_STAT);
    cpl_propertylist_set_comment(extheader, "EXTNAME", EXTNAME_STAT_COMMENT);
    char *ustat = cpl_sprintf("(%s)**2", unit); /* variance in squared units */
    cpl_propertylist_update_string(extheader, "BUNIT", ustat);
    cpl_free(ustat);
    muse_utils_set_hduclass(extheader, "ERROR", EXTNAME_DATA,
                            aImage->dq ? EXTNAME_DQ : NULL, EXTNAME_STAT);
    error = cpl_image_save(aImage->stat, aFilename, CPL_TYPE_FLOAT,
                           extheader, CPL_IO_EXTEND);
    if (error != CPL_ERROR_NONE) {
      cpl_msg_error(__func__, "Could not append stat image: %s",
                    cpl_error_get_message());
      cpl_propertylist_delete(extheader);
      return error;
    } /* if error */
  } /* if stat */
  cpl_propertylist_delete(extheader);

  return CPL_ERROR_NONE;
} /* muse_image_save() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Duplicate the three image extensions and the FITS headers of a
            MUSE image
  @param    aImage         input MUSE image
  @return   the newly created <tt><b>muse_image *</b></tt> or NULL on error
  @remark   The new image has to be deallocated using
            <tt><b>muse_image_delete()</b></tt>.

  Just calls <tt><b>muse_image_new()</b></tt> and uses CPL to copy the four
  components.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL, aImage is NULL}
  @error{propagate CPL error code\, return NULL,
         failure to duplicate any of the four components}
 */
/*----------------------------------------------------------------------------*/
muse_image *
muse_image_duplicate(const muse_image *aImage)
{
  cpl_ensure(aImage, CPL_ERROR_NULL_INPUT, NULL);

  muse_image *image = muse_image_new();
  image->data = cpl_image_duplicate(aImage->data);
  image->dq = cpl_image_duplicate(aImage->dq);
  image->stat = cpl_image_duplicate(aImage->stat);
  image->header = cpl_propertylist_duplicate(aImage->header);
  if (!image->data || !image->dq || !image->stat || !image->header) {
    muse_image_delete(image);
    return NULL;
  }
  return image;
} /* muse_image_duplicate() */

/*---------------------------------------------------------------------------*/
/**
  @private
  @brief   merge two DQ images
  @param   aDQ1   the first DQ image
  @param   aDQ2   the second DQ image to merge into the first
  @return  0 on success or a different value in case of failure
  @remark  This function directly changes the first DQ image.

  Loop through all pixels. If the bad pixel values are non-zero, merge them
  (using binary OR).

  @error{return -1, first input image is NULL}
  @error{return -2, second input image is NULL}
  @error{propagate CPL error, other image handling problem}
 */
/*---------------------------------------------------------------------------*/
static int
muse_image_dq_merge(cpl_image *aDQ1, cpl_image *aDQ2)
{
  cpl_ensure(aDQ1, CPL_ERROR_NULL_INPUT, -1);
  cpl_ensure(aDQ2, CPL_ERROR_NULL_INPUT, -2);

  /* get the output dq image buffer as normal int */
  int *dq1 = cpl_image_get_data_int(aDQ1);
  /* get the subtract dq image buffer as const because we don't want to change it */
  const int *dq2 = cpl_image_get_data_int_const(aDQ2);
  if (!dq1 || !dq2) {
    return cpl_error_get_code();
  }

  int i,
      nx = cpl_image_get_size_x(aDQ1),
      ny = cpl_image_get_size_y(aDQ1);
  for (i = 0; i < nx; i++) {
    int j;
    for (j = 0; j < ny; j++) {
      /* If the second pixel is bad somehow, merge it with the dq flag   *
       * of the first image. The output pixel gets a combined dq status. */
      if (dq2[i + j*nx]) {
        dq1[i + j*nx] |= dq2[i + j*nx];
      }
    } /* for j */
  } /* for i */

  return 0;
} /* muse_image_dq_merge() */

/*---------------------------------------------------------------------------*/
/**
  @brief   Subtract a muse_image from another with correct treatment of bad
           pixels and variance.
  @param   aImage      image to be changed
  @param   aSubtract   the image to subtract
  @return  0 on success or a different value in case of failure
  @remark  This function directly changes the input image.

  Subtract the image aSubtract from input image aImage and modify the
  variance image accordingly (by adding to the variance). The bad pixel
  extension of both input images are merged.

  @error{return -1, first input image is NULL}
  @error{return -2, second input image is NULL}
  @error{propagate CPL error, cpl_image_subtract or cpl_image_add fails}
 */
/*---------------------------------------------------------------------------*/
int
muse_image_subtract(muse_image *aImage, muse_image *aSubtract)
{
  cpl_ensure(aImage, CPL_ERROR_NULL_INPUT, -1);
  cpl_ensure(aSubtract, CPL_ERROR_NULL_INPUT, -2);

  cpl_error_code rc = cpl_image_subtract(aImage->data, aSubtract->data);
  if (rc != CPL_ERROR_NONE) {
    cpl_msg_error(__func__, "failure while subtracting data extension");
    return rc;
  }

  rc = cpl_image_add(aImage->stat, aSubtract->stat);
  if (rc != CPL_ERROR_NONE) {
    cpl_msg_error(__func__, "failure for stat extension");
    return rc;
  }

  rc = muse_image_dq_merge(aImage->dq, aSubtract->dq);
  if (rc) {
    cpl_msg_error(__func__, "failure for dq extension");
    return rc;
  }

  return 0;
} /* muse_image_subtract() */

/*---------------------------------------------------------------------------*/
/**
  @brief   Divide a muse_image by another with correct treatment of bad
           pixels and variance.
  @param   aImage      image to be changed
  @param   aDivisor    the image to divide by
  @return  0 on success or a different value in case of failure
  @remark  This function directly changes the input image.

  Divide the data of aImage by aDivisor and modify the variance image
  accordingly (by multiplying the variance). The bad pixel extension of both
  input images are merged.

  @error{return -1, first input image is NULL}
  @error{return -2, second input image is NULL}
  @error{propagate CPL error,
         cpl_image_divide\, cpl_image_multiply\, or cpl_image_add fails}
 */
/*---------------------------------------------------------------------------*/
int
muse_image_divide(muse_image *aImage, muse_image *aDivisor)
{
  cpl_ensure(aImage, CPL_ERROR_NULL_INPUT, -1);
  cpl_ensure(aDivisor, CPL_ERROR_NULL_INPUT, -2);

  /* Gaussian error propagation for a division:
   * variance_out = (variance_in + in^2 * variance_divisor / divisor^2 ) / divisor^2
   *                               ^------------- term2 -------------^
   */
  cpl_image *term2 = cpl_image_power_create(aImage->data, 2);

  cpl_error_code rc = cpl_image_divide(aImage->data, aDivisor->data);
  if (rc != CPL_ERROR_NONE) {
    cpl_msg_error(__func__, "failure while dividing data extension");
    cpl_image_delete(term2);
    return rc;
  }

  cpl_image *divsq = cpl_image_power_create(aDivisor->data, 2);
  rc = cpl_image_multiply(term2, aDivisor->stat);

  /* first access to stat, better check error */
  if (rc != CPL_ERROR_NONE) {
    cpl_msg_error(__func__, "failure while accessing stat extension of divisor");
    cpl_image_delete(term2);
    cpl_image_delete(divsq);
    return rc;
  }
  cpl_image_divide(term2, divsq);
  rc = cpl_image_add(aImage->stat, term2);
  if (rc != CPL_ERROR_NONE) {
    cpl_msg_error(__func__, "failure while accessing stat extension of image");
    cpl_image_delete(term2);
    cpl_image_delete(divsq);
    return rc;
  }
  cpl_image_delete(term2);
  cpl_image_divide(aImage->stat, divsq);
  cpl_image_delete(divsq);

  rc = muse_image_dq_merge(aImage->dq, aDivisor->dq);
  if (rc) {
    cpl_msg_error(__func__, "failure for dq extension");
    return rc;
  }
  return 0;
} /* muse_image_divide() */

/*---------------------------------------------------------------------------*/
/**
  @brief    Scale a muse_image with correct treatment of variance.
  @param    aImage   image to be changed
  @param    aScale   the scale factor
  @return   0 on success or a different value in case of failure
  @remark   This function directly changes the input image.

  Apply the scale to the input data extension and apply it squared to the
  stat extension to keep the varianace in sync with the data.

  @error{return -1, input image is NULL}
  @error{propagate CPL error, cpl_image_multiply_scalar fails}
 */
/*---------------------------------------------------------------------------*/
int
muse_image_scale(muse_image *aImage, double aScale)
{
  cpl_ensure(aImage, CPL_ERROR_NULL_INPUT, -1);

  /* scale the data extension */
  cpl_error_code rc = cpl_image_multiply_scalar(aImage->data, aScale);
  if (rc != CPL_ERROR_NONE) {
    cpl_msg_error(__func__, "failure while scaling data extension");
    return rc;
  }

  /* scale the stat extension (squared as this is the variance) */
  rc = cpl_image_multiply_scalar(aImage->stat, aScale*aScale);
  if (rc != CPL_ERROR_NONE) {
    cpl_msg_error(__func__, "failure while scaling stat extension");
    return rc;
  }

  return 0;
} /* muse_image_scale() */

/*---------------------------------------------------------------------------*/
/**
  @brief    Create the photon noise-based variance in the stat extension
  @param    aImage   input muse_image to work with
  @param    aBias    input bias image to take the RON from
  @return   0 on success or a different value in case of failure
  @remark   This function directly changes the input image.
  @note     Warning: this function deletes an already existing stat extension!

  This function computes the photon noise part of the variance formula (but in
  units of adu**2)
  i.e.    sigma_photon^2 = (counts - bias) / gain
  (where gain is the conversion factor with the unit count/adu).
  The resulting sigma_photon^2 is ensured to be larger than zero by cutting
  off low values (that can occur due to noise in the bias level at low count
  values).

  @error{return -1, input image is NULL}
  @error{return -2, input bias is NULL}
  @error{return -3, input images are of different size}
 */
/*---------------------------------------------------------------------------*/
int
muse_image_variance_create(muse_image *aImage, muse_image *aBias)
{
  cpl_ensure(aImage, CPL_ERROR_NULL_INPUT, -1);
  cpl_ensure(aBias, CPL_ERROR_NULL_INPUT, -2);
  int nx = cpl_image_get_size_x(aImage->stat),
      ny = cpl_image_get_size_y(aImage->stat),
      nxbias = cpl_image_get_size_x(aBias->stat),
      nybias = cpl_image_get_size_y(aBias->stat);
  cpl_ensure(nx == nxbias && ny == nybias, CPL_ERROR_INCOMPATIBLE_INPUT, -3);

  /* the easiest is, to remove the existing stat extension and *
   * create a new image in its place (otherwise one would need *
   * to check that it is still zero everywhere                 */
  cpl_image_delete(aImage->stat);
  aImage->stat = cpl_image_subtract_create(aImage->data, aBias->data);
  float *pixstat = cpl_image_get_data_float(aImage->stat);

  /* now loop through all quadrants and their pixels and divide *
   * by the gain value                                          */
  unsigned char n;
  for (n = 1; n <= 4; n++) {
    double gain = muse_pfits_get_gain(aImage->header, n); /* in count/adu */
    cpl_size *window = muse_quadrants_get_window(aImage, n);

    int i;
    for (i = window[0] - 1; i < window[1]; i++) {
      int j;
      for (j = window[2] - 1; j < window[3]; j++) {
        pixstat[i + j*nx] /= gain;
        /* if the photon variance turns out to be too low, we have to reset *
         * it, because variance cannot take a negative or zero value        */
        if (pixstat[i + j*nx] <= 0.0) {
          /* set it to the smallest value possible for 32bit float data */
          pixstat[i + j*nx] = FLT_MIN;
        }
      } /* for j (y pixels) */
    } /* for i (x pixels) */
    cpl_free(window);
  } /* for n (quadrants) */

  return 0;
} /* muse_image_variance_create() */

/*---------------------------------------------------------------------------*/
/**
  @brief    Convert the data units from raw adu to count (= electron) units.
  @param    aImage   input muse_image to work with
  @return   CPL_ERROR_NONE on success or a different cpl_error_code in case of
            failure
  @remark   This function directly changes the input image.

  This function gets the gain value (in count/adu) for each quadrant from the
  FITS header and then multiplies the data extension with this gain value and
  multiplies the variance extension with the squared gain value.

  @error{set and return CPL_ERROR_NULL_INPUT,
         aImage or its header component are NULL}
  @error{set and return CPL_ERROR_INCOMPATIBLE_INPUT,
         the header component of aImage does not contain BUNIT or its BUNIT is not "adu"}
  @error{set and return CPL_ERROR_ILLEGAL_INPUT,
         the data buffer for the data and/or stat components cannot be retrieved}
 */
/*---------------------------------------------------------------------------*/
cpl_error_code
muse_image_adu_to_count(muse_image *aImage)
{
  cpl_ensure_code(aImage && aImage->header, CPL_ERROR_NULL_INPUT);
  cpl_ensure_code(cpl_propertylist_has(aImage->header, "BUNIT") &&
                  !strncmp(muse_pfits_get_bunit(aImage->header), "adu", 4),
                  CPL_ERROR_INCOMPATIBLE_INPUT);
  int nx = cpl_image_get_size_x(aImage->data);
  float *data = cpl_image_get_data_float(aImage->data),
        *stat = cpl_image_get_data_float(aImage->stat);
  cpl_ensure_code(data && stat, CPL_ERROR_ILLEGAL_INPUT);

  unsigned char n;
  for (n = 1; n <= 4; n++) {
    double gain = muse_pfits_get_gain(aImage->header, n); /* gain in count/adu */
    cpl_size *w = muse_quadrants_get_window(aImage, n);
#if 0
    cpl_msg_debug(__func__, "looping %"CPL_SIZE_FORMAT"...%"CPL_SIZE_FORMAT
                  " %"CPL_SIZE_FORMAT"...%"CPL_SIZE_FORMAT"",
                  w[0], w[1], w[2], w[3]);
#endif
    /* loop through this window and multiply counts by gain to get count */
    int i;
    for (i = w[0] - 1; i < w[1]; i++) {
      int j;
      for (j = w[2] - 1; j < w[3]; j++) {
        data[i + j*nx] *= gain;
        stat[i + j*nx] *= gain*gain;
      } /* for j (window y pixels) */
    } /* for i (window x pixels) */
    cpl_free(w);
  } /* for n (quadrants) */
  cpl_propertylist_update_string(aImage->header, "BUNIT", "count");
  cpl_propertylist_set_comment(aImage->header, "BUNIT", "DATA is in electrons");
  return CPL_ERROR_NONE;
} /* muse_image_adu_to_count() */

/*---------------------------------------------------------------------------*/
/**
  @brief    Reject pixels of a muse_image depending on its DQ data.
  @param    aImage   the image to process
  @return   CPL_ERROR_NONE on success, another CPL error code on failure

  Pixels in the data (and, if present, stat) extension of the input muse_image
  aImage are set as bad depending on the dq extension (all non-zero DQ values
  are assumed to be bad), using cpl_image_reject().
  This is useful to let CPL functions automatically ignore the bad pixels.

  @error{return CPL_ERROR_NULL_INPUT,
         aImage or its data or dq extensions are NULL}
 */
/*---------------------------------------------------------------------------*/
cpl_error_code
muse_image_reject_from_dq(muse_image *aImage)
{
  cpl_ensure_code(aImage && aImage->data && aImage->dq, CPL_ERROR_NULL_INPUT);
  int nx = cpl_image_get_size_x(aImage->data),
      ny = cpl_image_get_size_y(aImage->data);
  const int *dq = cpl_image_get_data_int_const(aImage->dq);
  int i, j;
  for (i = 0; i < nx; i++) {
    for (j = 0; j < ny; j++) {
      if (!dq[i + j*nx]) { /* dq == 0 means bad */
        continue;
      }
      cpl_image_reject(aImage->data, i+1, j+1);
      if (aImage->stat) {
        cpl_image_reject(aImage->stat, i+1, j+1);
      }
    } /* for j (columns) */
  } /* for i (rows) */

  return CPL_ERROR_NONE;
} /* muse_image_reject_from_dq() */

/*---------------------------------------------------------------------------*/
/**
  @brief    Convert pixels flagged in the DQ extension to NANs in DATA
            (and STAT, if present).
  @param    aImage   the image to process
  @return   CPL_ERROR_NONE on success, another CPL error code on failure

  Pixels in the data (and, if present, stat) component of the input muse_image
  aImage are set as NAN depending on the dq component (all non-zero DQ values
  are assumed to be bad). The dq component is then deallocated and set to NULL.

  This should be used only directly before saving an image, because the pipeline
  then cannot use DQ or other checks any more.

  @error{return CPL_ERROR_NULL_INPUT,
         aImage or its data or dq extensions are NULL}
 */
/*---------------------------------------------------------------------------*/
cpl_error_code
muse_image_dq_to_nan(muse_image *aImage)
{
  cpl_ensure_code(aImage && aImage->data && aImage->dq, CPL_ERROR_NULL_INPUT);

  /* get pointers to the 2 mandatory components */
  int *pdq = cpl_image_get_data_int(aImage->dq);
  float *pdata = cpl_image_get_data_float(aImage->data),
        *pstat = NULL;
  /* get the pointers to optional stat component as well, if possible */
  if (aImage->stat) {
    pstat = cpl_image_get_data_float(aImage->stat);
  }
  int i, nx = cpl_image_get_size_x(aImage->data),
      ny = cpl_image_get_size_y(aImage->data);
  for (i = 0; i < nx; i++) {
    int j;
    for (j = 0; j < ny; j++) {
      if (pdq[i + j*nx] == EURO3D_GOODPIXEL) {
        continue; /* nothing to do for good pixels */
      }
      /* set bad pixels of any type to not-a-number in both extensions */
      pdata[i + j*nx] = NAN; /* supposed to be quiet NaN */
      if (pstat) {
        pstat[i + j*nx] = NAN;
      }
    } /* for j (y direction) */
  } /* for i (x direction) */

  /* deallocate DQ and set it to NULL to be able to check for it */
  cpl_image_delete(aImage->dq);
  aImage->dq = NULL;

  return CPL_ERROR_NONE;
} /* muse_image_dq_to_nan() */

/*---------------------------------------------------------------------------*/
/**
  @brief    Create a mask at the border of a given image.
  @param    aImage   the image to use for sizing the mask
  @param    aWidth   the width in pixels of the border to mask
  @return   a new cpl_mask on success, NULL on failure

  A new cpl_mask is created, with CPL_BINARY_0 in the center, but a border
  of size aWidth filled with CPL_BINERY_1.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL,
         aImage or its data extensions are NULL}
 */
/*---------------------------------------------------------------------------*/
cpl_mask *
muse_image_create_border_mask(const muse_image *aImage, unsigned int aWidth)
{
  cpl_ensure(aImage && aImage->data, CPL_ERROR_NULL_INPUT, NULL);

  int nx = cpl_image_get_size_x(aImage->data),
      ny = cpl_image_get_size_y(aImage->data);
  cpl_mask *mask = cpl_mask_new(nx, ny);
  /* left */
  muse_cplmask_fill_window(mask, 1, 1, aWidth, ny, CPL_BINARY_1);
  /* right */
  muse_cplmask_fill_window(mask, nx - aWidth + 1, 1, nx, ny, CPL_BINARY_1);
  /* bottom */
  muse_cplmask_fill_window(mask, 1, 1, nx, aWidth, CPL_BINARY_1);
  /* top */
  muse_cplmask_fill_window(mask, 1, ny - aWidth + 1, nx, ny, CPL_BINARY_1);

  return mask;
} /* muse_image_create_border_mask() */

/*---------------------------------------------------------------------------*/
/**
  @brief    Create a mask at the border of a given image.
  @param    aImage      the image to use for sizing the mask
  @param    aQuadrant   the image quadrant to use for the corner mask
  @param    aRadius     the radius in pixels from the actual image corner
  @return   a new cpl_mask on success, NULL on failure

  A new cpl_mask is created, with CPL_BINARY_0 in the center, but a corner
  of radius aRadius filled with CPL_BINERY_1. This corner is located in the
  outer edge of aQuadrant (as defined by the CCD-related headers of aImage).

  @error{set CPL_ERROR_NULL_INPUT\, return NULL,
         aImage or its data and/or header extensions are NULL}
 */
/*---------------------------------------------------------------------------*/
cpl_mask *
muse_image_create_corner_mask(const muse_image *aImage, unsigned char aQuadrant,
                              float aRadius)
{
  cpl_ensure(aImage && aImage->data && aImage->header, CPL_ERROR_NULL_INPUT,
             NULL);

  int i, j,
      nx = cpl_image_get_size_x(aImage->data),
      ny = cpl_image_get_size_y(aImage->data),
      outnx = muse_pfits_get_out_output_x(aImage->header, aQuadrant),
      outny = muse_pfits_get_out_output_y(aImage->header, aQuadrant);
  cpl_msg_debug(__func__, "Origin: %d,%d", outnx, outny);

  cpl_mask *mask = cpl_mask_new(nx, ny);
  cpl_binary *m = cpl_mask_get_data(mask);
  for (i = 1; i <= nx; i++) {
    for (j = 1; j <= ny; j++) {
      double r = sqrt((outnx - i) * (outnx - i) + (outny - j) * (outny - j));
      if (r <= aRadius) {
        m[(i-1) + (j-1)*nx] = CPL_BINARY_1;
      }
    } /* for j (columns) */
  } /* for i (rows) */

  return mask;
} /* muse_image_create_corner_mask() */

/**@}*/
