IIINSTRUMENT Pipeline Reference Manual  6.2.2
isaac_wavelength.c
1 /* $Id: isaac_wavelength.c,v 1.39 2013-03-12 08:06:48 llundin Exp $
2  *
3  * This file is part of the ISAAC Pipeline
4  * Copyright (C) 2002,2003 European Southern Observatory
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1307 USA
19  */
20 
21 /*
22  * $Author: llundin $
23  * $Date: 2013-03-12 08:06:48 $
24  * $Revision: 1.39 $
25  * $Name: not supported by cvs2svn $
26  */
27 
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31 
32 /*-----------------------------------------------------------------------------
33  Includes
34  -----------------------------------------------------------------------------*/
35 
36 #include "irplib_wavecal.h"
37 #include "irplib_utils.h"
38 #include "irplib_polynomial.h"
39 #include "isaac_wavelength.h"
40 #include "isaac_pfits.h"
41 #include "isaac_utils.h"
42 #include "isaac_dfs.h"
43 
44 #include <string.h>
45 #include <math.h>
46 #include <float.h>
47 
48 /*-----------------------------------------------------------------------------
49  Define
50  -----------------------------------------------------------------------------*/
51 
52 #define ISAAC_MAX(A,B) ((A) > (B) ? (A) : (B))
53 #define ISAAC_MIN(A,B) ((A) < (B) ? (A) : (B))
54 
55 #ifndef ISAAC_WFWHM
56 /* FIXME: Find a better value ? */
57 #define ISAAC_WFWHM 4.0
58 #endif
59 
60 #define SLITWIDTH_TO_SIGMA 0.25
61 
62 /* Evaluate 3rd degree (wavelength calibration) polynomial at
63  ipix using Horners method
64  Caveat: Both arguments are evaluated more than once here */
65 #define WAVELEN(poly,ipix) (poly[0] + (ipix) * \
66  (poly[1] + (ipix) * \
67  (poly[2] + (ipix) * \
68  poly[3])))
69 
70 /* Evaluate differentiated 3rd degree (wavelength calibration) polynomial at
71  ipix also using Horners method
72  Caveat: Both arguments are evaluated more than once here */
73 #define WAVEDIF(poly,ipix) (poly[1] + (ipix) * \
74  (poly[2]*2 + (ipix) * \
75  poly[3]*3))
76 
77 /* Evaluate WAVELEN(poly, ipix + 0.5) - WAVELEN(poly, ipix - 0.5) in terms
78  of WAVEDIF(). This is the width of pixel ipix in wavelengths
79  Caveat: Both arguments are evaluated more than once here */
80 #define WAVEDLT(poly,ipix) (0.25*poly[3] + WAVEDIF(poly, ipix))
81 
82 /* Default zone to discard in pixels */
83 #define ZEROPIX_LE 10
84 #define ZEROPIX_RI 10
85 /* Beginning of thermal regime in angstroms */
86 #define THERMAL_START 20000.0
87 
88 /* Number of coefficients in the wavelength calibration polynomial
89  - also the order of the resulting error term */
90 #define CALIB_COEFFS 4
91 
92 /*----------------------------------------------------------------------------*/
96 /*----------------------------------------------------------------------------*/
97 
98 /*-----------------------------------------------------------------------------
99  Function prototypes
100  -----------------------------------------------------------------------------*/
101 
102 static int isaac_wavelength_count_lines(const cpl_bivector *, double, double);
103 static int isaac_wavelength_count_linez(const cpl_bivector *, double, double);
104 static cpl_bivector * isaac_wavelength_load_catalog(const char *, const char *,
105  const char *, const char *);
106 static isaac_band isaac_get_band(const char *);
107 static const char * isaac_get_filtername(isaac_band);
108 static double get_line_offset(const cpl_bivector *, const cpl_vector *,
109  int, int, double, int, int, int, double, const double *,
110  double *, double *);
111 static double isaac_wavelength_centroid(const cpl_vector *, int, int);
112 static computed_disprel * spectro_refine_solution(const cpl_bivector *,
113  const cpl_vector *, int, int, int, double, const double *);
114 
115 /*-----------------------------------------------------------------------------
116  Function codes
117  -----------------------------------------------------------------------------*/
118 
121 /*----------------------------------------------------------------------------*/
194 /*----------------------------------------------------------------------------*/
195 computed_disprel * spectro_compute_disprel(
196  const cpl_image * in,
197  int discard_lo,
198  int discard_hi,
199  int discard_le,
200  int discard_ri,
201  int max_offset,
202  int remove_thermal,
203  const char * table_name,
204  const char * oh,
205  const char * ar,
206  const char * xe,
207  double slit_width,
208  int order,
209  int output_ascii,
210  const double * phdisprel)
211 {
212  cpl_bivector * spec_cat;
213  computed_disprel * solution;
214  double * disprel;
215  cpl_image * collapsed;
216  cpl_image * thresholded;
217  cpl_vector * spec_init;
218  cpl_vector * spec_bg;
219  cpl_vector * spec_bgclean;
220  double * pspec_bgclean;
221  cpl_vector * spec_bgclean_log;
222  double * pspec_bgclean_log;
223  double xc = 0.0;
224  const int npix = cpl_image_get_size_x(in);
225  double wl_min, wl_max;
226  int emil;
227  cpl_size i;
228 
229  cpl_ensure(in != NULL, CPL_ERROR_NULL_INPUT, NULL);
230  cpl_ensure(table_name != NULL, CPL_ERROR_NULL_INPUT, NULL);
231  cpl_ensure(phdisprel != NULL, CPL_ERROR_NULL_INPUT, NULL);
232 
233 
234  /* Initialize */
235  wl_min = WAVELEN(phdisprel, 0.5);
236  wl_max = WAVELEN(phdisprel, npix+0.5);
237 
238  /* Check acceptable wavelength range */
239  if ((wl_min > wl_max) || (wl_min < MIN_WAVELENGTH) ||
240  (wl_max > MAX_WAVELENGTH)) {
241  cpl_msg_error(cpl_func,
242  "in provided wavelength range: [%g %g] ([min max] is [%g %g])",
243  wl_min, wl_max, MIN_WAVELENGTH, MAX_WAVELENGTH);
244  return NULL;
245  }
246 
247  /* Load the catalog */
248  cpl_msg_info(cpl_func, "Load the catalog");
249  /* FIXME: Multiply catalogue wavelengths with otder */
250  if ((spec_cat=isaac_wavelength_load_catalog(table_name, oh,ar,xe))==NULL) {
251  cpl_msg_error(cpl_func, "Cannot load the catalog");
252  return NULL;
253  }
254 
255  if (order > 1) {
256  cpl_vector_multiply_scalar(cpl_bivector_get_x(spec_cat), order);
257  }
258 
259  cpl_msg_info(cpl_func, "Cross correlation");
260  /* Get the number of lines in the catalog for the specified range */
261  emil = isaac_wavelength_count_lines(spec_cat, wl_min, wl_max);
262 
263  cpl_msg_debug(cpl_func, "Spectral order: %d", order);
264  cpl_msg_debug(cpl_func, "First guess poly. wl = f(pix) (pix in 1-%d):",
265  npix);
266  cpl_msg_debug(cpl_func, "f(x) = %g + %g * x + %g * x^2 + %g * x^3\n",
267  phdisprel[0], phdisprel[1], phdisprel[2], phdisprel[3]);
268  cpl_msg_debug(cpl_func, "Spectral range [%g %g] with %02d out of %d lines",
269  wl_min, wl_max, emil, (int)cpl_bivector_get_size(spec_cat));
270 
271  /* Test if some lines are found in the catalog */
272  if (emil < 1) {
273  cpl_msg_error(cpl_func,
274  "No line found in catalog in the specified range - abort");
275  cpl_bivector_delete(spec_cat);
276  return NULL;
277  }
278 
279  /* Threshold the image to remove negative values */
280  thresholded = cpl_image_duplicate(in);
281  if (cpl_image_threshold(thresholded, 0.0, DBL_MAX, 0.0, 0.0)!=CPL_ERROR_NONE) {
282  cpl_msg_error(cpl_func,
283  "thresholding input image: aborting wavelength calibration");
284  cpl_bivector_delete(spec_cat);
285  cpl_image_delete(thresholded);
286  return NULL;
287  }
288 
289  /* Get a list of lines in the image by median-collapsing it over */
290  /* the horizontal direction. With a little help from the median, */
291  /* should get rid of cosmics and other spiky defects. */
292  if ((collapsed = cpl_image_collapse_median_create(thresholded, 0,
293  discard_lo, discard_hi)) == NULL) {
294  cpl_msg_error(cpl_func,
295  "collapsing input image: aborting wavelength calibration");
296  cpl_bivector_delete(spec_cat);
297  cpl_image_delete(thresholded);
298  return NULL;
299  }
300  cpl_image_delete(thresholded);
301 
302  /* Put the spectrum in a vector */
303  spec_init = cpl_vector_new_from_image_row(collapsed, 1);
304  cpl_image_delete(collapsed);
305 
306  /* Remove thermal background contributions above THERMAL_START */
307  spec_bgclean = cpl_vector_duplicate(spec_init);
308  if ((remove_thermal!=0&&wl_max>THERMAL_START) || !strcmp(table_name,"oh")){
309  cpl_msg_debug(cpl_func, "Removing low-frequency background");
310  /* Filter away very wide (8 * slit_width) features */
311  if ((spec_bg = cpl_vector_filter_median_create(spec_init,
312  (int)((0.5+8*slit_width)/2))) == NULL) {
313  cpl_msg_error(cpl_func, "sub_lowpass failed");
314  cpl_bivector_delete(spec_cat);
315  cpl_vector_delete(spec_init);
316  cpl_vector_delete(spec_bgclean);
317  return NULL;
318  }
319  cpl_vector_subtract(spec_bgclean, spec_bg);
320  cpl_vector_delete(spec_bg);
321  /* Threshold negative intensities */
322  pspec_bgclean = cpl_vector_get_data(spec_bgclean);
323  for (i=0; i<cpl_vector_get_size(spec_bgclean); i++)
324  if (pspec_bgclean[i] < 0) pspec_bgclean[i] = 0.0;
325  }
326 
327  /* See if default zeroing widths have been requested */
328  if (discard_le < 0 || discard_le >= npix) discard_le = ZEROPIX_LE;
329  if (discard_ri < 0 || discard_le + discard_ri >= npix) {
330  if (!strcmp(table_name, "oh") && wl_max > THERMAL_START) {
331  /* Calibrate using OH lines */
332  /* Thermal regime: discard the right side of the signal */
333  discard_ri = npix/2;
334  } else {
335  /* Calibrate using standard lamps */
336  discard_ri = ZEROPIX_RI;
337  }
338  }
339 
340  /* Put less weight on the intensity by taking the logarithm */
341  /* Add 1 to ensure continuity around zero */
342  spec_bgclean_log = cpl_vector_duplicate(spec_bgclean);
343  pspec_bgclean = cpl_vector_get_data(spec_bgclean);
344  pspec_bgclean_log = cpl_vector_get_data(spec_bgclean_log);
345  for (i=0; i<cpl_vector_get_size(spec_bgclean_log); i++)
346  pspec_bgclean_log[i] = pspec_bgclean[i] > 0.0 ?
347  log1p(pspec_bgclean[i]) : 0.0;
348 
349  disprel = cpl_malloc(CALIB_COEFFS * sizeof(double));
350 
351  if (1) { /* Start of irplib_wavecal based calibration */
352  irplib_line_spectrum_model model;
353 
354  const double wfwhm = ISAAC_WFWHM;
355  const double slitw = slit_width;
356  const double xtrunc = 0.5 * slitw + 5.0 * wfwhm * CPL_MATH_SIG_FWHM;
357 
358  /* Extract the interesting part of the spectrum */
359  cpl_vector * spec_short = cpl_vector_extract(spec_bgclean_log,
360  discard_le,
361  npix-discard_ri-1, 1);
362 
363  const int short_size = cpl_vector_get_size(spec_short);
364 
365  /* One practical limitation on hshiftmax is that it may not be so big,
366  as to cause phdisp to be evaluated outside its range of monotony. */
367  const int hshiftmax = ISAAC_MIN(short_size/4, max_offset);
368 
369  /* Initialize to zero */
370  cpl_vector * linepix
371  = cpl_vector_wrap(cpl_bivector_get_size(spec_cat),
372  cpl_calloc(cpl_bivector_get_size(spec_cat),
373  sizeof(double)));
374  cpl_vector * erftmp
375  = cpl_vector_wrap(1, cpl_calloc(1, sizeof(double)));
376  cpl_polynomial * phshift = cpl_polynomial_new(1);
377  cpl_error_code error = CPL_ERROR_NONE;
378  const int fitdeg = emil >= CALIB_COEFFS
379  ? CALIB_COEFFS-1 : emil-1;
380  double pixstep = 0.5;
381  double pixtol = 1e-5;
382  const int maxite = fitdeg * 200;
383  int maxfail = 3;
384  int maxcont = 1;
385  const unsigned clines
386  = (unsigned)(xtrunc*(short_size + 2 * hshiftmax));
387  const cpl_boolean doplot /* FIXME: Function parameter */
388  = getenv("ISAAC_DOPLOT") ? CPL_TRUE : CPL_FALSE;
389 
390  memset(&model, 0, sizeof(model));
391  model.wslit = slitw;
392  model.wfwhm = wfwhm;
393  model.xtrunc = xtrunc;
394  model.lines = spec_cat;
395  model.linepix = linepix;
396  model.erftmp = erftmp;
397  model.cost = 0;
398  model.xcost = 0;
399 
400  for (i = 0; i < CALIB_COEFFS; i++) { /* Revert order for efficiency */
401  error |= cpl_polynomial_set_coeff(phshift, &i, phdisprel[i]);
402  }
403  error |= cpl_polynomial_shift_1d(phshift, 0, discard_le);
404 
405  error |= irplib_polynomial_find_1d_from_correlation_all
406  (phshift, fitdeg, spec_short, 0, clines,
407  (irplib_base_spectrum_model*)&model,
408  irplib_vector_fill_logline_spectrum_fast, pixtol, pixstep,
409  hshiftmax, maxite, maxfail, maxcont, doplot, &xc);
410 
411  error |= irplib_polynomial_find_1d_from_correlation_all
412  (phshift, fitdeg, spec_short, 1, clines,
413  (irplib_base_spectrum_model*)&model,
414  irplib_vector_fill_logline_spectrum, pixtol, pixstep,
415  hshiftmax, maxite, maxfail, maxcont, doplot, &xc);
416 
417  cpl_vector_delete(spec_short);
418  cpl_vector_delete(linepix);
419  cpl_vector_delete(erftmp);
420 
421  error |= cpl_polynomial_shift_1d(phshift, 0, -discard_le);
422 
423  if (!error) {
424  cpl_msg_info(cpl_func, "XC: %g (cost=%u:%u. lines=%u)", xc,
425  (unsigned)model.cost, (unsigned)model.xcost,
426  (unsigned)model.ulines);
427 
428  for (i = 0; i < CALIB_COEFFS; i++) {
429  disprel[i] = cpl_polynomial_get_coeff(phshift, &i);
430  }
431  } else {
432  cpl_error_set_where(cpl_func);
433  for (i = 0; i < CALIB_COEFFS; i++) {
434  disprel[i] = phdisprel[i];
435  }
436  }
437 
438  cpl_polynomial_delete(phshift);
439 
440  } /* End of irplib_wavecal based calibration */
441 
442  if ((discard_le>0) || (discard_ri>0)) {
443  double wl_min_z = wl_min;
444  double wl_max_z = wl_max;
445  int emil_z;
446  if (discard_le>0) wl_min_z = WAVELEN(phdisprel, discard_le+0.5);
447  if (discard_ri>0) wl_max_z = WAVELEN(phdisprel, npix-discard_ri+0.5);
448 
449  emil_z = isaac_wavelength_count_lines(spec_cat, wl_min_z, wl_max_z);
450 
451  cpl_msg_debug(cpl_func,
452  "Zeroed calibration signal [%g %g] has %d lines, dropped %d",
453  wl_min_z, wl_max_z, emil_z, emil-emil_z);
454  }
455 
456  for (i=0; i<CALIB_COEFFS; i++)
457  cpl_msg_debug(cpl_func, "Coef nb. %d correction rate: %g / %g = %g",
458  (int)i+1, disprel[i], phdisprel[i], phdisprel[i] != 0 ?
459  disprel[i]/ phdisprel[i] : disprel[i]);
460 
461  /* Narrow refinable range by HALF_CENTROID_DOMAIN */
462  solution = spectro_refine_solution(spec_cat, spec_bgclean, discard_le+5,
463  discard_ri-5, npix, slit_width, disprel);
464  if (solution == NULL) {
465  cpl_free(disprel);
466  cpl_bivector_delete(spec_cat);
467  cpl_vector_delete(spec_init);
468  cpl_vector_delete(spec_bgclean);
469  cpl_vector_delete(spec_bgclean_log);
470  return NULL;
471  }
472 
473  /* Produce some ASCII files with the computed signals */
474  if (output_ascii) {
475  FILE * out_file;
476  /* First guess solution */
477  out_file = fopen("collapsed_physical.txt", "w");
478  for (i=0; i<npix; i++)
479  fprintf(out_file, "%g\t%g\n", WAVELEN(phdisprel, i+1),
480  cpl_vector_get(spec_init, i));
481  fclose(out_file);
482 
483  /* Computed solution */
484  out_file = fopen("collapsed_calibrated.txt", "w");
485  for (i=0; i<npix; i++)
486  fprintf(out_file, "%g\t%g\n", WAVELEN(disprel, i+1),
487  cpl_vector_get(spec_init, i));
488  fclose(out_file);
489 
490  /* Computed solution with the low frequency part removed */
491  out_file = fopen("collapsed_calibrated_filtered.txt", "w");
492  for (i=0; i<npix; i++)
493  fprintf(out_file, "%g\t%g\n", WAVELEN(disprel, i+1),
494  cpl_vector_get(spec_bgclean, i));
495  fclose(out_file);
496  }
497 
498  /* Free memory */
499  cpl_vector_delete(spec_init);
500  cpl_vector_delete(spec_bgclean);
501  cpl_vector_delete(spec_bgclean_log);
502  cpl_bivector_delete(spec_cat);
503 
504  /* Display results */
505  cpl_msg_debug(cpl_func, "Computed poly. wave = f(pix) (pix in 1-%d):",npix);
506  cpl_msg_debug(cpl_func, "f(x) = %g + %g * x + %g * x^2 + %g * x^3",
507  disprel[0], disprel[1], disprel[2], disprel[3]);
508  cpl_msg_debug(cpl_func, "Spectral range [%g %g]",
509  WAVELEN(disprel,1), WAVELEN(disprel,npix));
510 
511  /* Fill in the solution and remaining parameters */
512  solution->poly = disprel;
513  solution->degree = CALIB_COEFFS-1;
514  solution->cc = xc;
515  solution->offset = (disprel[0] - phdisprel[0]) /
516  WAVEDLT(disprel, 0.5 * (1 + npix));
517  solution->scal1 = disprel[1] / phdisprel[1];
518  solution->scal2 = disprel[2];
519  if (phdisprel[2] != 0) solution->scal2 /= phdisprel[2];
520  solution->scal3 = disprel[3];
521  if (phdisprel[3] != 0) solution->scal3 /= phdisprel[3];
522  disprel = NULL;
523 
524  /* Return the solution */
525  return solution;
526 }
527 
528 /*----------------------------------------------------------------------------*/
534 /*----------------------------------------------------------------------------*/
535 int isaac_find_order(const char * image_name)
536 {
537  int order;
538  const char * sval;
539  cpl_propertylist * plist;
540  char grat_name[128];
541  double wl_c;
542  isaac_band band;
543 
544  /* Initialize */
545  order = 1;
546  plist=cpl_propertylist_load(image_name, 0);
547 
548  /* Get the grating name */
549  if ((sval = isaac_pfits_get_resolution(plist)) == NULL) {
550  cpl_msg_error(cpl_func, "cannot get resolution from [%s]", image_name);
551  cpl_propertylist_delete(plist);
552  return -1;
553  }
554  strcpy(grat_name, sval);
555  /* Get the central wavelength */
556  wl_c = isaac_pfits_get_wlen(plist);
557  if (cpl_error_get_code()) {
558  cpl_msg_error(cpl_func, "cannot get central wlen from [%s]", image_name);
559  cpl_propertylist_delete(plist);
560  return -1;
561  }
562  wl_c *= 10000.0; /* microns to A */
563  /* Get the filter used */
564  if ((sval = isaac_pfits_get_filter(plist)) == NULL) {
565  cpl_msg_error(cpl_func, "cannot get filter from [%s]", image_name);
566  cpl_propertylist_delete(plist);
567  return -1;
568  }
569  band = isaac_get_band(sval);
570  cpl_propertylist_delete(plist);
571 
572  /* Association rules - Medium resolution */
573  if (grat_name[0] == 'M' && band == ISAAC_BAND_SH &&
574  27000 < wl_c && wl_c < 42000) order = 2;
575 
576  if (grat_name[0] == 'M' && band == ISAAC_BAND_JBLOCK &&
577  35500 < wl_c && wl_c < 42000) order = 3;
578 
579  /* This association is currently only relevant for historical data */
580  if (grat_name[0] == 'M' && band == ISAAC_BAND_SK &&
581  44000 < wl_c && wl_c < 51000) order = 2;
582 
583  if (grat_name[0] == 'M' && band == ISAAC_BAND_SH &&
584  44000 < wl_c && wl_c < 51000) order = 3;
585 
586  /* This association is currently only relevant for historical data */
587  if (grat_name[0] == 'M' && band == ISAAC_BAND_JBLOCK &&
588  44000 < wl_c && wl_c < 51000) order = 4;
589 
590  /* Association rules - Low resolution */
591 #if 1
592  /* verify with DFO */
593  /* This association is currently only relevant for historical data */
594  if (grat_name[0] == 'L' && band == ISAAC_BAND_SK &&
595  35500 < wl_c && wl_c < 42000) order = 2;
596 
597  /* This association is currently only relevant for historical data */
598  if (grat_name[0] == 'L' && band == ISAAC_BAND_SH &&
599  35500 < wl_c && wl_c < 42000) order = 2;
600 
601  /* This association is currently only relevant for historical data */
602  if (grat_name[0] == 'L' && band == ISAAC_BAND_JBLOCK &&
603  35500 < wl_c && wl_c < 42000) order = 3;
604 #endif
605 
606  if (grat_name[0] == 'L' && band == ISAAC_BAND_SH &&
607  44000 < wl_c && wl_c < 51000) order = 3;
608 
609  cpl_msg_debug(cpl_func,
610  "Find order: %d. Resol: %c. Lambda_c: %g. Filter: %s",
611  order, grat_name[0], wl_c, isaac_get_filtername(band));
612  return order;
613 }
614 
615 /*----------------------------------------------------------------------------*/
621 /*----------------------------------------------------------------------------*/
622 int isaac_has_thermal(const char * im_name)
623 {
624  const char * sval;
625  cpl_propertylist * plist;
626  char grat_name[128];
627  double wl_c;
628  isaac_band band;
629  int has_thermal = 0;
630 
631  /* Initialize */
632  plist=cpl_propertylist_load(im_name, 0);
633 
634  /* Get the grating name */
635  if ((sval = isaac_pfits_get_resolution(plist)) == NULL) {
636  cpl_msg_error(cpl_func, "cannot get resolution from [%s]", im_name);
637  cpl_propertylist_delete(plist);
638  return -1;
639  }
640  strcpy(grat_name, sval);
641  /* Get the central wavelength */
642  wl_c = isaac_pfits_get_wlen(plist);
643  if (cpl_error_get_code()) {
644  cpl_msg_error(cpl_func, "cannot get central wlen from [%s]", im_name);
645  cpl_propertylist_delete(plist);
646  return -1;
647  }
648  wl_c *= 10000.0; /* microns to A */
649  /* Get the filter used */
650  if ((sval = isaac_pfits_get_filter(plist)) == NULL) {
651  cpl_msg_error(cpl_func, "cannot get filter from [%s]", im_name);
652  cpl_propertylist_delete(plist);
653  return -1;
654  }
655  band = isaac_get_band(sval);
656  cpl_propertylist_delete(plist);
657 
658  /* LW LR SK 2.20 Xe - Added Ar after testing
659  - wl_c will currently only deviate from 2.22 in historical data */
660  if (grat_name[0] == 'L' && band == ISAAC_BAND_SK &&
661  21900 <= wl_c) has_thermal = 1;
662 
663  /* LW LR SL 3.55 Xe+Ar */
664  /* wl_c can in some (rare and actually unsupported) cases be lower */
665  if (grat_name[0] == 'L' && band == ISAAC_BAND_SL &&
666  34000 <= wl_c) has_thermal = 1;
667 
668  /* LW LR SH 3.55 Xe+Ar */
669  /* This association is currently only relevant for historical data */
670  /* The above limit for wl_c is chosen */
671  if (grat_name[0] == 'L' && band == ISAAC_BAND_SH &&
672  34000 <= wl_c && wl_c < 37000) has_thermal = 1;
673 
674  /* LW MR SK 2.35 - Added this after testing
675  - 2.2 has no thermal background, while 2.26463 has */
676  if (grat_name[0] == 'M' && band == ISAAC_BAND_SK &&
677  22500 <= wl_c) has_thermal = 1;
678 
679  /* LW MR SL 3.30 */
680  /* - and above (incl. 4.08) added after testing */
681  if (grat_name[0] == 'M' && band == ISAAC_BAND_SL &&
682  30000 <= wl_c) has_thermal = 1;
683 
684  /* LW MR SH Xe+Ar - Added after testing */
685  if (grat_name[0] == 'M' && band == ISAAC_BAND_SH &&
686  32000 <= wl_c) has_thermal = 1;
687 
688  /* LW MR J+Block Xe+Ar - Added after testing */
689  if (grat_name[0] == 'M' && band == ISAAC_BAND_JBLOCK &&
690  34000 <= wl_c) has_thermal = 1;
691 
692  cpl_msg_debug(cpl_func,
693  "Has thermal: %d. Resol: %c. Lambda_c: %g. Filter: %s",
694  has_thermal, grat_name[0], wl_c, isaac_get_filtername(band));
695 
696  return has_thermal;
697 }
698 
699 /*----------------------------------------------------------------------------*/
705 /*----------------------------------------------------------------------------*/
706 double isaac_get_slitwidth(const char * filename)
707 {
708  const char * sval;
709  double slit_width;
710  double pscale;
711  cpl_propertylist * plist;
712 
713  /* Initialize */
714  plist=cpl_propertylist_load(filename, 0);
715 
716  /* Get the slit name used */
717  if ((sval = isaac_pfits_get_opti1_id(plist)) == NULL) {
718  cpl_msg_error(cpl_func, "cannot get slit used");
719  cpl_propertylist_delete(plist);
720  return -1.0;
721  }
722 
723  /* Get the slit width in arcseconds */
724  if (!strcmp(sval, "slit_1")) slit_width = 1;
725  else if (!strcmp(sval, "slit_2")) slit_width = 2;
726  else if (!strcmp(sval, "slit_0.3_tilted")) slit_width = 0.3;
727  else if (!strcmp(sval, "slit_0.8")) slit_width = 0.8;
728  else if (!strcmp(sval, "slit_1.5")) slit_width = 1.5;
729  else if (!strcmp(sval, "slit_0.6_tilted")) slit_width = 0.6;
730  else {
731  cpl_msg_error(cpl_func, "unrecognized slit");
732  cpl_propertylist_delete(plist);
733  return -1.0;
734  }
735 
736  /* Get the pixelscale and convert arsec -> pixels */
737  pscale = isaac_pfits_get_pixscale(plist);
738  if (cpl_error_get_code()) {
739  cpl_msg_error(cpl_func, "cannot get pixscale");
740  cpl_propertylist_delete(plist);
741  return -1.0;
742  }
743  cpl_propertylist_delete(plist);
744 
745  cpl_msg_debug(cpl_func, "Slit width = %g arcsec (%-.2f pixels)\n",
746  slit_width, slit_width / pscale);
747 
748  slit_width /= pscale;
749 
750  /* Return */
751  return slit_width;
752 }
753 
757 /*----------------------------------------------------------------------------*/
765 /*----------------------------------------------------------------------------*/
766 static int isaac_wavelength_count_lines(const cpl_bivector * lines,
767  double wave_min,
768  double wave_max)
769 {
770  const int cat_nlines = cpl_bivector_get_size(lines);
771  const double * px = cpl_bivector_get_x_data_const(lines);
772  const double * py = cpl_bivector_get_y_data_const(lines);
773  int nb_lines = 0;
774  int i = 0;
775 
776  cpl_ensure(lines != NULL, CPL_ERROR_NULL_INPUT, -1);
777 
778  while (i < cat_nlines && px[i] < wave_min) i++;
779  while (i < cat_nlines && px[i] < wave_max)
780  if (py[i++] > 0) nb_lines++;
781 
782  return nb_lines;
783 }
784 
785 /*----------------------------------------------------------------------------*/
793 /*----------------------------------------------------------------------------*/
794 static int isaac_wavelength_count_linez(const cpl_bivector * lines,
795  double wave_min,
796  double wave_max)
797 {
798  const int cat_nlines = cpl_bivector_get_size(lines);
799  const double * px = cpl_bivector_get_x_data_const(lines);
800  int nb_lines = 0;
801  int i = 0;
802 
803  cpl_ensure(lines != NULL, CPL_ERROR_NULL_INPUT, -1);
804 
805  while (i < cat_nlines && px[i] < wave_min) i++;
806  while (i < cat_nlines && px[i++] < wave_max) nb_lines++;
807 
808  return nb_lines;
809 }
810 
811 /*----------------------------------------------------------------------------*/
820 /*----------------------------------------------------------------------------*/
821 static cpl_bivector * isaac_wavelength_load_catalog(
822  const char * name,
823  const char * oh,
824  const char * ar,
825  const char * xe)
826 {
827  cpl_bivector * sig;
828  double * psigx;
829  double * psigy;
830  double * pcat_tab1_wave;
831  double * pcat_tab1_emiss;
832  int nlines;
833  cpl_table * cat_tab1;
834  cpl_table * cat_tab2;
835  cpl_propertylist * reflist;
836  int i;
837 
838  /* Check entries */
839  if (name == NULL) return NULL;
840 
841  /* Handle the table names and load the tables */
842  if (!strcmp(name, "oh")) {
843  /* Check if the table is there */
844  if (oh == NULL) {
845  cpl_msg_error(cpl_func, "Please provide the OH lines catalog");
846  return NULL;
847  }
848  /* Load the table */
849  if ((cat_tab1 = cpl_table_load(oh, 1, 0)) == NULL) {
850  cpl_msg_error(cpl_func, "Cannot load the catalog");
851  return NULL;
852  }
853  } else if (!strcmp(name, "Xe")) {
854  /* Check if the table is there */
855  if (xe == NULL) {
856  cpl_msg_error(cpl_func, "Please provide the XE lines catalog");
857  return NULL;
858  }
859  /* Load the table */
860  if ((cat_tab1 = cpl_table_load(xe, 1, 0)) == NULL) {
861  cpl_msg_error(cpl_func, "Cannot load the XE catalog");
862  return NULL;
863  }
864  } else if (!strcmp(name, "Ar")) {
865  /* Check if the table is there */
866  if (ar == NULL) {
867  cpl_msg_error(cpl_func, "Please provide the AR lines catalog");
868  return NULL;
869  }
870  /* Load the table */
871  if ((cat_tab1 = cpl_table_load(ar, 1, 0)) == NULL) {
872  cpl_msg_error(cpl_func, "Cannot load the AR catalog");
873  return NULL;
874  }
875  } else if (!strcmp(name, "Xe+Ar")) {
876  /* Check if the table is there */
877  if (xe == NULL) {
878  cpl_msg_error(cpl_func, "Please provide the XE lines catalog");
879  return NULL;
880  }
881  /* Load the table */
882  if ((cat_tab1 = cpl_table_load(xe, 1, 0)) == NULL) {
883  cpl_msg_error(cpl_func, "Cannot load the XE catalog");
884  return NULL;
885  }
886  /* Check if the table is there */
887  if (ar == NULL) {
888  cpl_msg_error(cpl_func, "Please provide the AR lines catalog");
889  return NULL;
890  }
891  /* Load the table */
892  if ((cat_tab2 = cpl_table_load(ar, 1, 0)) == NULL) {
893  cpl_msg_error(cpl_func, "Cannot load the AR catalog");
894  cpl_table_delete(cat_tab1);
895  return NULL;
896  }
897  /* Merge the tables */
898  if (cpl_table_insert(cat_tab1, cat_tab2,
899  cpl_table_get_nrow(cat_tab1)) != CPL_ERROR_NONE) {
900  cpl_msg_error(cpl_func, "Cannot merge tables");
901  cpl_table_delete(cat_tab1);
902  cpl_table_delete(cat_tab2);
903  return NULL;
904  }
905  cpl_table_delete(cat_tab2);
906  } else return NULL;
907 
908  /* Sort the table */
909  reflist = cpl_propertylist_new();
910  cpl_propertylist_append_bool(reflist, ISAAC_COL_WAVELENGTH, 0);
911  cpl_table_sort(cat_tab1, reflist);
912  cpl_propertylist_delete(reflist);
913 
914  /* Create the bivector */
915  nlines = cpl_table_get_nrow(cat_tab1);
916  sig = cpl_bivector_new(nlines);
917  psigx = cpl_bivector_get_x_data(sig);
918  psigy = cpl_bivector_get_y_data(sig);
919  pcat_tab1_wave = cpl_table_get_data_double(cat_tab1, ISAAC_COL_WAVELENGTH);
920  pcat_tab1_emiss = cpl_table_get_data_double(cat_tab1, ISAAC_COL_EMISSION);
921  for (i=0; i<nlines; i++) {
922  psigx[i] = pcat_tab1_wave[i];
923  psigy[i] = pcat_tab1_emiss[i];
924  }
925  cpl_table_delete(cat_tab1);
926  return sig;
927 }
928 
929 /*----------------------------------------------------------------------------*/
935 /*----------------------------------------------------------------------------*/
936 static isaac_band isaac_get_band(const char * filter)
937 {
938  if (filter == NULL) return ISAAC_BAND_UNKNOWN;
939  if (!strcmp(filter, "Z")) return ISAAC_BAND_Z;
940  if (!strcmp(filter, "SZ")) return ISAAC_BAND_SZ;
941  if (!strcmp(filter, "Js")) return ISAAC_BAND_JS;
942  if (!strcmp(filter, "J")) return ISAAC_BAND_J;
943  if (!strcmp(filter, "J+Block")) return ISAAC_BAND_JBLOCK;
944  if (!strcmp(filter, "SH")) return ISAAC_BAND_SH;
945  if (!strcmp(filter, "H")) return ISAAC_BAND_H;
946  if (!strcmp(filter, "Ks")) return ISAAC_BAND_KS;
947  if (!strcmp(filter, "SK")) return ISAAC_BAND_SK;
948  if (!strcmp(filter, "K")) return ISAAC_BAND_K;
949  if (!strcmp(filter, "SL")) return ISAAC_BAND_SL;
950  if (!strcmp(filter, "L")) return ISAAC_BAND_L;
951  if (!strcmp(filter, "M")) return ISAAC_BAND_M;
952  return ISAAC_BAND_UNKNOWN;
953 }
954 
955 /*----------------------------------------------------------------------------*/
961 /*----------------------------------------------------------------------------*/
962 static const char * isaac_get_filtername(isaac_band band)
963 {
964  if (band == ISAAC_BAND_UNKNOWN) return NULL;
965  if (band == ISAAC_BAND_Z) return "Z";
966  if (band == ISAAC_BAND_SZ) return "SZ";
967  if (band == ISAAC_BAND_JS) return "Js";
968  if (band == ISAAC_BAND_J) return "J";
969  if (band == ISAAC_BAND_JBLOCK) return "J+Block";
970  if (band == ISAAC_BAND_SH) return "SH";
971  if (band == ISAAC_BAND_H) return "H";
972  if (band == ISAAC_BAND_KS) return "Ks";
973  if (band == ISAAC_BAND_SK) return "SK";
974  if (band == ISAAC_BAND_K) return "K";
975  if (band == ISAAC_BAND_SL) return "SL";
976  if (band == ISAAC_BAND_L) return "L";
977  if (band == ISAAC_BAND_M) return "M";
978 
979  return NULL;
980 }
981 
982 
983 /*----------------------------------------------------------------------------*/
999 /*----------------------------------------------------------------------------*/
1000 static double get_line_offset(
1001  const cpl_bivector * cat,
1002  const cpl_vector * spec,
1003  int pix_low,
1004  int ipix,
1005  double isub,
1006  int pix_high,
1007  int iline,
1008  int nline,
1009  double slit_width,
1010  const double * disprel,
1011  double * poffset,
1012  double * pmaxval)
1013 {
1014  double offset;
1015  const double * px = cpl_bivector_get_x_data_const(cat);
1016  const double * py = cpl_bivector_get_y_data_const(cat);
1017  const double wl = px[iline];
1018  /* The expected location with sub-pixel precision */
1019  const double spix = 1+ipix + isub;
1020  double lastval;
1021  double relint;
1022  const double sigma = slit_width * SLITWIDTH_TO_SIGMA;
1023  /* Tolerance for noise at emission-line center */
1024  const double centernoise = 1.25;
1025  const int mpix = pix_high-pix_low+1;
1026  const int maxdist = slit_width;
1027  const double * pspec = cpl_vector_get_data_const(spec);
1028  double maxval;
1029  int maxpos;
1030  int imin, imax;
1031  int i;
1032 
1033  cpl_ensure(pmaxval != NULL, CPL_ERROR_NULL_INPUT, -1);
1034 
1035  *pmaxval = -1;
1036  if (sigma <= 0) return -1;
1037  if (mpix <= 2) return -1;
1038 
1039  if (ipix >= cpl_vector_get_size(spec)) {
1040  cpl_msg_error(cpl_func, "IPIX(mpix=%d): %d <= ipix=%d <= %d <= %d",
1041  mpix, pix_low, ipix, pix_high,
1042  (int)cpl_vector_get_size(spec));
1043  return -1.0;
1044  }
1045 
1046  maxval = pspec[ipix];
1047  maxpos = ipix;
1048  for (i=pix_low; i<=pix_high; i++)
1049  if (pspec[i] > maxval) maxval = pspec[maxpos = i];
1050  *pmaxval = maxval;
1051 
1052  if (maxval == 0 ) {
1053  cpl_msg_debug(cpl_func, "0LINE %d (%g) at pixel: %d <= %g <= %d",
1054  nline, wl, 1+pix_low, spix, 1+pix_high);
1055  return -1;
1056  }
1057  lastval = maxval;
1058  imax = maxpos;
1059  while (imax+1 <= pix_high && (pspec[imax+1] < lastval || pspec[imax+1] ==0
1060  || (imax-maxpos < maxdist && pspec[imax+1] < centernoise * lastval)))
1061  lastval = pspec[++imax];
1062 
1063  lastval = maxval;
1064  imin = maxpos;
1065 
1066  while (imin-1 >= pix_low && (pspec[imin-1] < lastval || pspec[imin-1] == 0
1067  || (maxpos-imin < maxdist && pspec[imin-1] < centernoise * lastval)))
1068  lastval = pspec[--imin];
1069 
1070  offset = isaac_wavelength_centroid(spec, imin, imax);
1071 
1072  if (abs(maxpos - ipix) > maxdist ) {
1073  /* This must be another (unidenfitied) line which is
1074  brighter than the catalog line in this interval */
1075  const int plow = maxpos > ipix ? pix_low : (maxpos + ipix)/2;
1076  const int phigh = maxpos < ipix ? pix_high : (maxpos + ipix)/2;
1077  if (offset < 0) {
1078  cpl_msg_debug(cpl_func, "LINE %d (%g) at pixel: %d <= %g/%d <= %d",
1079  nline, WAVELEN(disprel,1+maxpos), 1+pix_low, spix,
1080  1+maxpos, 1+pix_high);
1081  } else {
1082  offset -= isub;
1083  cpl_msg_debug(cpl_func, "LIne %d (%g) at pixel: %d <= %g/%d/%g <= %d",
1084  nline, WAVELEN(disprel,1+offset+imin), 1+pix_low,
1085  spix, 1+maxpos, 1+offset+imin, 1+pix_high);
1086  }
1087  /* Try to find the catalog line */
1088  return get_line_offset(cat, spec, plow, ipix, isub, phigh, iline,
1089  nline, slit_width, disprel, poffset, pmaxval);
1090  }
1091 
1092  relint = 100*py[iline]/(sigma*sqrt(2*4*atan(1))*maxval);
1093 
1094  if (offset < 0) {
1095  /* maxpos too close to boundary or something like that */
1096  cpl_msg_debug(cpl_func, "LINe %d (%g) at pixel: %d/%d <= %g/%d <= %d/%d "
1097  "(%4.2f%%)\n", nline, wl, 1+pix_low, 1+imin, spix, 1+maxpos,
1098  1+imax, 1+pix_high, relint);
1099  return -1;
1100  }
1101  offset -= isub;
1102  *poffset = offset + imin - ipix;
1103 
1104  cpl_msg_debug(cpl_func,
1105  "Line %d (%g) at pixel: %d/%d <= %g/%d/%g <= %d/%d (%4.2f%%) %g",
1106  nline, wl, 1+pix_low, 1+imin, spix, 1+maxpos, 1+offset + imin,
1107  1+imax, 1+pix_high, relint, *poffset);
1108 
1109  return fabs(*poffset);
1110 }
1111 
1112 /*----------------------------------------------------------------------------*/
1124 /*----------------------------------------------------------------------------*/
1125 static double isaac_wavelength_centroid(
1126  const cpl_vector * vec,
1127  int start,
1128  int stop)
1129 {
1130  const double * pvec;
1131  int half_centroid_domain = 5;
1132  double min = 0;
1133  double max;
1134  double centroid;
1135  double weights;
1136  int i, maxpos;
1137 
1138  if (vec==NULL) return -1.0;
1139  pvec = cpl_vector_get_data_const(vec);
1140 
1141  /* Search for the maximum pixel value on the line */
1142  max = pvec[start];
1143  maxpos = start;
1144  for (i=start; i<=stop; i++) {
1145  if (pvec[i]>max) {
1146  max = pvec[i];
1147  maxpos = i;
1148  }
1149  }
1150 
1151  if (maxpos < start + half_centroid_domain ||
1152  maxpos >= stop + 1 - half_centroid_domain) return -1.0;
1153 
1154  /* Centroiding is only defined for non-negative intensities. If the
1155  centroiding region has negative intensities then find the minimum
1156  and offset the signal by this minimum */
1157  for (i=maxpos-half_centroid_domain; i<=maxpos+half_centroid_domain; i++)
1158  if (pvec[i] < min) min = pvec[i];
1159 
1160  /* The centroid pos is the weighted average around the max pixel */
1161  centroid = 0.0;
1162  weights = 0.0;
1163  if (min < 0) {
1164  for (i=maxpos-half_centroid_domain;i<=maxpos+half_centroid_domain;i++) {
1165  centroid += (double)(pvec[i]-min) * (double)i;
1166  weights += (double)(pvec[i]-min);
1167  }
1168  } else {
1169  for (i=maxpos-half_centroid_domain;i<=maxpos+half_centroid_domain;i++) {
1170  centroid += (double)(pvec[i]) * (double)i;
1171  weights += (double)(pvec[i]);
1172  }
1173  }
1174 
1175  if (fabs(weights)<fabs(centroid)*1e-5 ) centroid = -1.0;
1176  else centroid /= weights;
1177 
1178  return centroid - start;
1179 }
1180 
1181 /*----------------------------------------------------------------------------*/
1193 /*----------------------------------------------------------------------------*/
1194 static computed_disprel * spectro_refine_solution(
1195  const cpl_bivector * cat,
1196  const cpl_vector * spec,
1197  int discard_le,
1198  int discard_ri,
1199  int npix,
1200  double slit_width,
1201  const double * disprel)
1202 {
1203  computed_disprel * solution;
1204  const int istart = discard_le > 0 ? discard_le : 0;
1205  const int istop = discard_ri > 0 ? npix-discard_ri : npix;
1206 
1207  const double wl_min = WAVELEN(disprel, 0 + 0.5);
1208  const double wl_max = WAVELEN(disprel, npix + 1.5);
1209 
1210  const int nlines = cpl_bivector_get_size(cat);
1211  const double * px = cpl_bivector_get_x_data_const(cat);
1212  const double * py = cpl_bivector_get_y_data_const(cat);
1213 
1214  double isub = 0;
1215  double sum_offset = 0;
1216  double sum_aboffs = 0;
1217  double sum_sqoffs = 0;
1218  double offset;
1219  double maxval;
1220  double maxint = 0;
1221  double faint = 0;
1222 
1223  int pix_high = 0;
1224  int ical = 0;
1225  int iline = 0;
1226 
1227  const int emil = isaac_wavelength_count_lines(cat, wl_min, wl_max);
1228  const int emilz = isaac_wavelength_count_linez(cat, wl_min, wl_max);
1229  int nline = 0;
1230  int nfound = 0;
1231  int nzero = 0;
1232 
1233 
1234  if (emil < 1) return NULL;
1235 
1236  solution = cpl_malloc(sizeof(*solution));
1237 
1238  /* Find first line within range */
1239  while (iline < nlines && px[iline] < wl_min) iline++;
1240 
1241  /* Do not try to locate the very faint lines on a very crowded spectrum */
1242  if (emilz * (11 + slit_width) > npix) {
1243  int i;
1244 
1245  /* Find the maximum intensity */
1246  for (i=iline; i<nlines; i++) {
1247  if (px[i] > wl_max) break;
1248  if (py[i] > maxint) maxint = py[i];
1249  }
1250  /* Ignore lines with intensity less than 0.01 of the maximum */
1251  faint = 0.01;
1252  cpl_msg_debug(cpl_func,
1253  "No detection of faint lines (I < %g) in crowded spectrum: %g",
1254  maxint * faint, npix/(emilz * slit_width));
1255  }
1256 
1257  while (pix_high < npix - 1) {
1258  double wl;
1259  const double isub_prev = isub;
1260  const int icalprev = ical;
1261  const int pix_low = pix_high;
1262 
1263  /* Do not try to locate the very faint lines
1264  - zero-intensity means unknown intensity - not to be ignored
1265  - also ignore the duplicate OH sky lines */
1266  while (iline<nlines && 0 < py[iline] &&
1267  (py[iline] < maxint * faint ||
1268  (py[iline] == py[iline-1] &&
1269  (px[iline] - px[iline-1])
1270  < 0.5*WAVEDLT(disprel, 1+icalprev)*slit_width))) {
1271  cpl_msg_debug(cpl_func, "Skipping line(%d): %g %g", iline,
1272  px[iline], py[iline]);
1273  iline++;
1274  }
1275 
1276  if (iline < nlines &&
1277  /* The wavelength of the emission line */
1278  (wl = px[iline]) < wl_max) {
1279  double wl_low, wl_high;
1280  double delta;
1281  int i=3;
1282 
1283  /* Find the expected pixel of the emission line
1284  - sample nr. x (with ind i = x-1) has wavelengths
1285  from p(x-0.5) to p(x+0.5) */
1286 
1287  /* More than one wavelength can belong to a pixel */
1288  ical--;
1289 
1290  while (++ical < npix-1 && WAVELEN(disprel, ical+1 + 0.5) < wl);
1291  wl_low = WAVELEN(disprel, ical+1 - 0.5);
1292  wl_high = WAVELEN(disprel, ical+1 + 0.5);
1293 
1294  /* -0.5 <= isub < 0.5 */
1295  isub = (wl - wl_low) / (wl_high - wl_low) - 0.5;
1296  /* Just since its easy: Determine isub with maximum precision
1297  - using Newton-Raphson */
1298  do {
1299  delta = (WAVELEN(disprel, ical+1+isub)-wl)
1300  / WAVEDIF(disprel, ical+1+isub);
1301  isub -= delta;
1302  } while (--i && fabs(delta) > DBL_EPSILON);
1303  /* At this point delta is most likely true 0 :-) */
1304  pix_high = (ical + icalprev)/2;
1305  } else {
1306  pix_high = npix - 1;
1307  }
1308 
1309  if (nline > 0) {
1310  if (get_line_offset(cat, spec, pix_low, icalprev, isub_prev,
1311  pix_high, iline-1, nline, slit_width,
1312  disprel, &offset, &maxval) >=0) {
1313  if (istart <= icalprev && icalprev <= istop) {
1314  /* Only include lines that are not in the discard range */
1315  sum_offset += offset;
1316  sum_aboffs += fabs(offset);
1317  sum_sqoffs += offset * offset;
1318  nfound++;
1319  }
1320  }
1321  if (maxval == 0) nzero++;
1322  }
1323  iline++;
1324  nline++;
1325  }
1326  solution->dlines = nfound;
1327  solution->clines = emilz;
1328  solution->rms = -1;
1329  solution->mean = -1;
1330 
1331  if (nfound > 0) {
1332  double stdev = nfound ==1 ? 0 :
1333  (sum_sqoffs-sum_offset*sum_offset/nfound)/(nfound-1);
1334  stdev = stdev > 0 ? sqrt(stdev) : 0;
1335 
1336  cpl_msg_debug(cpl_func,
1337  "Mean & RMS pixel-offset on calibration (%d:%d:%d:%d): %g %g",
1338  emilz, emilz-emil,nzero, nfound, sum_aboffs/nfound, stdev);
1339 
1340  /* The bias-corrected standard deviation */
1341  solution->rms = stdev;
1342  /* The mean deviation */
1343  solution->mean = sum_aboffs/nfound;
1344  }
1345  return solution;
1346 }
const char * isaac_pfits_get_opti1_id(const cpl_propertylist *plist)
find out the OPTI1.ID key
Definition: isaac_pfits.c:700
double isaac_pfits_get_pixscale(const cpl_propertylist *plist)
find out the pixel scale
Definition: isaac_pfits.c:745
double isaac_pfits_get_wlen(const cpl_propertylist *plist)
find out the central wavelength
Definition: isaac_pfits.c:865
const char * isaac_pfits_get_resolution(const cpl_propertylist *plist)
find out the resolution
Definition: isaac_pfits.c:775
const char * isaac_pfits_get_filter(const cpl_propertylist *plist)
find out the filter
Definition: isaac_pfits.c:880