/* $Id: visir_util_apply_calib.c,v 1.3 2013-05-23 14:34:43 jtaylor Exp $
 *
 * This file is part of the VISIR Pipeline
 * Copyright (C) 2012 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: 2013-05-23 14:34:43 $
 * $Revision: 1.3 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include "visir_recipe.h"
#include <cxlist.h>

#include <string.h>


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

#define RECIPE_STRING   "visir_util_apply_calib"

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

// FIXME:
cpl_recipe_define(visir_util_apply_calib, VISIR_BINARY_VERSION,
                  "Julian Taylor", PACKAGE_BUGREPORT, "2012",
                  "Attempt to remove stripes in spectral data",
                  "The files listed in the Set Of Frames (sof-file) "
                  "must be tagged:\n"
                  "VISIR-chopnod-corrected-file.fits "
                  "\nThe product(s) will have a FITS card\n"
                  "'HIERARCH ESO PRO CATG' with a value of:\n");

/*----------------------------------------------------------------------------*/
/**
 * @defgroup visir_util_apply_calib   remove stripes
 */
/*----------------------------------------------------------------------------*/

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

static inline cpl_boolean
plist_strcmp(const cpl_propertylist * plist,
             const char * key, const char * val)
{
    if (!cpl_propertylist_has(plist, key))
        return CPL_FALSE;
    return strcmp(cpl_propertylist_get_string(plist, key), val) == 0;
}


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

*/
/*----------------------------------------------------------------------------*/
static cpl_error_code
visir_util_apply_calib_fill_parameterlist(cpl_parameterlist * self)
{
    if (self) {}
    return CPL_ERROR_NONE;
}


static cpl_frameset *
get_img_conv(double * conv, double * error,
             irplib_framelist * phot_frames)
{
    cpl_vector * v = NULL;
    double derror = 0.;
    cpl_errorstate cleanstate = cpl_errorstate_get();
    irplib_framelist * one_frames =  NULL;
    irplib_framelist * cmb_frames =  NULL;
    cpl_frameset * usedframes = cpl_frameset_new();
    cpl_propertylist * cmb_plist;

    cpl_ensure(conv, CPL_ERROR_NULL_INPUT, NULL);

    skip_if(irplib_framelist_load_propertylist_all(phot_frames, 0,
                                                   "ESO QC", 0));

    cmb_frames = irplib_framelist_extract(phot_frames,
                                          VISIR_IMG_PHOT_COMBINED_PROCATG);

    skip_if(irplib_framelist_contains(cmb_frames, "ESO QC CONVER",
                                      CPL_TYPE_DOUBLE, CPL_FALSE, 0.));
    cmb_plist = irplib_framelist_get_propertylist(cmb_frames, 0);
    cpl_frameset_insert(usedframes,
                  cpl_frame_duplicate(irplib_framelist_get(cmb_frames, 0)));

    one_frames = irplib_framelist_extract(phot_frames,
                                          VISIR_IMG_PHOT_ONEBEAM_PROCATG);
    if (one_frames == NULL)
        cpl_errorstate_set(cleanstate);
    else {
        /* TODO unused */
        skip_if(irplib_framelist_contains(one_frames, "ESO QC CONVER",
                                          CPL_TYPE_DOUBLE, CPL_FALSE, 0.));

        v = cpl_vector_new(irplib_framelist_get_size(one_frames));

        for (int i = 0; i < irplib_framelist_get_size(one_frames); i++) {
            const cpl_propertylist * plist =
                irplib_framelist_get_propertylist_const(one_frames, i);
            double conver = cpl_propertylist_get_double(plist, "ESO QC CONVER");
            skip_if(0);

            cpl_vector_set(v, i, conver);
        }
    }

    *conv = cpl_propertylist_get_double(cmb_plist, "ESO QC CONVER");

    if (derror >= 0)
        cpl_msg_info(cpl_func, "Conversion factor: %g +- %g", *conv, derror);
    else
        cpl_msg_info(cpl_func, "Conversion factor: %g", *conv);

    if (error)
        *error = derror;

    end_skip;

    cpl_vector_delete(v);
    irplib_framelist_delete(cmb_frames);
    irplib_framelist_delete(one_frames);
    if (cpl_error_get_code()) {
        cpl_frameset_delete(usedframes);
    }

    return usedframes;
}

