/* $Id: amber_BeamPos.c,v 1.20 2013-09-16 14:56:43 agabasch Exp $
 *
 * This file is part of the AMBER 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: agabasch $
 * $Date: 2013-09-16 14:56:43 $
 * $Revision: 1.20 $
 * $Name: not supported by cvs2svn $
 */

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

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

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

#include "amdrs.h"
#include "amber_dfs.h"

/* PAF file and QC log */
#include "giqclog.h"
/* #include "giutils.h" */


    
/*
static int                   errorType; 
static int                   pistonType; 
static int                   iBinningOfFrames;
*/
int                          iProductNumber;
char                         szFilenameProduct[512];
char                         szFilenameSCIENCE[150][512];  /* 3 bands * 50 raw frames = 150 products */ 
int                          isFrameScience[150];
cpl_frame *                  pframeSCIENCE[150];

cpl_parameterlist *   gparlist; 
cpl_frameset      *   gframelist;
/* cpl_propertylist  *   pHeader; */

FILE *                pFITSFile;

#ifdef OLD_STUFF
    amdlibERROR_MSG       errMsg;
static    amdlibRAW_DATA        rawData; /*  = {NULL}; */
    amdlibSCIENCE_DATA    sky; /*  = {NULL}; */
    amdlibSCIENCE_DATA    scienceData; /*  = {NULL}; */
    amdlibP2VM_MATRIX     p2vm; /*  = {NULL}; */
    amdlibPHOTOMETRY      photometry; /*  = {NULL}; */
    amdlibWAVELENGTH      wave; /*  = {NULL}; */
    amdlibPISTON          opd; /*  = {NULL}; */
    amdlibOI_TARGET       target; /*  = {NULL}; */
    amdlibOI_ARRAY        array; /*  = {NULL}; */
    amdlibVIS             vis; /*  = {NULL}; */
    amdlibVIS2            vis2; /*  = {NULL}; */
    amdlibVIS3            vis3; /*  = {NULL}; */
    amdlibSCIENCE_DATA    *skyPtr;
    amdlibSCIENCE_DATA    *sciencePtr;
    amdlibWAVEDATA        waveData;
#endif

    amdlibERROR_MSG       errMsg;
static    amdlibRAW_DATA        rawData ;
    amdlibSCIENCE_DATA    sky;
    amdlibSCIENCE_DATA    scienceData ;
    amdlibP2VM_MATRIX     p2vm;
    amdlibPHOTOMETRY      photometry, imdPhot;
    amdlibWAVELENGTH      wave, imdWave;
    amdlibPISTON          opd, imdOpd;
    amdlibOI_TARGET       target;
    amdlibOI_ARRAY        array;
    amdlibVIS             vis , imdVis ;
    amdlibVIS2            vis2, imdVis2;
    amdlibVIS3            vis3, imdVis3;
    amdlibSCIENCE_DATA    *skyPtr;
    amdlibSCIENCE_DATA    *sciencePtr;
    amdlibWAVEDATA        waveData;
    
    int                   band;


/*-----------------------------------------------------------------------------
                            Functions prototypes
 -----------------------------------------------------------------------------*/

/* cpl related */
static int amber_BeamPos_create(cpl_plugin *) ;
static int amber_BeamPos_exec(cpl_plugin *) ;
static int amber_BeamPos_destroy(cpl_plugin *) ;
static int amber_BeamPos(cpl_parameterlist *, cpl_frameset *) ;

static cpl_image * amber_smooth_image_median(
        const cpl_image     *   in,
        int                     size_x,
        int                     size_y);

/* amdlib */
cpl_propertylist * BeamPosCreateProductHeader(const char * fctid,
		char * szRawFile, cpl_frame * pFrameProduct);
void GetPrefix(int iFrame, char * szPrefix);

/*-----------------------------------------------------------------------------
                            Static variables
 -----------------------------------------------------------------------------*/

/*
static struct {
     Inputs
    int         bool_option ;
    char        str_option[512] ;

     Outputs
    double      qc_param ;
} amber_BeamPos_config ;
*/

static char amber_BeamPos_man[] =
"This recipe calculates the Beam's x position, y position, flux and size in x and y\n"
"\n";

