MUSE Pipeline Reference Manual  2.1.1
muse/muse_lsf.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-2015 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
20  * 02110-1301, USA.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 #define STORE_SLIT_WIDTH
28 #define STORE_BIN_WIDTH
29 
30 /*----------------------------------------------------------------------------*
31  * Includes *
32  *----------------------------------------------------------------------------*/
33 #include <math.h>
34 #include <string.h>
35 #include <fenv.h>
36 #include <cpl.h>
37 
38 #include "muse_lsf.h"
39 
40 #include "muse_instrument.h"
41 #include "muse_optimize.h"
42 #include "muse_pfits.h"
43 #include "muse_pixtable.h"
44 #include "muse_quality.h"
45 #include "muse_resampling.h"
46 #include "muse_tracing.h"
47 #include "muse_utils.h"
48 #include "muse_wavecalib.h"
49 
50 /*----------------------------------------------------------------------------*/
54 /*----------------------------------------------------------------------------*/
55 
74 static cpl_size
75 muse_lsf_check_arc_line(cpl_table *aPixtable, double aLambda) {
76  cpl_size nrows = cpl_table_get_nrow(aPixtable);
77  /* If we have too few data pointe, remove the line */
78  if (nrows <= 100) {
79  return 0;
80  }
81  cpl_errorstate prestate = cpl_errorstate_get();
82 
83  /* wrap table columns into vectors for the Gaussian fit */
84  cpl_vector *pos = cpl_vector_new(nrows);
85  cpl_vector *val = cpl_vector_new(nrows);
86  cpl_vector *err = cpl_vector_new(nrows);
87  double *d_pos = cpl_vector_get_data(pos);
88  double *d_val = cpl_vector_get_data(val);
89  double *d_err = cpl_vector_get_data(err);
90  float *d_lambda = cpl_table_get_data_float(aPixtable, MUSE_PIXTABLE_LAMBDA);
91  float *d_data = cpl_table_get_data_float(aPixtable, MUSE_PIXTABLE_DATA);
92  float *d_stat = cpl_table_get_data_float(aPixtable, MUSE_PIXTABLE_STAT);
93  cpl_size i, s = 1;
94  double bglevel = 0;
95  for (i = 0; i < nrows; i++) {
96  if (fabs(d_lambda[i] - aLambda) > 5.5) {
97  bglevel += d_data[i];
98  s += 1;
99  }
100  d_pos[i] = d_lambda[i];
101  d_val[i] = d_data[i];
102  d_err[i] = sqrt(d_stat[i]);
103  }
104  bglevel /= s;
105  double meansigma = sqrt(cpl_table_get_column_mean(aPixtable, MUSE_PIXTABLE_STAT));
106  double sigma, area, mse;
107  double xc = aLambda;
108  cpl_fit_mode fit_pars = CPL_FIT_STDEV | CPL_FIT_AREA | CPL_FIT_CENTROID;
109  cpl_vector_fit_gaussian(pos, NULL, val, NULL, fit_pars, &xc, &sigma,
110  &area, &bglevel, &mse, NULL, NULL);
111 
112  /* Subtract the fit */
113  for (i = 0; i < nrows; i++) {
114  double e = (d_pos[i] - xc)/sigma;
115  d_val[i] -= area / (CPL_MATH_SQRT2PI * sigma) * exp(-e*e/2) - bglevel;
116  }
117 
118  /* Remove all pixels that are too far from the gaussian estimate */
119  double maxerror = 0.5 * sqrt(area) + 0.15 * area;
120  cpl_size inv = 0;
121  cpl_table_unselect_all(aPixtable);
122  for (i = 0; i < nrows; i++) {
123  if ((d_val[i] < -maxerror) || (d_val[i] > maxerror)) {
124  inv++;
125  cpl_table_select_row(aPixtable, i);
126  }
127  }
128  cpl_table_erase_selected(aPixtable);
129  nrows = cpl_table_get_nrow(aPixtable);
130  cpl_vector_delete(pos);
131  cpl_vector_delete(val);
132  cpl_vector_delete(err);
133 
134  cpl_table_fill_column_window_float(aPixtable, "line_lambda",
135  0, nrows, xc);
136  cpl_table_fill_column_window_float(aPixtable, "line_flux",
137  0, nrows, area);
138  cpl_table_fill_column_window_float(aPixtable, "line_background",
139  0, nrows, bglevel);
140 
141  /* If the fit failed, just ignore the errors, and remove the line */
142  if (!cpl_errorstate_is_equal(prestate)) {
143  cpl_errorstate_set(prestate);
144  return 0;
145  }
146  /* If the flux (compared to noise) is too low, silently remove the line */
147  if (area/meansigma < 50) {
148  return 0;
149  }
150  /* If the estimated line width is too far away from our expectation,
151  remove the line */
152  if ((sigma > 3) || (sigma < 0.7)) {
153  uint32_t origin = (uint32_t)cpl_table_get_int(aPixtable,
155  0, NULL);
156  int i_ifu = muse_pixtable_origin_get_ifu(origin);
157  int i_slice = muse_pixtable_origin_get_slice(origin);
158  cpl_msg_debug(__func__, "Slice %2i.%02i: "
159  "Ignoring line %.1f with implausible width %f (flux=%.0f)",
160  i_ifu, i_slice, aLambda, sigma, area);
161  return 0;
162  }
163  return nrows;
164 }
165 
185 static cpl_table *
186 select_arc_line(muse_pixtable *aPixtable, double aLambda, double aWindow) {
187  cpl_size low = muse_cpltable_find_sorted(aPixtable->table, MUSE_PIXTABLE_LAMBDA,
188  aLambda - aWindow);
189  cpl_size high = muse_cpltable_find_sorted(aPixtable->table, MUSE_PIXTABLE_LAMBDA,
190  aLambda + aWindow);
191  cpl_table *sel = cpl_table_extract(aPixtable->table, low + 1, high - low);
192  cpl_table_select_all(sel);
193  cpl_table_and_selected_int(sel, "dq", CPL_NOT_EQUAL_TO, 0);
194  cpl_table_erase_selected(sel);
195 
196  cpl_table_new_column(sel, "line_lambda", CPL_TYPE_FLOAT);
197  cpl_table_new_column(sel, "line_flux", CPL_TYPE_FLOAT);
198  cpl_table_new_column(sel, "line_background", CPL_TYPE_FLOAT);
199 
200  cpl_size nrows = muse_lsf_check_arc_line(sel, aLambda);
201 
202  if (nrows == 0) {
203  cpl_table_delete(sel);
204  return NULL;
205  }
206  return sel;
207 }
208 
209 
234  cpl_table *aTrace, cpl_table *aWave,
235  cpl_table *aArcLines, int aQuality, double aWindow)
236 {
237  cpl_ensure(aImages, CPL_ERROR_NULL_INPUT, NULL);
238  cpl_ensure(aTrace, CPL_ERROR_NULL_INPUT, NULL);
239  cpl_ensure(aWave, CPL_ERROR_NULL_INPUT, NULL);
240  cpl_ensure(aArcLines, CPL_ERROR_NULL_INPUT, NULL);
241 
242  /* create the output pixel table with two extra columns */
243  muse_pixtable *pt = cpl_calloc(1, sizeof(muse_pixtable));
245  cpl_table_new_column(pt->table, "line_lambda", CPL_TYPE_FLOAT);
246  cpl_table_new_column(pt->table, "line_flux", CPL_TYPE_FLOAT);
247  cpl_table_new_column(pt->table, "line_background", CPL_TYPE_FLOAT);
248 
249  cpl_size i_image, n_images = muse_imagelist_get_size(aImages);
250 #if 0 /* no extra levels of parallelization in basic processing recipes */
251  #pragma omp parallel for default(none) /* as req. by Ralf */ \
252  shared(i_image, n_images, aImages, aTrace, aWave, aArcLines, \
253  aQuality, aWindow, pt)
254 #endif
255  for (i_image = 0; i_image < n_images; i_image++) {
256  muse_image *image = muse_imagelist_get(aImages, i_image);
257  muse_pixtable *pixtable = muse_pixtable_create(image, aTrace, aWave, NULL);
258  if (pixtable == NULL) {
259  continue;
260  }
261  /* create header of output pixel table, if it's not there yet */
262  if (!pt->header) {
263  pt->header = cpl_propertylist_duplicate(pixtable->header);
264  }
265  char *l = muse_utils_header_get_lamp_names(pixtable->header, '|');
266  if (strlen(l) == 0) {
267  cpl_msg_warning(__func__,
268  "Ignoring frame without arc lamp switched on");
269  cpl_free(l);
270  cpl_free(pixtable);
271  continue;
272  }
273  char *lamp = cpl_malloc(strlen(l)+3);
274  sprintf(lamp, "|%s|", l);
275  cpl_free(l);
276  cpl_size i_line;
277  cpl_size n_entries = 0;
278  muse_ins_mode insmode = muse_pfits_get_mode(pt->header);
279  muse_pixtable **slice_pixtable = muse_pixtable_extracted_get_slices(pixtable);
280  int n_slices = muse_pixtable_extracted_get_size(slice_pixtable);
281  for (i_line = 0; i_line < cpl_table_get_nrow(aArcLines); i_line++) {
282  double lambda = cpl_table_get_float(aArcLines, "lambda", i_line, NULL);
283  if (!muse_wave_lines_covered_by_data(lambda, insmode) ||
284  cpl_table_get_int(aArcLines, "quality", i_line, NULL) < aQuality) {
285  continue;
286  }
287  const char *n = muse_wave_lines_get_lampname(aArcLines, i_line);
288  char *name = cpl_malloc(strlen(n) + 3);
289  sprintf(name, "|%s|", n);
290  if (strstr(name, lamp)) {
291  int i_slice;
292 #if 0 /* no extra levels of parallelization in basic processing recipes */
293  #pragma omp parallel for default(none) /* as req. by Ralf */ \
294  shared(i_slice, n_slices, n_entries, lambda, slice_pixtable, \
295  aWindow, pt)
296 #endif
297  for (i_slice = 0; i_slice < n_slices; i_slice++) {
298  cpl_table *sel = select_arc_line(slice_pixtable[i_slice],
299  lambda, aWindow);
300  if (sel != NULL) {
301 #if 0 /* no parallelization here, not necessary any more */
302  #pragma omp critical(construct_pixtable)
303 #endif
304  cpl_table_insert(pt->table, sel, cpl_table_get_nrow(pt->table));
305 #if 0 /* no parallelization here, not necessary any more */
306  #pragma omp atomic
307 #endif
308  n_entries += cpl_table_get_nrow(sel);
309  cpl_table_delete(sel);
310  }
311  }
312  }
313  cpl_free(name);
314  } /* for i_line (arc lines) */
315  muse_pixtable_extracted_delete(slice_pixtable);
316  muse_pixtable_delete(pixtable);
317  cpl_msg_info(__func__, "Using %"CPL_SIZE_FORMAT" entries with lamp %s",
318  n_entries, lamp);
319  cpl_free(lamp);
320  } /* for i_image (all images in list) */
321  return pt;
322 } /* muse_lsf_create_arcpixtable() */
323 
347 static cpl_polynomial *
348 muse_lsf_fit_polynomial(cpl_table *aPixtable, int aOrderLsf, int aOrderLambda)
349 {
350  cpl_errorstate prestate = cpl_errorstate_get();
351  cpl_size nrows = cpl_table_get_nrow(aPixtable);
352  if (nrows <= (aOrderLsf + 1) * (aOrderLambda)) {
353  return NULL;
354  }
355  double *p_dlambda = cpl_table_get_data_double(aPixtable, "d_lambda");
356  cpl_ensure(p_dlambda != NULL, CPL_ERROR_ILLEGAL_INPUT, NULL);
357  float *p_line = cpl_table_get_data_float(aPixtable, "line_lambda");
358  cpl_ensure(p_line != NULL, CPL_ERROR_ILLEGAL_INPUT, NULL);
359  float *p_data = cpl_table_get_data_float(aPixtable, MUSE_PIXTABLE_DATA);
360  cpl_ensure(p_data != NULL, CPL_ERROR_ILLEGAL_INPUT, NULL);
361  float *p_flux = cpl_table_get_data_float(aPixtable, "line_flux");
362  cpl_ensure(p_flux != NULL, CPL_ERROR_ILLEGAL_INPUT, NULL);
363  float *p_background = cpl_table_get_data_float(aPixtable, "line_background");
364  cpl_ensure(p_background != NULL, CPL_ERROR_ILLEGAL_INPUT, NULL);
365 
366  cpl_matrix *coord = cpl_matrix_new(2, nrows);
367  cpl_vector *data = cpl_vector_new(nrows);
368  double *coord_ptr = cpl_matrix_get_data(coord);
369  double *data_ptr = cpl_vector_get_data(data);
370  cpl_size i_row;
371 
372  for (i_row = 0; i_row < nrows; i_row++) {
373  coord_ptr[i_row] = p_dlambda[i_row];
374  coord_ptr[i_row + nrows] = p_line[i_row];
375  data_ptr[i_row] = (p_data[i_row] - p_background[i_row]) / p_flux[i_row];
376  }
377 
378  cpl_polynomial *p = cpl_polynomial_new(2);
379  const cpl_size maxdeg2d[] = { aOrderLsf, aOrderLambda };
380  cpl_polynomial_fit(p, coord, NULL, data, NULL, CPL_TRUE, NULL, maxdeg2d);
381  cpl_matrix_delete(coord);
382  cpl_vector_delete(data);
383 
384  if (!cpl_errorstate_is_equal(prestate)) {
385  cpl_errorstate_set(prestate);
386  return NULL;
387  }
388 
389  return p;
390 }
391 
419 cpl_error_code
420 muse_lsf_fit_slice(const muse_pixtable *aPixtable, cpl_image *aLsfImage,
421  muse_wcs *aWCS, double aLsfRegressionWindow)
422 {
423  cpl_ensure_code(aPixtable && aPixtable->table, CPL_ERROR_NULL_INPUT);
424  cpl_ensure_code(aLsfImage, CPL_ERROR_NULL_INPUT);
425  cpl_ensure_code(aWCS, CPL_ERROR_NULL_INPUT);
426  cpl_ensure_code(aLsfRegressionWindow > 0, CPL_ERROR_ILLEGAL_INPUT);
427 
428  cpl_size nrows = muse_pixtable_get_nrow(aPixtable);
429  if (nrows == 0) {
430  return CPL_ERROR_NONE;
431  }
432  /* work on a locally duplicated table (since we mess with the columns) */
433  cpl_table *pixtable = cpl_table_duplicate(aPixtable->table);
434  uint32_t origin = (uint32_t)cpl_table_get_int(pixtable,
435  MUSE_PIXTABLE_ORIGIN, 0, NULL);
436  int i_ifu = muse_pixtable_origin_get_ifu(origin);
437  int i_slice = muse_pixtable_origin_get_slice(origin);
438  cpl_msg_info(__func__, "processing slice %2i.%02i"
439  " with %"CPL_SIZE_FORMAT" entries",
440  i_ifu, i_slice, nrows);
441 
442  /* Create temporary column with (lmbda - line_lambda) and sort pixtable
443  according to this column. Sorting is important since we use it later
444  to extract the values according to the regession window using
445  find_sorted(). */
446  cpl_table_cast_column(pixtable, MUSE_PIXTABLE_LAMBDA, "d_lambda", CPL_TYPE_DOUBLE);
447  cpl_table_subtract_columns(pixtable, "d_lambda", "line_lambda");
448  cpl_propertylist *order = cpl_propertylist_new();
449  cpl_propertylist_append_bool(order, "d_lambda", CPL_FALSE);
450  cpl_table_sort(pixtable, order);
451  cpl_propertylist_delete(order);
452 
453  /* Loop over all x (LSF) data points and create a polynomial for each
454  covering the whole wavelength range */
455  cpl_size i_lsf;
456  cpl_size n_lsf = cpl_image_get_size_x(aLsfImage);
457  cpl_size n_lambda = cpl_image_get_size_y(aLsfImage);
458  for (i_lsf = 1; i_lsf <= n_lsf; i_lsf++) {
459  double x = aWCS->crval1 + (i_lsf - aWCS->crpix1) * aWCS->cd11;
460  double x_ref = CPL_MAX(x, cpl_table_get_column_min(pixtable, "d_lambda")
461  + aLsfRegressionWindow);
462  x_ref = CPL_MIN(x, cpl_table_get_column_max(pixtable, "d_lambda")
463  - aLsfRegressionWindow);
464  cpl_size low = muse_cpltable_find_sorted(pixtable, "d_lambda",
465  x_ref - aLsfRegressionWindow);
466  cpl_size high = muse_cpltable_find_sorted(pixtable, "d_lambda",
467  x_ref + aLsfRegressionWindow);
468  cpl_table *selected = cpl_table_extract(pixtable, low+1, high-low);
469  cpl_polynomial *p = muse_lsf_fit_polynomial(selected, 2, 3);
470  if (p != NULL) {
471  cpl_vector *c = cpl_vector_new(2);
472  cpl_vector_set(c, 0, x);
473  cpl_size i_lambda;
474  /* Fill the image column with the values calculated from the polynomial */
475  for (i_lambda = 1; i_lambda <= n_lambda; i_lambda++) {
476  double y = aWCS->crval2 + (i_lambda - aWCS->crpix2) * aWCS->cd22;
477  cpl_vector_set(c, 1, y);
478  double flux = cpl_polynomial_eval(p, c);
479  cpl_image_set(aLsfImage, i_lsf, i_lambda, flux);
480  }
481  cpl_vector_delete(c);
482  } else {
483  cpl_msg_warning(__func__,
484  "Failed polynomial fit %2i.%02i %.2f;"
485  " %"CPL_SIZE_FORMAT" entries",
486  i_ifu, i_slice, x, cpl_table_get_nrow(selected));
487  cpl_size i_lambda;
488  for (i_lambda = 1; i_lambda <= n_lambda; i_lambda++) {
489  cpl_image_reject(aLsfImage, i_lsf, i_lambda);
490  }
491  }
492  cpl_polynomial_delete(p);
493  cpl_table_delete(selected);
494  }
495  cpl_table_delete(pixtable);
496  /* Re-normalize total area of each LSF, since normalization may
497  not kept during the polynomial fit */
498  cpl_size i_lambda;
499  for (i_lambda = 1; i_lambda <= n_lambda; i_lambda++) {
500  double line_flux = cpl_image_get_flux_window(aLsfImage, 1, i_lambda,
501  n_lsf, i_lambda) * aWCS->cd11;
502  for (i_lsf = 1; i_lsf <= n_lsf; i_lsf++) {
503  int invalid;
504  double flux = cpl_image_get(aLsfImage, i_lsf, i_lambda, &invalid);
505  if (!invalid) {
506  cpl_image_set(aLsfImage, i_lsf, i_lambda, flux / line_flux);
507  }
508  }
509  }
510  return CPL_ERROR_NONE;
511 }
512 
522 muse_lsf_cube_new(double aLsfHalfRange, cpl_size aNLsf, cpl_size aNLambda,
523  const cpl_propertylist *aHeader)
524 {
525  muse_lsf_cube *lsf = cpl_calloc(1, sizeof(muse_lsf_cube));
526  lsf->header = cpl_propertylist_new();
527  if (aHeader) {
528  /* copy all keywords, except keys that don't belong here */
529  const char *regex = MUSE_HDR_OVSC_REGEXP"|"MUSE_WCS_KEYS"|"MUSE_HDR_PT_REGEXP;
530  cpl_propertylist_copy_property_regexp(lsf->header, aHeader, regex, 1);
531  }
532  lsf->img = cpl_imagelist_new();
533  cpl_size i;
534  for (i = 0; i < kMuseSlicesPerCCD; i++) {
535  cpl_imagelist_set(lsf->img,
536  cpl_image_new(aNLsf, aNLambda, CPL_TYPE_FLOAT), i);
537  }
538  double lsf_step = 2*aLsfHalfRange / (aNLsf - 1),
539  lambda_step = (kMuseNominalLambdaMax - kMuseNominalLambdaMin)
540  / (aNLambda - 1);
541  lsf->wcs = cpl_calloc(1, sizeof(muse_wcs));
542  lsf->wcs->cd11 = lsf_step;
543  lsf->wcs->cd12 = 0.;
544  lsf->wcs->cd21 = 0.;
545  lsf->wcs->cd22 = lambda_step;
546  lsf->wcs->crval1 = -aLsfHalfRange;
547  lsf->wcs->crval2 = kMuseNominalLambdaMin;
548  lsf->wcs->crpix1 = 1.;
549  lsf->wcs->crpix2 = 1.;
550  return lsf;
551 } /* muse_lsf_cube_new() */
552 
557 void
559 {
560  if (!aLsfCube) {
561  return;
562  }
563  cpl_propertylist_delete(aLsfCube->header);
564  cpl_imagelist_delete(aLsfCube->img);
565  cpl_free(aLsfCube->wcs);
566  cpl_free(aLsfCube);
567 }
568 
574 cpl_error_code
575 muse_lsf_cube_save(muse_lsf_cube *aLsfCube, const char *aFileName)
576 {
577  cpl_ensure_code(aLsfCube, CPL_ERROR_NULL_INPUT);
578  cpl_error_code rc = cpl_propertylist_save(aLsfCube->header, aFileName,
579  CPL_IO_CREATE);
580  if (rc != CPL_ERROR_NONE) {
581  return rc;
582  }
583  /* create the extension header */
584  cpl_propertylist *header = cpl_propertylist_new();
585  cpl_propertylist_append_string(header, "EXTNAME", "LSF_PROFILE");
586  cpl_propertylist_append_int(header, "WCSAXES", 2);
587  cpl_propertylist_append_double(header, "CD1_1", aLsfCube->wcs->cd11);
588  cpl_propertylist_append_double(header, "CD1_2", aLsfCube->wcs->cd12);
589  cpl_propertylist_append_double(header, "CD2_1", aLsfCube->wcs->cd21);
590  cpl_propertylist_append_double(header, "CD2_2", aLsfCube->wcs->cd22);
591  cpl_propertylist_append_double(header, "CRPIX1", aLsfCube->wcs->crpix1);
592  cpl_propertylist_append_double(header, "CRPIX2", aLsfCube->wcs->crpix2);
593  cpl_propertylist_append_double(header, "CRVAL1", aLsfCube->wcs->crval1);
594  cpl_propertylist_append_double(header, "CRVAL2", aLsfCube->wcs->crval2);
595  cpl_propertylist_append_string(header, "CTYPE1", "PARAM");
596  cpl_propertylist_append_string(header, "CTYPE2", "AWAV");
597  cpl_propertylist_append_string(header, "CUNIT1", "Angstrom");
598  cpl_propertylist_append_string(header, "CUNIT2", "Angstrom");
599 
600  rc = cpl_imagelist_save(aLsfCube->img, aFileName,
601  CPL_TYPE_FLOAT, header, CPL_IO_EXTEND);
602  cpl_propertylist_delete(header);
603  return rc;
604 } /* muse_lsf_cube_save() */
605 
606 /*----------------------------------------------------------------------------*/
618 /*----------------------------------------------------------------------------*/
619 muse_lsf_cube **
621 {
622  cpl_ensure(aProcessing, CPL_ERROR_NULL_INPUT, NULL);
623  muse_lsf_cube **lsf = cpl_calloc(kMuseNumIFUs, sizeof(muse_lsf_cube *));
624  unsigned char ifu, nlsfs = 0; /* count number of LSF cubes loaded */
625  for (ifu = 1; ifu <= kMuseNumIFUs; ifu++) {
626  cpl_frameset *frames = muse_frameset_find(aProcessing->inframes,
627  MUSE_TAG_LSF_PROFILE, ifu, CPL_FALSE);
628  cpl_errorstate es = cpl_errorstate_get();
629  cpl_frame *frame = cpl_frameset_get_position(frames, 0);
630  if (frame == NULL) {
631  cpl_msg_warning(__func__, "No %s (cube format) specified for IFU %2hhu!",
632  MUSE_TAG_LSF_PROFILE, ifu);
633  cpl_errorstate_set(es); /* swallow the illegal input state */
634  cpl_frameset_delete(frames);
635  continue;
636  }
637  const char *fn = cpl_frame_get_filename(frame);
638  lsf[ifu-1] = muse_lsf_cube_load(fn, ifu);
639  if (lsf[ifu-1] == NULL) {
640  cpl_msg_warning(__func__, "Could not load LSF (cube format) for IFU %2hhu "
641  "from \"%s\"!", ifu, fn);
642  cpl_frameset_delete(frames);
644  return NULL;
645  }
646  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_CALIB, 1);
647  cpl_frameset_delete(frames);
648  nlsfs++;
649  } /* for ifu */
650  if (!nlsfs) {
651  cpl_msg_error(__func__, "Did not load any %ss (cube format)!",
652  MUSE_TAG_LSF_PROFILE);
654  return NULL;
655  } /* if none loaded */
656  cpl_msg_info(__func__, "Successfully loaded %s%hhu %ss (cube format).",
657  nlsfs == kMuseNumIFUs ? "all ": "", nlsfs, MUSE_TAG_LSF_PROFILE);
658  return lsf;
659 } /* muse_lsf_cube_load_all() */
660 
664 void
666  if (aLsfCube != NULL) {
667  int ifu;
668  for (ifu = 0; ifu < kMuseNumIFUs; ifu++) {
669  muse_lsf_cube_delete(aLsfCube[ifu]);
670  }
671  cpl_free(aLsfCube);
672  }
673 }
674 
683 muse_lsf_cube_load(const char *aFileName, unsigned char aIFU)
684 {
685  cpl_ensure(aFileName, CPL_ERROR_NULL_INPUT, NULL);
686 
687  /* start looking for the LSF_PROFILE extension */
688  int ext = cpl_fits_find_extension(aFileName, "LSF_PROFILE");
689  char *extname = NULL;
690  if (ext <= 0) { /* if not in an extension then look for merged name */
691  extname = cpl_sprintf("CHAN%02hhu.LSF_PROFILE", aIFU);
692  ext = cpl_fits_find_extension(aFileName, extname);
693  if (ext <= 0) { /* no extension for given IFU found */
694  cpl_free(extname);
695  cpl_error_set(__func__, CPL_ERROR_DATA_NOT_FOUND);
696  return NULL;
697  }
698  } /* if no LSF_PROFILE extension */
699  cpl_free(extname);
700 
701  muse_lsf_cube *lsf = cpl_calloc(1, sizeof(muse_lsf_cube));
702  lsf->header = cpl_propertylist_load(aFileName, 0); /* primary header */
703  lsf->img = cpl_imagelist_load(aFileName, CPL_TYPE_DOUBLE, ext);
704  lsf->wcs = cpl_calloc(1, sizeof(muse_wcs));
705  if (lsf->img == NULL) {
707  return NULL;
708  }
709  /* now load the WCS from the extension header */
710  cpl_propertylist *header = cpl_propertylist_load(aFileName, ext);
711  if (header == NULL) {
713  return NULL;
714  }
715  lsf->wcs->cd11 = muse_pfits_get_cd(header, 1, 1);
716  lsf->wcs->cd12 = muse_pfits_get_cd(header, 1, 2);
717  lsf->wcs->cd21 = muse_pfits_get_cd(header, 2, 1);
718  lsf->wcs->cd22 = muse_pfits_get_cd(header, 2, 2);
719  lsf->wcs->crpix1 = muse_pfits_get_crpix(header, 1);
720  lsf->wcs->crpix2 = muse_pfits_get_crpix(header, 2);
721  lsf->wcs->crval1 = muse_pfits_get_crval(header, 1);
722  lsf->wcs->crval2 = muse_pfits_get_crval(header, 2);
723  /* XXX check types and units of the WCS */
724  // muse_pfits_get_ctype(header, 1) "PARAM"
725  // muse_pfits_get_ctype(header, 2) "AWAV"
726  // muse_pfits_get_cunit(header, 1) "Angstrom"
727  // muse_pfits_get_cunit(header, 2) "Angstrom"
728  cpl_propertylist_delete(header);
729 
730  return lsf;
731 } /* muse_lsf_cube_load() */
732 
743 cpl_image *
745 {
746  cpl_ensure(aLsfCube, CPL_ERROR_NULL_INPUT, NULL);
747  cpl_image *img = NULL;
748  cpl_size wsum = 0;
749  cpl_size w[kMuseNumIFUs][kMuseSlicesPerCCD];
750  cpl_size ifu;
751  for (ifu = 0; ifu < kMuseNumIFUs; ifu++) {
752  cpl_size i_slice;
753  for (i_slice = 0; i_slice < kMuseSlicesPerCCD; i_slice++) {
754  w[ifu][i_slice] = (aPixtable == NULL)?1.0:0.0;
755  }
756  }
757 
758  if (aPixtable != NULL) {
759  cpl_size n_rows = muse_pixtable_get_nrow(aPixtable);
760  cpl_size i_row;
761  uint32_t *origin = (uint32_t*)
762  cpl_table_get_data_int(aPixtable->table, MUSE_PIXTABLE_ORIGIN);
763  for (i_row = 0; i_row < n_rows; i_row++) {
764  int i_ifu = muse_pixtable_origin_get_ifu(origin[i_row]);
765  int i_slice = muse_pixtable_origin_get_slice(origin[i_row]);
766  w[i_ifu-1][i_slice-1]++;
767  }
768  }
769 
770  for (ifu = 0; ifu < kMuseNumIFUs; ifu++) {
771  if (aLsfCube[ifu] == NULL) {
772  continue;
773  }
774  cpl_size i_slice;
775  cpl_size n_slices = cpl_imagelist_get_size(aLsfCube[ifu]->img);
776  for (i_slice = 0; i_slice < n_slices; i_slice++) {
777  if (w[ifu][i_slice] > 0) {
778  cpl_image *m = cpl_imagelist_get(aLsfCube[ifu]->img, i_slice);
779  m = cpl_image_duplicate(m);
780  cpl_image_multiply_scalar(m, w[ifu][i_slice]);
781  wsum += w[ifu][i_slice];
782  if (img == NULL) {
783  img = m;
784  } else {
785  cpl_errorstate pre = cpl_errorstate_get();
786  cpl_error_code rc = cpl_image_add(img, m);
787  cpl_image_delete(m);
788  if (rc != CPL_ERROR_NONE) {
789  cpl_msg_warning(__func__, "Could not add cube of IFU %"
790  CPL_SIZE_FORMAT": %s",
791  ifu+1, cpl_error_get_message());
792  cpl_errorstate_set(pre);
793  break;
794  }
795  }
796  }
797  }
798  }
799  if ((img != NULL) && (wsum > 0)) {
800  cpl_image_divide_scalar(img, wsum);
801  return img;
802  } else {
803  cpl_image_delete(img);
804  return NULL;
805  }
806 }
807 
808 // XXX this should be documented and actually check that all WCSs are identical!
809 muse_wcs *
810 muse_lsf_cube_get_wcs_all(muse_lsf_cube **aLsfCube) {
811  cpl_size ifu;
812  for (ifu = 0; ifu < kMuseNumIFUs; ifu++) {
813  if (aLsfCube[ifu] != NULL) {
814  return aLsfCube[ifu]->wcs;
815  }
816  }
817  return NULL;
818 }
819 
820 /*----------------------------------------------------------------------------*/
831 /*----------------------------------------------------------------------------*/
832 cpl_error_code
833 muse_lsf_fold_rectangle(cpl_image *aLsfImage, const muse_wcs *aWCS,
834  double aBinWidth)
835 {
836  cpl_ensure_code(aLsfImage && aWCS, CPL_ERROR_NULL_INPUT);
837  cpl_ensure_code(aBinWidth > 0., CPL_ERROR_ILLEGAL_INPUT);
838 
839  cpl_size ncol_kernel = aBinWidth / aWCS->cd11;
840  ncol_kernel = ((ncol_kernel + 1) / 2) * 2 + 1; /* odd number */
841  double r = ncol_kernel - aBinWidth / aWCS->cd11;
842  cpl_matrix *kernel = cpl_matrix_new(1, ncol_kernel);
843  cpl_matrix_fill(kernel, 1.0);
844  cpl_matrix_set(kernel, 0, 0, 1 - r/2);
845  cpl_matrix_set(kernel, 0, ncol_kernel-1, 1 - r/2);
846  cpl_image *lsfImage0 = cpl_image_duplicate(aLsfImage);
847  cpl_image_filter(aLsfImage, lsfImage0, kernel, CPL_FILTER_LINEAR,
848  CPL_BORDER_FILTER);
849  cpl_matrix_delete(kernel);
850  cpl_image_delete(lsfImage0);
851 
852  return CPL_ERROR_NONE;
853 } /* muse_lsf_fold_rectangle() */
854 
855 /*----------------------------------------------------------------------------*/
868 /*----------------------------------------------------------------------------*/
869 cpl_error_code
870 muse_lsf_apply(const cpl_image *aLsfImage, const muse_wcs *aWCS,
871  cpl_array *aVal, double aLambda)
872 {
873  cpl_ensure_code(aLsfImage, CPL_ERROR_NULL_INPUT);
874  cpl_ensure_code(aWCS, CPL_ERROR_NULL_INPUT);
875  cpl_ensure_code(aVal, CPL_ERROR_NULL_INPUT);
876  cpl_size n_lsf = cpl_image_get_size_x(aLsfImage);
877  cpl_size n_lambda = cpl_image_get_size_y(aLsfImage);
878 
879  /* Find y position in image corresponding to line wavelength */
880  double lambda_pix = (aLambda - aWCS->crval2) / aWCS->cd22 + aWCS->crpix2;
881  if (lambda_pix < 1) {
882  lambda_pix = 1;
883  }
884  if (lambda_pix > n_lambda) {
885  lambda_pix = n_lambda;
886  }
887  cpl_size i_lambda = floor(lambda_pix);
888  lambda_pix -= i_lambda; /* lambda_pix has fraction of y position */
889 
890  /* Create vector containing the LSF by linear interpolation.
891  For speed reasons, this is a plain C array. */
892  cpl_array *lsf_lambda = cpl_array_new(n_lsf + 4, CPL_TYPE_DOUBLE);
893  cpl_array *lsf_values = cpl_array_new(n_lsf + 4, CPL_TYPE_DOUBLE);
894  cpl_size i;
895  for (i = 1; i <= n_lsf; i++) {
896  int res;
897  double z = cpl_image_get(aLsfImage, i, i_lambda, &res);
898  if (lambda_pix > 0) {
899  z = z * (1 - lambda_pix)
900  + cpl_image_get(aLsfImage, i, i_lambda+1, &res) * lambda_pix;
901  }
902  cpl_array_set(lsf_values, i+1, z);
903  cpl_array_set(lsf_lambda, i+1, aWCS->crval1 + (i - aWCS->crpix1) * aWCS->cd11);
904  }
905  // Set outer limits of LSF to zero to avoid invalid values in the fit
906  // +- 10000 A is used since the pixtable range will never be in this range
907  cpl_array_set(lsf_lambda, 0, -10000);
908  cpl_array_set(lsf_values, 0, 0);
909  cpl_array_set(lsf_lambda, 1, aWCS->crval1 - aWCS->crpix1 * aWCS->cd11);
910  cpl_array_set(lsf_values, 1, 0);
911  cpl_array_set(lsf_lambda, n_lsf+2, aWCS->crval1 + (n_lsf+1 - aWCS->crpix1) * aWCS->cd11);
912  cpl_array_set(lsf_values, n_lsf+2, 0);
913  cpl_array_set(lsf_lambda, n_lsf+3, 10000);
914  cpl_array_set(lsf_values, n_lsf+3, 0);
915 
916  // Correct the central position of the LSF function
917  cpl_array *v = cpl_array_duplicate(lsf_values);
918  cpl_array_multiply(v, lsf_lambda);
919  double offset = cpl_array_get_mean(v)/cpl_array_get_mean(lsf_values);
920  cpl_array_delete(v);
921  cpl_array_subtract_scalar (lsf_lambda, offset);
922 
923  // Re-normalize, just to be safe
924  double norm = cpl_array_get_mean(lsf_values) * (n_lsf + 4) * aWCS->cd11;
925  cpl_array_divide_scalar(lsf_values, norm);
926 
927  cpl_array *values = muse_cplarray_interpolate_linear(aVal, lsf_lambda,
928  lsf_values);
929 
930  // Copy the values back to the function argument
931  memcpy(cpl_array_get_data_double(aVal), cpl_array_get_data_double(values),
932  cpl_array_get_size(aVal) * sizeof(double));
933 
934  cpl_array_delete(values);
935  cpl_array_delete(lsf_lambda);
936  cpl_array_delete(lsf_values);
937  return CPL_ERROR_NONE;
938 }
939 
double crpix2
Definition: muse_wcs.h:106
Structure definition for a collection of muse_images.
muse_wcs * wcs
Common WCS information for each slice.
Definition: muse_lsf.h:55
void muse_pixtable_extracted_delete(muse_pixtable **aPixtables)
Delete a pixel table array.
double muse_pfits_get_cd(const cpl_propertylist *aHeaders, unsigned int aAxisI, unsigned int aAxisJ)
find out the WCS coordinate at the reference point
Definition: muse_pfits.c:446
unsigned short muse_pixtable_origin_get_slice(uint32_t aOrigin)
Get the slice number from the encoded 32bit origin number.
cpl_size muse_pixtable_extracted_get_size(muse_pixtable **aPixtables)
Get the size of an array of extracted pixel tables.
cpl_propertylist * header
Primary header used to save the LSF cube to disk.
Definition: muse_lsf.h:51
double muse_pfits_get_crval(const cpl_propertylist *aHeaders, unsigned int aAxis)
find out the WCS coordinate at the reference point
Definition: muse_pfits.c:423
A structure containing a spatial two-axis WCS.
Definition: muse_wcs.h:105
double cd22
Definition: muse_wcs.h:108
muse_pixtable ** muse_pixtable_extracted_get_slices(muse_pixtable *aPixtable)
Extract one pixel table per IFU and slice.
cpl_size muse_pixtable_get_nrow(const muse_pixtable *aPixtable)
get the number of rows within the pixel table
cpl_image * muse_lsf_average_cube_all(muse_lsf_cube **aLsfCube, muse_pixtable *aPixtable)
Create an average image from all LSF cubes.
Data cube/stacked image list containing the LSF for one IFU.
Definition: muse_lsf.h:49
muse_lsf_cube * muse_lsf_cube_new(double aLsfHalfRange, cpl_size aNLsf, cpl_size aNLambda, const cpl_propertylist *aHeader)
Create a new LSF datacube.
Structure definition of MUSE three extension FITS file.
Definition: muse_image.h:40
cpl_error_code muse_lsf_cube_save(muse_lsf_cube *aLsfCube, const char *aFileName)
Save the LSF cube to disk.
cpl_table * table
The pixel table.
#define MUSE_PIXTABLE_DATA
Definition: muse_pixtable.h:48
unsigned int muse_imagelist_get_size(muse_imagelist *aList)
Return the number of stored images.
cpl_table * muse_cpltable_new(const muse_cpltable_def *aDef, cpl_size aLength)
Create an empty table according to the specified definition.
muse_lsf_cube * muse_lsf_cube_load(const char *aFileName, unsigned char aIFU)
Load a LSF cube for one single IFU from disk.
const char * muse_wave_lines_get_lampname(cpl_table *aTable, const int aIdx)
Associate the ion listed in a linelist table row to a lamp name.
double muse_pfits_get_crpix(const cpl_propertylist *aHeaders, unsigned int aAxis)
find out the WCS reference point
Definition: muse_pfits.c:401
Structure definition of MUSE pixel table.
cpl_boolean muse_wave_lines_covered_by_data(double aLambda, muse_ins_mode aMode)
Check, if a given wavelength is covered by a given instrument mode.
#define MUSE_WCS_KEYS
regular expression for WCS properties
Definition: muse_wcs.h:48
muse_image * muse_imagelist_get(muse_imagelist *aList, unsigned int aIdx)
Get the muse_image of given list index.
muse_lsf_cube ** muse_lsf_cube_load_all(muse_processing *aProcessing)
Load all LSF cubes for all IFUs into an array.
cpl_array * muse_cplarray_interpolate_linear(const cpl_array *aTargetAbscissa, const cpl_array *aSourceAbscissa, const cpl_array *aSourceOrdinate)
Linear interpolation of a 1d array.
static cpl_size muse_lsf_check_arc_line(cpl_table *aPixtable, double aLambda)
Check the pixels for one arc line.
Definition: muse/muse_lsf.c:75
void muse_processing_append_used(muse_processing *aProcessing, cpl_frame *aFrame, cpl_frame_group aGroup, int aDuplicate)
Add a frame to the set of used frames.
cpl_error_code muse_lsf_apply(const cpl_image *aLsfImage, const muse_wcs *aWCS, cpl_array *aVal, double aLambda)
Apply the LSF to a number of data points of one slice.
muse_pixtable * muse_pixtable_create(muse_image *aImage, cpl_table *aTrace, cpl_table *aWave, cpl_table *aGeoTable)
Create the pixel table for one CCD.
char * muse_utils_header_get_lamp_names(cpl_propertylist *aHeader, char aSep)
Concatenate names of all active calibration lamps.
Definition: muse_utils.c:989
#define MUSE_PIXTABLE_ORIGIN
Definition: muse_pixtable.h:54
cpl_size muse_cpltable_find_sorted(const cpl_table *aTable, const char *aColumn, double aValue)
Find a row in a table.
unsigned short muse_pixtable_origin_get_ifu(uint32_t aOrigin)
Get the IFU number from the encoded 32bit origin number.
#define MUSE_PIXTABLE_STAT
Definition: muse_pixtable.h:50
static cpl_table * select_arc_line(muse_pixtable *aPixtable, double aLambda, double aWindow)
Select the pixels for one arc line.
void muse_lsf_cube_delete_all(muse_lsf_cube **aLsfCube)
Delete all LSF cubes.
const muse_cpltable_def muse_pixtable_def[]
MUSE pixel table definition.
cpl_frameset * inframes
#define MUSE_PIXTABLE_LAMBDA
Definition: muse_pixtable.h:53
muse_pixtable * muse_lsf_create_arcpixtable(muse_imagelist *aImages, cpl_table *aTrace, cpl_table *aWave, cpl_table *aArcLines, int aQuality, double aWindow)
Read images and combine the arc lines into one pixtable.
cpl_error_code muse_lsf_fold_rectangle(cpl_image *aLsfImage, const muse_wcs *aWCS, double aBinWidth)
Filter an LSF image with a rectangle to model spectrum binning.
void muse_lsf_cube_delete(muse_lsf_cube *aLsfCube)
Deallocate the memory for the LSF cube.
cpl_imagelist * img
Stacked image list for LSF; one per slice.
Definition: muse_lsf.h:53
double crval2
Definition: muse_wcs.h:107
static cpl_polynomial * muse_lsf_fit_polynomial(cpl_table *aPixtable, int aOrderLsf, int aOrderLambda)
Do a polynomial fit on an arc pixtable.
cpl_frameset * muse_frameset_find(const cpl_frameset *aFrames, const char *aTag, unsigned char aIFU, cpl_boolean aInvert)
return frameset containing data from an IFU/channel with a certain tag
Definition: muse_utils.c:160
void muse_pixtable_delete(muse_pixtable *aPixtable)
Deallocate memory associated to a pixel table object.
muse_ins_mode muse_pfits_get_mode(const cpl_propertylist *aHeaders)
find out the observation mode
Definition: muse_pfits.c:1352
cpl_error_code muse_lsf_fit_slice(const muse_pixtable *aPixtable, cpl_image *aLsfImage, muse_wcs *aWCS, double aLsfRegressionWindow)
Compute the LSF for all wavelengths of one slice.
cpl_propertylist * header
The FITS header.