MUSE Pipeline Reference Manual  2.1.1
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-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 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 /*---------------------------------------------------------------------------*/
48 /*---------------------------------------------------------------------------*/
49 static void
50 muse_geometry_qc_spots(cpl_table *aSpots, cpl_propertylist *aHeader)
51 {
52  if (!aSpots || !aHeader) {
53  return;
54  }
55 
56  unsigned int nexp,
57  nexp1 = cpl_table_get_column_min(aSpots, "image"),
58  nexp2 = cpl_table_get_column_max(aSpots, "image");
59  cpl_msg_debug(__func__, "Adding QC keywords for %u exposures to %s",
60  nexp2 - nexp1 + 1, MUSE_TAG_SPOTS_TABLE);
61 
62  cpl_array *amean = cpl_array_new(nexp2 - nexp1 + 1, CPL_TYPE_DOUBLE);
63  for (nexp = nexp1; nexp <= nexp2; nexp++) {
64  cpl_table_unselect_all(aSpots);
65  cpl_table_or_selected_int(aSpots, "image", CPL_EQUAL_TO, nexp);
66  cpl_table_and_selected_double(aSpots, "xfwhm", CPL_GREATER_THAN, 0.);
67  cpl_table_and_selected_double(aSpots, "yfwhm", CPL_GREATER_THAN, 0.);
68  cpl_table_and_selected_double(aSpots, "flux", CPL_GREATER_THAN, 1000.);
69  cpl_table *table = cpl_table_extract_selected(aSpots);
70  /* compute the average of the x and y FWHM values */
71  cpl_table_duplicate_column(table, "fwhm", table, "xfwhm");
72  cpl_table_add_columns(table, "fwhm", "yfwhm");
73  cpl_table_multiply_scalar(table, "fwhm", 0.5);
74  double mean = cpl_table_get_column_mean(table, "fwhm"),
75  median = cpl_table_get_column_median(table, "fwhm"),
76  stdev = cpl_table_get_column_stdev(table, "fwhm");
77  cpl_table_delete(table);
78 
79  char keyword[KEYWORD_LENGTH];
80  snprintf(keyword, KEYWORD_LENGTH, QC_GEO_EXPk_MEAN, nexp);
81  cpl_propertylist_update_float(aHeader, keyword, mean);
82  snprintf(keyword, KEYWORD_LENGTH, QC_GEO_EXPk_MEDIAN, nexp);
83  cpl_propertylist_update_float(aHeader, keyword, median);
84  snprintf(keyword, KEYWORD_LENGTH, QC_GEO_EXPk_STDEV, nexp);
85  cpl_propertylist_update_float(aHeader, keyword, stdev);
86 
87  cpl_array_set_double(amean, nexp - nexp1, mean);
88  } /* for nexp */
89  cpl_propertylist_update_float(aHeader, QC_GEO_FWHM_MEAN,
90  cpl_array_get_mean(amean));
91  cpl_propertylist_update_float(aHeader, QC_GEO_FWHM_STDEV,
92  cpl_array_get_stdev(amean));
93  cpl_array_delete(amean);
94 } /* muse_geometry_qc_spots() */
95 
96 /*---------------------------------------------------------------------------*/
107 /*---------------------------------------------------------------------------*/
108 static void
109 muse_geometry_qc_ifu(muse_geo_table *aGeoInit, cpl_propertylist *aHeader,
110  const cpl_vector *aLambdas)
111 {
112  if (!aGeoInit || !aHeader || !aLambdas) {
113  return;
114  }
115  cpl_table *gtinit = aGeoInit->table;
116  const unsigned char ifu = cpl_table_get_int(gtinit, MUSE_GEOTABLE_FIELD, 0, NULL);
117  /* normally flip the angles here */
118  double angle = -cpl_table_get_column_mean(gtinit, MUSE_GEOTABLE_ANGLE),
119  astdev = cpl_table_get_column_stdev(gtinit, MUSE_GEOTABLE_ANGLE),
120  amedian = -cpl_table_get_column_median(gtinit, MUSE_GEOTABLE_ANGLE);
121  /* if the geometry table later is /not/ flipped, flip mean/median back */
122  if (getenv("MUSE_GEOMETRY_NO_INVERSION")
123  && atoi(getenv("MUSE_GEOMETRY_NO_INVERSION")) > 0) {
124  angle *= -1.;
125  amedian *= -1.;
126  }
127  cpl_msg_debug(__func__, "Adding QC keywords for IFU %hhu: angle = %.3f +/- %.3f "
128  "(%.3f) deg", ifu, angle, astdev, amedian);
129  char *keyword = cpl_sprintf(QC_GEO_IFUi_ANGLE, ifu);
130  cpl_propertylist_update_float(aHeader, keyword, amedian);
131  cpl_free(keyword);
132 
133  int i, n = cpl_vector_get_size(aLambdas);
134  for (i = 1; i <= n; i++) {
135  double lambda = cpl_vector_get(aLambdas, i - 1);
136  char *kw = cpl_sprintf(QC_GEO_IFUi_WLENj, ifu, i),
137  *kwmean = cpl_sprintf(QC_GEO_IFUi_MEANj, ifu, i),
138  *kwmedi = cpl_sprintf(QC_GEO_IFUi_MEDIANj, ifu, i),
139  *kwstdv = cpl_sprintf(QC_GEO_IFUi_STDEVj, ifu, i);
140  cpl_propertylist_update_float(aHeader, kw, lambda);
141 
142  cpl_table_unselect_all(gtinit);
143  cpl_table_or_selected_double(gtinit, "lambda", CPL_EQUAL_TO, lambda);
144  if (cpl_table_count_selected(gtinit) < 1) { /* fill in dummy values */
145  cpl_propertylist_update_float(aHeader, kwmean, i);
146  cpl_propertylist_update_float(aHeader, kwmedi, i);
147  cpl_propertylist_update_float(aHeader, kwstdv, i);
148  } else {
149  cpl_table *t = cpl_table_extract_selected(gtinit);
150  cpl_propertylist_update_float(aHeader, kwmean,
151  cpl_table_get_column_mean(t, "flux"));
152  cpl_propertylist_update_float(aHeader, kwmedi,
153  cpl_table_get_column_median(t, "flux"));
154  cpl_propertylist_update_float(aHeader, kwstdv,
155  cpl_table_get_column_stdev(t, "flux"));
156  cpl_table_delete(t);
157  }
158  cpl_free(kw);
159  cpl_free(kwmean);
160  cpl_free(kwmedi);
161  cpl_free(kwstdv);
162  } /* for i (all lambdas) */
163 } /* muse_geometry_qc_ifu() */
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);
189  cpl_frame_delete(fbias);
191  cpl_propertylist_delete(hbias);
192  images = muse_basicproc_load(aProcessing, aIFU, bpars);
194  } else { /* skipping some part of the loading/processing */
195  images = muse_imagelist_new();
196  /* filenames are "MASK_REDUCED_00ii-jj.fits" */
197  unsigned int ifile, nfiles = 99; /* first assume that we will read many files */
198  if (skip >= 2) {
199  cpl_msg_warning(__func__, "Skipping spot measurements, only loading first"
200  " (reduced) file for IFU %02hu", aIFU);
201  nfiles = 1;
202  } else {
203  cpl_msg_warning(__func__, "Skipping raw image processing, loading all "
204  "reduced images directly");
205  }
206  for (ifile = 0; ifile < nfiles; ifile++) {
207  char *fn = cpl_sprintf("MASK_REDUCED_00%02u-%02hu.fits", ifile + 1, aIFU);
208  cpl_errorstate es = cpl_errorstate_get();
209  muse_image *image = muse_image_load(fn);
210  if (!image) {
211  cpl_errorstate_set(es); /* ignore the errors */
212  cpl_free(fn);
213  break;
214  }
215  cpl_frame *frame = cpl_frame_new();
216  cpl_frame_set_filename(frame, fn);
217  cpl_frame_set_tag(frame, "MASK");
218  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_RAW, 0);
219  cpl_msg_debug(__func__, "file = %s", fn);
220  muse_imagelist_set(images, image, ifile);
221  cpl_free(fn);
222  } /* for ifile */
223  cpl_msg_debug(__func__, "Read %u file%s", ifile, ifile == 1 ? "" : "s");
224  } /* else (skipped some loading/processing) */
225  return images;
226 } /* muse_geometry_load_images() */
227 
228 /*----------------------------------------------------------------------------*/
251 /*----------------------------------------------------------------------------*/
252 static cpl_error_code
253 muse_geometry_reconstruct_combined(muse_processing *aProcessing,
254  muse_geometry_params_t *aParams,
255  muse_geo_table *aGeo,
256  double aLMin, double aLMax)
257 {
258  cpl_ensure_code(aProcessing && aParams && aGeo && aGeo->table,
259  CPL_ERROR_NULL_INPUT);
260  if (aLMin >= aLMax) {
261  cpl_msg_warning(__func__, "Invalid wavelength range for reconstruction "
262  "(%.2f..%.2f), skipping combined reconstruction!", aLMin, aLMax);
263  return CPL_ERROR_ILLEGAL_INPUT;
264  }
265  cpl_msg_info(__func__, "Reconstructing combined input as cube in the "
266  "wavelength range %.2f..%.2f.", aLMin, aLMax);
267  unsigned int skip = 0;
268  if (getenv("MUSE_GEOMETRY_SKIP") && atoi(getenv("MUSE_GEOMETRY_SKIP")) > 0) {
269  skip = atoi(getenv("MUSE_GEOMETRY_SKIP"));
270  }
271  cpl_table *gt = aGeo->table;
272 
273  muse_pixtable **pts = cpl_calloc(kMuseNumIFUs, sizeof(muse_pixtable));
274  unsigned char nifu;
275  #pragma omp parallel for default(none) /* as req. by Ralf */ \
276  shared(gt, aParams, aProcessing, pts, skip)
277  for (nifu = (unsigned)aParams->ifu1; nifu <= (unsigned)aParams->ifu2; nifu++) {
278  cpl_table *trace = muse_processing_load_ctable(aProcessing, MUSE_TAG_TRACE_TABLE, nifu),
279  *wavecal = muse_processing_load_ctable(aProcessing, MUSE_TAG_WAVECAL_TABLE, nifu);
280  if (!trace || !wavecal) {
281  cpl_table_delete(trace);
282  cpl_table_delete(wavecal);
283  continue; /* just skip this IFU */
284  }
285  /* now load again the file we saved previously */
286  cpl_frame *cframe = NULL;
287  if (skip == 2) { /* we are running with MUSE_GEOMETRY_SKIP=2 */
288  /* construct frame manually, assuming a local file */
289  cframe = cpl_frame_new();
290  char *fn = cpl_sprintf("MASK_COMBINED-%02hhu.fits", nifu);
291  cpl_frame_set_filename(cframe, fn);
292  cpl_free(fn);
293  } else {
294  cframe = muse_frameset_find_master(aProcessing->outframes,
295  MUSE_TAG_MASK_COMBINED, nifu);
296  }
297  if (!cframe) {
298  cpl_table_delete(trace);
299  cpl_table_delete(wavecal);
300  continue;
301  }
302  cpl_msg_debug(__func__, "reconstructing IFU %2hhu using \"%s\"", nifu,
303  cpl_frame_get_filename(cframe));
304  muse_image *combined = muse_image_load(cpl_frame_get_filename(cframe));
305  cpl_frame_delete(cframe);
306  pts[nifu - 1] = muse_pixtable_create(combined, trace, wavecal, gt);
307  cpl_table_delete(trace);
308  cpl_table_delete(wavecal);
309  muse_image_delete(combined);
310  if (!pts[nifu - 1]) {
311  cpl_msg_warning(__func__, "Could not create a pixel table for reconstruction"
312  " for IFU %2hhu!", nifu++);
313  }
314  } /* for nifu (all IFUs) */
315 
316  /* merge all pixel tables *
317  * a la muse_pixtable_load_merge_channels() but without flux correction */
318  nifu = aParams->ifu1;
319  muse_pixtable *pt = pts[nifu - 1];
320  pts[nifu - 1] = NULL; /* to not free it too early */
321  while (!pt && nifu <= aParams->ifu2) { /* in case we picked a non-existing table */
322  nifu++;
323  pt = pts[nifu - 1];
324  pts[nifu - 1] = NULL; /* to not free it too early */
325  } /* while */
326  if (pt) {
327  for ( ; nifu <= (unsigned)aParams->ifu2; nifu++) {
328  if (pts[nifu - 1]) {
329  cpl_table_insert(pt->table, pts[nifu - 1]->table, muse_pixtable_get_nrow(pt));
330  muse_pixtable_delete(pts[nifu - 1]);
331  } /* if */
332  } /* for nifu (all IFUs) */
333  } /* if pt */
334  cpl_free(pts);
335 
336  /* cut the cube, for faster reconstruction speed */
337  muse_pixtable_restrict_wavelength(pt, aLMin, aLMax);
338  if (muse_pixtable_get_nrow(pt) < 1) { /* will also cause return for NULL pt */
340  return cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_OUTPUT,
341  "After cutting to %.2f..%.2f in wavelength no "
342  "pixels for image reconstruction are left!",
343  aLMin, aLMax);
344  }
345 
346  /* now just reconstruct the cube and collapse over the full range */
348  rp->crsigma = -1.; /* no cr cleaning */
349  muse_datacube *cube = muse_resampling_cube(pt, rp, NULL);
352  /* also create a corresponding white-light imgae */
353  muse_image *white = muse_datacube_collapse(cube, NULL);
354  cube->recimages = muse_imagelist_new();
355  cube->recnames = cpl_array_new(1, CPL_TYPE_STRING);
356  muse_imagelist_set(cube->recimages, white, 0);
357  cpl_array_set_string(cube->recnames, 0, "white");
358  muse_processing_save_cube(aProcessing, -1, cube, "GEOMETRY_CUBE",
360  muse_datacube_delete(cube);
361 
362  return CPL_ERROR_NONE;
363 } /* muse_geometry_reconstruct_combined() */
364 
365 /*----------------------------------------------------------------------------*/
391 /*----------------------------------------------------------------------------*/
392 static cpl_error_code
393 muse_geometry_reconstruct(muse_processing *aProcessing,
394  muse_geometry_params_t *aParams,
395  muse_geo_table *aGeo, double aLMin, double aLMax)
396 {
397  cpl_ensure_code(aProcessing && aParams && aGeo && aGeo->table,
398  CPL_ERROR_NULL_INPUT);
399  if (aLMin >= aLMax) {
400  cpl_msg_warning(__func__, "Invalid wavelength range for reconstruction "
401  "(%.2f..%.2f), skipping reconstruction!", aLMin, aLMax);
402  return CPL_ERROR_ILLEGAL_INPUT;
403  }
404 
405  /* convert all existing raw frames in the used frames to *
406  * CPL_FRAME_GROUP_CALIB so that the dependency of the GEOMETRY_CHECK *
407  * output is clear, but keep the original usedframes around */
408  cpl_frameset *usedoriginal = cpl_frameset_duplicate(aProcessing->usedframes);
409  int iframe, nframes = cpl_frameset_get_size(aProcessing->usedframes);
410  for (iframe = 0; iframe < nframes; iframe++) {
411  cpl_frame *frame = cpl_frameset_get_position(aProcessing->usedframes, iframe);
412  if (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_RAW) {
413  cpl_frame_set_group(frame, CPL_FRAME_GROUP_CALIB);
414  } /* if */
415  } /* for iframe */
416 
417  cpl_table *gt = aGeo->table;
418  cpl_frameset *frames = muse_frameset_find(aProcessing->inframes,
419  "MASK_CHECK", 0, CPL_FALSE);
420  nframes = cpl_frameset_get_size(frames);
421  cpl_msg_info(__func__, "Found %d datasets to reconstruct", nframes);
422  for (iframe = 0; iframe < nframes; iframe++) {
423  cpl_frame *frame = cpl_frameset_get_position(frames, iframe);
424  const char *fn = cpl_frame_get_filename(frame);
425  cpl_msg_info(__func__, "Reconstructing image %d (from \"%s\"), using "
426  "wavelength range %.2f..%.2f", iframe + 1, fn, aLMin, aLMax);
427  muse_pixtable **pts = cpl_calloc(kMuseNumIFUs, sizeof(muse_pixtable));
428  unsigned char nifu;
429  #pragma omp parallel for default(none) /* as req. by Ralf */ \
430  shared(aParams, aProcessing, fn, frames, gt, pts)
431  for (nifu = (unsigned)aParams->ifu1; nifu <= (unsigned)aParams->ifu2; nifu++) {
432  cpl_table *trace = muse_processing_load_ctable(aProcessing, MUSE_TAG_TRACE_TABLE, nifu),
433  *wavecal = muse_processing_load_ctable(aProcessing, MUSE_TAG_WAVECAL_TABLE, nifu);
434  if (!trace || !wavecal) {
435  cpl_table_delete(trace);
436  cpl_table_delete(wavecal);
437  continue; /* just skip this IFU */
438  }
439  cpl_frame *bframe = muse_frameset_find_master(aProcessing->inframes,
440  MUSE_TAG_MASTER_BIAS, nifu);
441  if (!bframe) {
442  cpl_table_delete(trace);
443  cpl_table_delete(wavecal);
444  continue;
445  }
446  const char *bname = cpl_frame_get_filename(bframe);
447  cpl_errorstate es = cpl_errorstate_get();
448  muse_image *bias = muse_image_load(bname);
449  if (!bias) {
450  cpl_errorstate_set(es); /* ignore error for case of merged file */
451  bias = muse_image_load_from_extensions(bname, nifu);
452  }
453  cpl_frame_delete(bframe);
454  /* don't want all the overhead of muse_basicproc_load(), *
455  * do part of its processing manually */
456  int ext = muse_utils_get_extension_for_ifu(fn, nifu);
457  muse_image *raw = muse_image_load_from_raw(fn, ext);
458  if (!raw) {
459  cpl_table_delete(trace);
460  cpl_table_delete(wavecal);
461  muse_image_delete(bias);
462  continue; /* just skip this IFU */
463  }
465  muse_quadrants_overscan_stats(raw, bpars ? bpars->rejection : "dcr", 0);
466  if (bpars && !strncmp(bpars->overscan, "vpoly", 5)) {
467  /* vertical polyfit requested, see if there are more parameters */
468  unsigned char ovscvorder = 3;
469  double frms = 1.01,
470  fchisq = 1.04;
471  char *rest = strchr(bpars->overscan, ':');
472  if (strlen(bpars->overscan) > 6 && rest++) { /* try to access info after "vpoly:" */
473  ovscvorder = strtol(rest, &rest, 10);
474  if (strlen(rest++) > 0) { /* ++ to skip over the comma */
475  frms = strtod(rest, &rest);
476  if (strlen(rest++) > 0) {
477  fchisq = strtod(rest, &rest);
478  }
479  }
480  } /* if */
482  ovscvorder, bpars->ovscsigma,
483  frms, fchisq);
484  } /* if bpars and vpoly */
486  muse_image_delete(raw);
487  muse_image_variance_create(image, bias);
488  if (bpars && !strncmp(bpars->overscan, "offset", 6)) {
489  muse_quadrants_overscan_correct(image, bias);
490  } /* if bpars and offset */
492  muse_image_subtract(image, bias);
493  muse_image_delete(bias);
495  pts[nifu - 1] = muse_pixtable_create(image, trace, wavecal, gt);
496  cpl_table_delete(trace);
497  cpl_table_delete(wavecal);
498  muse_image_delete(image);
499  if (!pts[nifu - 1]) {
500  cpl_msg_warning(__func__, "Could not create a pixel table for "
501  "reconstruction for IFU %2hhu!", nifu++);
502  }
503  } /* for nifu (all IFUs) */
504 
505  /* merge all pixel tables *
506  * a la muse_pixtable_load_merge_channels() but without flux correction */
507  nifu = aParams->ifu1;
508  muse_pixtable *pt = pts[nifu - 1];
509  pts[nifu - 1] = NULL; /* to not free it too early */
510  while (!pt && nifu <= aParams->ifu2) { /* in case we picked a non-existing table */
511  nifu++;
512  pt = pts[nifu - 1];
513  pts[nifu - 1] = NULL; /* to not free it too early */
514  } /* while */
515  if (pt) {
516  for ( ; nifu <= (unsigned)aParams->ifu2; nifu++) {
517  if (pts[nifu - 1]) {
518  cpl_table_insert(pt->table, pts[nifu - 1]->table, muse_pixtable_get_nrow(pt));
519  muse_pixtable_delete(pts[nifu - 1]);
520  } /* if */
521  } /* for nifu (all IFUs) */
522  } /* if pt */
523  cpl_free(pts);
524 
525  /* cut the cube, for faster reconstruction speed */
526  muse_pixtable_restrict_wavelength(pt, aLMin, aLMax);
527  if (muse_pixtable_get_nrow(pt) < 1) {
528  cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_OUTPUT,
529  "After cutting to %.2f..%.2f in wavelength no "
530  "pixels for image reconstruction of \"%s\" are "
531  "left!", aLMin, aLMax, fn);
533  continue;
534  }
535 
536  /* now just reconstruct the cube and collapse over the full range */
538  rp->crsigma = -1.; /* no cr cleaning */
539  /* 10000 Angstrom large pixels -> just one relevant plane! *
540  * (there will be a second but empty plane) */
541  rp->dlambda = 10000.;
542  muse_datacube *cube = muse_resampling_cube(pt, rp, NULL);
545  /* I don't want all the cube stuff in the header, better derive it *
546  * from the original primary header of the raw MASK_CHECK exposure. *
547  * Use a trick so that the MASK_CHECK files appears first in the used *
548  * frames and appear in the headers. */
549  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_RAW, 1);
550 #pragma omp critical(muse_processing_used_frames)
551  cpl_frameset_insert(usedoriginal, cpl_frame_duplicate(frame)); /* also add here */
552  cpl_propertylist *header = cpl_propertylist_load(fn, 0);
553  muse_processing_save_cimage(aProcessing, -1, cpl_imagelist_get(cube->data, 0),
554  header, "GEOMETRY_CHECK");
555  cpl_propertylist_delete(header);
556  muse_datacube_delete(cube);
557  } /* for iframe: all found MASK_CHECK frames */
558  cpl_frameset_delete(frames);
559  cpl_frameset_delete(aProcessing->usedframes);
560  aProcessing->usedframes = usedoriginal;
561 
562  return CPL_ERROR_NONE;
563 } /* muse_geometry_reconstruct() */
564 
565 /*----------------------------------------------------------------------------*/
572 /*----------------------------------------------------------------------------*/
573 int
574 muse_geometry_compute(muse_processing *aProcessing,
575  muse_geometry_params_t *aParams)
576 {
577  /* set up centroiding type */
579  if (aParams->centroid == MUSE_GEOMETRY_PARAM_CENTROID_GAUSSIAN) {
580  centroid = MUSE_GEO_CENTROID_GAUSSIAN;
581  } else if (aParams->centroid != MUSE_GEOMETRY_PARAM_CENTROID_BARYCENTER) {
582  cpl_msg_error(__func__, "unknown centroiding method \"%s\"", aParams->centroid_s);
583  return -1;
584  }
585 
586  /* load linelist upfront, to not having to do that in each thread */
587  muse_table *linelist = muse_processing_load_table(aProcessing,
588  MUSE_TAG_LINE_CATALOG, 0);
589  cpl_boolean listvalid = muse_wave_lines_check(linelist);
590  cpl_vector *vlines = muse_geo_lines_get(linelist->table); /* suitable lines */
591  muse_table_delete(linelist);
592  if (!listvalid || !vlines) {
593  cpl_msg_error(__func__, "%s could not be loaded/verified or enough suitable"
594  " lines are missing", MUSE_TAG_LINE_CATALOG);
595  cpl_vector_delete(vlines);
596  return -1;
597  }
598 
599  cpl_msg_info(__func__, "Analyzing IFUs %d to %d", aParams->ifu1, aParams->ifu2);
600  cpl_error_code rc[kMuseNumIFUs];
601  cpl_table *mspots[kMuseNumIFUs],
602  *trace[kMuseNumIFUs],
603  *wavecal[kMuseNumIFUs];
604  cpl_propertylist *headfinal = NULL;
605  cpl_array *dy = cpl_array_new(0, CPL_TYPE_DOUBLE);
606  unsigned int nifu;
607  #pragma omp parallel for default(none) /* as req. by Ralf */ \
608  shared(aParams, aProcessing, centroid, dy, headfinal, mspots, rc, \
609  trace, vlines, wavecal)
610  for (nifu = (unsigned)aParams->ifu1; nifu <= (unsigned)aParams->ifu2; nifu++) {
611  rc[nifu - 1] = CPL_ERROR_NONE; /* signify success for this thread by default */
612  mspots[nifu - 1] = NULL;
613 
614  /* load and check trace and wavecal tables early-on, *
615  * to be able to quickly return on error */
616  trace[nifu - 1] = muse_processing_load_ctable(aProcessing, MUSE_TAG_TRACE_TABLE, nifu);
617  wavecal[nifu - 1] = muse_processing_load_ctable(aProcessing, MUSE_TAG_WAVECAL_TABLE, nifu);
618  if (!trace[nifu - 1] || !wavecal[nifu - 1]) {
619  cpl_msg_error(__func__, "Calibration could not be loaded for IFU %u: %s%s",
620  nifu, !trace[nifu - 1] ? " "MUSE_TAG_TRACE_TABLE : "",
621  !wavecal[nifu - 1] ? " "MUSE_TAG_WAVECAL_TABLE : "");
622  cpl_table_delete(trace[nifu - 1]);
623  cpl_table_delete(wavecal[nifu - 1]);
624  trace[nifu - 1] = NULL;
625  wavecal[nifu - 1] = NULL;
626  rc[nifu - 1] = CPL_ERROR_NULL_INPUT;
627  continue;
628  }
629 
630  /* load in separate function to allow for the lengthy *
631  * "skip" option handling from the environment */
632  muse_imagelist *images = muse_geometry_load_images(aProcessing, nifu);
633  if (!images) {
634  cpl_msg_error(__func__, "Loading and basic processing of the raw input "
635  "images failed for IFU %u!", nifu);
636  cpl_table_delete(trace[nifu - 1]);
637  cpl_table_delete(wavecal[nifu - 1]);
638  trace[nifu - 1] = NULL;
639  wavecal[nifu - 1] = NULL;
640  rc[nifu - 1] = cpl_error_get_code();
641  continue;
642  }
643 
644  unsigned int skip = 0;
645  if (getenv("MUSE_GEOMETRY_SKIP") && atoi(getenv("MUSE_GEOMETRY_SKIP")) > 0) {
646  skip = atoi(getenv("MUSE_GEOMETRY_SKIP"));
647  }
648  cpl_propertylist *header;
649  if (skip >= 2) { /* directly skip to existing spots table */
650  char *fn = cpl_sprintf("SPOTS_TABLE-%02u.fits", nifu);
651  cpl_msg_warning(__func__, "Reading spot measurements from \"%s\"", fn);
652  mspots[nifu - 1] = cpl_table_load(fn, 1, 1);
653  cpl_free(fn);
654  } else {
656  cpl_propertylist_append(av->header, muse_imagelist_get(images, 0)->header);
657  muse_processing_save_image(aProcessing, nifu, av, MUSE_TAG_MASK_COMBINED);
658  muse_image_reject_from_dq(av); /* make sure to recognize bad pixels */
659  mspots[nifu - 1] = muse_geo_measure_spots(av, images, trace[nifu - 1],
660  wavecal[nifu - 1], vlines,
661  aParams->sigma, centroid);
662  muse_image_delete(av);
663  /* now save this intermediate result */
664  header = cpl_propertylist_duplicate(muse_imagelist_get(images, 0)->header);
665  /* does this QC really belong into the spots table, or should that *
666  * just be for debugging and the QC go into the geometry table? */
667  muse_geometry_qc_spots(mspots[nifu - 1], header);
668  muse_processing_save_table(aProcessing, nifu, mspots[nifu - 1], header,
669  MUSE_TAG_SPOTS_TABLE, MUSE_TABLE_TYPE_CPL);
670  cpl_propertylist_delete(header);
671  } /* else: measure all spots */
672  cpl_msg_info(__func__, "measured %"CPL_SIZE_FORMAT" spots in %u images",
673  cpl_table_get_nrow(mspots[nifu - 1]), muse_imagelist_get_size(images));
674  cpl_table_delete(wavecal[nifu - 1]);
675 
676  if (!skip) { /* we started from the raw images */
677  /* save reduced images after the measurement because while setting up *
678  * the output header we delete the MUSE_HDR_TMP_FN keyword from the image */
679  unsigned int k;
680  for (k = 0; k < muse_imagelist_get_size(images); k++) {
681  muse_image *image = muse_imagelist_get(images, k);
682  muse_processing_save_image(aProcessing, nifu, image,
683  MUSE_TAG_MASK_REDUCED);
684  } /* for k (all images) */
685  } /* if !skip */
686  #pragma omp critical (muse_geo_header_construct)
687  {
688  if (!headfinal) {
689  headfinal = cpl_propertylist_duplicate(muse_imagelist_get(images, 0)->header);
690  /* Remove some properties that we don't need in the output file. *
691  * They refer to the specific extension and should not be *
692  * present in the output file that is global to the instrument. *
693  * Some may be put back from the first raw input file by *
694  * cpl_dfs_setup_product_header(), but at least try... */
695  cpl_propertylist_erase_regexp(headfinal,
696  "EXTNAME|ESO DET (CHIP|OUT)|ESO DET2", 0);
697  } /* if */
698  }
699  muse_imagelist_delete(images);
700  /* compute the effective vertical pinhole distance per IFU */
701  muse_geo_compute_pinhole_local_distance(dy, mspots[nifu - 1]);
702  } /* for nifu (all IFUs) */
703  /* now use the full array for the global statistics to get a final pinhole distance */
704  double pinholedy = muse_geo_compute_pinhole_global_distance(dy, 0.001, 0.5, 0.8);
705  /* return the final value, but don't do anything with it, since for *
706  * the moment it is already set in the environment (in the function) */
707  UNUSED_ARGUMENT(pinholedy);
708  cpl_array_delete(dy);
709 
710  /* variables for combined output tables */
711  cpl_table *spots = NULL;
712  muse_geo_table *geotable = NULL;
713  #pragma omp parallel for default(none) /* as req. by Ralf */ \
714  shared(aParams, geotable, headfinal, mspots, spots, trace, vlines)
715  for (nifu = (unsigned)aParams->ifu1; nifu <= (unsigned)aParams->ifu2; nifu++) {
716  /* finally compute the geometry, initial and horizontal parts for one IFU */
717  muse_geo_table *geoinit = muse_geo_determine_initial(mspots[nifu - 1], trace[nifu - 1]);
718  muse_geo_table *geohori = muse_geo_determine_horizontal(geoinit);
719  cpl_table_delete(trace[nifu - 1]);
720  /* add QC parameters and save the result, here, the initial geometry is needed */
721  #pragma omp critical (muse_geo_header_construct)
722  muse_geometry_qc_ifu(geoinit, headfinal, vlines);
723  muse_geo_table_delete(geoinit);
724 
725  /* prepare the combined tables (geometry and spots) */
726  #pragma omp critical (muse_geo_table_insert)
727  {
728  if (!geotable) {
729  geotable = geohori;
730  } else {
731  if (fabs(geotable->scale - geohori->scale) > 1e-10) {
732  /* for some reason, the difference it typically 2.5e-13, but we *
733  * just want to ensure that both are about 1.7050 +/- 0.0001 or *
734  * so and for that an accuracy of 1e-10 is more than enough */
735  cpl_msg_warning(__func__, "Combined and single geometry tables have "
736  "different scales (%.15f / %.15f)!", geotable->scale,
737  geohori->scale);
738  }
739  cpl_table_insert(geotable->table, geohori->table,
740  cpl_table_get_nrow(geotable->table));
741  muse_geo_table_delete(geohori);
742  }
743  }
744  #pragma omp critical (muse_spots_table_insert)
745  {
746  if (!spots) {
747  spots = mspots[nifu - 1];
748  } else {
749  cpl_table_insert(spots, mspots[nifu - 1], cpl_table_get_nrow(spots));
750  cpl_table_delete(mspots[nifu - 1]);
751  }
752  }
753  } /* for nifu (all IFUs) */
754  cpl_vector_delete(vlines);
755  cpl_error_code result = CPL_ERROR_NONE;
756  /* non-parallel loop to check for return codes */
757  for (nifu = (unsigned)aParams->ifu1; nifu <= (unsigned)aParams->ifu2; nifu++) {
758  if (result < rc[nifu - 1]) {
759  result = rc[nifu - 1];
760  } /* if */
761  } /* for nifu (all IFUs) */
762 #if 0
763  cpl_table_save(geotable, NULL, NULL, "geocombined.fits", CPL_IO_CREATE);
764  cpl_table_save(spots, NULL, NULL, "spotscombined.fits", CPL_IO_CREATE);
765 #endif
766  muse_geo_refine_horizontal(geotable, spots);
767  cpl_table_delete(spots);
768 
769  muse_geo_table *geofinal = muse_geo_determine_vertical(geotable);
770  muse_geo_table_delete(geotable);
771  cpl_error_code rcfinal = muse_geo_finalize(geofinal);
772  if (result < rcfinal) {
773  result = rcfinal;
774  }
775  /* smooth the result per slicer stack, if sigma > 0. */
776  if (aParams->smooth > 0.) {
777  /* save this state already */
778  if (geofinal) {
779  muse_geo_qc_global(geofinal, headfinal);
780  muse_processing_save_table(aProcessing, -1, geofinal->table, headfinal,
781  MUSE_TAG_GEOMETRY_OLD, MUSE_TABLE_TYPE_CPL);
782  }
783  rcfinal = muse_geo_correct_slices(geofinal, headfinal, aParams->smooth);
784  if (result < rcfinal) {
785  result = rcfinal;
786  }
787  } else {
788  /* add fake entries to signify that this wasn't smoothed */
789  cpl_propertylist_update_int(headfinal, QC_GEO_SMOOTH_NX, -1);
790  cpl_propertylist_update_int(headfinal, QC_GEO_SMOOTH_NY, -1);
791  cpl_propertylist_update_int(headfinal, QC_GEO_SMOOTH_NA, -1);
792  cpl_propertylist_update_int(headfinal, QC_GEO_SMOOTH_NW, -1);
793  }
794  /* now this is the really final table that we want to save */
795  if (geofinal) {
796  /* compute (or update) the global QC parameters */
797  muse_geo_qc_global(geofinal, headfinal);
798  muse_processing_save_table(aProcessing, -1, geofinal->table, headfinal,
799  MUSE_TAG_GEOMETRY_TABLE, MUSE_TABLE_TYPE_CPL);
800  }
801  /* first, try to reconstruct the (average-)combined images */
802  muse_geometry_reconstruct_combined(aProcessing, aParams, geofinal,
803  aParams->lambdamin, aParams->lambdamax);
804  /* try to reconstruct some images using this final geometry table, if *
805  * no MASK_CHECK files were passed to this recipe, this will do nothing */
806  muse_geometry_reconstruct(aProcessing, aParams, geofinal,
807  aParams->lambdamin, aParams->lambdamax);
808  muse_geo_table_delete(geofinal);
809  cpl_propertylist_delete(headfinal);
810 
811  return result != CPL_ERROR_NONE ? -1 : 0;
812 } /* 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:2360
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:118
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:292
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.
cpl_error_code muse_geo_correct_slices(muse_geo_table *aGeo, cpl_propertylist *aHeader, double aSigma)
Correct deviant slices in an existing MUSE geometry table.
Definition: muse_geo.c:3364
double lambdamax
When passing any MASK_CHECK frames in the input, use this upper wavelength cut before reconstructing ...
muse_datacube * muse_resampling_cube(muse_pixtable *aPixtable, muse_resampling_params *aParams, muse_pixgrid **aGrid)
Resample a pixel table onto a regular grid structure representing a FITS NAXIS=3 datacube.
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:593
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:1112
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:2737
cpl_boolean muse_wave_lines_check(muse_table *aTable)
Check that a LINE_CATALOG has the expected format.
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:71
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:747
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:1929
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:1016
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.
cpl_error_code muse_geo_qc_global(const muse_geo_table *aGeoTable, cpl_propertylist *aHeader)
Add the global QC parameters to the geometry table.
Definition: muse_geo.c:3508
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:3142
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_table * table
The table.
Definition: muse_table.h:49
muse_table * muse_processing_load_table(muse_processing *aProcessing, const char *aTag, unsigned char aIFU)
Load a MUSE table according to its tag and IFU/channel number.
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.
Structure to store a table together with a property list.
Definition: muse_table.h:43
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_table_delete(muse_table *aTable)
Deallocate memory associated to a muse_table object.
Definition: muse_table.c:80
void muse_geo_table_delete(muse_geo_table *aGeo)
Deallocate memory associated to a geometry table object.
Definition: muse_geo.c:1237
int centroid
Type of centroiding and FWHM determination to use for all spot measurements: simple barycenter method...
double smooth
Use this sigma-level cut for smoothing of the output table within each slicer stack. Set to non-positive value to deactivate smoothing.
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.
cpl_table * muse_processing_load_ctable(muse_processing *aProcessing, const char *aTag, unsigned char aIFU)
Load a CPL table according to its tag and IFU/channel number.
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:228
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:863
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.
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.
muse_image * muse_datacube_collapse(muse_datacube *aCube, const muse_table *aFilter)
Integrate a FITS NAXIS=3 datacube along the wavelength direction.
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:811
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
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:262
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:1313
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:505
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.