uves_wavecal_firstsolution.c

00001 /*                                                                              *
00002  *   This file is part of the ESO UVES Pipeline                                 *
00003  *   Copyright (C) 2004,2005 European Southern Observatory                      *
00004  *                                                                              *
00005  *   This library is free software; you can redistribute it and/or modify       *
00006  *   it under the terms of the GNU General Public License as published by       *
00007  *   the Free Software Foundation; either version 2 of the License, or          *
00008  *   (at your option) any later version.                                        *
00009  *                                                                              *
00010  *   This program is distributed in the hope that it will be useful,            *
00011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of             *
00012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
00013  *   GNU General Public License for more details.                               *
00014  *                                                                              *
00015  *   You should have received a copy of the GNU General Public License          *
00016  *   along with this program; if not, write to the Free Software                *
00017  *   Foundation, 51 Franklin St, Fifth Floor, Boston, MA  02111-1307  USA       *
00018  *                                                                              */
00019 
00020 /*
00021  * $Author: amodigli $
00022  * $Date: 2007/06/06 08:17:33 $
00023  * $Revision: 1.20 $
00024  * $Name: uves-3_3_1 $
00025  * $Log: uves_wavecal_firstsolution.c,v $
00026  * Revision 1.20  2007/06/06 08:17:33  amodigli
00027  * replace tab with 4 spaces
00028  *
00029  * Revision 1.19  2007/05/25 07:05:21  jmlarsen
00030  * Decreased  warning verbosity
00031  *
00032  * Revision 1.18  2007/04/26 13:21:04  jmlarsen
00033  * Made more robust against inaccurate abs_order polynomial
00034  *
00035  * Revision 1.17  2007/04/10 07:11:56  jmlarsen
00036  * Changed interface of polynomial_regression_2d()
00037  *
00038  * Revision 1.16  2007/03/05 10:22:24  jmlarsen
00039  * Fixed bug in computation of max/min physical order number
00040  *
00041  * Revision 1.15  2007/01/15 08:58:20  jmlarsen
00042  * More robust polynomial fitting
00043  *
00044  * Revision 1.14  2007/01/10 12:40:12  jmlarsen
00045  * Removed unused parameter
00046  *
00047  * Revision 1.13  2006/12/07 08:29:58  jmlarsen
00048  * Compute correct Ynew column for FLAMES
00049  *
00050  * Revision 1.12  2006/11/24 16:24:32  jmlarsen
00051  * Added check of abs order polynomial
00052  *
00053  * Revision 1.11  2006/11/15 15:02:15  jmlarsen
00054  * Implemented const safe workarounds for CPL functions
00055  *
00056  * Revision 1.9  2006/11/15 14:04:08  jmlarsen
00057  * Removed non-const version of parameterlist_get_first/last/next which is 
00058  * already in CPL, added const-safe wrapper, unwrapper and deallocator functions
00059  *
00060  * Revision 1.8  2006/11/06 15:19:42  jmlarsen
00061  * Removed unused include directives
00062  *
00063  * Revision 1.7  2006/08/18 07:07:43  jmlarsen
00064  * Switched order of cpl_calloc arguments
00065  *
00066  * Revision 1.6  2006/07/14 12:43:47  jmlarsen
00067  * Documentation update
00068  *
00069  * Revision 1.5  2006/07/03 13:29:45  jmlarsen
00070  * Reduced max line length
00071  *
00072  * Revision 1.4  2006/03/03 13:54:11  jmlarsen
00073  * Changed syntax of check macro
00074  *
00075  * Revision 1.3  2006/02/15 13:19:15  jmlarsen
00076  * Reduced source code max. line length
00077  *
00078  * Revision 1.2  2006/02/08 09:25:05  jmlarsen
00079  * Fixed bug caused by == comparison of doubles
00080  *
00081  * Revision 1.1  2006/02/03 07:46:30  jmlarsen
00082  * Moved recipe implementations to ./uves directory
00083  *
00084  * Revision 1.27  2005/12/20 08:11:44  jmlarsen
00085  * Added CVS  entry
00086  *
00087  */
00088 /*----------------------------------------------------------------------------*/
00089 /*
00090  * @addtogroup uves_wavecal
00091  */
00092 /*----------------------------------------------------------------------------*/
00095 #ifdef HAVE_CONFIG_H
00096 #  include <config.h>
00097 #endif
00098 
00099 #include <uves_wavecal_firstsolution.h>
00100 
00101 #include <uves_utils.h>
00102 #include <uves_utils_wrappers.h>
00103 #include <uves_dump.h>
00104 #include <uves_error.h>
00105 #include <uves_msg.h>
00106 
00107 #include <irplib_access.h>
00108 #include <cpl.h>
00109 
00110 #include <math.h>
00111 
00112 static int *
00113 write_physical_order(cpl_table *linetable,
00114              const polynomial *absolute_order, 
00115                      const cpl_table *ordertable,
00116              const polynomial *order_locations,
00117              int *first_abs_order, int *last_abs_order);
00118 
00119 static double
00120 calculate_shift(const cpl_table *linetable, const cpl_table *previous, 
00121         const char *column, const char *reference_column, 
00122         double range, double step, double tolerance);
00123 
00124 static double
00125 cross_correlation(double shift, 
00126           const cpl_table *t1, const cpl_table *t2,
00127           const char *column, const char* reference_column, 
00128           int minref, int maxref, double tolerance);
00129 
00130 static polynomial *apply_shift(const cpl_table *previous, 
00131                    const double shift, const int degree, double *mse);
00132 
00133 /*----------------------------------------------------------------------------*/
00180 /*----------------------------------------------------------------------------*/
00181 polynomial *
00182 uves_wavecal_firstsolution(cpl_table *linetable,
00183                const cpl_table *guess, 
00184                polynomial **absolute_order, 
00185                            const cpl_table *ordertable,
00186                            const polynomial *order_locations,
00187                bool flames,
00188                double offset,
00189                int **relative_order, 
00190                int DEGREE, double CORREL_RANGE, double CORREL_STEP,
00191                double CORREL_TOLERANCE, double MAXERROR, 
00192                int *first_abs_order, int *last_abs_order)
00193 {
00194     polynomial *initial_dispersion = NULL;
00195     polynomial *new_absorder = NULL;
00196     const char *er_msg = NULL;
00197     double shift;
00198     double mse;
00199 
00200     /* Get physical order numbering */
00201     check( *relative_order =   write_physical_order(linetable, *absolute_order, 
00202                                                     ordertable,
00203                             order_locations,
00204                             first_abs_order,
00205                             last_abs_order),
00206        "Could not calculate absolute order numbers");
00207 
00208     /* Update the 'absolute_order' map */
00209     {
00210     int row;
00211 
00212     /* Create column for Y-location (in pixels) of order */
00213     cpl_table_new_column(linetable, "Ynew", CPL_TYPE_DOUBLE);
00214     for (row = 0; row < cpl_table_get_nrow(linetable); row++)
00215         {
00216         /* For historical reasons, the column 'Y' contains the
00217            (relative) order number while 'Ynew' contains 
00218            the y-coordinate (in pixels) of the emission line. */
00219         int order = cpl_table_get_int   (linetable, "Y", row, NULL);
00220         double x  = cpl_table_get_double(linetable, "X", row, NULL);
00221         
00222         cpl_table_set_double(
00223             linetable, "Ynew", row, 
00224             uves_polynomial_evaluate_2d(order_locations, x, order));
00225         }
00226 
00227     assure_nomsg( cpl_error_get_code() == CPL_ERROR_NONE,
00228               cpl_error_get_code() );
00229 
00230     new_absorder =
00231         uves_polynomial_regression_2d(linetable, "X", "Ynew", "Order",
00232                       NULL,              /* uncertainty of order number */
00233                       DEGREE, DEGREE,
00234                       NULL, NULL, NULL,  /* New columns */
00235                       NULL, NULL,        /* mse, chi^2 */
00236                       NULL,              /* variance pol. */
00237                       -1, -1);           /* kappa */
00238 
00239     if (cpl_error_get_code() != CPL_ERROR_NONE) /* Singular matrix, or too few points */
00240         {
00241         er_msg = uves_sprintf("%s", cpl_error_get_message());
00242         
00243         uves_error_reset();
00244         uves_msg_warning("Could not make global fit of absolute order number (%s). "
00245                  "Polynomial is not updated",
00246                  er_msg);
00247         }
00248     else
00249         {
00250         uves_polynomial_delete(absolute_order);
00251         *absolute_order = uves_polynomial_duplicate(new_absorder);
00252         }
00253 
00254     /* Calculate absolute_order wrt center of orders, but add offset to Ynew column */
00255     if (flames)
00256         {
00257         cpl_table_add_scalar(linetable, "Ynew", + offset);
00258         }
00259     }
00260 
00261     /* Sort linetable by 'Order' (ascending), then 'X' (ascending) */
00262     uves_sort_table_2(linetable, "Order", "X", false, false);
00263 
00264     /* Cross correlation of guess (linetable) and linetable */
00265     /* Step size should not be less than 2*tolerance */
00266     check( shift = calculate_shift(guess, linetable, "X", "Order", 
00267                    CORREL_RANGE, CORREL_STEP, CORREL_TOLERANCE),
00268        "Could not calculate shift of position w.r.t. guess solution");
00269 
00270     /* Apply shift to guess solution
00271      * Note that it doesn't help to simply call uves_polynomial_shift()
00272      * on the guess solution
00273      * because the requested 'DEGREE' might be different from
00274      * the degree used in the guess solution
00275      */
00276     
00277     check( initial_dispersion = apply_shift(guess, shift, DEGREE, &mse),
00278        "Could not calculate initial dispersion relation");
00279     /* This fit may fail if the input guess table has too few or badly
00280        distributed points, but there is not much to do about that */
00281 
00282     /* Check quality of initial solution */
00283     if(mse > MAXERROR*MAXERROR) 
00284     {
00285         uves_msg_warning("RMS of initial fit (%f pixels) is greater "
00286                  "than tolerance (%f pixels)", sqrt(mse), MAXERROR);
00287     }
00288     
00289   cleanup:
00290     uves_free_string_const(&er_msg);
00291     uves_polynomial_delete(&new_absorder);
00292     if (cpl_error_get_code() != CPL_ERROR_NONE)
00293     {
00294         uves_polynomial_delete(&initial_dispersion);
00295     }
00296     
00297     return initial_dispersion;
00298 }
00299 
00300 /*----------------------------------------------------------------------------*/
00315 /*----------------------------------------------------------------------------*/
00316 static polynomial *
00317 apply_shift(const cpl_table *guess, double shift, int degree, double *mse)
00318 {
00319     polynomial *result = NULL;
00320     cpl_table *t = NULL;
00321     
00322     /* Copy guess table */
00323     check( t = cpl_table_duplicate(guess),
00324        "Error duplicating table");
00325     
00326     /* Create auxillary column  Ident*Order  */
00327     check(( cpl_table_duplicate_column(t, "ident_order", t, "Ident"),
00328         cpl_table_multiply_columns(t, "ident_order", "Order")),
00329       /* ident_order = Ident * Order */
00330       "Error creating auxillary column");
00331     
00332     /* Shift x values */
00333     check( cpl_table_add_scalar(t, "X", shift), "Error shifting column 'X'");
00334 
00335     /* Fit lambda*m = f(x, m) */
00336     /* Don't use uncertainties because they might not exist in guess solution */
00337     result = uves_polynomial_regression_2d(t, "X", "Order", "ident_order", NULL,
00338                        degree, degree,
00339                        NULL, NULL, NULL,
00340                        mse, NULL,
00341                        NULL, -1, -1);
00342 
00343     /* If failed, set error to SINGULAR_MATRIX */
00344     if (cpl_error_get_code() != CPL_ERROR_NONE) /* Singular matrix or too few points */
00345     {
00346         uves_error_reset();
00347 
00348         assure( false, CPL_ERROR_SINGULAR_MATRIX,
00349             "Polynomial fitting failed");
00350     }
00351 
00352   cleanup:
00353     uves_free_table(&t);
00354     return result;
00355 }
00356 
00357 /*----------------------------------------------------------------------------*/
00380 /*----------------------------------------------------------------------------*/
00381 
00382 static double
00383 calculate_shift(const cpl_table *linetable, const cpl_table *guess, const char *column,
00384         const char *reference_column, double range, double step, double tolerance)
00385 {
00386     cpl_type t;
00387     int minorder, maxorder;
00388     int N, i;
00389     double shift, max_corr, median_corr, maxpos = 0;
00390     cpl_table *temp = NULL;
00391 
00392     assure( cpl_table_has_column(linetable, column), 
00393         CPL_ERROR_ILLEGAL_INPUT, "Table has no '%s' column", column);
00394     assure( cpl_table_has_column(guess , column), 
00395         CPL_ERROR_ILLEGAL_INPUT, "Table has no '%s' column", column);
00396     assure( cpl_table_has_column(linetable, reference_column),
00397         CPL_ERROR_ILLEGAL_INPUT, "Table has no '%s' column", reference_column);
00398     assure( cpl_table_has_column(guess , reference_column), 
00399         CPL_ERROR_ILLEGAL_INPUT, "Table has no '%s' column", reference_column);
00400     assure( range > 0, CPL_ERROR_ILLEGAL_INPUT, "Range = %f", range);
00401 
00402     t = cpl_table_get_column_type(linetable, column);
00403     assure( t == CPL_TYPE_DOUBLE, CPL_ERROR_TYPE_MISMATCH,
00404         "Column '%s' has type '%s'. Double expected", column, uves_tostring_cpl_type(t));
00405 
00406     t = cpl_table_get_column_type(guess, column);
00407     assure( t == CPL_TYPE_DOUBLE, CPL_ERROR_TYPE_MISMATCH,
00408         "Column '%s' has type '%s'. Double expected", column, uves_tostring_cpl_type(t));
00409 
00410     t = cpl_table_get_column_type(linetable, reference_column);
00411     assure( t == CPL_TYPE_INT, CPL_ERROR_TYPE_MISMATCH,
00412         "Ref. column '%s' has type '%s'. Integer expected", 
00413         reference_column, uves_tostring_cpl_type(t));
00414     
00415     t = cpl_table_get_column_type(guess, reference_column);
00416     assure( t == CPL_TYPE_INT, CPL_ERROR_TYPE_MISMATCH,
00417         "Ref. column '%s' has type '%s'. Integer expected",
00418         reference_column, uves_tostring_cpl_type(t));
00419 
00420     /* Identify common orders    */
00421     check(( minorder = 
00422         uves_max_int(cpl_table_get_column_min(guess, reference_column), 
00423              cpl_table_get_column_min(linetable, reference_column)),
00424         maxorder = 
00425         uves_min_int(cpl_table_get_column_max(guess, reference_column), 
00426              cpl_table_get_column_max(linetable, reference_column))),
00427       "Error reading column '%s'", reference_column);
00428     
00429     assure(maxorder >= minorder, CPL_ERROR_ILLEGAL_INPUT, "No common orders found");
00430     
00431     uves_msg("Min/max common absolute orders = %d - %d", minorder, maxorder);
00432     
00433     /* Find maximum of cross correlation function 
00434        for shifts in [-range ; range]
00435     */
00436 
00437     /* Count number of candidates,
00438        so we can create a table of the correct size
00439        which is used to get the median of
00440        all cross-correlation values */
00441     N = 0;
00442     for (shift = -range; shift <= range; shift += step) 
00443     {
00444         N += 1;
00445     }
00446 
00447     temp = cpl_table_new(N);
00448     cpl_table_new_column(temp, "Corr", CPL_TYPE_DOUBLE);
00449 
00450     max_corr = -1;
00451     maxpos = 0;
00452     for (shift = -range, i = 0;
00453      i < N;
00454      shift += step , i++) 
00455     {
00456         double corr;
00457         check( corr = cross_correlation(shift, linetable, guess, column, 
00458                         reference_column, minorder, maxorder, tolerance),
00459            "Error calculating spectrum cross correlation for shift = %f pixel(s)", 
00460            shift);
00461         
00462         /* Update table */
00463         check( cpl_table_set_double(temp, "Corr", i, corr),
00464            "Error updating table");
00465         
00466         uves_msg_debug("Correlation(shift=%f) = %f", shift, corr);
00467         
00468         if (corr > max_corr) 
00469         {
00470             max_corr = corr;
00471             maxpos = shift;
00472         }
00473     }
00474 
00475     /* To estimate significance,
00476        compare the detected max cross-correlation 
00477        value to "no correlation" estimated as the
00478        median of all cross-corr. values */
00479 
00480     median_corr = cpl_table_get_column_median(temp, "Corr");
00481     
00482     /* Correlation value is integer ; don't divide by zero */
00483     if (median_corr < 0.5)
00484     {
00485         median_corr = 1;
00486     }
00487 
00488     uves_msg("Estimated shift compared to guess solution is %f pixels (%.2f sigma detection)",
00489          maxpos, max_corr / median_corr);
00490 
00491     /* The correlation peak is usually 
00492        ~30 or more times the background,
00493        so warn if peak value is less than, say,
00494        10 times background. */
00495     if (max_corr / median_corr < 10)
00496     {
00497         uves_msg_warning("Cross-correlation with guess solution is "
00498                  "only %f times no correlation (usually >30). "
00499                  "Make sure that the guess solution is within ~10 pixels "
00500                  "of the real dispersion relation; otherwise the following "
00501                  "wavelength calibration is likely to fail or converge "
00502                  "to a wrong solution",
00503                  max_corr / median_corr);
00504     }
00505     
00506   cleanup:
00507     uves_free_table(&temp);
00508     return maxpos;
00509 }
00510 
00511 /*----------------------------------------------------------------------------*/
00533 /*----------------------------------------------------------------------------*/
00534 static double
00535 cross_correlation(double shift,
00536           const cpl_table *t1, const cpl_table *t2,
00537           const char *column, const char* reference_column, 
00538           int minref, int maxref, double tolerance)
00539 {
00540     double result = 0;  /* The result */
00541     int i1 = 0;         /* Pointers to table rows */
00542     int i2 = 0;
00543 
00544     /* For efficiency reasons, retrieve the pointers to the columns */
00545     const double *col1 = irplib_table_get_data_double_const(t1, column);
00546     const double *col2 = irplib_table_get_data_double_const(t2, column);
00547     const int *ref1 = irplib_table_get_data_int_const(t1, reference_column);
00548     const int *ref2 = irplib_table_get_data_int_const(t2, reference_column);
00549 
00550     int N1 = cpl_table_get_nrow(t1);
00551     int N2 = cpl_table_get_nrow(t2);
00552 
00553     assure( cpl_error_get_code() == CPL_ERROR_NONE, cpl_error_get_code(),
00554         "Error reading input table");
00555     
00556     /* Search for matching rows */
00557     while (i1 < N1 && ref1[i1] <= maxref && 
00558        i2 < N2 && ref2[i2] <= maxref) {
00559     if      (i1 < minref || ref1[i1] < ref2[i2])
00560         i1++;
00561     else if (i2 < minref || ref1[i1] > ref2[i2])
00562         i2++;
00563     else {
00564         /* Reference values match */
00565         double difference = col2[i2] - (col1[i1] + shift);
00566         
00567         if      (difference > tolerance)
00568         {
00569             i1++;
00570         }
00571         else if (difference < -tolerance)
00572         {
00573             i2++;
00574         }
00575         else {
00576         /* Matching rows found: |col2-col1-shift| <= tolerance.
00577            Update result and continue search */
00578         result += 1.0;
00579         i2++;
00580         }
00581     }
00582     }
00583 
00584 
00585   cleanup:
00586     return result;
00587 }
00588 
00589 
00590 /*----------------------------------------------------------------------------*/
00609 /*----------------------------------------------------------------------------*/
00610 static int *
00611 write_physical_order(cpl_table *linetable, const polynomial *absolute_order,
00612                      const cpl_table *ordertable,
00613              const polynomial *order_locations,
00614              int *first_abs_order, int *last_abs_order)
00615 {
00616     int *relative_order = NULL; /* Result */
00617     int *physical_order = NULL;
00618     int minorder, maxorder;
00619     int maxphysical;
00620     cpl_table *temp = NULL;
00621     const polynomial *map = NULL;
00622 
00623     double *sum = NULL;   /* Auxillary variables used to calculate the average */
00624     int      *N = NULL;
00625     
00626     int i;
00627 
00628     check( cpl_table_new_column(linetable, "Order", CPL_TYPE_INT),
00629        "Error creating column");
00630 
00631     check( cpl_table_new_column(linetable, "AbsOrder", CPL_TYPE_DOUBLE),
00632        "Error creating column");
00633     
00634     check(( minorder = cpl_table_get_column_min(ordertable, "Order"),
00635         maxorder = cpl_table_get_column_max(ordertable, "Order")),
00636       "Could not read min. and max. order numbers");
00637 
00638     assure( minorder > 0, CPL_ERROR_ILLEGAL_INPUT,
00639         "Non-positive order number (%d) in linetable", minorder);
00640     
00641     physical_order = cpl_calloc(maxorder + 1, sizeof(int));
00642     assure_mem( physical_order );
00643     
00644     /* First calculate the estimation of the
00645        absolute order number at each line position */
00646     for (i = 0; i < cpl_table_get_nrow(linetable); i++) {
00647     double x, y;
00648     double absorder;
00649     int order;
00650     
00651     order = cpl_table_get_int   (linetable, "Y", i, NULL); 
00652         /* The column 'Y' contains the (relative) order number */
00653 
00654     x     = cpl_table_get_double(linetable, "X", i, NULL);
00655 
00656     y = uves_polynomial_evaluate_2d(order_locations, x, order);
00657 
00658         absorder = uves_polynomial_evaluate_2d(absolute_order, x, y);
00659 
00660         uves_msg_debug("Order #%d: Absolute order = %f at x = %f",
00661                order, absorder, x);
00662 
00663         cpl_table_set_double(linetable, "AbsOrder", i, absorder);
00664     }
00665  
00666     {
00667         int degree = 1;
00668         int coeff1, coeff2;  /* absorder = coeff1 + coeff2 * relative_order */
00669         int order;
00670         int relorder_median;
00671         int absorder_median;
00672 
00673         check_nomsg( map = 
00674                      uves_polynomial_regression_1d(linetable,
00675                                                    "Y", "AbsOrder", NULL,
00676                                                    degree, 
00677                                                    NULL, NULL, NULL, -1));
00678         
00679         relorder_median = uves_round_double(cpl_table_get_column_median(linetable, "Y"));
00680         absorder_median = uves_round_double(uves_polynomial_evaluate_1d(map, relorder_median));
00681             
00682         if (uves_polynomial_derivative_1d(map, relorder_median) > 0) {
00683             coeff2 = 1;
00684         }
00685         else {
00686             coeff2 = -1;
00687         }
00688         
00689         coeff1 = absorder_median - coeff2 * relorder_median;
00690 
00691     uves_msg_debug("Assuming relation: abs.order = %d + (%d) * rel.order",
00692                        coeff1, coeff2);
00693         
00694         maxphysical = -1;
00695         for (order = minorder; order <= maxorder; order++) {
00696             physical_order[order] = coeff1 + coeff2 * order;
00697             
00698             assure(physical_order[order] > 0, CPL_ERROR_ILLEGAL_OUTPUT,
00699                    "Estimated physical order number is non-positive (%d)", 
00700                    physical_order[order]);
00701             
00702             if (physical_order[order] > maxphysical) 
00703                 {
00704                     maxphysical = physical_order[order];
00705                 }
00706 
00707             uves_msg_debug("Mapping relative order #%d to absolute order #%d", 
00708                            order, physical_order[order]);
00709         }
00710         
00711         /* Get first and last physical orders */
00712         *first_abs_order = physical_order[minorder];
00713         *last_abs_order  = physical_order[maxorder];
00714         
00715         passure( *first_abs_order - *last_abs_order == coeff2*(minorder - maxorder),
00716                  "%d %d %d %d %d",
00717                  *first_abs_order, *last_abs_order, coeff2, minorder, maxorder);
00718         
00719     }
00720 
00721     /* Then write this rounded mean value to every row of the table */
00722     for (i = 0; i < cpl_table_get_nrow(linetable); i++) {
00723     int order;
00724     order = cpl_table_get_int (linetable, "Y", i, NULL);
00725     cpl_table_set_int(linetable, "Order", i, physical_order[order]);
00726     }
00727 
00728     /* Calculate the inverse of 'physical_order' */
00729     relative_order = cpl_calloc(maxphysical + 1, sizeof(int));
00730     for (i = 0; i <= maxorder; i++)
00731     {
00732         relative_order[physical_order[i]] = i;
00733     }
00734     
00735   cleanup:
00736     uves_free_table(&temp);
00737     uves_polynomial_delete_const(&map);
00738     cpl_free(sum);
00739     cpl_free(physical_order);
00740     cpl_free(N);
00741 
00742     return relative_order;
00743 }

Generated on Tue Jun 19 14:39:20 2007 for UVES Pipeline Reference Manual by  doxygen 1.4.6