MUSE Pipeline Reference Manual  1.0.2
muse_flux.c
1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set sw=2 sts=2 et cin: */
3 /*
4  * This file is part of the MUSE Instrument Pipeline
5  * Copyright (C) 2005-2014 European Southern Observatory
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 /*----------------------------------------------------------------------------*
27  * Includes *
28  *----------------------------------------------------------------------------*/
29 #include <cpl.h>
30 #include <math.h>
31 #include <string.h>
32 
33 #include "muse_flux.h"
34 #include "muse_instrument.h"
35 
36 #include "muse_astro.h"
37 #include "muse_cplwrappers.h"
38 #include "muse_pfits.h"
39 #include "muse_quality.h"
40 #include "muse_resampling.h"
41 #include "muse_utils.h"
42 #include "muse_wcs.h"
43 
44 /*----------------------------------------------------------------------------*/
48 /*----------------------------------------------------------------------------*/
49 
52 /*----------------------------------------------------------------------------*/
66 /*----------------------------------------------------------------------------*/
69 {
70  muse_flux_object *flux = cpl_calloc(1, sizeof(muse_flux_object));
71  /* signify non-filled reference coordinates */
72  flux->raref = NAN;
73  flux->decref = NAN;
74  return flux;
75 }
76 
77 /*----------------------------------------------------------------------------*/
87 /*----------------------------------------------------------------------------*/
88 void
90 {
91  if (!aFluxObj) {
92  return;
93  }
94  muse_datacube_delete(aFluxObj->cube);
95  aFluxObj->cube = NULL;
96  muse_image_delete(aFluxObj->intimage);
97  aFluxObj->intimage = NULL;
98  cpl_table_delete(aFluxObj->sensitivity);
99  aFluxObj->sensitivity = NULL;
100  cpl_table_delete(aFluxObj->response);
101  aFluxObj->response = NULL;
102  cpl_table_delete(aFluxObj->telluric);
103  aFluxObj->telluric = NULL;
104  cpl_table_delete(aFluxObj->tellbands);
105  aFluxObj->tellbands = NULL;
106  cpl_free(aFluxObj);
107 }
108 
109 /*----------------------------------------------------------------------------*/
117 /*----------------------------------------------------------------------------*/
118 static double
120 {
121  cpl_ensure(aTable, CPL_ERROR_NULL_INPUT, 0.);
122  cpl_table_unselect_all(aTable);
123  cpl_table_or_selected_double(aTable, "lambda", CPL_NOT_LESS_THAN,
124  kMuseNominalLambdaMin);
125  cpl_table_and_selected_double(aTable, "lambda", CPL_NOT_GREATER_THAN,
126  kMuseNominalLambdaMax);
127  cpl_size nsel = cpl_table_count_selected(aTable);
128  cpl_array *asel = cpl_table_where_selected(aTable);
129  cpl_size *sel = cpl_array_get_data_cplsize(asel);
130  double lmin = cpl_table_get_double(aTable, "lambda", sel[0], NULL),
131  lmax = cpl_table_get_double(aTable, "lambda", sel[nsel - 1], NULL);
132  cpl_array_delete(asel);
133  return (lmax - lmin) / nsel;
134 } /* muse_flux_reference_table_sampling() */
135 
136 /*----------------------------------------------------------------------------*/
166 /*----------------------------------------------------------------------------*/
167 cpl_error_code
169 {
170  cpl_ensure_code(aTable, CPL_ERROR_NULL_INPUT);
171 
172  const char *flam1 = "erg/s/cm**2/Angstrom",
173  *flam2 = "erg/s/cm^2/Angstrom";
174 
175  cpl_error_code rc = CPL_ERROR_NONE;
176  cpl_errorstate prestate = cpl_errorstate_get();
177  /* check two different types of tables: MUSE specific or HST CALSPEC */
178  if (cpl_table_has_column(aTable, "lambda") &&
179  cpl_table_has_column(aTable, "flux") &&
180  cpl_table_get_column_unit(aTable, "lambda") &&
181  cpl_table_get_column_unit(aTable, "flux") &&
182  !strncmp(cpl_table_get_column_unit(aTable, "lambda"), "Angstrom", 9) &&
183  (!strncmp(cpl_table_get_column_unit(aTable, "flux"), flam1, strlen(flam1)) ||
184  !strncmp(cpl_table_get_column_unit(aTable, "flux"), flam2, strlen(flam2)))) {
185  /* normal case: MUSE STD_FLUX_TABLE as specified; still need to *
186  * check, if we need to convert the column types (could be e.g. float) */
187  if (cpl_table_get_column_type(aTable, "lambda") != CPL_TYPE_DOUBLE) {
188  cpl_msg_debug(__func__, "Casting lambda column to double");
189  cpl_table_cast_column(aTable, "lambda", NULL, CPL_TYPE_DOUBLE);
190  }
191  if (cpl_table_get_column_type(aTable, "flux") != CPL_TYPE_DOUBLE) {
192  cpl_msg_debug(__func__, "Casting flux column to double");
193  cpl_table_cast_column(aTable, "flux", NULL, CPL_TYPE_DOUBLE);
194  }
195  /* check optional column */
196  if (cpl_table_has_column(aTable, "fluxerr")) {
197  if (cpl_table_get_column_type(aTable, "fluxerr") != CPL_TYPE_DOUBLE) {
198  cpl_msg_debug(__func__, "Casting fluxerr column to double");
199  cpl_table_cast_column(aTable, "fluxerr", NULL, CPL_TYPE_DOUBLE);
200  }
201  const char *unit = cpl_table_get_column_unit(aTable, "fluxerr");
202  if (!unit || (strncmp(unit, flam1, strlen(flam1)) &&
203  strncmp(unit, flam2, strlen(flam2)))) {
204  cpl_msg_debug(__func__, "Erasing fluxerr column because of unexpected "
205  "unit (%s)", unit);
206  cpl_table_erase_column(aTable, "fluxerr"); /* wrong unit, erase */
207  }
208  } /* if has fluxerr */
209  cpl_msg_info(__func__, "Found MUSE format, average sampling %.3f Angstrom/bin"
210  " over MUSE range", muse_flux_reference_table_sampling(aTable));
211  } else if (cpl_table_has_column(aTable, "WAVELENGTH") &&
212  cpl_table_has_column(aTable, "FLUX") &&
213  cpl_table_get_column_unit(aTable, "WAVELENGTH") &&
214  cpl_table_get_column_unit(aTable, "FLUX") &&
215  !strncmp(cpl_table_get_column_unit(aTable, "WAVELENGTH"), "ANGSTROMS", 10) &&
216  !strncmp(cpl_table_get_column_unit(aTable, "FLUX"), "FLAM", 5)) {
217 #if 0
218  printf("input HST CALSPEC table:\n");
219  cpl_table_dump_structure(aTable, stdout);
220  cpl_table_dump(aTable, cpl_table_get_nrow(aTable)/2, 3, stdout);
221  fflush(stdout);
222 #endif
223  /* other allowed case: HST CALSPEC format */
224  cpl_table_cast_column(aTable, "WAVELENGTH", "lambda", CPL_TYPE_DOUBLE);
225  cpl_table_cast_column(aTable, "FLUX", "flux", CPL_TYPE_DOUBLE);
226  cpl_table_erase_column(aTable, "WAVELENGTH");
227  cpl_table_erase_column(aTable, "FLUX");
228  cpl_table_set_column_unit(aTable, "lambda", "Angstrom");
229  cpl_table_set_column_unit(aTable, "flux", flam1);
230  /* if the table comes with the typical STATERROR/SYSERROR separation, *
231  * convert them into a single combined fluxerr column */
232  if (cpl_table_has_column(aTable, "STATERROR") &&
233  cpl_table_has_column(aTable, "SYSERROR") &&
234  cpl_table_get_column_unit(aTable, "STATERROR") &&
235  cpl_table_get_column_unit(aTable, "SYSERROR") &&
236  !strncmp(cpl_table_get_column_unit(aTable, "STATERROR"), "FLAM", 5) &&
237  !strncmp(cpl_table_get_column_unit(aTable, "SYSERROR"), "FLAM", 5)) {
238  /* Cast to double before, not to lose precision, then compute *
239  * fluxerr = sqrt(STATERROR**2 + SYSERROR**2) */
240  cpl_table_cast_column(aTable, "STATERROR", "fluxerr", CPL_TYPE_DOUBLE);
241  cpl_table_erase_column(aTable, "STATERROR");
242  cpl_table_cast_column(aTable, "SYSERROR", NULL, CPL_TYPE_DOUBLE);
243  cpl_table_power_column(aTable, "fluxerr", 2);
244  cpl_table_power_column(aTable, "SYSERROR", 2);
245  cpl_table_add_columns(aTable, "fluxerr", "SYSERROR");
246  cpl_table_erase_column(aTable, "SYSERROR");
247  cpl_table_power_column(aTable, "fluxerr", 0.5);
248  cpl_table_set_column_unit(aTable, "fluxerr", flam1);
249  } /* if error columns */
250  /* XXX how to handle invalid entries in the STATERROR column *
251  * in telluric regions (e.g. in gd105_005.fits)? */
252  /* XXX how to handle DATAQUAL column? */
253 
254  /* erase further columns we don't need */
255  if (cpl_table_has_column(aTable, "FWHM")) {
256  cpl_table_erase_column(aTable, "FWHM");
257  }
258  if (cpl_table_has_column(aTable, "DATAQUAL")) {
259  cpl_table_erase_column(aTable, "DATAQUAL");
260  }
261  if (cpl_table_has_column(aTable, "TOTEXP")) {
262  cpl_table_erase_column(aTable, "TOTEXP");
263  }
264  /* convert from vacuum to air wavelengths */
265  cpl_size irow, nrow = cpl_table_get_nrow(aTable);
266  for (irow = 0; irow < nrow; irow++) {
267  double lambda = cpl_table_get_double(aTable, "lambda", irow, NULL);
268  cpl_table_set_double(aTable, "lambda", irow,
270  } /* for irow (all table rows) */
271 #if 0
272  printf("converted HST CALSPEC table:\n");
273  cpl_table_dump_structure(aTable, stdout);
274  cpl_table_dump(aTable, cpl_table_get_nrow(aTable)/2, 3, stdout);
275  fflush(stdout);
276 #endif
277  cpl_msg_info(__func__, "Found HST CALSPEC format on input, converted to "
278  "MUSE format; average sampling %.3f Angstrom/bin over MUSE "
279  "range (assumed vacuum wavelengths on input, converted to air).",
281  } else {
282  cpl_msg_error(__func__, "Unknown format found!");
283 #if 0
284  cpl_table_dump_structure(aTable, stdout);
285 #endif
286  rc = CPL_ERROR_INCOMPATIBLE_INPUT;
287  } /* else: no recognized format */
288 
289  /* check for errors in the above (casting!) before returning */
290  if (!cpl_errorstate_is_equal(prestate)) {
291  rc = cpl_error_get_code();
292  }
293  return rc;
294 } /* muse_flux_reference_table_check() */
295 
296 /*----------------------------------------------------------------------------*/
334 /*----------------------------------------------------------------------------*/
335 double
336 muse_flux_response_interpolate(const cpl_table *aResponse, double aLambda,
337  double *aError, muse_flux_interpolation_type aType)
338 {
339  double rv = 0.;
340  if (aType == MUSE_FLUX_TELLURIC) {
341  rv = 1.;
342  }
343  cpl_ensure(aResponse, CPL_ERROR_NULL_INPUT, rv);
344  int size = cpl_table_get_nrow(aResponse);
345  cpl_ensure(size > 0, cpl_error_get_code(), rv);
346 
347  /* access the correct table column(s) depending on the type */
348  const double *lbda = cpl_table_get_data_double_const(aResponse, "lambda"),
349  *resp = NULL, *rerr = NULL;
350  switch (aType) {
352  resp = cpl_table_get_data_double_const(aResponse, "throughput");
353  break;
354  case MUSE_FLUX_RESP_FLUX:
355  resp = cpl_table_get_data_double_const(aResponse, "response");
356  if (aError) {
357  rerr = cpl_table_get_data_double_const(aResponse, "resperr");
358  }
359  break;
361  resp = cpl_table_get_data_double_const(aResponse, "flux");
362  if (aError) {
363  rerr = cpl_table_get_data_double_const(aResponse, "fluxerr");
364  }
365  break;
367  resp = cpl_table_get_data_double_const(aResponse, "extinction");
368  break;
369  case MUSE_FLUX_TELLURIC:
370  resp = cpl_table_get_data_double_const(aResponse, "ftelluric");
371  if (aError) {
372  rerr = cpl_table_get_data_double_const(aResponse, "ftellerr");
373  }
374  break;
375  default:
376  cpl_error_set(__func__, CPL_ERROR_UNSUPPORTED_MODE);
377  return rv;
378  } /* switch aType */
379  cpl_ensure(lbda && resp, cpl_error_get_code(), rv);
380  if (aError) {
381  cpl_ensure(rerr, cpl_error_get_code(), rv);
382  }
383 
384  /* outside wavelength range of table */
385  if (aLambda < lbda[0]) {
386  return rv;
387  }
388  if (aLambda > lbda[size-1]) {
389  return rv;
390  }
391 
392  /* binary search for the correct wavelength */
393  double response = rv, resperror = 0.;
394  int l = 0, r = size - 1, /* left and right end */
395  m = (l + r) / 2; /* middle index */
396  while (CPL_TRUE) {
397  if (aLambda >= lbda[m] && aLambda <= lbda[m+1]) {
398  /* found right interval, so interpolate */
399  double lquot = (aLambda - lbda[m]) / (lbda[m+1] - lbda[m]);
400  response = resp[m] + (resp[m+1] - resp[m]) * lquot;
401  if (rerr) { /* missing error information should be non-fatal */
402  /* checked again that the derivatives leading to this error estimate *
403  * are correct; apparently it's normal then, that the resulting *
404  * errors can be smaller than the two separate input errors */
405  resperror = sqrt(pow(rerr[m] * (1 - lquot), 2.)
406  + pow(rerr[m+1] * lquot, 2.));
407  }
408 #if 0
409  cpl_msg_debug(__func__, "Found at m=%d (%f: %f+/-%f) / "
410  "m+1=%d (%f: %f+/-%f) -> %f: %f+/-%f",
411  m, lbda[m], resp[m], rerr ? rerr[m] : 0.,
412  m+1, lbda[m+1], resp[m+1], rerr ? rerr[m+1] : 0.,
413  aLambda, response, resperror);
414 #endif
415  break;
416  }
417  /* create next interval */
418  if (aLambda < lbda[m]) {
419  r = m;
420  }
421  if (aLambda > lbda[m]) {
422  l = m;
423  }
424  m = (l + r) / 2;
425  } /* while */
426 
427 #if 0
428  cpl_msg_debug(__func__, "Response %g+/-%g at lambda=%fA", response, resperror,
429  aLambda);
430 #endif
431  if (aError && rerr) {
432  *aError = resperror;
433  }
434  return response;
435 } /* muse_flux_response_interpolate() */
436 
437 /*----------------------------------------------------------------------------*/
452 /*----------------------------------------------------------------------------*/
453 static double
454 muse_flux_image_sky(cpl_image *aImage, double aX, double aY, double aHalfSize,
455  unsigned int aDSky, float *aError)
456 {
457  if (aError) {
458  *aError = FLT_MAX;
459  }
460  /* coordinates of inner window */
461  int x1 = aX - aHalfSize, x2 = aX + aHalfSize,
462  y1 = aY - aHalfSize, y2 = aY + aHalfSize;
463  unsigned char nskyarea = 0;
464  double skylevel = 0., skyerror = 0.;
465  /* left */
466  cpl_errorstate state = cpl_errorstate_get();
467  cpl_stats_mode mode = CPL_STATS_MEDIAN | CPL_STATS_MEDIAN_DEV;
468  cpl_stats *s = cpl_stats_new_from_image_window(aImage, mode,
469  x1 - aDSky, y1, x1 - 1, y2);
470  if (s) {
471  /* only if there was no error, the area was inside the image *
472  * boundaries and we can use it, otherwise the value is undefined */
473  nskyarea++;
474  skylevel += cpl_stats_get_median(s);
475  skyerror += pow(cpl_stats_get_median_dev(s), 2);
476  cpl_stats_delete(s);
477  }
478  /* right */
479  s = cpl_stats_new_from_image_window(aImage,mode,
480  x2 + 1, y1, x2 + aDSky, y2);
481  if (s) {
482  nskyarea++;
483  skylevel += cpl_stats_get_median(s);
484  skyerror += pow(cpl_stats_get_median_dev(s), 2);
485  cpl_stats_delete(s);
486  }
487  /* bottom */
488  s = cpl_stats_new_from_image_window(aImage,mode,
489  x1, y1 - aDSky, x2, y1 - 1);
490  if (s) {
491  nskyarea++;
492  skylevel += cpl_stats_get_median(s);
493  skyerror += pow(cpl_stats_get_median_dev(s), 2);
494  cpl_stats_delete(s);
495  }
496  /* top */
497  s = cpl_stats_new_from_image_window(aImage,mode,
498  x1, y2 + 1, x2, y2 + aDSky);
499  if (s) {
500  nskyarea++;
501  skylevel += cpl_stats_get_median(s);
502  skyerror += pow(cpl_stats_get_median_dev(s), 2);
503  cpl_stats_delete(s);
504  }
505  if (nskyarea == 0) {
506  return 0.;
507  }
508  skylevel /= nskyarea;
509  skyerror = sqrt(skyerror) / nskyarea;
510  if (!cpl_errorstate_is_equal(state)) { /* reset error code */
511  cpl_errorstate_set(state);
512  }
513 #if 0
514  cpl_msg_debug(__func__, "skylevel = %f +/- %f (%u sky areas)",
515  skylevel, skyerror, nskyarea);
516 #endif
517  if (aError) {
518  *aError = skyerror;
519  }
520  return skylevel;
521 } /* muse_flux_image_sky() */
522 
523 /*----------------------------------------------------------------------------*/
540 /*----------------------------------------------------------------------------*/
541 static double
542 muse_flux_image_gaussian(cpl_image *aImage, cpl_image *aImErr, double aX,
543  double aY, double aHalfSize, unsigned int aDSky,
544  unsigned int aMaxBad, float *aFErr)
545 {
546  /* there is no simple way to count bad pixels inside an *
547  * image window, so ignore this argument at the moment */
548  UNUSED_ARGUMENT(aMaxBad);
549 
550  if (aFErr) { /* set high variance for an error case */
551  *aFErr = FLT_MAX;
552  }
553 
554  cpl_array *params = cpl_array_new(7, CPL_TYPE_DOUBLE),
555  *parerr = cpl_array_new(7, CPL_TYPE_DOUBLE);
556  /* Set some first-guess parameters to help the fitting function. *
557  * Just set background and central position, cpl_fit_image_gaussian() *
558  * finds good defaults for everything else. */
559  cpl_errorstate state = cpl_errorstate_get();
560  double skylevel = muse_flux_image_sky(aImage, aX, aY, aHalfSize, aDSky, NULL);
561  if (!cpl_errorstate_is_equal(state)) {
562  /* if background determination fails, a default of 0 should *
563  * be good enough, so that we can ignore this failure */
564  cpl_errorstate_set(state);
565  }
566  cpl_array_set_double(params, 0, skylevel);
567  cpl_array_set_double(params, 3, aX);
568  cpl_array_set_double(params, 4, aY);
569  double rms = 0, chisq = 0;
570  /* function wants full widths but at most out to the image boundary */
571  int nx = cpl_image_get_size_x(aImage),
572  ny = cpl_image_get_size_y(aImage),
573  xsize = fmin(aHalfSize, fmin(aX - 1., nx - aX)) * 2,
574  ysize = fmin(aHalfSize, fmin(aY - 1., ny - aY)) * 2;
575  cpl_error_code rc = cpl_fit_image_gaussian(aImage, aImErr, aX, aY, xsize, ysize,
576  params, parerr, NULL, &rms, &chisq,
577  NULL, NULL, NULL, NULL, NULL);
578  if (rc != CPL_ERROR_NONE) {
579  if (rc != CPL_ERROR_ILLEGAL_INPUT) {
580  cpl_msg_debug(__func__, "rc = %d: %s", rc, cpl_error_get_message());
581  }
582  cpl_array_delete(params);
583  cpl_array_delete(parerr);
584  return 0;
585  }
586  double flux = cpl_array_get_double(params, 1, NULL),
587  ferr = cpl_array_get_double(parerr, 1, NULL);
588 #if 0 /* DEBUG */
589  double fwhmx = cpl_array_get_double(params, 5, NULL) * CPL_MATH_FWHM_SIG,
590  fwhmy = cpl_array_get_double(params, 6, NULL) * CPL_MATH_FWHM_SIG;
591  cpl_msg_debug(__func__, "%.3f,%.3f: %g+/-%g (bg: %g, FWHM: %.3f,%.3f, %g, %g)",
592  cpl_array_get_double(params, 3, NULL), cpl_array_get_double(params, 4, NULL),
593  flux, ferr, cpl_array_get_double(params, 0, NULL), fwhmx, fwhmy,
594  rms, chisq);
595 #endif
596 #if 0 /* DEBUG */
597  cpl_msg_debug(__func__, "skylevel = %f", cpl_array_get_double(params, 0, NULL));
598  cpl_msg_debug(__func__, "measured flux %f +/- %f", flux, ferr);
599 #endif
600  cpl_array_delete(params);
601  cpl_array_delete(parerr);
602  if (aFErr) {
603  *aFErr = ferr;
604  }
605  return flux;
606 } /* muse_flux_image_gaussian() */
607 
608 /*----------------------------------------------------------------------------*/
627 /*----------------------------------------------------------------------------*/
628 static double
629 muse_flux_image_moffat(cpl_image *aImage, cpl_image *aImErr, double aX,
630  double aY, double aHalfSize, unsigned int aDSky,
631  unsigned int aMaxBad, float *aFErr)
632 {
633  if (aFErr) { /* set high variance for an error case */
634  *aFErr = FLT_MAX;
635  }
636  /* extract image regions around the fiducial peak into matrix and vectors */
637  int x1 = aX - aHalfSize, x2 = aX + aHalfSize,
638  y1 = aY - aHalfSize, y2 = aY + aHalfSize,
639  nx = cpl_image_get_size_x(aImage),
640  ny = cpl_image_get_size_y(aImage);
641  if (x1 < 1) {
642  x1 = 1;
643  }
644  if (x2 > nx) {
645  x2 = nx;
646  }
647  if (y1 < 1) {
648  y1 = 1;
649  }
650  if (y2 > ny) {
651  y2 = ny;
652  }
653  int npoints = (x2 - x1 + 1) * (y2 - y1 + 1);
654 
655  cpl_matrix *pos = cpl_matrix_new(npoints, 2);
656  cpl_vector *values = cpl_vector_new(npoints),
657  *errors = cpl_vector_new(npoints);
658  float *derr = cpl_image_get_data_float(aImErr);
659  int i, idx = 0;
660  for (i = x1; i <= x2; i++) {
661  int j;
662  for (j = y1; j <= y2; j++) {
663  int err;
664  double data = cpl_image_get(aImage, i, j, &err);
665  if (err) { /* bad pixel or error */
666  continue;
667  }
668  cpl_matrix_set(pos, idx, 0, i);
669  cpl_matrix_set(pos, idx, 1, j);
670  cpl_vector_set(values, idx, data);
671  cpl_vector_set(errors, idx, derr[(i-1) + (j-1)*nx]);
672  idx++;
673  } /* for j (y pixels) */
674  } /* for i (x pixels) */
675  /* need at least something like 4x4 pixels for a solid fit; *
676  * also check missing entries against max number of bad pixels */
677  if (idx < 16 || (npoints - idx) > (int)aMaxBad) {
678  cpl_matrix_delete(pos);
679  cpl_vector_delete(values);
680  cpl_vector_delete(errors);
681  return 0;
682  }
683  cpl_matrix_set_size(pos, idx, 2);
684  cpl_vector_set_size(values, idx);
685  cpl_vector_set_size(errors, idx);
686 
687  cpl_array *params = cpl_array_new(8, CPL_TYPE_DOUBLE),
688  *parerr = cpl_array_new(8, CPL_TYPE_DOUBLE);
689  /* Set some first-guess parameters to help the fitting function. */
690  cpl_errorstate state = cpl_errorstate_get();
691  double skylevel = muse_flux_image_sky(aImage, aX, aY, aHalfSize, aDSky, NULL);
692  if (!cpl_errorstate_is_equal(state)) {
693  /* if background determination fails, a default of 0 should *
694  * be good enough, so that we can ignore this failure */
695  cpl_errorstate_set(state);
696  }
697  cpl_array_set_double(params, 0, skylevel);
698  cpl_array_set_double(params, 2, aX);
699  cpl_array_set_double(params, 3, aY);
700  double rms = 0, chisq = 0;
701  cpl_error_code rc = muse_utils_fit_moffat_2d(pos, values, errors,
702  params, parerr, NULL,
703  &rms, &chisq);
704  cpl_matrix_delete(pos);
705  cpl_vector_delete(values);
706  cpl_vector_delete(errors);
707  if (rc != CPL_ERROR_NONE) {
708  if (rc != CPL_ERROR_ILLEGAL_INPUT) {
709  cpl_msg_debug(__func__, "rc = %d: %s", rc, cpl_error_get_message());
710  }
711  cpl_array_delete(params);
712  cpl_array_delete(parerr);
713  return 0;
714  }
715 
716  double flux = cpl_array_get_double(params, 1, NULL);
717  if (aFErr) {
718  *aFErr = cpl_array_get_double(parerr, 1, NULL);
719  }
720 #if 0 /* DEBUG */
721  cpl_msg_debug(__func__, "skylevel = %f", cpl_array_get_double(params, 0, NULL));
722  cpl_msg_debug(__func__, "measured flux %f +/- %f", flux, cpl_array_get_double(parerr, 1, NULL));
723 #endif
724  cpl_array_delete(params);
725  cpl_array_delete(parerr);
726  return flux;
727 } /* muse_flux_image_moffat() */
728 
729 /*----------------------------------------------------------------------------*/
748 /*----------------------------------------------------------------------------*/
749 static double
750 muse_flux_image_square(cpl_image *aImage, cpl_image *aImErr, double aX,
751  double aY, double aHalfSize, unsigned int aDSky,
752  unsigned int aMaxBad, float *aFErr)
753 {
754  if (aFErr) { /* set high variance for an error case */
755  *aFErr = FLT_MAX;
756  }
757  int x1 = aX - aHalfSize, x2 = aX + aHalfSize,
758  y1 = aY - aHalfSize, y2 = aY + aHalfSize,
759  nx = cpl_image_get_size_x(aImage),
760  ny = cpl_image_get_size_y(aImage);
761  if (x1 < 1) {
762  x1 = 1;
763  }
764  if (x2 > nx) {
765  x2 = nx;
766  }
767  if (y1 < 1) {
768  y1 = 1;
769  }
770  if (y2 > ny) {
771  y2 = ny;
772  }
773  float skyerror;
774  cpl_errorstate state = cpl_errorstate_get();
775  double skylevel = muse_flux_image_sky(aImage, aX, aY, aHalfSize, aDSky,
776  &skyerror);
777  if (!cpl_errorstate_is_equal(state)) {
778  /* background determination is critical for this method, *
779  * reset the error but return with zero anyway */
780  cpl_errorstate_set(state);
781  cpl_error_set(__func__, CPL_ERROR_DATA_NOT_FOUND);
782  return 0.; /* fail on missing background level */
783  }
784 
785  /* extract the measurement region; fail on too many bad pixels, *
786  * but interpolate, if there are only a few bad ones */
787  cpl_image *region = cpl_image_extract(aImage, x1, y1, x2, y2);
788 #if 0 /* DEBUG */
789  cpl_msg_debug(__func__, "region [%d:%d,%d:%d] %"CPL_SIZE_FORMAT" bad pixels",
790  x1, y1, x2, y2, cpl_image_count_rejected(region));
791 #endif
792  if (cpl_image_count_rejected(region) > aMaxBad) {
793  cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT);
794  cpl_image_delete(region);
795  return 0.; /* fail on too many bad pixels */
796  }
797  cpl_image *regerr = cpl_image_extract(aImErr, x1, y1, x2, y2);
798  if (cpl_image_count_rejected(region) > 0) {
799  cpl_detector_interpolate_rejected(region);
800  cpl_detector_interpolate_rejected(regerr);
801  }
802 
803  /* integrated flux, subtracted by the sky over the size of the *
804  * aperture; an error modeled approximately after IRAF phot */
805  int npoints = (x2 - x1 + 1) * (y2 - y1 + 1),
806  /* number of sky pixels should be counted in muse_flux_image_sky(), *
807  * but it's really not so important to add another parameter, the *
808  * error that we compute here is probably too small to be useful... */
809  nsky = 2 * aDSky * (x2 - x1 + y2 - y1 + 2);
810  double flux = cpl_image_get_flux(region) - skylevel * npoints,
811  ferr = sqrt(cpl_image_get_sqflux(regerr)
812  + npoints * skyerror*skyerror * (1. + (double)npoints / nsky));
813 #if 0 /* DEBUG */
814  cpl_msg_debug(__func__, "measured flux %f +/- %f (%d object pixels, %d pixels"
815  " with %f with sky %f)", flux, ferr, npoints, nsky,
816  cpl_image_get_flux(region), cpl_image_get_sqflux(regerr));
817 #endif
818  cpl_image_delete(region);
819  cpl_image_delete(regerr);
820  if (aFErr) {
821  *aFErr = ferr;
822  }
823  return flux;
824 } /* muse_flux_image_square() */
825 
826 /*----------------------------------------------------------------------------*/
846 /*----------------------------------------------------------------------------*/
847 static double
848 muse_flux_image_circle(cpl_image *aImage, cpl_image *aImErr, double aX,
849  double aY, double aAper, double aAnnu, double aDAnnu,
850  unsigned int aMaxBad, float *aFErr)
851 {
852  if (aFErr) { /* set high variance, for error cases */
853  *aFErr = FLT_MAX;
854  }
855  double rmax = ceil(fmax(aAper, aAnnu + aDAnnu));
856  int x1 = aX - rmax, x2 = aX + rmax,
857  y1 = aY - rmax, y2 = aY + rmax,
858  nx = cpl_image_get_size_x(aImage),
859  ny = cpl_image_get_size_y(aImage);
860  if (x1 < 1) {
861  x1 = 1;
862  }
863  if (x2 > nx) {
864  x2 = nx;
865  }
866  if (y1 < 1) {
867  y1 = 1;
868  }
869  if (y2 > ny) {
870  y2 = ny;
871  }
872  /* first loop to collect the background and *
873  * count bad pixels inside the aperture */
874  cpl_vector *vbg = cpl_vector_new((x2 - x1 + 1) * (y2 - y1 + 1)),
875  *vbe = cpl_vector_new((x2 - x1 + 1) * (y2 - y1 + 1));
876  unsigned int nbad = 0, nbg = 0;
877  int i;
878  for (i = x1; i <= x2; i++) {
879  int j;
880  for (j = y1; j <= y2; j++) {
881  double r = sqrt(pow(aX - i, 2) + pow(aY - j, 2));
882  if (r <= aAper) {
883  nbad += cpl_image_is_rejected(aImage, i, j) == 1;
884  }
885  if (r < aAnnu || r > aAnnu + aDAnnu) {
886  continue;
887  }
888  int err;
889  double value = cpl_image_get(aImage, i, j, &err);
890  if (err) { /* exclude bad pixels */
891  continue;
892  }
893  cpl_vector_set(vbg, nbg, value);
894  cpl_vector_set(vbe, nbg, cpl_image_get(aImErr, i, j, &err));
895  nbg++;
896  } /* for j (vertical pixels) */
897  } /* for i (horizontal pixels) */
898  if (nbg <= 0) {
899  cpl_error_set(__func__, CPL_ERROR_DATA_NOT_FOUND);
900  cpl_vector_delete(vbg);
901  cpl_vector_delete(vbe);
902  return 0.; /* fail on missing background pixels */
903  }
904  cpl_vector_set_size(vbg, nbg);
905  cpl_vector_set_size(vbe, nbg);
906  cpl_matrix *pos = cpl_matrix_new(1, nbg); /* we don't care about positions... */
907  double mse;
908  cpl_polynomial *fit = muse_utils_iterate_fit_polynomial(pos, vbg, vbe, NULL,
909  0, 3., &mse, NULL);
910 #if 0 /* DEBUG */
911  unsigned int nrej = nbg - cpl_vector_get_size(vbg);
912 #endif
913  nbg = cpl_vector_get_size(vbg);
914  cpl_size pows = 0; /* get the zero-order coefficient */
915  double smean = cpl_polynomial_get_coeff(fit, &pows),
916  sstdev = sqrt(mse);
917  cpl_polynomial_delete(fit);
918  cpl_matrix_delete(pos);
919  cpl_vector_delete(vbg);
920  cpl_vector_delete(vbe);
921 #if 0 /* DEBUG */
922  cpl_msg_debug(__func__, "sky: %d pixels (%d rejected), %f +/- %f; found %d "
923  "bad pixels inside aperture", nbg, nrej, smean, sstdev, nbad);
924 #endif
925  if (nbad > aMaxBad) { /* too many bad pixels inside integration area? */
926  cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT);
927  return 0.; /* fail on too many bad pixels */
928  }
929 
930  /* now replace the few bad pixels by interpolation */
931  if (nbad > 0) {
932  cpl_detector_interpolate_rejected(aImage);
933  cpl_detector_interpolate_rejected(aImErr);
934  }
935 
936  /* second loop to integrate the flux */
937  double flux = 0.,
938  ferr = 0.;
939  unsigned int nobj = 0;
940  for (i = x1; i <= x2; i++) {
941  int j;
942  for (j = y1; j <= y2; j++) {
943  double r = sqrt(pow(aX - i, 2) + pow(aY - j, 2));
944  if (r > aAper) {
945  continue;
946  }
947  int err;
948  double value = cpl_image_get(aImage, i, j, &err),
949  error = cpl_image_get(aImErr, i, j, &err);
950  flux += value;
951  ferr += error*error;
952  nobj++;
953  } /* for j (vertical pixels) */
954  } /* for i (horizontal pixels) */
955  flux -= smean * nobj;
956  /* Compute error like IRAF phot: *
957  * error = sqrt (flux / epadu + area * stdev**2 + *
958  * area**2 * stdev**2 / nsky) *
959  * We take our summed error instead of the error computed via the gain */
960  ferr = sqrt(ferr + nobj * sstdev*sstdev * (1. + (double)nobj / nbg));
961 #if 0 /* DEBUG */
962  cpl_msg_debug(__func__, "flux: %d pixels (%d interpolated), %f +/- %f",
963  nobj, nbad, flux, ferr);
964 #endif
965  if (aFErr) {
966  *aFErr = ferr;
967  }
968  return flux;
969 } /* muse_flux_image_circle() */
970 
971 /*----------------------------------------------------------------------------*/
1018 /*----------------------------------------------------------------------------*/
1019 cpl_error_code
1021  muse_flux_object *aFluxObj)
1022 {
1023  cpl_ensure_code(aPixtable && aFluxObj, CPL_ERROR_NULL_INPUT);
1024  switch (aProfile) {
1026  cpl_msg_info(__func__, "Gaussian profile fits for flux integration");
1027  break;
1029  cpl_msg_info(__func__, "Moffat profile fits for flux integration");
1030  break;
1032  cpl_msg_info(__func__, "Circular flux integration");
1033  break;
1035  cpl_msg_info(__func__, "Simple square window flux integration");
1036  break;
1037  default:
1038  return cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT);
1039  }
1040 
1041  if (getenv("MUSE_DEBUG_FLUX") && atoi(getenv("MUSE_DEBUG_FLUX")) > 2) {
1042  const char *fn = "flux__pixtable.fits";
1043  cpl_msg_info(__func__, "Saving pixel table as \"%s\"", fn);
1044  muse_pixtable_save(aPixtable, fn);
1045  }
1046  muse_resampling_params *params =
1048  params->pfx = 1.; /* large pixfrac to be sure to cover most gaps */
1049  params->pfy = 1.;
1050  params->pfl = 1.;
1051  /* resample at nominal resolution, the conversion to [1/Angstrom] *
1052  * is later done when computing the sensitivity function */
1053  params->dlambda = kMuseSpectralSamplingA;
1055  params->crsigma = 25.;
1056  muse_datacube *cube = muse_resampling_cube(aPixtable, params, NULL);
1057  if (cube) {
1058  aFluxObj->cube = cube;
1059  }
1061  if (getenv("MUSE_DEBUG_FLUX") && atoi(getenv("MUSE_DEBUG_FLUX")) >= 2) {
1062  const char *fn = "flux__cube.fits";
1063  cpl_msg_info(__func__, "Saving cube as \"%s\"", fn);
1064  muse_datacube_save(aFluxObj->cube, fn);
1065  }
1066  int nplane = cpl_imagelist_get_size(cube->data) / 2; /* central plane */
1067  cpl_image *cim = cpl_imagelist_get(cube->data, nplane);
1068  /* use high sigmas for detection */
1069  double dsigmas[] = { 50., 30., 10., 8., 6., 5. };
1070  cpl_vector *vsigmas = cpl_vector_wrap(sizeof(dsigmas) / sizeof(double),
1071  dsigmas);
1072  cpl_size isigma = -1;
1073  cpl_apertures *apertures = cpl_apertures_extract(cim, vsigmas, &isigma);
1074  int napertures = apertures ? cpl_apertures_get_size(apertures) : 0;
1075  if (napertures < 1) {
1076  cpl_vector_unwrap(vsigmas);
1077  cpl_apertures_delete(apertures);
1078  return cpl_error_set(__func__, CPL_ERROR_DATA_NOT_FOUND);
1079  }
1080  cpl_msg_debug(__func__, "The %.1f sigma threshold was used to find %d source%s",
1081  cpl_vector_get(vsigmas, isigma), napertures, napertures == 1 ? "" : "s");
1082  cpl_vector_unwrap(vsigmas);
1083 #if 1 /* DEBUG */
1084  cpl_apertures_dump(apertures, stdout);
1085  fflush(stdout);
1086 #endif
1087 
1088  /* construct image of number of wavelengths x number of stars */
1089  int nlambda = cpl_imagelist_get_size(cube->data);
1090  muse_image *intimage = muse_image_new();
1091  intimage->data = cpl_image_new(nlambda, napertures, CPL_TYPE_FLOAT);
1092  intimage->dq = cpl_image_new(nlambda, napertures, CPL_TYPE_INT);
1093  intimage->stat = cpl_image_new(nlambda, napertures, CPL_TYPE_FLOAT);
1094  /* copy wavelength WCS from 3rd axis of cube to x-axis of image */
1095  intimage->header = cpl_propertylist_new();
1096  cpl_propertylist_append_double(intimage->header, "CRVAL1",
1097  cpl_propertylist_get_double(cube->header,
1098  "CRVAL3"));
1099  cpl_propertylist_append_double(intimage->header, "CRPIX1",
1100  cpl_propertylist_get_double(cube->header,
1101  "CRPIX3"));
1102  cpl_propertylist_append_double(intimage->header, "CD1_1",
1103  cpl_propertylist_get_double(cube->header,
1104  "CD3_3"));
1105  cpl_propertylist_append_string(intimage->header, "CTYPE1",
1106  cpl_propertylist_get_string(cube->header,
1107  "CTYPE3"));
1108  cpl_propertylist_append_string(intimage->header, "CUNIT1",
1109  cpl_propertylist_get_string(cube->header,
1110  "CUNIT3"));
1111  /* fill the 2nd axis with standards */
1112  cpl_propertylist_append_double(intimage->header, "CRVAL2", 1.);
1113  cpl_propertylist_append_double(intimage->header, "CRPIX2", 1.);
1114  cpl_propertylist_append_double(intimage->header, "CD2_2", 1.);
1115  cpl_propertylist_append_string(intimage->header, "CTYPE2", "PIXEL");
1116  cpl_propertylist_append_string(intimage->header, "CUNIT2", "pixel");
1117  cpl_propertylist_append_double(intimage->header, "CD1_2", 0.);
1118  cpl_propertylist_append_double(intimage->header, "CD2_1", 0.);
1119  /* we need the date, data units, exposure time, and instrument mode, too */
1120  cpl_propertylist_append_string(intimage->header, "DATE-OBS",
1121  cpl_propertylist_get_string(cube->header,
1122  "DATE-OBS"));
1123  cpl_propertylist_append_string(intimage->header, "BUNIT",
1124  cpl_propertylist_get_string(cube->header,
1125  "BUNIT"));
1126  cpl_propertylist_append_double(intimage->header, "EXPTIME",
1128  cpl_propertylist_append_string(intimage->header, "ESO INS MODE",
1129  cpl_propertylist_get_string(cube->header,
1130  "ESO INS MODE"));
1131 
1132  /* get DIMM seeing from the headers, convert it to size in pixels */
1133  cpl_errorstate ps = cpl_errorstate_get();
1134  double fwhm = (muse_pfits_get_fwhm_start(cube->header)
1135  + muse_pfits_get_fwhm_end(cube->header)) / 2.;
1136  if (muse_pfits_get_mode(cube->header) < MUSE_MODE_NFM_AO_N) {
1137  fwhm /= (kMuseSpaxelSizeX_WFM + kMuseSpaxelSizeY_WFM) / 2.;
1138  } else { /* for NFM */
1139  fwhm /= (kMuseSpaxelSizeX_NFM + kMuseSpaxelSizeY_NFM) / 2.;
1140  }
1141  if (!cpl_errorstate_is_equal(ps)) { /* some headers are missing */
1142  double xc = cpl_apertures_get_centroid_x(apertures, 1),
1143  yc = cpl_apertures_get_centroid_y(apertures, 1),
1144  xfwhm, yfwhm;
1145  cpl_image_get_fwhm(cim, lround(xc), lround(yc), &xfwhm, &yfwhm);
1146  if (xfwhm > 0. && yfwhm > 0.) {
1147  fwhm = (xfwhm + yfwhm) / 2.;
1148  } else if (xfwhm > 0.) {
1149  fwhm = xfwhm;
1150  } else if (yfwhm > 0.) {
1151  fwhm = yfwhm;
1152  } else {
1153  fwhm = 5.; /* total failure to measure it, assume 1 arcsec seeing */
1154  }
1155  cpl_errorstate_set(ps);
1156  cpl_msg_debug(__func__, "Using roughly estimated reference FWHM (%.3f pix) "
1157  "instead of DIMM seeing", fwhm);
1158  } else {
1159  cpl_msg_debug(__func__, "Using DIMM seeing of %.3f pix for reference FWHM",
1160  fwhm);
1161  }
1162 
1163  /* track the sizes used for the fits/integrations in an image, *
1164  * record the half-sizes (or radiuses) in on row per object */
1165  cpl_image *sizes = cpl_image_new(nlambda, napertures, CPL_TYPE_DOUBLE);
1166  double *psizes = cpl_image_get_data_double(sizes);
1167  /* access pointers for the flux-image */
1168  float *data = cpl_image_get_data_float(intimage->data),
1169  *stat = cpl_image_get_data_float(intimage->stat);
1170  int *dq = cpl_image_get_data_int(intimage->dq);
1171  /* loop over all wavelengths and measure the flux */
1172  int l, ngood = 0, nillegal = 0, nbadbg = 0; /* count good fits and errors */
1173  #pragma omp parallel for default(none) /* as req. by Ralf */ \
1174  shared(apertures, aProfile, cube, data, dq, fwhm, napertures, nbadbg,\
1175  ngood, nillegal, nlambda, psizes, stat)
1176  for (l = 0; l < nlambda; l++) {
1177  cpl_image *plane = cpl_imagelist_get(cube->data, l),
1178  *pldq = cpl_imagelist_get(cube->dq, l),
1179  *plerr = cpl_image_duplicate(cpl_imagelist_get(cube->stat, l));
1180 #if 0 /* DEBUG */
1181  cpl_stats *stats = cpl_stats_new_from_image(plerr, CPL_STATS_ALL);
1182  cpl_msg_debug(__func__, "lambda = %d/%f %s", l + 1,
1183  (l + 1 - cpl_propertylist_get_double(cube->header, "CRPIX3"))
1184  * cpl_propertylist_get_double(cube->header, "CD3_3")
1185  + cpl_propertylist_get_double(cube->header, "CRVAL3"),
1186  cpl_propertylist_get_string(cube->header, "CUNIT3"));
1187  cpl_msg_debug(__func__, "variance: %g...%g...%g", cpl_stats_get_min(stats),
1188  cpl_stats_get_mean(stats), cpl_stats_get_max(stats));
1189  cpl_stats_delete(stats);
1190 #endif
1191  /* make sure to exclude bad pixels from the fits below */
1192  muse_quality_image_reject_using_dq(plane, pldq, plerr);
1193 #if 0 /* DEBUG */
1194  stats = cpl_stats_new_from_image(plerr, CPL_STATS_ALL);
1195  cpl_msg_debug(__func__, "cut variance: %g...%g...%g (%"CPL_SIZE_FORMAT" bad"
1196  " pixel)", cpl_stats_get_min(stats), cpl_stats_get_mean(stats),
1197  cpl_stats_get_max(stats), cpl_image_count_rejected(plane));
1198  cpl_stats_delete(stats);
1199 #endif
1200  /* convert variable to sigmas */
1201  cpl_image_power(plerr, 0.5);
1202 #if 0 /* DEBUG */
1203  stats = cpl_stats_new_from_image(plerr, CPL_STATS_ALL);
1204  cpl_msg_debug(__func__, "errors: %g...%g...%g", cpl_stats_get_min(stats),
1205  cpl_stats_get_mean(stats), cpl_stats_get_max(stats));
1206  cpl_stats_delete(stats);
1207 #endif
1208  cpl_errorstate state = cpl_errorstate_get();
1209  int n;
1210  for (n = 1; n <= napertures; n++) {
1211  /* use detection aperture to construct much larger one for measurement */
1212  double xc = cpl_apertures_get_centroid_x(apertures, n),
1213  yc = cpl_apertures_get_centroid_y(apertures, n),
1214  size = sqrt(cpl_apertures_get_npix(apertures, n)),
1215  xfwhm, yfwhm;
1216  cpl_errorstate prestate = cpl_errorstate_get();
1217  cpl_image_get_fwhm(plane, lround(xc), lround(yc), &xfwhm, &yfwhm);
1218  if (xfwhm < 0 || yfwhm < 0) {
1219  data[l + (n-1) * nlambda] = 0.;
1220  stat[l + (n-1) * nlambda] = FLT_MAX;
1221  cpl_errorstate_set(prestate);
1222  continue;
1223  }
1224  /* half size for flux integration, at least 3 x FWHM */
1225  double halfsize = fmax(1.5 * (xfwhm + yfwhm), 3. * fwhm);
1226  if (halfsize < size / 2) { /* at least the size of the det. aperture */
1227  halfsize = size / 2;
1228  }
1229  psizes[l + (n-1) * nlambda] = halfsize;
1230 #if 0 /* DEBUG */
1231  cpl_msg_debug(__func__, "%.2f,%.2f FWHM %.2f %.2f size %.2f --> %.2f",
1232  xc, yc, xfwhm, yfwhm, size, halfsize * 2.);
1233 #endif
1234 
1235  switch (aProfile) {
1237  data[l + (n-1) * nlambda] = muse_flux_image_gaussian(plane, plerr, xc, yc,
1238  halfsize, 5, 10,
1239  &stat[l + (n-1) * nlambda]);
1240  break;
1242  data[l + (n-1) * nlambda] = muse_flux_image_moffat(plane, plerr, xc, yc,
1243  halfsize, 5, 10,
1244  &stat[l + (n-1) * nlambda]);
1245  break;
1246  case MUSE_FLUX_PROFILE_CIRCLE: {
1247  /* the circular method needs larger region to properly integrate *
1248  * everything at least something like 4 x FWHM to be sure */
1249  double radius = 4./3. * halfsize,
1250  rannu = radius * 5. / 4.; /* background annulus a bit larger */
1251  psizes[l + (n-1) * nlambda] = radius;
1252  data[l + (n-1) * nlambda] = muse_flux_image_circle(plane, plerr, xc, yc,
1253  radius, rannu, 10, 10,
1254  &stat[l + (n-1) * nlambda]);
1255  break;
1256  } /* case MUSE_FLUX_PROFILE_CIRCLE */
1257  default: /* MUSE_FLUX_PROFILE_EQUAL_SQUARE */
1258  data[l + (n-1) * nlambda] = muse_flux_image_square(plane, plerr, xc, yc,
1259  halfsize, 5, 10,
1260  &stat[l + (n-1) * nlambda]);
1261  } /* switch */
1262  if (data[l + (n-1) * nlambda] < 0 || !isfinite(data[l + (n-1) * nlambda])) {
1263  data[l + (n-1) * nlambda] = 0.; /* should not contribute to flux */
1264  dq[l + (n-1) * nlambda] = EURO3D_MISSDATA; /* mark as bad in DQ extension */
1265  stat[l + (n-1) * nlambda] = FLT_MAX;
1266  }
1267  } /* for n (all apertures) */
1268 
1269  /* count "Illegal input" errors and for those reset the state, as there *
1270  * can be many of them (one for each incompletely filled wavelength plane */
1271  if (!cpl_errorstate_is_equal(state)) {
1272  if (cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT) {
1273  cpl_errorstate_set(state);
1274  #pragma omp atomic
1275  nillegal++;
1276  } else if (cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND) {
1277  cpl_errorstate_set(state);
1278  #pragma omp atomic
1279  nbadbg++;
1280  }
1281  } else {
1282  #pragma omp atomic
1283  ngood++;
1284  }
1285 
1286  cpl_image_delete(plerr);
1287  } /* for l (all wavelengths) */
1288 
1289  /* output statistics for the sizes used, mask out unset positions in the image */
1290  cpl_image_reject_value(sizes, CPL_VALUE_ZERO);
1291  int n;
1292  for (n = 1; n <= napertures; n++) {
1293  if (aProfile == MUSE_FLUX_PROFILE_CIRCLE) {
1294  cpl_msg_info(__func__, "Radiuses used for circular flux integration for "
1295  "source %d: %f +/- %f (%f) %f..%f", n,
1296  cpl_image_get_mean_window(sizes, 1, n, nlambda, n),
1297  cpl_image_get_stdev_window(sizes, 1, n, nlambda, n),
1298  cpl_image_get_median_window(sizes, 1, n, nlambda, n),
1299  cpl_image_get_min_window(sizes, 1, n, nlambda, n),
1300  cpl_image_get_max_window(sizes, 1, n, nlambda, n));
1301  } else {
1302  cpl_msg_info(__func__, "Half-sizes used for flux integration for source "
1303  "%d: %f +/- %f (%f) %f..%f", n,
1304  cpl_image_get_mean_window(sizes, 1, n, nlambda, n),
1305  cpl_image_get_stdev_window(sizes, 1, n, nlambda, n),
1306  cpl_image_get_median_window(sizes, 1, n, nlambda, n),
1307  cpl_image_get_min_window(sizes, 1, n, nlambda, n),
1308  cpl_image_get_max_window(sizes, 1, n, nlambda, n));
1309  } /* else */
1310  } /* for n (all apertures) */
1311 #if 0
1312  cpl_image_save(sizes, "sizes.fits", CPL_TYPE_UNSPECIFIED, NULL, CPL_IO_CREATE);
1313 #endif
1314  cpl_image_delete(sizes);
1315 
1316  /* add headers about the sources to the integrated image */
1317  cpl_propertylist_append_int(intimage->header, MUSE_HDR_FLUX_NOBJ,
1318  napertures);
1319  /* create a basic WCS, assuming nominal MUSE properties, for an *
1320  * estimate of the celestial position of all detected sources */
1321  cpl_propertylist *wcs1 = muse_wcs_create_default(),
1322  *wcs = muse_wcs_apply_cd(cube->header, wcs1);
1323  cpl_propertylist_delete(wcs1);
1324  /* update WCS just as in muse_resampling_cube() */
1325  double crpix1 = cpl_propertylist_get_double(cube->header, "CRPIX1")
1326  + (1. + cpl_image_get_size_x(cim)) / 2.,
1327  crpix2 = cpl_propertylist_get_double(cube->header, "CRPIX2")
1328  + (1. + cpl_image_get_size_y(cim)) / 2.;
1329  cpl_propertylist_update_double(wcs, "CRPIX1", crpix1);
1330  cpl_propertylist_update_double(wcs, "CRPIX2", crpix2);
1331  cpl_propertylist_update_double(wcs, "CRVAL1", muse_pfits_get_ra(cube->header));
1332  cpl_propertylist_update_double(wcs, "CRVAL2", muse_pfits_get_dec(cube->header));
1333  for (n = 1; n <= napertures; n++) {
1334  /* use detection aperture to construct much larger one for measurement */
1335  double xc = cpl_apertures_get_centroid_x(apertures, n),
1336  yc = cpl_apertures_get_centroid_y(apertures, n),
1337  ra, dec;
1338  muse_wcs_celestial_from_pixel(wcs, xc, yc, &ra, &dec);
1339  double flux = cpl_image_get_flux_window(intimage->data, 1, n, nlambda, n);
1340  cpl_msg_debug(__func__, "Source %02d: %.3f,%.3f pix, %f,%f deg, flux %e %s",
1341  n, xc, yc, ra, dec, flux,
1342  cpl_propertylist_get_string(intimage->header, "BUNIT"));
1343  char kw[KEYWORD_LENGTH];
1344  snprintf(kw, KEYWORD_LENGTH, MUSE_HDR_FLUX_OBJn_X, n);
1345  cpl_propertylist_append_float(intimage->header, kw, xc);
1346  snprintf(kw, KEYWORD_LENGTH, MUSE_HDR_FLUX_OBJn_Y, n);
1347  cpl_propertylist_append_float(intimage->header, kw, yc);
1348  snprintf(kw, KEYWORD_LENGTH, MUSE_HDR_FLUX_OBJn_RA, n);
1349  cpl_propertylist_append_double(intimage->header, kw, ra);
1350  snprintf(kw, KEYWORD_LENGTH, MUSE_HDR_FLUX_OBJn_DEC, n);
1351  cpl_propertylist_append_double(intimage->header, kw, dec);
1352  snprintf(kw, KEYWORD_LENGTH, MUSE_HDR_FLUX_OBJn_FLUX, n);
1353  cpl_propertylist_append_double(intimage->header, kw, flux);
1354  } /* for n (all apertures) */
1355  cpl_propertylist_delete(wcs);
1356  cpl_apertures_delete(apertures);
1357  aFluxObj->intimage = intimage; /* save integrated fluxes into in/out struct */
1358 
1359  if (nillegal > 0 || nbadbg > 0) {
1360  cpl_msg_warning(__func__, "Successful fits in %d wavelength planes, but "
1361  "encountered %d \"Illegal input\" errors and %d bad "
1362  "background determinations", ngood, nillegal, nbadbg);
1363  } else {
1364  cpl_msg_info(__func__, "Successful fits in %d wavelength planes", ngood);
1365  }
1366  return CPL_ERROR_NONE;
1367 } /* muse_flux_integrate_std() */
1368 
1369 /* prominent telluric absorption bands, together with clean regions: *
1370  * [0] lower limit of telluric region *
1371  * [1] upper limit of telluric region *
1372  * [2] lower limit of fit region (excludes telluric region) *
1373  * [3] upper limit of fit region (excludes telluric region) *
1374  * created from by-hand measurements on spectra from the ESO sky *
1375  * model, and from the Keck list of telluric lines */
1376 static const double kTelluricBands[][4] = {
1377  { 6273., 6320., 6213., 6380. }, /* now +/-60 Angstrom around... */
1378  { 6864., 6967., 6750., 7130. }, /* B-band */
1379  { 7164., 7325., 7070., 7580. },
1380  { 7590., 7700., 7470., 7830. }, /* A-band */
1381  { 8131., 8345., 7900., 8600. },
1382  { 8952., 9028., 8850., 9082. }, /* XXX very rough! */
1383  { 9274., 9770., 9080., 9263. }, /* XXX very very rough! */
1384  { -1., -1., -1., -1. }
1385 };
1386 
1387 /*----------------------------------------------------------------------------*/
1391 /*----------------------------------------------------------------------------*/
1393  { "lmin", CPL_TYPE_DOUBLE, "Angstrom", "%8.3f",
1394  "lower limit of the telluric region", CPL_TRUE },
1395  { "lmax", CPL_TYPE_DOUBLE, "Angstrom", "%8.3f",
1396  "upper limit of the telluric region", CPL_TRUE },
1397  { "bgmin", CPL_TYPE_DOUBLE, "Angstrom", "%8.3f",
1398  "lower limit of the background region", CPL_TRUE },
1399  { "bgmax", CPL_TYPE_DOUBLE, "Angstrom", "%8.3f",
1400  "upper limit of the background region", CPL_TRUE },
1401  { NULL, 0, NULL, NULL, NULL, CPL_FALSE }
1402 };
1403 
1404 /*----------------------------------------------------------------------------*/
1411 /*----------------------------------------------------------------------------*/
1412 static void
1413 muse_flux_response_set_telluric_bands(muse_flux_object *aFluxObj,
1414  const cpl_table *aTellBands)
1415 {
1416  if (!aFluxObj) {
1417  cpl_error_set(__func__, CPL_ERROR_NULL_INPUT);
1418  return;
1419  }
1420  /* if a valid table was passed, just set that */
1421  if (aTellBands && muse_cpltable_check(aTellBands, muse_response_tellbands_def)
1422  == CPL_ERROR_NONE) {
1423  cpl_msg_debug(__func__, "using given table for telluric bands");
1424  aFluxObj->tellbands = cpl_table_duplicate(aTellBands);
1425  return;
1426  }
1427  /* create a table for the default regions */
1428  unsigned int ntell = sizeof(kTelluricBands) / sizeof(kTelluricBands[0]) - 1;
1429  cpl_msg_debug(__func__, "using builtin regions for telluric bands (%u "
1430  "entries)", ntell);
1431  aFluxObj->tellbands = muse_cpltable_new(muse_response_tellbands_def, ntell);
1432  cpl_table *tb = aFluxObj->tellbands; /* shortcut */
1433  unsigned int k;
1434  for (k = 0; k < ntell; k++) {
1435  cpl_table_set_double(tb, "lmin", k, kTelluricBands[k][0]);
1436  cpl_table_set_double(tb, "lmax", k, kTelluricBands[k][1]);
1437  cpl_table_set_double(tb, "bgmin", k, kTelluricBands[k][2]);
1438  cpl_table_set_double(tb, "bgmax", k, kTelluricBands[k][3]);
1439  } /* for k */
1440  if (getenv("MUSE_DEBUG_FLUX") && atoi(getenv("MUSE_DEBUG_FLUX")) >= 2) {
1441  const char *fn = "flux__tellregions.fits";
1442  cpl_msg_info(__func__, "Saving telluric bands table as \"%s\"", fn);
1443  cpl_table_save(tb, NULL, NULL, fn, CPL_IO_CREATE);
1444  }
1445  return;
1446 } /* muse_flux_response_set_telluric_bands() */
1447 
1448 /*----------------------------------------------------------------------------*/
1461 /*----------------------------------------------------------------------------*/
1462 static void
1463 muse_flux_response_dump_sensitivity(muse_flux_object *aFluxObj,
1464  const char *aName)
1465 {
1466  char *dodebug = getenv("MUSE_DEBUG_FLUX");
1467  if (!dodebug || (dodebug && atoi(dodebug) <= 0)) {
1468  return;
1469  }
1470  char *fn = cpl_sprintf("flux__sens_%s.ascii", aName);
1471  FILE *fp = fopen(fn, "w");
1472  fprintf(fp, "#"); /* prefix first line (table header) for easier plotting */
1473  cpl_table_dump(aFluxObj->sensitivity, 0,
1474  cpl_table_get_nrow(aFluxObj->sensitivity), fp);
1475  fclose(fp);
1476  cpl_msg_debug(__func__, "Written %"CPL_SIZE_FORMAT" datapoints to \"%s\"",
1477  cpl_table_get_nrow(aFluxObj->sensitivity), fn);
1478  cpl_free(fn);
1479 } /* muse_flux_response_dump_sensitivity() */
1480 
1481 /*----------------------------------------------------------------------------*/
1500 /*----------------------------------------------------------------------------*/
1501 static void
1502 muse_flux_response_sensitivity(muse_flux_object *aFluxObj,
1503  unsigned int aStar, const cpl_table *aReference,
1504  double aAirmass, const cpl_table *aExtinct)
1505 {
1506  double crval = cpl_propertylist_get_double(aFluxObj->intimage->header, "CRVAL1"),
1507  cdelt = cpl_propertylist_get_double(aFluxObj->intimage->header, "CD1_1"),
1508  crpix = cpl_propertylist_get_double(aFluxObj->intimage->header, "CRPIX1"),
1509  exptime = muse_pfits_get_exptime(aFluxObj->intimage->header);
1510  int nlambda = cpl_image_get_size_x(aFluxObj->intimage->data);
1511 
1512  aFluxObj->sensitivity = cpl_table_new(nlambda);
1513  cpl_table *sensitivity = aFluxObj->sensitivity;
1514  cpl_table_new_column(sensitivity, "lambda", CPL_TYPE_DOUBLE);
1515  cpl_table_new_column(sensitivity, "sens", CPL_TYPE_DOUBLE);
1516  cpl_table_new_column(sensitivity, "serr", CPL_TYPE_DOUBLE);
1517  cpl_table_new_column(sensitivity, "dq", CPL_TYPE_INT);
1518  cpl_table_set_column_format(sensitivity, "dq", "%u");
1519  float *data = cpl_image_get_data_float(aFluxObj->intimage->data),
1520  *stat = cpl_image_get_data_float(aFluxObj->intimage->stat);
1521  int l, idx = 0;
1522  for (l = 0; l < nlambda; l++) {
1523  if (data[l + aStar*nlambda] <= 0. ||
1524  stat[l + aStar*nlambda] <= 0. ||
1525  stat[l + aStar*nlambda] == FLT_MAX) { /* exclude bad fits */
1526  continue;
1527  }
1528 
1529  double lambda = crval + cdelt * (l + 1 - crpix),
1530  /* interpolate extinction curve at this wavelength */
1531  extinct = !aExtinct ? 0. /* no extinction term */
1532  : muse_flux_response_interpolate(aExtinct, lambda, NULL,
1534  cpl_errorstate prestate = cpl_errorstate_get();
1535  double referr = 0.,
1536  ref = muse_flux_response_interpolate(aReference, lambda, &referr,
1538  /* on error, try again without trying to find the fluxerr column */
1539  if (!cpl_errorstate_is_equal(prestate)) {
1540  cpl_errorstate_set(prestate); /* don't want to propagate this error outside */
1541  ref = muse_flux_response_interpolate(aReference, lambda, NULL,
1543  }
1544  /* calibration factor at this wavelength: ratio of observed count *
1545  * rate corrected for extinction to expected flux in magnitudes */
1546  double c = 2.5 * log10(data[l + aStar*nlambda]
1547  / exptime / cdelt / ref)
1548  + aAirmass * extinct,
1549  cerr = sqrt(pow(referr / ref, 2) + stat[l + aStar*nlambda]
1550  / pow(data[l + aStar*nlambda], 2))
1551  * 2.5 / CPL_MATH_LN10;
1552  cpl_table_set_double(sensitivity, "lambda", idx, lambda);
1553  cpl_table_set_double(sensitivity, "sens", idx, c);
1554  cpl_table_set_double(sensitivity, "serr", idx, cerr);
1555  cpl_table_set_int(sensitivity, "dq", idx, EURO3D_GOODPIXEL);
1556  idx++;
1557  } /* for l (all wavelengths) */
1558  /* cut the data to the used size */
1559  cpl_table_set_size(sensitivity, idx);
1560 } /* muse_flux_response_sensitivity() */
1561 
1562 /*----------------------------------------------------------------------------*/
1575 /*----------------------------------------------------------------------------*/
1576 static void
1577 muse_flux_response_mark_questionable(muse_flux_object *aFluxObj)
1578 {
1579  if (!aFluxObj) {
1580  cpl_error_set(__func__, CPL_ERROR_NULL_INPUT);
1581  return;
1582  }
1583  if (!aFluxObj->tellbands) {
1584  cpl_error_set(__func__, CPL_ERROR_NULL_INPUT);
1585  return;
1586  }
1587  cpl_table *tsens = aFluxObj->sensitivity,
1588  *tb = aFluxObj->tellbands;
1589 
1590  /* check for MUSE setup: is it nominal wavelength range? */
1591  cpl_boolean isnominal = muse_pfits_get_mode(aFluxObj->intimage->header)
1592  > MUSE_MODE_WFM_NONAO_X;
1593  /* exclude regions within the telluric absorption bands *
1594  * and outside wavelength range */
1595  int irow, nfluxes = cpl_table_get_nrow(tsens);
1596  for (irow = 0; irow < nfluxes; irow++) {
1597  double lambda = cpl_table_get_double(tsens, "lambda", irow, NULL);
1598  unsigned int dq = EURO3D_GOODPIXEL,
1599  k, nk = cpl_table_get_nrow(tb);
1600  for (k = 0; k < nk; k++) {
1601  double lmin = cpl_table_get_double(tb, "lmin", k, NULL),
1602  lmax = cpl_table_get_double(tb, "lmax", k, NULL);
1603  if (lambda >= lmin && lambda <= lmax) {
1604  dq |= EURO3D_TELLURIC;
1605  }
1606  } /* for k */
1607  if (isnominal && lambda < kMuseNominalCutoff) {
1608  dq |= EURO3D_OUTSDRANGE;
1609  }
1610  cpl_table_set_int(tsens, "dq", irow, dq);
1611  } /* for irow (all table) */
1612 } /* muse_flux_response_mark_questionable() */
1613 
1614 /*----------------------------------------------------------------------------*/
1637 /*----------------------------------------------------------------------------*/
1638 static cpl_polynomial *
1639 muse_flux_response_fit(muse_flux_object *aFluxObj,
1640  double aLambda1, double aLambda2,
1641  unsigned int aOrder, double aRSigma, double *aRMSE)
1642 {
1643  cpl_table *tsens = aFluxObj->sensitivity;
1644  cpl_table_select_all(tsens); /* default, but make sure it's true...*/
1645  cpl_table_and_selected_int(tsens, "dq", CPL_NOT_EQUAL_TO, EURO3D_GOODPIXEL);
1646  cpl_table_and_selected_int(tsens, "dq", CPL_NOT_EQUAL_TO, EURO3D_TELLCOR);
1647  cpl_table_or_selected_double(tsens, "lambda", CPL_LESS_THAN, aLambda1);
1648  cpl_table_or_selected_double(tsens, "lambda", CPL_GREATER_THAN, aLambda2);
1649  /* keep the "bad" ones around */
1650  cpl_table *tunwanted = cpl_table_extract_selected(tsens);
1651  cpl_table_erase_selected(tsens);
1652  muse_flux_response_dump_sensitivity(aFluxObj, "fitinput");
1653 
1654  /* convert sensitivity table to matrix (lambda) and vectors (sens *
1655  * and serr), exclude pixels marked as not EURO3D_GOODPIXEL */
1656  int nrow = cpl_table_get_nrow(tsens);
1657  cpl_matrix *lambdas = cpl_matrix_new(1, nrow);
1658  cpl_vector *sens = cpl_vector_new(nrow),
1659  *serr = cpl_vector_new(nrow);
1660  memcpy(cpl_matrix_get_data(lambdas),
1661  cpl_table_get_data_double_const(tsens, "lambda"), nrow*sizeof(double));
1662  memcpy(cpl_vector_get_data(sens),
1663  cpl_table_get_data_double_const(tsens, "sens"), nrow*sizeof(double));
1664  memcpy(cpl_vector_get_data(serr),
1665  cpl_table_get_data_double_const(tsens, "serr"), nrow*sizeof(double));
1666 
1667  /* do the fit */
1668  double chisq, mse;
1669  cpl_polynomial *fit = muse_utils_iterate_fit_polynomial(lambdas, sens, serr,
1670  tsens, aOrder, aRSigma,
1671  &mse, &chisq);
1672  int nout = cpl_vector_get_size(sens);
1673 #if 0
1674  cpl_msg_debug(__func__, "transferred %d entries (%.3f...%.3f) for the "
1675  "order %u fit, %d entries are left, RMS %f", nrow, aLambda1,
1676  aLambda2, aOrder, nout, sqrt(mse));
1677 #endif
1678  cpl_matrix_delete(lambdas);
1679  cpl_vector_delete(sens);
1680  cpl_vector_delete(serr);
1681  if (aRMSE) {
1682  *aRMSE = mse / (nout - aOrder - 1);
1683  }
1684 
1685  /* put "bad" entries back, at the end of the table */
1686  cpl_table_insert(tsens, tunwanted, nout);
1687  cpl_table_delete(tunwanted);
1688  return fit;
1689 } /* muse_flux_response_fit() */
1690 
1691 /*----------------------------------------------------------------------------*/
1710 /*----------------------------------------------------------------------------*/
1711 static void
1712 muse_flux_response_telluric(muse_flux_object *aFluxObj, double aAirmass)
1713 {
1714  cpl_table *tsens = aFluxObj->sensitivity,
1715  *tb = aFluxObj->tellbands;
1716  cpl_table_new_column(tsens, "sens_orig", CPL_TYPE_DOUBLE);
1717  cpl_table_new_column(tsens, "serr_orig", CPL_TYPE_DOUBLE);
1718  cpl_table_new_column(tsens, "tellcor", CPL_TYPE_DOUBLE);
1719  unsigned int k, nk = cpl_table_get_nrow(tb);
1720  for (k = 0; k < nk; k++) {
1721  unsigned int order = 2;
1722  double lmin = cpl_table_get_double(tb, "lmin", k, NULL),
1723  lmax = cpl_table_get_double(tb, "lmax", k, NULL),
1724  bgmin = cpl_table_get_double(tb, "bgmin", k, NULL),
1725  bgmax = cpl_table_get_double(tb, "bgmax", k, NULL);
1726  /* if we extrapolate (redward) then use a linear fit only */
1727  if (bgmax < lmax) {
1728  order = 1;
1729  }
1730  double rmse = 0.;
1731  cpl_polynomial *fit = muse_flux_response_fit(aFluxObj, bgmin, bgmax,
1732  order, 3., &rmse);
1733  cpl_msg_debug(__func__, "Telluric region %u: %.2f...%.2f, reference region "
1734  "%.2f...%.2f", k + 1, lmin, lmax, bgmin, bgmax);
1735 #if 0
1736  cpl_polynomial_dump(fit, stdout);
1737  fflush(stdout);
1738 #endif
1739  int irow, nrow = cpl_table_get_nrow(tsens);
1740  for (irow = 0; irow < nrow; irow++) {
1741  double lambda = cpl_table_get_double(tsens, "lambda", irow, NULL);
1742  if (lambda >= lmin && lambda <= lmax &&
1743  (unsigned int)cpl_table_get_int(tsens, "dq", irow, NULL)
1744  == EURO3D_TELLURIC) {
1745  double origval = cpl_table_get_double(tsens, "sens", irow, NULL),
1746  origerr = cpl_table_get_double(tsens, "serr", irow, NULL),
1747  interpval = cpl_polynomial_eval_1d(fit, lambda, NULL);
1748  cpl_table_set_int(tsens, "dq", irow, EURO3D_TELLCOR);
1749  cpl_table_set_double(tsens, "sens_orig", irow, origval);
1750  cpl_table_set_double(tsens, "sens", irow, interpval);
1751  /* correct the error bars of the fitted points *
1752  * by adding in quadrature the reduced MSE */
1753  cpl_table_set_double(tsens, "serr_orig", irow, origerr);
1754  cpl_table_set_double(tsens, "serr", irow, sqrt(origerr*origerr + rmse));
1755 
1756  if (interpval > origval) {
1757  /* compute the factor, as flux ratio */
1758  double ftelluric = pow(10, -0.4 * (interpval - origval));
1759  cpl_table_set(tsens, "tellcor", irow, ftelluric);
1760  } else {
1761  cpl_table_set_double(tsens, "tellcor", irow, 1.);
1762  }
1763  }
1764  } /* for irow */
1765  cpl_polynomial_delete(fit);
1766  } /* for k (telluric line regions) */
1767  /* correct for the airmass */
1768  cpl_table_power_column(tsens, "tellcor", 1. / aAirmass);
1769 } /* muse_flux_response_telluric() */
1770 
1771 /*----------------------------------------------------------------------------*/
1788 /*----------------------------------------------------------------------------*/
1789 static void
1790 muse_flux_response_extrapolate(muse_flux_object *aFluxObj, double aDistance)
1791 {
1792  cpl_table *tsens = aFluxObj->sensitivity;
1793  cpl_propertylist *order = cpl_propertylist_new();
1794  cpl_propertylist_append_bool(order, "lambda", CPL_FALSE);
1795  cpl_table_sort(tsens, order);
1796 
1797  int nrow = cpl_table_get_nrow(tsens);
1798  /* first and last good wavelength and corresponsing error */
1799  double lambda1 = cpl_table_get_double(tsens, "lambda", 0, NULL),
1800  serr1 = cpl_table_get_double(tsens, "serr", 0, NULL);
1801  unsigned int dq = cpl_table_get_int(tsens, "dq", 0, NULL);
1802  int irow = 0;
1803  while (dq != EURO3D_GOODPIXEL && dq != EURO3D_TELLCOR) {
1804  lambda1 = cpl_table_get_double(tsens, "lambda", ++irow, NULL);
1805  serr1 = cpl_table_get_double(tsens, "serr", irow, NULL);
1806  dq = cpl_table_get_int(tsens, "dq", irow, NULL);
1807  }
1808  double lambda2 = cpl_table_get_double(tsens, "lambda", nrow - 1, NULL),
1809  serr2 = cpl_table_get_double(tsens, "serr", nrow - 1, NULL);
1810  dq = cpl_table_get_int(tsens, "dq", nrow - 1, NULL);
1811  irow = nrow - 1;
1812  while (dq != EURO3D_GOODPIXEL && dq != EURO3D_TELLCOR) {
1813  lambda2 = cpl_table_get_double(tsens, "lambda", --irow, NULL);
1814  serr2 = cpl_table_get_double(tsens, "serr", irow, NULL);
1815  dq = cpl_table_get_int(tsens, "dq", irow, NULL);
1816  }
1817  cpl_polynomial *fit1 = muse_flux_response_fit(aFluxObj, lambda1,
1818  lambda1 + aDistance, 1, 5., NULL),
1819  *fit2 = muse_flux_response_fit(aFluxObj, lambda2 - aDistance,
1820  lambda2, 1, 5., NULL);
1821  nrow = cpl_table_get_nrow(tsens); /* fitting may have erased rows! */
1822  double d1 = (lambda1 - kMuseLambdaMinX) / 100., /* want 10 additional entries... */
1823  d2 = (kMuseLambdaMaxX - lambda2) / 100.; /* ... at each end */
1824  cpl_table_set_size(tsens, nrow + 200);
1825  irow = nrow;
1826  double l;
1827  for (l = kMuseLambdaMinX; l <= lambda1 - d1; l += d1) {
1828  double sens = cpl_polynomial_eval_1d(fit1, l, NULL),
1829  /* error that doubles every 50 Angstrom */
1830  serr = 2. * (lambda1 - l) / 50. * serr1 + serr1;
1831  if (sens <= 0) {
1832  cpl_table_set_invalid(tsens, "lambda", irow++);
1833  cpl_msg_debug(__func__, "invalid blueward extrapolation: %.3f %f +/- %f",
1834  l, sens, serr);
1835  continue;
1836  }
1837  cpl_table_set_double(tsens, "lambda", irow, l);
1838  cpl_table_set_double(tsens, "sens", irow, sens);
1839  cpl_table_set_double(tsens, "serr", irow, serr);
1840  cpl_table_set_int(tsens, "dq", irow++, (int)EURO3D_OUTSDRANGE);
1841  } /* for l */
1842  for (l = lambda2 + d2; l <= kMuseLambdaMaxX; l += d2) {
1843  double sens = cpl_polynomial_eval_1d(fit2, l, NULL),
1844  serr = 2. * (l - lambda2) / 50. * serr2 + serr2;
1845  if (sens <= 0) {
1846  cpl_table_set_invalid(tsens, "lambda", irow++);
1847  cpl_msg_debug(__func__, "invalid redward extrapolation: %.3f %f +/- %f",
1848  l, sens, serr);
1849  continue;
1850  }
1851  cpl_table_set_double(tsens, "lambda", irow, l);
1852  cpl_table_set_double(tsens, "sens", irow, sens);
1853  cpl_table_set_double(tsens, "serr", irow, serr);
1854  cpl_table_set_int(tsens, "dq", irow++, (int)EURO3D_OUTSDRANGE);
1855  } /* for l */
1856  cpl_msg_debug(__func__, "Extrapolated regions %.1f...%.1f Angstrom (using data "
1857  "from %.1f...%.1f A) and %.1f...%.1f Angstrom (using %.1f...%.1f A)",
1858  kMuseLambdaMinX, lambda1 - d1, lambda1, lambda1 + aDistance,
1859  lambda2 + d2, kMuseLambdaMaxX, lambda2 - aDistance, lambda2);
1860 #if 0
1861  cpl_polynomial_dump(fit1, stdout);
1862  cpl_polynomial_dump(fit2, stdout);
1863  fflush(stdout);
1864 #endif
1865  cpl_polynomial_delete(fit1);
1866  cpl_polynomial_delete(fit2);
1867  /* clean up invalid entries */
1868  cpl_table_select_all(tsens);
1869  cpl_table_and_selected_invalid(tsens, "sens");
1870  cpl_table_erase_selected(tsens);
1871  /* sort the resulting table again */
1872  cpl_table_sort(tsens, order);
1873  cpl_propertylist_delete(order);
1874 } /* muse_flux_response_extrapolate() */
1875 
1876 /*----------------------------------------------------------------------------*/
1928 /*----------------------------------------------------------------------------*/
1929 cpl_error_code
1931  muse_flux_selection_type aSelect, double aAirmass,
1932  const cpl_table *aReference,
1933  const cpl_table *aTellBands,
1934  const cpl_table *aExtinct)
1935 {
1936  cpl_ensure_code(aFluxObj && aFluxObj->intimage && aReference,
1937  CPL_ERROR_NULL_INPUT);
1938  cpl_ensure_code(aAirmass >= 1., CPL_ERROR_ILLEGAL_INPUT);
1939  if (!aExtinct) {
1940  cpl_msg_warning(__func__, "Extinction table not given!");
1941  }
1942  if (aSelect == MUSE_FLUX_SELECT_NEAREST &&
1943  (!isfinite(aFluxObj->raref) || !isfinite(aFluxObj->decref))) {
1944  cpl_msg_warning(__func__, "Reference position %f,%f contains infinite "
1945  "values, using flux to select star!", aFluxObj->raref,
1946  aFluxObj->decref);
1947  aSelect = MUSE_FLUX_SELECT_BRIGHTEST;
1948  }
1949  muse_flux_response_set_telluric_bands(aFluxObj, aTellBands);
1950 
1951  int nobjects = cpl_image_get_size_y(aFluxObj->intimage->data);
1952  const char *bunit = cpl_propertylist_get_string(aFluxObj->intimage->header,
1953  "BUNIT");
1954  /* find brightest and nearest star */
1955  double flux = 0., dmin = DBL_MAX;
1956  int n, nstar = 1, nstardist = 1;
1957  for (n = 1; n <= nobjects; n++) {
1958  char kw[KEYWORD_LENGTH];
1959  snprintf(kw, KEYWORD_LENGTH, MUSE_HDR_FLUX_OBJn_RA, n);
1960  double ra = cpl_propertylist_get_double(aFluxObj->intimage->header, kw);
1961  snprintf(kw, KEYWORD_LENGTH, MUSE_HDR_FLUX_OBJn_DEC, n);
1962  double dec = cpl_propertylist_get_double(aFluxObj->intimage->header, kw),
1963  dthis = muse_astro_angular_distance(ra, dec, aFluxObj->raref,
1964  aFluxObj->decref);
1965  cpl_msg_debug(__func__, "distance(%d) = %f arcsec", n, dthis * 3600.);
1966  if (fabs(dthis) < dmin) {
1967  dmin = dthis;
1968  nstardist = n;
1969  }
1970  snprintf(kw, KEYWORD_LENGTH, MUSE_HDR_FLUX_OBJn_FLUX, n);
1971  double this = cpl_propertylist_get_double(aFluxObj->intimage->header, kw);
1972  cpl_msg_debug(__func__, "flux(%d) = %e %s", n, this, bunit);
1973  if (this > flux) {
1974  flux = this;
1975  nstar = n;
1976  }
1977  } /* for n (all objects) */
1978  int nselected;
1979  char *outstring = NULL;
1980  if (aSelect == MUSE_FLUX_SELECT_BRIGHTEST) {
1981  outstring = cpl_sprintf("Selected the brightest star (%d of %d; %.3e %s)"
1982  " as reference source", nstar, nobjects, flux, bunit);
1983  nselected = nstar;
1984  } else {
1985  outstring = cpl_sprintf("Selected the nearest star (%d of %d; %.2f arcsec) "
1986  "as reference source", nstar, nobjects, dmin*3600.);
1987  nselected = nstardist;
1988  }
1989  cpl_msg_info(__func__, "%s", outstring);
1990  cpl_free(outstring);
1991  /* table of sensitivity function, its sigma, and a Euro3D-like quality flag */
1992  muse_flux_response_sensitivity(aFluxObj,
1993  nselected - 1, aReference,
1994  aAirmass, aExtinct);
1995  muse_flux_response_dump_sensitivity(aFluxObj, "initial");
1996 
1997  muse_flux_response_mark_questionable(aFluxObj);
1998  muse_flux_response_dump_sensitivity(aFluxObj, "intermediate");
1999  /* remove the ones outside the wavelength range */
2000  cpl_table_select_all(aFluxObj->sensitivity);
2001  cpl_table_and_selected_int(aFluxObj->sensitivity, "dq", CPL_EQUAL_TO,
2002  (int)EURO3D_OUTSDRANGE);
2003  cpl_table_erase_selected(aFluxObj->sensitivity);
2004  muse_flux_response_dump_sensitivity(aFluxObj, "intercut");
2005 
2006  /* interpolate telluric (dq == EURO3D_TELLURIC) regions *
2007  * and compute telluric correction factor */
2008  muse_flux_response_telluric(aFluxObj, aAirmass);
2009  muse_flux_response_dump_sensitivity(aFluxObj, "interpolated");
2010  /* extend the wavelength range using linear extrapolation */
2011  muse_flux_response_extrapolate(aFluxObj, 150.);
2012  muse_flux_response_dump_sensitivity(aFluxObj, "extrapolated");
2013 
2014  return CPL_ERROR_NONE;
2015 } /* muse_flux_response_compute() */
2016 
2017 /*----------------------------------------------------------------------------*/
2026 /*----------------------------------------------------------------------------*/
2028  { "lambda", CPL_TYPE_DOUBLE, "Angstrom", "%7.2f", "wavelength", CPL_TRUE },
2029  { "response", CPL_TYPE_DOUBLE,
2030  "2.5*log10((count/s/Angstrom)/(erg/s/cm**2/Angstrom))", "%.4e",
2031  "instrument response derived from standard star", CPL_TRUE },
2032  { "resperr", CPL_TYPE_DOUBLE,
2033  "2.5*log10((count/s/Angstrom)/(erg/s/cm**2/Angstrom))", "%.4e",
2034  "instrument response error derived from standard star", CPL_TRUE },
2035  { NULL, 0, NULL, NULL, NULL, CPL_FALSE }
2036 };
2037 
2038 /*----------------------------------------------------------------------------*/
2047 /*----------------------------------------------------------------------------*/
2049  { "lambda", CPL_TYPE_DOUBLE, "Angstrom", "%7.2f", "wavelength", CPL_TRUE },
2050  { "ftelluric", CPL_TYPE_DOUBLE, "", "%.5f",
2051  "the telluric correction factor, normalized to an airmass of 1", CPL_TRUE },
2052  { "ftellerr", CPL_TYPE_DOUBLE, "", "%.5f",
2053  "the error of the telluric correction factor", CPL_TRUE },
2054  { NULL, 0, NULL, NULL, NULL, CPL_FALSE }
2055 };
2056 
2057 /*----------------------------------------------------------------------------*/
2076 /*----------------------------------------------------------------------------*/
2077 static void
2078 muse_flux_get_response_table_smooth(cpl_table *aResp, double aHalfwidth,
2079  double aLambdaMin, double aLambdaMax,
2080  cpl_boolean aAverage)
2081 {
2082  /* duplicate the input columns, to not disturb the smoothing while running */
2083  cpl_table_duplicate_column(aResp, "sens", aResp, "response");
2084  cpl_table_duplicate_column(aResp, "serr", aResp, "resperr");
2085 
2086  /* select the rows which to use for the smoothing */
2087  cpl_table_select_all(aResp);
2088  cpl_table_and_selected_double(aResp, "lambda", CPL_NOT_LESS_THAN, aLambdaMin);
2089  cpl_table_and_selected_double(aResp, "lambda", CPL_NOT_GREATER_THAN, aLambdaMax);
2090 
2091  cpl_boolean sym = cpl_table_count_selected(aResp) < cpl_table_get_nrow(aResp);
2092  cpl_msg_debug(__func__, "%s smoothing response +/- %.3f Angstrom between %.3f "
2093  "and %.3f Angstrom", sym ? "symmetrical" : "", aHalfwidth,
2094  aLambdaMin, aLambdaMax);
2095 
2096  /* sliding median to get the values, and its median deviation for the error */
2097  int i, n = cpl_table_get_nrow(aResp);
2098  for (i = 0; i < n; i++) {
2099  if (!cpl_table_is_selected(aResp, i)) {
2100  continue;
2101  }
2102  double lambda = cpl_table_get_double(aResp, "lambda", i, NULL);
2103  int j = i, j1 = i, j2 = i;
2104  /* search for the range */
2105  while (--j > 0 && cpl_table_is_selected(aResp, j) &&
2106  lambda - cpl_table_get_double(aResp, "lambda", j, NULL) <= aHalfwidth) {
2107  j1 = j;
2108  }
2109  j = i;
2110  while (++j < n && cpl_table_is_selected(aResp, j) &&
2111  cpl_table_get_double(aResp, "lambda", j, NULL) - lambda <= aHalfwidth) {
2112  j2 = j;
2113  }
2114  if (sym) { /* adjust ranges to the smaller one for symmetrical smoothing */
2115  int jd1 = i - j1,
2116  jd2 = j2 - i;
2117  if (jd1 < jd2) {
2118  j2 = i + jd1;
2119  } else {
2120  j1 = i + jd2;
2121  }
2122  } /* if sym */
2123 
2124  double *sens = cpl_table_get_data_double(aResp, "sens"),
2125  *serr = cpl_table_get_data_double(aResp, "serr");
2126  cpl_vector *v = cpl_vector_wrap(j2 - j1 + 1, sens + j1),
2127  *ve = cpl_vector_wrap(j2 - j1 + 1, serr + j1);
2128  if (aAverage) {
2129  /* sliding average */
2130  double mean = cpl_vector_get_mean(v),
2131  stdev = cpl_vector_get_stdev(v),
2132  rerr = cpl_table_get_double(aResp, "resperr", i, NULL);
2133  cpl_table_set_double(aResp, "response", i, mean);
2134  cpl_table_set_double(aResp, "resperr", i, sqrt(rerr*rerr + stdev*stdev));
2135  } else {
2136  /* sliding median */
2137  double median = cpl_vector_get_median_const(v),
2138  mdev = muse_cplvector_get_adev_const(v, median),
2139  mederr = cpl_vector_get_median_const(ve);
2140  if (j2 == j1) { /* for single points, copy the error */
2141  mdev = cpl_table_get_double(aResp, "serr", j1, NULL);
2142  }
2143  if (mdev < mederr) {
2144  mdev = mederr;
2145  }
2146 #if 0
2147  cpl_msg_debug(__func__, "%d %.3f %d...%d --> %f +/- %f", i, lambda, j1, j2,
2148  median, mdev);
2149 #endif
2150  cpl_table_set_double(aResp, "response", i, median);
2151  cpl_table_set_double(aResp, "resperr", i, mdev);
2152  }
2153  cpl_vector_unwrap(v);
2154  cpl_vector_unwrap(ve);
2155  } /* for i (all aResp rows) */
2156 
2157  /* erase the extra columns again */
2158  cpl_table_erase_column(aResp, "sens");
2159  cpl_table_erase_column(aResp, "serr");
2160 } /* muse_flux_get_response_table_smooth() */
2161 
2162 /*----------------------------------------------------------------------------*/
2179 /*----------------------------------------------------------------------------*/
2180 static unsigned int
2181 muse_flux_get_response_table_collect_points(const cpl_table *aTable,
2182  double aLambda, double aLDist,
2183  cpl_matrix *aPos, cpl_vector *aVal,
2184  cpl_vector *aErr)
2185 {
2186  unsigned int np = 0; /* counter for number of transfered points */
2187  int irow, nrow = cpl_table_get_nrow(aTable);
2188  for (irow = 0; irow < nrow; irow++) {
2189  double lambda = cpl_table_get(aTable, "lambda", irow, NULL);
2190  if (lambda < aLambda - aLDist || lambda > aLambda + aLDist) {
2191  continue;
2192  }
2193  cpl_matrix_set(aPos, 0, np, lambda);
2194  cpl_vector_set(aVal, np, cpl_table_get(aTable, "sens", irow, NULL));
2195  cpl_vector_set(aErr, np, cpl_table_get(aTable, "serr", irow, NULL));
2196  np++;
2197  } /* for irow */
2198  cpl_matrix_set_size(aPos, 1, np);
2199  cpl_vector_set_size(aVal, np);
2200  cpl_vector_set_size(aErr, np);
2201  return np;
2202 } /* muse_flux_get_response_table_collect_points() */
2203 
2204 /*----------------------------------------------------------------------------*/
2229 /*----------------------------------------------------------------------------*/
2230 static void
2231 muse_flux_get_response_table_piecewise_poly(cpl_table *aResp, double aDMin,
2232  double aDMax, float aRSigma)
2233 {
2234  /* duplicate the input columns, to not disturb the smoothing while running */
2235  cpl_table_duplicate_column(aResp, "sens", aResp, "response");
2236  cpl_table_duplicate_column(aResp, "serr", aResp, "resperr");
2237 
2238  /* variables to keep track of jumps that need to be fixed afterwards */
2239  unsigned int npold = 0, njumps = 0;
2240  double ldistold = -1,
2241  lambdaold = -1;
2242  cpl_array *jumppos = cpl_array_new(0, CPL_TYPE_DOUBLE),
2243  *jumplen = cpl_array_new(0, CPL_TYPE_DOUBLE);
2244 
2245  /* compute the piecewise cubic polynomial for the data around each input datapoint */
2246  int irow, nrow = cpl_table_get_nrow(aResp);
2247  for (irow = 0; irow < nrow; irow++) {
2248  double lambda = cpl_table_get(aResp, "lambda", irow, NULL);
2249  /* set up breakpoints every aDMax Angstrom, but set them every *
2250  * aDMin Angstrom between 5700...6200 and 6900...7200 Angstrom, *
2251  * to better model the instrumental/flat-field features */
2252  double ldist = aDMax;
2253  if ((lambda >= 5700 && lambda < 6200) || (lambda >= 6900 && lambda < 7200)) {
2254  ldist = aDMin;
2255  }
2256  /* collect all data ldist / 2 below and ldist / 2 above the knot wavelength */
2257  cpl_matrix *pos = cpl_matrix_new(1, nrow); /* start with far too long ones */
2258  cpl_vector *val = cpl_vector_new(nrow),
2259  *err = cpl_vector_new(nrow);
2260  unsigned int np = muse_flux_get_response_table_collect_points(aResp,
2261  lambda, ldist,
2262  pos, val, err);
2263  if (!ldistold < 0) {
2264  npold = np;
2265  ldistold = ldist;
2266  lambdaold = lambda;
2267  }
2268 #if 0
2269  printf("%f Angstrom %u points (%u, %.3f):\n", lambda, np, npold, (double)np / npold - 1.);
2270  //cpl_matrix_dump(pos, stdout);
2271  fflush(stdout);
2272 #endif
2273  /* the number of points changed more than 10% */
2274  if (np > 10 && fabs((double)np / npold - 1.) > 0.1) {
2275  cpl_msg_debug(__func__, "possible jump, changed at lambda %.3f (%u -> %u, "
2276  "%.3f -> %.3f)", lambda, npold, np, ldistold, ldist);
2277  cpl_array_set_size(jumppos, ++njumps);
2278  cpl_array_set_size(jumplen, njumps);
2279  cpl_array_set_double(jumppos, njumps - 1, (lambdaold + lambda) / 2.);
2280  cpl_array_set_double(jumplen, njumps - 1, (ldistold + ldist) / 2.);
2281  }
2282  /* we want to simulate a cubic spline, i.e. use order 3, *
2283  * but we have to do with the number of points we got */
2284  unsigned int order = np > 3 ? 3 : np - 1;
2285  double mse;
2286  /* fit the polynomial, but without rejection, i.e. use high rejection sigma */
2287  cpl_polynomial *poly = muse_utils_iterate_fit_polynomial(pos, val, err,
2288  NULL, order, aRSigma,
2289  &mse, NULL);
2290 #if 0
2291  if (fabs(lambda - 40861.3) < 1.) {
2292  cpl_vector *res = cpl_vector_new(cpl_vector_get_size(val));
2293  cpl_vector_fill_polynomial_fit_residual(res, val, NULL, poly, pos, NULL);
2294  double rms = sqrt(cpl_vector_product(res, res) / cpl_vector_get_size(res));
2295  cpl_msg_debug(__func__, "lambda %f rms %f (%u/%d points)", lambda, rms,
2296  (unsigned)cpl_vector_get_size(val), np);
2297  //const cpl_vector *v[] = { NULL, val, res };
2298  cpl_plot_vector(NULL, NULL, NULL, val);
2299  cpl_plot_vector(NULL, NULL, NULL, res);
2300  cpl_vector_delete(res);
2301  }
2302 #endif
2303  cpl_matrix_delete(pos);
2304  cpl_vector_delete(val);
2305  cpl_vector_delete(err);
2306  double resp = cpl_polynomial_eval_1d(poly, lambda, NULL);
2307  cpl_polynomial_delete(poly);
2308  cpl_table_set(aResp, "lambda", irow, lambda);
2309  cpl_table_set(aResp, "response", irow, resp);
2310  double serr = cpl_table_get(aResp, "serr", irow, NULL);
2311  cpl_table_set(aResp, "resperr", irow, sqrt(mse + serr*serr));
2312 
2313  npold = np;
2314  ldistold = ldist;
2315  lambdaold = lambda;
2316  } /* for i (all rows) */
2317 
2318  /* erase the extra columns again */
2319  cpl_table_erase_column(aResp, "sens");
2320  cpl_table_erase_column(aResp, "serr");
2321 
2322 #if 0
2323  printf("jumppos (%u):\n", njumps);
2324  cpl_array_dump(jumppos, 0, 10000, stdout);
2325  printf("jumplen:\n");
2326  cpl_array_dump(jumplen, 0, 10000, stdout);
2327  fflush(stdout);
2328 #endif
2329 
2330  /* this needs median smoothing afterwards as well *
2331  * to remove the jumps where the length changed */
2332  unsigned int iarr;
2333  for (iarr = 0; iarr < njumps; iarr++) {
2334  double lambda = cpl_array_get_double(jumppos, iarr, NULL),
2335  ldist = cpl_array_get_double(jumplen, iarr, NULL) / 2;
2336  /* check that the step in the +/- 5 Angstrom region actually worth worrying about */
2337  cpl_table_select_all(aResp);
2338  cpl_table_and_selected_double(aResp, "lambda", CPL_NOT_LESS_THAN, lambda - 5.);
2339  cpl_table_and_selected_double(aResp, "lambda", CPL_NOT_GREATER_THAN, lambda + 5.);
2340  int nsel = cpl_table_count_selected(aResp);
2341  if (nsel <= 1) {
2342  cpl_msg_debug(__func__, "Only %d points near jump around %.1f Angstrom, "
2343  "not doing anything", nsel, lambda);
2344  continue;
2345  }
2346  cpl_table *xresp = cpl_table_extract_selected(aResp);
2347  double stdev = cpl_table_get_column_stdev(xresp, "response");
2348  cpl_table_dump(xresp, 0, nsel, stdout);
2349  fflush(stdout);
2350  cpl_table_delete(xresp);
2351  if (stdev < 0.001) {
2352  cpl_msg_debug(__func__, "%d points near jump around %.1f Angstrom, stdev "
2353  "only %f, not doing anything", nsel, lambda, stdev);
2354  continue;
2355  }
2356  cpl_msg_debug(__func__, "%d points near jump around %.1f Angstrom, stdev "
2357  "%f, erasing the region", nsel, lambda, stdev);
2358 
2359  /* erase the affected part of the response, linear *
2360  * interpolation when applying will take care of the gap */
2361  cpl_table_select_all(aResp);
2362  cpl_table_and_selected_double(aResp, "lambda", CPL_NOT_LESS_THAN, lambda - ldist);
2363  cpl_table_and_selected_double(aResp, "lambda", CPL_NOT_GREATER_THAN, lambda + ldist);
2364  cpl_table_erase_selected(aResp);
2365  } /* for iarr (all jump points) */
2366  cpl_array_delete(jumppos);
2367  cpl_array_delete(jumplen);
2368 } /* muse_flux_get_response_table_piecewise_poly() */
2369 
2370 /*----------------------------------------------------------------------------*/
2395 /*----------------------------------------------------------------------------*/
2396 cpl_error_code
2398  muse_flux_smooth_type aSmooth)
2399 {
2400  cpl_ensure_code(aFluxObj && aFluxObj->sensitivity, CPL_ERROR_NULL_INPUT);
2401  cpl_ensure_code(aSmooth <= MUSE_FLUX_SMOOTH_PPOLY, CPL_ERROR_ILLEGAL_INPUT);
2402 
2403  int nrow = cpl_table_get_nrow(aFluxObj->sensitivity);
2404  cpl_table *resp = muse_cpltable_new(muse_flux_responsetable_def, nrow);
2405  /* copy the relevant columns from the sensitivity table */
2406  const double *lambdas = cpl_table_get_data_double_const(aFluxObj->sensitivity,
2407  "lambda"),
2408  *sens = cpl_table_get_data_double_const(aFluxObj->sensitivity,
2409  "sens"),
2410  *serr = cpl_table_get_data_double_const(aFluxObj->sensitivity,
2411  "serr");
2412  cpl_table_copy_data_double(resp, "lambda", lambdas);
2413  cpl_table_copy_data_double(resp, "response", sens);
2414  cpl_table_copy_data_double(resp, "resperr", serr);
2415 
2416  /* now smooth the response */
2417  if (aSmooth == MUSE_FLUX_SMOOTH_MEDIAN) {
2418  cpl_msg_info(__func__, "Smoothing response curve with median filter");
2419  /* use a sliding median over +/- 15 Angstrom width */
2420  muse_flux_get_response_table_smooth(resp, 15., 0., 20000., CPL_FALSE);
2421  } else if (aSmooth == MUSE_FLUX_SMOOTH_PPOLY) {
2422  cpl_msg_info(__func__, "Smoothing response curve with piecewise polynomial");
2423  /* use a piecewise polynomial, i.e. a simple, non-contiguous spline */
2424  muse_flux_get_response_table_piecewise_poly(resp, 150., 150., 3.);
2425  /* but this usually needs extra smoothing *
2426  * after the fact with a sliding average */
2427  muse_flux_get_response_table_smooth(resp, 15., 0., 20000., CPL_TRUE);
2428  } else {
2429  cpl_msg_warning(__func__, "NOT smoothing the response curve at all!");
2430  }
2431 
2432  /* set the table in the flux object */
2433  aFluxObj->response = resp;
2434  return CPL_ERROR_NONE;
2435 } /* muse_flux_get_response_table() */
2436 
2437 /*----------------------------------------------------------------------------*/
2456 /*----------------------------------------------------------------------------*/
2457 cpl_error_code
2459 {
2460  cpl_ensure_code(aFluxObj && aFluxObj->sensitivity, CPL_ERROR_NULL_INPUT);
2461  cpl_table *tsens = aFluxObj->sensitivity;
2462  int nrow = cpl_table_get_nrow(tsens);
2463  cpl_table *tell = muse_cpltable_new(muse_flux_tellurictable_def, nrow);
2464  /* copy the lambda and tellcor columns from the sensitivity table */
2465  cpl_table_fill_column_window_double(tell, "lambda", 0, nrow, 0);
2466  cpl_table_copy_data_double(tell, "lambda",
2467  cpl_table_get_data_double_const(tsens, "lambda"));
2468  cpl_table_fill_column_window_double(tell, "ftelluric", 0, nrow, 0);
2469  cpl_table_copy_data_double(tell, "ftelluric",
2470  cpl_table_get_data_double_const(tsens, "tellcor"));
2471  /* XXX no (good) error estimates, for use constant error as starting point */
2472 #define TELL_MAX_ERR 0.1
2473 #define TELL_MIN_ERR 1e-4
2474  cpl_table_fill_column_window_double(tell, "ftellerr", 0, nrow, TELL_MAX_ERR);
2475 
2476  /* duplicate the tellcor column, to get the invalidity info; *
2477  * pad telluric correction factors with 1. in the new table */
2478  cpl_table_duplicate_column(tell, "tellcor", tsens, "tellcor");
2479  /* pad entries adjacent to the ones with a real telluric factor with 1 */
2480  cpl_table_unselect_all(tell);
2481  int irow;
2482  for (irow = 0; irow < nrow; irow++) {
2483  int err;
2484  cpl_table_get_double(tell, "tellcor", irow, &err); /* ignore value */
2485  if (err == 0) { /* has a valid entry -> do nothing */
2486  continue;
2487  }
2488  /* invalid entry, check previous one (again) */
2489  cpl_errorstate state = cpl_errorstate_get();
2490  double ftellcor = cpl_table_get_double(tell, "tellcor", irow - 1, &err);
2491  if (!cpl_errorstate_is_equal(state)) { /* recover from possible errors */
2492  cpl_errorstate_set(state);
2493  }
2494  if (err == 0 && ftellcor != 1.) { /* exist and is not 1 -> pad */
2495  cpl_table_set_double(tell, "ftelluric", irow, 1.);
2496  continue;
2497  }
2498  /* check the next one, too */
2499  state = cpl_errorstate_get();
2500  ftellcor = cpl_table_get_double(tell, "tellcor", irow + 1, &err);
2501  if (!cpl_errorstate_is_equal(state)) { /* recover from possible errors */
2502  cpl_errorstate_set(state);
2503  }
2504  if (err == 0 && ftellcor != 1.) { /* exist and is not 1 -> pad */
2505  cpl_table_set_double(tell, "ftelluric", irow, 1.);
2506  continue;
2507  }
2508  cpl_table_select_row(tell, irow); /* surrounded by invalid -> select */
2509  } /* for irow */
2510  cpl_table_erase_selected(tell); /* erase all the still invalid ones */
2511  cpl_table_erase_column(tell, "tellcor");
2512 
2513  /* next pass: adjust the error to be only about the distance between 1 and the value */
2514  nrow = cpl_table_get_nrow(tell);
2515  for (irow = 0; irow < nrow; irow++) {
2516  int err;
2517  double dftell = 1. - cpl_table_get_double(tell, "ftelluric", irow, &err),
2518  ftellerr = cpl_table_get_double(tell, "ftellerr", irow, &err);
2519  if (ftellerr > dftell) {
2520  ftellerr = fmax(dftell, TELL_MIN_ERR);
2521  cpl_table_set_double(tell, "ftellerr", irow, ftellerr);
2522  }
2523  } /* for irow */
2524 
2525  aFluxObj->telluric = tell;
2526  return CPL_ERROR_NONE;
2527 } /* muse_flux_get_telluric_table() */
2528 
2529 /*----------------------------------------------------------------------------*/
2566 /*----------------------------------------------------------------------------*/
2567 cpl_error_code
2568 muse_flux_calibrate(muse_pixtable *aPixtable, const cpl_table *aResponse,
2569  const cpl_table *aExtinction, const cpl_table *aTelluric)
2570 {
2571  cpl_ensure_code(aPixtable && aPixtable->header && aResponse,
2572  CPL_ERROR_NULL_INPUT);
2573  const char *unitdata = cpl_table_get_column_unit(aPixtable->table,
2574  MUSE_PIXTABLE_DATA);
2575  cpl_ensure_code(unitdata && !strncmp(unitdata, "count", 6),
2576  CPL_ERROR_INCOMPATIBLE_INPUT);
2577  /* warn for non-critical failure */
2578  if (!aExtinction) {
2579  cpl_msg_warning(__func__, "%s missing!", MUSE_TAG_EXTINCT_TABLE);
2580  }
2581 
2582  double exptime = muse_pfits_get_exptime(aPixtable->header);
2583  if (exptime <= 0.) {
2584  cpl_msg_warning(__func__, "Non-positive EXPTIME, not doing flux calibration!");
2585  return CPL_ERROR_ILLEGAL_INPUT;
2586  }
2587  double airmass = muse_astro_airmass(aPixtable->header);
2588  if (airmass < 0) {
2589  cpl_msg_warning(__func__, "Airmass unknown, not doing extinction "
2590  "correction: %s", cpl_error_get_message());
2591  /* reset to zero so that it has no effect */
2592  airmass = 0.;
2593  }
2594 
2595  cpl_table *telluric = NULL;
2596  if (aTelluric) {
2597  /* duplicate the telluric correction table to apply the airmass correction */
2598  telluric = cpl_table_duplicate(aTelluric);
2599  /* use negative exponent to be able to multiply (instead of divide) *
2600  * by the correction factor (should be slightly faster) */
2601  cpl_table_power_column(telluric, "ftelluric", -airmass);
2602  }
2603 
2604  cpl_msg_info(__func__, "Starting flux calibration (exptime=%.2fs, airmass=%.4f),"
2605  " %s telluric correction", exptime, airmass,
2606  aTelluric ? "with" : "without ("MUSE_TAG_STD_TELLURIC" not given)");
2607  float *lambda = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA),
2608  *data = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_DATA),
2609  *stat = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_STAT);
2610  cpl_size i, nrow = muse_pixtable_get_nrow(aPixtable);
2611  #pragma omp parallel for default(none) /* as req. by Ralf */ \
2612  shared(aExtinction, aResponse, airmass, data, exptime, lambda, nrow, \
2613  stat, telluric)
2614  for (i = 0; i < nrow; i++) {
2615  /* double values for intermediate results of this row */
2616  double ddata = data[i], dstat = stat[i];
2617 
2618  /* correct for extinction */
2619  if (aExtinction) {
2620  double fext = pow(10., 0.4 * airmass
2621  * muse_flux_response_interpolate(aExtinction,
2622  lambda[i], NULL,
2624 #if 0
2625  printf("%f, data/stat = %f/%f -> ", fext, ddata, dstat);
2626 #endif
2627  ddata *= fext;
2628  dstat *= (fext * fext);
2629 #if 0
2630  printf(" --> %f/%f\n", ddata, dstat), fflush(NULL);
2631 #endif
2632  }
2633 
2634  /* the difference in lambda coverage per pixel seems to be *
2635  * corrected for by the flat-field, so assuming a constant *
2636  * value for all pixels seems to be the correct thing to do */
2637  double dlambda = kMuseSpectralSamplingA,
2638  dlamerr = 0.02, /* assume typical fixed error for the moment */
2639  /* resp/rerr get returned in mag units as in the table */
2640  rerr, resp = muse_flux_response_interpolate(aResponse, lambda[i],
2641  &rerr,
2643  /* convert from 2.5 log10(x) to non-log flux units */
2644  resp = pow(10., 0.4 * resp);
2645  /* magerr = 2.5 / log(10) * error / flux (see IRAF phot docs) *
2646  * ==> error = magerr / 2.5 * log(10) * flux */
2647  rerr = rerr * CPL_MATH_LN10 * resp / 2.5;
2648 #if 0
2649  printf("%f/%f/%f, %e/%e, data/stat = %e/%e -> ", lambda[i], dlambda, dlamerr, resp, rerr,
2650  ddata, dstat);
2651 #endif
2652  dstat = dstat * pow((1./(resp * exptime * dlambda)), 2)
2653  + pow(ddata * rerr / (resp*resp * exptime * dlambda), 2)
2654  + pow(ddata * dlamerr / (resp * exptime * dlambda*dlambda), 2);
2655  ddata /= (resp * exptime * dlambda);
2656 #if 0
2657  printf("%e/%e\n", ddata, dstat), fflush(NULL);
2658 #endif
2659 
2660  /* now convert to the float values to be stored in the pixel table, *
2661  * scaled by kMuseFluxUnitFactor to keep the floats from underflowing */
2662  ddata *= kMuseFluxUnitFactor;
2663  dstat *= kMuseFluxStatFactor;
2664 
2665  /* do the telluric correction, if the wavelength is redward of the start *
2666  * of the telluric regions, and if a telluric correction was given */
2667  if (lambda[i] < kTelluricBands[0][0] || !telluric) {
2668  data[i] = ddata;
2669  stat[i] = dstat;
2670  continue; /* skip telluric correction in the blue */
2671  }
2672  double terr, tell = muse_flux_response_interpolate(telluric, lambda[i],
2673  &terr,
2675  data[i] = ddata * tell;
2676  stat[i] = tell*tell * dstat + ddata*ddata * terr*terr;
2677  } /* for i (pixel table rows) */
2678  cpl_table_delete(telluric); /* NULL check done there... */
2679 
2680  /* now set the table column headers reflecting the flux units used */
2681  cpl_table_set_column_unit(aPixtable->table, MUSE_PIXTABLE_DATA,
2682  kMuseFluxUnitString);
2683  cpl_table_set_column_unit(aPixtable->table, MUSE_PIXTABLE_STAT,
2684  kMuseFluxStatString);
2685 
2686  /* add the status header */
2687  cpl_propertylist_update_bool(aPixtable->header, MUSE_HDR_PT_FLUXCAL,
2688  CPL_TRUE);
2689  cpl_propertylist_set_comment(aPixtable->header, MUSE_HDR_PT_FLUXCAL,
2690  MUSE_HDR_PT_FLUXCAL_COMMENT);
2691  return CPL_ERROR_NONE;
2692 } /* muse_flux_calibrate() */
2693 
Structure definition of a MUSE datacube.
Definition: muse_datacube.h:47
cpl_table * telluric
Definition: muse_flux.h:79
void muse_image_delete(muse_image *aImage)
Deallocate memory associated to a muse_image object.
Definition: muse_image.c:85
muse_image * intimage
Definition: muse_flux.h:70
muse_flux_smooth_type
Type of response curve smoothing to use.
Definition: muse_flux.h:131
cpl_table * response
Definition: muse_flux.h:75
double muse_pfits_get_ra(const cpl_propertylist *aHeaders)
find out the right ascension
Definition: muse_pfits.c:232
#define MUSE_HDR_PT_FLUXCAL
cpl_image * data
the data extension
Definition: muse_image.h:46
const muse_cpltable_def muse_flux_responsetable_def[]
MUSE response table definition.
Definition: muse_flux.c:2027
cpl_size muse_pixtable_get_nrow(const muse_pixtable *aPixtable)
get the number of rows within the pixel table
muse_flux_profile_type
Type of optimal profile to use.
Definition: muse_flux.h:109
cpl_image * stat
the statistics extension
Definition: muse_image.h:64
void muse_datacube_delete(muse_datacube *aCube)
Deallocate memory associated to a muse_datacube object.
cpl_error_code muse_flux_reference_table_check(cpl_table *aTable)
Check and/or adapt the standard flux reference table format.
Definition: muse_flux.c:168
const muse_cpltable_def muse_response_tellbands_def[]
Table definition for a telluric bands table.
Definition: muse_flux.c:1392
cpl_table * sensitivity
Definition: muse_flux.h:73
Structure definition of MUSE three extension FITS file.
Definition: muse_image.h:40
muse_flux_object * muse_flux_object_new(void)
Allocate memory for a new muse_flux_object object.
Definition: muse_flux.c:68
static double muse_flux_reference_table_sampling(cpl_table *aTable)
Compute average sampling for a MUSE-format flux reference table.
Definition: muse_flux.c:119
cpl_table * table
The pixel table.
cpl_propertylist * header
the FITS header
Definition: muse_image.h:72
cpl_error_code muse_cpltable_check(const cpl_table *aTable, const muse_cpltable_def *aDef)
Check whether the table contains the fields of the definition.
cpl_error_code muse_datacube_save(muse_datacube *aCube, const char *aFilename)
Save the three cube extensions and the FITS headers of a MUSE datacube to a file. ...
muse_resampling_crstats_type crtype
cpl_image * dq
the data quality extension
Definition: muse_image.h:56
cpl_table * muse_cpltable_new(const muse_cpltable_def *aDef, cpl_size aLength)
Create an empty table according to the specified definition.
cpl_error_code muse_flux_get_telluric_table(muse_flux_object *aFluxObj)
Get the table of the telluric correction.
Definition: muse_flux.c:2458
double muse_flux_response_interpolate(const cpl_table *aResponse, double aLambda, double *aError, muse_flux_interpolation_type aType)
Compute linearly interpolated response of some kind at given wavelength.
Definition: muse_flux.c:336
double muse_pfits_get_fwhm_end(const cpl_propertylist *aHeaders)
find out the ambient seeing at end of exposure (in arcsec)
Definition: muse_pfits.c:950
Structure definition of MUSE pixel table.
Flux object to store data needed while computing the flux calibration.
Definition: muse_flux.h:65
double muse_astro_wavelength_vacuum_to_air(double aVac)
Compute air wavelength for a given vacuum wavelength.
Definition: muse_astro.c:534
cpl_error_code muse_wcs_celestial_from_pixel(cpl_propertylist *aHeader, double aX, double aY, double *aRA, double *aDEC)
Convert from pixel coordinates to celestial spherical coordinates.
Definition: muse_wcs.c:1226
cpl_error_code muse_utils_fit_moffat_2d(const cpl_matrix *aPositions, const cpl_vector *aValues, const cpl_vector *aErrors, cpl_array *aParams, cpl_array *aPErrors, const cpl_array *aPFlags, double *aRMS, double *aRedChisq)
Fit a 2D Moffat function to a given set of data.
Definition: muse_utils.c:1822
cpl_error_code muse_flux_response_compute(muse_flux_object *aFluxObj, muse_flux_selection_type aSelect, double aAirmass, const cpl_table *aReference, const cpl_table *aTellBands, const cpl_table *aExtinct)
Compare measured flux distribution over wavelength with calibrated stellar fluxes and derive instrume...
Definition: muse_flux.c:1930
muse_resampling_params * muse_resampling_params_new(muse_resampling_type aMethod)
Create the resampling parameters structure.
cpl_polynomial * muse_utils_iterate_fit_polynomial(cpl_matrix *aPos, cpl_vector *aVal, cpl_vector *aErr, cpl_table *aExtra, const unsigned int aOrder, const double aRSigma, double *aMSE, double *aChiSq)
Iterate a polynomial fit.
Definition: muse_utils.c:2178
double muse_pfits_get_fwhm_start(const cpl_propertylist *aHeaders)
find out the ambient seeing at start of exposure (in arcsec)
Definition: muse_pfits.c:931
muse_datacube * cube
Definition: muse_flux.h:67
cpl_error_code muse_flux_calibrate(muse_pixtable *aPixtable, const cpl_table *aResponse, const cpl_table *aExtinction, const cpl_table *aTelluric)
Convert the input pixel table from counts to fluxes.
Definition: muse_flux.c:2568
cpl_imagelist * data
the cube containing the actual data values
Definition: muse_datacube.h:75
double muse_pfits_get_dec(const cpl_propertylist *aHeaders)
find out the declination
Definition: muse_pfits.c:250
muse_flux_interpolation_type
Type of table interpolation to use.
Definition: muse_flux.h:96
cpl_error_code muse_flux_get_response_table(muse_flux_object *aFluxObj, muse_flux_smooth_type aSmooth)
Get the table of the standard star response function.
Definition: muse_flux.c:2397
muse_datacube * muse_resampling_cube(muse_pixtable *aPixtable, muse_resampling_params *aParams, muse_pixgrid **aPixgrid)
Resample a pixel table onto a regular grid structure representing a FITS NAXIS=3 datacube.
cpl_imagelist * dq
the optional cube containing the bad pixel status
Definition: muse_datacube.h:80
cpl_table * tellbands
Definition: muse_flux.h:77
const muse_cpltable_def muse_flux_tellurictable_def[]
MUSE telluric correction table definition.
Definition: muse_flux.c:2048
cpl_error_code muse_pixtable_save(muse_pixtable *aPixtable, const char *aFilename)
Save a MUSE pixel table to a file on disk.
cpl_error_code muse_flux_integrate_std(muse_pixtable *aPixtable, muse_flux_profile_type aProfile, muse_flux_object *aFluxObj)
Integrate the flux of the standard star(s) in the field over all wavelengths.
Definition: muse_flux.c:1020
cpl_propertylist * header
the FITS header
Definition: muse_datacube.h:56
double muse_pfits_get_exptime(const cpl_propertylist *aHeaders)
find out the exposure time
Definition: muse_pfits.c:347
cpl_propertylist * muse_wcs_apply_cd(const cpl_propertylist *aExpHeader, const cpl_propertylist *aCalHeader)
Apply the CDi_j matrix of an astrometric solution to an observation.
Definition: muse_wcs.c:904
Resampling parameters.
Definition of a cpl table structure.
void muse_resampling_params_delete(muse_resampling_params *aParams)
Delete a resampling parameters structure.
double muse_cplvector_get_adev_const(const cpl_vector *aVector, double aCenter)
Compute the average absolute deviation of a (constant) vector.
muse_image * muse_image_new(void)
Allocate memory for a new muse_image object.
Definition: muse_image.c:66
muse_flux_selection_type
Type of star selection to use.
Definition: muse_flux.h:121
void muse_flux_object_delete(muse_flux_object *aFluxObj)
Deallocate memory associated to a muse_flux_object.
Definition: muse_flux.c:89
int muse_quality_image_reject_using_dq(cpl_image *aData, cpl_image *aDQ, cpl_image *aStat)
Reject pixels of one or two images on a DQ image.
Definition: muse_quality.c:628
double muse_astro_angular_distance(double aRA1, double aDEC1, double aRA2, double aDEC2)
Compute angular distance in the sky between two positions.
Definition: muse_astro.c:496
muse_ins_mode muse_pfits_get_mode(const cpl_propertylist *aHeaders)
find out the observation mode
Definition: muse_pfits.c:1097
static void muse_flux_get_response_table_smooth(cpl_table *aResp, double aHalfwidth, double aLambdaMin, double aLambdaMax, cpl_boolean aAverage)
Get the table of the standard star response function.
Definition: muse_flux.c:2078
double muse_astro_airmass(cpl_propertylist *aHeader)
Derive the effective airmass of an observation from information in a FITS header. ...
Definition: muse_astro.c:331
cpl_imagelist * stat
the cube containing the data variance
Definition: muse_datacube.h:85
cpl_propertylist * header
The FITS header.
cpl_propertylist * muse_wcs_create_default(void)
Create FITS headers containing a default (relative) WCS.
Definition: muse_wcs.c:839