UVES Pipeline Reference Manual  5.4.0
uves_wavecal_identify.c
1 /* *
2  * This file is part of the ESO UVES Pipeline *
3  * Copyright (C) 2004,2005 European Southern Observatory *
4  * *
5  * This library is free software; you can redistribute it and/or modify *
6  * it under the terms of the GNU General Public License as published by *
7  * the Free Software Foundation; either version 2 of the License, or *
8  * (at your option) any later version. *
9  * *
10  * This program is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU General Public License *
16  * along with this program; if not, write to the Free Software *
17  * Foundation, 51 Franklin St, Fifth Floor, Boston, MA 02111-1307 USA *
18  * */
19 
20 /*
21  * $Author: amodigli $
22  * $Date: 2012-05-02 06:11:40 $
23  * $Revision: 1.38 $
24  * $Name: not supported by cvs2svn $
25  * $Log: not supported by cvs2svn $
26  * Revision 1.37 2012/03/02 16:40:40 amodigli
27  * fixed warning related to upgrade to CPL6
28  *
29  * Revision 1.36 2011/12/08 14:00:02 amodigli
30  * Fox warnings with CPL6
31  *
32  * Revision 1.35 2011/04/14 11:25:40 amodigli
33  * fixed typo QC key in comments
34  *
35  * Revision 1.34 2011/04/11 09:07:41 amodigli
36  * implemented QC comments corrections from DFO
37  *
38  * Revision 1.33 2011/04/11 07:53:12 amodigli
39  * uniformed QC param key name
40  *
41  * Revision 1.32 2011/03/23 12:27:31 amodigli
42  * changed QC key as user likes
43  *
44  * Revision 1.31 2011/03/23 10:08:47 amodigli
45  * added QC to better characterize wave accuracy
46  *
47  * Revision 1.30 2010/09/24 09:32:09 amodigli
48  * put back QFITS dependency to fix problem spot by NRI on FIBER mode (with MIDAS calibs) data
49  *
50  * Revision 1.28 2007/07/23 14:57:30 jmlarsen
51  * Make workaround work
52  *
53  * Revision 1.27 2007/07/23 12:40:37 jmlarsen
54  * Update to CPL4
55  *
56  * Revision 1.26 2007/06/06 08:17:33 amodigli
57  * replace tab with 4 spaces
58  *
59  * Revision 1.25 2007/05/22 11:46:15 jmlarsen
60  * Removed 1d wavecal mode which was not supported
61  *
62  * Revision 1.24 2007/05/16 16:33:42 amodigli
63  * fixed leak
64  *
65  * Revision 1.23 2007/05/10 08:32:48 jmlarsen
66  * Minor output message change
67  *
68  * Revision 1.22 2007/05/07 14:26:44 jmlarsen
69  * Added QC.NLINSOL parameter
70  *
71  * Revision 1.21 2007/05/07 07:13:59 jmlarsen
72  * Made resolution computation robust against negative dl/dx
73  *
74  * Revision 1.20 2007/04/27 07:22:57 jmlarsen
75  * Implemented possibility to use automatic polynomial degree
76  *
77  * Revision 1.19 2007/04/13 07:34:54 jmlarsen
78  * Removed dead code
79  *
80  * Revision 1.18 2007/04/10 07:12:09 jmlarsen
81  * Changed interface of polynomial_regression_2d()
82  *
83  * Revision 1.17 2007/03/15 12:36:44 jmlarsen
84  * Added experimental ppm code
85  *
86  * Revision 1.16 2007/03/05 10:24:14 jmlarsen
87  * Do kappa-sigma rejection only in second loop
88  *
89  * Revision 1.15 2007/02/22 15:37:35 jmlarsen
90  * Use kappa-sigma clipping when fitting dispersion
91  *
92  * Revision 1.14 2007/01/15 08:58:51 jmlarsen
93  * Added text output
94  *
95  * Revision 1.13 2006/11/06 15:19:42 jmlarsen
96  * Removed unused include directives
97  *
98  * Revision 1.12 2006/10/12 11:36:48 jmlarsen
99  * Reduced max line length
100  *
101  * Revision 1.11 2006/10/10 11:20:11 jmlarsen
102  * Renamed line table columns to match MIDAS
103  *
104  * Revision 1.10 2006/08/17 14:11:25 jmlarsen
105  * Use assure_mem macro to check for memory allocation failure
106  *
107  * Revision 1.9 2006/08/17 13:56:53 jmlarsen
108  * Reduced max line length
109  *
110  * Revision 1.8 2006/08/11 14:36:37 jmlarsen
111  * Added profiling info
112  *
113  * Revision 1.7 2006/08/07 11:35:08 jmlarsen
114  * Removed hardcoded constant
115  *
116  * Revision 1.6 2006/07/14 12:52:57 jmlarsen
117  * Exported/renamed function find_nearest
118  *
119  * Revision 1.5 2006/07/14 12:44:26 jmlarsen
120  * Use less significant digits
121  *
122  * Revision 1.4 2006/04/24 09:33:48 jmlarsen
123  * Shortened max line length
124  *
125  * Revision 1.3 2006/03/03 13:54:11 jmlarsen
126  * Changed syntax of check macro
127  *
128  * Revision 1.2 2006/02/15 13:19:15 jmlarsen
129  * Reduced source code max. line length
130  *
131  * Revision 1.1 2006/02/03 07:46:30 jmlarsen
132  * Moved recipe implementations to ./uves directory
133  *
134  * Revision 1.31 2005/12/20 08:11:44 jmlarsen
135  * Added CVS entry
136  *
137  */
138 
139 /*----------------------------------------------------------------------------*/
143 /*----------------------------------------------------------------------------*/
146 #ifdef HAVE_CONFIG_H
147 # include <config.h>
148 #endif
149 
150 #include <uves_wavecal_identify.h>
151 
152 #include <uves_wavecal_utils.h>
153 #include <uves_utils.h>
154 #include <uves_utils_wrappers.h>
155 #include <uves_error.h>
156 #include <uves_msg.h>
157 #include <cpl_ppm.h> /* missing from cpl.h */
158 #include <uves_qclog.h>
159 #include <cpl.h>
160 
161 #include <math.h>
162 #include <float.h>
163 
164 #define USE_PPM 0
165 
166 static cpl_error_code verify_calibration(const cpl_table *selected,
167  const cpl_table *linetable,
168  double TOLERANCE,
169  double red_chisq,cpl_table* qclog);
170 static cpl_error_code compute_lambda(cpl_table *linetable,
171  const polynomial *dispersion_relation,
172  const polynomial *dispersion_variance,
173  bool verbose);
174 
175 static int identify_lines(cpl_table *linetable,
176  const cpl_table *line_refer,
177  double ALPHA);
178 
179 static polynomial *calibrate_global(const cpl_table *linetable,
180  cpl_table **selected,
181  int degree, bool verbose,
182  bool reject,
183  double TOLERANCE,
184  double kappa,
185  double *red_chisq,
186  polynomial **dispersion_variance,
187  double *pixelsize,
188  double *rms_wlu,
189  double *rms_pixels);
190 
191 /*----------------------------------------------------------------------------*/
231 /*----------------------------------------------------------------------------*/
232 
233 polynomial *
234 uves_wavecal_identify(cpl_table *linetable,
235  const cpl_table *line_refer,
236  const polynomial *guess_dispersion,
237  int DEGREE, double TOLERANCE,
238  double ALPHA, double MAXERROR,
239  double kappa,
240  const int trace,const int window,cpl_table* qclog)
241 {
242  polynomial *dispersion_relation = NULL; /* Result */
243  polynomial *dispersion_variance = NULL; /* Variance of result,
244  written to line table */
245  int current_id; /* Current and previous number of line identifications */
246  int previous_id;
247  int idloop; /* Number of iterations of grand loop */
248  int n; /* Number of iterations in ID loop */
249  double pixelsize; /* Average conversion factor between pixels and wlu */
250  double red_chisq; /* Reduced chi^2 of fit */
251  cpl_table *selected = NULL; /* Lines used in final fit */
252  char qc_key[40];
253 
254  passure( linetable != NULL, " ");
255  passure( line_refer != NULL, " ");
256  passure( guess_dispersion != NULL, " ");
257 
258  assure( 0 < ALPHA && ALPHA <= 1, CPL_ERROR_ILLEGAL_INPUT,
259  "Illegal alpha = %e", ALPHA);
260 
261  /* Calculate LambdaC from the initial dispersion relation */
262  {
263  cpl_table_new_column(linetable, LINETAB_LAMBDAC , CPL_TYPE_DOUBLE);
264  cpl_table_new_column(linetable, "dLambdaC" , CPL_TYPE_DOUBLE);
265  cpl_table_new_column(linetable, LINETAB_PIXELSIZE , CPL_TYPE_DOUBLE);
266  cpl_table_new_column(linetable, LINETAB_RESIDUAL , CPL_TYPE_DOUBLE);
267  cpl_table_new_column(linetable, "Residual_pix" , CPL_TYPE_DOUBLE);
268  cpl_table_new_column(linetable, "Lambda_candidate" , CPL_TYPE_DOUBLE);
269  cpl_table_new_column(linetable, "dLambda_candidate", CPL_TYPE_DOUBLE);
270  cpl_table_new_column(linetable, "dLambda_cat_sq" , CPL_TYPE_DOUBLE);
271  cpl_table_new_column(linetable, "dLambda_nn_sq" , CPL_TYPE_DOUBLE);
272 
273  /* Create columns 'Ident' and 'dIdent' (uncertainty) and fill with
274  invalid (no identification made) */
275  cpl_table_new_column(linetable, "Ident", CPL_TYPE_DOUBLE);
276  cpl_table_new_column(linetable, "dIdent",CPL_TYPE_DOUBLE);
277  cpl_table_set_column_invalid(linetable, "Ident", 0, cpl_table_get_nrow(linetable));
278  cpl_table_set_column_invalid(linetable, "dIdent",0, cpl_table_get_nrow(linetable));
279 
280  /* Residuals are not calculated because 'Ident' is invalid */
281  check( compute_lambda(linetable, guess_dispersion, NULL, false),
282  "Error applying dispersion relation");
283  }
284 
285 
286 #if USE_PPM
287  for (idloop = 2; idloop <= 2; idloop += 1)
288 #else
289  for (idloop = 1; idloop <= 2; idloop += 1)
290 #endif
291  {
292 
293  current_id = 0;
294  n = 0;
295  /* Iterate until no more identifications can be made */
296  do {
297  double rms_wlu;
298  double rms_pixels;
299  bool reject = (idloop == 2);
300 #if USE_PPM
301  int nident_ppm;
302 #endif
303 
304  previous_id = current_id;
305  n++;
306 
307  /* Identify lines */
308  check( current_id = identify_lines(linetable, line_refer, ALPHA),
309  "Error identifying lines");
310 
311 
312 #if USE_PPM
313  /* Try PPM */
314  check( nident_ppm = uves_wavecal_identify_lines_ppm(linetable, line_refer),
315  "Error during point pattern matching");
316 
317  cpl_table_erase_column(linetable, "Ident");
318  cpl_table_duplicate_column(linetable, "Ident", linetable, "Ident_ppm");
319  current_id = nident_ppm;
320 
321  /* FIXME: This only works if 'dIdent' is constant.
322  We should propagate error bars during ppm matching */
323  cpl_table_fill_column_window(linetable, "dIdent",
324  0, cpl_table_get_nrow(linetable),
325  cpl_table_get_column_mean(linetable, "dIdent"));
326 #endif
327 
328  /* Calibrate with
329  * 1st loop: tolerance=infinity (i.e. all identified lines are considered good).
330  * 2nd loop: use specified tolerance (ignore outliers)
331  */
332  uves_polynomial_delete(&dispersion_relation);
333  uves_polynomial_delete(&dispersion_variance);
334 
335  check( dispersion_relation = calibrate_global(
336  linetable, NULL,
337  DEGREE, false,
338  reject,
339  TOLERANCE,
340  kappa,
341  &red_chisq,
342  &dispersion_variance,
343  &pixelsize,
344  &rms_wlu,
345  &rms_pixels),
346  "Could not perform global calibration");
347 
348  uves_msg_debug("Average pixelsize = %f wlu", pixelsize);
349  if (idloop == 1)
350  {
351  uves_msg("%d identifications made. RMS = %.5f wlu = %.3f "
352  "pixels (no rejection)",
353  current_id, rms_wlu, rms_pixels);
354 
355 
356 
357 
358  }
359  else
360  {
361  uves_msg("%d identifications made. RMS = %.5f wlu = %.3f "
362  "pixels (%f %s rejection, kappa = %.1f)",
363  current_id, rms_wlu, rms_pixels,
364  fabs(TOLERANCE), (TOLERANCE > 0) ? "pixels" : "wlu",
365  kappa);
366  }
367 
368  sprintf(qc_key,"QC TRACE%d WIN%d NLINID%d",trace,window,idloop);
369  ck0_nomsg(uves_qclog_add_int(qclog,qc_key,current_id,
370  "ThAr lamp identified lines",
371  "%d"));
372 
373 #if USE_PPM
374  uves_msg("%d identifications from point pattern matching",
375  nident_ppm);
376 #endif
377 
378  assure( rms_pixels < MAXERROR, CPL_ERROR_CONTINUE,
379  "Wavelength calibration did not converge. "
380  "After %d iterations the RMS was %f pixels. "
381  "Try to improve on the initial solution", n, rms_pixels);
382 
383 
384  /* Apply calibration result */
385  check( compute_lambda(linetable, dispersion_relation, dispersion_variance,
386  false),
387  "Error applying dispersion relation");
388 
389 
390  }
391  while (current_id > previous_id) ;
392 
393  sprintf(qc_key,"QC TRACE%d WIN%d NLINID NITERS",trace,window);
394  ck0_nomsg(uves_qclog_add_int(qclog,qc_key,idloop+1,
395  "Number of iterations",
396  "%d"));
397 
398 
399 
400  if (idloop == 1)
401  {
402  /*
403  * Remove all identifications and repeat
404  */
405 
406  uves_msg("Identification loop converged. Resetting identifications");
407  cpl_table_set_column_invalid(linetable, "Ident", 0,
408  cpl_table_get_nrow(linetable));
409  }
410  }
411 
412  /* Calibrate again with a global polynomial, but this time don't
413  use lines with residuals worse than TOLERANCE */
414  uves_polynomial_delete(&dispersion_relation);
415  uves_polynomial_delete(&dispersion_variance);
416  uves_free_table(&selected);
417 
418  check( dispersion_relation = calibrate_global(linetable,
419  &selected,
420  DEGREE, true,
421  true, /* do rejection? */
422  TOLERANCE,
423  kappa,
424  &red_chisq,
425  &dispersion_variance,
426  NULL, NULL, NULL),
427  "Could not perform global calibration");
428 
429  /* Update the computed wavelengths */
430  check( compute_lambda(linetable, dispersion_relation, dispersion_variance,
431  true),
432  "Error applying dispersion relation");
433 
434  /* Add columns 'Select' and 'NLinSol' to linetable.
435  The columns defines which lines were identified,
436  and which lines were used in the final fit */
437  {
438  int i, j;
439 
440  /* Tables are sorted by Order, X */
441 
442  cpl_table_new_column(linetable, "NLinSol", CPL_TYPE_INT);
443  cpl_table_new_column(linetable, "Select", CPL_TYPE_INT);
444 
445  cpl_table_fill_column_window_int(linetable, "NLinSol",
446  0, cpl_table_get_nrow(linetable),
447  0);
448  cpl_table_fill_column_window_int(linetable, "Select",
449  0, cpl_table_get_nrow(linetable),
450  0);
451 
452  j = 0;
453  for (i = 0; i < cpl_table_get_nrow(selected); i++) {
454  int order = cpl_table_get_int(selected, "Order", i, NULL);
455  double x = cpl_table_get_double(selected, "X", i, NULL);
456  int order2;
457  double x2;
458 
459  /* Find this line in the original linetable */
460  passure( j < cpl_table_get_nrow(linetable), "%d %" CPL_SIZE_FORMAT "",
461  j, cpl_table_get_nrow(linetable));
462  do {
463  order2 = cpl_table_get_int(linetable, "Order", j, NULL);
464  x2 = cpl_table_get_double(linetable, "X", j, NULL);
465  if (cpl_table_is_valid(linetable, "Ident", j))
466  {
467  cpl_table_set_int(linetable, "Select", j, 1);
468  }
469  j++;
470 
471  } while (order2 < order || x2 < x - 0.1);
472 
473  passure( order2 == order && fabs(x2 - x) < 0.1,
474  "%d %d %g %g", order2, order, x2, x);
475 
476  cpl_table_set_int(linetable, "NLinSol", j-1, 1);
477  }
478  }
479 
480  /* Display results */
481  check( verify_calibration(selected, linetable, TOLERANCE, red_chisq,qclog),
482  "Error verifying calibration");
483 
484  cleanup:
485  uves_free_table(&selected);
486  uves_polynomial_delete(&dispersion_variance);
487  return dispersion_relation;
488 }
489 
490 /*----------------------------------------------------------------------------*/
504 /*----------------------------------------------------------------------------*/
505 static cpl_error_code
506 verify_calibration(const cpl_table *selected,
507  const cpl_table *linetable, double TOLERANCE,
508  double red_chisq, cpl_table* qclog)
509 {
510  cpl_table *brightest = NULL;
511  double median_intensity;
512  int ninvalid; /* Number of unidentified lines among the brightest half */
513  double ratio;
514  double rms_wlu;
515  double rms_pixels;
516  double rms_speed;
517  char qc_key[40];
518 
519  {
520  double mean;
521  double stdev;
522 
523  check(( mean = cpl_table_get_column_mean (selected, LINETAB_RESIDUAL),
524  stdev= cpl_table_get_column_stdev(selected, LINETAB_RESIDUAL),
525  rms_wlu = sqrt(mean*mean + stdev*stdev),
526 
527  mean = cpl_table_get_column_mean (selected, "Residual_pix"),
528  stdev= cpl_table_get_column_stdev(selected, "Residual_pix"),
529  rms_pixels = sqrt(mean*mean + stdev*stdev)),
530  "Error reading RMS of fit");
531  }
532  rms_speed=rms_wlu * SPEED_OF_LIGHT/
533  cpl_table_get_column_mean(selected,LINETAB_LAMBDAC);
534  uves_msg("%" CPL_SIZE_FORMAT " lines accepted", cpl_table_get_nrow(selected));
535  uves_msg("Average RMS of calibration (tolerance = %.3f %s) = %.5f wlu = %.4f pixels ~ %.1f m/s",
536  fabs(TOLERANCE),
537  (TOLERANCE > 0) ? "pixels" : "wlu",
538  rms_wlu, rms_pixels, rms_speed);
539 
540  sprintf(qc_key,"QC LINE RESIDRMS WLU");
541  ck0_nomsg(uves_qclog_add_double(qclog,qc_key,rms_wlu,
542  "Line ID RMS TRACE0 WIN2 [Angstrom]",
543  "%f"));
544  sprintf(qc_key,"QC LINE RESIDRMS PIX");
545  ck0_nomsg(uves_qclog_add_double(qclog,qc_key,rms_pixels,
546  "Line ID RMS TRACE0 WIN2 [pix]",
547  "%f"));
548  sprintf(qc_key,"QC LINE RESIDRMS SPEED");
549  ck0_nomsg(uves_qclog_add_double(qclog,qc_key,rms_speed,
550  "Line ID RMS TRACE0 WIN2 [m/s]",
551  "%f"));
552 
553 
554  uves_msg("Reduced chi^2 of calibration = %f", red_chisq);
555  sprintf(qc_key,"QC LINE IDCHI2");
556  ck0_nomsg(uves_qclog_add_double(qclog,qc_key,red_chisq,
557  "Reduced chi^2 of line ID TRACE0 WIN2",
558  "%f"));
559 
560  if (red_chisq < .01)
561  {
562  uves_msg_warning("Reduced chi^2 of fit is less than 1/100: %f",
563  red_chisq);
564  }
565  if (red_chisq > 100)
566  {
567  uves_msg_warning("Reduced chi^2 of fit is greater than 100: %f",
568  red_chisq);
569  }
570 
571  check(( median_intensity = cpl_table_get_column_median(linetable, "Peak"),
572  brightest = uves_extract_table_rows(linetable, "Peak",
573  CPL_GREATER_THAN,
574  median_intensity),
575  ninvalid = cpl_table_count_invalid(brightest, "Ident")),
576  "Error counting identifications");
577 
578  ratio = 1 - ((double) ninvalid)/cpl_table_get_nrow(brightest);
579  uves_msg("Percentage of identifications among the half brighter lines : %.2f %%",
580  100*ratio);
581 
582  sprintf(qc_key,"QC LINE HALFBRIG");
583  ck0_nomsg(uves_qclog_add_double(qclog,qc_key,100*ratio,
584  "Half brighter lines frac TRACE0 WIN2",
585  "%f"));
586 
587  cleanup:
588  uves_free_table(&brightest);
589 
590  return cpl_error_get_code();
591 }
592 
593 /*----------------------------------------------------------------------------*/
607 /*----------------------------------------------------------------------------*/
608 static cpl_error_code
609 compute_lambda(cpl_table *linetable,
610  const polynomial *dispersion_relation,
611  const polynomial *dispersion_variance,
612  bool verbose)
613 {
614  int i;
615  bool printed_warning = false;
616 
617  /* Check input */
618  passure(linetable != NULL, " ");
619  passure(dispersion_relation != NULL, " ");
620  /* 'dispersion_variance' may be NULL */
621 
622  passure( uves_polynomial_get_dimension(dispersion_relation) == 2, "%d",
623  uves_polynomial_get_dimension(dispersion_relation));
624 
625  /* Input columns */
626  passure(cpl_table_has_column(linetable, "X") , " ");
627  passure(cpl_table_has_column(linetable, "Order") , " ");
628  passure(cpl_table_has_column(linetable, "Ident") , " ");
629  /* Output columns */
630  passure(cpl_table_has_column(linetable, LINETAB_LAMBDAC) , " ");
631  /* The column 'dLambdaC' is set to invalid if 'dispersion_variance' is NULL */
632  passure(cpl_table_has_column(linetable, "dLambdaC") , " ");
633  passure(cpl_table_has_column(linetable, "dIdent") , " ");
634  passure(cpl_table_has_column(linetable, LINETAB_RESIDUAL), " ");
635  passure(cpl_table_has_column(linetable, "Residual_pix"), " ");
636  passure(cpl_table_has_column(linetable, LINETAB_PIXELSIZE) , " ");
637 
638  /* The linetable is sorted w.r.t. order.
639  Move to the first order above minorder */
640  for(i = 0; i < cpl_table_get_nrow(linetable); i++)
641  {
642  int order;
643  double x, dfdx;
644  double lambdac, dlambdac, pixelsize;
645  order = cpl_table_get_int(linetable, "Order", i, NULL);
646 
647  x = cpl_table_get_double(linetable, "X", i, NULL);
648 
649  /* Evaluate the dispersion relation
650  m.lambda = f(x,m) (2d global fit) */
651 
652  lambdac =
653  uves_polynomial_evaluate_2d(dispersion_relation, x, order) / order;
654 
655  /* Pixelsize = dl/dx = (df/dx)/m (for fixed m) */
656  dfdx = uves_polynomial_derivative_2d(dispersion_relation, x, order, 1);
657  if (dfdx < 0) {
658  if (!printed_warning && verbose) {
659  uves_msg_warning("Inferred dispersion (dlambda/dx) is negative at"
660  "(x, order) = (%f, %d)", x, order);
661  printed_warning = true; /* To avoid repeating the same warning */
662  }
663  else {
664  uves_msg_debug("Inferred dispersion (dlambda/dx) is negative at "
665  "(x, order) = (%f, %d)", x, order);
666  }
667  }
668  pixelsize = dfdx / order;
669 
670  check(( cpl_table_set_double(linetable, LINETAB_LAMBDAC , i, lambdac),
671  cpl_table_set_double(linetable, LINETAB_PIXELSIZE, i, pixelsize)),
672  "Error writing table");
673 
674  if (dispersion_variance != NULL)
675  {
676  /* d( lambda (x, order) ) =
677  d( lambda*m(x, order) ) / m */
678  dlambdac =
679  sqrt(uves_polynomial_evaluate_2d(dispersion_variance, x, order))
680  / order;
681 
682  cpl_table_set_double(linetable, "dLambdaC" , i, dlambdac);
683  }
684  else
685  {
686  /* Only the ratio of a line's "dLambdaC" to other
687  lines' are used, so set "dLambdaC" to a constant value
688  when the actual uncertainty is not known
689  */
690  cpl_table_set_double(linetable, "dLambdaC" , i, 1.0);
691  }
692 
693  /* If line is identified, calculate residual */
694  if (cpl_table_is_valid(linetable, "Ident", i))
695  {
696  double ident = cpl_table_get_double(linetable, "Ident", i, NULL);
697  cpl_table_set_double(linetable, LINETAB_RESIDUAL, i,
698  ident - lambdac);
699  cpl_table_set_double(linetable, "Residual_pix", i,
700  (ident - lambdac)/pixelsize);
701  }
702  else
703  {
704  cpl_table_set_invalid(linetable, LINETAB_RESIDUAL, i);
705  cpl_table_set_invalid(linetable, "Residual_pix", i);
706  }
707  }
708 
709  /* Sort by 'Order' (ascending), then 'X' (ascending) */
710  check( uves_sort_table_2(linetable, "Order", "X", false, false),
711  "Error sorting table");
712 
713  cleanup:
714  return cpl_error_get_code();
715 }
716 
717 
718 /*----------------------------------------------------------------------------*/
755 /*----------------------------------------------------------------------------*/
756 
757 static int
758 identify_lines(cpl_table *linetable, const cpl_table *line_refer, double ALPHA)
759 {
760  int number_identifications = 0; /* Result */
761  int linetable_size;
762  int linerefer_size;
763  int row;
764  int *histogram = NULL;
765  const double minlog = -5.0; /* Histogram (it's sort of ugly
766  to hardcode these numbers, but
767  as long as it works, ...) */
768  const double maxlog = 15.0;
769  const int nbins = 400;
770  double error = 0; /* Dimensionless factor
771  that controls IDs */
772  double average_dlambda_com = 0; /* Average of uncertainty of
773  predicted wavelenghts */
774 
775  /* Check input */
776  passure( linetable != NULL, " ");
777  /* Line table input columns */
778  passure( cpl_table_has_column(linetable, LINETAB_LAMBDAC ), " "); /* Predicted
779  wavelength */
780  passure( cpl_table_has_column(linetable, "dLambdaC" ), " "); /* Predicted wavelength
781  uncertainty */
782  passure( cpl_table_has_column(linetable, "X" ), " "); /* Line position, used
783  only for messaging */
784  passure( cpl_table_has_column(linetable, "Order" ), " "); /* Absolute order number
785  of line */
786  passure( cpl_table_has_column(linetable, "Xwidth" ), " "); /* Line width (sigma) */
787  passure( cpl_table_has_column(linetable, LINETAB_PIXELSIZE), " "); /* Pixelsize */
788 
789  /* Line table output columns */
790  passure( cpl_table_has_column(linetable, "Ident" ), " "); /* Identified catalogue
791  wavelength */
792  passure( cpl_table_has_column(linetable, "dIdent" ), " "); /* Uncertainty of IDed
793  catalogue wavelength */
794 
795  /* Catalogue */
796  passure( line_refer != NULL, " ");
797  passure( cpl_table_has_column(line_refer, "Wave" ), " "); /* Catalogue wavelength */
798  passure( cpl_table_has_column(line_refer, "dWave"), " "); /* Uncertainty of
799  catalogue wavelength */
800 
801  linetable_size = cpl_table_get_nrow(linetable);
802  linerefer_size = cpl_table_get_nrow(line_refer);
803  assure(linerefer_size >= 1, CPL_ERROR_ILLEGAL_INPUT, "Empty line reference table");
804 
805  /* Parameter */
806  passure( 0 < ALPHA && ALPHA <= 1, "%e", ALPHA);
807 
808  /* Get average uncertainty of predicted wavelength */
809  average_dlambda_com = cpl_table_get_column_median(linetable, "dLambdaC");
810 
811  /* Initialize histogram to zero */
812  histogram = cpl_calloc(nbins, sizeof(int));
813  assure_mem( histogram );
814 
815 
816  /* First: Find distance to closest catalogue match,
817  distance to nearest neighbour,
818  and calculate histogram (to get average of distances to nearest neighbour) */
819  for (row = 0; row < linetable_size; row++) {
820  double lambda_com; /* Computed (predicted) wavelength */
821  double line_width; /* Line width (sigma) in wlu */
822  double line_fwhm; /* Line FWHM in wlu */
823  int order; /* (Absolute) order of detected wavelength */
824  double lambda_cat; /* Catalogue wavelength */
825  double lambda_cat_sigma; /* Catalogue wavelength uncertainty */
826  double distance_cat_sq; /* Distance to catalogue wavelength (squared) */
827  double nn_distance_sq; /* Distance to nearest neighbour (squared) */
828  int row_cat; /* Row number of best matching catalogue wavelength */
829 
830  /* Read line table */
831  lambda_com = cpl_table_get_double(linetable, LINETAB_LAMBDAC , row, NULL);
832  order = cpl_table_get_int (linetable, "Order" , row, NULL);
833 
834 
835  line_width =
836  cpl_table_get_double(linetable, "Xwidth" , row, NULL) *
837  fabs(cpl_table_get_double(linetable, LINETAB_PIXELSIZE , row, NULL));
838  /* Convert pixel->wlu */
839 
840  line_fwhm = TWOSQRT2LN2 * line_width;
841 
842  /* Find closest match in catalogue */
843  row_cat = uves_wavecal_find_nearest(
844  line_refer, lambda_com, 0, linerefer_size - 1);
845  lambda_cat = cpl_table_get_double(line_refer, "Wave", row_cat, NULL);
846  lambda_cat_sigma = cpl_table_get_double(line_refer, "dWave",row_cat, NULL);
847 
848  /* Distance to closest match */
849  distance_cat_sq = (lambda_com - lambda_cat)*(lambda_com - lambda_cat);
850 
851  /* Determine the distance to the next neighbour
852  * There are (max) 4 candiates: 2 neigbours in spectrum (i.e. line table)
853  * and 2 neigbours in line catalogue
854  */
855  {
856  double lambda_com_prev, lambda_com_next;
857  int order_prev, order_next;
858  double lambda_cat_prev, lambda_cat_next;
859 
860  nn_distance_sq = DBL_MAX;
861 
862  /* Read previous and next rows of line table */
863  if (row >= 1)
864  {
865  order_prev = cpl_table_get_int (
866  linetable, "Order" , row - 1, NULL);
867  lambda_com_prev = cpl_table_get_double(
868  linetable, LINETAB_LAMBDAC, row - 1, NULL);
869 
870  if (order == order_prev)
871  {
872  nn_distance_sq = uves_min_double(nn_distance_sq,
873  (lambda_com_prev - lambda_com)*
874  (lambda_com_prev - lambda_com)
875  );
876  }
877  }
878 
879  if (row <= linetable_size - 2)
880  {
881  order_next = cpl_table_get_int (linetable, "Order",
882  row + 1, NULL);
883  lambda_com_next = cpl_table_get_double(linetable, LINETAB_LAMBDAC,
884  row + 1, NULL);
885 
886  if (order == order_next)
887  {
888  nn_distance_sq = uves_min_double(nn_distance_sq,
889  (lambda_com_next - lambda_com)*
890  (lambda_com_next - lambda_com)
891  );
892  }
893  }
894 
895  /* Read previous and next rows of catalogue */
896  if (row_cat >= 1)
897  {
898  lambda_cat_prev = cpl_table_get_double(
899  line_refer, "Wave", row_cat - 1, NULL);
900 
901  nn_distance_sq = uves_min_double(
902  nn_distance_sq,
903  (lambda_cat_prev - lambda_cat)*
904  (lambda_cat_prev - lambda_cat)
905  );
906  }
907  if (row_cat <= linerefer_size - 2)
908  {
909  lambda_cat_next = cpl_table_get_double(
910  line_refer, "Wave", row_cat + 1, NULL);
911 
912  nn_distance_sq = uves_min_double(
913  nn_distance_sq,
914  (lambda_cat_next - lambda_cat)*
915  (lambda_cat_next - lambda_cat)
916  );
917  }
918 
919  /* Update distance to nearest neighbour with a
920  safety margin (determined by parameter ALPHA < 1) */
921  if (nn_distance_sq < DBL_MAX)
922  {
923  nn_distance_sq *= ALPHA*ALPHA;
924  }
925 
926  }/* Find next neighbour */
927 
928  /* Update line table */
929  cpl_table_set_double(linetable, "Lambda_candidate", row, lambda_cat);
930  cpl_table_set_double(linetable, "dLambda_candidate",row, lambda_cat_sigma);
931  cpl_table_set_double(linetable, "dLambda_cat_sq", row, distance_cat_sq);
932  cpl_table_set_double(linetable, "dLambda_nn_sq", row, nn_distance_sq);
933 
934  /* Update histogram with the interval
935  [distance_cat_sq ; nn_distance_sq] (in units of line_fwhm) */
936  {
937  int ilow = uves_round_double((0.5*log(distance_cat_sq/(line_fwhm*line_fwhm))
938  - minlog)/(maxlog - minlog) * nbins);
939  int ihigh = uves_round_double((0.5*log(nn_distance_sq /(line_fwhm*line_fwhm))
940  - minlog)/(maxlog - minlog) * nbins);
941  int i;
942 
943  for (i = uves_max_int(ilow, 0); i < uves_min_int(ihigh, nbins); i++)
944  {
945  histogram[i] += 1;
946  }
947  }
948  }/* ... finding neighbours */
949 
950  /* Determine error as peak of histogram */
951  {
952  int i;
953  int maxfreq = -1;
954  for (i = 0; i < nbins; i++)
955  {
956  uves_msg_debug("histogram[%d] = %d", i, histogram[i]);
957  if (histogram[i] > maxfreq)
958  {
959  maxfreq = histogram[i];
960  error = exp( i / ((double)nbins) * (maxlog - minlog) + minlog ) ;
961  /* == the dimensionless factor to be multiplied by Xwidth */
962  }
963  }
964  uves_msg_debug("Dimensionless error factor is %f", error);
965  }
966 
967  /* Sketch of situation:
968 
969  lambda_com Nearest neighbour
970 
971  | |
972  | | |
973  | | |
974  | | |
975  |
976 
977  lambda_cat
978 
979 
980  The 'average' (as inferred from the histogram)
981  midpoint between 'lambda_cat' and 'nearest neighbour'
982  is at 'error' * 'line_fwhm' .
983  */
984 
985  /* Make the identification if
986 
987  1) the catalogue candidate is within two sigma:
988  | lambda_cat - lambda_com | < 2 * dlambda_com
989 
990  and
991 
992  2) after multiplying the distance to the nearest neighbour by ALPHA < 1,
993  the nearest neighbour is farther away than the catalogue wavelength
994  distance_nn > distance_cat
995  and farther away than the tolerance
996  distance_nn > line_fwhm * error
997 
998  */
999  for (row = 0; row < linetable_size; row++)
1000  {
1001  double distance_cat_sq; /* Distance to catalogue wavelength (squared) */
1002  double nn_distance_sq; /* Distance to nearest neighbour (squared) */
1003  double tolerance_sq;
1004  double dlambda_com;
1005  double line_width; /* Line width (1 sigma) */
1006  double line_fwhm;
1007  double lambda_cat;
1008  double lambda_cat_sigma; /* Uncertainty of lambda_cat */
1009 
1010  lambda_cat = cpl_table_get_double(linetable, "Lambda_candidate", row, NULL);
1011  lambda_cat_sigma = cpl_table_get_double(linetable, "dLambda_candidate", row, NULL);
1012 
1013 
1014  /* Sigma less than 1 pixel is usually not
1015  justified by the data (which obviously
1016  has a resolution of only 1 pixel). Such
1017  an underenstimation of the uncertainty
1018  leads to wrong identifications.
1019  Therefore use a width of at least 1 pixel */
1020  line_width =
1021  uves_max_double(1, cpl_table_get_double(linetable, "Xwidth" , row, NULL)) *
1022  fabs(cpl_table_get_double(linetable, LINETAB_PIXELSIZE , row, NULL));
1023  /* convert to wlu */
1024 
1025  line_fwhm = TWOSQRT2LN2 * line_width;
1026 
1027  /* As the uncertainty of the computed wavelength is used
1028  * line_fwhm (in w.l.u.)
1029  * To take into account the fact that lines near the edge of
1030  * the chip have larger error of the computed wavelength,
1031  * this is also scaled according to the accuracy of the dispersion
1032  * relation, i.e. multiplied by dl/<dl>,
1033  * where <dl> is an average, say the median, of uncertainties of
1034  * all predicted wavelengths.
1035  */
1036 
1037  dlambda_com = line_fwhm
1038  * cpl_table_get_double(linetable, "dLambdaC" , row, NULL)
1039  / average_dlambda_com;
1040 
1041  tolerance_sq = line_fwhm*line_fwhm * error*error;
1042 
1043  distance_cat_sq = cpl_table_get_double(linetable, "dLambda_cat_sq", row, NULL);
1044  nn_distance_sq = cpl_table_get_double(linetable, "dLambda_nn_sq" , row, NULL);
1045 
1046 #if WANT_BIG_LOGFILE
1047  uves_msg_debug("(order,x) = (%d,%f) lcom = %f+-%f lcat = %f "
1048  "dist_cat = %f (%f pixels) tolerance = %.3f error = %f "
1049  "nn = %f (%f pixels)",
1050  cpl_table_get_int (linetable, "Order" , row, NULL),
1051  cpl_table_get_double(linetable, "X" , row, NULL),
1052  cpl_table_get_double(linetable, LINETAB_LAMBDAC, row, NULL),
1053  dlambda_com,
1054  lambda_cat,
1055  sqrt(distance_cat_sq),
1056  sqrt(distance_cat_sq)
1057  /cpl_table_get_double(linetable, LINETAB_PIXELSIZE, row, NULL),
1058  sqrt(tolerance_sq),
1059  error,
1060  sqrt(nn_distance_sq),
1061  sqrt(nn_distance_sq)
1062  /cpl_table_get_double(linetable, LINETAB_PIXELSIZE, row, NULL));
1063 #endif
1064 
1065  /* Make the ID? */
1066  if (distance_cat_sq < (dlambda_com)*(dlambda_com)
1067  && tolerance_sq < nn_distance_sq
1068  && distance_cat_sq < nn_distance_sq)
1069  {
1070  number_identifications++;
1071  cpl_table_set_double(linetable, "Ident", row, lambda_cat);
1072  cpl_table_set_double(linetable, "dIdent",row, lambda_cat_sigma);
1073 #if WANT_BIG_LOGFILE
1074  uves_msg_debug("ID made");
1075 #endif
1076  }
1077  else
1078  {
1079  if (cpl_table_is_valid(linetable, "Ident", row)) {
1080  number_identifications++;
1081  /* Also count lines that were already identified */
1082  uves_msg_debug("Line at (%d,%f) does not match ID criterion anymore",
1083  cpl_table_get_int (linetable, "Order", row, NULL),
1084  cpl_table_get_double(linetable, "X", row, NULL)
1085  );
1086  }
1087  }
1088  }
1089 
1090  cleanup:
1091  cpl_free(histogram);
1092  return number_identifications;
1093 }
1094 
1095 /*----------------------------------------------------------------------------*/
1121 /*----------------------------------------------------------------------------*/
1122 static polynomial *
1123 calibrate_global(const cpl_table *linetable,
1124  cpl_table **selected,
1125  int degree, bool verbose,
1126  bool reject,
1127  double TOLERANCE,
1128  double kappa,
1129  double *red_chisq, polynomial **dispersion_variance,
1130  double *pixelsize,
1131  double *rms_wlu,
1132  double *rms_pixels)
1133 {
1134  polynomial *dispersion_relation = NULL; /* Result */
1135  cpl_table *identified = NULL;
1136  int valid_ids =
1137  cpl_table_get_nrow(linetable) -
1138  cpl_table_count_invalid(linetable, "Ident");
1139  int rejected;
1140 
1141  passure( (pixelsize == NULL) == (rms_wlu == NULL) &&
1142  (pixelsize == NULL) == (rms_pixels == NULL), " ");
1143 
1144  assure( degree < 0 ||
1145  valid_ids >= (degree + 1)*(degree + 1), CPL_ERROR_ILLEGAL_INPUT,
1146  "There are not enough identifications to create a %d.-degree global fit. "
1147  "%d needed. %d found", degree, (degree + 1)*(degree + 1), valid_ids);
1148 
1149  identified = cpl_table_duplicate(linetable);
1150  assure_mem(identified);
1151 
1152  /* Delete rows with invalid 'Ident' and large residuals */
1153  if (reject)
1154  {
1155  check_nomsg( rejected = uves_delete_bad_lines(identified, TOLERANCE, kappa) );
1156  uves_msg_debug("%d lines rejected %f %f", rejected, TOLERANCE, kappa);
1157  }
1158  else
1159  {
1160  check( uves_erase_invalid_table_rows(identified, "Ident"),
1161  "Error erasing un-identified lines");
1162  }
1163 
1164 
1165  /* Create column 'Aux' = 'Order' * 'Ident' */
1166  check(( cpl_table_duplicate_column(identified, "Aux", identified, "Ident"),
1167  cpl_table_multiply_columns(identified, "Aux", "Order"),
1168 
1169  /* Create column 'dAux' = 'Order' * 'dIdent' */
1170  cpl_table_duplicate_column(identified, "dAux", identified, "dIdent"),
1171  cpl_table_multiply_columns(identified, "dAux", "Order")),
1172  "Error setting up temporary table");
1173 
1174  /* Fit */
1175 
1176  if (degree >= 0) {
1177  check( dispersion_relation =
1178  uves_polynomial_regression_2d(identified,
1179  "X", "Order", "Aux",
1180  "dAux", /* Use "dAux" for weighting,
1181  to be able to compute an uncertainty
1182  of WAVEC.
1183 
1184  It would probably make more sense
1185  to use the uncertainty of 'dX' for
1186  weighting. */
1187  degree, degree,
1188  NULL, NULL, NULL, /* Don't add extra columns */
1189  NULL, /* mse */
1190  red_chisq,
1191  dispersion_variance,
1192  reject ? kappa : -1, -1),
1193  "Error fitting polynomial. Possible cause: too few (%d) "
1194  "line identifications", valid_ids);
1195  }
1196  else {
1197  int max_degree = 8;
1198  double min_rms = -1; /* disabled */
1199  double min_reject = -1; /* disabled */
1200  check( dispersion_relation =
1202  "X", "Order", "Aux",
1203  "dAux",
1204  NULL, NULL, NULL,
1205  NULL,
1206  red_chisq,
1207  dispersion_variance,
1208  reject ? kappa : -1,
1209  max_degree, max_degree,
1210  min_rms, min_reject,
1211  verbose,
1212  NULL, NULL, 0, NULL),
1213  "Error fitting polynomial. Possible cause: too few (%d) "
1214  "line identifications", valid_ids);
1215  }
1216 
1217  if (pixelsize != NULL)
1218  {
1219  /* Compute parameters if requested */
1220 
1221  check( compute_lambda(identified, dispersion_relation, NULL,
1222  false),
1223  "Error applying dispersion relation");
1224 
1225  *pixelsize = cpl_table_get_column_median(identified, LINETAB_PIXELSIZE);
1226  *rms_wlu = cpl_table_get_column_stdev (identified, LINETAB_RESIDUAL);
1227  *rms_pixels= cpl_table_get_column_stdev (identified, "Residual_pix");
1228  }
1229 
1230  if (selected != NULL) {
1231  *selected = cpl_table_duplicate(identified);
1232  }
1233 
1234  cleanup:
1235  uves_free_table(&identified);
1236  if (cpl_error_get_code() != CPL_ERROR_NONE)
1237  {
1238  uves_polynomial_delete(&dispersion_relation);
1239  }
1240 
1241  return dispersion_relation;
1242 }
1243 
1244 
1245 
1246 /*----------------------------------------------------------------------------*/
1253 /*----------------------------------------------------------------------------*/
1254 
1255 int
1256 uves_wavecal_identify_lines_ppm(cpl_table *linetable, const cpl_table *line_refer)
1257 {
1258  int result = 0;
1259  int minorder, maxorder;
1260  int order;
1261  cpl_table *lt_order = NULL;
1262  cpl_table *refer_order = NULL;
1263  cpl_vector *peaks = NULL;
1264  cpl_vector *lines = NULL;
1265  cpl_bivector *ids = NULL;
1266 
1267  assure( cpl_table_has_column(linetable, LINETAB_LAMBDAC), CPL_ERROR_DATA_NOT_FOUND,
1268  "Missing column %s", LINETAB_LAMBDAC);
1269 
1270  assure( cpl_table_has_column(linetable, LINETAB_PIXELSIZE), CPL_ERROR_DATA_NOT_FOUND,
1271  "Missing column %s", LINETAB_PIXELSIZE);
1272 
1273  assure( cpl_table_has_column(linetable, "Order"), CPL_ERROR_DATA_NOT_FOUND,
1274  "Missing column %s", "Order");
1275 
1276  minorder = uves_round_double( cpl_table_get_column_min(linetable, "Order"));
1277  maxorder = uves_round_double( cpl_table_get_column_max(linetable, "Order"));
1278 
1279  /* Reset identifications */
1280  if (cpl_table_has_column(linetable, "Ident_ppm"))
1281  {
1282  cpl_table_erase_column(linetable, "Ident_ppm");
1283  }
1284 
1285  cpl_table_new_column(linetable, "Ident_ppm", CPL_TYPE_DOUBLE);
1286 
1287  for (order = minorder; order <= maxorder; order++)
1288  {
1289  const double tolerance = 0.05; /* relative tolerance on interval ratios */
1290  double min_lambda, max_lambda;
1291  double min_disp, max_disp;
1292 
1293  /* Extract current order */
1294 
1295  uves_free_table(&lt_order);
1296  lt_order = uves_extract_table_rows(linetable, "Order",
1297  CPL_EQUAL_TO, order); /* Uses integer comparison */
1298 
1299  check_nomsg((min_lambda = cpl_table_get_column_min(lt_order, LINETAB_LAMBDAC),
1300  max_lambda = cpl_table_get_column_max(lt_order, LINETAB_LAMBDAC),
1301  min_disp = cpl_table_get_column_min(lt_order, LINETAB_PIXELSIZE)*0.99,
1302  max_disp = cpl_table_get_column_max(lt_order, LINETAB_PIXELSIZE)*1.01));
1303 
1304  uves_free_table(&refer_order);
1305  refer_order = uves_extract_table_rows(line_refer, "Wave", CPL_GREATER_THAN,
1306  min_lambda);
1307  uves_extract_table_rows_local(refer_order, "Wave", CPL_LESS_THAN,
1308  max_lambda);
1309 
1310  /* Convert to vectors */
1311  {
1312  int i;
1313  uves_free_vector(&peaks);
1314  peaks = cpl_vector_new(cpl_table_get_nrow(lt_order));
1315  for (i = 0; i < cpl_vector_get_size(peaks); i++)
1316  {
1317  cpl_vector_set(peaks, i, cpl_table_get_double(lt_order, "X", i, NULL));
1318  }
1319 
1320  uves_free_vector(&lines);
1321  lines = cpl_vector_new(cpl_table_get_nrow(refer_order));
1322  for (i = 0; i < cpl_vector_get_size(lines); i++)
1323  {
1324  cpl_vector_set(lines, i, cpl_table_get_double(refer_order, "Wave", i, NULL));
1325  }
1326  }
1327 
1328  /* Not sure if this is necessary for the PPM algorithm */
1329  cpl_vector_sort(peaks, 1);
1330  cpl_vector_sort(lines, 1);
1331 
1332  uves_msg_debug("Call ppm with %" CPL_SIZE_FORMAT " peaks, %" CPL_SIZE_FORMAT " lines, dispersion range = %f - %f A/pixel",
1333  cpl_vector_get_size(peaks),
1334  cpl_vector_get_size(lines),
1335  min_disp, max_disp);
1336 
1337  uves_free_bivector(&ids);
1338 
1339  ids = cpl_ppm_match_positions(peaks, lines,
1340  min_disp, max_disp,
1341  tolerance,
1342  NULL, NULL);
1343 
1344 
1345  if (ids == NULL)
1346  {
1347  uves_msg_warning("Order %d: Point pattern matching failed", order);
1348  if (cpl_error_get_code() != CPL_ERROR_NONE)
1349  {
1350  uves_msg_debug("%s at %s", cpl_error_get_message(),
1351  cpl_error_get_where());
1352  uves_error_reset();
1353  }
1354  }
1355  else
1356  {
1357  int i, j;
1358 
1359  uves_msg_debug("%" CPL_SIZE_FORMAT " identifications from point pattern matching (order %d)",
1360  cpl_bivector_get_size(ids), order);
1361 
1362  result += cpl_bivector_get_size(ids);
1363 
1364  for (i = 0; i < cpl_table_get_nrow(linetable); i++) {
1365 
1366  if (cpl_table_get_int(linetable, "Order", i, NULL) == order)
1367  for (j = 0; j < cpl_bivector_get_size(ids); j++)
1368  {
1369  if (fabs(cpl_table_get_double(linetable, "X", i, NULL) -
1370  cpl_bivector_get_x_data(ids)[j]) < 0.001)
1371  cpl_table_set_double(linetable, "Ident_ppm", i,
1372  cpl_bivector_get_y_data(ids)[j]);
1373  }
1374  }
1375  }
1376  }
1377 
1378  cleanup:
1379  uves_free_table(&lt_order);
1380  uves_free_table(&refer_order);
1381  uves_free_vector(&peaks);
1382  uves_free_vector(&lines);
1383  uves_free_bivector(&ids);
1384 
1385  return result;
1386 }