/* $Id: visir_util_convert_pos.c,v 1.60 2012/02/02 10:26:53 jtaylor Exp $
 *
 * This file is part of the VISIR 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., 51 Franklin St, Fifth Floor, Boston, MA  02111-1307  USA
 */

/*
 * $Author: jtaylor $
 * $Date: 2012/02/02 10:26:53 $
 * $Revision: 1.60 $
 * $Name: visir-3_5_1 $
 */

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

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

#include "visir_recipe.h"

/*-----------------------------------------------------------------------------
                                Defines
 -----------------------------------------------------------------------------*/

#define RECIPE_STRING   "visir_util_convert_pos"

#define FLUX_COL    "FLUX_AUTO"
#define X_COL       "XWIN_IMAGE"
#define Y_COL       "YWIN_IMAGE"

#define VISIR_FLUX      "FLXSCALE"
#define VISIR_DRS_FLUX  "ESO DRS FLXSCALE"
#define VISIR_FLUX_COMM "The relative flux (to the 1st source flux)"
#define VISIR_DRS_SNR   "ESO DRS SNR"
#define VISIR_DRS_SNR_COMM "The signal-to-noise ratio for the extracted source"
#define SQR(a) ((a)*(a))


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

static cpl_error_code visir_util_convert_pos_inv(cpl_frameset *, double *,
                                                const irplib_framelist *,
                                                const irplib_framelist *,
                                                const cpl_mask *, int,
                                                const cpl_parameterlist *);

static cpl_error_code visir_util_convert_pos_one(cpl_frameset *, double *,
                                                 const irplib_framelist *,
                                                 const irplib_framelist *, int,
                                                 const cpl_parameterlist *);

static cpl_error_code visir_util_convert_pos_save(cpl_frameset *,
                                                  double, double,
                                                  int, int, int,
                                                  const char *,
                                                  const char *,
                                                  const cpl_frameset      *,
                                                  const cpl_image         *,
                                                  const cpl_propertylist  *,
                                                  const cpl_parameterlist *);

cpl_recipe_define(visir_util_convert_pos, VISIR_BINARY_VERSION,
                  "Lars Lundin", PACKAGE_BUGREPORT, "2011",
                  "Conversion of object position(s) from matching "
                  "pairs of nodded object files + sextractor tables",
                  "The files listed in the Set Of Frames (sof-file) "
                  "must be tagged:\n"
                  "Sextractor-object-file.fits " VISIR_UTIL_CONVERT_RAW "\n"
                  "Sextractor-table-file.fits "  VISIR_UTIL_CONVERT_TAB
                  "\nThe table must include these columns, all with numerical"
                  " data: " X_COL ", " Y_COL ", " FLUX_COL ".\nSuch a table "
                  "can be created by the source extractor software sextractor"
                  ".\n\n"
                  "In addition to containing pairs of object- and table-files"
                  " the SOF may also contain only object-files.\n"
                  "In this case the object files must be tagged with: "
                  VISIR_UTIL_CONVERT_RAW ".\n"
                  "\nFor each input object frame one or more products will be"
                  " created with updated WCS-ccordinates (CRPIX[12]).\n"
                  "When the object frame is matched with a table-frame, one "
                  "product will be created for each row in the table.\nSuch "
                  "products will be created in order of decreasing object "
                  "brightness.\n"
                  "\nWhen the object frames are pairs of 4-beam-frames, "
                  "the position (centroid) of the "
                  "brightest object in the 4-beam-frame will be written into "
                  "the first product.\nThe nodding and chopping throw will "
                  "determine the position written into the second product "
                  "based on the raw-frame and the position written into the "
                  "two products based on the matching off-frame.\n"
                  "When the object frames are pairs of 4-beam-frames, "
                  "the recipe also supports updating "
                  "the WCS-coordinates of an auxiliary frame (e.g. error map)"
                  " for each object.\n\nIf present these "
                  "auxiliary frames must be tagged:\n"
                  "Object-auxiliary.fits " VISIR_UTIL_CONVERT_POS_AUX
                  " (optional).\n"
                  "\nThe primary product(s) will have a FITS card\n"
                  "'HIERARCH " CPL_DFS_PRO_CATG "' with a value of:\n"
                  VISIR_UTIL_CONVERT_PROCATG "\n"
                  "If created, the auxiliary products will have a FITS "
                  "card\n'HIERARCH " CPL_DFS_PRO_CATG "' with a value of:\n"
                  VISIR_UTIL_CONVERT_AUX_PROCATG "\n"
                  );

typedef struct {
    cpl_size zoom;
    cpl_size radius;
    cpl_vector * xyprofile;
} visir_zoom_info;

/*----------------------------------------------------------------------------*/
/**
 * @defgroup visir_util_convert_pos   Conversion of object coordinates
 */
/*----------------------------------------------------------------------------*/

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