/*-----------------------------------------------------------------------------
                                Functions code
 -----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/**
  @brief    Append the properties in one list to another
  @param    self   The PAF propertylist to append to
  @param    other  The propertylist whose elements are copied or NULL
  @return   CPL_ERROR_NONE or the relevant #_cpl_error_code_ on error

  If other is NULL or empty nothing happens

  Comments are copied as well, but it is (currently) undefined how this works
  for non-unique properties.

  In addition the hierarchical keys will be changed according to the PAF rules:

  No ESO as first part
  No blank separator but a .
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code amber_propertylist_append(cpl_propertylist * self,
                                                const cpl_propertylist * other)
{

    int i, c;
    char szPAF[2048];
    char szTMP[2048];

    if (other == NULL) return CPL_ERROR_NONE;

    /*visir_assure_code(self, CPL_ERROR_NULL_INPUT);*/

    for (i=0; i < cpl_propertylist_get_size(other); i++)
    {
        const cpl_property * prop = cpl_propertylist_get((cpl_propertylist *)
                                                         other, i);
        const char * name    = cpl_property_get_name(prop);
        /*const char * comment = cpl_property_get_comment(prop);*/

        cpl_error_code err;

	/* Change the key to PAF syntax */
	strcpy( szPAF, name );

	/* Get rid of ESO (HAR, what a joke!) */
	if( !strncmp( szPAF, "ESO", 3 ) )
	{
	  strcpy( szTMP, &szPAF[4] );
	  strcpy( szPAF, szTMP );
	}

	/* Substitute spaces by points (YEAH!) */
	for( c=0; c<(int)strlen(szPAF); c++ )
	  if( szPAF[c] == ' ' )	
	    szPAF[c] = '.';	

        strcpy( (char *)name, szPAF );
	
        /* Append prop to self */

        switch (cpl_property_get_type(prop)) {
        case CPL_TYPE_CHAR:
            err = cpl_propertylist_append_char(self, name,
                                               cpl_property_get_char(prop));
            break;
        case CPL_TYPE_BOOL:
            err = cpl_propertylist_append_bool(self, name,
                                               cpl_property_get_bool(prop));
            break;
        case CPL_TYPE_INT: 
            err = cpl_propertylist_append_int(self, name,
                                              cpl_property_get_int(prop));
            break;
        case CPL_TYPE_LONG: 
            err = cpl_propertylist_append_long(self, name,
                                               cpl_property_get_long(prop));
            break;
        case CPL_TYPE_FLOAT:
            err = cpl_propertylist_append_float(self, name,
                                                cpl_property_get_float(prop));
            break;
        case CPL_TYPE_DOUBLE:
            err = cpl_propertylist_append_double(self, name,
                                                 cpl_property_get_double(prop));
            break;
        case CPL_TYPE_STRING:
            err = cpl_propertylist_append_string(self, name,
                                                 cpl_property_get_string(prop));
            break;

	/* Avoid compiler warnings but do nothing */    
        case CPL_TYPE_FLAG_ARRAY:
        case CPL_TYPE_INVALID:
        case CPL_TYPE_UCHAR:
        case CPL_TYPE_UINT:
        case CPL_TYPE_ULONG:
        case CPL_TYPE_POINTER:
        case CPL_TYPE_FLOAT_COMPLEX:
        case CPL_TYPE_DOUBLE_COMPLEX:
        case CPL_TYPE_UNSPECIFIED:
        case CPL_TYPE_BITMASK:
        case CPL_TYPE_COMPLEX:
        case CPL_TYPE_LONG_LONG:
        case CPL_TYPE_SHORT:
        case CPL_TYPE_SIZE:
        case CPL_TYPE_USHORT:
	/*case CPL_TYPE_COMPLEX: not anymode in CPL 3.0 */
	  break;    
        /*default:
            visir_assure_code(0, CPL_ERROR_UNSUPPORTED_MODE);*/
        }

        /*visir_assure_code(!err, err);

        if (comment && cpl_propertylist_set_comment(self, name, comment))
            visir_assure_code(0, cpl_error_get_code());*/
    }

    return CPL_ERROR_NONE;
}


cpl_propertylist * BeamPosCreateProductHeader( const char * fctid, char * szRawFile, cpl_frame * pFrameProduct )
{
  //int  iStatus = 0;
  char szMessage[1024];
  
  cpl_frameset * frameSetTmp;
  cpl_frame    * frameTmp;
  cpl_propertylist *  pHeader;

  /* For DFS fill Header function later */;
/*   pHeader = cpl_propertylist_new();   */

    /* 
     * Create the Product file, start with filling the header 
     *
     * Attention: for the time of this workaround for cpl 3D tables the amdlib must
     * be patched to NOT create OWN files, but APPEND to existing ones!!
     *
     * see comment below
     * 
     */


     /* 
      * Workaround for cpl_dfs_setup_product_header picking the wrong Header in this CPL release and
      * also might pick the wrong in the future! It uses the first RAW frame, but this recipe can handle 
      * many raw frames. Hence:
      *
      * Read the Header of the RAW file to be written as a product and send it to the function
     */ 
     pHeader = cpl_propertylist_load(  szRawFile, 0 ); 

     /* Create a set of frames with just this frame, so header will be correct */
     frameSetTmp = cpl_frameset_new();
     frameTmp    = cpl_frame_new();
     
     cpl_frame_set_filename( frameTmp, szRawFile ); 
     cpl_frame_set_type( frameTmp, CPL_FRAME_TYPE_TABLE );
     cpl_frame_set_tag( frameTmp, "AMBER_BEAMPOS" );
     cpl_frame_set_group( frameTmp, CPL_FRAME_GROUP_RAW ); 
     cpl_frame_set_level( frameTmp, CPL_FRAME_LEVEL_NONE ); 
     cpl_frameset_insert( frameSetTmp, frameTmp ); 

     /* Add the necessary DFS fits header information to the product */
#if defined CPL_VERSION_CODE && CPL_VERSION_CODE >= CPL_VERSION(4, 8, 0)
     if( cpl_dfs_setup_product_header(  pHeader,  
                                        pFrameProduct,   
                                        frameSetTmp,  
                                        gparlist,  
                                        "amber_BeamPos", /* const char *  recid,  */
                                        PACKAGE "/" PACKAGE_VERSION, /* const char *  pipeline_id,  */
                                        "AMBER",  /* const char *  dictionary_id */
                                        NULL
                                     )  != CPL_ERROR_NONE )
     {
       /* Error */
       sprintf( szMessage, "Error in setting up the product header." ); 
       cpl_msg_info( fctid, "%s", szMessage ); 
       //iStatus = 16;
     }
#else 
     if( cpl_dfs_setup_product_header(  pHeader,  
                                        pFrameProduct,   
                                        frameSetTmp,  
                                        gparlist,  
                                        "amber_BeamPos", /* const char *  recid,  */
                                        PACKAGE "/" PACKAGE_VERSION, /* const char *  pipeline_id,  */
                                        "AMBER"  /* const char *  dictionary_id */
                                     )  != CPL_ERROR_NONE )
     {
       /* Error */
       sprintf( szMessage, "Error in setting up the product header." ); 
       cpl_msg_info( fctid, "%s", szMessage ); 
       iStatus = 16;
     }

#endif 

     /* Destroy tmpFrameset and implicitly its contents */
     cpl_frameset_delete( frameSetTmp );
     /*cpl_frame_delete( frameTmp );*/
     
  return pHeader;
} 