static cpl_error_code
update_error(cpl_image * eimg_, const cpl_image * img_,
             const double conv, const double cerror)
{
    cpl_image * img = cpl_image_cast(img_, CPL_TYPE_DOUBLE);
    cpl_image * eimg = cpl_image_cast(eimg_, CPL_TYPE_DOUBLE);
    size_t nx = cpl_image_get_size_x(img);
    size_t npix = nx * cpl_image_get_size_y(img);
    double * eimgd = cpl_image_get_data_double(eimg);
    const double * imgd = cpl_image_get_data_double_const(img);

    cpl_ensure_code(conv != 0. && cerror >= 0., CPL_ERROR_ILLEGAL_INPUT);
    skip_if(imgd == NULL || eimgd == NULL);

    for (size_t i = 0; i < npix; i++) {
        /* gaussian propagation */
        double da = eimgd[i] / conv;
        double db = cerror * imgd[i] / (conv * conv);
        cpl_image_set(eimg_, i % nx + 1, i / nx + 1, hypot(da, db));
    }

    end_skip;
    cpl_image_delete(img);
    cpl_image_delete(eimg);

    return cpl_error_get_code();
}

typedef struct {
    cpl_image * img;
    cpl_table * tbl;
    cpl_propertylist * plist;
} visir_plane;

static visir_plane *
visir_plane_new(cpl_image * img, cpl_table * tbl, cpl_propertylist * plist)
{
    visir_plane * pl = cpl_calloc(1, sizeof(visir_plane));
    pl->img = img;
    pl->tbl = tbl;
    pl->plist = plist;
    return pl;
}

static void visir_plane_delete(visir_plane * pl)
{
    if (pl) {
        cpl_image_delete(pl->img);
        cpl_table_delete(pl->tbl);
        cpl_propertylist_delete(pl->plist);
        cpl_free(pl);
    }
}


static cx_list *
planelist_from_frame(const cpl_frame * frame)
{
    cpl_size next = cpl_frame_get_nextensions(frame);
    const char * fn = cpl_frame_get_filename(frame);
    cpl_errorstate cleanstate = cpl_errorstate_get();
    cx_list * planelist = NULL;
    cpl_propertylist * plist = NULL;
    cpl_image * img = NULL;
    cpl_table * tbl = NULL;

    plist = cpl_propertylist_load(fn, 0);
    skip_if(plist == NULL);
    planelist = cx_list_new();

    img = cpl_image_load(fn, CPL_TYPE_UNSPECIFIED, 0, 0);
    if (img == NULL)
        cpl_errorstate_set(cleanstate);

    cx_list_push_back(planelist, visir_plane_new(img, NULL, plist));
    img = NULL;
    tbl = NULL;
    plist = NULL;

    for (cpl_size e = 1; e < next + 1; e++) {
        img = cpl_image_load(fn, CPL_TYPE_UNSPECIFIED, 0, e);
        if (img == 0) {
            cpl_errorstate_set(cleanstate);
            tbl = cpl_table_load(fn, e, 0);
        }
        skip_if(0);
        plist = cpl_propertylist_load(fn, e);
        skip_if(plist == NULL);
        cx_list_push_back(planelist, visir_plane_new(img, tbl, plist));
        img = NULL;
        tbl = NULL;
        plist = NULL;
    }

    end_skip;
    cpl_image_delete(img);
    cpl_propertylist_delete(plist);

    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        cx_list_destroy(planelist, (visir_free)visir_plane_delete);
        return NULL;
    }
    else
        return planelist;
}