static cpl_image *
zoom_img(const cpl_image * img, const visir_zoom_info zoom_info)
{
    const cpl_size radius = zoom_info.radius;
    const cpl_size nx = cpl_image_get_size_x(img);
    const cpl_size ny = cpl_image_get_size_y(img);
    const cpl_size zoom = zoom_info.zoom;
    const cpl_vector * xyprofile = zoom_info.xyprofile;
    cpl_image * new;
    double * pndata;

    if (zoom_info.zoom == 1)
        return cpl_image_duplicate(img);

    new = cpl_image_new(nx*zoom, ny*zoom, CPL_TYPE_DOUBLE);

    pndata = cpl_image_get_data(new);

    for (cpl_size iv = 0; iv < ny * zoom; iv++) {
        for (cpl_size iu = 0; iu < nx * zoom; iu++) {
            double confidence;

            double value =
                cpl_image_get_interpolated(img,
                                           (double)(iu+1.) / zoom,
                                           (double)(iv+1.) / zoom,
                                           xyprofile, radius,
                                           xyprofile, radius,
                                           &confidence);
            if (confidence >= 0)
                pndata[iu + iv * (nx * zoom)] = value;
            else {
                cpl_msg_error(cpl_func, "Interpolation failure");
                cpl_error_set(cpl_func, CPL_ERROR_ILLEGAL_INPUT);
                return NULL;
            }
        }
    }

    return new;
}

/*----------------------------------------------------------------------------*/
/**
  @internal
  @brief    Fill the recipe parameterlist
  @param    self  The parameterlist
  @return   0 iff everything is ok

 */
