MUSE Pipeline Reference Manual  0.18.5
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  cpl_msg_debug(__func__, "Adding QC keywords to %s", MUSE_TAG_SPOTS_TABLE);
52 
53  unsigned int nexp,
54  nexp1 = cpl_table_get_column_min(aSpots, "image"),
55  nexp2 = cpl_table_get_column_max(aSpots, "image");
56  for (nexp = nexp1; nexp <= nexp2; nexp++) {
57  cpl_table_unselect_all(aSpots);
58  cpl_table_or_selected_int(aSpots, "image", CPL_EQUAL_TO, nexp);
59  cpl_table_and_selected_double(aSpots, "xfwhm", CPL_GREATER_THAN, 0.);
60  cpl_table_and_selected_double(aSpots, "yfwhm", CPL_GREATER_THAN, 0.);
61  cpl_table_and_selected_double(aSpots, "flux", CPL_GREATER_THAN, 1000.);
62  cpl_table *table = cpl_table_extract_selected(aSpots);
63  /* compute the average of the x and y FWHM values */
64  cpl_table_duplicate_column(table, "fwhm", table, "xfwhm");
65  cpl_table_add_columns(table, "fwhm", "yfwhm");
66  cpl_table_multiply_scalar(table, "fwhm", 0.5);
67  double mean = cpl_table_get_column_mean(table, "fwhm"),
68  median = cpl_table_get_column_median(table, "fwhm"),
69  stdev = cpl_table_get_column_stdev(table, "fwhm");
70  cpl_table_delete(table);
71 
72  char keyword[KEYWORD_LENGTH];
73  snprintf(keyword, KEYWORD_LENGTH, QC_GEO_EXPk_MEAN, nexp);
74  cpl_propertylist_update_float(aHeader, keyword, mean);
75  snprintf(keyword, KEYWORD_LENGTH, QC_GEO_EXPk_MEDIAN, nexp);
76  cpl_propertylist_update_float(aHeader, keyword, median);
77  snprintf(keyword, KEYWORD_LENGTH, QC_GEO_EXPk_STDEV, nexp);
78  cpl_propertylist_update_float(aHeader, keyword, stdev);
79  } /* for nexp */
80 } /* muse_geometry_qc_spots() */
81 
82 /*---------------------------------------------------------------------------*/
89 /*---------------------------------------------------------------------------*/
90 static void
91 muse_geometry_qc_ifu(muse_geo_table *aGeoInit, cpl_propertylist *aHeader,
92  const cpl_vector *aLambdas)
93 {
94  cpl_table *gtinit = aGeoInit->table;
95  const unsigned char ifu = cpl_table_get_int(gtinit, MUSE_GEOTABLE_FIELD, 0, NULL);
96  double angle = cpl_table_get_column_mean(gtinit, MUSE_GEOTABLE_ANGLE),
97  astdev = cpl_table_get_column_stdev(gtinit, MUSE_GEOTABLE_ANGLE),
98  amedian = cpl_table_get_column_median(gtinit, MUSE_GEOTABLE_ANGLE);
99  cpl_msg_debug(__func__, "Adding QC keywords for IFU %hhu: angle = %.3f +/- %.3f "
100  "(%.3f) deg", ifu, angle, astdev, amedian);
101  char *keyword = cpl_sprintf(QC_GEO_IFUi_ANGLE, ifu);
102  cpl_propertylist_update_float(aHeader, keyword, amedian);
103  cpl_free(keyword);
104 
105  int i, n = cpl_vector_get_size(aLambdas);
106  for (i = 1; i <= n; i++) {
107  double lambda = cpl_vector_get(aLambdas, i - 1);
108  char *kw = cpl_sprintf(QC_GEO_IFUi_WLENj, ifu, i),
109  *kwmean = cpl_sprintf(QC_GEO_IFUi_MEANj, ifu, i),
110  *kwmedi = cpl_sprintf(QC_GEO_IFUi_MEDIANj, ifu, i),
111  *kwstdv = cpl_sprintf(QC_GEO_IFUi_STDEVj, ifu, i);
112  cpl_propertylist_update_float(aHeader, kw, lambda);
113 
114  cpl_table_unselect_all(gtinit);
115  cpl_table_or_selected_double(gtinit, "lambda", CPL_EQUAL_TO, lambda);
116  if (cpl_table_count_selected(gtinit) < 1) { /* fill in dummy values */
117  cpl_propertylist_update_float(aHeader, kwmean, i);
118  cpl_propertylist_update_float(aHeader, kwmedi, i);
119  cpl_propertylist_update_float(aHeader, kwstdv, i);
120  } else {
121  cpl_table *t = cpl_table_extract_selected(gtinit);
122  cpl_propertylist_update_float(aHeader, kwmean,
123  cpl_table_get_column_mean(t, "flux"));
124  cpl_propertylist_update_float(aHeader, kwmedi,
125  cpl_table_get_column_median(t, "flux"));
126  cpl_propertylist_update_float(aHeader, kwstdv,
127  cpl_table_get_column_stdev(t, "flux"));
128  cpl_table_delete(t);
129  }
130  cpl_free(kw);
131  cpl_free(kwmean);
132  cpl_free(kwmedi);
133  cpl_free(kwstdv);
134  } /* for i (all lambdas) */
135 } /* muse_geometry_qc_ifu() */
136 
137 /*---------------------------------------------------------------------------*/
143 /*---------------------------------------------------------------------------*/
144 static void
145 muse_geometry_qc_global(const muse_geo_table *aGeoTable,
146  cpl_propertylist *aHeader)
147 {
148  double angle = cpl_table_get_column_mean(aGeoTable->table, MUSE_GEOTABLE_ANGLE),
149  astdev = cpl_table_get_column_stdev(aGeoTable->table, MUSE_GEOTABLE_ANGLE),
150  amedian = cpl_table_get_column_median(aGeoTable->table, MUSE_GEOTABLE_ANGLE);
151  cpl_msg_debug(__func__, "Adding global QC keywords: angle = %.3f +/- %.3f "
152  "(%.3f) deg", angle, astdev, amedian);
153  cpl_propertylist_update_float(aHeader, QC_GEO_MASK_ANGLE, amedian);
154 } /* muse_geometry_qc_global() */
155 
156 /*---------------------------------------------------------------------------*/
163 /*---------------------------------------------------------------------------*/
164 static muse_imagelist *
165 muse_geometry_load_images(muse_processing *aProcessing, unsigned short aIFU)
166 {
167  unsigned int skip = 0;
168  if (getenv("MUSE_GEOMETRY_SKIP") && atoi(getenv("MUSE_GEOMETRY_SKIP")) > 0) {
169  skip = atoi(getenv("MUSE_GEOMETRY_SKIP"));
170  }
171 
172  muse_imagelist *images;
173  if (!skip) { /* going all the way from raw exposures */
174  images = muse_basicproc_load(aProcessing, aIFU, NULL);
175  } else { /* skipping some part of the loading/processing */
176  images = muse_imagelist_new();
177  /* filenames are "MASK_REDUCED_00ii-jj.fits" */
178  unsigned int ifile, nfiles = 99; /* first assume that we will read many files */
179  if (skip >= 2) {
180  cpl_msg_warning(__func__, "Skipping spot measurements, only loading first"
181  " (reduced) file for IFU %02hu", aIFU);
182  nfiles = 1;
183  } else {
184  cpl_msg_warning(__func__, "Skipping raw image processing, loading all "
185  "reduced images directly");
186  }
187  for (ifile = 0; ifile < nfiles; ifile++) {
188  char *fn = cpl_sprintf("MASK_REDUCED_00%02u-%02hu.fits", ifile + 1, aIFU);
189  cpl_errorstate es = cpl_errorstate_get();
190  muse_image *image = muse_image_load(fn);
191  if (!image) {
192  cpl_errorstate_set(es); /* ignore the errors */
193  cpl_free(fn);
194  break;
195  }
196  cpl_frame *frame = cpl_frame_new();
197  cpl_frame_set_filename(frame, fn);
198  cpl_frame_set_tag(frame, "MASK");
199  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_RAW, 0);
200  cpl_msg_debug(__func__, "file = %s", fn);
201  muse_imagelist_set(images, image, ifile);
202  cpl_free(fn);
203  } /* for ifile */
204  cpl_msg_debug(__func__, "Read %u file%s", ifile, ifile == 1 ? "" : "s");
205  } /* else (skipped some loading/processing) */
206  return images;
207 } /* muse_geometry_load_images() */
208 
209 /*----------------------------------------------------------------------------*/
232 /*----------------------------------------------------------------------------*/
233 static cpl_error_code
234 muse_geometry_reconstruct_combined(muse_processing *aProcessing,
235  muse_geometry_params_t *aParams,
236  muse_geo_table *aGeo,
237  double aLMin, double aLMax)
238 {
239  cpl_ensure_code(aProcessing && aParams && aGeo && aGeo->table,
240  CPL_ERROR_NULL_INPUT);
241  if (aLMin >= aLMax) {
242  cpl_msg_warning(__func__, "Invalid wavelength range for reconstruction "
243  "(%.2f..%.2f), using 6800..7200 instead!", aLMin, aLMax);
244  aLMin = 6800.;
245  aLMax = 7200.;
246  }
247  unsigned int skip = 0;
248  if (getenv("MUSE_GEOMETRY_SKIP") && atoi(getenv("MUSE_GEOMETRY_SKIP")) > 0) {
249  skip = atoi(getenv("MUSE_GEOMETRY_SKIP"));
250  }
251  cpl_table *gt = aGeo->table;
252 
253  muse_pixtable **pts = cpl_calloc(kMuseNumIFUs, sizeof(muse_pixtable));
254  unsigned char nifu;
255  #pragma omp parallel for default(none) /* as req. by Ralf */ \
256  shared(gt, aParams, aProcessing, pts, skip)
257  for (nifu = (unsigned)aParams->ifu1; nifu <= (unsigned)aParams->ifu2; nifu++) {
258  cpl_table *trace = muse_table_load(aProcessing, MUSE_TAG_TRACE_TABLE, nifu),
259  *wavecal = muse_table_load(aProcessing, MUSE_TAG_WAVECAL_TABLE, nifu);
260  if (!trace || !wavecal) {
261  cpl_table_delete(trace);
262  cpl_table_delete(wavecal);
263  continue; /* just skip this IFU */
264  }
265  /* now load again the file we saved previously */
266  cpl_frame *cframe = NULL;
267  if (skip == 2) { /* we are running with MUSE_GEOMETRY_SKIP=2 */
268  /* construct frame manually, assuming a local file */
269  cframe = cpl_frame_new();
270  char *fn = cpl_sprintf("MASK_COMBINED-%02hhu.fits", nifu);
271  cpl_frame_set_filename(cframe, fn);
272  } else {
273  cframe = muse_frameset_find_master(aProcessing->outputFrames,
274  MUSE_TAG_MASK_COMBINED, nifu);
275  }
276  if (!cframe) {
277  cpl_table_delete(trace);
278  cpl_table_delete(wavecal);
279  continue;
280  }
281  cpl_msg_debug(__func__, "reconstructing IFU %2hhu using \"%s\"", nifu,
282  cpl_frame_get_filename(cframe));
283  muse_image *combined = muse_image_load(cpl_frame_get_filename(cframe));
284  cpl_frame_delete(cframe);
285  pts[nifu - 1] = muse_pixtable_create(combined, trace, wavecal, gt);
286  cpl_table_delete(trace);
287  cpl_table_delete(wavecal);
288  muse_image_delete(combined);
289  } /* for nifu (all IFUs) */
290  muse_pixtable *pt = pts[aParams->ifu1 - 1];
291  /* merge all pixel tables *
292  * a la muse_pixtable_load_merge_channels() but without flux correction */
293  for (nifu = (unsigned)aParams->ifu1 + 1; nifu <= (unsigned)aParams->ifu2; nifu++) {
294  if (pts[nifu - 1]) {
295  cpl_table_insert(pt->table, pts[nifu - 1]->table, muse_pixtable_get_nrow(pt));
296  muse_pixtable_delete(pts[nifu - 1]);
297  } /* if */
298  } /* for nifu (all IFUs) */
299  /* cut the cube, for faster reconstruction speed */
300  cpl_free(pts);
301  muse_pixtable_restrict_wavelength(pt, aLMin, aLMax);
302  if (muse_pixtable_get_nrow(pt) < 1) {
304  return cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_OUTPUT,
305  "After cutting to %.2f..%.2f in wavelength no "
306  "pixels for image reconstruction are left!",
307  aLMin, aLMax);
308  }
309 
310  /* now just reconstruct the cube and collapse over the full range */
312  rp->crsigma = -1.; /* no cr cleaning */
313  muse_datacube *cube = muse_resampling_cube(pt, rp, NULL);
316  /* also create a corresponding white-light imgae */
317  muse_image *white = muse_datacube_collapse(cube, NULL);
318  cube->recimages = muse_imagelist_new();
319  cube->recnames = cpl_array_new(1, CPL_TYPE_STRING);
320  muse_imagelist_set(cube->recimages, white, 0);
321  cpl_array_set_string(cube->recnames, 0, "white");
322  muse_processing_save_cube(aProcessing, -1, cube, "GEOMETRY_CUBE",
324  muse_datacube_delete(cube);
325 
326  return CPL_ERROR_NONE;
327 } /* muse_geometry_reconstruct_combined() */
328 
329 /*----------------------------------------------------------------------------*/
355 /*----------------------------------------------------------------------------*/
356 static cpl_error_code
357 muse_geometry_reconstruct(muse_processing *aProcessing,
358  muse_geometry_params_t *aParams,
359  muse_geo_table *aGeo, double aLMin, double aLMax)
360 {
361  cpl_ensure_code(aProcessing && aParams && aGeo && aGeo->table,
362  CPL_ERROR_NULL_INPUT);
363  if (aLMin >= aLMax) {
364  cpl_msg_warning(__func__, "Invalid wavelength range for reconstruction "
365  "(%.2f..%.2f), using 6800..7200 instead!", aLMin, aLMax);
366  aLMin = 6800.;
367  aLMax = 7200.;
368  }
369 
370  /* convert all existing raw frames in the used frames to *
371  * CPL_FRAME_GROUP_CALIB so that the dependency of the GEOMETRY_CHECK *
372  * output is clear, but keep the original usedFrames around */
373  cpl_frameset *usedoriginal = cpl_frameset_duplicate(aProcessing->usedFrames);
374  int iframe, nframes = cpl_frameset_get_size(aProcessing->usedFrames);
375  for (iframe = 0; iframe < nframes; iframe++) {
376  cpl_frame *frame = cpl_frameset_get_position(aProcessing->usedFrames, iframe);
377  if (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_RAW) {
378  cpl_frame_set_group(frame, CPL_FRAME_GROUP_CALIB);
379  } /* if */
380  } /* for iframe */
381 
382  cpl_table *gt = aGeo->table;
383  cpl_frameset *frames = muse_frameset_find(aProcessing->inputFrames,
384  "MASK_CHECK", 0, CPL_FALSE);
385  nframes = cpl_frameset_get_size(frames);
386  cpl_msg_info(__func__, "Found %d datasets to reconstruct", nframes);
387  for (iframe = 0; iframe < nframes; iframe++) {
388  cpl_frame *frame = cpl_frameset_get_position(frames, iframe);
389  const char *fn = cpl_frame_get_filename(frame);
390  cpl_msg_info(__func__, "Reconstructing image %d (from \"%s\"), using "
391  "wavelength range %.2f..%.2f", iframe + 1, fn, aLMin, aLMax);
392  muse_pixtable **pts = cpl_calloc(kMuseNumIFUs, sizeof(muse_pixtable));
393  unsigned char nifu;
394  #pragma omp parallel for default(none) /* as req. by Ralf */ \
395  shared(aParams, aProcessing, fn, frames, gt, pts)
396  for (nifu = (unsigned)aParams->ifu1; nifu <= (unsigned)aParams->ifu2; nifu++) {
397  cpl_table *trace = muse_table_load(aProcessing, MUSE_TAG_TRACE_TABLE, nifu),
398  *wavecal = muse_table_load(aProcessing, MUSE_TAG_WAVECAL_TABLE, nifu);
399  if (!trace || !wavecal) {
400  cpl_table_delete(trace);
401  cpl_table_delete(wavecal);
402  continue; /* just skip this IFU */
403  }
404  cpl_frame *bframe = muse_frameset_find_master(aProcessing->inputFrames,
405  MUSE_TAG_MASTER_BIAS, nifu);
406  if (!bframe) {
407  cpl_table_delete(trace);
408  cpl_table_delete(wavecal);
409  continue;
410  }
411  const char *bname = cpl_frame_get_filename(bframe);
412  cpl_errorstate es = cpl_errorstate_get();
413  muse_image *bias = muse_image_load(bname);
414  if (!bias) {
415  cpl_errorstate_set(es); /* ignore error for case of merged file */
416  bias = muse_image_load_from_extensions(bname, nifu);
417  }
418  cpl_frame_delete(bframe);
419  /* don't want all the overhead of muse_basicproc_load(), *
420  * do part of its processing manually */
421  int ext = muse_utils_get_extension_for_ifu(fn, nifu);
422  muse_image *raw = muse_image_load_from_raw(fn, ext);
423  if (!raw) {
424  cpl_table_delete(trace);
425  cpl_table_delete(wavecal);
426  muse_image_delete(bias);
427  continue; /* just skip this IFU */
428  }
429  muse_quadrants_overscan_stats(raw, "dcr", 0);
431  muse_image_delete(raw);
432  muse_image_variance_create(image, bias);
433  muse_quadrants_overscan_correct(image, bias);
434  muse_image_subtract(image, bias);
435  muse_image_delete(bias);
437  pts[nifu - 1] = muse_pixtable_create(image, trace, wavecal, gt);
438  cpl_table_delete(trace);
439  cpl_table_delete(wavecal);
440  muse_image_delete(image);
441  } /* for nifu (all IFUs) */
442  muse_pixtable *pt = pts[aParams->ifu1 - 1];
443  /* merge all pixel tables *
444  * a la muse_pixtable_load_merge_channels() but without flux correction */
445  for (nifu = (unsigned)aParams->ifu1 + 1; nifu <= (unsigned)aParams->ifu2; nifu++) {
446  if (pts[nifu - 1]) {
447  cpl_table_insert(pt->table, pts[nifu - 1]->table, muse_pixtable_get_nrow(pt));
448  muse_pixtable_delete(pts[nifu - 1]);
449  } /* if */
450  } /* for nifu (all IFUs) */
451  /* cut the cube, for faster reconstruction speed */
452  cpl_free(pts);
453  muse_pixtable_restrict_wavelength(pt, aLMin, aLMax);
454  if (muse_pixtable_get_nrow(pt) < 1) {
455  cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_OUTPUT,
456  "After cutting to %.2f..%.2f in wavelength no "
457  "pixels for image reconstruction of \"%s\" are "
458  "left!", aLMin, aLMax, fn);
460  continue;
461  }
462 
463  /* now just reconstruct the cube and collapse over the full range */
465  rp->crsigma = -1.; /* no cr cleaning */
466  /* 10000 Angstrom large pixels -> just one relevant plane! *
467  * (there will be a second but empty plane) */
468  rp->dlambda = 10000.;
469  muse_datacube *cube = muse_resampling_cube(pt, rp, NULL);
472  /* don't want all the cube stuff in the header, better derive it from the *
473  * original primary header of the raw MASK_CHECK exposure; use a trick *
474  * so that the MASK_CHECK files appears first in the used frames and appear in the headers */
475  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_RAW, 1);
476  cpl_frameset_insert(usedoriginal, cpl_frame_duplicate(frame)); /* also add here */
477  cpl_propertylist *header = cpl_propertylist_load(fn, 0);
478  muse_processing_save_cimage(aProcessing, -1, cpl_imagelist_get(cube->data, 0),
479  header, "GEOMETRY_CHECK");
480  cpl_propertylist_delete(header);
481  muse_datacube_delete(cube);
482  } /* for iframe: all found MASK_CHECK frames */
483  cpl_frameset_delete(frames);
484  cpl_frameset_delete(aProcessing->usedFrames);
485  aProcessing->usedFrames = usedoriginal;
486 
487  return CPL_ERROR_NONE;
488 } /* muse_geometry_reconstruct() */
489 
490 /*----------------------------------------------------------------------------*/
497 /*----------------------------------------------------------------------------*/
498 int
499 muse_geometry_compute(muse_processing *aProcessing,
500  muse_geometry_params_t *aParams)
501 {
502  /* set up centroiding type */
504  if (aParams->centroid == MUSE_GEOMETRY_PARAM_CENTROID_GAUSSIAN) {
505  centroid = MUSE_GEO_CENTROID_GAUSSIAN;
506  } else if (aParams->centroid != MUSE_GEOMETRY_PARAM_CENTROID_BARYCENTER) {
507  cpl_msg_error(__func__, "unknown centroiding method \"%s\"", aParams->centroid_s);
508  return -1;
509  }
510 
511  /* load linelist upfront, to not having to do that in each thread */
512  cpl_table *linelist = muse_table_load(aProcessing, MUSE_TAG_LINE_CATALOG, 0);
513  cpl_propertylist *linehead = muse_propertylist_load(aProcessing,
514  MUSE_TAG_LINE_CATALOG);
515  cpl_boolean listvalid = muse_wave_lines_check(linelist, linehead);
516  cpl_propertylist_delete(linehead);
517  cpl_vector *vlines = muse_geo_lines_get(linelist); /* suitable lines */
518  cpl_table_delete(linelist);
519  if (!listvalid || !vlines) {
520  cpl_msg_error(__func__, "%s could not be loaded/verified or enough suitable"
521  "lines are missing", MUSE_TAG_LINE_CATALOG);
522  cpl_vector_delete(vlines);
523  return -1;
524  }
525 
526  cpl_msg_info(__func__, "Analyzing IFUs %d to %d", aParams->ifu1, aParams->ifu2);
527  cpl_error_code rc[kMuseNumIFUs];
528  cpl_table *mspots[kMuseNumIFUs],
529  *trace[kMuseNumIFUs],
530  *wavecal[kMuseNumIFUs];
531  cpl_propertylist *headfinal = NULL;
532  cpl_array *dy = cpl_array_new(0, CPL_TYPE_DOUBLE);
533  unsigned int nifu;
534  #pragma omp parallel for default(none) /* as req. by Ralf */ \
535  shared(aParams, aProcessing, centroid, dy, headfinal, mspots, rc, \
536  trace, vlines, wavecal)
537  for (nifu = (unsigned)aParams->ifu1; nifu <= (unsigned)aParams->ifu2; nifu++) {
538  rc[nifu - 1] = CPL_ERROR_NONE; /* signify success for this thread by default */
539  mspots[nifu - 1] = NULL;
540 
541  /* load and check trace and wavecal tables early-on, *
542  * to be able to quickly return on error */
543  trace[nifu - 1] = muse_table_load(aProcessing, MUSE_TAG_TRACE_TABLE, nifu);
544  wavecal[nifu - 1] = muse_table_load(aProcessing, MUSE_TAG_WAVECAL_TABLE, nifu);
545  if (!trace[nifu - 1] || !wavecal[nifu - 1]) {
546  cpl_msg_error(__func__, "Calibration could not be loaded for IFU %hu: %s%s",
547  nifu, !trace[nifu - 1] ? " "MUSE_TAG_TRACE_TABLE : "",
548  !wavecal[nifu - 1] ? " "MUSE_TAG_WAVECAL_TABLE : "");
549  cpl_table_delete(trace[nifu - 1]);
550  cpl_table_delete(wavecal[nifu - 1]);
551  trace[nifu - 1] = NULL;
552  wavecal[nifu - 1] = NULL;
553  rc[nifu - 1] = CPL_ERROR_NULL_INPUT;
554  continue;
555  }
556 
557  /* load in separate function to allow for the lengthy *
558  * "skip" option handling from the environment */
559  muse_imagelist *images = muse_geometry_load_images(aProcessing, nifu);
560  if (!images) {
561  cpl_msg_error(__func__, "Loading and basic processing of the raw input "
562  "images failed for IFU %hu!", nifu);
563  cpl_table_delete(trace[nifu - 1]);
564  cpl_table_delete(wavecal[nifu - 1]);
565  trace[nifu - 1] = NULL;
566  wavecal[nifu - 1] = NULL;
567  rc[nifu - 1] = cpl_error_get_code();
568  continue;
569  }
570 
571  unsigned int skip = 0;
572  if (getenv("MUSE_GEOMETRY_SKIP") && atoi(getenv("MUSE_GEOMETRY_SKIP")) > 0) {
573  skip = atoi(getenv("MUSE_GEOMETRY_SKIP"));
574  }
575  cpl_propertylist *header;
576  if (skip >= 2) { /* directly skip to existing spots table */
577  char *fn = cpl_sprintf("SPOTS_TABLE-%02hu.fits", nifu);
578  cpl_msg_warning(__func__, "Reading spot measurements from \"%s\"", fn);
579  mspots[nifu - 1] = cpl_table_load(fn, 1, 1);
580  cpl_free(fn);
581  } else {
583  cpl_propertylist_append(av->header, muse_imagelist_get(images, 0)->header);
584  muse_processing_save_image(aProcessing, nifu, av, MUSE_TAG_MASK_COMBINED);
585  muse_image_reject_from_dq(av); /* make sure to recognize bad pixels */
586  mspots[nifu - 1] = muse_geo_measure_spots(av, images, trace[nifu - 1],
587  wavecal[nifu - 1], vlines,
588  aParams->sigma, centroid);
589  muse_image_delete(av);
590  /* now save this intermediate result */
591  header = cpl_propertylist_duplicate(muse_imagelist_get(images, 0)->header);
592  /* does this QC really belong into the spots table, or should that *
593  * just be for debugging and the QC go into the geometry table? */
594  muse_geometry_qc_spots(mspots[nifu - 1], header);
595  muse_processing_save_table(aProcessing, nifu, mspots[nifu - 1], header,
596  MUSE_TAG_SPOTS_TABLE, MUSE_TABLE_TYPE_CPL);
597  cpl_propertylist_delete(header);
598  } /* else: measure all spots */
599  cpl_msg_info(__func__, "measured %"CPL_SIZE_FORMAT" spots in %u images",
600  cpl_table_get_nrow(mspots[nifu - 1]), muse_imagelist_get_size(images));
601  cpl_table_delete(wavecal[nifu - 1]);
602 
603  if (!skip) { /* we started from the raw images */
604  /* save reduced images after the measurement because while setting up *
605  * the output header we delete the MUSE_HDR_TMP_FN keyword from the image */
606  unsigned int k;
607  for (k = 0; k < muse_imagelist_get_size(images); k++) {
608  muse_image *image = muse_imagelist_get(images, k);
609  muse_processing_save_image(aProcessing, nifu, image,
610  MUSE_TAG_MASK_REDUCED);
611  } /* for k (all images) */
612  } /* if !skip */
613  #pragma omp critical (muse_geo_header_construct)
614  {
615  if (!headfinal) {
616  headfinal = cpl_propertylist_duplicate(muse_imagelist_get(images, 0)->header);
617  /* Remove some properties that we don't need in the output file. *
618  * They refer to the specific extension and should not be *
619  * present in the output file that is global to the instrument. *
620  * Some may be put back from the first raw input file by *
621  * cpl_dfs_setup_product_header(), but at least try... */
622  cpl_propertylist_erase_regexp(headfinal,
623  "EXTNAME|ESO DET (CHIP|OUT)|ESO DET2", 0);
624  } /* if */
625  }
626  muse_imagelist_delete(images);
627  /* compute the effective vertical pinhole distance per IFU */
628  muse_geo_compute_pinhole_local_distance(dy, mspots[nifu - 1]);
629  } /* for nifu (all IFUs) */
630  /* now use the full array for the global statistics to get a final pinhole distance */
631  double pinholedy = muse_geo_compute_pinhole_global_distance(dy, 0.001, 0.5, 0.8);
632  /* XXX return the final value, but don't do anything with it, since for *
633  * the moment it is already set in the environment (in the function) */
634  UNUSED_ARGUMENT(pinholedy);
635  cpl_array_delete(dy);
636 
637  /* variables for combined output tables */
638  cpl_table *spots = NULL;
639  muse_geo_table *geotable = NULL;
640  #pragma omp parallel for default(none) /* as req. by Ralf */ \
641  shared(aParams, geotable, headfinal, mspots, spots, trace, vlines)
642  for (nifu = (unsigned)aParams->ifu1; nifu <= (unsigned)aParams->ifu2; nifu++) {
643  /* finally compute the geometry, initial and horizontal parts for one IFU */
644  muse_geo_table *geoinit = muse_geo_determine_initial(mspots[nifu - 1], trace[nifu - 1]);
645  muse_geo_table *geohori = muse_geo_determine_horizontal(geoinit);
646  cpl_table_delete(trace[nifu - 1]);
647  /* add QC parameters and save the result, here, the initial geometry is needed */
648  #pragma omp critical (muse_geo_header_construct)
649  muse_geometry_qc_ifu(geoinit, headfinal, vlines);
650  muse_geo_table_delete(geoinit);
651 
652  /* prepare the combined tables (geometry and spots) */
653  #pragma omp critical (muse_geo_table_insert)
654  {
655  if (!geotable) {
656  geotable = geohori;
657  } else {
658  if (fabs(geotable->scale - geohori->scale) > DBL_EPSILON) {
659  cpl_msg_warning(__func__, "Combined and single geometry tables have "
660  "different scales (%f / %f)!", geotable->scale,
661  geohori->scale);
662  }
663  cpl_table_insert(geotable->table, geohori->table,
664  cpl_table_get_nrow(geotable->table));
665  muse_geo_table_delete(geohori);
666  }
667  }
668  #pragma omp critical (muse_spots_table_insert)
669  {
670  if (!spots) {
671  spots = mspots[nifu - 1];
672  } else {
673  cpl_table_insert(spots, mspots[nifu - 1], cpl_table_get_nrow(spots));
674  cpl_table_delete(mspots[nifu - 1]);
675  }
676  }
677  } /* for nifu (all IFUs) */
678  cpl_vector_delete(vlines);
679  cpl_error_code result = CPL_ERROR_NONE;
680  /* non-parallel loop to check for return codes */
681  for (nifu = (unsigned)aParams->ifu1; nifu <= (unsigned)aParams->ifu2; nifu++) {
682  if (result < rc[nifu - 1]) {
683  result = rc[nifu - 1];
684  } /* if */
685  } /* for nifu (all IFUs) */
686 #if 0
687  cpl_table_save(geotable, NULL, NULL, "geocombined.fits", CPL_IO_CREATE);
688  cpl_table_save(spots, NULL, NULL, "spotscombined.fits", CPL_IO_CREATE);
689 #endif
690  muse_geo_refine_horizontal(geotable, spots);
691  cpl_table_delete(spots);
692 
693  muse_geo_table *geofinal = muse_geo_determine_vertical(geotable);
694  muse_geo_table_delete(geotable);
695  cpl_error_code rcfinal = muse_geo_finalize(geofinal);
696  if (result < rcfinal) {
697  result = rcfinal;
698  }
699  muse_geometry_qc_global(geofinal, headfinal);
700  muse_processing_save_table(aProcessing, -1, geofinal->table, headfinal,
701  MUSE_TAG_GEOMETRY_TABLE, MUSE_TABLE_TYPE_CPL);
702  /* first, try to reconstruct the (average-)combined images */
703  muse_geometry_reconstruct_combined(aProcessing, aParams, geofinal,
704  aParams->lambdamin, aParams->lambdamax);
705  /* try to reconstruct some images using this final geometry table, if *
706  * no MASK_CHECK files were passed to this recipe, this will do nothing */
707  muse_geometry_reconstruct(aProcessing, aParams, geofinal,
708  aParams->lambdamin, aParams->lambdamax);
709  muse_geo_table_delete(geofinal);
710  cpl_propertylist_delete(headfinal);
711 
712  return result != CPL_ERROR_NONE ? -1 : 0;
713 } /* 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:48
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:2357
void muse_image_delete(muse_image *aImage)
Deallocate memory associated to a muse_image object.
Definition: muse_image.c:84
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:115
cpl_size muse_pixtable_get_nrow(muse_pixtable *aPixtable)
get the number of rows within the pixel table
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:286
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:577
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:1105
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:2733
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:41
cpl_array * recnames
the reconstructed image filter names
Definition: muse_datacube.h:71
cpl_table * table
The pixel table.
cpl_propertylist * header
the FITS header
Definition: muse_image.h:73
muse_image * muse_combine_average_create(muse_imagelist *aImages)
Average a list of input images.
Definition: muse_combine.c:235
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:731
unsigned int muse_imagelist_get_size(muse_imagelist *aList)
Return the number of stored images.
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:1926
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:1009
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:456
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_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:3137
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_frameset * outputFrames
cpl_imagelist * data
the cube containing the actual data values
Definition: muse_datacube.h:76
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:1230
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:222
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:848
muse_imagelist * muse_imagelist_new(void)
Create a new (empty) MUSE image list.
int ifu1
First IFU to analyze.
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:452
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.
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:520
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:795
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:154
cpl_vector * muse_geo_lines_get(const cpl_table *aLines)
Select lines suitable for geometrical calibration from a line list.
Definition: muse_geo.c:352
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:256
cpl_frameset * inputFrames
cpl_frameset * usedFrames
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:1306
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:425
muse_imagelist * recimages
the reconstructed image data
Definition: muse_datacube.h:64
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.