KMOS Pipeline Reference Manual  1.3.0
kmo_flat.c
00001 /* 
00002  * This file is part of the KMOS Pipeline
00003  * Copyright (C) 2002,2003 European Southern Observatory
00004  *
00005  * This program is free software; you can redistribute it and/or modify
00006  * it under the terms of the GNU General Public License as published by
00007  * the Free Software Foundation; either version 2 of the License, or
00008  * (at your option) any later version.
00009  *
00010  * This program is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  * GNU General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License
00016  * along with this program; if not, write to the Free Software
00017  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00018  */
00019 
00020 #ifdef HAVE_CONFIG_H
00021 #include <config.h>
00022 #endif
00023 
00024 /*-----------------------------------------------------------------------------
00025  *                              Includes
00026  *----------------------------------------------------------------------------*/
00027 #include <string.h>
00028 #include <math.h>
00029 
00030 #include <cpl.h>
00031 
00032 #include "kmo_utils.h"
00033 #include "kmo_priv_flat.h"
00034 #include "kmo_priv_functions.h"
00035 #include "kmo_dfs.h"
00036 #include "kmo_error.h"
00037 #include "kmo_constants.h"
00038 #include "kmo_cpl_extensions.h"
00039 #include "kmo_debug.h"
00040 
00041 /*-----------------------------------------------------------------------------
00042  *                          Functions prototypes
00043  *----------------------------------------------------------------------------*/
00044 
00045 static int kmo_flat_create(cpl_plugin *);
00046 static int kmo_flat_exec(cpl_plugin *);
00047 static int kmo_flat_destroy(cpl_plugin *);
00048 static int kmo_flat(cpl_parameterlist *, cpl_frameset *);
00049 
00050 /*-----------------------------------------------------------------------------
00051  *                          Static variables
00052  *----------------------------------------------------------------------------*/
00053 
00054 static char kmo_flat_description[] =
00055 "This recipe creates the master flat field and calibration frames needed for\n"
00056 "spatial calibration for all three detectors. It must be called after the \n"
00057 "kmo_dark-recipe, which generates a bad pixel mask (badpixel_dark.fits). The\n"
00058 "bad pixel mask will be updated in this recipe.\n"
00059 "As input at least 3 dark frames, 3 frames with the flat lamp on are\n"
00060 "recommended. Additionally a badpixel mask from kmo_dark is required.\n"
00061 "\n"
00062 "The badpixel mask contains 0 for bad pixels and 1 for good ones.\n"
00063 "\n"
00064 "The structure of the resulting xcal and ycal frames is quite complex since\n"
00065 "the arrangement of the IFUs isn't just linear on the detector. Basically the\n"
00066 "integer part of the calibration data shows the offset of each pixels centre\n"
00067 "in mas (Milli arcsec) from the field centre. The viewing of an IFU is\n"
00068 "2800 mas (14pix*0.2arcsec/pix). So the values in these two frames will vary\n"
00069 "between +/-1500 (One would expect 1400, but since the slitlets aren't\n"
00070 "expected to be exactly vertical, the values can even go up to around 1500).\n"
00071 "Additionally in the calibration data in y-direction the decimal part of the\n"
00072 "data designates the IFU to which the slitlet corresponds to (for each\n"
00073 "detector from 1 to 8).\n"
00074 "Because of the irregular arrangement of the IFUs not all x-direction\n"
00075 "calibration data is found in xcal and similarly not all y-direction\n"
00076 "calibration data is located in ycal. For certain IFUs they are switched\n"
00077 " and/or flipped in x- or y-direction:\n"
00078 "For IFUs 1,2,3,4,13,14,15,16:  x- and y- data is switched\n"
00079 "For IFUs 17,18,19,20:          y-data is flipped \n"
00080 "For IFUs 21,22,23,24:          x-data is flipped \n"
00081 "For IFUs 5,6,7,8,9,10,11,12:   x- and y- data is switched and\n"
00082 "                               x- and y- data is flipped\n"
00083 "\n"
00084 "Furthermore frames can be provided for several rotator angles. In this case\n"
00085 "the resulting calibration frames for each detector are repeatedly saved as \n"
00086 "extension for every angle.\n"
00087 "\n"
00088 "Advanced features:\n"
00089 "------------------\n"
00090 "To create the badpixel mask the edges of all slitlets are fitted to a\n"
00091 "polynomial. Since it can happen that some of these fits (3 detectors\n"
00092 "8 IFUs * 14slitlets * 2 edges  (left and right edge of slitlet)= 672 edges)\n"
00093 "fail, the fit parameters are themselves fitted again to detect any outliers.\n"
00094 "By default, the parameters of all left and all right edges are grouped\n"
00095 "individually and then fitted using chebyshev polynomials. The advantage of\n"
00096 "a chebyshev polynomial is, that it consists in fact of a series of\n"
00097 "orthogonal polynomials. This implies that the parameters of the polynomials\n"
00098 "are independent. This fact predestines the use of chebyshev polynomials\n"
00099 "for our case. So each individual parameter can be examined independently.\n"
00100 "The reason why the left and right edges are fitted individually is that\n"
00101 "there is a systematic pattern specific to these groups. The reason for\n"
00102 "this pattern is probably to be found in the optical path the light is\n"
00103 "traversing.\n"
00104 "\n"
00105 "The behaviour of this fitting step can be influenced via environment\n"
00106 "parameters:\n"
00107 "* KF_ALLPARS (default: 1)\n"
00108 "  When set to 1 all coefficients of the polynomial of an edge are to be\n"
00109 "  corrected, also when just one of these coefficients is an outlier. When\n"
00110 "  set to 0 only the outlier is to be corrected.\n"
00111 "* KF_CH (default: 1)\n"
00112 "  When set to 1 chebyshev polynomials are used to fit the fitted parameters.\n"
00113 "  When set to 0 normal polynomials are used.\n"
00114 "* KF_SIDES (default: 2)\n"
00115 "  This variable can either be set to 1 or 2. When set to 2 the left and\n"
00116 "  right edges are examined individually. When set to 1 all edges are\n"
00117 "  examined as one group.\n"
00118 "* KF_FACTOR(default: 4)\n"
00119 "  This factor defines the threshold factor. All parameters deviating \n"
00120 "  KF_FACTOR*stddev are to be corrected\n"
00121 "\n"
00122 "BASIC PARAMETERS:\n"
00123 "-----------------\n"
00124 "--badpix_thresh\n"
00125 "The threshold level to mark pixels as bad on the dark subtracted frames [%]"
00126 "\n"
00127 "--surrounding_pixels\n"
00128 "The amount of bad pixels to surround a specific pixel, to let it be marked\n"
00129 "bad as well.\n"
00130 "\n"
00131 "--cmethod\n"
00132 "Following methods of frame combination are available:\n"
00133 "   * 'ksigma' (Default)\n"
00134 "   An iterative sigma clipping. For each position all pixels in the\n"
00135 "   spectrum are examined. If they deviate significantly, they will be\n"
00136 "   rejected according to the conditions:\n"
00137 "       val > mean + stdev * cpos_rej\n"
00138 "   and\n"
00139 "       val < mean - stdev * cneg_rej\n"
00140 "   where --cpos_rej, --cneg_rej and --citer are the configuration\n"
00141 "   parameters. In the first iteration median and percentile level are used.\n"
00142 "\n"
00143 "   * 'median'\n"
00144 "   At each pixel position the median is calculated.\n"
00145 "\n"
00146 "   * 'average'\n"
00147 "   At each pixel position the average is calculated.\n"
00148 "\n"
00149 "   * 'sum'\n"
00150 "   At each pixel position the sum is calculated.\n"
00151 "\n"
00152 "   * 'min_max'\n"
00153 "   The specified number of min and max pixel values will be rejected.\n"
00154 "   --cmax and --cmin apply to this method.\n"
00155 "\n"
00156 "ADVANCED PARAMETERS\n"
00157 "-------------------\n"
00158 "--cpos_rej\n"
00159 "--cneg_rej\n"
00160 "--citer\n"
00161 "see --cmethod='ksigma'\n"
00162 "\n"
00163 "--cmax\n"
00164 "--cmin\n"
00165 "see --cmethod='min_max'\n"
00166 "\n"
00167 "--suppress_extension\n"
00168 "If set to TRUE, the arbitrary filename extensions are supressed. If\n"
00169 "multiple products with the same category are produced, they will be\n"
00170 "numered consecutively starting from 0.\n"
00171 "\n"
00172 "-------------------------------------------------------------------------------\n"
00173 "  Input files:\n"
00174 "   DO CATG           Type   Explanation                    Required #Frames\n"
00175 "   -------           -----  -----------                    -------- -------\n"
00176 "   FLAT_ON           RAW    Flatlamp-on exposures             Y       1-n  \n"
00177 "                            (at least 3 frames recommended)                \n"
00178 "   FLAT_OFF          RAW    Flatlamp-off exposures            Y       1-n  \n"
00179 "                            (at least 3 frames recommended)                \n"
00180 "   BADPIXEL_DARK     B2D    Bad pixel mask                    Y        1   \n"
00181 "\n"
00182 "  Output files:\n"
00183 "   DO CATG           Type   Explanation\n"
00184 "   -------           -----  -----------\n"
00185 "   MASTER_FLAT       F2D    Normalised flat field\n"
00186 "                            (6 extensions: alternating data & noise\n"
00187 "   BADPIXEL_FLAT     B2D    Updated bad pixel mask (3 Extensions)\n"
00188 "   XCAL              F2D    Calibration frame 1 (3 Extensions)\n"
00189 "   YCAL              F2D    Calibration frame 2 (3 Extensions)\n"
00190 "   FLAT_EDGE         F2L    Frame containing parameters of fitted \n"
00191 "                            slitlets of all IFUs of all detectors\n"
00192 "---------------------------------------------------------------------------"
00193 "\n";
00194 
00195 /*-----------------------------------------------------------------------------
00196  *                              Functions code
00197  *----------------------------------------------------------------------------*/
00198 
00205 /*----------------------------------------------------------------------------*/
00214 /*----------------------------------------------------------------------------*/
00215 int cpl_plugin_get_info(cpl_pluginlist *list)
00216 {
00217     cpl_recipe *recipe = cpl_calloc(1, sizeof *recipe);
00218     cpl_plugin *plugin = &recipe->interface;
00219 
00220     cpl_plugin_init(plugin,
00221             CPL_PLUGIN_API,
00222             KMOS_BINARY_VERSION,
00223             CPL_PLUGIN_TYPE_RECIPE,
00224             "kmo_flat",
00225             "Create master flatfield frame and badpixel map",
00226             kmo_flat_description,
00227             "Alex Agudo Berbel",
00228             "usd-help@eso.org",
00229             kmos_get_license(),
00230             kmo_flat_create,
00231             kmo_flat_exec,
00232             kmo_flat_destroy);
00233 
00234     cpl_pluginlist_append(list, plugin);
00235 
00236     return 0;
00237 }
00238 
00239 /*----------------------------------------------------------------------------*/
00247 /*----------------------------------------------------------------------------*/
00248 static int kmo_flat_create(cpl_plugin *plugin)
00249 {
00250     cpl_recipe *recipe;
00251     cpl_parameter *p;
00252 
00253     /* Check that the plugin is part of a valid recipe */
00254     if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
00255         recipe = (cpl_recipe *)plugin;
00256     else
00257         return -1;
00258 
00259     /* Create the parameters list in the cpl_recipe object */
00260     recipe->parameters = cpl_parameterlist_new();
00261 
00262     /* Fill the parameters list */
00263 
00264     /* --badpix_thresh */
00265     p = cpl_parameter_new_value("kmos.kmo_flat.badpix_thresh",
00266             CPL_TYPE_INT,
00267             "The threshold level to mark pixels as bad on the dark subtracted frames [%].",
00268             "kmos.kmo_flat", 35);
00269     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "badpix_thresh");
00270     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00271     cpl_parameterlist_append(recipe->parameters, p);
00272 
00273     /* --surrounding_pixels */
00274     p = cpl_parameter_new_value("kmos.kmo_flat.surrounding_pixels",
00275             CPL_TYPE_INT,
00276             "The amount of bad pixels to surround a specific pixel, to let it be marked bad as well.",
00277             "kmos.kmo_flat", 5);
00278     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "surrounding_pixels");
00279     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00280     cpl_parameterlist_append(recipe->parameters, p);
00281 
00282     /* --suppress_extension */
00283     p = cpl_parameter_new_value("kmos.kmo_flat.suppress_extension",
00284             CPL_TYPE_BOOL,
00285             "Suppress arbitrary filename extension. (TRUE (apply) or FALSE (don't apply)",
00286             "kmos.kmo_flat", FALSE);
00287     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "suppress_extension");
00288     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00289     cpl_parameterlist_append(recipe->parameters, p);
00290 
00291     return kmo_combine_pars_create(recipe->parameters, "kmos.kmo_flat", 
00292             DEF_REJ_METHOD, FALSE);
00293 }
00294 
00295 /*----------------------------------------------------------------------------*/
00301 /*----------------------------------------------------------------------------*/
00302 static int kmo_flat_exec(cpl_plugin *plugin)
00303 {
00304     cpl_recipe  *recipe;
00305 
00306     /* Get the recipe out of the plugin */
00307     if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
00308         recipe = (cpl_recipe *)plugin;
00309     else return -1;
00310 
00311     return kmo_flat(recipe->parameters, recipe->frames);
00312 }
00313 
00314 /*----------------------------------------------------------------------------*/
00320 /*----------------------------------------------------------------------------*/
00321 static int kmo_flat_destroy(cpl_plugin *plugin)
00322 {
00323     cpl_recipe *recipe;
00324 
00325     /* Get the recipe out of the plugin */
00326     if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
00327         recipe = (cpl_recipe *)plugin;
00328     else return -1 ;
00329 
00330     cpl_parameterlist_delete(recipe->parameters);
00331     return 0 ;
00332 }
00333 
00334 /*----------------------------------------------------------------------------*/
00348 /*----------------------------------------------------------------------------*/
00349 static int kmo_flat(cpl_parameterlist *parlist, cpl_frameset *frameset)
00350 {
00351     cpl_imagelist    *det_lamp_on           = NULL,
00352                      *det_lamp_off          = NULL;
00353 
00354     cpl_image        *img_in                = NULL,
00355                      *combined_data_on      = NULL,
00356                      *combined_noise_on     = NULL,
00357                      *combined_data_off     = NULL,
00358                      *combined_noise_off    = NULL,
00359                      *bad_pix_mask_flat     = NULL,
00360                      *bad_pix_mask_dark     = NULL,
00361                      *xcal                  = NULL,
00362                      *ycal                  = NULL;
00363 
00364     cpl_image        **stored_flat          = NULL,
00365                      **stored_noise         = NULL,
00366                      **stored_badpix        = NULL,
00367                      **stored_xcal          = NULL,
00368                      **stored_ycal          = NULL;
00369 
00370     cpl_frame        *frame                 = NULL;
00371     cpl_frameset     ** angle_frameset     = NULL;
00372 
00373     int              ret_val                = 0,
00374                      nr_devices             = 0,
00375                      sx                     = 0,
00376                      cmax                   = 0,
00377                      cmin                   = 0,
00378                      citer                  = 0,
00379                      surrounding_pixels     = 0,
00380                      badpix_thresh          = 0,
00381                      nx                     = 0,
00382                      ny                     = 0,
00383                      nz                     = 0,
00384                      *stored_qc_flat_sat    = NULL,
00385                      *bounds                = NULL,
00386                      **total_bounds         = NULL,
00387                      nr_bad_pix             = 0,
00388                      ndit                   = 0,
00389                      suppress_extension     = FALSE,
00390                      nr_sat                 = 0,
00391                      nr_angles              = 0,
00392                      i = 0, j = 0, ax = 0, a = 0;
00393     cpl_size         ix = 0, iy = 0;
00394 
00395     double           cpos_rej               = 0.0,
00396                      cneg_rej               = 0.0,
00397                      gain                   = 0.0,
00398                      exptime                = 0.0,
00399                      *stored_qc_flat_eff    = NULL,
00400                      *stored_qc_flat_sn     = NULL,
00401                      mean_data              = 0.0,
00402                      mean_noise             = 0.0,
00403                      *stored_gapmean        = NULL,
00404                      *stored_gapsdv         = NULL,
00405                      *stored_gapmaxdev      = NULL,
00406                      *stored_slitmean       = NULL,
00407                      *stored_slitsdv        = NULL,
00408                      *stored_slitmaxdev     = NULL;
00409 
00410     const char       *cmethod               = NULL;
00411 
00412     char             *extname               = NULL,
00413                      filename_flat[256],
00414                      filename_xcal[256],
00415                      filename_ycal[256],
00416                      filename_bad[256],
00417                      filename_edge[256],
00418                      *suffix                = NULL,
00419                      *fn_suffix             = NULL,
00420                      *tmpstr                = NULL,
00421                      *readmode              = NULL;
00422 
00423     cpl_propertylist *main_header           = NULL,
00424                      *main_header_xcal      = NULL,
00425                      *sub_header            = NULL;
00426 
00427     cpl_table       ***edge_table           = NULL;
00428 
00429     main_fits_desc   desc1, desc2;
00430 
00431     cpl_array        **unused_ifus_before   = NULL,
00432                      **unused_ifus_after    = NULL;
00433 
00434     cpl_error_code  *spec_found             = NULL;
00435 
00436     char            *fn_flat   = "flat_tmp.fits",
00437                     *fn_noise  = "flat_noise_tmp.fits",
00438                     *fn_badpix = "badpix_tmp.fits";
00439     unsigned int    save_mode = CPL_IO_CREATE; // at first files must be created
00440 
00441     KMO_TRY
00442     {
00443 
00444         kmo_init_fits_desc(&desc1);
00445         kmo_init_fits_desc(&desc2);
00446 
00447         KMO_TRY_ASSURE((parlist != NULL) && (frameset != NULL),
00448                 CPL_ERROR_NULL_INPUT, "Not all input data is provided!");
00449 
00450         KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, FLAT_OFF) >= 1,
00451                 CPL_ERROR_NULL_INPUT,
00452                 "At least 1 FLAT_OFF frame is needed (3 or more recommended)");
00453 
00454         KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, FLAT_ON) >= 1,
00455                 CPL_ERROR_NULL_INPUT,
00456                 "At least 1 FLAT_ON frame is needed (3 or more recommended)");
00457 
00458         if (cpl_frameset_count_tags(frameset, FLAT_OFF) < 3) {
00459             cpl_msg_warning(cpl_func, 
00460                     "It is recommended to provide at least 3 FLAT_OFF frames");
00461         }
00462 
00463         if (cpl_frameset_count_tags(frameset, FLAT_ON) < 3) {
00464             cpl_msg_warning(cpl_func, 
00465                     "It is recommended to provide at least 3 FLAT_ON frames") ;
00466         }
00467 
00468         KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, BADPIXEL_DARK) == 1,
00469                 CPL_ERROR_NULL_INPUT, "BADPIXEL_DARK frame must be provided");
00470 
00471         KMO_TRY_ASSURE(kmo_dfs_set_groups(frameset, "kmo_flat") == 1,
00472                 CPL_ERROR_ILLEGAL_INPUT,"Cannot identify RAW and CALIB frames");
00473 
00474         /* ------------ get parameters ------------ */
00475         cpl_msg_info("", "--- Parameter setup for kmo_flat ----------");
00476 
00477         surrounding_pixels = kmo_dfs_get_parameter_int(parlist,
00478                 "kmos.kmo_flat.surrounding_pixels");
00479         KMO_TRY_CHECK_ERROR_STATE();
00480         KMO_TRY_EXIT_IF_ERROR(kmo_dfs_print_parameter_help(parlist,
00481                     "kmos.kmo_flat.surrounding_pixels"));
00482 
00483         badpix_thresh = kmo_dfs_get_parameter_int(parlist,
00484                 "kmos.kmo_flat.badpix_thresh");
00485         KMO_TRY_CHECK_ERROR_STATE();
00486         KMO_TRY_EXIT_IF_ERROR(kmo_dfs_print_parameter_help(parlist,
00487                     "kmos.kmo_flat.badpix_thresh"));
00488 
00489         suppress_extension = kmo_dfs_get_parameter_bool(parlist,
00490                 "kmos.kmo_flat.suppress_extension");
00491         KMO_TRY_CHECK_ERROR_STATE();
00492         KMO_TRY_EXIT_IF_ERROR(kmo_dfs_print_parameter_help(parlist,
00493                     "kmos.kmo_flat.suppress_extension"));
00494 
00495         KMO_TRY_ASSURE((suppress_extension == TRUE) || 
00496                 (suppress_extension == FALSE),
00497                 CPL_ERROR_ILLEGAL_INPUT,
00498                 "suppress_extension must be TRUE or FALSE!");
00499 
00500         KMO_TRY_EXIT_IF_ERROR(
00501             kmo_combine_pars_load(parlist, "kmos.kmo_flat", &cmethod, &cpos_rej,
00502                 &cneg_rej, &citer, &cmin, &cmax, FALSE));
00503 
00504         cpl_msg_info("", "-------------------------------------------");
00505 
00506         /* check BADPIXEL_DARK */
00507         KMO_TRY_EXIT_IF_NULL(frame = kmo_dfs_get_frame(frameset,BADPIXEL_DARK));
00508         desc2 = kmo_identify_fits_header(cpl_frame_get_filename(frame));
00509         KMO_TRY_CHECK_ERROR_STATE();
00510 
00511         KMO_TRY_ASSURE((desc2.nr_ext == 3) && (desc2.ex_badpix == TRUE) &&
00512                 (desc2.fits_type == b2d_fits) &&
00513                 (desc2.frame_type == detector_frame), CPL_ERROR_ILLEGAL_INPUT,
00514                 "BADPIXEL_DARK isn't in the correct format");
00515 
00516         nx = desc2.naxis1;
00517         ny = desc2.naxis2;
00518         nz = desc2.naxis3;
00519 
00520         KMO_TRY_ASSURE((surrounding_pixels >= 0) && (surrounding_pixels <= 8),
00521                 CPL_ERROR_ILLEGAL_INPUT,
00522                 "surrounding_pixels must be between 0 and 8!");
00523 
00524         KMO_TRY_ASSURE((badpix_thresh >= 0) && (badpix_thresh <= 100),
00525                 CPL_ERROR_ILLEGAL_INPUT, 
00526                 "badpix_thresh must be between 0 and 100%%!");
00527 
00528         // ------------ check EXPTIME, NDIT, READMODE and lamps ------------
00529         //    EXPTIME, NDIT, READMODE: the same for all frames
00530         //    lamps:   at least 3 lamp on and 3 lamp off frames
00531         KMO_TRY_EXIT_IF_NULL(frame = kmo_dfs_get_frame(frameset, FLAT_OFF));
00532         
00533         KMO_TRY_EXIT_IF_NULL(main_header = kmclipm_propertylist_load(
00534                     cpl_frame_get_filename(frame), 0));
00535         ndit = cpl_propertylist_get_int(main_header, NDIT);
00536         KMO_TRY_CHECK_ERROR_STATE("NDIT keyword in main header missing");
00537         exptime = cpl_propertylist_get_double(main_header, EXPTIME);
00538         KMO_TRY_CHECK_ERROR_STATE("EXPTIME keyword in main header missing");
00539         readmode =cpl_strdup(cpl_propertylist_get_string(main_header,READMODE));
00540         KMO_TRY_CHECK_ERROR_STATE("ESO DET READ CURNAME keyword is missing");
00541         cpl_propertylist_delete(main_header); main_header = NULL;
00542 
00543         /* Loop through FLAT_OFF frames */
00544         while (frame != NULL) {
00545             KMO_TRY_EXIT_IF_NULL(
00546                 main_header = kmclipm_propertylist_load(
00547                     cpl_frame_get_filename(frame), 0));
00548 
00549             KMO_TRY_ASSURE(cpl_propertylist_get_int(main_header, NDIT) == ndit,
00550                     CPL_ERROR_ILLEGAL_INPUT,
00551                     "NDIT isn't the same for all frames: (is %d and %d).",
00552                     cpl_propertylist_get_int(main_header, NDIT), ndit);
00553 
00554             KMO_TRY_ASSURE(
00555                     cpl_propertylist_get_double(main_header, EXPTIME)==exptime,
00556                     CPL_ERROR_ILLEGAL_INPUT,
00557                     "EXPTIME isn't the same for all frames: (is %g and %g).",
00558                     cpl_propertylist_get_double(main_header, EXPTIME), exptime);
00559 
00560             KMO_TRY_ASSURE(strcmp(cpl_propertylist_get_string(main_header, 
00561                             READMODE), readmode) == 0,
00562                     CPL_ERROR_ILLEGAL_INPUT,
00563                     "ESO DET READ CURNAME isn't constant: (is %s and %s).",
00564                     cpl_propertylist_get_string(main_header, READMODE), 
00565                     readmode);
00566 
00567             desc1 = kmo_identify_fits_header(cpl_frame_get_filename(frame));
00568             KMO_TRY_CHECK_ERROR_STATE_MSG(
00569                     cpl_sprintf("File (%s) doesn't seem to be in KMOS-format",
00570                         cpl_frame_get_filename(frame)));
00571 
00572             KMO_TRY_ASSURE(desc1.fits_type == raw_fits,
00573                     CPL_ERROR_ILLEGAL_INPUT,
00574                     "File hasn't correct data type (%s must be a raw)",
00575                     cpl_frame_get_filename(frame));
00576 
00577             KMO_TRY_ASSURE((desc1.naxis1 == nx) && (desc1.naxis2 == ny) &&
00578                     (desc1.naxis3 == nz), CPL_ERROR_ILLEGAL_INPUT,
00579                     "File (%s) has wrong dimensions! (x,y): (%d,%d) vs (%d,%d)",
00580                     cpl_frame_get_filename(frame), desc1.naxis1, desc1.naxis2, 
00581                     nx, ny);
00582 
00583             /* Assure that arc lamps are off */
00584             KMO_TRY_ASSURE((kmo_check_lamp(main_header, INS_LAMP1_ST) == FALSE)
00585                     && (kmo_check_lamp(main_header, INS_LAMP2_ST) == FALSE),
00586                     CPL_ERROR_ILLEGAL_INPUT,
00587                     "Arc lamps must be switched off (%s)!",
00588                     cpl_frame_get_filename(frame));
00589 
00590             /* Check if flat lamps are off */
00591             if ((kmo_check_lamp(main_header, INS_LAMP3_ST) == TRUE) ||
00592                 (kmo_check_lamp(main_header, INS_LAMP4_ST) == TRUE)) {
00593                 if (!(strcmp(cpl_propertylist_get_string(main_header, 
00594                                     "ESO INS FILT1 ID"), "Block") == 0) ||
00595                     !(strcmp(cpl_propertylist_get_string(main_header, 
00596                                 "ESO INS FILT2 ID"), "Block") == 0) ||
00597                     !(strcmp(cpl_propertylist_get_string(main_header, 
00598                                 "ESO INS FILT3 ID"), "Block") == 0)) {
00599                     cpl_msg_warning(__func__, "At least one flat lamp is on");
00600                 }
00601             }
00602             kmo_free_fits_desc(&desc1);
00603 
00604             /* Get next FLAT_OFF frame */
00605             frame = kmo_dfs_get_frame(frameset, NULL);
00606             KMO_TRY_CHECK_ERROR_STATE();
00607 
00608             cpl_propertylist_delete(main_header); main_header = NULL;
00609         }
00610 
00611         /* Loop through FLAT_ON frames */
00612         KMO_TRY_EXIT_IF_NULL(frame = kmo_dfs_get_frame(frameset, FLAT_ON));
00613         while (frame != NULL) {
00614             KMO_TRY_EXIT_IF_NULL(main_header = kmclipm_propertylist_load(
00615                         cpl_frame_get_filename(frame), 0));
00616 
00617             KMO_TRY_ASSURE(cpl_propertylist_get_int(main_header, NDIT) == ndit,
00618                     CPL_ERROR_ILLEGAL_INPUT,
00619                     "NDIT isn't the same for all frames: (is %d and %d).",
00620                     cpl_propertylist_get_int(main_header, NDIT), ndit);
00621 
00622             KMO_TRY_ASSURE(
00623                     cpl_propertylist_get_double(main_header, EXPTIME)==exptime,
00624                     CPL_ERROR_ILLEGAL_INPUT,
00625                     "EXPTIME isn't the same for all frames: (is %g and %g).",
00626                     cpl_propertylist_get_double(main_header, EXPTIME), exptime);
00627 
00628             KMO_TRY_ASSURE(strcmp(cpl_propertylist_get_string(main_header, 
00629                             READMODE), readmode) == 0,
00630                     CPL_ERROR_ILLEGAL_INPUT,
00631                     "ESO DET READ CURNAME isn't constant: (is %s and %s).",
00632                     cpl_propertylist_get_string(main_header,READMODE),readmode);
00633 
00634             desc1 = kmo_identify_fits_header(cpl_frame_get_filename(frame));
00635             KMO_TRY_CHECK_ERROR_STATE_MSG(
00636                     cpl_sprintf("File (%s) doesn't seem to be in KMOS-format",
00637                         cpl_frame_get_filename(frame)));
00638 
00639             KMO_TRY_ASSURE(desc1.fits_type == raw_fits, CPL_ERROR_ILLEGAL_INPUT,
00640                     "File hasn't correct data type (%s must be a raw)",
00641                     cpl_frame_get_filename(frame));
00642 
00643             KMO_TRY_ASSURE((desc1.naxis1 == nx) && (desc1.naxis2 == ny) &&
00644                     (desc1.naxis3 == nz), CPL_ERROR_ILLEGAL_INPUT,
00645                     "File (%s) has wrong dimensions (x,y): (%d,%d) vs (%d,%d)",
00646                     cpl_frame_get_filename(frame),
00647                     desc1.naxis1, desc1.naxis2, nx, ny);
00648 
00649             /* Assure that arc lamps are off */
00650             KMO_TRY_ASSURE((kmo_check_lamp(main_header, INS_LAMP1_ST) == FALSE)
00651                     && (kmo_check_lamp(main_header, INS_LAMP2_ST) == FALSE),
00652                     CPL_ERROR_ILLEGAL_INPUT,
00653                     "Arc lamps must be switched off (%s)",
00654                     cpl_frame_get_filename(frame));
00655 
00656             /* Assure that at least one flat lamp is on */
00657             KMO_TRY_ASSURE((kmo_check_lamp(main_header, INS_LAMP3_ST) == TRUE)
00658                     || (kmo_check_lamp(main_header, INS_LAMP4_ST) == TRUE),
00659                     CPL_ERROR_ILLEGAL_INPUT,
00660                     "At least one flat lamps must be switched on (%s)",
00661                     cpl_frame_get_filename(frame));
00662             kmo_free_fits_desc(&desc1);
00663 
00664             /* Get next FLAT_ON frame */
00665             frame = kmo_dfs_get_frame(frameset, NULL);
00666             KMO_TRY_CHECK_ERROR_STATE();
00667 
00668             cpl_propertylist_delete(main_header); main_header = NULL;
00669         }
00670 
00671         /* -------- check filter_id, grating_id and rotator offset -------- */
00672         /* must match for all detectors */
00673         KMO_TRY_EXIT_IF_ERROR(kmo_check_frameset_setup(frameset, FLAT_ON,
00674                     TRUE, FALSE, FALSE));
00675         strcpy(filename_flat, MASTER_FLAT);
00676         strcpy(filename_bad, BADPIXEL_FLAT);
00677         strcpy(filename_xcal, XCAL);
00678         strcpy(filename_ycal, YCAL);
00679         strcpy(filename_edge, FLAT_EDGE);
00680 
00681         KMO_TRY_EXIT_IF_NULL(frame = kmo_dfs_get_frame(frameset, FLAT_ON));
00682         KMO_TRY_EXIT_IF_NULL(suffix = kmo_dfs_get_suffix(frame, TRUE, FALSE));
00683 
00684         cpl_msg_info("", "Detected instrument setup:   %s", suffix+1);
00685         cpl_msg_info("", "(grating 1, 2 & 3)");
00686 
00687         /* ---- scan for rotator angles */
00688         #define ANGLE_DIM 360
00689         int rotang_found[ANGLE_DIM];
00690         int rotang_cnt[ANGLE_DIM];
00691         for (i = 0; i < ANGLE_DIM; i++) {
00692             rotang_found[i] = 0;
00693             rotang_cnt[i] = 0;
00694         }
00695         KMO_TRY_EXIT_IF_NULL(frame = kmo_dfs_get_frame(frameset, FLAT_ON));
00696         while (frame != NULL) {
00697             main_header = kmclipm_propertylist_load(
00698                     cpl_frame_get_filename(frame), 0);
00699             if (cpl_propertylist_has(main_header, ROTANGLE)) {
00700                 int rot_angle = (int) rint(cpl_propertylist_get_double(
00701                             main_header, ROTANGLE));
00702                 if (rot_angle < 0)                      rot_angle += 360;
00703                 if (rot_angle < 360 && rot_angle >= 0)  rotang_cnt[rot_angle]++;
00704             } else {
00705                 cpl_msg_warning(__func__, "File %s has no keyword \"ROTANGLE\"",
00706                         cpl_frame_get_filename(frame));
00707             }
00708 
00709             cpl_propertylist_delete(main_header); main_header = NULL;
00710             frame = kmo_dfs_get_frame(frameset, NULL);
00711             KMO_TRY_CHECK_ERROR_STATE();
00712         }
00713         nr_angles = 0;
00714         for (ax = 0; ax < ANGLE_DIM; ax++) {
00715             if (rotang_cnt[ax] != 0) {
00716                 cpl_msg_info(__func__, "Found %d frames with rotator angle %d",
00717                         rotang_cnt[ax],ax);
00718                 rotang_found[nr_angles] = ax;
00719                 nr_angles++;
00720             }
00721         }
00722 
00723         /* Create a Frameset per different Angle */
00724         KMO_TRY_EXIT_IF_NULL(angle_frameset = 
00725                 (cpl_frameset **)cpl_malloc(nr_angles * sizeof(cpl_frameset*)));
00726         for (i = 0; i < nr_angles; i++) {
00727             angle_frameset[i] = cpl_frameset_new();
00728         }
00729         KMO_TRY_EXIT_IF_NULL(frame = kmo_dfs_get_frame(frameset, FLAT_ON));
00730         while (frame != NULL) {
00731             main_header = kmclipm_propertylist_load(cpl_frame_get_filename(
00732                         frame), 0);
00733             if (cpl_propertylist_has(main_header, ROTANGLE)) {
00734                 int rot_angle = (int)rint(cpl_propertylist_get_double(
00735                             main_header, ROTANGLE));
00736                 if (rot_angle < 0)  rot_angle += 360;
00737                 int ix = -1;
00738                 for (ix = 0; ix<nr_angles; ix++) {
00739                     if (rotang_found[ix] == rot_angle) {
00740                         break;
00741                     }
00742                 }
00743                 if (ix<nr_angles) {
00744                     KMO_TRY_EXIT_IF_ERROR(cpl_frameset_insert(
00745                                 angle_frameset[ix],cpl_frame_duplicate(frame)));
00746                 }
00747             }
00748 
00749             cpl_propertylist_delete(main_header); main_header = NULL;
00750             frame = kmo_dfs_get_frame(frameset, NULL);
00751             KMO_TRY_CHECK_ERROR_STATE();
00752         }
00753 
00754 
00755         /* -------- Allocate temporary memory */
00756         /* Load descriptor and header of first operand */
00757         KMO_TRY_EXIT_IF_NULL(frame = kmo_dfs_get_frame(frameset, FLAT_ON));
00758         desc1 = kmo_identify_fits_header(cpl_frame_get_filename(frame));
00759         KMO_TRY_CHECK_ERROR_STATE();
00760 
00761         nr_devices = desc1.nr_ext;
00762 
00763         /* The frames have to be stored temporarily because the QC parameters */
00764         /* for the main header are calculated from each detector.  */
00765         /* So they can be stored only when all detectors are processed */
00766         KMO_TRY_EXIT_IF_NULL(stored_flat = (cpl_image**)cpl_calloc(
00767                     nr_devices * nr_angles, sizeof(cpl_image*)));
00768         KMO_TRY_EXIT_IF_NULL(stored_noise = (cpl_image**)cpl_calloc(
00769                     nr_devices * nr_angles, sizeof(cpl_image*)));
00770         KMO_TRY_EXIT_IF_NULL(stored_badpix = (cpl_image**)cpl_calloc(
00771                     nr_devices * nr_angles, sizeof(cpl_image*)));
00772         KMO_TRY_EXIT_IF_NULL(stored_xcal = (cpl_image**)cpl_calloc(
00773                     nr_devices * nr_angles, sizeof(cpl_image*)));
00774         KMO_TRY_EXIT_IF_NULL(stored_ycal = (cpl_image**)cpl_calloc(
00775                     nr_devices * nr_angles, sizeof(cpl_image*)));
00776         KMO_TRY_EXIT_IF_NULL(stored_qc_flat_sat = (int*)cpl_malloc(
00777                     nr_devices * nr_angles * sizeof(int)));
00778         KMO_TRY_EXIT_IF_NULL(stored_qc_flat_eff = (double*)cpl_malloc(
00779                     nr_devices * nr_angles * sizeof(double)));
00780         KMO_TRY_EXIT_IF_NULL(stored_qc_flat_sn = (double*)cpl_malloc(
00781                     nr_devices * nr_angles * sizeof(double)));
00782         KMO_TRY_EXIT_IF_NULL(stored_gapmean = (double*)cpl_malloc(
00783                     nr_devices * nr_angles * sizeof(double)));
00784         KMO_TRY_EXIT_IF_NULL(stored_gapsdv = (double*)cpl_malloc(
00785                     nr_devices * nr_angles * sizeof(double)));
00786         KMO_TRY_EXIT_IF_NULL(stored_gapmaxdev = (double*)cpl_malloc(
00787                     nr_devices * nr_angles * sizeof(double)));
00788         KMO_TRY_EXIT_IF_NULL(stored_slitmean = (double*)cpl_malloc(
00789                     nr_devices * nr_angles * sizeof(double)));
00790         KMO_TRY_EXIT_IF_NULL(stored_slitsdv = (double*)cpl_malloc(
00791                     nr_devices * nr_angles * sizeof(double)));
00792         KMO_TRY_EXIT_IF_NULL(stored_slitmaxdev = (double*)cpl_malloc(
00793                     nr_devices * nr_angles * sizeof(double)));
00794         KMO_TRY_EXIT_IF_NULL(spec_found = (cpl_error_code*)cpl_malloc(
00795                     nr_devices * nr_angles * sizeof(cpl_error_code)));
00796         KMO_TRY_EXIT_IF_NULL(edge_table = (cpl_table***)cpl_malloc(
00797                     KMOS_NR_DETECTORS * nr_angles * sizeof(cpl_table**)));
00798 
00799         // initialize to NULL
00800         for (i = 0; i < nr_devices * nr_angles ; i++) {
00801             stored_qc_flat_sat[i] = 0.0;
00802             stored_qc_flat_eff[i] = 0.0;
00803             stored_qc_flat_sn[i] = 0.0;
00804             stored_gapmean[i] = 0.0;
00805             stored_gapsdv[i] = 0.0;
00806             stored_gapmaxdev[i] = 0.0;
00807             stored_slitmean[i] = 0.0;
00808             stored_slitsdv[i] = 0.0;
00809             stored_slitmaxdev[i] = 0.0;
00810             spec_found[i] = CPL_ERROR_NONE;
00811         }
00812 
00813         for (i = 0; i < KMOS_NR_DETECTORS * nr_angles; i++) {
00814             edge_table[i] = NULL;
00815         }
00816 
00817         /* ------------------- Process data */
00818 
00819         /* Check which IFUs are active for all FLAT_ON frames */
00820         KMO_TRY_EXIT_IF_NULL(unused_ifus_before = 
00821                 kmo_get_unused_ifus(frameset, 0, 0));
00822         KMO_TRY_EXIT_IF_NULL(unused_ifus_after = 
00823                 kmo_duplicate_unused_ifus(unused_ifus_before));
00824         kmo_print_unused_ifus(unused_ifus_before, FALSE);
00825 
00826         cpl_msg_info(__func__, "EXPTIME:  %g seconds", exptime);
00827         cpl_msg_info(__func__, "NDIT: %d", ndit);
00828         cpl_msg_info(__func__, "Detector readout mode: %s", readmode);
00829         cpl_msg_info(__func__, "-------------------------------------------");
00830 
00831         /* ------------ loop all rotator angles and detectors-------- */
00832         for (a = 0; a < nr_angles; a++) {
00833             cpl_msg_info(__func__, "Processing rotator angle %d -> %d degree", 
00834                     a, rotang_found[a]);
00835             for (i = 1; i <= nr_devices; i++) {
00836                 cpl_msg_info(__func__, "Processing detector No. %d", i);
00837                 sx = a * nr_devices + (i - 1);
00838                 KMO_TRY_EXIT_IF_NULL(bad_pix_mask_dark = 
00839                         kmo_dfs_load_image(frameset, BADPIXEL_DARK, i, 2, 
00840                             FALSE, NULL));
00841 
00842                 /* -------- load all lamp_on and lamp_off images */
00843                 KMO_TRY_EXIT_IF_NULL(det_lamp_on = cpl_imagelist_new());
00844                 KMO_TRY_EXIT_IF_NULL(det_lamp_off = cpl_imagelist_new());
00845 
00846                 /* Load lamp-on images for Angle a */
00847                 KMO_TRY_EXIT_IF_NULL(frame = kmo_dfs_get_frame(
00848                             angle_frameset[a], FLAT_ON));
00849                 j = 0;
00850                 while (frame != NULL) {
00851                     KMO_TRY_EXIT_IF_NULL(img_in = kmo_dfs_load_image_frame(
00852                                 frame, i, FALSE, TRUE, &nr_sat));
00853                     KMO_TRY_EXIT_IF_ERROR(kmo_image_reject_from_mask(
00854                                 img_in, bad_pix_mask_dark));
00855 
00856                     cpl_imagelist_set(det_lamp_on, img_in, j++);
00857                     KMO_TRY_CHECK_ERROR_STATE();
00858 
00859                     frame = kmo_dfs_get_frame(angle_frameset[a], NULL);
00860                     KMO_TRY_CHECK_ERROR_STATE();
00861                 }
00862 
00863                 /* Load lamp-off images */
00864                 KMO_TRY_EXIT_IF_NULL(frame = kmo_dfs_get_frame(frameset, 
00865                             FLAT_OFF));
00866                 j = 0;
00867                 while (frame != NULL) {
00868                     KMO_TRY_EXIT_IF_NULL(img_in = kmo_dfs_load_image_frame(
00869                                 frame, i, FALSE, FALSE, NULL));
00870 
00871                     KMO_TRY_EXIT_IF_ERROR(kmo_image_reject_from_mask(
00872                                 img_in, bad_pix_mask_dark));
00873 
00874                     cpl_imagelist_set(det_lamp_off, img_in, j++);
00875                     KMO_TRY_CHECK_ERROR_STATE();
00876 
00877                     /* Get next frame */
00878                     frame = kmo_dfs_get_frame(frameset, NULL);
00879                     KMO_TRY_CHECK_ERROR_STATE();
00880                 }
00881 
00882                 /* ------------ Process imagelist */
00883 
00884                 /* Count saturated pixels for each detector */
00885                 KMO_TRY_EXIT_IF_NULL(frame = kmo_dfs_get_frame(
00886                             angle_frameset[a], FLAT_ON));
00887                 KMO_TRY_EXIT_IF_NULL(main_header = kmclipm_propertylist_load(
00888                             cpl_frame_get_filename(frame), 0));
00889                 if (strcmp(cpl_propertylist_get_string(main_header, READMODE), 
00890                             "Nondest") == 0) {
00891                     // NDR: non-destructive readout mode
00892                     stored_qc_flat_sat[sx] = nr_sat;
00893                 } else {
00894                     // normal readout mode
00895                     stored_qc_flat_sat[sx] = kmo_imagelist_get_saturated(
00896                             det_lamp_on, KMO_FLAT_SATURATED, KMO_FLAT_SAT_MIN);
00897                 }
00898                 cpl_propertylist_delete(main_header); main_header = NULL;
00899                 KMO_TRY_CHECK_ERROR_STATE();
00900 
00901                 /* Combine imagelists and create noise */
00902                 KMO_TRY_EXIT_IF_ERROR(
00903                     kmclipm_combine_frames(det_lamp_on, NULL, NULL, cmethod, 
00904                         cpos_rej, cneg_rej, citer, cmax, cmin, 
00905                         &combined_data_on, &combined_noise_on, -1.0));
00906 
00907                 KMO_TRY_EXIT_IF_ERROR(
00908                     kmclipm_combine_frames(det_lamp_off, NULL, NULL, cmethod, 
00909                         cpos_rej, cneg_rej, citer, cmax, cmin,
00910                         &combined_data_off, &combined_noise_off, -1.0));
00911 
00912                 if (kmclipm_omit_warning_one_slice > 10) {
00913                     kmclipm_omit_warning_one_slice = FALSE;
00914                 }
00915 
00916                 /* Subtract combined lamp_off from lamp_on */
00917                 // (for noise: sig_x = sqrt(sig_u^2 + sig_v^2)
00918                 KMO_TRY_EXIT_IF_ERROR(
00919                     cpl_image_subtract(combined_data_on, combined_data_off));
00920 
00921                 KMO_TRY_EXIT_IF_ERROR(cpl_image_power(combined_noise_on, 2.0));
00922                 KMO_TRY_EXIT_IF_ERROR(cpl_image_power(combined_noise_off, 2.0));
00923                 KMO_TRY_EXIT_IF_ERROR(cpl_image_add(combined_noise_on, 
00924                             combined_noise_off));
00925                 KMO_TRY_EXIT_IF_ERROR(cpl_image_power(combined_noise_on, 0.5));
00926 
00927                 /* Create bad-pixel-mask */
00928                 KMO_TRY_EXIT_IF_NULL(bad_pix_mask_flat = 
00929                         kmo_create_bad_pix_flat_thresh(combined_data_on,
00930                             surrounding_pixels, badpix_thresh));
00931 
00932                 /* Calculate spectral curvature here */
00933                 spec_found[sx] = kmo_calc_curvature(
00934                         combined_data_on,
00935                         combined_noise_on,
00936                         unused_ifus_after[i-1],
00937                         bad_pix_mask_flat,
00938                         i,
00939                         &xcal,
00940                         &ycal,
00941                         stored_gapmean+(sx),
00942                         stored_gapsdv+(sx),
00943                         stored_gapmaxdev+(sx),
00944                         stored_slitmean+(sx),
00945                         stored_slitsdv+(sx),
00946                         stored_slitmaxdev+(sx),
00947                         &edge_table[sx]);
00948 
00949                 if (spec_found[sx] == CPL_ERROR_NONE) {
00950                     // in kmo_calc_curvature() the spectral slope of each 
00951                     // slitlet has been normalised individually. Now the 
00952                     // normalisation on the whole frame is applied. 
00953                     // (cpl_image_get_mean() ignores bad pixels when 
00954                     // calculating the mean)
00955                     mean_data = cpl_image_get_mean(combined_data_on);
00956                     KMO_TRY_CHECK_ERROR_STATE();
00957 
00958                     stored_qc_flat_eff[sx] = mean_data / exptime;
00959 
00960                     mean_noise = cpl_image_get_mean(combined_noise_on);
00961                     KMO_TRY_CHECK_ERROR_STATE();
00962 
00963                     if ((cpl_frameset_count_tags(frameset, FLAT_OFF) > 1) ||
00964                         (cpl_frameset_count_tags(frameset, FLAT_ON) > 1)) {
00965                         KMO_TRY_ASSURE(mean_noise != 0.0,
00966                                 CPL_ERROR_ILLEGAL_INPUT,
00967                                 "All frames of detector %i are the same", i);
00968                         stored_qc_flat_sn[sx] = mean_data / mean_noise;
00969                     }
00970 
00971                     /* Normalize data & noise on the whole detector frame */
00972                     /* The spectral slope on each slitlet has already been  */
00973                     /* normalised in kmo_calc_curvature() */
00974                     KMO_TRY_EXIT_IF_ERROR(
00975                         cpl_image_divide_scalar(combined_data_on, mean_data));
00976 
00977                     KMO_TRY_EXIT_IF_ERROR(
00978                         cpl_image_divide_scalar(combined_noise_on, mean_data));
00979 
00980                     /* Apply the badpixel mask to the produced frames */
00981                     KMO_TRY_EXIT_IF_ERROR(cpl_image_multiply(combined_data_on, 
00982                                 bad_pix_mask_flat));
00983 
00984                     KMO_TRY_EXIT_IF_ERROR(cpl_image_multiply(combined_noise_on,
00985                                 bad_pix_mask_flat));
00986 
00987                     KMO_TRY_EXIT_IF_ERROR(cpl_image_multiply(xcal, 
00988                                 bad_pix_mask_flat));
00989 
00990                     KMO_TRY_EXIT_IF_ERROR(cpl_image_multiply(ycal, 
00991                                 bad_pix_mask_flat));
00992 
00993                     /* Store temporarily flat, badpixel and calibration */
00994                     stored_flat[sx] = combined_data_on;
00995                     stored_noise[sx] = combined_noise_on;
00996                     stored_badpix[sx] = bad_pix_mask_flat;
00997                     stored_xcal[sx] = xcal;
00998                     stored_ycal[sx] = ycal;
00999                 } else if (spec_found[sx] == CPL_ERROR_DATA_NOT_FOUND) {
01000                     // all IFUs seem to be deativated, continue processing
01001                     // just save empty frames
01002                     cpl_error_reset();
01003 
01004                     cpl_image_delete(combined_data_on); 
01005                     combined_data_on = NULL;
01006                     cpl_image_delete(combined_noise_on); 
01007                     combined_noise_on = NULL;
01008                     cpl_image_delete(bad_pix_mask_flat); 
01009                     bad_pix_mask_flat = NULL;
01010 
01011                     KMO_TRY_EXIT_IF_NULL(
01012                         stored_flat[sx]=cpl_image_new(nx, ny, CPL_TYPE_FLOAT));
01013                     for(ix = 1; ix <= nx; ix++) {
01014                         for(iy = 1; iy <= ny; iy++) {
01015                             cpl_image_reject(stored_flat[sx], ix, iy);
01016                         }
01017                     }
01018                     KMO_TRY_CHECK_ERROR_STATE();
01019 
01020                     KMO_TRY_EXIT_IF_NULL(
01021                         stored_noise[sx]=cpl_image_duplicate(stored_flat[sx]));
01022                     KMO_TRY_EXIT_IF_NULL(
01023                         stored_xcal[sx]=cpl_image_duplicate(stored_flat[sx]));
01024                     KMO_TRY_EXIT_IF_NULL(
01025                         stored_ycal[sx]=cpl_image_duplicate(stored_flat[sx]));
01026                     KMO_TRY_EXIT_IF_NULL(
01027                         stored_badpix[sx]=cpl_image_new(nx, ny,CPL_TYPE_FLOAT));
01028                 } else {
01029                     // another error occured
01030                     KMO_TRY_CHECK_ERROR_STATE();
01031                 }
01032 
01033                 // store immediate results, free memory
01034                 KMO_TRY_EXIT_IF_ERROR(kmclipm_image_save(stored_flat[sx],
01035                             fn_flat, CPL_TYPE_FLOAT, NULL, save_mode, 0./0.));
01036                 KMO_TRY_EXIT_IF_ERROR(kmclipm_image_save(stored_noise[sx],
01037                             fn_noise, CPL_TYPE_FLOAT, NULL, save_mode, 0./0.));
01038                 KMO_TRY_EXIT_IF_ERROR(kmclipm_image_save(stored_badpix[sx],
01039                             fn_badpix, CPL_TYPE_FLOAT, NULL, save_mode, 0./0.));
01040 
01041                 /* All other saves will create extensions */
01042                 save_mode = CPL_IO_EXTEND;
01043                 cpl_image_delete(stored_flat[sx]); stored_flat[sx] = NULL;
01044                 cpl_image_delete(stored_noise[sx]); stored_noise[sx] = NULL;
01045                 cpl_image_delete(stored_badpix[sx]); stored_badpix[sx] = NULL;
01046 
01047                 // free memory
01048                 cpl_imagelist_delete(det_lamp_on); det_lamp_on = NULL;
01049                 cpl_imagelist_delete(det_lamp_off); det_lamp_off = NULL;
01050                 cpl_image_delete(combined_data_off); combined_data_off = NULL;
01051                 cpl_image_delete(combined_noise_off); combined_noise_off = NULL;
01052                 cpl_image_delete(bad_pix_mask_dark); bad_pix_mask_dark = NULL;
01053             } // for i = 1; i <= nr_devices
01054         } // for a = 0; a < nr_angles
01055         KMO_TRY_CHECK_ERROR_STATE();
01056 
01057         /* ----- QC parameters & saving */
01058         /* ---- load, update & save primary header */
01059         KMO_TRY_EXIT_IF_NULL(main_header = kmo_dfs_load_primary_header(
01060                     frameset, FLAT_ON));
01061 
01062         /* Update which IFUs are not used */
01063         kmo_print_unused_ifus(unused_ifus_after, TRUE);
01064 
01065         KMO_TRY_EXIT_IF_ERROR(
01066             kmo_set_unused_ifus(unused_ifus_after, main_header, "kmo_flat"));
01067 
01068         // write main_header for data-, noise-, ycal- and badpix-frame
01069         // xcal gets additionally the boundaries of the IFUs for reconstruction
01070 
01071         // add here boundaries for reconstruction
01072         KMO_TRY_EXIT_IF_NULL(main_header_xcal = cpl_propertylist_new());
01073 
01074         KMO_TRY_EXIT_IF_NULL(
01075             total_bounds = (int**)cpl_malloc(nr_devices*sizeof(int*)));
01076         for (i = 0; i < nr_devices; i++) {
01077             KMO_TRY_EXIT_IF_NULL(
01078                 total_bounds[i] = 
01079                 (int*) cpl_calloc(2*KMOS_IFUS_PER_DETECTOR,sizeof(int)));
01080             for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
01081                 total_bounds[i][2*j] = 2048;
01082                 total_bounds[i][2*j+1] = 0;
01083             }
01084         }
01085         KMO_TRY_CHECK_ERROR_STATE();
01086 
01087         /* Store the min left bound and max right bound for all angles */
01088         for (a = 0; a < nr_angles; a++) {
01089             for (i = 0; i < nr_devices; i++) {
01090                 sx = a * nr_devices + i;
01091                 if (stored_ycal[sx] != NULL) {
01092                     KMO_TRY_EXIT_IF_NULL(
01093                         bounds = kmo_split_frame(stored_ycal[sx]));
01094 
01095                     for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
01096                         if ((total_bounds[i][2*j] == -1)||(bounds[2*j] == -1)) {
01097                             total_bounds[i][2*j] = -1;
01098                         } else {
01099                             if (total_bounds[i][2*j] > bounds[2*j]) {
01100                                 total_bounds[i][2*j] = bounds[2*j];
01101                             }
01102                         }
01103 
01104                         if ((total_bounds[i][2*j+1] == -1) || 
01105                                 (bounds[2*j+1] == -1)) {
01106                             total_bounds[i][2*j+1] = -1;
01107                         } else {
01108                             if (total_bounds[i][2*j+1] < bounds[2*j+1]) {
01109                                 total_bounds[i][2*j+1] = bounds[2*j+1];
01110                             }
01111                         }
01112                     }
01113                 } else {
01114                     // whole detector inactive
01115                     for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
01116                         total_bounds[i][2*j] = -1;
01117                         total_bounds[i][2*j+1] = -1;
01118                     }
01119                 }
01120                 if (bounds != NULL) {
01121                     cpl_free(bounds); bounds = NULL;
01122                 }
01123             } // for (nr_devices)
01124         } // for (nr_angles)
01125 
01126         /* Write the min left bound and max right bound for all angles */
01127         /* into the main header */
01128         for (i = 0; i < nr_devices; i++) {
01129             for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
01130                 if (total_bounds[i][2*j] > -1) {
01131                     KMO_TRY_EXIT_IF_NULL(tmpstr= cpl_sprintf("%s%d%s",
01132                         BOUNDS_PREFIX, i*KMOS_IFUS_PER_DETECTOR + j+1, "_L"));
01133                     KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_int(
01134                                 main_header_xcal, tmpstr, total_bounds[i][2*j],
01135                                 "[pix] left boundary for reconstr."));
01136                     cpl_free(tmpstr); tmpstr = NULL;
01137                 }
01138 
01139                 if (total_bounds[i][2*j+1] > -1) {
01140                     KMO_TRY_EXIT_IF_NULL(tmpstr= cpl_sprintf("%s%d%s",
01141                         BOUNDS_PREFIX, i*KMOS_IFUS_PER_DETECTOR + j+1, "_R"));
01142                     KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_int(
01143                                 main_header_xcal,tmpstr, total_bounds[i][2*j+1],
01144                                 "[pix] right boundary for reconstr."));
01145                     cpl_free(tmpstr); tmpstr = NULL;
01146                 }
01147             }
01148         } // for (nr_devices)
01149         KMO_TRY_CHECK_ERROR_STATE();
01150 
01151         /* --------- saving headers  */
01152         if (!suppress_extension) {
01153             KMO_TRY_EXIT_IF_NULL(fn_suffix = cpl_sprintf("%s", suffix));
01154         } else {
01155             KMO_TRY_EXIT_IF_NULL(fn_suffix = cpl_sprintf("%s", ""));
01156         }
01157 
01158         cpl_msg_info(__func__, "Saving data...");
01159 
01160         KMO_TRY_EXIT_IF_NULL(frame = kmo_dfs_get_frame(frameset, FLAT_ON));
01161 
01162         KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_main_header(frameset, filename_flat,
01163                     fn_suffix, frame, main_header, parlist, cpl_func));
01164         KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_main_header(frameset, filename_xcal,
01165                     fn_suffix, frame, main_header_xcal, parlist, cpl_func));
01166         KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_main_header(frameset, filename_ycal,
01167                     fn_suffix, frame, main_header, parlist, cpl_func));
01168         KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_main_header(frameset, filename_bad,
01169                     fn_suffix, frame, main_header, parlist, cpl_func));
01170         KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_main_header(frameset, filename_edge,
01171                     fn_suffix, frame, main_header, parlist, cpl_func));
01172 
01173         cpl_propertylist_delete(main_header); main_header = NULL;
01174         cpl_propertylist_delete(main_header_xcal); main_header_xcal = NULL;
01175 
01176         /* -------- saving sub frames  */
01177         for (a = 0; a < nr_angles; a++) {
01178             for (i = 1; i <= nr_devices; i++) {
01179                 sx = a * nr_devices + (i - 1);
01180 
01181                 // load stored data again
01182                 KMO_TRY_EXIT_IF_NULL(stored_flat[sx] = kmclipm_image_load(
01183                             fn_flat, CPL_TYPE_FLOAT, 0, sx));
01184                 KMO_TRY_EXIT_IF_NULL(stored_noise[sx] = kmclipm_image_load(
01185                             fn_noise, CPL_TYPE_FLOAT, 0, sx));
01186                 KMO_TRY_EXIT_IF_NULL(stored_badpix[sx] = kmclipm_image_load(
01187                             fn_badpix, CPL_TYPE_FLOAT, 0, sx));
01188 
01189                 KMO_TRY_EXIT_IF_NULL(sub_header = kmo_dfs_load_sub_header(
01190                             frameset, FLAT_ON, i, FALSE));
01191                 KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_double(
01192                             sub_header,CAL_ROTANGLE, ((double) rotang_found[a]),
01193                             "[deg] Rotator relative to nasmyth"));
01194 
01195                 if (spec_found[sx] == CPL_ERROR_NONE) {
01196                     KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_int(
01197                                 sub_header, QC_FLAT_SAT, stored_qc_flat_sat[sx],
01198                                 "[] nr. saturated pixels of master flat"));
01199                     /* Load gain */
01200                     gain = kmo_dfs_get_property_double(sub_header, GAIN);
01201                     KMO_TRY_CHECK_ERROR_STATE_MSG(
01202                             "GAIN-keyword in fits-header is missing!");
01203 
01204                     KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_double(
01205                                 sub_header, QC_FLAT_EFF,
01206                                 stored_qc_flat_eff[sx]/gain,
01207                                 "[e-/s] rel. brightness of flat lamp"));
01208                     KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_double(
01209                                 sub_header, QC_FLAT_SN, stored_qc_flat_sn[sx],
01210                                 "[] S/N of master flat"));
01211                 }
01212 
01213                 /* Store qc parameters only if any slitlet- and gap-width  */
01214                 /* has been detected (should be the case when at least */
01215                 /* one IFU is active) */
01216                 if (stored_xcal[sx] != NULL) {
01217                     KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_double(
01218                                 sub_header, QC_GAP_MEAN, stored_gapmean[sx],
01219                                 "[pix] mean gap width between slitlets"));
01220                     KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_double(
01221                                 sub_header, QC_GAP_SDV, stored_gapsdv[sx],
01222                                 "[pix] stdev of gap width between slitlets"));
01223                     KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_double(
01224                                 sub_header, QC_GAP_MAXDEV, stored_gapmaxdev[sx],
01225                                 "[pix] max gap deviation between slitlets"));
01226                     KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_double(
01227                                 sub_header, QC_SLIT_MEAN, stored_slitmean[sx],
01228                                 "[pix] mean slitlet width"));
01229                     KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_double(
01230                                 sub_header, QC_SLIT_SDV, stored_slitsdv[sx],
01231                                 "[pix] stdev of slitlet widths"));
01232                     KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_double(
01233                                 sub_header, QC_SLIT_MAXDEV, 
01234                                 stored_slitmaxdev[sx],
01235                                 "[pix] max slitlet width deviation"));
01236                 }
01237 
01238                 /* Calculate QC.BADPIX.NCOUNT */
01239                 nr_bad_pix = cpl_image_count_rejected(stored_badpix[sx]);
01240                 KMO_TRY_CHECK_ERROR_STATE();
01241 
01242                 /* Remove 4pixel-border as bad pixels */
01243                 nr_bad_pix -= 2*KMOS_BADPIX_BORDER*(nx-2*KMOS_BADPIX_BORDER) +
01244                     2*KMOS_BADPIX_BORDER*ny;
01245 
01246                 KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_int(sub_header,
01247                             QC_NR_BAD_PIX, nr_bad_pix, "[] nr. of bad pixels"));
01248 
01249                 /* Save flat frame */
01250                 KMO_TRY_EXIT_IF_NULL(extname = kmo_extname_creator(
01251                             detector_frame, i, EXT_DATA));
01252                 KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_string(
01253                             sub_header, EXTNAME,extname,"FITS extension name"));
01254                 cpl_free(extname); extname = NULL;
01255 
01256                 KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_int(
01257                             sub_header, EXTVER, sx+1, "FITS extension ver"));
01258 
01259                 KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_image(stored_flat[sx], 
01260                             filename_flat, fn_suffix, sub_header, 0./0.));
01261 
01262                 /* Save noise frame when enough input frames were available */
01263                 KMO_TRY_EXIT_IF_NULL(extname = kmo_extname_creator(
01264                             detector_frame, i, EXT_NOISE));
01265                 KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_string(
01266                             sub_header, EXTNAME,extname,"FITS extension name"));
01267                 cpl_free(extname); extname = NULL;
01268 
01269                 KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_image(stored_noise[sx], 
01270                             filename_flat, fn_suffix, sub_header, 0./0.));
01271 
01272                 /* Save bad_pix frame */
01273                 KMO_TRY_EXIT_IF_NULL(extname = kmo_extname_creator(
01274                             detector_frame, i, EXT_BADPIX));
01275                 KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_string(
01276                             sub_header, EXTNAME,extname,"FITS extension name"));
01277                 cpl_free(extname); extname = NULL;
01278 
01279                 KMO_TRY_EXIT_IF_ERROR(
01280                     kmo_dfs_save_image(stored_badpix[sx], filename_bad,
01281                                        fn_suffix, sub_header, 0.));
01282 
01283                 // save xcal and ycal-frame
01284                 KMO_TRY_EXIT_IF_NULL(
01285                     extname = kmo_extname_creator(detector_frame, i, EXT_DATA));
01286                 KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_string(
01287                             sub_header, EXTNAME,extname,"FITS extension name"));
01288                 cpl_free(extname); extname = NULL;
01289 
01290                 KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_image(stored_xcal[sx],
01291                             filename_xcal, fn_suffix, sub_header, 0./0.));
01292 
01293                 KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_image(stored_ycal[sx], 
01294                             filename_ycal, fn_suffix, sub_header, 0./0.));
01295 
01296                 cpl_free(extname); extname = NULL;
01297 
01298                 /* Save edge_pars-frame */
01299                 for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
01300                     KMO_TRY_EXIT_IF_NULL(
01301                             extname = cpl_sprintf("%s_IFU.%d_ANGLE.%d", 
01302                                 EXT_LIST, j+1+(i-1)*KMOS_IFUS_PER_DETECTOR,
01303                                 rotang_found[a]));
01304                     KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_string(
01305                                 sub_header, EXTNAME, extname,
01306                                 "FITS extension name"));
01307                     cpl_free(extname); extname = NULL;
01308 
01309                     KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_int(
01310                                 sub_header, CAL_IFU_NR,
01311                                 j+1+(i-1)*KMOS_IFUS_PER_DETECTOR,
01312                                 "IFU Number {1..24}"));
01313 
01314                     /* Save edge-parameters as product */
01315                     if ((spec_found[sx] != CPL_ERROR_DATA_NOT_FOUND) && 
01316                             (edge_table[sx][j] != NULL))  {
01317                         KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_table(
01318                                     edge_table[sx][j], filename_edge, 
01319                                     fn_suffix, sub_header));
01320                     } else {
01321                         cpl_propertylist_erase(sub_header, CRVAL1);
01322                         cpl_propertylist_erase(sub_header, CRVAL2);
01323                         cpl_propertylist_erase(sub_header, CD1_1);
01324                         cpl_propertylist_erase(sub_header, CD1_2);
01325                         cpl_propertylist_erase(sub_header, CD2_1);
01326                         cpl_propertylist_erase(sub_header, CD2_2);
01327                         cpl_propertylist_erase(sub_header, CRPIX1);
01328                         cpl_propertylist_erase(sub_header, CRPIX2);
01329                         cpl_propertylist_erase(sub_header, CTYPE1);
01330                         cpl_propertylist_erase(sub_header, CTYPE2);
01331                         KMO_TRY_CHECK_ERROR_STATE();
01332 
01333                         KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_table(NULL, 
01334                                     filename_edge, fn_suffix, sub_header));
01335                     }
01336                 }
01337 
01338                 cpl_propertylist_delete(sub_header); sub_header = NULL;
01339 
01340                 cpl_image_delete(stored_flat[sx]); stored_flat[sx] = NULL;
01341                 cpl_image_delete(stored_noise[sx]); stored_noise[sx] = NULL;
01342                 cpl_image_delete(stored_badpix[sx]); stored_badpix[sx] = NULL;
01343             } // for (i = nr_devices)
01344         } // for (a = nr_angles)
01345     }
01346     KMO_CATCH
01347     {
01348         KMO_CATCH_MSG();
01349         ret_val = -1;
01350     }
01351     // delete temporary files
01352     unlink(fn_flat);
01353     unlink(fn_noise);
01354     unlink(fn_badpix);
01355 
01356     kmo_free_fits_desc(&desc1);
01357     kmo_free_fits_desc(&desc2);
01358     kmo_free_unused_ifus(unused_ifus_before); unused_ifus_before = NULL;
01359     kmo_free_unused_ifus(unused_ifus_after); unused_ifus_after = NULL;
01360     cpl_propertylist_delete(main_header); main_header = NULL;
01361     cpl_propertylist_delete(main_header_xcal); main_header_xcal = NULL;
01362     cpl_propertylist_delete(sub_header); sub_header = NULL;
01363     cpl_imagelist_delete(det_lamp_on); det_lamp_on = NULL;
01364     cpl_imagelist_delete(det_lamp_off); det_lamp_off = NULL;
01365     cpl_image_delete(combined_data_off); combined_data_off = NULL;
01366     cpl_image_delete(combined_noise_off); combined_noise_off = NULL;
01367     cpl_image_delete(bad_pix_mask_dark); bad_pix_mask_dark = NULL;
01368     cpl_free(stored_qc_flat_sat); stored_qc_flat_sat = NULL;
01369     cpl_free(stored_qc_flat_eff); stored_qc_flat_eff = NULL;
01370     cpl_free(stored_qc_flat_sn); stored_qc_flat_sn = NULL;
01371     cpl_free(stored_gapmean); stored_gapmean = NULL;
01372     cpl_free(stored_gapsdv); stored_gapsdv = NULL;
01373     cpl_free(stored_gapmaxdev); stored_gapmaxdev = NULL;
01374     cpl_free(stored_slitmean); stored_slitmean = NULL;
01375     cpl_free(stored_slitsdv); stored_slitsdv = NULL;
01376     cpl_free(stored_slitmaxdev); stored_slitmaxdev = NULL;
01377     cpl_free(readmode); readmode = NULL;
01378     cpl_free(suffix); suffix = NULL;
01379     cpl_free(fn_suffix); fn_suffix = NULL;
01380     if (total_bounds != NULL) {
01381         for (i = 0; i < nr_devices; i++) {
01382             cpl_free(total_bounds[i]); total_bounds[i] = NULL;
01383         }
01384     }
01385     cpl_free(total_bounds); total_bounds = NULL;
01386     for (i = 0; i < nr_devices * nr_angles; i++) {
01387         cpl_image_delete(stored_flat[i]); stored_flat[i] = NULL;
01388         cpl_image_delete(stored_noise[i]); stored_noise[i] = NULL;
01389         cpl_image_delete(stored_badpix[i]); stored_badpix[i] = NULL;
01390         cpl_image_delete(stored_xcal[i]); stored_xcal[i] = NULL;
01391         cpl_image_delete(stored_ycal[i]); stored_ycal[i] = NULL;
01392     }
01393     cpl_free(stored_flat); stored_flat = NULL;
01394     cpl_free(stored_noise); stored_noise = NULL;
01395     cpl_free(stored_badpix); stored_badpix = NULL;
01396     cpl_free(stored_xcal); stored_xcal = NULL;
01397     cpl_free(stored_ycal); stored_ycal = NULL;
01398     for (a = 0; a < nr_angles; a++) {
01399         cpl_frameset_delete(angle_frameset[a]); angle_frameset[a] = NULL;
01400     }
01401     cpl_free(angle_frameset); angle_frameset = NULL;
01402     if (edge_table != NULL) {
01403         for (i = 0; i < KMOS_NR_DETECTORS * nr_angles; i++) {
01404             if (edge_table[i] != NULL) {
01405                 for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
01406                     cpl_table_delete(edge_table[i][j]);
01407                     edge_table[i][j] = NULL;
01408                 }
01409                 cpl_free(edge_table[i]); edge_table[i] = NULL;
01410             }
01411         }
01412         cpl_free(edge_table); edge_table = NULL;
01413     }
01414     if (bounds != NULL) {
01415         cpl_free(bounds); bounds = NULL;
01416     }
01417     if (spec_found != NULL) {
01418         cpl_free(spec_found); spec_found = NULL;
01419     }
01420 
01421     return ret_val;
01422 }
01423