/*----------------------------------------------------------------------------*/
static cpl_error_code
visir_util_convert_pos_fill_parameterlist(cpl_parameterlist * self)
{
    const char * context = PACKAGE "." RECIPE_STRING;
    cpl_error_code err;

    cpl_ensure_code(self, CPL_ERROR_NULL_INPUT);

    /* Fill the parameters list */

    err =
        irplib_parameterlist_set_double(self, PACKAGE, RECIPE_STRING, "eccmax",
                                      0.25, NULL, context,
                       "The maximum eccentricity allowed in the combination "
                       "of the three (in parallel nod/chopping) or four (in "
                       "perpendicular nod/chopping) beams. In parallel mode, "
                       "three perfectly aligned points spaced with the "
                       "chopnod throw will have eccentricity 0, while in "
                       "perpedicular mode a square with the chopnod throw as "
                       "the side length will have eccentricity 0"
                                      );
    cpl_ensure_code(!err, err);

    err =
        irplib_parameterlist_set_int(self, PACKAGE, RECIPE_STRING, "naverage",
                                     1, NULL, context,
                       "Number of plains to average before searching for the "
                       "beam positions.");
    cpl_ensure_code(!err, err);

    err =
        irplib_parameterlist_set_bool(self, PACKAGE, RECIPE_STRING, "xcorrelate",
                                     CPL_FALSE, NULL, context,
                       "Shift beam positions by cross correlating images.");
    cpl_ensure_code(!err, err);

    err =
        irplib_parameterlist_set_bool(self, PACKAGE, RECIPE_STRING, "brightest",
                                     CPL_TRUE, NULL, context,
                       "Shift beam positions by brightest pixel.");
    cpl_ensure_code(!err, err);

    err =
        irplib_parameterlist_set_int(self, PACKAGE, RECIPE_STRING, "window",
                                     64, NULL, context,
                       "Window around detected beams to use for cross "
                       "correlation.");
    cpl_ensure_code(!err, err);

    return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    The recipe data reduction part is implemented here 
  @param    framelist   the frames list
  @param    parlist     the parameters list
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static int visir_util_convert_pos(cpl_frameset            * framelist,
                                  const cpl_parameterlist * parlist)
{
    cpl_errorstate     cleanstate = cpl_errorstate_get();
    cpl_error_code     errori     = CPL_ERROR_NONE;
    irplib_framelist * allframes = NULL;
    irplib_framelist * rawframes = NULL;
    irplib_framelist * tabframes = NULL;
    irplib_framelist * invframes = NULL;
    irplib_framelist * auxframes = NULL;
    irplib_framelist * auxinvframes = NULL;
    cpl_mask         * bpm = NULL;
    double             flux1      = 0.0;
    const cpl_boolean  only_obj =
        cpl_frameset_find_const(framelist, VISIR_UTIL_CONVERT_TAB)
        ? CPL_FALSE : CPL_TRUE;
    int                i, n;
    

    /* Identify the RAW and TAB frames in the input frameset */
    skip_if (visir_dfs_set_groups(framelist));

    /* Objects observation */
    allframes = irplib_framelist_cast(framelist);
    skip_if(allframes == NULL);

    rawframes = irplib_framelist_extract(allframes, VISIR_UTIL_CONVERT_RAW);
    skip_if(rawframes == NULL);

    n = irplib_framelist_get_size(rawframes);

    if (only_obj) {
        if (cpl_frameset_find_const(framelist, VISIR_UTIL_CONVERT_POS_AUX)) {
            auxframes = irplib_framelist_extract(allframes,
                                                 VISIR_UTIL_CONVERT_POS_AUX);
            error_if(irplib_framelist_get_size(auxframes) != n,
                     CPL_ERROR_INCOMPATIBLE_INPUT, "%d auxiliary frame(s) does "
                     "not match the %d 4-beam-frame(s)",
                     irplib_framelist_get_size(auxframes), n);
        }
    } else {
        tabframes = irplib_framelist_extract(allframes, VISIR_UTIL_CONVERT_TAB);
        skip_if_lt(irplib_framelist_get_size(tabframes), n, "table-frames to "
                   "match the object frames");
    }

    if (cpl_frameset_find_const(framelist, VISIR_CALIB_STATIC_MASK)) {
        irplib_framelist * bpmframes =
            irplib_framelist_extract(allframes, VISIR_CALIB_STATIC_MASK);
        if (irplib_framelist_get_size(bpmframes) == 1) {
            const cpl_frame * bpmframe =
                irplib_framelist_get_const(bpmframes, 0);
            const char * bpmname = cpl_frame_get_filename(bpmframe);
            cpl_image * bpmimg  =
                cpl_image_load(bpmname, CPL_TYPE_UNSPECIFIED, 0, 0);
            bpm = cpl_mask_threshold_image_create(bpmimg, 0.1, DBL_MAX);
            cpl_image_delete(bpmimg);

            errori = cpl_error_set_where(cpl_func);
            error_if(errori, errori, "Could not load bad pixel map");
            cpl_msg_info(cpl_func, "Loaded bad pixel map");
        }
        irplib_framelist_delete(bpmframes);
    }

    for (i = 0; i < n; i++) {
        cpl_msg_info(cpl_func, "Converting frame %d/%d", 1+i, n);

        if (irplib_framelist_load_propertylist(rawframes, i, 0, "^("
                                               IRPLIB_PFITS_WCS_REGEXP "|"
                                               VISIR_PFITS_STRING_PIXSCALE
                                               "|"
                                               VISIR_PFITS_STRING_CHOPNOD_DIR
                                               "|"
                                               VISIR_PFITS_DOUBLE_CHOP_THROW
                                               ")$",
                                               CPL_FALSE)
            || (only_obj
                ? visir_util_convert_pos_inv(framelist, &flux1, rawframes,
                                             auxframes, bpm, i, parlist)
                : visir_util_convert_pos_one(framelist, &flux1, rawframes,
                                             tabframes, i, parlist))) {
            errori = cpl_error_set_where(cpl_func);
            if (errori != CPL_ERROR_NONE)
                break;
        }
    }
    cpl_errorstate_dump(cleanstate, CPL_FALSE, NULL);
    cpl_errorstate_set(cleanstate);

    error_if(errori, errori, "Failed to process %d frame(s)", n);

    end_skip;

    irplib_framelist_delete(allframes);
    irplib_framelist_delete(rawframes);
    irplib_framelist_delete(invframes);
    irplib_framelist_delete(auxframes);
    irplib_framelist_delete(auxinvframes);
    irplib_framelist_delete(tabframes);
    cpl_mask_delete(bpm);

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @internal
  @brief  convienience wrapper around visir_util_convert_pos_save
  @return CPL_ERROR_NONE iff OK, otherwise the relevant CPL error code.
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code
save_image(const cpl_propertylist * plist, cpl_propertylist * updlist,
           cpl_frameset * products, cpl_frameset * usedframes,
           const cpl_parameterlist * parlist,
           cpl_image * image,
           const char * name, const char * auxname,
           const char * beamstr,
           int iframe, int iext, int proext,
           double xpos, double ypos,
           double relflux)
{
    cpl_propertylist * extplist = NULL;
    bug_if(cpl_propertylist_copy_property_regexp(updlist, plist, "^("
                                                 IRPLIB_PFITS_WCS_REGEXP ")$",
                                                 CPL_FALSE));

    if (iext > 0) {
        /* Overwrite default with WCS of current extension, if it has any */
        cpl_propertylist_delete(extplist);
        extplist = cpl_propertylist_load_regexp(name, iext, "^("
                                                IRPLIB_PFITS_WCS_REGEXP ")$",
                                                CPL_FALSE);
        any_if("WCS keys could not be read from extension %d of %s",
               iext, name);
        bug_if(cpl_propertylist_append(updlist, extplist));
    }

    bug_if(cpl_propertylist_append_double(updlist, VISIR_DRS_FLUX,relflux));
    bug_if(cpl_propertylist_append_double(updlist, VISIR_FLUX,    relflux));

    /* Comments copied from actual header */
    bug_if(cpl_propertylist_set_comment(updlist, VISIR_DRS_FLUX,
                                        VISIR_FLUX_COMM));
    bug_if(cpl_propertylist_set_comment(updlist, VISIR_FLUX,
                                        VISIR_FLUX_COMM));


    skip_if(visir_util_convert_pos_save(products, xpos, ypos, iext, proext,
                                        iframe, beamstr, auxname, usedframes,
                                        image, updlist, parlist));

    end_skip;
    cpl_propertylist_delete(extplist);

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Process the i'th on-frame with its off-frame
  @param    framelist   The frameset to append products to
  @param    pflux1      0 means set it, when set divide the flux with it.
  @param    rawframes  The A-on- object frames list
  @param    invframes  The A-off-object frames list
  @param    auxframes  The A-on auxiliary frames list, or NULL
  @param    auxinvframes The A-off auxiliary frames list, or NULL
  @param    iframe       The frame to process (0 for first)
  @param    parlist     The parameters list
  @return   CPL_ERROR_NONE iff OK, otherwise the relevant CPL error code.
 */
/*----------------------------------------------------------------------------*/
static
cpl_error_code visir_util_convert_pos_inv(cpl_frameset * framelist,
                                         double * pflux1,
                                         const irplib_framelist * rawframes,
                                         const irplib_framelist * auxframes,
                                         const cpl_mask * bpm,
                                         int iframe,
                                         const cpl_parameterlist * parlist)
{

    const cpl_propertylist * plist =
        irplib_framelist_get_propertylist_const(rawframes, iframe);
    cpl_propertylist * updlist    = cpl_propertylist_new();
    const cpl_frame  * frame;
    cpl_frameset     * products   = cpl_frameset_new();
    cpl_frameset     * usedframes = cpl_frameset_new();
    const cpl_frame * rawframe = irplib_framelist_get_const(rawframes, iframe);
    const cpl_frame * auxframe = auxframes ?
        irplib_framelist_get_const(auxframes, iframe) : NULL;

    const char * rawname = cpl_frame_get_filename(rawframe);
    const char * auxname = auxframes == NULL ? NULL
        : cpl_frame_get_filename(auxframe);

    const cpl_size next = cpl_fits_count_extensions(rawname);
    int proext = -1; /* Last extension where an image (product) was saved */
    cpl_imagelist * poslist = cpl_imagelist_new();
    cpl_imagelist * invlist = cpl_imagelist_new();
    int naverage = irplib_parameterlist_get_int(parlist, PACKAGE,
                                                RECIPE_STRING, "naverage");
    double       x4[4];
    double       y4[4];
    int          index = 0;
    cpl_boolean  bfirst = CPL_FALSE;
    cpl_image * shifted_avg[] = { NULL, NULL, NULL, NULL };
    double first_x[4];
    double first_y[4];
    int fcount[4] = {0,0,0,0};
    int window = irplib_parameterlist_get_int(parlist, PACKAGE,
                                              RECIPE_STRING, "window");
    cpl_boolean bxcorr =
        irplib_parameterlist_get_bool(parlist, PACKAGE,
                                      RECIPE_STRING, "xcorrelate");

    cpl_boolean bmaxpos =
        irplib_parameterlist_get_bool(parlist, PACKAGE,
                                      RECIPE_STRING, "brightest");

    error_if(bmaxpos && bxcorr, CPL_ERROR_INCOMPATIBLE_INPUT, "choose one");


    if (naverage <= 0)
        naverage = 1;
    else if (naverage > next)
        naverage = next;

    bug_if(rawframes == NULL);
    bug_if(pflux1    == NULL);
    bug_if(parlist   == NULL);

    bug_if(cpl_frameset_insert(usedframes, cpl_frame_duplicate(rawframe)));
    if (auxframe != NULL) {
        bug_if(cpl_frameset_insert(usedframes, cpl_frame_duplicate(auxframe)));
    }

    /* Loop over all extensions */

    for (int iext = - naverage / 2; iext <= next - naverage / 2; iext++) {
        int iloaded = iext + naverage / 2;
        cpl_errorstate prestate = cpl_errorstate_get();
        double relflux, flux;
        cpl_image * rawload, * invload;
        cpl_size nx, ny;


        visir_chopnod_mode mode;
        cpl_image * avraw, * avinv;
        if (iloaded <= next) {

            rawload = cpl_image_load(rawname, CPL_TYPE_UNSPECIFIED, 0, iloaded);
            if (bpm)
                cpl_image_reject_from_mask(rawload, bpm);
            invload = cpl_image_duplicate(rawload);
            cpl_image_multiply_scalar(invload, -1);

            if (!cpl_errorstate_is_equal(prestate)) {
                /* If load fails on one, it must fail on both */
                if (rawload != NULL || invload != NULL) {
                    any_if("One HDU has image data, the other does not");
                }

                /* Recover from the error */
                cpl_msg_info(cpl_func, "No image-data in extension %d", iloaded);
                cpl_errorstate_set(prestate);
                index++;
                continue;
            }
        }
        nx = cpl_image_get_size_x(rawload);
        ny = cpl_image_get_size_y(rawload);

        bug_if(cpl_imagelist_get_size(poslist) != cpl_imagelist_get_size(invlist));

        if (cpl_imagelist_get_size(poslist) == naverage) {
            cpl_image_delete(cpl_imagelist_unset(poslist, 0));
            cpl_image_delete(cpl_imagelist_unset(invlist, 0));
        }
        cpl_imagelist_set(poslist, rawload,
                          cpl_imagelist_get_size(poslist));
        cpl_imagelist_set(invlist, invload,
                          cpl_imagelist_get_size(invlist));

        /* skip incomplete averages, positions set
         * to first complete one later */
        if (cpl_imagelist_get_size(poslist) != naverage)
            continue;

        // FIXME: use a weighted average
        // rolling average might be needed for larger lists
            /* fill bad pixels so they aren't
             * considered as apertures in overall negative images*/
        avraw = cpl_imagelist_collapse_create(poslist);
        skip_if(cpl_detector_interpolate_rejected(avraw));
        avinv = cpl_imagelist_collapse_create(invlist);
        skip_if(cpl_detector_interpolate_rejected(avinv));
        cpl_propertylist_empty(updlist);

        mode = visir_img_find_beam(updlist, avraw, avinv, plist,
                                   parlist, RECIPE_STRING, x4, y4);

        if (bxcorr && shifted_avg[0] == NULL)
            for (int a = 0; a < 4; a++) {
                /* FIXME: handle 3 beam */
                shifted_avg[a] =
                    cpl_image_extract(avraw,
                                      CX_MAX(1, x4[a] - window/2),
                                      CX_MAX(1, y4[a] - window/2),
                                      CX_MIN(nx, x4[a] + window/2),
                                      CX_MIN(ny, y4[a] + window/2));
                bug_if(shifted_avg[a] == NULL);
                cpl_image_abs(shifted_avg[a]);
                first_x[a] = x4[a];
                first_y[a] = y4[a];
            }

        skip_if(mode == VISIR_CHOPNOD_AUTO);

        flux = cpl_propertylist_get_double(updlist, "ESO QC ONEBEAM FLUX");
        skip_if(0);

        if (*pflux1 != 0.0) {
            relflux = flux / *pflux1;
        } else {
            *pflux1 = flux;
            relflux = 1.0;
        }

        bug_if(iext <= 0);

        for (int j = 0; j <= naverage; j++) {
            cpl_image * rawimage, * invimage;
            /* first value */
            if (!bfirst) {
                if (j > naverage / 2) {
                    bfirst = CPL_TRUE;
                    break;
                }
                rawimage = cpl_imagelist_get(poslist, j);
                invimage = cpl_imagelist_get(invlist, j);
                cpl_msg_info(cpl_func, "Incomplete average, assuming values of"
                             " nearest complete average");
            }
            /* last value */
            else if (iloaded >= next) {
                if (j + naverage / 2 >= naverage)
                    break;
                rawimage = cpl_imagelist_get(poslist, j + naverage / 2);
                invimage = cpl_imagelist_get(invlist, j + naverage / 2);
                cpl_msg_info(cpl_func, "Incomplete average, assuming values of"
                             " nearest complete average");
            }
            else {
                rawimage = cpl_imagelist_get(poslist, naverage / 2);
                invimage = cpl_imagelist_get(invlist, naverage / 2);
                j += next; /* break after saving */
            }
            skip_if(cpl_detector_interpolate_rejected(rawimage));
            skip_if(cpl_detector_interpolate_rejected(invimage));

            if (bxcorr) {
                cpl_msg_info(cpl_func, "Cross-Correlation");
#ifdef _OPENMP
#pragma omp parallel for
#endif
                for (int a = 0; a < 4; a++) {
                    double xshift = 0, yshift = 0;
                    cpl_image * img =
                        cpl_image_extract(avraw,
                                          CX_MAX(1, first_x[a] - window/2),
                                          CX_MAX(1, first_y[a] - window/2),
                                          CX_MIN(nx, first_x[a] + window/2),
                                          CX_MIN(ny, first_y[a] + window/2));
                    cpl_image_abs(img);
                    visir_fftxcorrelate(shifted_avg[a], img, CPL_TRUE, &xshift, &yshift);
                    // FIXME: parameter value? skip the complete image
                    if (sqrt(SQR(xshift) + SQR(yshift)) > 5)
                        xshift = yshift = 0;
                    x4[a] = first_x[a] + xshift;
                    y4[a] = first_y[a] + yshift;
                    // FIXME: bad, add a subpixel shift or drop when first image good
                    cpl_image_shift(img, -round(xshift), -round(yshift));
                    cpl_image_multiply_scalar(shifted_avg[a], fcount[a]++);
                    cpl_image_add(shifted_avg[a], img);
                    cpl_image_divide_scalar(shifted_avg[a], fcount[a]);
                    cpl_msg_info(cpl_func, "%d: %f %f", a, x4[a], y4[a]);
                    cpl_image_delete(img);
                }
            }
            if (bmaxpos) {
                cpl_msg_info(cpl_func, "Brightest pixel");
#ifdef _OPENMP
#pragma omp parallel for
#endif
                for (int a = 0; a < 4; a++) {
                    cpl_size xshift = 0, yshift = 0;
                    double xsub, ysub;
                    double xlow = (int)(x4[a] - window / 2);
                    double ylow = (int)(y4[a] - window / 2);
                    cpl_image * img =
                        cpl_image_extract(avraw,
                                          CX_MAX(1, xlow),
                                          CX_MAX(1, ylow),
                                          CX_MIN(nx, x4[a] + window/2),
                                          CX_MIN(ny, y4[a] + window/2));
                    cpl_image_abs(img);
                    cpl_image_get_maxpos(img, &xshift, &yshift);
                    visir_get_subpixel_maxpos(img, xshift, yshift, &xsub, &ysub);
                    if (sqrt(SQR(xshift + xsub - (x4[a] - xlow)) +
                             SQR(yshift + ysub - (y4[a] - ylow))) < 5) {
                        x4[a] = xlow + xshift + xsub;
                        y4[a] = ylow + yshift + ysub;
                    }
                    cpl_msg_info(cpl_func, "%d: %f %f", a, x4[a], y4[a]);
                    cpl_image_delete(img);
                }
            }

            cpl_msg_info(cpl_func, "On-object in frame %d extension %d has "
                         "max-flux=%g at (%g,%g)", 1+iframe, index,
                         flux, x4[0], y4[0]);


            /* The A-on-frame */
            skip_if(save_image(plist, updlist, products, usedframes, parlist,
                               rawimage, rawname, auxname, "A_on",
                               iframe, index, proext, x4[0], y4[0], relflux));

            if(mode == VISIR_CHOPNOD_PERPENDICULAR) {

                /* The B-off-frame */
                skip_if(save_image(plist, updlist, products, usedframes, parlist,
                                   rawimage, rawname, auxname, "B_off",
                                   iframe, index, proext, x4[1], y4[1], relflux));

                /* The B-on-frame */
                skip_if(save_image(plist, updlist, products, usedframes, parlist,
                                   invimage, rawname, auxname, "B_on",
                                   iframe, index, proext, x4[3], y4[3], relflux));


                /* The A-off-frame */
                skip_if(save_image(plist, updlist, products, usedframes, parlist,
                                   invimage, rawname, auxname, "A_off",
                                   iframe, index, proext, x4[2], y4[2], relflux));

            } else {

                /* FIXME: check if index <-> beam assignment is correct */
                /* The B-off-frame */
                skip_if(save_image(plist, updlist, products, usedframes, parlist,
                                   rawimage, rawname, auxname, "B_off",
                                   iframe, index, proext, x4[2], y4[2], relflux));


                /* The B-on-frame */
                skip_if(save_image(plist, updlist, products, usedframes, parlist,
                                   invimage, rawname, auxname, "B_on",
                                   iframe, index, proext,x4[1], y4[1], relflux));

            }



            proext = index++; /* An image (product) was created/appended */
        }
        cpl_image_delete(avraw);
        cpl_image_delete(avinv);
    }

    for (frame = cpl_frameset_get_first_const(products);
         frame != NULL;
         frame = cpl_frameset_get_next_const(products)) {
        cpl_frame * copy = cpl_frame_duplicate(frame);
        cpl_error_code error;

        error = cpl_frameset_insert(framelist, copy);

        if (error) break;
    }
    bug_if(frame != NULL);

    end_skip;

    cpl_imagelist_delete(poslist);
    cpl_imagelist_delete(invlist);

    cpl_propertylist_delete(updlist);
    cpl_frameset_delete(usedframes);
    cpl_frameset_delete(products);
    for (int i = 0; i < 4; i++)
        cpl_image_delete(shifted_avg[i]);

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Process the i'th on-frame with its source table
  @param    framelist   The frameset to append products to
  @param    pflux1      0 means set it, when set divide the flux with it.
  @param    rawframes   The object frames list
  @param    tabframes   The table frames list
  @param    i           The frame to process (0 for first)
  @param    parlist     The parameters list
  @return   CPL_ERROR_NONE iff OK, otherwise the relevant CPL error code.
 */
/*----------------------------------------------------------------------------*/
static
cpl_error_code visir_util_convert_pos_one(cpl_frameset * framelist,
                                          double * pflux1,
                                          const irplib_framelist * rawframes,
                                          const irplib_framelist * tabframes,
                                          int i,
                                          const cpl_parameterlist * parlist)
{

    cpl_propertylist * updlist = cpl_propertylist_duplicate
        (irplib_framelist_get_propertylist_const(rawframes, i));
    const cpl_frame  * frame;
    cpl_frameset     * products   = cpl_frameset_new();
    cpl_frameset     * usedframes = cpl_frameset_new();
    cpl_propertylist * plsort = cpl_propertylist_new();
    const char    * imgname = cpl_frame_get_filename(irplib_framelist_get_const
                                                      (rawframes, i));
    const char    * tabname = cpl_frame_get_filename(irplib_framelist_get_const
                                                      (tabframes, i));
    cpl_table     * table = cpl_table_load(tabname, 1, 0);
    cpl_image     * image = cpl_image_load(imgname, CPL_TYPE_UNSPECIFIED, 0, 0);
    char          * proname = NULL;
    const cpl_size  nobjs = cpl_table_get_nrow(table);

    skip_if(0);
    bug_if(pflux1 == NULL);

    skip_if_lt(nobjs, 1, "row(s) in table of extracted sources with "
               "column " FLUX_COL ": %s", tabname);

    bug_if(cpl_frameset_insert(usedframes, cpl_frame_duplicate
                               (irplib_framelist_get_const(rawframes, i))));
    bug_if(cpl_frameset_insert(usedframes, cpl_frame_duplicate
                               (irplib_framelist_get_const(tabframes, i))));

    for (cpl_size iobj = 0; iobj < nobjs; iobj++) {
        cpl_size maxpos;
        double   xpos, ypos, relflux, flux;

        cpl_free(proname);
        proname = cpl_sprintf(RECIPE_STRING "_%d_%d" CPL_DFS_FITS,
                              (int)(1+i), (int)(1+iobj));

        skip_if(cpl_table_get_column_maxpos(table, FLUX_COL, &maxpos));

        xpos = cpl_table_get(table, X_COL,    maxpos, NULL);
        ypos = cpl_table_get(table, Y_COL,    maxpos, NULL);
        flux = cpl_table_get(table, FLUX_COL, maxpos, NULL);

        /* Flag as processed */
        bug_if(cpl_table_set_invalid(table, FLUX_COL, maxpos));

        if (*pflux1 != 0.0) {
            relflux = flux / *pflux1;
        } else {
            *pflux1 = flux;
            relflux = 1.0;
        }

        cpl_msg_info(cpl_func, "Row %d/%d in frame %d: (%g,%g)", 1+(int)maxpos,
                     (int)nobjs, (int)(1+i), xpos, ypos);

        bug_if(cpl_propertylist_append_double(updlist, "CRPIX1",      xpos));
        bug_if(cpl_propertylist_append_double(updlist, "CRPIX2",      ypos));
        bug_if(cpl_propertylist_append_double(updlist, VISIR_DRS_FLUX,relflux));
        bug_if(cpl_propertylist_append_double(updlist, VISIR_FLUX,    relflux));

        if (iobj == 0) {
            /* Comments copied from actual header */
            bug_if(cpl_propertylist_set_comment(updlist, "CRPIX1", "Windowed "
                                                "position estimate along x"));
            bug_if(cpl_propertylist_set_comment(updlist, "CRPIX2", "Windowed "
                                                "position estimate along y"));
            bug_if(cpl_propertylist_set_comment(updlist, VISIR_DRS_FLUX,
                                                VISIR_FLUX_COMM));
            bug_if(cpl_propertylist_set_comment(updlist, VISIR_FLUX,
                                                VISIR_FLUX_COMM));
        }

        skip_if(irplib_dfs_save_image(products, parlist, usedframes, image,
                                      CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                                      VISIR_UTIL_CONVERT_PROCATG, updlist,
                                      NULL, visir_pipe_id, proname));

    }

    /* Failure here is a bug */
    skip_if_lt(cpl_table_count_invalid(table, FLUX_COL), nobjs,
               "processed objects from table %s", tabname);

    for (frame = cpl_frameset_get_first_const(products);
         frame != NULL;
         frame = cpl_frameset_get_next_const(products)) {
        cpl_frame * copy = cpl_frame_duplicate(frame);
        cpl_error_code error;

        error = cpl_frameset_insert(framelist, copy);

        if (error) break;
    }
    bug_if(frame != NULL);

    end_skip;

    cpl_image_delete(image);
    cpl_table_delete(table);
    cpl_free(proname);
    cpl_propertylist_delete(updlist);
    cpl_propertylist_delete(plsort);
    cpl_frameset_delete(usedframes);
    cpl_frameset_delete(products);

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief  Process the i'th on-frame with its off-frame
  @param  framelist  The frameset to append products to
  @param  xpos       The updated X-position
  @param  ypos       The updated Y-position
  @param  iext       The extension number (0 for primay HDU)
  @param  proext     The extension number of last image extension (-1 for none)
  @param  i          The frame number
  @param  beamstr    The beam string, A_on, A_off, B_on, B_off
  @param  auxrawname The name of the auxiliary raw file to also update, or NULL
  @param  usedframes The list of raw/calibration frames used for this product
  @param  image      The image to be saved
  @param  applist    Optional propertylist to append to primary header or NULL
  @param  parlist    The parameters list
  @return CPL_ERROR_NONE iff OK, otherwise the relevant CPL error code.
 */
/*----------------------------------------------------------------------------*/
static
cpl_error_code visir_util_convert_pos_save(cpl_frameset * framelist,
                                           double xpos, double ypos,
                                           int iext, int proext, int i,
                                           const char * beamstr,
                                           const char * auxrawname,
                                           const cpl_frameset      * usedframes,
                                           const cpl_image         * image,
                                           const cpl_propertylist  * applist,
                                           const cpl_parameterlist * parlist)
{

    char       * proname =
        cpl_sprintf(RECIPE_STRING "_%d_%s" CPL_DFS_FITS, 1+i, beamstr);
    char       * auxproname = cpl_sprintf(RECIPE_STRING "_%d_%s_aux"
                                          CPL_DFS_FITS, 1+i, beamstr);
    cpl_propertylist * plempty = cpl_propertylist_new();
    cpl_propertylist * updlist = cpl_propertylist_duplicate(applist);
    cpl_image        * auximage = NULL;


    bug_if(proext >= iext);
    bug_if(framelist  == NULL);
    bug_if(beamstr    == NULL);
    bug_if(usedframes == NULL);
    bug_if(image      == NULL);
    bug_if(applist    == NULL);
    bug_if(parlist    == NULL);

    bug_if(cpl_propertylist_append_double(updlist, "CRPIX1", xpos));
    bug_if(cpl_propertylist_append_double(updlist, "CRPIX2", ypos));

    /* Comments copied from actual header */
    bug_if(cpl_propertylist_set_comment(updlist, "CRPIX1", "Windowed "
                                        "position estimate along x"));
    bug_if(cpl_propertylist_set_comment(updlist, "CRPIX2", "Windowed "
                                        "position estimate along y"));

    if (iext == 0) {
        skip_if(irplib_dfs_save_image(framelist, parlist, usedframes,
                                      image, CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                                      VISIR_UTIL_CONVERT_PROCATG, updlist,
                                      NULL, visir_pipe_id, proname));
        if (auxrawname != NULL) {
            auximage = cpl_image_load(auxrawname, CPL_TYPE_UNSPECIFIED, 0,
                                      iext);
            skip_if(auximage == NULL);
            skip_if(irplib_dfs_save_image(framelist, parlist, usedframes,
                                          auximage, CPL_BPP_IEEE_FLOAT,
                                          RECIPE_STRING,
                                          VISIR_UTIL_CONVERT_AUX_PROCATG,
                                          updlist, NULL, visir_pipe_id,
                                          auxproname));
        }
    } else {
        int jext;

        for (jext = 1 + proext; jext < iext; jext++) {
            if (jext == 0) {
                skip_if(irplib_dfs_save_propertylist(framelist, parlist,
                                                     usedframes, RECIPE_STRING,
                                                     VISIR_UTIL_CONVERT_PROCATG,
                                                     NULL, NULL, visir_pipe_id,
                                                     proname));
                if (auxrawname != NULL) {
                    skip_if(irplib_dfs_save_propertylist(framelist, parlist,
                                                         usedframes,
                                                         RECIPE_STRING,
                                                 VISIR_UTIL_CONVERT_AUX_PROCATG,
                                                         NULL, NULL,
                                                         visir_pipe_id,
                                                         auxproname));
                }
            } else {
                /* FIXME: Copy unused/unmodified extension header ? */
                skip_if(cpl_propertylist_save(plempty, proname,
                                              CPL_IO_EXTEND));
                if (auxrawname != NULL) {
                    skip_if(cpl_propertylist_save(plempty, auxproname,
                                                  CPL_IO_EXTEND));
                }
            }
        }
        skip_if(cpl_image_save(image, proname, CPL_BPP_IEEE_FLOAT,
                               updlist, CPL_IO_EXTEND));
        if (auxrawname != NULL) {
            auximage = cpl_image_load(auxrawname, CPL_TYPE_UNSPECIFIED, 0,
                                      iext);
            skip_if(auximage == NULL);
            skip_if(cpl_image_save(auximage, auxproname, CPL_BPP_IEEE_FLOAT,
                                   updlist, CPL_IO_EXTEND));
        }
    }

    end_skip;

    cpl_image_delete(auximage);
    cpl_free(proname);
    cpl_free(auxproname);
    cpl_propertylist_delete(plempty);
    cpl_propertylist_delete(updlist);

    return CPL_ERROR_NONE;
}
