IIINSTRUMENT Pipeline Reference Manual 6.2.5
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
102static int isaac_wavelength_count_lines(const cpl_bivector *, double, double);
103static int isaac_wavelength_count_linez(const cpl_bivector *, double, double);
104static cpl_bivector * isaac_wavelength_load_catalog(const char *, const char *,
105 const char *, const char *);
106static isaac_band isaac_get_band(const char *);
107static const char * isaac_get_filtername(isaac_band);
108static double get_line_offset(const cpl_bivector *, const cpl_vector *,
109 int, int, double, int, int, int, double, const double *,
110 double *, double *);
111static double isaac_wavelength_centroid(const cpl_vector *, int, int);
112static 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/*----------------------------------------------------------------------------*/
195computed_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/*----------------------------------------------------------------------------*/
535int 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/*----------------------------------------------------------------------------*/
622int 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/*----------------------------------------------------------------------------*/
706double 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/*----------------------------------------------------------------------------*/
766static 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/*----------------------------------------------------------------------------*/
794static 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/*----------------------------------------------------------------------------*/
821static 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/*----------------------------------------------------------------------------*/
936static 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/*----------------------------------------------------------------------------*/
962static 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/*----------------------------------------------------------------------------*/
1000static 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/*----------------------------------------------------------------------------*/
1125static 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/*----------------------------------------------------------------------------*/
1194static 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_filter(const cpl_propertylist *plist)
find out the filter
Definition: isaac_pfits.c:880
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_opti1_id(const cpl_propertylist *plist)
find out the OPTI1.ID key
Definition: isaac_pfits.c:700
const char * isaac_pfits_get_resolution(const cpl_propertylist *plist)
find out the resolution
Definition: isaac_pfits.c:775