static cpl_error_code
apply_img_calib(cpl_frameset *framelist, const cpl_parameterlist * parlist,
                irplib_framelist * objects,
                const double conv, const double error,
                cpl_frameset * calibframes)
{
    cpl_frameset * usedframes = cpl_frameset_new();
    cx_list * planes = NULL;

    for (int i = 0; i < irplib_framelist_get_size(objects); i++) {
        cpl_frame * frm = irplib_framelist_get(objects, i);
        cpl_image * eimg = NULL;
        char buffer[300];
        visir_plane * skip[] = {NULL, NULL};

        planes = planelist_from_frame(frm);
        skip_if(planes == NULL);

        cpl_frame_set_group(frm, CPL_FRAME_GROUP_RAW);

        cpl_frameset_insert(usedframes, cpl_frame_duplicate(frm));
        FOR_EACH_FRAMESET(cfrm, calibframes) {
            cpl_frame_set_group(cfrm, CPL_FRAME_GROUP_RAW);
            cpl_frameset_insert(usedframes, cpl_frame_duplicate(cfrm));
        }
        skip_if(0);

        visir_plane * first = cx_list_front(planes);
        cpl_image * simg =
            cpl_image_multiply_scalar_create(first->img, 1. / conv);
        skip_if(simg == NULL);
        cpl_propertylist_update_string(first->plist, "BUNIT", "Jy");

        sprintf(buffer, "visir_calibrated_%03d.fits", i + 1);
        irplib_dfs_save_image(framelist, parlist, usedframes, simg,
                              CPL_TYPE_FLOAT, RECIPE_STRING,
                              VISIR_IMG_OBJ_CALIBRATED_PROCATG, first->plist,
                              NULL, visir_pipe_id, buffer);
        cpl_image_delete(simg);
        skip_if(0);

        /* propagate the error */
        FOR_EACH_T(visir_plane * pl, planes) {
            if (plist_strcmp(pl->plist, "EXTNAME", VISIR_EXTN_ERROR)) {
                eimg = pl->img;

                update_error(eimg, first->img, conv, error);
                cpl_propertylist_update_string(pl->plist, "BUNIT", "Jy");

                cpl_image_save(eimg, buffer, CPL_TYPE_FLOAT,
                               pl->plist, CPL_IO_EXTEND);
                skip[0] = pl;
            }
            skip_if(0);
        }
        /* fix the weight map */
        FOR_EACH_T(visir_plane * pl, planes) {
            if (plist_strcmp(pl->plist, "EXTNAME", VISIR_EXTN_WEIGHT)) {
                cpl_image * wgt = cpl_image_power_create(eimg, -2);
                cpl_image_fill_rejected(wgt, 0);

                cpl_image_save(wgt, buffer, CPL_TYPE_FLOAT,
                               pl->plist, CPL_IO_EXTEND);
                cpl_image_delete(wgt);
                skip[1] = pl;
            }
            skip_if(0);
        }
        /* propagate the rest */
        FOR_EACH_T(visir_plane * pl, planes) {
            if (pl == first || pl == skip[0] || pl == skip[1])
                continue;

            if (pl->img)
                cpl_image_save(pl->img, buffer, CPL_TYPE_UNSPECIFIED,
                               pl->plist, CPL_IO_EXTEND);
            else if (pl->tbl)
                cpl_table_save(pl->tbl, NULL, pl->plist, buffer,
                               CPL_IO_EXTEND);
            else
                cpl_propertylist_save(pl->plist, buffer, CPL_IO_EXTEND);
            skip_if(0);
        }
        cx_list_destroy(planes, (visir_free)visir_plane_delete);
        planes = NULL;
    }

    end_skip;
    cx_list_destroy(planes, (visir_free)visir_plane_delete);

    cpl_frameset_delete(usedframes);

    return cpl_error_get_code();
}

