MUSE Pipeline Reference Manual  1.0.2
muse_geometry.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 <string.h>
30 
31 #include <muse.h>
32 #include "muse_geometry_z.h"
33 
34 /*----------------------------------------------------------------------------*
35  * Functions code *
36  *----------------------------------------------------------------------------*/
37 
38 /*---------------------------------------------------------------------------*/
47 /*---------------------------------------------------------------------------*/
48 static void
49 muse_geometry_qc_spots(cpl_table *aSpots, cpl_propertylist *aHeader)
50 {
51  if (!aSpots || !aHeader) {
52  return;
53  }
54  cpl_msg_debug(__func__, "Adding QC keywords to %s", MUSE_TAG_SPOTS_TABLE);
55 
56  unsigned int nexp,
57  nexp1 = cpl_table_get_column_min(aSpots, "image"),
58  nexp2 = cpl_table_get_column_max(aSpots, "image");
59  for (nexp = nexp1; nexp <= nexp2; nexp++) {
60  cpl_table_unselect_all(aSpots);
61  cpl_table_or_selected_int(aSpots, "image", CPL_EQUAL_TO, nexp);
62  cpl_table_and_selected_double(aSpots, "xfwhm", CPL_GREATER_THAN, 0.);
63  cpl_table_and_selected_double(aSpots, "yfwhm", CPL_GREATER_THAN, 0.);
64  cpl_table_and_selected_double(aSpots, "flux", CPL_GREATER_THAN, 1000.);
65  cpl_table *table = cpl_table_extract_selected(aSpots);
66  /* compute the average of the x and y FWHM values */
67  cpl_table_duplicate_column(table, "fwhm", table, "xfwhm");
68  cpl_table_add_columns(table, "fwhm", "yfwhm");
69  cpl_table_multiply_scalar(table, "fwhm", 0.5);
70  double mean = cpl_table_get_column_mean(table, "fwhm"),
71  median = cpl_table_get_column_median(table, "fwhm"),
72  stdev = cpl_table_get_column_stdev(table, "fwhm");
73  cpl_table_delete(table);
74 
75  char keyword[KEYWORD_LENGTH];
76  snprintf(keyword, KEYWORD_LENGTH, QC_GEO_EXPk_MEAN, nexp);
77  cpl_propertylist_update_float(aHeader, keyword, mean);
78  snprintf(keyword, KEYWORD_LENGTH, QC_GEO_EXPk_MEDIAN, nexp);
79  cpl_propertylist_update_float(aHeader, keyword, median);
80  snprintf(keyword, KEYWORD_LENGTH, QC_GEO_EXPk_STDEV, nexp);
81  cpl_propertylist_update_float(aHeader, keyword, stdev);
82  } /* for nexp */
83 } /* muse_geometry_qc_spots() */
84 
85 /*---------------------------------------------------------------------------*/
92 /*---------------------------------------------------------------------------*/
93 static void
94 muse_geometry_qc_ifu(muse_geo_table *aGeoInit, cpl_propertylist *aHeader,
95  const cpl_vector *aLambdas)
96 {
97  if (!aGeoInit || !aHeader || !aLambdas) {
98  return;
99  }
100  cpl_table *gtinit = aGeoInit->table;
101  const unsigned char ifu = cpl_table_get_int(gtinit, MUSE_GEOTABLE_FIELD, 0, NULL);
102  double angle = cpl_table_get_column_mean(gtinit, MUSE_GEOTABLE_ANGLE),
103  astdev = cpl_table_get_column_stdev(gtinit, MUSE_GEOTABLE_ANGLE),
104  amedian = cpl_table_get_column_median(gtinit, MUSE_GEOTABLE_ANGLE);
105  cpl_msg_debug(__func__, "Adding QC keywords for IFU %hhu: angle = %.3f +/- %.3f "
106  "(%.3f) deg", ifu, angle, astdev, amedian);
107  char *keyword = cpl_sprintf(QC_GEO_IFUi_ANGLE, ifu);
108  cpl_propertylist_update_float(aHeader, keyword, amedian);
109  cpl_free(keyword);
110 
111  int i, n = cpl_vector_get_size(aLambdas);
112  for (i = 1; i <= n; i++) {
113  double lambda = cpl_vector_get(aLambdas, i - 1);
114  char *kw = cpl_sprintf(QC_GEO_IFUi_WLENj, ifu, i),
115  *kwmean = cpl_sprintf(QC_GEO_IFUi_MEANj, ifu, i),
116  *kwmedi = cpl_sprintf(QC_GEO_IFUi_MEDIANj, ifu, i),
117  *kwstdv = cpl_sprintf(QC_GEO_IFUi_STDEVj, ifu, i);
118  cpl_propertylist_update_float(aHeader, kw, lambda);
119 
120  cpl_table_unselect_all(gtinit);
121  cpl_table_or_selected_double(gtinit, "lambda", CPL_EQUAL_TO, lambda);
122  if (cpl_table_count_selected(gtinit) < 1) { /* fill in dummy values */
123  cpl_propertylist_update_float(aHeader, kwmean, i);
124  cpl_propertylist_update_float(aHeader, kwmedi, i);
125  cpl_propertylist_update_float(aHeader, kwstdv, i);
126  } else {
127  cpl_table *t = cpl_table_extract_selected(gtinit);
128  cpl_propertylist_update_float(aHeader, kwmean,
129  cpl_table_get_column_mean(t, "flux"));
130  cpl_propertylist_update_float(aHeader, kwmedi,
131  cpl_table_get_column_median(t, "flux"));
132  cpl_propertylist_update_float(aHeader, kwstdv,
133  cpl_table_get_column_stdev(t, "flux"));
134  cpl_table_delete(t);
135  }
136  cpl_free(kw);
137  cpl_free(kwmean);
138  cpl_free(kwmedi);
139  cpl_free(kwstdv);
140  } /* for i (all lambdas) */
141 } /* muse_geometry_qc_ifu() */
142 
143 /*---------------------------------------------------------------------------*/
149 /*---------------------------------------------------------------------------*/
150 static void
151 muse_geometry_qc_global(const muse_geo_table *aGeoTable,
152  cpl_propertylist *aHeader)
153 {
154  if (!aGeoTable || !aHeader) {
155  return;
156  }
157  double angle = cpl_table_get_column_mean(aGeoTable->table, MUSE_GEOTABLE_ANGLE),
158  astdev = cpl_table_get_column_stdev(aGeoTable->table, MUSE_GEOTABLE_ANGLE),
159  amedian = cpl_table_get_column_median(aGeoTable->table, MUSE_GEOTABLE_ANGLE);
160  cpl_msg_debug(__func__, "Adding global QC keywords: angle = %.3f +/- %.3f "
161  "(%.3f) deg", angle, astdev, amedian);
162  cpl_propertylist_update_float(aHeader, QC_GEO_MASK_ANGLE, amedian);
163 } /* muse_geometry_qc_global() */
164 
165 /*---------------------------------------------------------------------------*/
172 /*---------------------------------------------------------------------------*/
173 static muse_imagelist *
174 muse_geometry_load_images(muse_processing *aProcessing, unsigned short aIFU)
175 {
176  unsigned int skip = 0;
177  if (getenv("MUSE_GEOMETRY_SKIP") && atoi(getenv("MUSE_GEOMETRY_SKIP")) > 0) {
178  skip = atoi(getenv("MUSE_GEOMETRY_SKIP"));
179  }
180 
181  muse_imagelist *images;
182  if (!skip) { /* going all the way from raw exposures */
183  /* Find MASTER_BIAS file and load the relevant FITS headers, to see *
184  * what basic processing parameters we need to take over from it. */
185  cpl_frame *fbias = muse_frameset_find_master(aProcessing->inframes,
186  MUSE_TAG_MASTER_BIAS, aIFU);
187  /* Merged or not, the REC1 headers we need here are in the primary HDU: */
188  cpl_propertylist *hbias = cpl_propertylist_load(cpl_frame_get_filename(fbias), 0);
190  cpl_propertylist_delete(hbias);
191  images = muse_basicproc_load(aProcessing, aIFU, bpars);
193  } else { /* skipping some part of the loading/processing */
194  images = muse_imagelist_new();
195  /* filenames are "MASK_REDUCED_00ii-jj.fits" */
196  unsigned int ifile, nfiles = 99; /* first assume that we will read many files */
197  if (skip >= 2) {
198  cpl_msg_warning(__func__, "Skipping spot measurements, only loading first"
199  " (reduced) file for IFU %02hu", aIFU);
200  nfiles = 1;
201  } else {
202  cpl_msg_warning(__func__, "Skipping raw image processing, loading all "
203  "reduced images directly");
204  }
205  for (ifile = 0; ifile < nfiles; ifile++) {
206  char *fn = cpl_sprintf("MASK_REDUCED_00%02u-%02hu.fits", ifile + 1, aIFU);
207  cpl_errorstate es = cpl_errorstate_get();
208  muse_image *image = muse_image_load(fn);
209  if (!image) {
210  cpl_errorstate_set(es); /* ignore the errors */
211  cpl_free(fn);
212  break;
213  }
214  cpl_frame *frame = cpl_frame_new();
215  cpl_frame_set_filename(frame, fn);
216  cpl_frame_set_tag(frame, "MASK");
217  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_RAW, 0);
218  cpl_msg_debug(__func__, "file = %s", fn);
219  muse_imagelist_set(images, image, ifile);
220  cpl_free(fn);
221  } /* for ifile */
222  cpl_msg_debug(__func__, "Read %u file%s", ifile, ifile == 1 ? "" : "s");
223  } /* else (skipped some loading/processing) */
224  return images;
225 } /* muse_geometry_load_images() */
226 
227 /*----------------------------------------------------------------------------*/
250 /*----------------------------------------------------------------------------*/
251 static cpl_error_code
252 muse_geometry_reconstruct_combined(muse_processing *aProcessing,
253  muse_geometry_params_t *aParams,
254  muse_geo_table *aGeo,
255  double aLMin, double aLMax)
256 {
257  cpl_ensure_code(aProcessing && aParams && aGeo && aGeo->table,
258  CPL_ERROR_NULL_INPUT);
259  if (aLMin >= aLMax) {
260  cpl_msg_warning(__func__, "Invalid wavelength range for reconstruction "
261  "(%.2f..%.2f), using 6800..7200 instead!", aLMin, aLMax);
262  aLMin = 6800.;
263  aLMax = 7200.;
264  }
265  unsigned int skip = 0;
266  if (getenv("MUSE_GEOMETRY_SKIP") && atoi(getenv("MUSE_GEOMETRY_SKIP")) > 0) {
267  skip = atoi(getenv("MUSE_GEOMETRY_SKIP"));
268  }
269  cpl_table *gt = aGeo->table;
270 
271  muse_pixtable **pts = cpl_calloc(kMuseNumIFUs, sizeof(muse_pixtable));
272  unsigned char nifu;
273  #pragma omp parallel for default(none) /* as req. by Ralf */ \
274  shared(gt, aParams, aProcessing, pts, skip)
275  for (nifu = (unsigned)aParams->ifu1; nifu <= (unsigned)aParams->ifu2; nifu++) {
276  cpl_table *trace = muse_table_load(aProcessing, MUSE_TAG_TRACE_TABLE, nifu),
277  *wavecal = muse_table_load(aProcessing, MUSE_TAG_WAVECAL_TABLE, nifu);
278  if (!trace || !wavecal) {
279  cpl_table_delete(trace);
280  cpl_table_delete(wavecal);
281  continue; /* just skip this IFU */
282  }
283  /* now load again the file we saved previously */
284  cpl_frame *cframe = NULL;
285  if (skip == 2) { /* we are running with MUSE_GEOMETRY_SKIP=2 */
286  /* construct frame manually, assuming a local file */
287  cframe = cpl_frame_new();
288  char *fn = cpl_sprintf("MASK_COMBINED-%02hhu.fits", nifu);
289  cpl_frame_set_filename(cframe, fn);
290  } else {
291  cframe = muse_frameset_find_master(aProcessing->outframes,
292  MUSE_TAG_MASK_COMBINED, nifu);
293  }
294  if (!cframe) {
295  cpl_table_delete(trace);
296  cpl_table_delete(wavecal);
297  continue;
298  }
299  cpl_msg_debug(__func__, "reconstructing IFU %2hhu using \"%s\"", nifu,
300  cpl_frame_get_filename(cframe));
301  muse_image *combined = muse_image_load(cpl_frame_get_filename(cframe));
302  cpl_frame_delete(cframe);
303  pts[nifu - 1] = muse_pixtable_create(combined, trace, wavecal, gt);
304  cpl_table_delete(trace);
305  cpl_table_delete(wavecal);
306  muse_image_delete(combined);
307  } /* for nifu (all IFUs) */
308  muse_pixtable *pt = pts[aParams->ifu1 - 1];
309  /* merge all pixel tables *
310  * a la muse_pixtable_load_merge_channels() but without flux correction */
311  for (nifu = (unsigned)aParams->ifu1 + 1; nifu <= (unsigned)aParams->ifu2; nifu++) {
312  if (pts[nifu - 1]) {
313  cpl_table_insert(pt->table, pts[nifu - 1]->table, muse_pixtable_get_nrow(pt));
314  muse_pixtable_delete(pts[nifu - 1]);
315  } /* if */
316  } /* for nifu (all IFUs) */
317  /* cut the cube, for faster reconstruction speed */
318  cpl_free(pts);
319  muse_pixtable_restrict_wavelength(pt, aLMin, aLMax);
320  if (muse_pixtable_get_nrow(pt) < 1) {
322  return cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_OUTPUT,
323  "After cutting to %.2f..%.2f in wavelength no "
324  "pixels for image reconstruction are left!",
325  aLMin, aLMax);
326  }
327 
328  /* now just reconstruct the cube and collapse over the full range */
330  rp->crsigma = -1.; /* no cr cleaning */
331  muse_datacube *cube = muse_resampling_cube(pt, rp, NULL);
334  /* also create a corresponding white-light imgae */
335  muse_image *white = muse_datacube_collapse(cube, NULL);
336  cube->recimages = muse_imagelist_new();
337  cube->recnames = cpl_array_new(1, CPL_TYPE_STRING);
338  muse_imagelist_set(cube->recimages, white, 0);
339  cpl_array_set_string(cube->recnames, 0, "white");
340  muse_processing_save_cube(aProcessing, -1, cube, "GEOMETRY_CUBE",
342  muse_datacube_delete(cube);
343 
344  return CPL_ERROR_NONE;
345 } /* muse_geometry_reconstruct_combined() */
346 
347 /*----------------------------------------------------------------------------*/
373 /*----------------------------------------------------------------------------*/
374 static cpl_error_code
375 muse_geometry_reconstruct(muse_processing *aProcessing,
376  muse_geometry_params_t *aParams,
377  muse_geo_table *aGeo, double aLMin, double aLMax)
378 {
379  cpl_ensure_code(aProcessing && aParams && aGeo && aGeo->table,
380  CPL_ERROR_NULL_INPUT);
381  if (aLMin >= aLMax) {
382  cpl_msg_warning(__func__, "Invalid wavelength range for reconstruction "
383  "(%.2f..%.2f), using 6800..7200 instead!", aLMin, aLMax);
384  aLMin = 6800.;
385  aLMax = 7200.;
386  }
387 
388  /* convert all existing raw frames in the used frames to *
389  * CPL_FRAME_GROUP_CALIB so that the dependency of the GEOMETRY_CHECK *
390  * output is clear, but keep the original usedframes around */
391  cpl_frameset *usedoriginal = cpl_frameset_duplicate(aProcessing->usedframes);
392  int iframe, nframes = cpl_frameset_get_size(aProcessing->usedframes);
393  for (iframe = 0; iframe < nframes; iframe++) {
394  cpl_frame *frame = cpl_frameset_get_position(aProcessing->usedframes, iframe);
395  if (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_RAW) {
396  cpl_frame_set_group(frame, CPL_FRAME_GROUP_CALIB);
397  } /* if */
398  } /* for iframe */
399 
400  cpl_table *gt = aGeo->table;
401  cpl_frameset *frames = muse_frameset_find(aProcessing->inframes,
402  "MASK_CHECK", 0, CPL_FALSE);
403  nframes = cpl_frameset_get_size(frames);
404  cpl_msg_info(__func__, "Found %d datasets to reconstruct", nframes);
405  for (iframe = 0; iframe < nframes; iframe++) {
406  cpl_frame *frame = cpl_frameset_get_position(frames, iframe);
407  const char *fn = cpl_frame_get_filename(frame);
408  cpl_msg_info(__func__, "Reconstructing image %d (from \"%s\"), using "
409  "wavelength range %.2f..%.2f", iframe + 1, fn, aLMin, aLMax);
410  muse_pixtable **pts = cpl_calloc(kMuseNumIFUs, sizeof(muse_pixtable));
411  unsigned char nifu;
412  #pragma omp parallel for default(none) /* as req. by Ralf */ \
413  shared(aParams, aProcessing, fn, frames, gt, pts)
414  for (nifu = (unsigned)aParams->ifu1; nifu <= (unsigned)aParams->ifu2; nifu++) {
415  cpl_table *trace = muse_table_load(aProcessing, MUSE_TAG_TRACE_TABLE, nifu),
416  *wavecal = muse_table_load(aProcessing, MUSE_TAG_WAVECAL_TABLE, nifu);
417  if (!trace || !wavecal) {
418  cpl_table_delete(trace);
419  cpl_table_delete(wavecal);
420  continue; /* just skip this IFU */
421  }
422  cpl_frame *bframe = muse_frameset_find_master(aProcessing->inframes,
423  MUSE_TAG_MASTER_BIAS, nifu);
424  if (!bframe) {
425  cpl_table_delete(trace);
426  cpl_table_delete(wavecal);
427  continue;
428  }
429  const char *bname = cpl_frame_get_filename(bframe);
430  cpl_errorstate es = cpl_errorstate_get();
431  muse_image *bias = muse_image_load(bname);
432  if (!bias) {
433  cpl_errorstate_set(es); /* ignore error for case of merged file */
434  bias = muse_image_load_from_extensions(bname, nifu);
435  }
436  cpl_frame_delete(bframe);
437  /* don't want all the overhead of muse_basicproc_load(), *
438  * do part of its processing manually */
439  int ext = muse_utils_get_extension_for_ifu(fn, nifu);
440  muse_image *raw = muse_image_load_from_raw(fn, ext);
441  if (!raw) {
442  cpl_table_delete(trace);
443  cpl_table_delete(wavecal);
444  muse_image_delete(bias);
445  continue; /* just skip this IFU */
446  }
448  muse_quadrants_overscan_stats(raw, bpars ? bpars->rejection : "dcr", 0);
449  if (bpars && !strncmp(bpars->overscan, "vpoly", 5)) {
450  /* vertical polyfit requested, see if there are more parameters */
451  unsigned char ovscvorder = 3;
452  double frms = 1.01,
453  fchisq = 1.04;
454  char *rest = strchr(bpars->overscan, ':');
455  if (strlen(bpars->overscan) > 6 && rest) { /* try to access info after "vpoly:" */
456  ovscvorder = strtol(++rest, &rest, 10);
457  if (strlen(rest) > 0) {
458  frms = strtod(++rest, &rest); /* ++ to skip over the comma */
459  if (strlen(rest) > 0) {
460  fchisq = strtod(++rest, &rest);
461  }
462  }
463  } /* if */
465  ovscvorder, bpars->ovscsigma,
466  frms, fchisq);
467  } /* if bpars and vpoly */
469  muse_image_delete(raw);
470  muse_image_variance_create(image, bias);
471  if (bpars && !strncmp(bpars->overscan, "offset", 6)) {
472  muse_quadrants_overscan_correct(image, bias);
473  } /* if bpars and offset */
475  muse_image_subtract(image, bias);
476  muse_image_delete(bias);
478  pts[nifu - 1] = muse_pixtable_create(image, trace, wavecal, gt);
479  cpl_table_delete(trace);
480  cpl_table_delete(wavecal);
481  muse_image_delete(image);
482  } /* for nifu (all IFUs) */
483  muse_pixtable *pt = pts[aParams->ifu1 - 1];
484  /* merge all pixel tables *
485  * a la muse_pixtable_load_merge_channels() but without flux correction */
486  for (nifu = (unsigned)aParams->ifu1 + 1; nifu <= (unsigned)aParams->ifu2; nifu++) {
487  if (pts[nifu - 1]) {
488  cpl_table_insert(pt->table, pts[nifu - 1]->table, muse_pixtable_get_nrow(pt));
489  muse_pixtable_delete(pts[nifu - 1]);
490  } /* if */
491  } /* for nifu (all IFUs) */
492  /* cut the cube, for faster reconstruction speed */
493  cpl_free(pts);
494  muse_pixtable_restrict_wavelength(pt, aLMin, aLMax);
495  if (muse_pixtable_get_nrow(pt) < 1) {
496  cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_OUTPUT,
497  "After cutting to %.2f..%.2f in wavelength no "
498  "pixels for image reconstruction of \"%s\" are "
499  "left!", aLMin, aLMax, fn);
501  continue;
502  }
503 
504  /* now just reconstruct the cube and collapse over the full range */
506  rp->crsigma = -1.; /* no cr cleaning */
507  /* 10000 Angstrom large pixels -> just one relevant plane! *
508  * (there will be a second but empty plane) */
509  rp->dlambda = 10000.;
510  muse_datacube *cube = muse_resampling_cube(pt, rp, NULL);
513  /* don't want all the cube stuff in the header, better derive it from the *
514  * original primary header of the raw MASK_CHECK exposure; use a trick *
515  * so that the MASK_CHECK files appears first in the used frames and appear in the headers */
516  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_RAW, 1);
517  cpl_frameset_insert(usedoriginal, cpl_frame_duplicate(frame)); /* also add here */
518  cpl_propertylist *header = cpl_propertylist_load(fn, 0);
519  muse_processing_save_cimage(aProcessing, -1, cpl_imagelist_get(cube->data, 0),
520  header, "GEOMETRY_CHECK");
521  cpl_propertylist_delete(header);
522  muse_datacube_delete(cube);
523  } /* for iframe: all found MASK_CHECK frames */
524  cpl_frameset_delete(frames);
525  cpl_frameset_delete(aProcessing->usedframes);
526  aProcessing->usedframes = usedoriginal;
527 
528  return CPL_ERROR_NONE;
529 } /* muse_geometry_reconstruct() */
530 
531 /*----------------------------------------------------------------------------*/
538 /*----------------------------------------------------------------------------*/
539 int
540 muse_geometry_compute(muse_processing *aProcessing,
541  muse_geometry_params_t *aParams)
542 {
543  /* set up centroiding type */
545  if (aParams->centroid == MUSE_GEOMETRY_PARAM_CENTROID_GAUSSIAN) {
546  centroid = MUSE_GEO_CENTROID_GAUSSIAN;
547  } else if (aParams->centroid != MUSE_GEOMETRY_PARAM_CENTROID_BARYCENTER) {
548  cpl_msg_error(__func__, "unknown centroiding method \"%s\"", aParams->centroid_s);
549  return -1;
550  }
551 
552  /* load linelist upfront, to not having to do that in each thread */
553  cpl_table *linelist = muse_table_load(aProcessing, MUSE_TAG_LINE_CATALOG, 0);
554  cpl_propertylist *linehead = muse_propertylist_load(aProcessing,
555  MUSE_TAG_LINE_CATALOG);
556  cpl_boolean listvalid = muse_wave_lines_check(linelist, linehead);
557  cpl_propertylist_delete(linehead);
558  cpl_vector *vlines = muse_geo_lines_get(linelist); /* suitable lines */
559  cpl_table_delete(linelist);
560  if (!listvalid || !vlines) {
561  cpl_msg_error(__func__, "%s could not be loaded/verified or enough suitable"
562  "lines are missing", MUSE_TAG_LINE_CATALOG);
563  cpl_vector_delete(vlines);
564  return -1;
565  }
566 
567  cpl_msg_info(__func__, "Analyzing IFUs %d to %d", aParams->ifu1, aParams->ifu2);
568  cpl_error_code rc[kMuseNumIFUs];
569  cpl_table *mspots[kMuseNumIFUs],
570  *trace[kMuseNumIFUs],
571  *wavecal[kMuseNumIFUs];
572  cpl_propertylist *headfinal = NULL;
573  cpl_array *dy = cpl_array_new(0, CPL_TYPE_DOUBLE);
574  unsigned int nifu;
575  #pragma omp parallel for default(none) /* as req. by Ralf */ \
576  shared(aParams, aProcessing, centroid, dy, headfinal, mspots, rc, \
577  trace, vlines, wavecal)
578  for (nifu = (unsigned)aParams->ifu1; nifu <= (unsigned)aParams->ifu2; nifu++) {
579  rc[nifu - 1] = CPL_ERROR_NONE; /* signify success for this thread by default */
580  mspots[nifu - 1] = NULL;
581 
582  /* load and check trace and wavecal tables early-on, *
583  * to be able to quickly return on error */
584  trace[nifu - 1] = muse_table_load(aProcessing, MUSE_TAG_TRACE_TABLE, nifu);
585  wavecal[nifu - 1] = muse_table_load(aProcessing, MUSE_TAG_WAVECAL_TABLE, nifu);
586  if (!trace[nifu - 1] || !wavecal[nifu - 1]) {
587  cpl_msg_error(__func__, "Calibration could not be loaded for IFU %hu: %s%s",
588  nifu, !trace[nifu - 1] ? " "MUSE_TAG_TRACE_TABLE : "",
589  !wavecal[nifu - 1] ? " "MUSE_TAG_WAVECAL_TABLE : "");
590  cpl_table_delete(trace[nifu - 1]);
591  cpl_table_delete(wavecal[nifu - 1]);
592  trace[nifu - 1] = NULL;
593  wavecal[nifu - 1] = NULL;
594  rc[nifu - 1] = CPL_ERROR_NULL_INPUT;
595  continue;
596  }
597 
598  /* load in separate function to allow for the lengthy *
599  * "skip" option handling from the environment */
600  muse_imagelist *images = muse_geometry_load_images(aProcessing, nifu);
601  if (!images) {
602  cpl_msg_error(__func__, "Loading and basic processing of the raw input "
603  "images failed for IFU %hu!", nifu);
604  cpl_table_delete(trace[nifu - 1]);
605  cpl_table_delete(wavecal[nifu - 1]);
606  trace[nifu - 1] = NULL;
607  wavecal[nifu - 1] = NULL;
608  rc[nifu - 1] = cpl_error_get_code();
609  continue;
610  }
611 
612  unsigned int skip = 0;
613  if (getenv("MUSE_GEOMETRY_SKIP") && atoi(getenv("MUSE_GEOMETRY_SKIP")) > 0) {
614  skip = atoi(getenv("MUSE_GEOMETRY_SKIP"));
615  }
616  cpl_propertylist *header;
617  if (skip >= 2) { /* directly skip to existing spots table */
618  char *fn = cpl_sprintf("SPOTS_TABLE-%02hu.fits", nifu);
619  cpl_msg_warning(__func__, "Reading spot measurements from \"%s\"", fn);
620  mspots[nifu - 1] = cpl_table_load(fn, 1, 1);
621  cpl_free(fn);
622  } else {
624  cpl_propertylist_append(av->header, muse_imagelist_get(images, 0)->header);
625  muse_processing_save_image(aProcessing, nifu, av, MUSE_TAG_MASK_COMBINED);
626  muse_image_reject_from_dq(av); /* make sure to recognize bad pixels */
627  mspots[nifu - 1] = muse_geo_measure_spots(av, images, trace[nifu - 1],
628  wavecal[nifu - 1], vlines,
629  aParams->sigma, centroid);
630  muse_image_delete(av);
631  /* now save this intermediate result */
632  header = cpl_propertylist_duplicate(muse_imagelist_get(images, 0)->header);
633  /* does this QC really belong into the spots table, or should that *
634  * just be for debugging and the QC go into the geometry table? */
635  muse_geometry_qc_spots(mspots[nifu - 1], header);
636  muse_processing_save_table(aProcessing, nifu, mspots[nifu - 1], header,
637  MUSE_TAG_SPOTS_TABLE, MUSE_TABLE_TYPE_CPL);
638  cpl_propertylist_delete(header);
639  } /* else: measure all spots */
640  cpl_msg_info(__func__, "measured %"CPL_SIZE_FORMAT" spots in %u images",
641  cpl_table_get_nrow(mspots[nifu - 1]), muse_imagelist_get_size(images));
642  cpl_table_delete(wavecal[nifu - 1]);
643 
644  if (!skip) { /* we started from the raw images */
645  /* save reduced images after the measurement because while setting up *
646  * the output header we delete the MUSE_HDR_TMP_FN keyword from the image */
647  unsigned int k;
648  for (k = 0; k < muse_imagelist_get_size(images); k++) {
649  muse_image *image = muse_imagelist_get(images, k);
650  muse_processing_save_image(aProcessing, nifu, image,
651  MUSE_TAG_MASK_REDUCED);
652  } /* for k (all images) */
653  } /* if !skip */
654  #pragma omp critical (muse_geo_header_construct)
655  {
656  if (!headfinal) {
657  headfinal = cpl_propertylist_duplicate(muse_imagelist_get(images, 0)->header);
658  /* Remove some properties that we don't need in the output file. *
659  * They refer to the specific extension and should not be *
660  * present in the output file that is global to the instrument. *
661  * Some may be put back from the first raw input file by *
662  * cpl_dfs_setup_product_header(), but at least try... */
663  cpl_propertylist_erase_regexp(headfinal,
664  "EXTNAME|ESO DET (CHIP|OUT)|ESO DET2", 0);
665  } /* if */
666  }
667  muse_imagelist_delete(images);
668  /* compute the effective vertical pinhole distance per IFU */
669  muse_geo_compute_pinhole_local_distance(dy, mspots[nifu - 1]);
670  } /* for nifu (all IFUs) */
671  /* now use the full array for the global statistics to get a final pinhole distance */
672  double pinholedy = muse_geo_compute_pinhole_global_distance(dy, 0.001, 0.5, 0.8);
673  /* XXX return the final value, but don't do anything with it, since for *
674  * the moment it is already set in the environment (in the function) */
675  UNUSED_ARGUMENT(pinholedy);
676  cpl_array_delete(dy);
677 
678  /* variables for combined output tables */
679  cpl_table *spots = NULL;
680  muse_geo_table *geotable = NULL;
681  #pragma omp parallel for default(none) /* as req. by Ralf */ \
682  shared(aParams, geotable, headfinal, mspots, spots, trace, vlines)
683  for (nifu = (unsigned)aParams->ifu1; nifu <= (unsigned)aParams->ifu2; nifu++) {
684  /* finally compute the geometry, initial and horizontal parts for one IFU */
685  muse_geo_table *geoinit = muse_geo_determine_initial(mspots[nifu - 1], trace[nifu - 1]);
686  muse_geo_table *geohori = muse_geo_determine_horizontal(geoinit);
687  cpl_table_delete(trace[nifu - 1]);
688  /* add QC parameters and save the result, here, the initial geometry is needed */
689  #pragma omp critical (muse_geo_header_construct)
690  muse_geometry_qc_ifu(geoinit, headfinal, vlines);
691  muse_geo_table_delete(geoinit);
692 
693  /* prepare the combined tables (geometry and spots) */
694  #pragma omp critical (muse_geo_table_insert)
695  {
696  if (!geotable) {
697  geotable = geohori;
698  } else {
699  if (fabs(geotable->scale - geohori->scale) > DBL_EPSILON) {
700  cpl_msg_warning(__func__, "Combined and single geometry tables have "
701  "different scales (%f / %f)!", geotable->scale,
702  geohori->scale);
703  }
704  cpl_table_insert(geotable->table, geohori->table,
705  cpl_table_get_nrow(geotable->table));
706  muse_geo_table_delete(geohori);
707  }
708  }
709  #pragma omp critical (muse_spots_table_insert)
710  {
711  if (!spots) {
712  spots = mspots[nifu - 1];
713  } else {
714  cpl_table_insert(spots, mspots[nifu - 1], cpl_table_get_nrow(spots));
715  cpl_table_delete(mspots[nifu - 1]);
716  }
717  }
718  } /* for nifu (all IFUs) */
719  cpl_vector_delete(vlines);
720  cpl_error_code result = CPL_ERROR_NONE;
721  /* non-parallel loop to check for return codes */
722  for (nifu = (unsigned)aParams->ifu1; nifu <= (unsigned)aParams->ifu2; nifu++) {
723  if (result < rc[nifu - 1]) {
724  result = rc[nifu - 1];
725  } /* if */
726  } /* for nifu (all IFUs) */
727 #if 0
728  cpl_table_save(geotable, NULL, NULL, "geocombined.fits", CPL_IO_CREATE);
729  cpl_table_save(spots, NULL, NULL, "spotscombined.fits", CPL_IO_CREATE);
730 #endif
731  muse_geo_refine_horizontal(geotable, spots);
732  cpl_table_delete(spots);
733 
734  muse_geo_table *geofinal = muse_geo_determine_vertical(geotable);
735  muse_geo_table_delete(geotable);
736  cpl_error_code rcfinal = muse_geo_finalize(geofinal);
737  if (result < rcfinal) {
738  result = rcfinal;
739  }
740  muse_geometry_qc_global(geofinal, headfinal);
741  if (geofinal) {
742  muse_processing_save_table(aProcessing, -1, geofinal->table, headfinal,
743  MUSE_TAG_GEOMETRY_TABLE, MUSE_TABLE_TYPE_CPL);
744  }
745  /* first, try to reconstruct the (average-)combined images */
746  muse_geometry_reconstruct_combined(aProcessing, aParams, geofinal,
747  aParams->lambdamin, aParams->lambdamax);
748  /* try to reconstruct some images using this final geometry table, if *
749  * no MASK_CHECK files were passed to this recipe, this will do nothing */
750  muse_geometry_reconstruct(aProcessing, aParams, geofinal,
751  aParams->lambdamin, aParams->lambdamax);
752  muse_geo_table_delete(geofinal);
753  cpl_propertylist_delete(headfinal);
754 
755  return result != CPL_ERROR_NONE ? -1 : 0;
756 } /* muse_geometry_compute() */
cpl_error_code muse_quadrants_overscan_correct(muse_image *aImage, muse_image *aRefImage)
Adapt bias level to reference image using overscan statistics.
int muse_processing_save_cimage(muse_processing *aProcessing, int aIFU, cpl_image *aImage, cpl_propertylist *aHeader, const char *aTag)
Save a computed FITS image to disk.
muse_imagelist * muse_basicproc_load(muse_processing *aProcessing, unsigned char aIFU, muse_basicproc_params *aBPars)
Load the raw input files from disk and do basic processing.
Structure definition of a MUSE datacube.
Definition: muse_datacube.h:47
Structure definition for a collection of muse_images.
const char * centroid_s
Type of centroiding and FWHM determination to use for all spot measurements: simple barycenter method...
cpl_error_code muse_geo_refine_horizontal(muse_geo_table *aGeo, cpl_table *aSpots)
Refine relative horizontal positions of adjacent IFUs.
Definition: muse_geo.c:2359
void muse_image_delete(muse_image *aImage)
Deallocate memory associated to a muse_image object.
Definition: muse_image.c:85
int muse_utils_get_extension_for_ifu(const char *aFilename, unsigned char aIFU)
Return extension number that corresponds to this IFU/channel number.
Definition: muse_utils.c:119
muse_image * muse_image_load_from_raw(const char *aFilename, int aExtension)
Load raw image into the data extension of a MUSE image.
Definition: muse_image.c:287
cpl_size muse_pixtable_get_nrow(const muse_pixtable *aPixtable)
get the number of rows within the pixel table
double sigma
Sigma detection level for spot detection, in terms of median deviation above the median.
double lambdamax
When passing any MASK_CHECK frames in the input, use this upper wavelength cut before reconstructing ...
int muse_image_subtract(muse_image *aImage, muse_image *aSubtract)
Subtract a muse_image from another with correct treatment of bad pixels and variance.
Definition: muse_image.c:585
void muse_datacube_delete(muse_datacube *aCube)
Deallocate memory associated to a muse_datacube object.
double muse_geo_compute_pinhole_global_distance(cpl_array *aDY, double aWidth, double aMin, double aMax)
Use vertical pinhole distance measurements of all IFUs to compute the effective global value...
Definition: muse_geo.c:1107
void muse_imagelist_delete(muse_imagelist *aList)
Free the memory of the MUSE image list.
double scale
The VLT focal plane scale factor of the data. output file.
Definition: muse_geo.h:83
muse_geo_table * muse_geo_determine_vertical(const muse_geo_table *aGeo)
Use all properties of the central spot and the horizontal properties in each slice to compute the ver...
Definition: muse_geo.c:2735
muse_image * muse_datacube_collapse(muse_datacube *aCube, cpl_table *aFilter)
Integrate a FITS NAXIS=3 datacube along the wavelength direction.
Structure definition of MUSE three extension FITS file.
Definition: muse_image.h:40
cpl_array * recnames
the reconstructed image filter names
Definition: muse_datacube.h:70
cpl_error_code muse_quadrants_overscan_polyfit_vertical(muse_image *aImage, unsigned aIgnore, unsigned char aOrder, double aSigma, const double aFRMS, double aFChiSq)
Correct quadrants by polynomial representation of vertical overscan.
unsigned int ovscignore
cpl_table * table
The pixel table.
void muse_basicproc_params_delete(muse_basicproc_params *aBPars)
Free a structure of basic processing parameters.
cpl_propertylist * header
the FITS header
Definition: muse_image.h:72
muse_image * muse_combine_average_create(muse_imagelist *aImages)
Average a list of input images.
Definition: muse_combine.c:234
int muse_image_variance_create(muse_image *aImage, muse_image *aBias)
Create the photon noise-based variance in the stat extension.
Definition: muse_image.c:739
unsigned int muse_imagelist_get_size(muse_imagelist *aList)
Return the number of stored images.
cpl_frameset * usedframes
muse_geo_table * muse_geo_determine_horizontal(const muse_geo_table *aGeo)
Use per-spot and per-wavelength partial geometry to determine the horizontal geometrical properties f...
Definition: muse_geo.c:1928
cpl_error_code muse_geo_compute_pinhole_local_distance(cpl_array *aDY, cpl_table *aSpots)
Use spot measurements of one IFU to compute vertical pinhole distance.
Definition: muse_geo.c:1011
cpl_error_code muse_pixtable_restrict_wavelength(muse_pixtable *aPixtable, double aLow, double aHigh)
Restrict a pixel table to a certain wavelength range.
cpl_table * muse_geo_measure_spots(muse_image *aImage, muse_imagelist *aList, const cpl_table *aTrace, const cpl_table *aWave, const cpl_vector *aLines, double aSigma, muse_geo_centroid_type aCentroid)
Detect spots on a combined image and measure them on the corresponding series of images.
Definition: muse_geo.c:458
Structure definition of MUSE pixel table.
muse_image * muse_imagelist_get(muse_imagelist *aList, unsigned int aIdx)
Get the muse_image of given list index.
cpl_frameset * outframes
cpl_table * table
The geometry table.
Definition: muse_geo.h:77
Structure definition of MUSE geometry table.
Definition: muse_geo.h:71
cpl_error_code muse_geo_finalize(muse_geo_table *aGeo)
Create a final version of a geometry table.
Definition: muse_geo.c:3139
muse_resampling_params * muse_resampling_params_new(muse_resampling_type aMethod)
Create the resampling parameters structure.
muse_geo_centroid_type
Type of centroiding algorithm to use.
Definition: muse_geo.h:91
cpl_error_code muse_processing_save_cube(muse_processing *aProcessing, int aIFU, void *aCube, const char *aTag, muse_cube_type aType)
Save a MUSE datacube to disk.
cpl_imagelist * data
the cube containing the actual data values
Definition: muse_datacube.h: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.
muse_pixtable * muse_pixtable_create(muse_image *aImage, cpl_table *aTrace, cpl_table *aWave, cpl_table *aGeoTable)
Create the pixel table for one CCD.
void muse_geo_table_delete(muse_geo_table *aGeo)
Deallocate memory associated to a geometry table object.
Definition: muse_geo.c:1232
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.
int centroid
Type of centroiding and FWHM determination to use for all spot measurements: simple barycenter method...
cpl_error_code muse_quadrants_overscan_stats(muse_image *aImage, const char *aRejection, unsigned int aIgnore)
Compute overscan statistics of all quadrants and save in FITS header.
int muse_processing_save_image(muse_processing *aProcessing, int aIFU, muse_image *aImage, const char *aTag)
Save a computed MUSE image to disk.
int ifu2
Last IFU to analyze.
muse_image * muse_image_load(const char *aFilename)
Load the three extensions and the FITS headers of a MUSE image from a file.
Definition: muse_image.c:223
cpl_error_code muse_image_reject_from_dq(muse_image *aImage)
Reject pixels of a muse_image depending on its DQ data.
Definition: muse_image.c:856
muse_imagelist * muse_imagelist_new(void)
Create a new (empty) MUSE image list.
cpl_frameset * inframes
int ifu1
First IFU to analyze.
muse_basicproc_params * muse_basicproc_params_new_from_propertylist(const cpl_propertylist *aHeader)
Create a structure of basic processing parameters from a FITS header.
cpl_table * muse_table_load(muse_processing *aProcessing, const char *aTag, unsigned char aIFU)
load a table according to its tag and IFU/channel number
Definition: muse_utils.c:721
double lambdamin
When passing any MASK_CHECK frames in the input, use this lower wavelength cut before reconstructing ...
cpl_error_code muse_processing_save_table(muse_processing *aProcessing, int aIFU, void *aTable, cpl_propertylist *aHeader, const char *aTag, muse_table_type aType)
Save a computed table to disk.
Structure of basic processing parameters.
Resampling parameters.
void muse_resampling_params_delete(muse_resampling_params *aParams)
Delete a resampling parameters structure.
cpl_propertylist * muse_propertylist_load(muse_processing *aProcessing, const char *aTag)
load a propertylist according to its tag
Definition: muse_utils.c:789
cpl_boolean muse_wave_lines_check(cpl_table *aTable, cpl_propertylist *aHeader)
Check that a LINE_CATALOG has the expected format.
cpl_error_code muse_image_adu_to_count(muse_image *aImage)
Convert the data units from raw adu to count (= electron) units.
Definition: muse_image.c:803
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:158
cpl_vector * muse_geo_lines_get(const cpl_table *aLines)
Select lines suitable for geometrical calibration from a line list.
Definition: muse_geo.c:354
muse_image * muse_image_load_from_extensions(const char *aFilename, unsigned char aIFU)
Load the three extensions and the FITS headers of a MUSE image from extensions of a merged file...
Definition: muse_image.c:257
void muse_pixtable_delete(muse_pixtable *aPixtable)
Deallocate memory associated to a pixel table object.
cpl_error_code muse_imagelist_set(muse_imagelist *aList, muse_image *aImage, unsigned int aIdx)
Set the muse_image of given list index.
muse_geo_table * muse_geo_determine_initial(cpl_table *aSpots, const cpl_table *aTrace)
Use spot measurements to compute initial geometrical properties.
Definition: muse_geo.c:1308
cpl_frame * muse_frameset_find_master(const cpl_frameset *aFrames, const char *aTag, unsigned char aIFU)
find the master frame according to its CCD number and tag
Definition: muse_utils.c:468
muse_imagelist * recimages
the reconstructed image data
Definition: muse_datacube.h:63
muse_image * muse_quadrants_trim_image(muse_image *aImage)
Trim the input image of pre- and over-scan regions of all quadrants.
Structure to hold the parameters of the muse_geometry recipe.