FORS Pipeline Reference Manual 4.9.20
fors_pmos_science.c
00001 /* $Id: fors_pmos_science.c,v 1.63 2013/02/28 15:15:40 cgarcia Exp $
00002  *
00003  * This file is part of the FORS Data Reduction Pipeline
00004  * Copyright (C) 2002-2010 European Southern Observatory
00005  *
00006  * This program is free software; you can redistribute it and/or modify
00007  * it under the terms of the GNU General Public License as published by
00008  * the Free Software Foundation; either version 2 of the License, or
00009  * (at your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU General Public License
00017  * along with this program; if not, write to the Free Software
00018  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00019  */
00020 
00021 /*
00022  * $Author: cgarcia $
00023  * $Date: 2013/02/28 15:15:40 $
00024  * $Revision: 1.63 $
00025  * $Name: fors-4_9_20 $
00026  */
00027 
00028 #ifdef HAVE_CONFIG_H
00029 #include <config.h>
00030 #endif
00031 
00032 #include <math.h>
00033 #include <string.h>
00034 #include <ctype.h>
00035 #include <assert.h>
00036 
00037 #include <cpl.h>
00038 #include <moses.h>
00039 #include <fors_dfs.h>
00040 #include <fors_utils.h>
00041 #include <fors_qc.h>
00042 
00043 static int fors_pmos_science_create(cpl_plugin *);
00044 static int fors_pmos_science_exec(cpl_plugin *);
00045 static int fors_pmos_science_destroy(cpl_plugin *);
00046 static int fors_pmos_science(cpl_parameterlist *, cpl_frameset *);
00047 
00048 static float * fors_check_angles(cpl_frameset *, int, const char *, int *);
00049 static int
00050 fors_find_angle_pos(float * angles, int nangles, float angle);
00051 
00052 static char fors_pmos_science_description[] =
00053 "This recipe is used to reduce scientific spectra using the extraction\n"
00054 "mask and the products created by the recipe fors_mpol_calib. The spectra\n"
00055 "are bias subtracted, flat fielded (if a normalised flat field is specified)\n"
00056 "and remapped eliminating the optical distortions. The wavelength calibration\n"
00057 "can be optionally upgraded using a number of sky lines: if no sky lines\n"
00058 "catalog of wavelengths is specified, an internal one is used instead.\n"
00059 "If the alignment to the sky lines is performed, the input dispersion\n"
00060 "coefficients table is upgraded and saved to disk, and a new CCD wavelengths\n"
00061 "map is created.\n"
00062 "This recipe accepts both FORS1 and FORS2 frames. A grism table (typically\n"
00063 "depending on the instrument mode, and in particular on the grism used)\n"
00064 "may also be specified: this table contains a default recipe parameter\n" 
00065 "setting to control the way spectra are extracted for a specific instrument\n"
00066 "mode, as it is used for automatic run of the pipeline on Paranal and in\n" 
00067 "Garching. If this table is specified, it will modify the default recipe\n" 
00068 "parameter setting, with the exception of those parameters which have been\n" 
00069 "explicitly modifyed on the command line. If a grism table is not specified,\n"
00070 "the input recipe parameters values will always be read from the command\n" 
00071 "line, or from an esorex configuration file if present, or from their\n" 
00072 "generic default values (that are rarely meaningful).\n" 
00073 "Either a scientific or a standard star exposure can be specified in input.\n"
00074 "The acronym SCI on products should be read STD in case of standard stars\n"
00075 "observations.\n\n"
00076 "Input files:\n\n"
00077 "  DO category:               Type:       Explanation:            Required:\n"
00078 "  SCIENCE_PMOS                  Raw         Scientific exposure      Y\n"
00079 "  or STANDARD_PMOS              Raw         Standard star exposure   Y\n"
00080 "  MASTER_BIAS                   Calib       Master bias              Y\n"
00081 "  GRISM_TABLE                   Calib       Grism table              .\n"
00082 "  MASTER_SKYLINECAT             Calib       Sky lines catalog        .\n"
00083 "  MASTER_NORM_FLAT_PMOS         Calib       Normalised flat field    .\n"
00084 "  DISP_COEFF_PMOS               Calib       Inverse dispersion       Y\n"
00085 "  CURV_COEFF_PMOS               Calib       Spectral curvature       Y\n"
00086 "  SLIT_LOCATION_PMOS            Calib       Slits positions table    Y\n"
00087 "  RETARDER_WAVEPLATE_CHROMATISM Calib       Chromatism correction    .\n"
00088 "  STD_PMOS_TABLE                Calib       Linear pol. of std stars .\n"
00089 "\n"
00090 "Output files:\n\n"
00091 "  DO category:               Data type:  Explanation:\n"
00092 "  REDUCED_SCI_PMOS             FITS image  Extracted scientific spectra\n"
00093 "  REDUCED_SKY_SCI_PMOS         FITS image  Extracted sky spectra\n"
00094 "  REDUCED_ERROR_SCI_PMOS       FITS image  Errors on extracted spectra\n"
00095 "  REDUCED_X_SCI_PMOS           FITS image  X Stokes parameter (and L)\n"
00096 "  REDUCED_ERROR_X_SCI_PMOS     FITS image  Error on X Stokes parameter\n"
00097 "  REDUCED_NUL_X_SCI_PMOS       FITS image  Null parameter for X\n"
00098 "  REDUCED_ANGLE_SCI_PMOS       FITS image  Direction of linear polarization\n"
00099 "  REDUCED_ERROR_ANGLE_SCI_PMOS FITS image  Error on polarization direction\n"
00100 "  UNMAPPED_SCI_PMOS            FITS image  Sky subtracted scientific spectra\n"
00101 "  MAPPED_SCI_PMOS              FITS image  Rectified scientific spectra\n"
00102 "  MAPPED_ALL_SCI_PMOS          FITS image  Rectified science spectra with sky\n"
00103 "  MAPPED_SKY_SCI_PMOS          FITS image  Rectified sky spectra\n"
00104 "  UNMAPPED_SKY_SCI_PMOS        FITS image  Sky on CCD\n"
00105 "  OBJECT_TABLE_SCI_PMOS        FITS table  Positions of detected objects\n"
00106 "  OBJECT_TABLE_POL_SCI_PMOS    FITS table  Positions of real objects\n"
00107 "\n"
00108 "  Only if the sky-alignment of the wavelength solution is requested:\n"
00109 "  DISP_COEFF_SCI_PMOS          FITS table  Upgraded dispersion coefficients\n"
00110 "  WAVELENGTH_MAP_SCI_PMOS      FITS image  Upgraded wavelength map\n\n";
00111 
00112 #define fors_pmos_science_exit(message)            \
00113 {                                             \
00114 if (message) cpl_msg_error(recipe, message);  \
00115 cpl_free(instrume);                           \
00116 cpl_image_delete(dummy);                      \
00117 cpl_image_delete(mapped_sky);                 \
00118 cpl_image_delete(mapped_cleaned);             \
00119 cpl_image_delete(skymap);                     \
00120 cpl_image_delete(smapped);                    \
00121 cpl_table_delete(offsets);                    \
00122 cpl_table_delete(sky);                        \
00123 cpl_image_delete(bias);                       \
00124 cpl_image_delete(spectra);                    \
00125 cpl_image_delete(coordinate);                 \
00126 cpl_image_delete(norm_flat);                  \
00127 cpl_image_delete(rainbow);                    \
00128 cpl_image_delete(rectified);                  \
00129 cpl_image_delete(wavemap);                    \
00130 cpl_propertylist_delete(header);              \
00131 cpl_propertylist_delete(save_header);         \
00132 cpl_table_delete(grism_table);                \
00133 cpl_table_delete(idscoeff);                   \
00134 cpl_table_delete(maskslits);                  \
00135 cpl_table_delete(overscans);                  \
00136 cpl_table_delete(polytraces);                 \
00137 cpl_table_delete(wavelengths);                \
00138 cpl_table_delete(mask_science);               \
00139 cpl_table_delete(mask_arc);                   \
00140 cpl_table_delete(mask_flat);                  \
00141 cpl_vector_delete(lines);                     \
00142 cpl_msg_indent_less();                        \
00143 return -1;                                    \
00144 }
00145 
00146 
00147 #define fors_pmos_science_exit_memcheck(message)   \
00148 {                                             \
00149 if (message) cpl_msg_info(recipe, message);   \
00150 cpl_free(instrume);                           \
00151 cpl_image_delete(dummy);                      \
00152 cpl_image_delete(mapped_cleaned);             \
00153 cpl_image_delete(mapped_sky);                 \
00154 cpl_image_delete(skymap);                     \
00155 cpl_image_delete(smapped);                    \
00156 cpl_table_delete(offsets);                    \
00157 cpl_table_delete(sky);                        \
00158 cpl_image_delete(bias);                       \
00159 cpl_image_delete(spectra);                    \
00160 cpl_image_delete(coordinate);                 \
00161 cpl_image_delete(norm_flat);                  \
00162 cpl_image_delete(rainbow);                    \
00163 cpl_image_delete(rectified);                  \
00164 cpl_image_delete(wavemap);                    \
00165 cpl_propertylist_delete(header);              \
00166 cpl_propertylist_delete(save_header);         \
00167 cpl_table_delete(grism_table);                \
00168 cpl_table_delete(idscoeff);                   \
00169 cpl_table_delete(maskslits);                  \
00170 cpl_table_delete(overscans);                  \
00171 cpl_table_delete(polytraces);                 \
00172 cpl_table_delete(wavelengths);                \
00173 cpl_table_delete(mask_science);               \
00174 cpl_table_delete(mask_arc);                   \
00175 cpl_table_delete(mask_flat);                  \
00176 cpl_vector_delete(lines);                     \
00177 cpl_msg_indent_less();                        \
00178 return 0;                                     \
00179 }
00180 
00181 
00193 int cpl_plugin_get_info(cpl_pluginlist *list)
00194 {
00195     cpl_recipe *recipe = cpl_calloc(1, sizeof *recipe );
00196     cpl_plugin *plugin = &recipe->interface;
00197 
00198     cpl_plugin_init(plugin,
00199                     CPL_PLUGIN_API,
00200                     FORS_BINARY_VERSION,
00201                     CPL_PLUGIN_TYPE_RECIPE,
00202                     "fors_pmos_science",
00203                     "Extraction of scientific spectra",
00204                     fors_pmos_science_description,
00205                     "Carlo Izzo",
00206                     PACKAGE_BUGREPORT,
00207     "This file is currently part of the FORS Instrument Pipeline\n"
00208     "Copyright (C) 2002-2010 European Southern Observatory\n\n"
00209     "This program is free software; you can redistribute it and/or modify\n"
00210     "it under the terms of the GNU General Public License as published by\n"
00211     "the Free Software Foundation; either version 2 of the License, or\n"
00212     "(at your option) any later version.\n\n"
00213     "This program is distributed in the hope that it will be useful,\n"
00214     "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
00215     "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
00216     "GNU General Public License for more details.\n\n"
00217     "You should have received a copy of the GNU General Public License\n"
00218     "along with this program; if not, write to the Free Software Foundation,\n"
00219     "Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n",
00220                     fors_pmos_science_create,
00221                     fors_pmos_science_exec,
00222                     fors_pmos_science_destroy);
00223 
00224     cpl_pluginlist_append(list, plugin);
00225     
00226     return 0;
00227 }
00228 
00229 
00240 static int fors_pmos_science_create(cpl_plugin *plugin)
00241 {
00242     cpl_recipe    *recipe;
00243     cpl_parameter *p;
00244 
00245 
00246     /* 
00247      * Check that the plugin is part of a valid recipe 
00248      */
00249 
00250     if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
00251         recipe = (cpl_recipe *)plugin;
00252     else 
00253         return -1;
00254 
00255     /* 
00256      * Create the parameters list in the cpl_recipe object 
00257      */
00258 
00259     recipe->parameters = cpl_parameterlist_new(); 
00260 
00261 
00262     /*
00263      * Dispersion
00264      */
00265 
00266     p = cpl_parameter_new_value("fors.fors_pmos_science.dispersion",
00267                                 CPL_TYPE_DOUBLE,
00268                                 "Expected spectral dispersion (Angstrom/pixel)",
00269                                 "fors.fors_pmos_science",
00270                                 0.0);
00271     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "dispersion");
00272     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00273     cpl_parameterlist_append(recipe->parameters, p);
00274 
00275     /*
00276      * Rebin
00277      */
00278 
00279     p = cpl_parameter_new_value("fors.fors_pmos_science.rebin",
00280                                 CPL_TYPE_INT,
00281                                 "Rebin (pixel)",
00282                                 "fors.fors_pmos_science",
00283                                 1);
00284     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "rebin");
00285     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00286     cpl_parameterlist_append(recipe->parameters, p);
00287 
00288     /*
00289      * Sky lines alignment
00290      */
00291 
00292     p = cpl_parameter_new_value("fors.fors_pmos_science.skyalign",
00293                                 CPL_TYPE_INT,
00294                                 "Polynomial order for sky lines alignment, "
00295                                 "or -1 to avoid alignment",
00296                                 "fors.fors_pmos_science",
00297                                 0);
00298     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "skyalign");
00299     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00300     cpl_parameterlist_append(recipe->parameters, p);
00301 
00302     /*
00303      * Line catalog table column containing the sky reference wavelengths
00304      */
00305 
00306     p = cpl_parameter_new_value("fors.fors_pmos_science.wcolumn",
00307                                 CPL_TYPE_STRING,
00308                                 "Name of sky line catalog table column "
00309                                 "with wavelengths",
00310                                 "fors.fors_pmos_science",
00311                                 "WLEN");
00312     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcolumn");
00313     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00314     cpl_parameterlist_append(recipe->parameters, p);
00315 
00316     /*
00317      * Start wavelength for spectral extraction
00318      */
00319 
00320     p = cpl_parameter_new_value("fors.fors_pmos_science.startwavelength",
00321                                 CPL_TYPE_DOUBLE,
00322                                 "Start wavelength in spectral extraction",
00323                                 "fors.fors_pmos_science",
00324                                 0.0);
00325     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "startwavelength");
00326     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00327     cpl_parameterlist_append(recipe->parameters, p);
00328 
00329     /*
00330      * End wavelength for spectral extraction
00331      */
00332 
00333     p = cpl_parameter_new_value("fors.fors_pmos_science.endwavelength",
00334                                 CPL_TYPE_DOUBLE,
00335                                 "End wavelength in spectral extraction",
00336                                 "fors.fors_pmos_science",
00337                                 0.0);
00338     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "endwavelength");
00339     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00340     cpl_parameterlist_append(recipe->parameters, p);
00341 
00342     /*
00343      * Flux conservation
00344      */
00345 
00346     p = cpl_parameter_new_value("fors.fors_pmos_science.flux",
00347                                 CPL_TYPE_BOOL,
00348                                 "Apply flux conservation",
00349                                 "fors.fors_pmos_science",
00350                                 TRUE);
00351     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "flux");
00352     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00353     cpl_parameterlist_append(recipe->parameters, p);
00354 
00355     /*
00356      * Apply flat field
00357      */
00358 
00359     p = cpl_parameter_new_value("fors.fors_pmos_science.flatfield",
00360                                 CPL_TYPE_BOOL,
00361                                 "Apply flat field",
00362                                 "fors.fors_pmos_science",
00363                                 TRUE);
00364     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "flatfield");
00365     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00366     cpl_parameterlist_append(recipe->parameters, p);
00367 
00368     /*
00369      * Median sky subtraction method
00370      */
00371 
00372     p = cpl_parameter_new_value("fors.fors_pmos_science.skymedian",
00373                                 CPL_TYPE_BOOL,
00374                                 "Sky subtraction from extracted slit spectra",
00375                                 "fors.fors_pmos_science",
00376                                 FALSE);
00377     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "skymedian");
00378     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00379     cpl_parameterlist_append(recipe->parameters, p);
00380 
00381     /*
00382      * Local sky subtraction on CCD spectra
00383      */
00384 
00385     p = cpl_parameter_new_value("fors.fors_pmos_science.skylocal",
00386                                 CPL_TYPE_BOOL,
00387                                 "Sky subtraction from CCD slit spectra",
00388                                 "fors.fors_pmos_science",
00389                                 TRUE);
00390     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "skylocal");
00391     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00392     cpl_parameterlist_append(recipe->parameters, p);
00393 
00394     /*
00395      * Cosmic rays removal
00396      */
00397 
00398     p = cpl_parameter_new_value("fors.fors_pmos_science.cosmics",
00399                                 CPL_TYPE_BOOL,
00400                                 "Eliminate cosmic rays hits (only if local "
00401                                 "sky subtraction is also requested)",
00402                                 "fors.fors_pmos_science",
00403                                 FALSE);
00404     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cosmics");
00405     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00406     cpl_parameterlist_append(recipe->parameters, p);
00407 
00408     /*
00409      * Slit margin
00410      */
00411 
00412     p = cpl_parameter_new_value("fors.fors_pmos_science.slit_margin",
00413                                 CPL_TYPE_INT,
00414                                 "Number of pixels to exclude at each slit "
00415                                 "in object detection and extraction",
00416                                 "fors.fors_pmos_science",
00417                                 3);
00418     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "slit_margin");
00419     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00420     cpl_parameterlist_append(recipe->parameters, p);
00421 
00422     /*
00423      * Extraction radius
00424      */
00425 
00426     p = cpl_parameter_new_value("fors.fors_pmos_science.ext_radius",
00427                                 CPL_TYPE_INT,
00428                                 "Maximum extraction radius for detected "
00429                                 "objects (pixel)",
00430                                 "fors.fors_pmos_science",
00431                                 12);
00432     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "ext_radius");
00433     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00434     cpl_parameterlist_append(recipe->parameters, p);
00435 
00436     /*
00437      * Contamination radius
00438      */
00439 
00440     p = cpl_parameter_new_value("fors.fors_pmos_science.cont_radius",
00441                                 CPL_TYPE_INT,
00442                                 "Minimum distance at which two objects "
00443                                 "of equal luminosity do not contaminate "
00444                                 "each other (pixel)",
00445                                 "fors.fors_pmos_science",
00446                                 0);
00447     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cont_radius");
00448     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00449     cpl_parameterlist_append(recipe->parameters, p);
00450 
00451     /*
00452      * Object extraction method
00453      */
00454 
00455     p = cpl_parameter_new_value("fors.fors_pmos_science.ext_mode",
00456                                 CPL_TYPE_INT,
00457                                 "Object extraction method: 0 = aperture, "
00458                                 "1 = Horne optimal extraction",
00459                                 "fors.fors_pmos_science",
00460                                 1);
00461     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "ext_mode");
00462     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00463     cpl_parameterlist_append(recipe->parameters, p);
00464 
00465     /*
00466      * Tolerance in object matching
00467      */
00468 
00469     p = cpl_parameter_new_value("fors.fors_pmos_science.match_tolerance",
00470                                 CPL_TYPE_DOUBLE,
00471                                 "Tolerance for matching spectra from the "
00472                                 "same object at different angles and beams "
00473                                 "(pixel)",
00474                                 "fors.fors_pmos_science",
00475                                 5.0);
00476     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "match_tolerance");
00477     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00478     cpl_parameterlist_append(recipe->parameters, p);
00479 
00480     /*
00481      * Normalise output by exposure time
00482      */
00483 
00484     p = cpl_parameter_new_value("fors.fors_pmos_science.time_normalise",
00485                                 CPL_TYPE_BOOL,
00486                                 "Normalise output spectra by the exposure time",
00487                                 "fors.fors_pmos_science",
00488                                 TRUE);
00489     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "time_normalise");
00490     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00491     cpl_parameterlist_append(recipe->parameters, p);
00492 
00493     /*
00494      * Apply chromatism correction to polarization angle
00495      */
00496 
00497     p = cpl_parameter_new_value("fors.fors_pmos_science.chromatism",
00498                                 CPL_TYPE_BOOL,
00499                                 "Chromatism correction to polarization angles",
00500                                 "fors.fors_pmos_science",
00501                                 TRUE);
00502     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "chromatism");
00503     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00504     cpl_parameterlist_append(recipe->parameters, p);
00505 
00506     /*
00507      * Rotation correction for linear polarisation
00508      */
00509 
00510     p = cpl_parameter_new_value("fors.fors_pmos_science.wollaston",
00511                                 CPL_TYPE_BOOL,
00512                      "Wollaston mounting (FORS2 only): true = 0 degrees "
00513                      "(ord. beam on top, extr. beam on bottom), "
00514                      "false = 180 degrees (beams are reversed), for FORS1 "
00515                      "is frozen to true",
00516                                 "fors.fors_pmos_science",
00517                                 TRUE);
00518     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wollaston");
00519     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00520     cpl_parameterlist_append(recipe->parameters, p);
00521 
00522     /*
00523      * Create check products
00524      */
00525 
00526     p = cpl_parameter_new_value("fors.fors_pmos_science.check",
00527                                 CPL_TYPE_BOOL,
00528                                 "Create intermediate products",
00529                                 "fors.fors_pmos_science",
00530                                 FALSE);
00531     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "check");
00532     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00533     cpl_parameterlist_append(recipe->parameters, p);
00534 
00535     /*
00536      * Computation of QC1 parameters
00537      */
00538 
00539     p = cpl_parameter_new_value("fors.fors_pmos_science.qc",
00540                                 CPL_TYPE_BOOL,
00541                                 "Compute QC1 parameters",
00542                                 "fors.fors_pmos_science",
00543                                 TRUE);
00544     cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "qc");
00545     cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
00546     cpl_parameterlist_append(recipe->parameters, p);
00547 
00548     return 0;
00549 }
00550 
00551 
00560 static int fors_pmos_science_exec(cpl_plugin *plugin)
00561 {
00562     cpl_recipe *recipe;
00563     
00564     if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
00565         recipe = (cpl_recipe *)plugin;
00566     else 
00567         return -1;
00568 
00569     return fors_pmos_science(recipe->parameters, recipe->frames);
00570 }
00571 
00572 
00581 static int fors_pmos_science_destroy(cpl_plugin *plugin)
00582 {
00583     cpl_recipe *recipe;
00584     
00585     if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
00586         recipe = (cpl_recipe *)plugin;
00587     else 
00588         return -1;
00589 
00590     cpl_parameterlist_delete(recipe->parameters); 
00591 
00592     return 0;
00593 }
00594 
00595 
00605 static int fors_pmos_science(cpl_parameterlist *parlist, cpl_frameset *frameset)
00606 {
00607 
00608     const char *recipe = "fors_pmos_science";
00609 
00610 
00611     /*
00612      * Input parameters
00613      */
00614 
00615     double      dispersion;
00616     int         group;
00617     int         skyalign;
00618     const char *wcolumn;
00619     double      startwavelength;
00620     double      endwavelength;
00621     int         flux;
00622     int         flatfield;
00623     int         skylocal;
00624     int         skymedian;
00625     int         chromatism;
00626     double      wollaston;
00627     int         cosmics;
00628     int         slit_margin;
00629     int         ext_radius;
00630     int         cont_radius;
00631     int         ext_mode;
00632     double      tolerance;
00633     int         time_normalise;
00634     int         check;
00635     int         qc;
00636 
00637     /*
00638      * CPL objects
00639      */
00640 
00641     cpl_image       **images;
00642 
00643     cpl_image       **reduceds       = NULL;
00644     cpl_image       **rerrors        = NULL;
00645     cpl_table       **slitss         = NULL;
00646     cpl_image       **mappeds        = NULL;
00647     cpl_image       **skylocalmaps   = NULL;
00648     
00649     int nobjects = 0;
00650 
00651     cpl_image        *bias           = NULL;
00652     cpl_image        *norm_flat      = NULL;
00653     cpl_image        *spectra        = NULL;
00654     cpl_image        *rectified      = NULL;
00655     cpl_image        *coordinate     = NULL;
00656     cpl_image        *rainbow        = NULL;
00657     cpl_image        *mapped         = NULL;
00658     cpl_image        *mapped_sky     = NULL;
00659     cpl_image        *mapped_cleaned = NULL;
00660     cpl_image        *smapped        = NULL;
00661     cpl_image        *wavemap        = NULL;
00662     cpl_image        *skymap         = NULL;
00663     cpl_image        *skylocalmap    = NULL;
00664     cpl_image        *dummy          = NULL;
00665 
00666     cpl_table        *grism_table    = NULL;
00667     cpl_table        *overscans      = NULL;
00668     cpl_table        *wavelengths    = NULL;
00669     cpl_table        *idscoeff       = NULL;
00670     cpl_table        *slits          = NULL;
00671     cpl_table        *origslits      = NULL;
00672     cpl_table        *maskslits      = NULL;
00673     cpl_table        *mask_science   = NULL;
00674     cpl_table        *mask_arc       = NULL;
00675     cpl_table        *mask_flat      = NULL;
00676     cpl_table        *polytraces     = NULL;
00677     cpl_table        *offsets        = NULL;
00678     cpl_table        *sky            = NULL;
00679 
00680     cpl_vector       *lines          = NULL;
00681 
00682     cpl_propertylist *header         = NULL;
00683     cpl_propertylist *save_header    = NULL;
00684 
00685     /*
00686      * Auxiliary variables
00687      */
00688 
00689     char    version[80];
00690     char   *instrume = NULL;
00691     const char   *science_tag;
00692     const char   *master_norm_flat_tag;
00693     const char   *disp_coeff_tag;
00694     const char   *disp_coeff_sky_tag;
00695     const char   *wavelength_map_sky_tag;
00696     const char   *curv_coeff_tag;
00697     const char   *slit_location_tag;
00698     const char   *reduced_science_tag;
00699     const char   *reduced_sky_tag;
00700     const char   *reduced_error_tag;
00701     const char   *mapped_science_tag;
00702     const char   *unmapped_science_tag;
00703     const char   *mapped_science_sky_tag;
00704     const char   *mapped_sky_tag;
00705     const char   *unmapped_sky_tag;
00706     const char   *object_table_tag;
00707     const char   *object_table_pol_tag;
00708     const char   *skylines_offsets_tag;
00709     const char   *reduced_q_tag;
00710     const char   *reduced_u_tag;
00711     const char   *reduced_v_tag;
00712     const char   *reduced_l_tag;
00713     const char   *reduced_i_tag;
00714     const char   *reduced_error_q_tag;
00715     const char   *reduced_error_u_tag;
00716     const char   *reduced_error_v_tag;
00717     const char   *reduced_error_l_tag;
00718     const char   *reduced_error_i_tag;
00719     const char   *reduced_nul_q_tag;
00720     const char   *reduced_nul_u_tag;
00721     const char   *reduced_nul_v_tag;
00722     const char   *reduced_angle_tag;
00723     const char   *reduced_error_angle_tag;
00724     const char   *chrom_table_tag = "RETARDER_WAVEPLATE_CHROMATISM";
00725     const char   *std_pmos_table_tag = "STD_PMOS_TABLE";
00726     float  *angles = NULL;
00727     int     pmos, circ;
00728     int     nscience;
00729     double  alltime;
00730     double  mean_rms;
00731     int     nlines;
00732     int     rebin;
00733     double *line;
00734     int     nx = 0, ny;
00735     int     ccd_xsize, ccd_ysize;
00736     double  reference;
00737     double  gain;
00738     double  ron;
00739     double  ra, dec;
00740     char    filter;
00741     double  qc_angle;
00742     double  qc_angle_err;
00743     double  qc_pl;
00744     double  qc_pl_err;
00745     int     standard;
00746     int     polarised;
00747     int     highres;
00748     int     i, j;
00749 
00750     int    *nobjs_per_slit;
00751     int     nslits;
00752 
00753     int     bagoo = 0;
00754     double  blevel = 0.0;
00755     int     doit = 0;           // montecarlo simulation
00756     int     conta = 0;          // Bagoo, conta gli oggetti con S/N > s2n
00757     int     bright = 0;         // Bagoo, marca un oggetto con S/N > s2n
00758     int     nslits_out_det = 0;
00759 
00760 
00761     cpl_error_code error;
00762 
00763     snprintf(version, 80, "%s-%s", PACKAGE, PACKAGE_VERSION);
00764 
00765     if (bagoo) {
00766         char *montecarlo = getenv("MONTECARLO");
00767 
00768         if (montecarlo) {
00769             doit = atoi(montecarlo);
00770         }
00771     }
00772 
00773     cpl_msg_set_indentation(2);
00774 
00775     if (dfs_files_dont_exist(frameset))
00776         fors_pmos_science_exit(NULL);
00777 
00778     fors_dfs_set_groups(frameset);
00779 
00780 
00781     /* 
00782      * Get configuration parameters
00783      */
00784 
00785     cpl_msg_info(recipe, "Recipe %s configuration parameters:", recipe);
00786     cpl_msg_indent_more();
00787 
00788     if (cpl_frameset_count_tags(frameset, "GRISM_TABLE") > 1)
00789         fors_pmos_science_exit("Too many in input: GRISM_TABLE");
00790 
00791     grism_table = dfs_load_table(frameset, "GRISM_TABLE", 1);
00792 
00793     dispersion = dfs_get_parameter_double(parlist, 
00794                     "fors.fors_pmos_science.dispersion", grism_table);
00795 
00796     if (dispersion <= 0.0)
00797         fors_pmos_science_exit("Invalid spectral dispersion");
00798 
00799     group = dfs_get_parameter_int(parlist,
00800                     "fors.fors_pmos_science.rebin", NULL);
00801 
00802     if (group < 1)
00803         fors_pmos_science_exit("Invalid rebin factor");
00804 
00805     skyalign = dfs_get_parameter_int(parlist, 
00806                     "fors.fors_pmos_science.skyalign", NULL);
00807 
00808     if (skyalign > 2)
00809         fors_pmos_science_exit("Max polynomial degree for sky alignment is 2");
00810 
00811     wcolumn = dfs_get_parameter_string(parlist, 
00812                     "fors.fors_pmos_science.wcolumn", NULL);
00813 
00814     startwavelength = dfs_get_parameter_double(parlist, 
00815                     "fors.fors_pmos_science.startwavelength", grism_table);
00816     if (startwavelength < 3000.0 || startwavelength > 13000.0)
00817         fors_pmos_science_exit("Invalid wavelength");
00818 
00819     endwavelength = dfs_get_parameter_double(parlist, 
00820                     "fors.fors_pmos_science.endwavelength", grism_table);
00821     if (endwavelength < 3000.0 || endwavelength > 13000.0)
00822         fors_pmos_science_exit("Invalid wavelength");
00823 
00824     if (endwavelength - startwavelength <= 0.0)
00825         fors_pmos_science_exit("Invalid wavelength interval");
00826 
00827     flux = dfs_get_parameter_bool(parlist, "fors.fors_pmos_science.flux", NULL);
00828 
00829     flatfield = dfs_get_parameter_bool(parlist, 
00830                                        "fors.fors_pmos_science.flatfield", 
00831                                        NULL);
00832 
00833     skylocal  = dfs_get_parameter_bool(parlist, 
00834                                        "fors.fors_pmos_science.skylocal", 
00835                                        NULL);
00836     skymedian = dfs_get_parameter_bool(parlist, 
00837                                        "fors.fors_pmos_science.skymedian", 
00838                                        NULL);
00839     
00840     chromatism = dfs_get_parameter_bool(parlist, 
00841                                         "fors.fors_pmos_science.chromatism", 
00842                                         NULL);
00843 
00844     wollaston = dfs_get_parameter_bool(parlist,
00845                                        "fors.fors_pmos_science.wollaston",
00846                                        NULL);
00847 
00848     wollaston = wollaston ? 0 : 1;
00849 
00850     if (skylocal && skymedian)
00851         fors_pmos_science_exit("Cannot apply sky subtraction both on "
00852                                "extracted and non-extracted spectra");
00853 
00854     cosmics = dfs_get_parameter_bool(parlist, 
00855                                      "fors.fors_pmos_science.cosmics", NULL);
00856 
00857     if (cosmics)
00858         if (!skylocal)
00859             fors_pmos_science_exit("Cosmic rays correction requires "
00860                                    "skylocal=true");
00861 
00862     slit_margin = dfs_get_parameter_int(parlist, 
00863                                         "fors.fors_pmos_science.slit_margin",
00864                                         NULL);
00865     if (slit_margin < 0)
00866         fors_pmos_science_exit("Value must be zero or positive");
00867 
00868     ext_radius = dfs_get_parameter_int(parlist, 
00869                                        "fors.fors_pmos_science.ext_radius",
00870                                        NULL);
00871     if (ext_radius < 0)
00872         fors_pmos_science_exit("Value must be zero or positive");
00873 
00874     cont_radius = dfs_get_parameter_int(parlist, 
00875                                         "fors.fors_pmos_science.cont_radius",
00876                                         NULL);
00877     if (cont_radius < 0)
00878         fors_pmos_science_exit("Value must be zero or positive");
00879 
00880     ext_mode = dfs_get_parameter_int(parlist, "fors.fors_pmos_science.ext_mode",
00881                                        NULL);
00882     if (ext_mode < 0 || ext_mode > 1)
00883         fors_pmos_science_exit("Invalid object extraction mode");
00884 
00885     tolerance = dfs_get_parameter_double(parlist, 
00886                     "fors.fors_pmos_science.match_tolerance", NULL);
00887     if (tolerance <= 0.0)
00888         fors_pmos_science_exit("Invalid object match tolerance");
00889 
00890     time_normalise = dfs_get_parameter_bool(parlist, 
00891                              "fors.fors_pmos_science.time_normalise", NULL);
00892 
00893     check = dfs_get_parameter_bool(parlist, 
00894                              "fors.fors_pmos_science.check", NULL);
00895 
00896     qc = dfs_get_parameter_bool(parlist, "fors.fors_pmos_science.qc", NULL);
00897 
00898     cpl_table_delete(grism_table); grism_table = NULL;
00899 
00900     if (cpl_error_get_code())
00901         fors_pmos_science_exit("Failure getting the configuration parameters");
00902 
00903     
00904     /* 
00905      * Check input set-of-frames
00906      */
00907 
00908     cpl_msg_indent_less();
00909     cpl_msg_info(recipe, "Check input set-of-frames:");
00910     cpl_msg_indent_more();
00911 
00912     {
00913         cpl_frameset *subframeset = cpl_frameset_duplicate(frameset);
00914         cpl_frameset_erase(subframeset, "MASTER_BIAS");
00915 
00916         if (!dfs_equal_keyword(subframeset, "ESO INS GRIS1 ID"))
00917             fors_pmos_science_exit("Input frames are not from the same grism");
00918 
00919         if (!dfs_equal_keyword(subframeset, "ESO INS FILT1 ID"))
00920             fors_pmos_science_exit("Input frames are not from the same filter");
00921 
00922         if (!dfs_equal_keyword(subframeset, "ESO DET CHIP1 ID"))
00923             fors_pmos_science_exit("Input frames are not from the same chip");
00924 
00925         cpl_frameset_delete(subframeset);
00926     }
00927 
00928     standard = 0;
00929     pmos = cpl_frameset_count_tags(frameset, "SCIENCE_PMOS");
00930 
00931     if (pmos == 0) {
00932         pmos = cpl_frameset_count_tags(frameset, "STANDARD_PMOS");
00933         standard = 1;
00934     }
00935 
00936     if (pmos == 0)
00937         fors_pmos_science_exit("Missing input scientific frame");
00938 
00939     angles = fors_check_angles(frameset, pmos, 
00940                                 standard ? "STANDARD_PMOS" : "SCIENCE_PMOS", 
00941                                 &circ);
00942     if (angles == NULL)
00943         fors_pmos_science_exit("Polarization angles could not be read");
00944 
00945     if (circ)
00946         chromatism = 0; /* Chromatism correction unrequired for 
00947                            circular polarimetry */
00948 
00949 
00950     nscience = pmos;
00951 
00952     reduceds = (cpl_image **)cpl_malloc(sizeof(cpl_image *) * nscience);
00953     rerrors  = (cpl_image **)cpl_malloc(sizeof(cpl_image *) * nscience);
00954     slitss   = (cpl_table **)cpl_malloc(sizeof(cpl_table *) * nscience);
00955     mappeds  = (cpl_image **)cpl_malloc(sizeof(cpl_image *) * nscience);
00956     skylocalmaps = (cpl_image **)cpl_malloc(sizeof(cpl_image *) * nscience);
00957 
00958     if (pmos) {
00959         cpl_msg_info(recipe, "PMOS data found");
00960         if (standard) {
00961             science_tag             = "STANDARD_PMOS";
00962             reduced_science_tag     = "REDUCED_STD_PMOS";
00963             unmapped_science_tag    = "UNMAPPED_STD_PMOS";
00964             mapped_science_tag      = "MAPPED_STD_PMOS";
00965             mapped_science_sky_tag  = "MAPPED_ALL_STD_PMOS";
00966             skylines_offsets_tag    = "SKY_SHIFTS_SLIT_STD_PMOS";
00967             wavelength_map_sky_tag  = "WAVELENGTH_MAP_STD_PMOS";
00968             disp_coeff_sky_tag      = "DISP_COEFF_STD_PMOS";
00969             mapped_sky_tag          = "MAPPED_SKY_STD_PMOS";
00970             unmapped_sky_tag        = "UNMAPPED_SKY_STD_PMOS";
00971             object_table_tag        = "OBJECT_TABLE_STD_PMOS";
00972             object_table_pol_tag    = "OBJECT_TABLE_POL_STD_PMOS";
00973             reduced_sky_tag         = "REDUCED_SKY_STD_PMOS";
00974             reduced_error_tag       = "REDUCED_ERROR_STD_PMOS";
00975             reduced_q_tag           = "REDUCED_Q_STD_PMOS";
00976             reduced_u_tag           = "REDUCED_U_STD_PMOS";
00977             reduced_v_tag           = "REDUCED_V_STD_PMOS";
00978             reduced_l_tag           = "REDUCED_L_STD_PMOS";
00979             reduced_i_tag           = "REDUCED_I_STD_PMOS";
00980             reduced_error_q_tag     = "REDUCED_ERROR_Q_STD_PMOS";
00981             reduced_error_u_tag     = "REDUCED_ERROR_U_STD_PMOS";
00982             reduced_error_v_tag     = "REDUCED_ERROR_V_STD_PMOS";
00983             reduced_error_l_tag     = "REDUCED_ERROR_L_STD_PMOS";
00984             reduced_error_i_tag     = "REDUCED_ERROR_I_STD_PMOS";
00985             reduced_nul_q_tag       = "REDUCED_NUL_Q_STD_PMOS";
00986             reduced_nul_u_tag       = "REDUCED_NUL_U_STD_PMOS";
00987             reduced_nul_v_tag       = "REDUCED_NUL_V_STD_PMOS";
00988             reduced_angle_tag       = "REDUCED_ANGLE_STD_PMOS";
00989             reduced_error_angle_tag = "REDUCED_ERROR_ANGLE_STD_PMOS";
00990         }
00991         else {
00992             science_tag             = "SCIENCE_PMOS";
00993             reduced_science_tag     = "REDUCED_SCI_PMOS";
00994             unmapped_science_tag    = "UNMAPPED_SCI_PMOS";
00995             mapped_science_tag      = "MAPPED_SCI_PMOS";
00996             mapped_science_sky_tag  = "MAPPED_ALL_SCI_PMOS";
00997             skylines_offsets_tag    = "SKY_SHIFTS_SLIT_SCI_PMOS";
00998             wavelength_map_sky_tag  = "WAVELENGTH_MAP_SCI_PMOS";
00999             disp_coeff_sky_tag      = "DISP_COEFF_SCI_PMOS";
01000             mapped_sky_tag          = "MAPPED_SKY_SCI_PMOS";
01001             unmapped_sky_tag        = "UNMAPPED_SKY_SCI_PMOS";
01002             object_table_tag        = "OBJECT_TABLE_SCI_PMOS";
01003             object_table_pol_tag    = "OBJECT_TABLE_POL_SCI_PMOS";
01004             reduced_sky_tag         = "REDUCED_SKY_SCI_PMOS";
01005             reduced_error_tag       = "REDUCED_ERROR_SCI_PMOS";
01006             reduced_q_tag           = "REDUCED_Q_SCI_PMOS";
01007             reduced_u_tag           = "REDUCED_U_SCI_PMOS";
01008             reduced_v_tag           = "REDUCED_V_SCI_PMOS";
01009             reduced_l_tag           = "REDUCED_L_SCI_PMOS";
01010             reduced_i_tag           = "REDUCED_I_SCI_PMOS";
01011             reduced_error_q_tag     = "REDUCED_ERROR_Q_SCI_PMOS";
01012             reduced_error_u_tag     = "REDUCED_ERROR_U_SCI_PMOS";
01013             reduced_error_v_tag     = "REDUCED_ERROR_V_SCI_PMOS";
01014             reduced_error_l_tag     = "REDUCED_ERROR_L_SCI_PMOS";
01015             reduced_error_i_tag     = "REDUCED_ERROR_I_SCI_PMOS";
01016             reduced_nul_q_tag       = "REDUCED_NUL_Q_SCI_PMOS";
01017             reduced_nul_u_tag       = "REDUCED_NUL_U_SCI_PMOS";
01018             reduced_nul_v_tag       = "REDUCED_NUL_V_SCI_PMOS";
01019             reduced_angle_tag       = "REDUCED_ANGLE_SCI_PMOS";
01020             reduced_error_angle_tag = "REDUCED_ERROR_ANGLE_SCI_PMOS";
01021         }
01022 
01023         master_norm_flat_tag    = "MASTER_NORM_FLAT_PMOS";
01024         disp_coeff_tag          = "DISP_COEFF_PMOS";
01025         curv_coeff_tag          = "CURV_COEFF_PMOS";
01026         slit_location_tag       = "SLIT_LOCATION_PMOS";
01027 
01028         if (!cpl_frameset_count_tags(frameset, master_norm_flat_tag)) {
01029             master_norm_flat_tag    = "MASTER_NORM_FLAT_LONG_PMOS";
01030             disp_coeff_tag          = "DISP_COEFF_LONG_PMOS";
01031             slit_location_tag       = "SLIT_LOCATION_LONG_PMOS";
01032         }
01033     }
01034 
01035     if (cpl_frameset_count_tags(frameset, "MASTER_BIAS") == 0)
01036         fors_pmos_science_exit("Missing required input: MASTER_BIAS");
01037 
01038     if (cpl_frameset_count_tags(frameset, "MASTER_BIAS") > 1)
01039         fors_pmos_science_exit("Too many in input: MASTER_BIAS");
01040 
01041     if (skyalign >= 0)
01042         if (cpl_frameset_count_tags(frameset, "MASTER_SKYLINECAT") > 1)
01043             fors_pmos_science_exit("Too many in input: MASTER_SKYLINECAT");
01044 
01045     if (cpl_frameset_count_tags(frameset, disp_coeff_tag) == 0) {
01046         cpl_msg_error(recipe, "Missing required input: %s", disp_coeff_tag);
01047         fors_pmos_science_exit(NULL);
01048     }
01049 
01050     if (cpl_frameset_count_tags(frameset, disp_coeff_tag) > 1) {
01051         cpl_msg_error(recipe, "Too many in input: %s", disp_coeff_tag);
01052         fors_pmos_science_exit(NULL);
01053     }
01054 
01055     if (cpl_frameset_count_tags(frameset, slit_location_tag) == 0) {
01056         cpl_msg_error(recipe, "Missing required input: %s",
01057                       slit_location_tag);
01058         fors_pmos_science_exit(NULL);
01059     }
01060 
01061     if (cpl_frameset_count_tags(frameset, slit_location_tag) > 1) {
01062         cpl_msg_error(recipe, "Too many in input: %s", slit_location_tag);
01063         fors_pmos_science_exit(NULL);
01064     }
01065 
01066     if (chromatism) {
01067         if (cpl_frameset_count_tags(frameset, chrom_table_tag) == 0) {
01068             cpl_msg_error(recipe, "Missing required input: %s",
01069                           chrom_table_tag);
01070             fors_pmos_science_exit(NULL);
01071         }
01072 
01073         if (cpl_frameset_count_tags(frameset, chrom_table_tag) > 1) {
01074             cpl_msg_error(recipe, "Too many in input: %s", chrom_table_tag);
01075             fors_pmos_science_exit(NULL);
01076         }
01077     }
01078 
01079     if (cpl_frameset_count_tags(frameset, master_norm_flat_tag) > 1) {
01080         if (flatfield) {
01081             cpl_msg_error(recipe, "Too many in input: %s", 
01082                           master_norm_flat_tag);
01083             fors_pmos_science_exit(NULL);
01084         }
01085         else {
01086             cpl_msg_warning(recipe, "%s in input are ignored, "
01087                             "since flat field correction was not requested", 
01088                             master_norm_flat_tag);
01089         }
01090     }
01091 
01092     if (cpl_frameset_count_tags(frameset, master_norm_flat_tag) == 1) {
01093         if (!flatfield) {
01094             cpl_msg_warning(recipe, "%s in input is ignored, "
01095                             "since flat field correction was not requested", 
01096                             master_norm_flat_tag);
01097         }
01098     }
01099 
01100     if (cpl_frameset_count_tags(frameset, master_norm_flat_tag) == 0) {
01101         if (flatfield) {
01102             cpl_msg_error(recipe, "Flat field correction was requested, "
01103                           "but no %s are found in input",
01104                           master_norm_flat_tag);
01105             fors_pmos_science_exit(NULL);
01106         }
01107     }
01108 
01109     if (standard) {
01110         if (cpl_frameset_count_tags(frameset, std_pmos_table_tag) > 1) {
01111             cpl_msg_error(recipe, "Too many in input: %s", std_pmos_table_tag);
01112             fors_pmos_science_exit(NULL);
01113         }
01114 
01115         if (qc) {
01116             if (cpl_frameset_count_tags(frameset, std_pmos_table_tag) == 0) {
01117                 cpl_msg_error(recipe, "QC computation was requested, but no "
01118                               "%s is found in input", std_pmos_table_tag);
01119                 fors_pmos_science_exit(NULL);
01120             }
01121         }
01122     }
01123 
01124     cpl_msg_indent_less();
01125 
01126 
01127     /*
01128      * Get the reference wavelength and the rebin factor along the
01129      * dispersion direction from a scientific exposure
01130      */
01131 
01132     header = dfs_load_header(frameset, science_tag, 0);
01133 
01134     if (header == NULL)
01135         fors_pmos_science_exit("Cannot load scientific frame header");
01136 
01137     instrume = (char *)cpl_propertylist_get_string(header, "INSTRUME");
01138     if (instrume == NULL)
01139         fors_pmos_science_exit("Missing keyword INSTRUME in scientific header");
01140     instrume = cpl_strdup(instrume);
01141 
01142     if (instrume[4] == '1')
01143         snprintf(version, 80, "%s/%s", "fors1", VERSION);
01144     if (instrume[4] == '2')
01145         snprintf(version, 80, "%s/%s", "fors2", VERSION);
01146 
01147     reference = cpl_propertylist_get_double(header, "ESO INS GRIS1 WLEN");
01148 
01149     if (cpl_error_get_code() != CPL_ERROR_NONE)
01150         fors_pmos_science_exit("Missing keyword ESO INS GRIS1 WLEN in scientific "
01151                         "frame header");
01152 
01153     if (reference < 3000.0)   /* Perhaps in nanometers... */
01154         reference *= 10;
01155 
01156     if (reference < 3000.0 || reference > 13000.0) {
01157         cpl_msg_error(recipe, "Invalid central wavelength %.2f read from "
01158                       "keyword ESO INS GRIS1 WLEN in scientific frame header",
01159                       reference);
01160         fors_pmos_science_exit(NULL);
01161     }
01162 
01163     cpl_msg_info(recipe, "The central wavelength is: %.2f", reference);
01164 
01165     rebin = cpl_propertylist_get_int(header, "ESO DET WIN1 BINX");
01166 
01167     if (cpl_error_get_code() != CPL_ERROR_NONE)
01168         fors_pmos_science_exit("Missing keyword ESO DET WIN1 BINX in "
01169                                "scientific frame header");
01170 
01171     if (rebin != 1) {
01172         dispersion *= rebin;
01173         cpl_msg_warning(recipe, "The rebin factor is %d, and therefore the "
01174                         "spectral dispersion used is %f A/pixel", rebin, 
01175                         dispersion);
01176         ext_radius /= rebin;
01177         cpl_msg_warning(recipe, "The rebin factor is %d, and therefore the "
01178                         "extraction radius used is %d pixel", rebin, 
01179                         ext_radius);
01180     }
01181 
01182     gain = cpl_propertylist_get_double(header, "ESO DET OUT1 CONAD");
01183 
01184     if (cpl_error_get_code() != CPL_ERROR_NONE)
01185         fors_pmos_science_exit("Missing keyword ESO DET OUT1 CONAD in "
01186                           "scientific frame header");
01187 
01188     cpl_msg_info(recipe, "The gain factor is: %.2f e-/ADU", gain);
01189 
01190     ron = cpl_propertylist_get_double(header, "ESO DET OUT1 RON");
01191 
01192     if (cpl_error_get_code() != CPL_ERROR_NONE)
01193         fors_pmos_science_exit("Missing keyword ESO DET OUT1 RON in "
01194                                "scientific frame header");
01195 
01196     ron /= gain;     /* Convert from electrons to ADU */
01197 
01198     cpl_msg_info(recipe, "The read-out-noise is: %.2f ADU", ron);
01199 
01200     if (cpl_frameset_count_tags(frameset, curv_coeff_tag) == 0) {
01201         cpl_msg_error(recipe, "Missing required input: %s", curv_coeff_tag);
01202         fors_pmos_science_exit(NULL);
01203     }
01204 
01205     if (cpl_frameset_count_tags(frameset, curv_coeff_tag) > 1) {
01206         cpl_msg_error(recipe, "Too many in input: %s", curv_coeff_tag);
01207         fors_pmos_science_exit(NULL);
01208     }
01209 
01210     cpl_msg_info(recipe, "Load normalised flat field (if present)...");
01211     cpl_msg_indent_more();
01212 
01213     if (flatfield) {
01214         norm_flat = dfs_load_image(frameset, master_norm_flat_tag, 
01215                                    CPL_TYPE_FLOAT, 0, 1);
01216     }
01217 
01218     if (skyalign >= 0) {
01219 
01220         cpl_msg_indent_less();
01221         cpl_msg_info(recipe, "Load input sky line catalog...");
01222         cpl_msg_indent_more();
01223 
01224         wavelengths = dfs_load_table(frameset, "MASTER_SKYLINECAT", 1);
01225 
01226         if (wavelengths) {
01227             /*
01228              * Cast the wavelengths into a (double precision) CPL vector
01229              */
01230 
01231             nlines = cpl_table_get_nrow(wavelengths);
01232 
01233             if (nlines == 0)
01234                 fors_pmos_science_exit("Empty input sky line catalog");
01235 
01236             if (cpl_table_has_column(wavelengths, wcolumn) != 1) {
01237                 cpl_msg_error(recipe, "Missing column %s in input line "
01238                               "catalog table", wcolumn);
01239                 fors_pmos_science_exit(NULL);
01240             }
01241 
01242             line = cpl_malloc(nlines * sizeof(double));
01243     
01244             for (i = 0; i < nlines; i++)
01245                 line[i] = cpl_table_get(wavelengths, wcolumn, i, NULL);
01246 
01247             cpl_table_delete(wavelengths); wavelengths = NULL;
01248 
01249             lines = cpl_vector_wrap(nlines, line);
01250         }
01251         else {
01252             cpl_msg_info(recipe, "No sky line catalog found in input - fine!");
01253         }
01254     }
01255 
01256     /*
01257      * Keep a table of slit positions according to science, in order to 
01258      * check its consistency with those from arc and flat.
01259      */
01260 
01261     mask_science = mos_load_slits_fors_mos(header, &nslits_out_det);
01262 
01263     cpl_propertylist_delete(header); header = NULL;
01264 
01265     cpl_table_name_column(mask_science, "xtop", "science");
01266 
01267     /*
01268      * Load the wavelength calibration table
01269      */
01270 
01271     idscoeff = dfs_load_table(frameset, disp_coeff_tag, 1);
01272 
01273     if (idscoeff == NULL)
01274         fors_pmos_science_exit("Cannot load wavelength calibration table");
01275 
01276     /*
01277      * Keep a table of slit positions according to arc, in order to 
01278      * check its consistency with those from science and flat.
01279      */
01280 
01281     header = dfs_load_header(frameset, disp_coeff_tag, 0);
01282 
01283     mask_arc = mos_load_slits_fors_mos(header, &nslits_out_det);
01284 
01285     cpl_propertylist_delete(header); header = NULL;
01286 
01287     if (cpl_table_move_column(mask_science, "xtop", mask_arc)) {
01288         cpl_error_reset();
01289         cpl_msg_warning(recipe, 
01290                         "Slit configuration of science and arc differs!");
01291         cpl_table_delete(mask_arc); mask_arc = NULL;
01292         goto skip;
01293     }
01294     cpl_table_name_column(mask_science, "xtop", "arc");
01295     cpl_table_delete(mask_arc); mask_arc = NULL;
01296 
01297     if (norm_flat) {
01298 
01299         /*
01300          * Keep a table of slit positions according to arc, in order to 
01301          * check its consistency with those from science and flat.
01302          */
01303 
01304         header = dfs_load_header(frameset, master_norm_flat_tag, 0);
01305 
01306         mask_flat = mos_load_slits_fors_mos(header, &nslits_out_det);
01307 
01308         cpl_propertylist_delete(header); header = NULL;
01309 
01310         if (cpl_table_move_column(mask_science, "xtop", mask_flat)) {
01311             cpl_error_reset();
01312             cpl_msg_warning(recipe, 
01313                             "Slit configuration of science and flat differs!");
01314             cpl_table_delete(mask_flat); mask_flat = NULL;
01315             goto skip;
01316         }
01317         cpl_table_name_column(mask_science, "xtop", "flat");
01318         cpl_table_delete(mask_flat); mask_flat = NULL;
01319     }
01320 
01321     cpl_table_duplicate_column(mask_science, "diff", mask_science, "science");
01322     cpl_table_subtract_columns(mask_science, "diff", "arc");
01323     cpl_table_abs_column(mask_science, "diff");
01324 
01325     if (cpl_table_get_column_max(mask_science, "diff") > 0.01)  {
01326         cpl_msg_warning(recipe, 
01327                         "Slit configuration of science and arc differs!");
01328         goto skip;
01329     }
01330 
01331     if (norm_flat) {
01332         cpl_table_erase_column(mask_science, "diff");
01333 
01334         cpl_table_duplicate_column(mask_science, "diff", 
01335                                    mask_science, "science");
01336         cpl_table_subtract_columns(mask_science, "diff", "flat");
01337         cpl_table_abs_column(mask_science, "diff");
01338 
01339         if (cpl_table_get_column_max(mask_science, "diff") > 0.01)  {
01340             cpl_msg_warning(recipe, 
01341                             "Slit configuration of science and flat differs!");
01342             goto skip;
01343         }
01344     }
01345 
01346 skip:
01347 
01348     cpl_table_delete(mask_science); mask_science = NULL;
01349 
01350     for (j = 0; j < nscience; j++) {
01351         int k;
01352 
01353         cpl_msg_indent_less();
01354         cpl_msg_info(recipe, "Processing scientific exposure of angle %.2f "
01355                      "(%d out of %d) ...",
01356                      angles[j], j + 1, nscience);
01357         cpl_msg_indent_more();
01358 
01359         cpl_msg_info(recipe, "Load scientific exposure...");
01360         cpl_msg_indent_more();
01361 
01362 
01363         /*
01364          * FIXME: Horrible workaround to avoid the problem because of the
01365          * multiple encapsulation of cpl_frameset_find() in different 
01366          * loading functions
01367          */
01368 
01369         header = dfs_load_header(frameset, science_tag, 0);
01370 
01371         for (k = 0; k < j; k ++) {
01372             cpl_propertylist_delete(header);
01373             header = dfs_load_header(frameset, NULL, 0);
01374         }
01375 
01376         spectra = dfs_load_image(frameset, science_tag, CPL_TYPE_FLOAT, 0, 0);
01377 
01378         for (k = 0; k < j; k ++) {
01379             cpl_image_delete(spectra);
01380             spectra = dfs_load_image(frameset, NULL, CPL_TYPE_FLOAT, 0, 0);
01381         }
01382 
01383         if (spectra == NULL)
01384             fors_pmos_science_exit("Cannot load scientific frame");
01385             
01386         if (header == NULL)
01387             fors_pmos_science_exit("Cannot load scientific frame header");
01388 
01389         alltime = cpl_propertylist_get_double(header, "EXPTIME");
01390 
01391         if (cpl_error_get_code() != CPL_ERROR_NONE)
01392             fors_pmos_science_exit("Missing keyword EXPTIME in scientific "
01393                                    "frame header");
01394 
01395         cpl_msg_info(recipe, "Scientific frame exposure time: %.2f s", 
01396                      alltime);
01397 
01398         ra = cpl_propertylist_get_double(header, "RA");
01399         dec = cpl_propertylist_get_double(header, "DEC");
01400 
01401         if (cpl_error_get_code() != CPL_ERROR_NONE)
01402             fors_pmos_science_exit("Missing keywords RA and DEC in scientific "
01403                                    "frame header");
01404 
01405         /* Leave the header on for the next step... */
01406 
01407         cpl_msg_indent_less();
01408 
01409         /*
01410          * Remove the master bias
01411          */
01412 
01413         cpl_msg_info(recipe, "Remove the master bias...");
01414 
01415         bias = dfs_load_image(frameset, "MASTER_BIAS", CPL_TYPE_FLOAT, 0, 1);
01416 
01417         if (bias == NULL)
01418             fors_pmos_science_exit("Cannot load master bias");
01419 
01420         if (doit) {
01421             if (j == 0)
01422                blevel = cpl_image_get_mean(bias); 
01423             mos_randomise_image(spectra, ron, gain, blevel);
01424         }
01425 
01426         overscans = mos_load_overscans_fors(header);
01427 
01428         dummy = mos_remove_bias(spectra, bias, overscans);
01429         cpl_image_delete(spectra); spectra = dummy; dummy = NULL;
01430         cpl_image_delete(bias); bias = NULL;
01431         cpl_table_delete(overscans); overscans = NULL;
01432 
01433         if (spectra == NULL)
01434             fors_pmos_science_exit("Cannot remove bias from scientific frame");
01435 
01436         ccd_xsize = nx = cpl_image_get_size_x(spectra);
01437         ccd_ysize = ny = cpl_image_get_size_y(spectra);
01438 
01439         if (flatfield) {
01440 
01441             if (norm_flat) {
01442                 cpl_msg_info(recipe, "Apply flat field correction...");
01443                 if (cpl_image_divide(spectra, norm_flat) != CPL_ERROR_NONE) {
01444                     cpl_msg_error(recipe, 
01445                                   "Failure of flat field correction: %s",
01446                                   cpl_error_get_message());
01447                     fors_pmos_science_exit(NULL);
01448                 }
01449             }
01450             else {
01451                 cpl_msg_error(recipe, "Cannot load input %s for flat field "
01452                               "correction", master_norm_flat_tag);
01453                 fors_pmos_science_exit(NULL);
01454             }
01455 
01456         }
01457 
01458         /*
01459          * Load the spectral curvature table
01460          */
01461 
01462         polytraces = dfs_load_table(frameset, curv_coeff_tag, 1);
01463         if (polytraces == NULL)
01464             fors_pmos_science_exit("Cannot load spectral curvature table");
01465 
01466         /*
01467          * Load the slit location table
01468          */
01469 
01470         slits = dfs_load_table(frameset, slit_location_tag, 1);
01471         if (slits == NULL)
01472             fors_pmos_science_exit("Cannot load slits location table");
01473 
01474         cpl_msg_info(recipe, "Processing scientific spectra...");
01475 
01476         /*
01477          * This one will also generate the spatial map from the spectral 
01478          * curvature table (in the case of multislit data)
01479          */
01480 
01481         coordinate = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
01482 
01483         smapped = mos_spatial_calibration(spectra, slits, polytraces, 
01484                                           reference, startwavelength, 
01485                                           endwavelength, dispersion, 
01486                                           flux, coordinate);
01487 
01488         /*
01489          * Generate a rectified wavelength map from the wavelength calibration 
01490          * table
01491          */
01492 
01493         rainbow = mos_map_idscoeff(idscoeff, nx, reference, startwavelength, 
01494                                    endwavelength);
01495 
01496         if (dispersion > 1.0)
01497             highres = 0;
01498         else
01499             highres = 1;
01500 
01501         if (skyalign >= 0) {
01502             if (skyalign) {
01503                 cpl_msg_info(recipe, 
01504                              "Align wavelength solution to reference skylines "
01505                              "applying %d order residual fit...", skyalign);
01506             }
01507             else {
01508                 cpl_msg_info(recipe, "Align wavelength solution to reference "
01509                              "skylines applying median offset...");
01510             }
01511 
01512             if (!j) {
01513                 offsets = mos_wavelength_align(smapped, slits, reference, 
01514                                                startwavelength, endwavelength, 
01515                                                idscoeff, lines, highres, 
01516                                                skyalign, rainbow, 4);
01517                 if (offsets) {
01518                     if (standard)
01519                         cpl_msg_warning(recipe, "Alignment of the wavelength "
01520                                         "solution to reference sky lines may "
01521                                         "be unreliable in this case!");
01522 
01523                     if (dfs_save_table(frameset, offsets, skylines_offsets_tag,
01524                                        NULL, parlist, recipe, version)) {
01525                         fors_pmos_science_exit(NULL);
01526                     }
01527 
01528                 } else {
01529                     cpl_msg_warning(recipe, "Alignment of the wavelength "
01530                                     "solution to reference sky lines could "
01531                                     "not be done!");
01532                     skyalign = -1;
01533                 }
01534             }
01535 
01536 
01537         }
01538 
01539         wavemap = mos_map_wavelengths(coordinate, rainbow, slits, 
01540                                       polytraces, reference, 
01541                                       startwavelength, endwavelength,
01542                                       dispersion);
01543 
01544 
01545         cpl_image_delete(rainbow); rainbow = NULL;
01546         cpl_image_delete(coordinate); coordinate = NULL;
01547 
01548         /*
01549          * Here the wavelength calibrated slit spectra are created. This frame
01550          * contains sky_science.
01551          */
01552 
01553         mapped_sky = mos_wavelength_calibration(smapped, reference,
01554                                                 startwavelength, endwavelength,
01555                                                 dispersion, idscoeff, flux);
01556 
01557         if (!j) {
01558             cpl_msg_indent_less();
01559             cpl_msg_info(recipe, 
01560                          "Check applied wavelength against skylines...");
01561             cpl_msg_indent_more();
01562 
01563             mean_rms = mos_distortions_rms(mapped_sky, lines, startwavelength,
01564                                            dispersion, 6, highres);
01565 
01566 
01567             cpl_msg_info(recipe, "Mean residual: %f", mean_rms);
01568 
01569             mean_rms = cpl_table_get_column_mean(idscoeff, "error");
01570 
01571             cpl_msg_info(recipe, "Mean model accuracy: %f pixel (%f A)",
01572                          mean_rms, mean_rms * dispersion);
01573         }
01574 
01575         save_header = cpl_propertylist_duplicate(header);
01576 
01577         cpl_propertylist_update_double(header, "CRPIX1", 1.0);
01578         cpl_propertylist_update_double(header, "CRPIX2", 1.0);
01579         cpl_propertylist_update_double(header, "CRVAL1", 
01580                                        startwavelength + dispersion/2);
01581         cpl_propertylist_update_double(header, "CRVAL2", 1.0);
01582         cpl_propertylist_update_double(header, "CD1_1", dispersion);
01583         cpl_propertylist_update_double(header, "CD1_2", 0.0);
01584         cpl_propertylist_update_double(header, "CD2_1", 0.0);
01585         cpl_propertylist_update_double(header, "CD2_2", 1.0);
01586         cpl_propertylist_update_string(header, "CTYPE1", "LINEAR");
01587         cpl_propertylist_update_string(header, "CTYPE2", "PIXEL");
01588 
01589         if (time_normalise) {
01590             dummy = cpl_image_divide_scalar_create(mapped_sky, alltime);
01591 
01592             if (!j) {
01593                 if(dfs_save_image_null(frameset, parlist,
01594                                        mapped_science_sky_tag,
01595                                        recipe, version)) {
01596                     fors_pmos_science_exit(NULL);
01597                 }
01598             }
01599 
01600             if (dfs_save_image_ext(dummy, mapped_science_sky_tag, header)) {
01601                 fors_pmos_science_exit(NULL);
01602             }
01603 
01604             cpl_image_delete(dummy); dummy = NULL;
01605         }
01606         else {
01607 
01608             if (!j) {
01609                 if(dfs_save_image_null(frameset, parlist,
01610                                        mapped_science_sky_tag,
01611                                        recipe, version)) {
01612                     fors_pmos_science_exit(NULL);
01613                 }
01614             }
01615 
01616             if (dfs_save_image_ext(mapped_sky,
01617                                    mapped_science_sky_tag, header)) {
01618                 fors_pmos_science_exit(NULL);
01619             }
01620 
01621         }
01622 
01623         if (skymedian == 0 && skylocal == 0) {
01624             cpl_image_delete(mapped_sky); mapped_sky = NULL;
01625         }
01626 
01627         if (skylocal) {
01628 
01629             cpl_msg_indent_less();
01630 
01631             cpl_msg_info(recipe, "Local sky determination...");
01632             cpl_msg_indent_more();
01633             skymap = mos_subtract_sky(spectra, slits, polytraces, reference,
01634                                   startwavelength, endwavelength, dispersion);
01635 
01636             if (skymap) {
01637                 if (time_normalise)
01638                     cpl_image_divide_scalar(skymap, alltime);
01639 
01640                 if (!j) {
01641                     if(dfs_save_image_null(frameset, parlist,
01642                                            unmapped_sky_tag,
01643                                            recipe, version)) {
01644                         fors_pmos_science_exit(NULL);
01645                     }
01646                 }
01647 
01648                 if (dfs_save_image_ext(skymap, unmapped_sky_tag,
01649                                        save_header)) {
01650                     fors_pmos_science_exit(NULL);
01651                 }
01652 
01653                 cpl_image_delete(skymap); skymap = NULL;
01654 
01655                 if (!j) {
01656                     if(dfs_save_image_null(frameset, parlist,
01657                                            unmapped_science_tag,
01658                                            recipe, version)) {
01659                         fors_pmos_science_exit(NULL);
01660                     }
01661                 }
01662 
01663                 if (dfs_save_image_ext(spectra, unmapped_science_tag,
01664                                        save_header)) {
01665                     fors_pmos_science_exit(NULL);
01666                 }
01667 
01668                 if (cosmics) {
01669                     cpl_msg_info(recipe, "Removing cosmic rays...");
01670                     mos_clean_cosmics(spectra, gain, -1., -1.);
01671                 }
01672 
01673                 /*
01674                  * The spatially rectified image, that contained the sky,
01675                  * is replaced by a sky-subtracted spatially rectified image:
01676                  */
01677 
01678                 cpl_image_delete(smapped); smapped = NULL;
01679 
01680                 smapped = mos_spatial_calibration(spectra, slits, polytraces, 
01681                                                   reference, startwavelength, 
01682                                                   endwavelength, dispersion, 
01683                                                   flux, NULL);
01684             }
01685             else {
01686                 cpl_msg_warning(recipe, "Sky subtraction failure");
01687                 if (cosmics)
01688                     cpl_msg_warning(recipe, 
01689                                     "Cosmic rays removal not performed!");
01690                 cosmics = skylocal = 0;
01691             }
01692         }
01693 
01694         cpl_image_delete(spectra); spectra = NULL;
01695         cpl_table_delete(polytraces); polytraces = NULL;
01696 
01697         if (skyalign >= 0) {
01698             save_header = dfs_load_header(frameset, science_tag, 0);
01699 
01700             if (!j) {
01701                 if(dfs_save_image_null(frameset, parlist,
01702                                        wavelength_map_sky_tag,
01703                                        recipe, version)) {
01704                     fors_pmos_science_exit(NULL);
01705                 }
01706             }
01707 
01708             if (dfs_save_image_ext(wavemap, wavelength_map_sky_tag,
01709                                    save_header)) {
01710                 fors_pmos_science_exit(NULL);
01711             }
01712         }
01713 
01714         cpl_image_delete(wavemap); wavemap = NULL;
01715 
01716         mapped = mos_wavelength_calibration(smapped, reference,
01717                                             startwavelength, endwavelength,
01718                                             dispersion, idscoeff, flux);
01719 
01720         cpl_image_delete(smapped); smapped = NULL;
01721 
01722         if (skyalign >= 0) {
01723             if (!j) {
01724                 if (dfs_save_table(frameset, idscoeff, disp_coeff_sky_tag,
01725                                    NULL, parlist, recipe, version)) {
01726                     fors_pmos_science_exit(NULL);
01727                 }
01728             }
01729         }
01730 
01731         if (skymedian) {
01732             cpl_msg_indent_less();
01733             cpl_msg_info(recipe, "Local sky determination...");
01734             cpl_msg_indent_more();
01735        
01736             skylocalmap = mos_sky_local_old(mapped, slits);       
01737             cpl_image_subtract(mapped, skylocalmap);
01738             cpl_image_delete(skylocalmap); skylocalmap = NULL;
01739         }
01740 
01741         if (skymedian || skylocal) {
01742 
01743             skylocalmap = cpl_image_subtract_create(mapped_sky, mapped);
01744 
01745             cpl_image_delete(mapped_sky); mapped_sky = NULL;
01746 
01747             if (time_normalise) {
01748                 dummy = cpl_image_divide_scalar_create(skylocalmap, alltime);
01749 
01750                 if (!j) {
01751                     if(dfs_save_image_null(frameset, parlist,
01752                                            mapped_sky_tag,
01753                                            recipe, version)) {
01754                         fors_pmos_science_exit(NULL);
01755                     }
01756                 }
01757 
01758                 if (dfs_save_image_ext(dummy, mapped_sky_tag,
01759                                        header)) {
01760                     fors_pmos_science_exit(NULL);
01761                 }
01762 
01763                 cpl_image_delete(dummy); dummy = NULL;
01764             }
01765             else {
01766                 if (!j) {
01767                     if(dfs_save_image_null(frameset, parlist,
01768                                            mapped_sky_tag,
01769                                            recipe, version)) {
01770                         fors_pmos_science_exit(NULL);
01771                     }
01772                 }
01773 
01774                 if (dfs_save_image_ext(skylocalmap, mapped_sky_tag,
01775                                        header)) {
01776                     fors_pmos_science_exit(NULL);
01777                 }
01778             }
01779 
01780             skylocalmaps[j] = skylocalmap;
01781 
01782             cpl_msg_indent_less();
01783             cpl_msg_info(recipe, "Object detection...");
01784             cpl_msg_indent_more();
01785 
01786             if (!j) {
01787                 origslits = cpl_table_duplicate(slits);
01788                 nslits = cpl_table_get_nrow(slits);
01789             }
01790 
01791             if (cosmics || nscience > 1) {
01792                 dummy = mos_detect_objects(mapped, slits, slit_margin, 
01793                                            ext_radius, cont_radius);
01794             }
01795             else {
01796                 mapped_cleaned = cpl_image_duplicate(mapped);
01797                 mos_clean_cosmics(mapped_cleaned, gain, -1., -1.);
01798                 dummy = mos_detect_objects(mapped_cleaned, slits, slit_margin, 
01799                                            ext_radius, cont_radius);
01800 
01801                 cpl_image_delete(mapped_cleaned); mapped_cleaned = NULL;
01802             }
01803 
01804             cpl_image_delete(dummy); dummy = NULL;
01805 
01806         }
01807 
01808         slitss[j]  = slits;
01809         mappeds[j] = mapped;
01810 
01811         cpl_msg_indent_less();
01812 
01813         cpl_propertylist_delete(header); header = NULL;
01814         cpl_propertylist_delete(save_header); save_header = NULL;
01815     }
01816 
01817     cpl_table_delete(offsets); offsets = NULL;
01818     cpl_table_delete(idscoeff); idscoeff = NULL;
01819 
01820     cpl_image_delete(norm_flat); norm_flat = NULL;
01821     cpl_vector_delete(lines); lines = NULL;
01822 
01823         
01824     cpl_msg_indent_less();
01825     cpl_msg_info(recipe, 
01826                  "Check object detection in both beams for all angles...");
01827     cpl_msg_indent_more();
01828 
01829     /* 
01830      * House keeping - selection of objects for which information required 
01831      * for Stokes parameters computation is present 
01832      */
01833 
01834     error = mos_object_intersect(slitss, origslits, nscience, tolerance);
01835     if (error == CPL_ERROR_DATA_NOT_FOUND) {
01836         cpl_msg_warning(recipe, "No objects found: no Stokes "
01837                        "parameters to compute!");
01838         for (j = 0; j < nscience; j++)
01839             cpl_table_delete(slitss[j]);
01840         cpl_free(slitss);
01841         cpl_table_delete(origslits);
01842         return 0;
01843     } else if (error) {
01844         fors_pmos_science_exit("Problem in polarimetric object selection");
01845     }
01846 
01847     if (dfs_save_table(frameset, origslits, object_table_pol_tag,
01848                        NULL, parlist, recipe, version)) {
01849         fors_pmos_science_exit(NULL);
01850     }
01851 
01852     /*
01853      * Save also object tables per angle after intersection
01854      */
01855 
01856     for (j = 0; j < nscience; j++) {
01857         if (!j) {
01858             if(dfs_save_image_null(frameset, parlist, object_table_tag,
01859                                    recipe, version)) {
01860                 fors_pmos_science_exit(NULL);
01861             }
01862         }
01863 
01864         if (dfs_save_table_ext(slitss[j], object_table_tag, NULL)) {
01865             fors_pmos_science_exit(NULL);
01866         }
01867     }
01868 
01869     nobjs_per_slit = fors_get_nobjs_perslit(origslits);
01870 
01871     cpl_msg_indent_less();
01872     cpl_msg_info(recipe, "Object extraction...");
01873     cpl_msg_indent_more();
01874 
01875     for (j = 0; j < nscience; j++) {
01876         int k;
01877 
01878         header = dfs_load_header(frameset, science_tag, 0);
01879 
01880         for (k = 0; k < j; k ++) {
01881             cpl_propertylist_delete(header);
01882             header = dfs_load_header(frameset, NULL, 0);
01883         }
01884 
01885         cpl_propertylist_update_double(header, "CRPIX1", 1.0);
01886         cpl_propertylist_update_double(header, "CRPIX2", 1.0);
01887         cpl_propertylist_update_double(header, "CRVAL1", 
01888                                 startwavelength + (dispersion * group)/2);
01889         cpl_propertylist_update_double(header, "CRVAL2", 1.0);
01890         cpl_propertylist_update_double(header, "CD1_1", dispersion * group);
01891         cpl_propertylist_update_double(header, "CD1_2", 0.0);
01892         cpl_propertylist_update_double(header, "CD2_1", 0.0);
01893         cpl_propertylist_update_double(header, "CD2_2", 1.0);
01894         cpl_propertylist_update_string(header, "CTYPE1", "LINEAR");
01895         cpl_propertylist_update_string(header, "CTYPE2", "PIXEL");
01896 
01897         if (skymedian || skylocal) {
01898 
01899             cpl_msg_info(recipe, "Extracting at angle %.2f (%d out of %d) ...",
01900                          angles[j], j + 1, nscience);
01901 
01902             images = mos_extract_objects(mappeds[j], skylocalmaps[j],
01903                                          origslits, 
01904                                          ext_mode, ron, gain, 1);
01905 
01906             cpl_image_delete(skylocalmaps[j]); skylocalmaps[j] = NULL;
01907 
01908             if (images) {
01909                 if (time_normalise)
01910                     cpl_image_divide_scalar(images[0], alltime);
01911 
01912                 mos_rebin_signal(images, group);
01913 
01914                 if (!j) {
01915                     if(dfs_save_image_null(frameset, parlist,
01916                                            reduced_science_tag,
01917                                            recipe, version)) {
01918                         fors_pmos_science_exit(NULL);
01919                     }
01920                 }
01921 
01922                 if (dfs_save_image_ext(images[0], reduced_science_tag,
01923                                        header)) {
01924                     fors_pmos_science_exit(NULL);
01925                 }
01926 
01927                 reduceds[j] = images[0];
01928     
01929                 if (time_normalise)
01930                     cpl_image_divide_scalar(images[1], alltime);
01931 
01932                 mos_rebin_signal(images + 1, group);
01933 
01934                 if (!j) {
01935                     if(dfs_save_image_null(frameset, parlist,
01936                                            reduced_sky_tag,
01937                                            recipe, version)) {
01938                         fors_pmos_science_exit(NULL);
01939                     }
01940                 }
01941 
01942                 if (dfs_save_image_ext(images[1], reduced_sky_tag,
01943                                        header)) {
01944                     fors_pmos_science_exit(NULL);
01945                 }
01946                 cpl_image_delete(images[1]);
01947     
01948                 if (time_normalise)
01949                     cpl_image_divide_scalar(images[2], alltime);
01950 
01951                 mos_rebin_error(images + 2, group);
01952 
01953                 if (!j) {
01954                     if(dfs_save_image_null(frameset, parlist,
01955                                            reduced_error_tag,
01956                                            recipe, version)) {
01957                         fors_pmos_science_exit(NULL);
01958                     }
01959                 }
01960 
01961                 if (dfs_save_image_ext(images[2], reduced_error_tag,
01962                                        header)) {
01963                     fors_pmos_science_exit(NULL);
01964                 }
01965 
01966                 rerrors[j] = images[2];
01967 
01968                 cpl_free(images);
01969             }
01970             else {
01971                 cpl_msg_warning(recipe, "No objects found: the products "
01972                                 "%s, %s, and %s are not created", 
01973                                 reduced_science_tag, reduced_sky_tag, 
01974                                 reduced_error_tag);
01975             }
01976 
01977         }
01978 
01979         if (skymedian || skylocal) {
01980             if (time_normalise)
01981                 cpl_image_divide_scalar(mappeds[j], alltime);
01982 
01983             if (!j) {
01984                 if(dfs_save_image_null(frameset, parlist,
01985                                        mapped_science_tag,
01986                                        recipe, version)) {
01987                     fors_pmos_science_exit(NULL);
01988                 }
01989             }
01990 
01991             if (dfs_save_image_ext(mappeds[j], mapped_science_tag,
01992                                    header)) {
01993                 fors_pmos_science_exit(NULL);
01994             }
01995         }
01996 
01997         cpl_image_delete(mappeds[j]); mappeds[j] = NULL;
01998         cpl_propertylist_delete(header); header = NULL;
01999 
02000     }
02001 
02002     cpl_table_delete(origslits);
02003 
02004     /* Stokes computation */
02005 
02006     nobjects = cpl_image_get_size_y(reduceds[0]) / 2;
02007     nx       = cpl_image_get_size_x(reduceds[0]);
02008 
02009     header = cpl_propertylist_new();
02010     cpl_propertylist_update_double(header, "CRPIX1", 1.0);
02011     cpl_propertylist_update_double(header, "CRPIX2", 1.0);
02012     cpl_propertylist_update_double(header, "CRVAL1", 
02013                                    startwavelength + (dispersion * group)/2);
02014     cpl_propertylist_update_double(header, "CRVAL2", 1.0);
02015     cpl_propertylist_update_double(header, "CD1_1", dispersion * group);
02016     cpl_propertylist_update_double(header, "CD1_2", 0.0);
02017     cpl_propertylist_update_double(header, "CD2_1", 0.0);
02018     cpl_propertylist_update_double(header, "CD2_2", 1.0);
02019     cpl_propertylist_update_string(header, "CTYPE1", "LINEAR");
02020     cpl_propertylist_update_string(header, "CTYPE2", "PIXEL");
02021     
02022     if (circ) {
02023 
02024         cpl_image        *pv_im          = NULL;
02025         cpl_image        *pi_im          = NULL;
02026         cpl_image        *pvnull_im      = NULL;
02027         cpl_image        *pierr_im       = NULL;
02028         cpl_image        *perr_im        = NULL;
02029 
02030         double           *p_v            = NULL;
02031         double           *p_i            = NULL;
02032         double           *p_vnull        = NULL;
02033         double           *perr           = NULL;
02034         double           *pierr           = NULL;
02035 
02036         double            mean_vnull;
02037 
02038         int p = -1;
02039         int total = 0;
02040 
02041         pv_im     = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02042         perr_im   = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02043         pi_im     = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02044         pierr_im  = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02045 
02046         p_v     = cpl_image_get_data_double(pv_im);
02047         perr    = cpl_image_get_data_double(perr_im);
02048         p_i     = cpl_image_get_data_double(pi_im);
02049         pierr   = cpl_image_get_data_double(pierr_im);
02050 
02051         if (nscience / 2 > 1) {
02052             pvnull_im = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02053             p_vnull = cpl_image_get_data_double(pvnull_im);
02054         }
02055 
02056         for (j = 0; j < nobjects; j++) {
02057 
02058             FILE *file;               // Bagoo
02059             char *filename;           // Bagoo
02060 
02061             int k, m;
02062 
02063             double * ip_v, * ip_i, * ipierr,
02064                    * ip_vnull, * iperr;
02065 
02066             float * data;
02067             float * iff,  * ierr;
02068 
02069             ip_v = p_v + (nobjects - 1 - j) * nx;
02070 
02071             if (nscience / 2 > 1)
02072                 ip_vnull = p_vnull + (nobjects - 1 - j) * nx;
02073 
02074             iperr = perr + (nobjects - 1 - j) * nx;
02075 
02076             ip_i = p_i + (nobjects - 1 - j) * nx;
02077             ipierr = pierr + (nobjects - 1 - j) * nx;
02078 
02079             total = 0;
02080             for (i = 0; i < nslits; i += 2) {
02081                 total += nobjs_per_slit[i];
02082                 if (total > j) {
02083                     p = i;
02084                     break;
02085                 }
02086             }
02087 
02088             for (k = 0; k < nscience / 2; k++) {
02089                 float *if_o,  *if_e,  *ifdelta_o, *ifdelta_e;
02090                 float *if_o_err,  *if_e_err,  *ifdelta_o_err, *ifdelta_e_err;
02091 
02092                 int pos   = fors_find_angle_pos(angles, nscience, 180 * k - 45);
02093                 int pos_d = fors_find_angle_pos(angles, nscience, 180 * k + 45);
02094 
02095 
02096                 data = cpl_image_get_data_float(reduceds[pos]);
02097 
02098                 if_o = data + (2 * (nobjects - total) + nobjs_per_slit[p] 
02099                      + (total - j - 1)) * nx;
02100 
02101                 if_e = data + (2 * (nobjects - total) 
02102                      + (total - j - 1)) * nx;
02103 
02104                 data = cpl_image_get_data_float(reduceds[pos_d]);
02105 
02106                 ifdelta_o = data + (2 * (nobjects - total) + nobjs_per_slit[p] 
02107                           + (total - j - 1)) * nx;
02108 
02109                 ifdelta_e = data + (2 * (nobjects - total) 
02110                           + (total - j - 1)) * nx;
02111 
02112                 data = cpl_image_get_data_float(rerrors[pos]);
02113 
02114                 if_o_err = data 
02115                          + (2 * (nobjects - total) + nobjs_per_slit[p]
02116                          + (total - j - 1)) * nx;
02117 
02118                 if_e_err = data + (2 * (nobjects - total)
02119                          + (total - j - 1)) * nx;
02120 
02121                 data = cpl_image_get_data_float(rerrors[pos_d]);
02122 
02123                 ifdelta_o_err = data 
02124                               + (2 * (nobjects - total) + nobjs_per_slit[p]
02125                               + (total - j - 1)) * nx;
02126 
02127                 ifdelta_e_err = data + (2 * (nobjects - total)
02128                               + (total - j - 1)) * nx;
02129 
02130                 if (bagoo) {
02131 
02132                     char *signal_to_noise  = getenv("SIGNAL_TO_NOISE" );
02133                     float s2n = 100.;
02134                     char *min_s2n  = getenv("MIN_S2N" );
02135                     int   ms2n = 50;
02136 
02137                     if (signal_to_noise)
02138                         s2n = atof(signal_to_noise);
02139 
02140                     if (min_s2n)
02141                         ms2n = atoi(min_s2n);
02142 
02143                     /*
02144                      * Check whether S/N is > s2n in more than ms2n pixels
02145                      * (on first frame, on ordinary beam)
02146                      */
02147 
02148                     if (k == 0) {
02149                         bright = 0;
02150                         for (m = 0; m < nx; m++) {
02151                             if (if_o_err[m] > 0.0) {
02152                                 if (if_o[m]/if_o_err[m] > s2n) {
02153                                     bright++;
02154                                     if (bright > ms2n) {
02155                                         break;
02156                                     }
02157                                 }
02158                             }
02159                         }
02160                     }
02161 
02162                     if (bright > ms2n) {
02163                         conta++;
02164                         filename = cpl_sprintf("angle_%d_%d.dat", 
02165                                                180*k-45, conta);
02166                         file = fopen(filename, "w");
02167     
02168                         fprintf(file, "%d\n", p + 2);
02169 
02170                         for (m = 0; m < nx; m++) {
02171                             double lambda = startwavelength 
02172                                           + dispersion * group * (0.5 + m);
02173                             fprintf(file, "%.3f %.9e %.9e %.9e %.9e\n",
02174                                     lambda, if_o[m], if_o_err[m], 
02175                                     if_e[m], if_e_err[m]);
02176                         }
02177 
02178                         fclose(file);
02179                         cpl_free(filename);
02180 
02181                         filename = cpl_sprintf("angle_%d_%d.dat", 
02182                                                180*k+45, conta);
02183                         file = fopen(filename, "w");
02184 
02185                         fprintf(file, "%d\n", p + 2);
02186 
02187                         for (m = 0; m < nx; m++) {
02188                             double lambda = startwavelength 
02189                                           + dispersion * group * (0.5 + m);
02190                             fprintf(file, "%.3f %.9e %.9e %.9e %.9e\n",
02191                                     lambda, ifdelta_o[m], ifdelta_o_err[m], 
02192                                     ifdelta_e[m], ifdelta_e_err[m]);
02193                         }
02194     
02195                         fclose(file);
02196                         cpl_free(filename);
02197                     }
02198                     else {
02199                         cpl_msg_info(recipe, 
02200                                      "Extracted signal not written to "
02201                                      "ASCII (S/N > %.0f only in %d < %d "
02202                                      "bins)", s2n, bright, ms2n);
02203                     }
02204                 }  // End of bagoo
02205 
02206                 for (m = 0; m < nx; m++) {
02207 
02208                     double quantity = if_o[m] + if_e[m] == 0.0 ? 0.0 :
02209                         (if_o[m]      - if_e[m]     ) /
02210                         (if_o[m]      + if_e[m]     ) -
02211                         (ifdelta_o[m] - ifdelta_e[m]) /
02212                         (ifdelta_o[m] + ifdelta_e[m]);
02213 
02214                     quantity = isfinite(quantity) ? quantity : 0.0;
02215 
02216                     /* PQ map computation */
02217                     ip_v[m] += quantity * 0.5 / (nscience / 2);
02218 
02219                     /* PQnull map computation */
02220                     if (nscience / 2 > 1) {
02221                         if (k % 2)
02222                             ip_vnull[m] += quantity * 0.5 / (nscience / 2);
02223                         else
02224                             ip_vnull[m] -= quantity * 0.5 / (nscience / 2);
02225                     }
02226 
02227                     /* I map computation */
02228                     ip_i[m] += (if_o[m] + if_e[m] + 
02229                                 ifdelta_o[m] + ifdelta_e[m]) / nscience;
02230 
02231                     /* Variance map computation */
02232                     ipierr[m] += (if_o_err[m]      * if_o_err[m]
02233                                 + if_e_err[m]      * if_e_err[m]
02234                                 + ifdelta_o_err[m] * ifdelta_o_err[m]
02235                                 + ifdelta_e_err[m] * ifdelta_e_err[m]) 
02236                                / nscience / nscience;
02237 
02238                 }
02239             }
02240 
02241             /* Error map */
02242             data = cpl_image_get_data_float(reduceds[0]);
02243             iff  = data + (2 * (nobjects - total) + (total - j - 1)) * nx;
02244 
02245             data = cpl_image_get_data_float(rerrors[0]);
02246             ierr = data + (2 * (nobjects - total) + (total - j - 1)) * nx;
02247 
02248             for (m = 0; m < nx; m++)
02249                 iperr[m] = iff[m] <= 0.0 ? 
02250                     0.0 : ierr[m] / iff[m] * 0.5 / sqrt (nscience / 2);
02251 
02252             if (nscience / 2 > 1) {
02253                 float * weights;
02254                 float   max, sum, sum2, imean;
02255 
02256                 int k;
02257 
02258                 /* QC on U NULL */
02259                 weights = cpl_malloc(sizeof(float) * nx);
02260 
02261                 max = 0.0;
02262                 for (k = 0; k < nx; k++) {
02263                     if (max < iff[k]) max = iff[k];
02264                 }
02265             
02266                 for (k = 0; k < nx; k++) {
02267                     weights[k] = iff[k] < 0.0 ? 
02268                         0.0 : iff[k] * iff[k] / (max * max);
02269                 }
02270             
02271                 sum  = 0.0;
02272                 sum2 = 0.0;
02273                 for (k = 0; k < nx; k++) {
02274                     sum  += weights[k] * ip_vnull[k];
02275                     sum2 += weights[k];
02276                 }
02277 
02278                 cpl_free(weights);
02279 
02280                 imean = sum / sum2;
02281 
02282                 mean_vnull += (imean - mean_vnull) / (j + 1.0);
02283             }
02284         }
02285 
02286         if (dfs_save_image(frameset, pv_im, reduced_v_tag, header, 
02287                            parlist, recipe, version))
02288             fors_pmos_science_exit(NULL);
02289 
02290         if (dfs_save_image(frameset, pi_im, reduced_i_tag, header, 
02291                            parlist, recipe, version))
02292             fors_pmos_science_exit(NULL);
02293 
02294         if (nscience / 2 > 1) {
02295             char             *pipefile;
02296             char             *keyname;
02297             cpl_propertylist *qheader;
02298 
02299             qheader = dfs_load_header(frameset, science_tag, 0);
02300             cpl_propertylist_update_double(qheader, "CRPIX1", 1.0);
02301             cpl_propertylist_update_double(qheader, "CRPIX2", 1.0);
02302             cpl_propertylist_update_double(qheader, "CRVAL1", 
02303                                    startwavelength + (dispersion * group)/2);
02304             cpl_propertylist_update_double(qheader, "CRVAL2", 1.0);
02305             cpl_propertylist_update_double(qheader, "CD1_1", 
02306                                            dispersion * group);
02307             cpl_propertylist_update_double(qheader, "CD1_2", 0.0);
02308             cpl_propertylist_update_double(qheader, "CD2_1", 0.0);
02309             cpl_propertylist_update_double(qheader, "CD2_2", 1.0);
02310             cpl_propertylist_update_string(qheader, "CTYPE1", "LINEAR");
02311             cpl_propertylist_update_string(qheader, "CTYPE2", "PIXEL");
02312 
02313             if (qc) {
02314                 fors_qc_start_group(qheader, "2.0", instrume);
02315 
02316                 /*
02317                  * QC1 group header
02318                  */
02319 
02320                 if (fors_qc_write_string("PRO.CATG", reduced_nul_v_tag,
02321                                          "Product category", instrume))
02322                     fors_pmos_science_exit("Cannot write product category to "
02323                                            "QC log file");
02324 
02325                 if (fors_qc_keyword_to_paf(qheader, "ESO DPR TYPE", NULL,
02326                                            "DPR type", instrume))
02327                     fors_pmos_science_exit("Missing keyword DPR TYPE in "
02328                                            "scientific frame header");
02329     
02330                 if (fors_qc_keyword_to_paf(qheader, "ESO TPL ID", NULL,
02331                                            "Template", instrume))
02332                     fors_pmos_science_exit("Missing keyword TPL ID in "
02333                                            "scientific frame header");
02334     
02335                 if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 NAME", NULL,
02336                                            "Grism name", instrume))
02337                     fors_pmos_science_exit("Missing keyword INS GRIS1 NAME in "
02338                                            "scientific frame header");
02339 
02340                 if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 ID", NULL,
02341                                            "Grism identifier", instrume))
02342                     fors_pmos_science_exit("Missing keyword INS GRIS1 ID in "
02343                                            "scientific frame header");
02344 
02345                 if (cpl_propertylist_has(qheader, "ESO INS FILT1 NAME"))
02346                     fors_qc_keyword_to_paf(qheader, "ESO INS FILT1 NAME", NULL,
02347                                            "Filter name", instrume);
02348 
02349                 if (fors_qc_keyword_to_paf(qheader, "ESO INS COLL NAME", NULL,
02350                                            "Collimator name", instrume))
02351                     fors_pmos_science_exit("Missing keyword INS COLL NAME in "
02352                                            "scientific frame header");
02353 
02354                 if (fors_qc_keyword_to_paf(qheader, "ESO DET CHIP1 ID", NULL,
02355                                            "Chip identifier", instrume))
02356                     fors_pmos_science_exit("Missing keyword DET CHIP1 ID in "
02357                                            "scientific frame header");
02358 
02359                 if (fors_qc_keyword_to_paf(qheader, "ARCFILE", NULL,
02360                                            "Archive name of input data", 
02361                                            instrume))
02362                     fors_pmos_science_exit("Missing keyword ARCFILE in "
02363                                            "scientific frame header");
02364 
02365                 pipefile = dfs_generate_filename_tfits(reduced_nul_v_tag);
02366                 if (fors_qc_write_string("PIPEFILE", pipefile,
02367                                          "Pipeline product name", instrume))
02368                     fors_pmos_science_exit("Cannot write PIPEFILE to "
02369                                            "QC log file");
02370                 cpl_free(pipefile); pipefile = NULL;
02371 
02372 
02373                 /*
02374                  * QC1 parameters
02375                  */
02376 
02377                 keyname = "QC.NULL.V.MEAN";
02378                     
02379                 if (fors_qc_write_qc_double(qheader, mean_vnull,
02380                                             keyname, NULL,
02381                                             "Mean V null parameter",
02382                                             instrume)) {
02383                     fors_pmos_science_exit("Cannot write mean Q null "
02384                                            "parameter to QC log file.");
02385                 }
02386 
02387                 keyname = "QC.NANGLES";
02388 
02389                 if (fors_qc_write_qc_int(qheader, nscience,
02390                                          keyname, NULL,
02391                                          "Number of processed plate angles",
02392                                          instrume)) {
02393                     fors_pmos_science_exit("Cannot write number of processed "
02394                                            "plate angles.");
02395                 }
02396 
02397                 fors_qc_end_group();
02398             }
02399 
02400             if (dfs_save_image(frameset, pvnull_im, reduced_nul_v_tag, qheader, 
02401                                parlist, recipe, version))
02402                 fors_pmos_science_exit(NULL);
02403 
02404             cpl_propertylist_delete(qheader);
02405         }
02406 
02407         if (dfs_save_image(frameset, perr_im, reduced_error_v_tag, header, 
02408                            parlist, recipe, version))
02409             fors_pmos_science_exit(NULL);
02410 
02411         cpl_image_power(pierr_im, 0.5);
02412 
02413         if (dfs_save_image(frameset, pierr_im, reduced_error_i_tag, header, 
02414                            parlist, recipe, version))
02415             fors_pmos_science_exit(NULL);
02416 
02417         cpl_image_delete(pv_im);
02418         cpl_image_delete(pvnull_im);
02419         cpl_image_delete(perr_im);
02420         cpl_image_delete(pi_im);
02421         cpl_image_delete(pierr_im);
02422     } 
02423     else {                            /* Linear polarisation */
02424         cpl_image *pq_im      = NULL;
02425         cpl_image *pu_im      = NULL;
02426         cpl_image *pl_im      = NULL;
02427         cpl_image *pi_im      = NULL;
02428 
02429         cpl_image *pqnull_im  = NULL;
02430         cpl_image *punull_im  = NULL;
02431 
02432         cpl_image *pqerr_im   = NULL;
02433         cpl_image *puerr_im   = NULL;
02434         cpl_image *plerr_im   = NULL;
02435         cpl_image *pierr_im   = NULL;
02436 
02437         cpl_image *pang_im    = NULL;
02438         cpl_image *pangerr_im = NULL;
02439 
02440         double    *p_q        = NULL;
02441         double    *p_u        = NULL;
02442         double    *p_l        = NULL;
02443         double    *p_i        = NULL;
02444 
02445         double    *p_qnull    = NULL;
02446         double    *p_unull    = NULL;
02447 
02448         double    *pqerr      = NULL;
02449         double    *puerr      = NULL;
02450         double    *plerr      = NULL;
02451         double    *pierr      = NULL;
02452 
02453         double    *pang       = NULL;
02454         double    *pangerr    = NULL;
02455 
02456         int        k, m;
02457 
02458         cpl_image *correct_im = cpl_image_new(nx, 1, CPL_TYPE_DOUBLE);
02459         double    *correct    = cpl_image_get_data_double(correct_im);
02460 
02461         double     mean_unull, mean_qnull;
02462 
02463         int        p          = -1;
02464         int        total      = 0;
02465             
02466         pq_im      = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02467         pu_im      = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02468         pl_im      = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02469         pi_im      = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02470 
02471         pqerr_im   = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02472         puerr_im   = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02473         plerr_im   = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02474         pierr_im   = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02475 
02476         pang_im    = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02477         pangerr_im = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02478 
02479         p_q        = cpl_image_get_data_double(pq_im);
02480         p_u        = cpl_image_get_data_double(pu_im);
02481         p_l        = cpl_image_get_data_double(pl_im);
02482         p_i        = cpl_image_get_data_double(pi_im);
02483 
02484         if (nscience / 4 > 1) {
02485             pqnull_im = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02486             punull_im = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
02487 
02488             p_qnull = cpl_image_get_data_double(pqnull_im);
02489             p_unull = cpl_image_get_data_double(punull_im);
02490         } else {
02491             cpl_msg_warning(cpl_func, 
02492                             "Not enough pairs to compute null parameters");
02493         }
02494 
02495         pqerr = cpl_image_get_data_double(pqerr_im);
02496         puerr = cpl_image_get_data_double(puerr_im);
02497         plerr = cpl_image_get_data_double(plerr_im);
02498         pierr = cpl_image_get_data_double(pierr_im);
02499 
02500         pang = cpl_image_get_data_double(pang_im);
02501         pangerr = cpl_image_get_data_double(pangerr_im);
02502 
02503         if (chromatism) {
02504             cpl_table * chrotbl = 
02505                 dfs_load_table(frameset, chrom_table_tag, 1);
02506 
02507             int      nrow   = cpl_table_get_nrow(chrotbl);
02508             float  * lambda = cpl_table_get_data_float(chrotbl, "lambda");
02509             float  * theta  = cpl_table_get_data_float(chrotbl, "eps_theta");
02510 
02511             for (j = 0; j < nx; j++) {
02512                 double c_wave = startwavelength 
02513                               + (dispersion * group) / 2 
02514                               + j * dispersion * group;
02515             
02516                 int found = 0;
02517 
02518                 for (k = 0; k < nrow - 1; k++) {
02519                     if (lambda[k] <= c_wave && c_wave < lambda[k + 1]) {
02520                         found = 1;
02521                         break;
02522                     }
02523                 }
02524 
02525                 if (found) {
02526                     correct[j] = (theta [k + 1] - theta [k]) /
02527                                  (lambda[k + 1] - lambda[k]) *
02528                                  (c_wave        - lambda[k])   + theta[k];
02529                     correct[j] *= M_PI / 180;   /* Radians */
02530                 }
02531                 else if (j)
02532                     correct[j] = correct[j-1];
02533                 else
02534                     correct[j] = 0.0;
02535             }
02536 
02537             cpl_table_delete(chrotbl);
02538         }
02539 
02540         for (j = 0; j < nobjects; j++) {
02541             double *ip_q;
02542             double *ip_u;
02543             double *ip_l;
02544             double *ip_i;
02545             double *ipierr;
02546             double *ip_qnull;
02547             double *ip_unull;
02548             double *ipqerr;
02549             double *ipuerr;
02550             double *iplerr;
02551             double *ipang;
02552             double *ipangerr;
02553 
02554             float  *data;
02555             float  *iffq;
02556             float  *ierrq;
02557             float  *iffu;
02558             float  *ierru;
02559 
02560             int pos, pos_d;
02561 
02562             ip_q = p_q + (nobjects - 1 - j) * nx;
02563             ip_u = p_u + (nobjects - 1 - j) * nx;
02564             ip_l = p_l + (nobjects - 1 - j) * nx;
02565             ip_i = p_i + (nobjects - 1 - j) * nx;
02566 
02567             if (nscience / 4 > 1) {
02568                 ip_qnull = p_qnull + (nobjects - 1 - j) * nx;
02569                 ip_unull = p_unull + (nobjects - 1 - j) * nx;
02570             }
02571 
02572             ipqerr = pqerr + (nobjects - 1 - j) * nx;
02573             ipuerr = puerr + (nobjects - 1 - j) * nx;
02574             iplerr = plerr + (nobjects - 1 - j) * nx;
02575             ipierr = pierr + (nobjects - 1 - j) * nx;
02576 
02577             ipang = pang + (nobjects - 1 - j) * nx;
02578             ipangerr = pangerr + (nobjects - 1 - j) * nx;
02579 
02580             total = 0;
02581             for (i = 0; i < nslits; i += 2) {
02582                 total += nobjs_per_slit[i];
02583                 if (total > j) {
02584                     p = i;
02585                     break;
02586                 }
02587             }
02588 
02589             for (k = 0; k < nscience / 4; k++) {
02590                 float * if_o, * if_e,  * ifdelta_o, * ifdelta_e;
02591                 float * if_o_err, * if_e_err,  * ifdelta_o_err, * ifdelta_e_err;
02592 
02593                 /* First P_Q */
02594 
02595                 pos   = fors_find_angle_pos(angles, nscience, 90 * k);
02596                 pos_d = fors_find_angle_pos(angles, nscience, 90 * k + 45);
02597 
02598                 data = cpl_image_get_data_float(reduceds[pos]);
02599 
02600                 if_o = data + (2 * (nobjects - total) + nobjs_per_slit[p] 
02601                                + (total - j - 1)) * nx;
02602 
02603                 if_e = data + (2 * (nobjects - total) 
02604                                + (total - j - 1)) * nx;
02605 
02606                 data = cpl_image_get_data_float(reduceds[pos_d]);
02607 
02608                 ifdelta_o = data + (2 * (nobjects - total) + nobjs_per_slit[p] 
02609                                + (total - j - 1)) * nx;
02610 
02611                 ifdelta_e = data + (2 * (nobjects - total) 
02612                                + (total - j - 1)) * nx;
02613 
02614                 data = cpl_image_get_data_float(rerrors[pos]);
02615 
02616                 if_o_err = data + (2 * (nobjects - total) + nobjs_per_slit[p]
02617                                + (total - j - 1)) * nx;
02618 
02619                 if_e_err = data + (2 * (nobjects - total)
02620                                + (total - j - 1)) * nx;
02621 
02622                 data = cpl_image_get_data_float(rerrors[pos_d]);
02623 
02624                 ifdelta_o_err = data + (2 * (nobjects - total) 
02625                               + nobjs_per_slit[p] + (total - j - 1)) * nx;
02626 
02627                 ifdelta_e_err = data + (2 * (nobjects - total)
02628                               + (total - j - 1)) * nx;
02629 
02630                 for (m = 0; m < nx; m++) {
02631 
02632                     double quantity = fabs(if_o[m] + if_e[m]) < FLT_MIN ? 0.0 :
02633                         (if_o[m]      - if_e[m]     ) /
02634                         (if_o[m]      + if_e[m]     ) -
02635                         (ifdelta_o[m] - ifdelta_e[m]) /
02636                         (ifdelta_o[m] + ifdelta_e[m]);
02637 
02638                     quantity = isfinite(quantity) ? quantity : 0.0;
02639 
02640                     /* PQ map computation */
02641                     ip_q[m] += quantity * 0.5 / (nscience / 4);
02642 
02643                     /* PQnull map computation */
02644                     if (nscience / 4 > 1) {
02645                         if (k % 2)
02646                             ip_qnull[m] += quantity * 0.5 / (nscience / 4);
02647                         else
02648                             ip_qnull[m] -= quantity * 0.5 / (nscience / 4);
02649                     }
02650 
02651                     /* I map computation */
02652                     ip_i[m] += (if_o[m] + if_e[m] +
02653                                 ifdelta_o[m] + ifdelta_e[m]) / nscience;
02654 
02655                     /* Variance map computation */
02656                     ipierr[m] += (if_o_err[m]      * if_o_err[m]
02657                                 + if_e_err[m]      * if_e_err[m]
02658                                 + ifdelta_o_err[m] * ifdelta_o_err[m]
02659                                 + ifdelta_e_err[m] * ifdelta_e_err[m]) 
02660                                / nscience / nscience;
02661                 }
02662 
02663                 /* Now P_U */
02664 
02665                 pos   = fors_find_angle_pos(angles, nscience, 90 * k + 22.5);
02666                 pos_d = fors_find_angle_pos(angles, nscience, 90 * k + 67.5);
02667 
02668                 data = cpl_image_get_data_float(reduceds[pos]);
02669 
02670                 if_o = data + (2 * (nobjects - total) + nobjs_per_slit[p] 
02671                                + (total - j - 1)) * nx;
02672 
02673                 if_e = data + (2 * (nobjects - total) 
02674                                + (total - j - 1)) * nx;
02675 
02676                 data = cpl_image_get_data_float(reduceds[pos_d]);
02677 
02678                 ifdelta_o = data + (2 * (nobjects - total) + nobjs_per_slit[p] 
02679                                + (total - j - 1)) * nx;
02680 
02681                 ifdelta_e = data + (2 * (nobjects - total) 
02682                                + (total - j - 1)) * nx;
02683 
02684                 data = cpl_image_get_data_float(rerrors[pos]);
02685 
02686                 if_o_err = data + (2 * (nobjects - total) + nobjs_per_slit[p]
02687                                + (total - j - 1)) * nx;
02688 
02689                 if_e_err = data + (2 * (nobjects - total)
02690                                + (total - j - 1)) * nx;
02691 
02692                 data = cpl_image_get_data_float(rerrors[pos_d]);
02693 
02694                 ifdelta_o_err = data + (2 * (nobjects - total)
02695                               + nobjs_per_slit[p] + (total - j - 1)) * nx;
02696 
02697                 ifdelta_e_err = data + (2 * (nobjects - total)
02698                               + (total - j - 1)) * nx;
02699 
02700                 for (m = 0; m < nx; m++) {
02701 
02702                     double quantity = fabs(if_o[m] + if_e[m]) < FLT_MIN ? 0.0 :
02703                         (if_o[m]      - if_e[m]     ) /
02704                         (if_o[m]      + if_e[m]     ) -
02705                         (ifdelta_o[m] - ifdelta_e[m]) /
02706                         (ifdelta_o[m] + ifdelta_e[m]);
02707 
02708                     quantity = isfinite(quantity) ? quantity : 0.0;
02709 
02710                     /* PU map computation */
02711                     ip_u[m] += quantity * 0.5 / (nscience / 4);
02712 
02713                     /* PUnull map computation */
02714                     if (nscience / 4 > 1) {
02715                         if (k % 2)
02716                             ip_unull[m] += quantity * 0.5 / (nscience / 4);
02717                         else
02718                             ip_unull[m] -= quantity * 0.5 / (nscience / 4);
02719                     }
02720 
02721                     /* I map computation */
02722                     ip_i[m] += (if_o[m] + if_e[m] +
02723                                 ifdelta_o[m] + ifdelta_e[m]) / nscience;
02724 
02725                     /* Variance map computation */
02726                     ipierr[m] += (if_o_err[m]      * if_o_err[m]
02727                                 + if_e_err[m]      * if_e_err[m]
02728                                 + ifdelta_o_err[m] * ifdelta_o_err[m]
02729                                 + ifdelta_e_err[m] * ifdelta_e_err[m]) 
02730                                / nscience / nscience;
02731                 }
02732             }
02733 
02734             /* Error map */
02735 
02736             pos   = fors_find_angle_pos(angles, nscience, 0.0);
02737 
02738             data = cpl_image_get_data_float(reduceds[pos]);
02739             iffq = data + (2 * (nobjects - total) + (total - j - 1)) * nx;
02740 
02741             data = cpl_image_get_data_float(rerrors[pos]);
02742             ierrq = data + (2 * (nobjects - total) + (total - j - 1)) * nx;
02743             
02744             pos   = fors_find_angle_pos(angles, nscience, 22.5);
02745 
02746             data = cpl_image_get_data_float(reduceds[pos]);
02747             iffu = data + (2 * (nobjects - total) + (total - j - 1)) * nx;
02748 
02749             data = cpl_image_get_data_float(rerrors[pos]);
02750             ierru = data + (2 * (nobjects - total) + (total - j - 1)) * nx;
02751 
02752             for (m = 0; m < nx; m++) {
02753 
02754                 double radicand; 
02755 
02756                 ipqerr[m] = iffq[m] <= 0.0 ? 
02757                     0.0 : ierrq[m] / iffq[m] * 0.5 / sqrt (nscience / 4);
02758 
02759                 ipuerr[m] = iffu[m] <= 0.0 ? 
02760                     0.0 : ierru[m] / iffu[m] * 0.5 / sqrt (nscience / 4);
02761 
02762                 iplerr[m] = 0.5 * (ipqerr[m] + ipuerr[m]);
02763 
02764                 /* PL computation */
02765                 ip_l[m] = sqrt(ip_u[m] * ip_u[m] + ip_q[m] * ip_q[m]);
02766 
02767                 /* P angle computation */
02768                 if (fabs(ip_q[m]) < 0.00001) {
02769                     if (ip_u[m] > 0.0) {
02770                         ipang[m] = 45.0;
02771                     }
02772                     else {
02773                         ipang[m] = 135.0;
02774                     }
02775                 }
02776                 else {
02777                     ipang[m] = 0.5 * atan(ip_u[m] / ip_q[m]) * 180 / M_PI;
02778                     if (ip_q[m] > 0.0) {
02779                         if (ip_u[m] < 0.0) {
02780                             ipang[m] += 180.;
02781                         }
02782                     }
02783                     else {
02784                         ipang[m] += 90.;
02785                     }
02786                 }
02787 
02788                 /* Error on the angle computation */
02789                 radicand = ip_q[m] * ip_q[m] * ipuerr[m] * ipuerr[m] + 
02790                            ip_u[m] * ip_u[m] * ipqerr[m] * ipqerr[m];
02791   
02792                 ipangerr[m] = (ip_l[m] == 0.0 ? 0.0 :
02793                      sqrt(radicand) * 0.5 / (ip_l[m] * ip_l[m]) * 180 / M_PI);
02794 
02795                 /*
02796                  * This is a quick and dirty patch for FORS2 had the
02797                  * Wolly mounted +180 with respect to FORS1. I must
02798                  * hardcode it, because there is no such info in the 
02799                  * header.
02800                  */
02801 
02802                 if (instrume[4] == '2') {
02803 
02804                     double w_rotation = - wollaston * M_PI / 2;
02805 
02806                     ipang[m] -= w_rotation * 180 / M_PI;
02807 
02808                     ip_q[m] = ip_q[m] * cos(2 * w_rotation)
02809                             + ip_u[m] * sin(2 * w_rotation);
02810 
02811                     ip_u[m] = ip_u[m] * cos(2 * w_rotation)
02812                             - ip_q[m] * sin(2 * w_rotation);
02813                 }
02814 
02815                 if (chromatism) {
02816                     ipang[m] -= correct[m] * 180 / M_PI;
02817 
02818                     ip_q[m] = ip_q[m] * cos(2 * correct[m])
02819                             + ip_u[m] * sin(2 * correct[m]);
02820     
02821                     ip_u[m] = ip_u[m] * cos(2 * correct[m])
02822                             - ip_q[m] * sin(2 * correct[m]);
02823                 }
02824 
02825                 if (ipang[m] < 0.0)
02826                     ipang[m] += 180.;
02827                 else if (ipang[m] >= 180.0)
02828                     ipang[m] -= 180.;
02829             }
02830 
02831             if (nscience / 4 > 1) {
02832                 float * weights;
02833                 float   max, sum, sum2, imean;
02834 
02835                 int k;
02836 
02837                 /* QC on Q NULL */
02838                 weights = cpl_malloc(sizeof(float) * nx);
02839 
02840                 max = 0.0;
02841                 for (k = 0; k < nx; k++) {
02842                     if (max < iffq[k]) max = iffq[k];
02843                 }
02844             
02845                 for (k = 0; k < nx; k++) {
02846                     weights[k] = iffq[k] < 0.0 ? 
02847                         0.0 : iffq[k] * iffq[k] / (max * max);
02848                 }
02849             
02850                 sum  = 0.0;
02851                 sum2 = 0.0;
02852                 for (k = 0; k < nx; k++) {
02853                     sum  += weights[k] * ip_qnull[k];
02854                     sum2 += weights[k];
02855                 }
02856 
02857                 cpl_free(weights);
02858 
02859                 imean = sum / sum2;
02860 
02861                 mean_qnull += (imean - mean_qnull) / (j + 1.0);
02862                   
02863                 /* QC on U NULL */
02864                 weights = cpl_malloc(sizeof(float) * nx);
02865             
02866                 max = 0.0;
02867                 for (k = 0; k < nx; k++) {
02868                     if (max < iffu[k]) max = iffu[k];
02869                 }
02870             
02871                 for (k = 0; k < nx; k++) {
02872                     weights[k] = iffu[k] < 0.0 ? 
02873                         0.0 : iffu[k] * iffu[k] / (max * max);
02874                 }
02875             
02876                 sum  = 0.0;
02877                 sum2 = 0.0;
02878                 for (k = 0; k < nx; k++) {
02879                     sum  += weights[k] * ip_unull[k];
02880                     sum2 += weights[k];
02881                 }
02882 
02883                 cpl_free(weights);
02884 
02885                 imean = sum / sum2;
02886 
02887                 mean_unull += (imean - mean_unull) / (j + 1.0);
02888             }
02889         }
02890 
02891         cpl_image_delete(correct_im);
02892 
02893         if (dfs_save_image(frameset, pq_im, reduced_q_tag, header, 
02894                            parlist, recipe, version))
02895             fors_pmos_science_exit(NULL);
02896 
02897         if (dfs_save_image(frameset, pu_im, reduced_u_tag, header, 
02898                            parlist, recipe, version))
02899             fors_pmos_science_exit(NULL);
02900 
02901         if (qc && standard) {
02902             cpl_table *polsta = dfs_load_table(frameset, std_pmos_table_tag, 1);
02903             cpl_propertylist *qheader = dfs_load_header(frameset,
02904                                                         science_tag, 0);
02905             cpl_propertylist_update_double(qheader, "CRPIX1", 1.0);
02906             cpl_propertylist_update_double(qheader, "CRPIX2", 1.0);
02907             cpl_propertylist_update_double(qheader, "CRVAL1",
02908                                    startwavelength + (dispersion * group)/2);
02909             cpl_propertylist_update_double(qheader, "CRVAL2", 1.0);
02910             cpl_propertylist_update_double(qheader, "CD1_1",
02911                                            dispersion * group);
02912             cpl_propertylist_update_double(qheader, "CD1_2", 0.0);
02913             cpl_propertylist_update_double(qheader, "CD2_1", 0.0);
02914             cpl_propertylist_update_double(qheader, "CD2_2", 1.0);
02915             cpl_propertylist_update_string(qheader, "CTYPE1", "LINEAR");
02916             cpl_propertylist_update_string(qheader, "CTYPE2", "PIXEL");
02917 
02918             if (mos_check_polarisation(pq_im, pqerr_im, pu_im, puerr_im,
02919                                        startwavelength, dispersion, 1000.,
02920                                        polsta, ra, dec, &filter,
02921                                        &polarised,
02922                                        &qc_pl, &qc_pl_err, 
02923                                        &qc_angle, &qc_angle_err)) {
02924                 cpl_msg_warning(cpl_func, "No QC can be computed");
02925             }
02926             else {
02927                 char *pipefile;
02928                 char *keyname;
02929                 char *text;
02930                 char  band[] = {' ', '\0'};
02931 
02932                 fors_qc_start_group(qheader, "2.0", instrume);
02933 
02934                 /*
02935                  * QC1 group header
02936                  */
02937 
02938                 if (fors_qc_write_string("PRO.CATG", reduced_l_tag,
02939                                          "Product category", instrume))
02940                     fors_pmos_science_exit("Cannot write product category to "
02941                                            "QC log file");
02942 
02943                 if (fors_qc_keyword_to_paf(qheader, "ESO DPR TYPE", NULL,
02944                                            "DPR type", instrume))
02945                     fors_pmos_science_exit("Missing keyword DPR TYPE in "
02946                                            "scientific frame header");
02947 
02948                 if (fors_qc_keyword_to_paf(qheader, "ESO TPL ID", NULL,
02949                                            "Template", instrume))
02950                     fors_pmos_science_exit("Missing keyword TPL ID in "
02951                                            "scientific frame header");
02952 
02953                 if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 NAME", NULL,
02954                                            "Grism name", instrume))
02955                     fors_pmos_science_exit("Missing keyword INS GRIS1 NAME in "
02956                                            "scientific frame header");
02957 
02958                 if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 ID", NULL,
02959                                            "Grism identifier", instrume))
02960                     fors_pmos_science_exit("Missing keyword INS GRIS1 ID in "
02961                                            "scientific frame header");
02962 
02963                 if (cpl_propertylist_has(qheader, "ESO INS FILT1 NAME"))
02964                     fors_qc_keyword_to_paf(qheader, "ESO INS FILT1 NAME", NULL,
02965                                            "Filter name", instrume);
02966 
02967                 if (fors_qc_keyword_to_paf(qheader, "ESO INS COLL NAME", NULL,
02968                                            "Collimator name", instrume))
02969                     fors_pmos_science_exit("Missing keyword INS COLL NAME in "
02970                                            "scientific frame header");
02971 
02972                 if (fors_qc_keyword_to_paf(qheader, "ESO DET CHIP1 ID", NULL,
02973                                            "Chip identifier", instrume))
02974                     fors_pmos_science_exit("Missing keyword DET CHIP1 ID in "
02975                                            "scientific frame header");
02976 
02977                 if (fors_qc_keyword_to_paf(qheader, "ARCFILE", NULL,
02978                                            "Archive name of input data",
02979                                            instrume))
02980                     fors_pmos_science_exit("Missing keyword ARCFILE in "
02981                                            "scientific frame header");
02982 
02983                 pipefile = dfs_generate_filename_tfits(reduced_nul_q_tag);
02984                 if (fors_qc_write_string("PIPEFILE", pipefile,
02985                                          "Pipeline product name", instrume))
02986                     fors_pmos_science_exit("Cannot write PIPEFILE to "
02987                                            "QC log file");
02988                 cpl_free(pipefile); pipefile = NULL;
02989 
02990 
02991                 /*
02992                  * QC1 parameters
02993                  */
02994 
02995                 keyname = "QC.PMOS.BAND";
02996 
02997                 band[0] = filter;
02998                 if (fors_qc_write_qc_string(qheader, keyname, band,
02999                                             "Band where polarisation was "
03000                                             "measured", instrume)) {
03001                     fors_pmos_science_exit("Cannot write QC.PMOS.BAND "
03002                                            "parameter to QC log file");
03003                 }
03004 
03005                 keyname = "QC.PMOS.POLARISED";
03006 
03007                 if (fors_qc_write_qc_int(qheader, polarised, keyname, NULL,
03008                                          "Polarisation is expected (1 = yes, "
03009                                          "0 = no)", instrume)) {
03010                     fors_pmos_science_exit("Cannot write QC.PMOS.POLARISED "
03011                                            "parameter to QC log file");
03012                 }
03013 
03014                 keyname = "QC.PMOS.L.OFFSET";
03015 
03016                 if (polarised)
03017                     text = "Linear polarisation relative offset";
03018                 else
03019                     text = "Linear polarisation offset";
03020 
03021                 if (fors_qc_write_qc_double(qheader, qc_pl, keyname, NULL,
03022                                             text, instrume)) {
03023                     fors_pmos_science_exit("Cannot write linear polarisation "
03024                                            "offset to QC log file");
03025                 }
03026 
03027                 keyname = "QC.PMOS.L.OFFSETERR";
03028 
03029                 if (fors_qc_write_qc_double(qheader, qc_pl_err, keyname, NULL,
03030                                        "Error on linear polarisation offset",
03031                                        instrume)) {
03032                     fors_pmos_science_exit("Cannot write linear polarisation "
03033                                        "offset error to QC log file");
03034                 }
03035 
03036                 if (polarised) {
03037                     keyname = "QC.PMOS.ANGLE.OFFSET";
03038 
03039                     if (fors_qc_write_qc_double(qheader, qc_angle, keyname, NULL,
03040                                                 "Polarisation angle offset",
03041                                                 instrume)) {
03042                         fors_pmos_science_exit("Cannot write polarisation "
03043                                                "angle offset to QC log file");
03044                     }
03045 
03046                     keyname = "QC.PMOS.ANGLE.OFFSETERR";
03047 
03048                     if (fors_qc_write_qc_double(qheader, qc_angle_err, keyname, 
03049                                                 NULL, "Error on polarisation "
03050                                                 "angle offset", instrume)) {
03051                         fors_pmos_science_exit("Cannot write polarisation "
03052                                                "angle offset error to QC "
03053                                                "log file");
03054                     }
03055                 }
03056 
03057                 fors_qc_end_group();
03058             }
03059 
03060             if (dfs_save_image(frameset, pl_im, reduced_l_tag, qheader,
03061                                parlist, recipe, version))
03062                 fors_pmos_science_exit(NULL);
03063 
03064             cpl_propertylist_delete(qheader);
03065         }
03066         else {
03067             if (dfs_save_image(frameset, pl_im, reduced_l_tag, header, 
03068                                parlist, recipe, version))
03069                 fors_pmos_science_exit(NULL);
03070         }
03071 
03072         if (nscience / 4 > 1) {
03073             char *pipefile; 
03074             char *keyname;
03075             cpl_propertylist *qheader = dfs_load_header(frameset, 
03076                                                         science_tag, 0);
03077 
03078             cpl_propertylist_update_double(qheader, "CRPIX1", 1.0);
03079             cpl_propertylist_update_double(qheader, "CRPIX2", 1.0);
03080             cpl_propertylist_update_double(qheader, "CRVAL1", 
03081                                    startwavelength + (dispersion * group)/2);
03082             cpl_propertylist_update_double(qheader, "CRVAL2", 1.0);
03083             cpl_propertylist_update_double(qheader, "CD1_1", 
03084                                            dispersion * group);
03085             cpl_propertylist_update_double(qheader, "CD1_2", 0.0);
03086             cpl_propertylist_update_double(qheader, "CD2_1", 0.0);
03087             cpl_propertylist_update_double(qheader, "CD2_2", 1.0);
03088             cpl_propertylist_update_string(qheader, "CTYPE1", "LINEAR");
03089             cpl_propertylist_update_string(qheader, "CTYPE2", "PIXEL");
03090 
03091             if (qc) {
03092                 fors_qc_start_group(qheader, "2.0", instrume);
03093 
03094                 /*
03095                  * QC1 group header
03096                  */
03097 
03098                 if (fors_qc_write_string("PRO.CATG", reduced_nul_q_tag,
03099                                          "Product category", instrume))
03100                     fors_pmos_science_exit("Cannot write product category to "
03101                                            "QC log file");
03102 
03103                 if (fors_qc_keyword_to_paf(qheader, "ESO DPR TYPE", NULL,
03104                                            "DPR type", instrume))
03105                     fors_pmos_science_exit("Missing keyword DPR TYPE in "
03106                                            "scientific frame header");
03107 
03108                 if (fors_qc_keyword_to_paf(qheader, "ESO TPL ID", NULL,
03109                                            "Template", instrume))
03110                     fors_pmos_science_exit("Missing keyword TPL ID in "
03111                                            "scientific frame header");
03112 
03113                 if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 NAME", NULL,
03114                                            "Grism name", instrume))
03115                     fors_pmos_science_exit("Missing keyword INS GRIS1 NAME in "
03116                                            "scientific frame header");
03117 
03118                 if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 ID", NULL,
03119                                            "Grism identifier", instrume))
03120                     fors_pmos_science_exit("Missing keyword INS GRIS1 ID in "
03121                                            "scientific frame header");
03122 
03123                 if (cpl_propertylist_has(qheader, "ESO INS FILT1 NAME"))
03124                     fors_qc_keyword_to_paf(qheader, "ESO INS FILT1 NAME", NULL,
03125                                            "Filter name", instrume);
03126 
03127                 if (fors_qc_keyword_to_paf(qheader, "ESO INS COLL NAME", NULL,
03128                                            "Collimator name", instrume))
03129                     fors_pmos_science_exit("Missing keyword INS COLL NAME in "
03130                                            "scientific frame header");
03131 
03132                 if (fors_qc_keyword_to_paf(qheader, "ESO DET CHIP1 ID", NULL,
03133                                            "Chip identifier", instrume))
03134                     fors_pmos_science_exit("Missing keyword DET CHIP1 ID in "
03135                                            "scientific frame header");
03136 
03137                 if (fors_qc_keyword_to_paf(qheader, "ARCFILE", NULL,
03138                                            "Archive name of input data", 
03139                                            instrume))
03140                     fors_pmos_science_exit("Missing keyword ARCFILE in "
03141                                            "scientific frame header");
03142 
03143                 pipefile = dfs_generate_filename_tfits(reduced_nul_q_tag);
03144                 if (fors_qc_write_string("PIPEFILE", pipefile,
03145                                          "Pipeline product name", instrume))
03146                     fors_pmos_science_exit("Cannot write PIPEFILE to "
03147                                            "QC log file");
03148                 cpl_free(pipefile); pipefile = NULL;
03149 
03150 
03151                 /*
03152                  * QC1 parameters
03153                  */
03154 
03155                 keyname = "QC.NULL.Q.MEAN";
03156                     
03157                 if (fors_qc_write_qc_double(qheader, mean_qnull,
03158                                             keyname, NULL,
03159                                             "Mean Q null parameter",
03160                                             instrume)) {
03161                     fors_pmos_science_exit("Cannot write mean Q null "
03162                                            "parameter to QC log file");
03163                 }
03164 
03165                 keyname = "QC.NANGLES";
03166 
03167                 if (fors_qc_write_qc_int(qheader, nscience / 2,
03168                                          keyname, NULL,
03169                                          "Number of processed plate angles",
03170                                          instrume)) {
03171                     fors_pmos_science_exit("Cannot write number of processed "
03172                                            "plate angles.");
03173                 }
03174 
03175                 fors_qc_end_group();
03176             }
03177 
03178             if (dfs_save_image(frameset, pqnull_im, reduced_nul_q_tag, qheader, 
03179                                parlist, recipe, version))
03180                 fors_pmos_science_exit(NULL);
03181 
03182             cpl_propertylist_delete(qheader);
03183 
03184             qheader = dfs_load_header(frameset, science_tag, 0);
03185 
03186             cpl_propertylist_update_double(qheader, "CRPIX1", 1.0);
03187             cpl_propertylist_update_double(qheader, "CRPIX2", 1.0);
03188             cpl_propertylist_update_double(qheader, "CRVAL1", 
03189                                    startwavelength + (dispersion * group)/2);
03190             cpl_propertylist_update_double(qheader, "CRVAL2", 1.0);
03191             cpl_propertylist_update_double(qheader, "CD1_1", 
03192                                            dispersion * group);
03193             cpl_propertylist_update_double(qheader, "CD1_2", 0.0);
03194             cpl_propertylist_update_double(qheader, "CD2_1", 0.0);
03195             cpl_propertylist_update_double(qheader, "CD2_2", 1.0);
03196             cpl_propertylist_update_string(qheader, "CTYPE1", "LINEAR");
03197             cpl_propertylist_update_string(qheader, "CTYPE2", "PIXEL");
03198 
03199             if (qc) {
03200                 fors_qc_start_group(qheader, "2.0", instrume);
03201 
03202                 /*
03203                  * QC1 group header
03204                  */
03205 
03206                 if (fors_qc_write_string("PRO.CATG", reduced_nul_u_tag,
03207                                          "Product category", instrume))
03208                     fors_pmos_science_exit("Cannot write product category to "
03209                                          "QC log file");
03210 
03211                 if (fors_qc_keyword_to_paf(qheader, "ESO DPR TYPE", NULL,
03212                                            "DPR type", instrume))
03213                     fors_pmos_science_exit("Missing keyword DPR TYPE in "
03214                                            "scientific frame header");
03215 
03216                 if (fors_qc_keyword_to_paf(qheader, "ESO TPL ID", NULL,
03217                                            "Template", instrume))
03218                     fors_pmos_science_exit("Missing keyword TPL ID in "
03219                                            "scientific frame header");
03220 
03221                 if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 NAME", NULL,
03222                                            "Grism name", instrume))
03223                     fors_pmos_science_exit("Missing keyword INS GRIS1 NAME in "
03224                                            "scientific frame header");
03225 
03226                 if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 ID", NULL,
03227                                            "Grism identifier", instrume))
03228                     fors_pmos_science_exit("Missing keyword INS GRIS1 ID in "
03229                                            "scientific frame header");
03230 
03231                 if (cpl_propertylist_has(qheader, "ESO INS FILT1 NAME"))
03232                     fors_qc_keyword_to_paf(qheader, "ESO INS FILT1 NAME", NULL,
03233                                            "Filter name", instrume);
03234 
03235                 if (fors_qc_keyword_to_paf(qheader, "ESO INS COLL NAME", NULL,
03236                                            "Collimator name", instrume))
03237                     fors_pmos_science_exit("Missing keyword INS COLL NAME in "
03238                                            "scientific frame header");
03239 
03240                 if (fors_qc_keyword_to_paf(qheader, "ESO DET CHIP1 ID", NULL,
03241                                            "Chip identifier", instrume))
03242                     fors_pmos_science_exit("Missing keyword DET CHIP1 ID in "
03243                                            "scientific frame header");
03244 
03245                 if (fors_qc_keyword_to_paf(qheader, "ARCFILE", NULL,
03246                                            "Archive name of input data", 
03247                                            instrume))
03248                     fors_pmos_science_exit("Missing keyword ARCFILE in "
03249                                            "scientific frame header");
03250 
03251                 pipefile = dfs_generate_filename_tfits(reduced_nul_u_tag);
03252                 if (fors_qc_write_string("PIPEFILE", pipefile,
03253                                          "Pipeline product name", instrume))
03254                     fors_pmos_science_exit("Cannot write PIPEFILE to "
03255                                            "QC log file");
03256                 cpl_free(pipefile); pipefile = NULL;
03257 
03258 
03259                 /*
03260                  * QC1 parameters
03261                  */
03262 
03263                 keyname = "QC.NULL.U.MEAN";
03264                     
03265                 if (fors_qc_write_qc_double(qheader, mean_unull,
03266                                             keyname, NULL,
03267                                             "Mean U null parameter",
03268                                             instrume)) {
03269                     fors_pmos_science_exit("Cannot write mean U null "
03270                                            "parameter to QC log file");
03271                 }
03272 
03273                 keyname = "QC.NANGLES";
03274 
03275                 if (fors_qc_write_qc_int(qheader, nscience / 2,
03276                                          keyname, NULL,
03277                                          "Number of processed plate angles",
03278                                          instrume)) {
03279                     fors_pmos_science_exit("Cannot write number of processed "
03280                                            "plate angles.");
03281                 }
03282 
03283                 fors_qc_end_group();
03284             }
03285 
03286             if (dfs_save_image(frameset, punull_im, reduced_nul_u_tag, qheader, 
03287                                parlist, recipe, version))
03288                 fors_pmos_science_exit(NULL);
03289 
03290             cpl_propertylist_delete(qheader);
03291         }
03292 
03293         if (dfs_save_image(frameset, pqerr_im, reduced_error_q_tag, header, 
03294                            parlist, recipe, version))
03295             fors_pmos_science_exit(NULL);
03296 
03297         if (dfs_save_image(frameset, puerr_im, reduced_error_u_tag, header, 
03298                            parlist, recipe, version))
03299             fors_pmos_science_exit(NULL);
03300 
03301         if (dfs_save_image(frameset, plerr_im, reduced_error_l_tag, header, 
03302                            parlist, recipe, version))
03303             fors_pmos_science_exit(NULL);
03304 
03305         if (dfs_save_image(frameset, pang_im, reduced_angle_tag, header, 
03306                            parlist, recipe, version))
03307             fors_pmos_science_exit(NULL);
03308 
03309         if (dfs_save_image(frameset, pangerr_im, reduced_error_angle_tag, 
03310                            header, parlist, recipe, version))
03311             fors_pmos_science_exit(NULL);
03312 
03313         if (dfs_save_image(frameset, pi_im, reduced_i_tag, 
03314                            header, parlist, recipe, version))
03315             fors_pmos_science_exit(NULL);
03316 
03317         cpl_image_power(pierr_im, 0.5);
03318 
03319         if (dfs_save_image(frameset, pierr_im, reduced_error_i_tag, 
03320                            header, parlist, recipe, version))
03321             fors_pmos_science_exit(NULL);
03322 
03323 /* %%% */
03324 
03325         cpl_image_delete(pq_im);
03326         cpl_image_delete(pu_im);
03327         cpl_image_delete(pl_im);
03328         cpl_image_delete(pi_im);
03329 
03330         cpl_image_delete(pqnull_im);
03331         cpl_image_delete(punull_im);
03332 
03333         cpl_image_delete(pqerr_im);
03334         cpl_image_delete(puerr_im);
03335         cpl_image_delete(plerr_im);
03336         cpl_image_delete(pierr_im);
03337         cpl_image_delete(pang_im);
03338         cpl_image_delete(pangerr_im);
03339     }
03340 
03341     cpl_propertylist_delete(header);
03342 
03343     /* End of Stokes computation */
03344 
03345     for (j = 0; j < nscience; j++) {
03346         cpl_image_delete(reduceds[j]);
03347         cpl_image_delete(rerrors[j]);
03348         cpl_table_delete(slitss[j]);
03349         cpl_image_delete(mappeds[j]);
03350     }
03351 
03352     cpl_free(reduceds);
03353     cpl_free(rerrors);
03354     cpl_free(slitss);
03355     cpl_free(mappeds);
03356 
03357     cpl_free(instrume); instrume = NULL;
03358 
03359     cpl_free(skylocalmaps);
03360 
03361     cpl_free(nobjs_per_slit);
03362 
03363     if (cpl_error_get_code()) {
03364         cpl_msg_error(cpl_error_get_where(), cpl_error_get_message());
03365         fors_pmos_science_exit(NULL);
03366     }
03367     else 
03368         return 0;
03369 }
03370 
03371 /*----------------------------------------------------------------------------*/
03382 /*----------------------------------------------------------------------------*/
03383 static float * fors_check_angles(cpl_frameset * frameset,
03384                                  int pmos, const char *tag, int * circ)
03385 {
03386     float     *angles  = NULL;
03387     cpl_frame *c_frame = NULL;
03388     char      *ret_id  = NULL;
03389 
03390     int i = 0;
03391 
03392     angles = cpl_malloc(sizeof(float) * pmos);
03393 
03394     for (c_frame  = cpl_frameset_find(frameset, tag);
03395          c_frame != NULL; c_frame = cpl_frameset_find(frameset, NULL)) {
03396 
03397         cpl_propertylist * header =
03398             cpl_propertylist_load(cpl_frame_get_filename(c_frame), 0);
03399         
03400         if (!ret_id) {
03401             ret_id = cpl_strdup(cpl_propertylist_get_string(header, 
03402                                                         "ESO INS OPTI4 ID"));
03403 
03404             if (ret_id[1] != '5' && ret_id[1] != '4') {
03405                 cpl_msg_error(cpl_func, 
03406                               "Unknown retarder plate id: %s", ret_id);
03407                 return NULL;
03408             }
03409         } else {
03410             char * c_ret_id = (char *)
03411                 cpl_propertylist_get_string(header, "ESO INS OPTI4 ID");
03412             if (ret_id[1] != c_ret_id[1]) {
03413                 cpl_msg_error(cpl_func, "Input frames are not from the same "
03414                               "retarder plate");
03415                 return NULL;
03416             }
03417         }
03418         
03419         if (ret_id[1] == '5') {  /* Linear polarimetry */
03420             if (cpl_propertylist_has(header, "ESO INS RETA2 ROT")) {
03421                 angles[i] = (float)floor(2*cpl_propertylist_get_double(header, 
03422                                                 "ESO INS RETA2 ROT") + 0.5)/2;
03423             }
03424             else if (cpl_propertylist_has(header, "ESO INS RETA2 POSANG")) {
03425                 if (cpl_propertylist_has(header, "ESO ADA POSANG")) {
03426                     double reta2pos = cpl_propertylist_get_double(header, 
03427                                                      "ESO INS RETA2 POSANG");
03428                     double adapos = cpl_propertylist_get_double(header, 
03429                                                      "ESO ADA POSANG");
03430                     angles[i] = (float)floor(2*(reta2pos - adapos) + 0.5)/2;
03431                 }
03432                 else {
03433                     cpl_msg_error(cpl_func, 
03434                                   "ESO ADA POSANG not found in header");
03435                     return NULL;
03436                 }
03437             }
03438             else {
03439                 cpl_msg_error(cpl_func, "Neither ESO INS RETA2 ROT nor "
03440                               "ESO INS RETA2 POSANG found in header");
03441                 return NULL;
03442             }
03443             *circ = 0;
03444         } else {                 /* Circular polarimetry */
03445             if (cpl_propertylist_has(header, "ESO INS RETA4 ROT")) {
03446                 angles[i] = (float)floor(2*cpl_propertylist_get_double(header, 
03447                                                 "ESO INS RETA4 ROT") + 0.5)/2;
03448                 //Check if it makes sense. Change in all other places
03449                 if (angles[i] < 0) 
03450                     angles[i] = angles[i] + 360;
03451             }
03452             else if (cpl_propertylist_has(header, "ESO INS RETA4 POSANG")) {
03453                 if (cpl_propertylist_has(header, "ESO ADA POSANG")) {
03454                     double reta4pos = cpl_propertylist_get_double(header, 
03455                                                      "ESO INS RETA4 POSANG");
03456                     double adapos = cpl_propertylist_get_double(header, 
03457                                                      "ESO ADA POSANG");
03458                     angles[i] = (float)floor(2*(reta4pos - adapos) + 0.5/2);
03459                 }
03460                 else {
03461                     cpl_msg_error(cpl_func, 
03462                                   "ESO ADA POSANG not found in header");
03463                     return NULL;
03464                 }
03465             }
03466             else {
03467                 cpl_msg_error(cpl_func, "Neither ESO INS RETA4 ROT nor "
03468                               "ESO INS RETA4 POSANG found in header");
03469                 return NULL;
03470             }
03471             *circ = 1;
03472         }
03473 
03474         cpl_propertylist_delete(header);
03475         i++;
03476     }
03477 
03478     cpl_free(ret_id);
03479 
03480     if (*circ) {
03481         if (pmos != 2 && pmos != 4) {
03482             cpl_msg_error(cpl_func, "Wrong angle configuration: %d angles "
03483                           "found, but either 2 or 4 are required for "
03484                           "circular polarization measurements!", pmos);
03485             return NULL;
03486         }
03487     } else {
03488         if (pmos != 4 && pmos != 8 && pmos != 16) {
03489             cpl_msg_error(cpl_func, "Wrong angle configuration: %d angles "
03490                           "found, but either 4, 8, or 16 are required for "
03491                           "linear polarization measurements!", pmos);
03492             return NULL;
03493         }
03494     }
03495     
03496     /* Check completeness */
03497 
03498     if (*circ) {
03499         for (i = 0; i < pmos; i++) {
03500             if (fors_find_angle_pos(angles, pmos, 90.0 * i - 45.0) < 0) {
03501                 const char *cangles;
03502                 switch (pmos) {
03503                 case 2: cangles  = "-45.0, 45.0"; break;
03504                 case 4: cangles  = "-45.0, 45.0, 135.0, 225.0"; break;
03505                 default: assert(0);
03506                 }  
03507 
03508                 cpl_msg_error(cpl_func, "Wrong angle configuration: missing "
03509                               "angle %.2f. All angles %s must be provided.",
03510                               angles[i], cangles);
03511                 return NULL;
03512             }
03513         }
03514     }
03515     else {
03516         for (i = 0; i < pmos; i++) {
03517             if (fors_find_angle_pos(angles, pmos, 22.5 * i) < 0) {
03518                 const char *cangles;
03519                 switch (pmos) {
03520                 case 4: cangles  = "0.0, 22.5, 45.0, 67.5"; break;
03521                 case 8: cangles  = "0.0, 22.5, 45.0, 67.5, "
03522                                    "90.0, 112.5, 135.0, 157.5"; break;
03523                 case 16: cangles = "0.0, 22.5, 45.0, 67.5, "
03524                                    "90.0, 112.5, 135.0, 157.5, "
03525                                    "180.0, 202.5, 225.0, 247.5, "
03526                                    "270.0, 292.5, 315.0, 337.5"; break;
03527                 default: assert(0);
03528                 }  
03529 
03530                 cpl_msg_error(cpl_func, "Wrong angle configuration: missing "
03531                               "angle %.2f. All angles %s must be provided.",
03532                               angles[i], cangles);
03533                 return NULL;
03534             }
03535         }
03536     }
03537 
03538     return angles;
03539 }
03540 
03541 /*----------------------------------------------------------------------------*/
03549 /*----------------------------------------------------------------------------*/
03550 static int
03551 fors_find_angle_pos(float * angles, int nangles, float angle)
03552 {
03553     int i, match = 0;
03554 
03555     for (i = 0; i < nangles; i++) {
03556         if (fabs(angles[i]         - angle) < 1.0 || 
03557             fabs(angles[i] - 360.0 - angle) < 1.0) {
03558             match = 1;
03559             break;
03560         }
03561     }
03562 
03563     return match ? i : -1;
03564 }
03565