static cpl_error_code
propagate_all(cpl_frameset * framelist, const cpl_parameterlist * parlist,
              irplib_framelist * allframes)
{
    cpl_frameset * usedframes = cpl_frameset_new();
    cx_list * planes = NULL;

    for (int i = 0; i < irplib_framelist_get_size(allframes); i++) {
        cpl_frame * frm = irplib_framelist_get(allframes, i);
        char buffer[300];
        sprintf(buffer, "visir_result_%03d.fits", i + 1);

        cpl_frame_set_group(frm, CPL_FRAME_GROUP_RAW);
        cpl_frameset_insert(usedframes, cpl_frame_duplicate(frm));

        planes = planelist_from_frame(frm);
        skip_if(planes == NULL);
        visir_plane * first = cx_list_front(planes);

        if (first->img)
            irplib_dfs_save_image(framelist, parlist, usedframes, first->img,
                                  CPL_TYPE_UNSPECIFIED, RECIPE_STRING,
                                  cpl_frame_get_tag(frm), first->plist,
                                  NULL, visir_pipe_id, buffer);
        else
            irplib_dfs_save_propertylist(framelist, parlist, usedframes,
                                         RECIPE_STRING,
                                         cpl_frame_get_tag(frm),
                                         first->plist, NULL,
                                         visir_pipe_id, buffer);
        skip_if(0);

        FOR_EACH_T(visir_plane * pl, planes) {
            if (pl == first)
                continue;
            if (pl->img)
                cpl_image_save(pl->img, buffer, CPL_TYPE_UNSPECIFIED,
                               pl->plist, CPL_IO_EXTEND);
            else if (pl->tbl)
                cpl_table_save(pl->tbl, NULL, pl->plist, buffer,
                               CPL_IO_EXTEND);
            else
                cpl_propertylist_save(pl->plist, buffer, CPL_IO_EXTEND);
            skip_if(0);
        }

        cx_list_destroy(planes, (visir_free)visir_plane_delete);
        planes = NULL;
    }

    end_skip;
    cpl_frameset_delete(usedframes);
    cx_list_destroy(planes, (visir_free)visir_plane_delete);

    return cpl_error_get_code();
}


static cpl_table *
calibrate_frame(const cpl_frame * frm, const cpl_bivector * std,
                const cpl_bivector * stde)
{
    const char * dfn = cpl_frame_get_filename(frm);
    const cpl_vector * swlen = cpl_bivector_get_x_const(std);
    cpl_bivector * intstd = NULL;
    cpl_bivector * intstde = NULL;
    cpl_table * obj = cpl_table_load(dfn, 1, 0);
    skip_if(obj == NULL);
    const size_t n = cpl_table_get_nrow(obj);

    /* get region where calib and object wavelength overlap */
    cpl_vector * objv =
        cpl_vector_wrap(n, cpl_table_get_data_double(obj, "WLEN"));

    const double smin = cpl_vector_get(swlen, 0);
    const double smax = cpl_vector_get(swlen, cpl_vector_get_size(swlen) - 1);
    const size_t l = visir_lower_bound(objv, smin);
    const size_t u = visir_upper_bound(objv, smax);

    if (u - l == 0) {
        cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
                              "Calibration data has no wavelength overlap "
                              "with object");
        cpl_vector_unwrap(objv);
        skip_if(1);
    }
    cpl_msg_info(cpl_func, "Overlapping wavelength: %.4g [mu] (%zu) to "
                 "%.4g [mu] (%zu)", cpl_vector_get(objv, l) * 1e6, l,
                 cpl_vector_get(objv, u - 1) * 1e6, u);
    cpl_vector_unwrap(objv);

    {
        /* prepare data and error on same grid as object */
        intstd = cpl_bivector_new(u - l);
        intstde = cpl_bivector_new(u - l);
        skip_if(intstd == NULL);
        skip_if(intstde == NULL);
        /* copy object wavelength grid */
        memcpy(cpl_bivector_get_x_data(intstd),
               cpl_table_get_data_double_const(obj, "WLEN") + l,
               sizeof(double) * (u - l));
        memcpy(cpl_bivector_get_x_data(intstde),
               cpl_table_get_data_double_const(obj, "WLEN") + l,
               sizeof(double) * (u - l));

        /* interpolate standard star data to object wavelength grid */
        skip_if(cpl_bivector_interpolate_linear(intstd, std));
        skip_if(cpl_bivector_interpolate_linear(intstde, stde));
    }

    skip_if(0);

    /* calibrate the data, marking out of bound as invalid */
    cpl_table_duplicate_column(obj, "SPC_CALIBRATED",
                               obj, "SPC_EXTRACTED");
    skip_if(0);
    {
        double * d = cpl_table_get_data_double(obj, "SPC_CALIBRATED");
        double * v = cpl_vector_get_data(cpl_bivector_get_y(intstd));
        for (size_t i = 0; i < n; i++) {
            if (i < l || i >= u)
                cpl_table_set_invalid(obj, "SPC_CALIBRATED", i);
            else
                d[i] /= v[i - l];
        }
    }

    cpl_table_new_column(obj, "SPC_CALIBRATED_ERROR", CPL_TYPE_DOUBLE);
    skip_if(0);

    {
        double * ed = cpl_table_get_data_double(obj, "SPC_ERROR");
        double * vd = cpl_table_get_data_double(obj, "SPC_EXTRACTED");
        double * svd = cpl_bivector_get_y_data(intstd);
        double * sed = cpl_bivector_get_y_data(intstde);
        for (size_t j = 0; j < n; j++) {
            if (j < l || j >= u)
                cpl_table_set_invalid(obj, "SPC_CALIBRATED_ERROR", j);
            else {
                double da = ed[j] / svd[j - l];
                double db = sed[j - l] * vd[j] / (svd[j - l] * svd[j - l]);
                cpl_table_set_double(obj, "SPC_CALIBRATED_ERROR",
                                     j, hypot(da, db));
            }
        }
    }

    skip_if(0);

    end_skip;

    cpl_bivector_delete(intstd);
    cpl_bivector_delete(intstde);

    return obj;
}