/*----------------------------------------------------------------------------*/
/**
  @brief    Calculate statistics on image subwindow
  @param    pImage         background subtracted image of type CPL_TYPE_FLOAT,
                           containing no bad pixels
  @param    threshold      only pixels above this level will contribute to the
                           computation
  @param    llx            lower left corner (x), inclusive, FITS convention
  @param    lly            lower left corner (y), inclusive, FITS convention
  @param    urx            upper right corner (x), inclusive, FITS convention
  @param    ury            upper right corner (y), inclusive, FITS convention
  @param    cx             (output) centroid (x)
  @param    cy             (output) centroid (y)
  @param    sigma_x        (output) standard deviation wrt to 1st axis
  @param    sigma_y        (output) standard deviation wrt to 2nd axis
  @param    flux           (output) integrated flux
  @param    angle          (output) angle of rotation of the object axis. Of the
                           two possible object axes (90 deg difference), this
                           returned angle is always the one closest to the x-axis
  @return   zero if and only if okay

 */
/*----------------------------------------------------------------------------*/
static int get_centroid_stdev_flux_angle( const cpl_image *pImage,
					  double threshold,
					  int llx, int lly,
					  int urx, int ury,
					  double *cx, double *cy,
					  double *sigma_x, double *sigma_y,
					  double *flux, double *angle, double *peakflux )
{
    const char *fctid = "get_centroid_stdev_flux_angle";

    double pi = 3.1415926535897932384626433832795;

    const float *data = cpl_image_get_data_float_const( pImage );
    int    nx   = cpl_image_get_size_x( pImage );
    double s, sx, sy;
    double Ixx, Iyy, Ixy, I1, I2;
    int x, y;


    cpl_bivector* result_iqefit;

    /* Get centroid, flux */
    s = 0;
    sx = 0;
    sy = 0;
    for (y = lly - 1; y < ury - 1; y++)
	{
	    for (x = llx - 1; x < urx - 1; x++)
		{
		    float pixel = data[x + y*nx];

		    if (pixel > threshold)
			{
			    s   += pixel;
			    sx  += pixel*x;
			    sy  += pixel*y;
			}
		}
	}

    if (s <= 0)
	{
	    cpl_msg_warning( fctid, "Integrated flux is non-positive: %g",
			     s );
	    return 1;
	}

    *flux = s;
    *cx = sx/s;
    *cy = sy/s;

/*
  #####################################
  Fits a 2D Gauss to the beam and derives the peak flux
*/
    
/*     result_iqefit=cpl_bivector_new(7);    */
    result_iqefit=cpl_image_iqe(pImage,llx,lly,urx,ury); 		
    *peakflux=cpl_vector_get(cpl_bivector_get_x(result_iqefit), 5);
    cpl_bivector_delete(result_iqefit);    
/*
  ######################################
*/
    
    /* Rather than assuming any particular shape of the beam (e.g. Gaussian),
       compute the moment of inertia (in the CM system)

        I = [ Ixx  Ixy ]   where
            [ Iyx  Iyy ]

        Ixx =   sum_i flux_i x_i^2
        Iyy =   sum_i flux_i y_i^2
        Ixy = - sum_i flux_i x_i y_i,

        and find a rotation, so that the two moments are minimal/maximal,
	i.e. write I as

        I = C * [ Ix'x'  0    ] * C^T
                [ 0     Iy'y' ]

        where C is the rotation matrix relating the object principal
	axes to the coordinate axes, and Ix'x' and Iy'y' are the
        moments with respect to the object axes.
    */
    
    Ixx = 0;
    Iyy = 0;
    Ixy = 0;
    for (y = lly - 1; y < ury - 1; y++)
	{
	    for (x = llx - 1; x < urx - 1; x++)
		{
		    float pixel = data[x + y*nx];

		    if (pixel > threshold)
			{
			    Ixx += pixel*(x-*cx)*(x-*cx);
			    Iyy += pixel*(y-*cy)*(y-*cy);
			    Ixy -= pixel*(x-*cx)*(y-*cy);
			}
		}
	}

    /* Get eigenvalues Ix'x' and Iy'y' as solutions to
     * (Ixx - lambda)*(Iyy - lambda) - Ixy^2 = 0
     */
    I1 = (Ixx+Iyy)/2.0 + sqrt( (Ixx-Iyy)*(Ixx-Iyy)/4.0 + Ixy*Ixy);
    I2 = (Ixx+Iyy)/2.0 - sqrt( (Ixx-Iyy)*(Ixx-Iyy)/4.0 + Ixy*Ixy);

    /* No need to compute C, just get the angle of rotation.
       eigenvector = (cos(angle), -sin(angle)) is
       solution to
       (Ixx - lambda)*cos(angle) + Ixy*(-sin(angle)) = 0
       => sin(angle)/cos(angle) = (Ixx - lambda)/Ixy */

    *angle = atan2(Ixx - I1, Ixy); /* in [-pi; pi] */

    /* Mirror angle to [-pi/2 ; pi/2] */
    if (*angle >=  pi/2) *angle -= pi;
    if (*angle <= -pi/2) *angle += pi;

    /* By convention, get the angle which is within pi/4 of the
       x-axis. The output sigma_x is the stdev wrt this axis */
    if (*angle <= -pi/4)
	{
	    *angle += pi/2;
	    *sigma_x = sqrt(I2/(*flux));
	    *sigma_y = sqrt(I1/(*flux));
	}
    else if (- pi/4 <= *angle && *angle <= pi/4)
	{
	    /* angle ok */
	    *sigma_x = sqrt(I1/(*flux));
	    *sigma_y = sqrt(I2/(*flux));
	}
    else if (pi/4 <= *angle)
	{
	    *angle -= pi/2;
	    *sigma_x = sqrt(I2/(*flux));
	    *sigma_y = sqrt(I1/(*flux));
	}	

    /* Convert to FITS */
    *cx += 1;
    *cy += 1;






    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Apply n x m median filter
  @param    pImage       image containing the beam, must be of
  @param    xwindow      window width, should be an odd number
  @param    ywindow      window height, should be an odd number
  @return   The median filtered image which must be deallocated using 
            cpl_image_delete()

 The function is a workaround for cpl_image_filter_median() 
 supporting kernels no larger than 7x7
 */
/*----------------------------------------------------------------------------*/
static cpl_image *
image_filter_median( const cpl_image *image, int xwindow, int ywindow )
{
    int nx = cpl_image_get_size_x(image);
    int ny = cpl_image_get_size_y(image);
    cpl_image *result = cpl_image_new( nx, ny, CPL_TYPE_FLOAT );
    int x, y;

    if (result == NULL)
	{
	    return NULL;
	}

    for (y = 1; y <= ny; y++)
    for (x = 1; x <= nx; x++)
	{
	    double median;
	    int llx = x - (xwindow - 1) / 2;
	    int lly = y - (ywindow - 1) / 2;
	    int urx = x + (xwindow - 1) / 2;
	    int ury = y + (ywindow - 1) / 2;
	    if (llx <  1) llx = 1;
	    if (lly <  1) lly = 1;
	    if (urx > nx) urx = nx;
	    if (ury > ny) ury = ny;
	    median = cpl_image_get_median_window( image,
						  llx, lly,
						  urx, ury );

	    cpl_image_set( result, x, y, median );
	}

    return result;
}
    
/*----------------------------------------------------------------------------*/
/**
  @brief    Calculates a Beamposition, Beamsize and Beamflux from a given Image 
  @param    pImage       image containing the beam, must be of
                         type CPL_TYPE_FLOAT and contain no bad pixels
  @param    dPosX        (output) beam centroid position (x)
  @param    dPosY        (output) beam centroid position (y)   
  @param    dSizeX       (output) beam FWHM wrt 1st beam principal axis (the one
                         most aligned with x)
  @param    dSizeY       (output) beam FWHM wrt 2nd beam principal axis (the one
                         most aligned with y)
  @param    dFlux        (output) integrated flux
  @param    dAngle       (output) angle of rotation (degrees) of the beam. Of the
                         two possible axes (90 deg difference), this is always
                         the angle of the beam axis closest to the x-axis
  @return   Non-0 in case of errors 

  The cpl_error_code must not already be set when calling the function.

  Failure codes
  1      : cpl_error_code was set
  2, 3, 4: illegal input
  5      : too few/faint pixels to compute centroid/FWHM
  6      : unexpected error
  
  The function
  - Subtracts the background using a the minimum
    of a (image_width, 1) median filter and a (1, image_height)
    median filter at each pixel
  - Subtracts the background using a 1 x n median filter
  - Filters out isolated hot/cold areas using a smaller median filter
  - Computes centroid and FWHM of pixels above threshold
    (iteratively, reducing the window size for each iteration)

  Assumptions
  - The beam is somewhat wider (in x) than 3-4 pixels
  - The beam is no higher (in y) than ~30 pixels
  - Beam pixels are brighter than 5 ADU (relative to local background)
*/
/*----------------------------------------------------------------------------*/
static int CalculateQCParams( const cpl_image * pImage, 
			      double * dPosX, double * dPosY, 
			      double * dSizeX, double * dSizeY, 
			      double * dFlux, double * dAngle, double *dPeakFlux )
{
  const char *fctid = "CalculateQCParams";

  int median_filter_x = 7;    /* for filtering out isolated hot/cold pixels,
				 must be less than double beam width, 
				 larger than 5 (which has proven not to work),
				 should be an odd number
			      */
  int median_filter_y = 61;   /* for measuring the local background, 
				 must be larger than the double beam height, or
				 the beam itself would be subtracted,
				 should be an odd number
			      */
  double threshold_min = 5.0; /* minimum beam pixel value */

  double kappa_flux = 3.0;    /* to distinguish background/beam pixels */
  double kappa_w    = 2.0;    /* for window size clipping */
  int min_window_y = 10;      /* stop iterating if window ... */
  int min_window_x = 10;      /*    ... reached this size */
  double pi = 3.1415926535897932384626433832795;
      
  int iStatus = 0;

  if ( cpl_error_get_code() != CPL_ERROR_NONE )
      {
	  cpl_msg_error( fctid, "Error code is set: %s", cpl_error_get_message() );
	  return 1;
      }
  if ( pImage == NULL ) 
      {
	  cpl_msg_error( fctid, "Null input" );
	  return 2;
      }
  else if ( cpl_image_get_type(pImage) != CPL_TYPE_FLOAT ) 
      {
	  cpl_msg_error( fctid, "Invalid image type" );
	  return 3;
      }
  else if ( cpl_image_count_rejected( pImage ) != 0 ) 
      {
	  cpl_msg_error( fctid, "Image has bad pixels" );
	  return 4;
      }
  else 
      {
	  double stdev_x;
	  double stdev_y;
	  int debug = 0;    /* Save intermediate images to /tmp ? */
	  int nx = cpl_image_get_size_x( pImage );
	  int ny = cpl_image_get_size_y( pImage );
	  int llx = 1;
	  int lly = 1;
	  int urx = nx;
	  int ury = ny;
	  int llx_old, lly_old, urx_old, ury_old;
	  cpl_matrix *kernel_7x1 = NULL;
	  cpl_image  *background = NULL;
	  cpl_image  *pFiltered  = NULL;
	  cpl_image  *temp       = NULL;

	  kernel_7x1 = cpl_matrix_new( 1, median_filter_x ); 
	  /* The order of arguments is (ywindow, xwindow) */

	  cpl_matrix_fill( kernel_7x1, 1.0 );

	  /* Create, subtract background
	     Two components: 1. related to overlapping channels/chips
                                aligned with image rows/columns
                             2. local variations
	  */
	  background = image_filter_median( pImage, 2*nx, 1 );
	  temp       = image_filter_median( pImage, 1, 2*ny );

	  /* Set b := min(b, t) = b + ((t-b) < 0) ? t-b : 0
	     at each pixel */	     
	  cpl_image_subtract ( temp, background );
	  cpl_image_threshold( temp, -(DBL_MAX), 0, 0, 0 ); /* Set positive -> 0 */
	  cpl_image_add      ( background, temp);
	  cpl_image_delete( temp );

	  pFiltered = cpl_image_subtract_create( pImage, background );

	  if (debug) cpl_image_save( pFiltered , "/tmp/backsub1.fits", 
				     CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_DEFAULT );
	  
	  /* Measure, subtract local background */
	  cpl_image_delete( background );
	  background = image_filter_median( pFiltered, 1, median_filter_y );
	  cpl_image_subtract( pFiltered, background );

	  if (debug) cpl_image_save( pFiltered , "/tmp/backsub2.fits", 
				     CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_DEFAULT );
	  if (debug) cpl_image_save( background, "/tmp/back.fits"    , 
				     CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_DEFAULT );
	  
	  /* Filter out hot/cold pixels */
	  temp = pFiltered;
	  pFiltered=amber_smooth_image_median(temp, median_filter_x, 1);
	  //pFiltered = cpl_image_filter_median( temp, kernel_7x1 );
	  cpl_image_delete( temp );

	  if (debug) cpl_image_save( pFiltered , "/tmp/backsub2_filtered.fits", 
				     CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_DEFAULT );
	  
	  do {
	      double noise;
	      double threshold;
	      
	      /* Estimate background noise = RMS =
		 sqrt( median_xy (P_xy - background )^2 )
	      */

	      temp = cpl_image_power_create( pFiltered, 2.0 );
	      noise = sqrt( cpl_image_get_median_window( temp,
							 llx, lly,
							 urx, ury ) );
	      cpl_image_delete( temp );
	      
	      threshold = kappa_flux * noise;
	      if (threshold < threshold_min)
		  {
		      threshold = threshold_min;
		  }

	      cpl_msg_info( fctid, "Current window = (%d, %d) - (%d, %d)",
			    llx, lly, urx, ury );
	      
	      cpl_msg_info( fctid, "Background = %g +- %g ; Threshold = %g", 
			    cpl_image_get_mean_window( background,
						       llx, lly,
						       urx, ury ),
			    noise, threshold );
	      
	      if ( get_centroid_stdev_flux_angle( pFiltered, 
						  threshold,
						  llx, lly, 
						  urx, ury,
						  dPosX, dPosY,
						  &stdev_x, &stdev_y,
						  dFlux,
						  dAngle,
                                                  dPeakFlux ) != 0)
		  {
		      cpl_matrix_delete( kernel_7x1 );
		      cpl_image_delete( pFiltered );
		      cpl_image_delete( background );
		      cpl_error_reset();
		      return 5;
		  }
	      else
		  {
		      cpl_msg_info( fctid, "x = %g", *dPosX );
		      cpl_msg_info( fctid, "y = %g", *dPosY );
		      cpl_msg_info( fctid, "dx = %g", stdev_x );
		      cpl_msg_info( fctid, "dy = %g", stdev_y );
		      cpl_msg_info( fctid, "dx / dy = %g", 
				    stdev_x / ((stdev_y != 0) ? stdev_y : 1.0));
		      cpl_msg_info( fctid, "angle   = %g degrees", (*dAngle/pi)*180);
		  }

	      llx_old = llx;
	      lly_old = lly;
	      urx_old = urx;
	      ury_old = ury;

	      llx = (*dPosX - kappa_w * stdev_x);
	      lly = (*dPosY - kappa_w * stdev_y);
	      urx = (*dPosX + kappa_w * stdev_x);
	      ury = (*dPosY + kappa_w * stdev_y);
	      if (llx < llx_old) llx = llx_old;
	      if (lly < lly_old) lly = lly_old;
	      if (urx > urx_old) urx = urx_old;
	      if (ury > ury_old) ury = ury_old;
	      
	      /* While window size decreased,
		 and window not too small */
	  } while ((llx > llx_old || lly > lly_old || urx < urx_old || ury < ury_old)
		   &&
		   (ury - lly > min_window_y && urx - llx > min_window_x) );
	
	  cpl_matrix_delete( kernel_7x1 );
	  cpl_image_delete( background );
	  cpl_image_delete( pFiltered );
        
	  /* Convert from stdev to FWHM */
	  {
	      double TWOSQRT2LN2 = 2.35482004503095; /* valid for a gaussian shape */
	      *dSizeX = TWOSQRT2LN2 * stdev_x;
	      *dSizeY = TWOSQRT2LN2 * stdev_y;
	  }
	  /* Convert to degrees */
	  *dAngle *= 180/pi;
	  
	  /* Draw a cross at the inferred beam position 
	     (for debugging, the input image should not change)
	  {
	      int i;
	      for (i = *dPosX - *dSizeX; i <= *dPosX + *dSizeX; i++)
		  {
		      cpl_image_set( pImage, i, (int) (*dPosY + 0.5), 0 );
		  }
	      
	      for (i = *dPosY - *dSizeY; i <= *dPosY + *dSizeY; i++)
		  {
		      cpl_image_set( pImage, (int) (*dPosX + 0.5), i, 0 );
		  }
	  }
	  */
      }

  if (cpl_error_get_code() != CPL_ERROR_NONE)
      {
	  /* Should not happen */
	  cpl_error_reset();
	  iStatus = 6;
      }

  return iStatus;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Creates a proper prefix for QC parameters for a particular file 
  @param    
  @return   

  This function is exported.
 */
/*----------------------------------------------------------------------------*/
void GetPrefix( int iFrame, char * szPrefix )
{
  //strcpy( szPrefix, "ESO QC REFPIX " );
  strcpy( szPrefix, "ESO QC REFPIX " );

  switch( iFrame )
  {
    case 1:
      strcat( szPrefix, "CLDSTP " ); 	    
      break;
    case 2:
      strcat( szPrefix, "A K " ); 	    
      break;
    case 3:
      strcat( szPrefix, "B K " ); 	    
      break;
    case 4:
      strcat( szPrefix, "C K " ); 	    
      break;
    case 5:
      strcat( szPrefix, "A H " ); 	    
      break;
    case 6:
      strcat( szPrefix, "B H " ); 	    
      break;
    case 7:
      strcat( szPrefix, "C H " ); 	    
      break;
    case 8:
      strcat( szPrefix, "A J " ); 	    
      break;
    case 9:
      strcat( szPrefix, "B J " ); 	    
      break;
    case 10:
      strcat( szPrefix, "C J " ); 	    
      break;
    case 11:
      strcat( szPrefix, "BCD A K " ); 	    
      break;
    case 12:
      strcat( szPrefix, "BCD B K " ); 	    
      break;
    case 13:
      strcat( szPrefix, "BCD C K " ); 	    
      break;
    case 14:
      strcat( szPrefix, "BCD A H " ); 	    
      break;
    case 15:
      strcat( szPrefix, "BCD B H " ); 	    
      break;
    case 16:
      strcat( szPrefix, "BCD C H " ); 	    
      break;
    case 17:
      strcat( szPrefix, "BCD A J " ); 	    
      break;
    case 18:
      strcat( szPrefix, "BCD B J " ); 	    
      break;
    case 19:
      strcat( szPrefix, "BCD C J " ); 	    
      break;
    case 20:
      strcat( szPrefix, "OUTF A " ); 	    
      break;
    case 21:
      strcat( szPrefix, "OUTF B " ); 	    
      break;
    case 22:
      strcat( szPrefix, "OUTF C " ); 	    
      break;
      
     
    default:
      strcat( szPrefix, "UNKNOWN" ); 	    
  }

}

/*----------------------------------------------------------------------------*/
/**
  @brief    Build the list of available plugins, for this module. 
  @param    list    the plugin list
  @return   0 if everything is ok

  This function is exported.
 */
/*----------------------------------------------------------------------------*/
int cpl_plugin_get_info(cpl_pluginlist * list)
{
    cpl_recipe  *   recipe = cpl_calloc(1, sizeof(*recipe)) ;
    cpl_plugin  *   plugin = &recipe->interface ;

    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    AMBER_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "amber_BeamPos",
                    "AMBER Beam Monitoring Recipe",
                    amber_BeamPos_man,
                    "Tom Licha",
                    PACKAGE_BUGREPORT,
                    "GP",
                    amber_BeamPos_create,
                    amber_BeamPos_exec,
                    amber_BeamPos_destroy) ;

    cpl_pluginlist_append(list, plugin) ;
    
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Setup the recipe options    
  @param    plugin  the plugin
  @return   0 if everything is ok

  Create the recipe instance and make it available to the application using the 
  interface. 
 */
/*----------------------------------------------------------------------------*/
static int amber_BeamPos_create(cpl_plugin * plugin)
{
    cpl_recipe * recipe = (cpl_recipe *)plugin ;
    //cpl_parameter * p ;

    /* Create the parameters list in the cpl_recipe object */
    recipe->parameters = cpl_parameterlist_new() ; 

    /* Fill the parameters list */
 
    /* Return */
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int amber_BeamPos_exec(cpl_plugin * plugin)
{
    cpl_recipe * recipe = (cpl_recipe *)plugin ;
    return amber_BeamPos(recipe->parameters, recipe->frames) ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Destroy what has been created by the 'create' function
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int amber_BeamPos_destroy(cpl_plugin * plugin)
{
    cpl_recipe  *   recipe = (cpl_recipe *)plugin ;
    cpl_parameterlist_delete(recipe->parameters) ; 
    return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the command line options and execute the data reduction
  @param    parlist     the parameters list
  @param    framelist   the frames list
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int amber_BeamPos(
        cpl_parameterlist     *   parlist, 
        cpl_frameset    *   framelist)
{
  /* CPL structures */
  cpl_frame     * cur_frame;
/*  cpl_parameter * cur_param; */

	
  char   szMessage[1024];
  char   szPrefix[1024];
  
  char         szProduct[1024];
  cpl_frame  * pFrameProduct = NULL;
  cpl_image  * pImage        = NULL;
  
  char   szQCParameter[1024];
  const char * pszARCFILE;
  char * pszFilename;
  char * pszFileTag;
  
  int  iStatus = 0;
/*  int  i;*/
  int  iFrameCount = 0;

  /* QC Params */
  double dPosX  = 0;
  double dPosY  = 0;
  double dSizeX = 0;
  double dSizeY = 0;
  double dFlux  = 0;
  double dPeakFlux  = 0;
  double dAngle = 0;
  double dPosX0 = 0.; /* for CLDSTP */
  double dPosY0 = 0.; /* for CLDSTP */
  double dOffsetX=0.;
  double dOffsetY=0.;

  AmPaf            * qc            = NULL;
  cpl_propertylist * qclog         = NULL;
  cpl_propertylist * qcFromProduct = NULL;
  cpl_propertylist * qcFromRawfile = NULL;




  cpl_propertylist * qc_properties = NULL;
  
  const char      *   fctid = "amber_BeamPos" ;

  cpl_msg_info( fctid, "Start of DataReduction");
  amber_dfs_set_groups(framelist);
  gframelist = framelist;
  gparlist   = parlist;

  /* For each of the 22 input files create and calculate:
   *
   * re-image of the amber detector out of amber data tables
   * x and y position of the beam
   * flux of the beam
   * size of the beam in x and y 
   * 
   * This functions assumes that the input files are in the right order of the template 1 of 22 -> 22 of 22
   *
  */

  cpl_frameset_iterator * it = cpl_frameset_iterator_new(framelist);


  while( (cur_frame = cpl_frameset_iterator_get(it)) && !iStatus && iFrameCount <22 )
  {
 
   /*  iFrameCount++; Count only "good" frames */

    /* Get Filename and Classification Tag */
    pszFilename = (char *)cpl_frame_get_filename( cur_frame );
    pszFileTag  = (char *)cpl_frame_get_tag( cur_frame );
    
    /* Check Tag and Filename */
    if( pszFilename && pszFileTag )
    {
	    
      /* BEAMPOS OBJECT */
      if( !strcmp( pszFileTag, "AMBER_BEAMPOS" ) )
      {
        /* Data Reduction on the highest level possible for amdlib 
	 *
	 * QC Parameters, Image-Analysis done in CPL
	 *
	 * */
         iFrameCount++; /* Count BEAMPOS-frames only */

         sprintf( szMessage, "Frame #%d of 22 [Current execution status=%d]", iFrameCount, iStatus );
         cpl_msg_info( fctid, "%s", szMessage );   
         
	if( iStatus == 0 )      
	{	
          /* Create re-image to /tmp/amber_beampos.fits */
          if( amdlibLoadRawData(pszFilename, &rawData, errMsg) != amdlibSUCCESS )
          {
            sprintf ( szMessage, "Could not load raw data file '%s'", pszFilename );
            cpl_msg_info( fctid, "%s", szMessage );   
            iStatus = 20;
          }
	  else
	  {
             
             amdlibSaveRawDataToFits( "/tmp/amber_beampos.fits", &rawData, errMsg);		  
             /* release the memory */
             amdlibReleaseRawData(&rawData);
	  }

    
          /* Load /tmp/amber_beampos.fits to a cpl_image */
          pImage = cpl_image_load( "/tmp/amber_beampos.fits", CPL_TYPE_FLOAT, 0, 0 );  
		
	  
          /* Calculate QC Parameters out of cpl_image  */
	  if( CalculateQCParams( pImage, &dPosX, &dPosY, &dSizeX, &dSizeY, &dFlux, &dAngle, &dPeakFlux) != 0 )
	  {
            sprintf( szMessage, "Beam localization failed!" );		  
            cpl_msg_info( fctid, "%s", szMessage );
	    dPosX  = 0;
	    dPosY  = 0;
	    dSizeX = 0;
	    dSizeY = 0;
	    dFlux  = 0;
	    dPeakFlux  = 0;
	    dAngle = 0;
            dOffsetX=0;
            dOffsetY=0;
	  }

          /* Save the CLDSTP Parameter */
	  if (iFrameCount==1)
          {
             dPosX0  =dPosX;
             dPosY0  =dPosY;
          }
          dOffsetX=dPosX - dPosX0;
          dOffsetY=dPosY - dPosY0;
          
	  /* Create Header and write QC-Parameters */
	  GetPrefix( iFrameCount, szPrefix );
	  sprintf( szProduct, "amber_beampos_%04d.fits", iFrameCount );
	  
       	  sprintf( szMessage, "%s %s", szProduct, szPrefix );
          cpl_msg_info( fctid, "%s", szMessage );

	  pFrameProduct = cpl_frame_new();
          cpl_frame_set_filename( pFrameProduct, szProduct ); 
          cpl_frame_set_type    ( pFrameProduct, CPL_FRAME_TYPE_IMAGE );
          cpl_frame_set_tag     ( pFrameProduct, "AMBER_BEAMPOS_REDUCED" );
          cpl_frame_set_group   ( pFrameProduct, CPL_FRAME_GROUP_PRODUCT ); 
          cpl_frame_set_level   ( pFrameProduct, CPL_FRAME_LEVEL_FINAL ); 

	  
	  qc_properties = BeamPosCreateProductHeader( fctid, pszFilename, pFrameProduct );

	  sprintf( szQCParameter, "%sX", szPrefix );
 	  sprintf( szMessage, "%s = %.2f", szQCParameter, dPosX );
          cpl_msg_info( fctid, "%s", szMessage );
	  cpl_propertylist_append_float( qc_properties, szQCParameter, dPosX ); 

 	  sprintf( szQCParameter, "%sY", szPrefix );
 	  sprintf( szMessage, "%s = %.2f", szQCParameter, dPosY );
          cpl_msg_info( fctid, "%s", szMessage );
	  cpl_propertylist_append_float( qc_properties, szQCParameter, dPosY ); 


	  sprintf( szQCParameter, "%sSHX", szPrefix );
 	  sprintf( szMessage, "%s = %.2f", szQCParameter, dOffsetX );
          cpl_msg_info( fctid, "%s", szMessage );
	  cpl_propertylist_append_float( qc_properties, szQCParameter, dOffsetX ); 

 	  sprintf( szQCParameter, "%sSHY", szPrefix );
 	  sprintf( szMessage, "%s = %.2f", szQCParameter, dOffsetY );
          cpl_msg_info( fctid, "%s", szMessage );
	  cpl_propertylist_append_float( qc_properties, szQCParameter, dOffsetY ); 

  	  sprintf( szQCParameter, "%sSIZX", szPrefix );
 	  sprintf( szMessage, "%s = %.2f", szQCParameter, dSizeX );
          cpl_msg_info( fctid, "%s", szMessage );
	  cpl_propertylist_append_float( qc_properties, szQCParameter, dSizeX ); 

 	  sprintf( szQCParameter, "%sSIZY", szPrefix );
 	  sprintf( szMessage, "%s = %.2f", szQCParameter, dSizeY );
          cpl_msg_info( fctid, "%s", szMessage );
	  cpl_propertylist_append_float( qc_properties, szQCParameter, dSizeY ); 

   	  sprintf( szQCParameter, "%sFLUX", szPrefix );
 	  sprintf( szMessage, "%s = %.2f", szQCParameter, dFlux );
          cpl_msg_info( fctid, "%s", szMessage );
	  cpl_propertylist_append_float( qc_properties, szQCParameter, dFlux ); 

   	  sprintf( szQCParameter, "%sPEAKFLUX", szPrefix );
 	  sprintf( szMessage, "%s = %.2f", szQCParameter, dPeakFlux );
          cpl_msg_info( fctid, "%s", szMessage );
	  cpl_propertylist_append_float( qc_properties, szQCParameter, dPeakFlux ); 

    	  sprintf( szQCParameter, "%sANGLE", szPrefix );
 	  sprintf( szMessage, "%s = %.2f", szQCParameter, dAngle );
          cpl_msg_info( fctid, "%s", szMessage );
	  cpl_propertylist_append_float( qc_properties, szQCParameter, dAngle ); 

	  /* Save Product */	
     	  /*sprintf( szMessage, "Saving Product %s %s...", szProduct, szPrefix );
          cpl_msg_info( fctid, "%s", szMessage );*/

	  /*Adding the JMMC acknowledgements*/
	  amber_JMMC_acknowledgement(qc_properties);

	  cpl_image_save( pImage, szProduct, CPL_BPP_IEEE_FLOAT, qc_properties, CPL_IO_DEFAULT );
	  /*sprintf( szMessage, "cpl_frame_insert (\"%s\")", cpl_frame_get_filename(pFrameProduct)  ); */
          /*cpl_msg_info( fctid, "%s", szMessage );     */
          cpl_frameset_insert( framelist, pFrameProduct );  


	  /*---------------------------------------------------------------------------------------------------------------*/
          /* Writing QC1 Parameters to log */ 


	  qcFromRawfile= cpl_propertylist_load(pszFilename, 0 ); 	 
          qc = amber_qclog_open( iFrameCount-1 );

          if( qc )
          {
             qclog = amber_paf_get_properties( qc );

             /* Read original ARCFILE entry and copy to PAF-File */
             pszARCFILE = cpl_propertylist_get_string( qcFromRawfile, "ARCFILE" ); 

	     /* Add mandatory keys */
             cpl_propertylist_append_string( qclog, "ARCFILE", pszARCFILE );
	   
             /* Take the whole header including QC parameters */
             qcFromProduct = qc_properties;

             /* copy to PAF */
             amber_propertylist_append( qclog, qcFromProduct);

             /* Finished... */
             amber_qclog_close(qc);

          } /* if qc */

          /*cpl_frame_delete( pFrameProduct ); no, deleted by caller, i.e. ESOREX */
	  cpl_image_delete( pImage );
          cpl_propertylist_delete(qc_properties );
	  cpl_propertylist_delete(qcFromRawfile);

	} /* Status still OK */
      }  /* All BEAMPOS frames */ 


    } /* Filename and Tag present */  	
    else
    {
      sprintf( szMessage, "Missing FileName or Tag for Frame #%d", iFrameCount ); 
      cpl_msg_info( fctid, "%s", szMessage );   
    } /* Filename and Tag present */    
    
    cpl_frameset_iterator_advance(it, 1);


  } /* while more frames */

  cpl_frameset_iterator_delete(it);

  if( iFrameCount != 22 )
  {
    sprintf( szMessage, "Error: Please input 22 raw files of type AMBER_BEAMPOS", iFrameCount ); 
    cpl_msg_info( fctid, "%s", szMessage ); 
    iStatus = 1;
  } 

  cpl_msg_info( fctid, "End of DataReduction");  

  /* there is one remaining, due to a workarround above. Ignore it, ESOREX shall NOT talk about that :) */
  cpl_error_reset();
  
  return iStatus ;
}
/*---------------------------------------------------------------------------*/
/**
  @brief    Smooth an image using a median filter
  @param    in      image to be smoothed
  @param    size_x  size_x of kernel matrix to use in filtering
  @param    size_y  size_y of kernel matrix to use in filtering
  @return   out     smoothed image
 */
/*---------------------------------------------------------------------------*/
static cpl_image * amber_smooth_image_median(
        const cpl_image     *   in,
        int                     size_x,
        int                     size_y)
{
    cpl_image * out = cpl_image_duplicate(in);
    cpl_mask * kernel = cpl_mask_new(size_x, size_y);
    cpl_mask_not(kernel);

    if(cpl_image_filter_mask(out, in, kernel, CPL_FILTER_MEDIAN,
                             CPL_BORDER_FILTER) != CPL_ERROR_NONE) {
        cpl_image_delete(out);
        cpl_mask_delete(kernel);
        return NULL;
    }

    cpl_mask_delete(kernel);
    return (out);
}