static cpl_error_code
get_spec_std(cpl_frameset            * framelist,
             const irplib_framelist * stdtab,
             const irplib_framelist * objtab,
             const cpl_parameterlist * parlist,
             const char              * procatg)
{
    const cpl_frame * frm;
    cpl_bivector * std = NULL;
    cpl_bivector * stde = NULL;
    skip_if(stdtab == NULL);
    skip_if(objtab == NULL);
    cpl_ensure_code(irplib_framelist_get_size(stdtab) == 1,
                    CPL_ERROR_UNSUPPORTED_MODE);
    cpl_ensure_code(irplib_framelist_get_size(objtab) > 0,
                    CPL_ERROR_UNSUPPORTED_MODE);

    frm = irplib_framelist_get_const(stdtab, 0);
    skip_if(frm == NULL);
    {
        const char * fn = cpl_frame_get_filename(frm);
        cpl_bivector * model = visir_bivector_load_fits(fn, "WLEN",
                                                        "STD_STAR_MODEL", 1);
        std = visir_bivector_load_fits(fn, "WLEN", "SPC_EXTRACTED", 1);
        stde = visir_bivector_load_fits(fn, "WLEN", "SPC_ERROR", 1);

        /* calibrate spectrum, FIXME: do in spc_obs? */
        cpl_vector_divide(cpl_bivector_get_y(std), cpl_bivector_get_y(model));
        cpl_vector_divide(cpl_bivector_get_y(stde), cpl_bivector_get_y(model));
        cpl_bivector_delete(model);
    }
    skip_if(0);

    for (int i = 0; i < irplib_framelist_get_size(objtab); i++) {
        char buffer[300];
        cpl_frame * ofrm =
            cpl_frame_duplicate(irplib_framelist_get_const(objtab, i));
        cpl_frame * sfrm = cpl_frame_duplicate(frm);
        cpl_frameset * usedframes = cpl_frameset_new();
        cpl_frame_set_group(sfrm, CPL_FRAME_GROUP_CALIB);
        cpl_frame_set_group(ofrm, CPL_FRAME_GROUP_RAW);
        cpl_frameset_insert(usedframes, sfrm);
        cpl_frameset_insert(usedframes, ofrm);

        cpl_table * obj = calibrate_frame(ofrm, std, stde);
        sprintf(buffer, "visir_calibrated_%03d.fits", i + 1);
        irplib_dfs_save_table(framelist, parlist, usedframes, obj,
                                      NULL, RECIPE_STRING, procatg, NULL,/* todo*/
                                      NULL, visir_pipe_id, buffer);

        /* add the image and extraction weight too (see visir_spc_obs_save) */
        if (cpl_frame_get_nextensions(ofrm) == 3) {
            cpl_propertylist * plist =
                cpl_propertylist_load(cpl_frame_get_filename(ofrm), 2);
            cpl_image * img = cpl_image_load(cpl_frame_get_filename(ofrm),
                                             CPL_TYPE_UNSPECIFIED, 0, 2);
            cpl_image_save(img, buffer, CPL_TYPE_UNSPECIFIED, plist, CPL_IO_EXTEND);
            cpl_image_delete(img);
            cpl_propertylist_delete(plist);
            plist = cpl_propertylist_load(cpl_frame_get_filename(ofrm), 2);
            img = cpl_image_load(cpl_frame_get_filename(ofrm),
                                 CPL_TYPE_UNSPECIFIED, 0, 2);
            cpl_image_save(img, buffer, CPL_TYPE_UNSPECIFIED, plist, CPL_IO_EXTEND);
            cpl_image_delete(img);
            cpl_propertylist_delete(plist);
        }

        cpl_frameset_delete(usedframes);
        cpl_table_delete(obj);
        skip_if(0);
    }

    end_skip;

    cpl_bivector_delete(std);
    cpl_bivector_delete(stde);

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @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_apply_calib(cpl_frameset            * framelist,
                                  const cpl_parameterlist * parlist)
{
    irplib_framelist * allframes = irplib_framelist_cast(framelist);
    cpl_errorstate cleanstate = cpl_errorstate_get();

    irplib_framelist * others = NULL;
    irplib_framelist * objects =
        irplib_framelist_extract_regexp(allframes, ".*OBJ.*", CPL_FALSE);
    irplib_framelist * specobs =
        irplib_framelist_extract_regexp(allframes, ".*SPC_OBS.*", CPL_FALSE);

    if (irplib_framelist_get_size(allframes) > 0)
        cpl_errorstate_set(cleanstate);

    /* no objects or only objects -> nothing to apply */
    if (objects && irplib_framelist_get_size(objects) <
        irplib_framelist_get_size(allframes)) {
        double conv = 1., error = 0.;
        cpl_msg_info(cpl_func, "Applying conversion factor");
        cpl_frameset * calibframes = get_img_conv(&conv, &error, allframes);
        skip_if(calibframes == NULL);
        skip_if(apply_img_calib(framelist, parlist, objects, conv, error, calibframes));
        others = irplib_framelist_extract_regexp(allframes, ".*OBJ.*", CPL_TRUE);
        skip_if(propagate_all(framelist, parlist, others));
    }
    else if (specobs && irplib_framelist_get_size(specobs) <
             irplib_framelist_get_size(allframes)) {
        irplib_framelist * stdtab =
            irplib_framelist_extract_regexp(allframes, ".*PHOT.*TAB", CPL_FALSE);
        irplib_framelist * objtab =
            irplib_framelist_extract_regexp(allframes, ".*OBS.*TAB", CPL_FALSE);
        cpl_msg_info(cpl_func, "Removing telluric lines");
        skip_if(get_spec_std(framelist, stdtab, objtab,
                             parlist, "SPC_OBS_LMR_TAB"));
        others = irplib_framelist_extract_regexp(allframes, ".*OBS.*TAB", CPL_TRUE);
        propagate_all(framelist, parlist, others);
        irplib_framelist_delete(objtab);
        irplib_framelist_delete(stdtab);
        skip_if(0);
    }
    else {
        cpl_msg_info(cpl_func, "No calibration data to apply, doing nothing");
        skip_if(propagate_all(framelist, parlist, allframes));
    }

    end_skip;
    irplib_framelist_delete(allframes);
    irplib_framelist_delete(objects);
    irplib_framelist_delete(specobs);
    irplib_framelist_delete(others);

    return cpl_error_get_code();
}
