MUSE Pipeline Reference Manual  2.1.1
muse_processing.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-2016 European Southern Observatory
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20  * 02110-1301, USA.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 /*----------------------------------------------------------------------------*
28  * Includes *
29  *----------------------------------------------------------------------------*/
30 #include <stdio.h>
31 #include <float.h>
32 #include <math.h>
33 #include <string.h>
34 #include <cpl.h>
35 
36 #include "muse_processing.h"
37 #include "muse_instrument.h"
38 
39 #include "muse_datacube.h"
40 #include "muse_dfs.h"
41 #include "muse_idp.h"
42 #include "muse_pfits.h"
43 #include "muse_lsf.h"
44 #include "muse_utils.h"
45 
46 /*----------------------------------------------------------------------------*
47  * Debugging Macros *
48  * Set these to 1 or higher for (lots of) debugging output *
49  *----------------------------------------------------------------------------*/
50 #define DEBUG_EXPOSURES_SORT 0 /* debug muse_processing_sort_exposures() */
51 
52 /*----------------------------------------------------------------------------*/
59 /*----------------------------------------------------------------------------*/
60 
63 /*----------------------------------------------------------------------------*
64  * Functions *
65  *----------------------------------------------------------------------------*/
66 
67 /*---------------------------------------------------------------------------*/
77 /*---------------------------------------------------------------------------*/
78 static cpl_array *
79 muse_processing_get_rawtags(cpl_recipe *aRecipe)
80 {
81  /* start with zero length array, that is returned on error */
82  cpl_array *rawtags = cpl_array_new(0, CPL_TYPE_STRING);
83  /* we need a recipe config for this to work */
84  cpl_recipeconfig *recipeconfig = muse_processing_get_recipeconfig(aRecipe);
85  if (!recipeconfig) {
86  cpl_msg_error(__func__, "No recipeconfig found!");
87  return rawtags;
88  }
89 
90  /* loop through all frames of the recipe (which at this point will be *
91  * just the input frames) and check if they are inputs, i.e. raw frames */
92  cpl_size iframe, nframes = cpl_frameset_get_size(aRecipe->frames);
93  for (iframe = 0; iframe < nframes; iframe++) {
94  cpl_frame *frame = cpl_frameset_get_position(aRecipe->frames, iframe);
95  const char *tag = cpl_frame_get_tag(frame); /* fiducial raw tag to check */
96  /* see if it's already in our list */
97  cpl_size itag, ntags = cpl_array_get_size(rawtags);
98  for (itag = 0; itag < ntags; itag++) {
99  if (!strcmp(cpl_array_get_string(rawtags, itag), tag)) {
100  tag = NULL; /* set to NULL to make cpl_recipeconfig_get_inputs() fail */
101  break;
102  } /* if match */
103  } /* for itag */
104  cpl_errorstate state = cpl_errorstate_get();
105  char **tags = cpl_recipeconfig_get_inputs(recipeconfig, tag);
106  if (!tags) { /* not a raw tag! */
107  cpl_errorstate_set(state); /* CPL will have set an error, swallow it */
108  continue;
109  } /* if */
110  /* we have a raw tag! record this tag in the output array of raw tags */
111  cpl_array_set_size(rawtags, ntags + 1);
112  cpl_array_set_string(rawtags, ntags, tag);
113  /* clean up the elements which we didn't actually need */
114  int i;
115  for (i = 0; tags[i]; i++) {
116  cpl_free(tags[i]);
117  }
118  cpl_free(tags);
119  } /* for iframe (all recipe frames) */
120  if (!cpl_array_get_size(rawtags)) {
121  cpl_msg_error(__func__, "No valid raw tag(s) found!");
122  }
123  return rawtags;
124 } /* muse_processing_get_rawtags() */
125 
126 /*---------------------------------------------------------------------------*/
133 /*---------------------------------------------------------------------------*/
135 muse_processing_new(const char *aName, cpl_recipe *aRecipe)
136 {
137  muse_processing *processing = cpl_malloc(sizeof(muse_processing));
138  processing->name = aName;
139  processing->recipe = aRecipe;
140  processing->inframes = cpl_frameset_duplicate(aRecipe->frames);
141  processing->usedframes = cpl_frameset_new();
142  processing->outframes = cpl_frameset_new();
143  #pragma omp critical(cpl_parameters)
144  processing->parameters = muse_cplparameterlist_duplicate(aRecipe->parameters);
145  processing->intags = muse_processing_get_rawtags(aRecipe);
146  processing->counter = cpl_malloc(sizeof(muse_processing_framecounter));
147  processing->counter[0].tag = NULL;
148  return processing;
149 } /* muse_processing_new() */
150 
151 /*---------------------------------------------------------------------------*/
156 /*---------------------------------------------------------------------------*/
157 void
159 {
160  if (!aProcessing) {
161  return;
162  }
163  cpl_array_delete(aProcessing->intags);
164  cpl_frameset_delete(aProcessing->inframes);
165  cpl_frameset_delete(aProcessing->usedframes);
166  cpl_frameset_delete(aProcessing->outframes);
167  cpl_parameterlist_delete(aProcessing->parameters);
168  int i;
169  for (i = 0; aProcessing->counter[i].tag != NULL; i++) {
170  cpl_free((char *)aProcessing->counter[i].tag);
171  }
172  cpl_free(aProcessing->counter);
173  cpl_free(aProcessing);
174 } /* muse_processing_delete() */
175 
176 /*---------------------------------------------------------------------------*/
192 /*---------------------------------------------------------------------------*/
193 void
195  cpl_frame *aFrame, cpl_frame_group aGroup,
196  int aDuplicate)
197 {
198  if (!aProcessing) {
199  cpl_msg_error(__func__, "NULL processing struct!");
200  return;
201  }
202 #if 0
203  if (aGroup == CPL_FRAME_GROUP_CALIB) {
204  cpl_msg_debug(__func__, "using %s %s frame %s", "calibration",
205  cpl_frame_get_tag(aFrame), cpl_frame_get_filename(aFrame));
206  } else if (aGroup == CPL_FRAME_GROUP_RAW) {
207  cpl_msg_debug(__func__, "using %s %s frame %s", "raw",
208  cpl_frame_get_tag(aFrame), cpl_frame_get_filename(aFrame));
209  }
210 #endif
211  /* check, if this frame is already listed *
212  * (this has to be locked by other threads) */
213  cpl_boolean doreturn = CPL_FALSE;
214 #pragma omp critical(muse_processing_used_frames)
215  {
216  const char *fn = cpl_frame_get_filename(aFrame),
217  *tag = cpl_frame_get_tag(aFrame);
218  cpl_size iframe, nframes = cpl_frameset_get_size(aProcessing->usedframes);
219  for (iframe = 0;
220  (iframe < nframes) && fn && tag; /* only check with valid info */
221  iframe++) {
222  cpl_frame *frame = cpl_frameset_get_position(aProcessing->usedframes, iframe);
223  const char *fn2 = cpl_frame_get_filename(frame),
224  *tag2 = cpl_frame_get_tag(frame);
225  if (fn2 && !strncmp(fn, fn2, strlen(fn) + 1) &&
226  tag2 && !strncmp(tag, tag2, strlen(tag) + 1)) {
227  if (!aDuplicate) { /* destroy frame, assuming it was duplicated elsewhere */
228  cpl_frame_delete(aFrame);
229  }
230  doreturn = CPL_TRUE; /* already listed, mark for early return */
231  break;
232  }
233  } /* for iframe (all usedframes) */
234  } /* end of pragma omp */
235 
236  if (doreturn) {
237  return; /* frame is already in the usedframes set, so return */
238  }
239  /* need to store this frame, so set a group */
240  cpl_frame_set_group(aFrame, aGroup);
241 
242  /* actual inserting the frame has to be guarded against other threads */
243 #pragma omp critical(muse_processing_used_frames)
244  {
245  if (aDuplicate) {
246  cpl_frameset_insert(aProcessing->usedframes, cpl_frame_duplicate(aFrame));
247  } else {
248  cpl_frameset_insert(aProcessing->usedframes, aFrame);
249  }
250  } /* end of pragma omp */
251 } /* muse_processing_append_used() */
252 
253 /*---------------------------------------------------------------------------*/
266 /*---------------------------------------------------------------------------*/
267 static int
268 muse_processing_get_framecounter(muse_processing *aProcessing,
269  const char *aTag, int aIFU)
270 {
271  if (!aProcessing) {
272  cpl_msg_error(__func__, "NULL processing struct!");
273  return 0;
274  }
275  int i;
276  for (i = 0; aProcessing->counter[i].tag != NULL; i++) {
277  if (strcmp(aProcessing->counter[i].tag, aTag) == 0 &&
278  aProcessing->counter[i].ifu == aIFU) {
279  return ++aProcessing->counter[i].counter;
280  }
281  }
282  aProcessing->counter = cpl_realloc(aProcessing->counter,
283  (i+2)*sizeof(muse_processing_framecounter));
284  aProcessing->counter[i].tag = cpl_strdup(aTag);
285  aProcessing->counter[i].ifu = aIFU;
286  aProcessing->counter[i].counter = 1;
287  aProcessing->counter[i+1].tag = NULL;
288  return 1;
289 }
290 
291 /*---------------------------------------------------------------------------*/
304 /*---------------------------------------------------------------------------*/
305 static void
306 muse_processing_setup_header(muse_processing *aProcessing,
307  cpl_propertylist *aHeader, cpl_frame *aFrame,
308  int aIndex, const char *aDateObs,
309  cpl_boolean aSequence)
310 {
311  if (!aProcessing) {
312  cpl_msg_error(__func__, "NULL processing struct!");
313  return;
314  }
315  if (!aProcessing->inframes ||
316  cpl_frameset_is_empty(aProcessing->inframes)) {
317  cpl_msg_warning(__func__, "No raw input files, no DFS product header added");
318  return;
319  }
320  /* save some headers which are be over overwritten by cpl_dfs, *
321  * including OBJECT and ESO.DRS.MUSE */
322  cpl_propertylist *hkeep = cpl_propertylist_new();
323  cpl_propertylist_copy_property_regexp(hkeep, aHeader,
324  MUSE_HEADERS_KEEP_REGEXP, 0);
325  /* don't want any DFS-generated ESO.PRO keywords before calling cpl_dfs *
326  * to create them, so delete all ESO.PRO stuff and COMMENTS */
327  cpl_propertylist_erase_regexp(aHeader, "^ESO PRO|^COMMENT", 0);
328  /* remove temporary stuff that is not meant to be saved to disk */
329  cpl_propertylist_erase_regexp(aHeader, MUSE_HDR_TMP_REGEXP, 0);
330 
331  /* make sure that the raw frame(s) are first in the list of used frames */
332  cpl_frameset *fset = muse_frameset_sort_raw_other(aProcessing->usedframes,
333  aIndex, aDateObs, aSequence);
334 
335 #if CPL_VERSION_CODE < CPL_VERSION(7, 0, 0)
336  /* workaround for the EQUINOX warning, replace with double-type keyword */
337  if (cpl_propertylist_has(aHeader, "EQUINOX") &&
338  cpl_propertylist_get_type(aHeader, "EQUINOX") < CPL_TYPE_FLOAT) {
339  cpl_property *equinox = cpl_propertylist_get_property(aHeader, "EQUINOX");
340  double equvalue = cpl_property_get_long_long(equinox);
341  const char *equcomment = cpl_property_get_comment(equinox);
342  cpl_property_set_name(equinox, "EQUIBRK");
343  cpl_propertylist_insert_after_double(aHeader, "EQUIBRK", "EQUINOX",
344  equvalue);
345  cpl_propertylist_set_comment(aHeader, "EQUINOX", equcomment);
346  cpl_propertylist_erase(aHeader, "EQUIBRK");
347  } /* if non-float EQUINOX */
348 #endif
349 
350  char *pkgstring = cpl_sprintf("%s/%s", PACKAGE, PACKAGE_VERSION);
351 
352 #pragma omp critical(muse_processing_dfs_setup_header)
353  if (cpl_dfs_setup_product_header(aHeader, aFrame, fset,
354  aProcessing->parameters, aProcessing->name,
355  pkgstring, MUSE_PRO_DID, NULL)
356  != CPL_ERROR_NONE) {
357  cpl_msg_error(__func__, "Could not add DFS product header: %s",
358  cpl_error_get_message());
359  }
360  cpl_free(pkgstring);
361  cpl_frameset_delete(fset);
362 
363  /* reset overwritten headers (if they were there before) */
364  int i, n = cpl_propertylist_get_size(hkeep);
365  for (i = 0; i < n; i++) {
366  const cpl_property *p = cpl_propertylist_get_const(hkeep, i);
367  cpl_propertylist_erase(aHeader, cpl_property_get_name(p));
368  cpl_propertylist_append_property(aHeader, p);
369  } /* for i (all properties in hkeep) */
370  cpl_propertylist_delete(hkeep);
371 
372  /* MUSE data is always taken with an IFU, so override PRO.TECH */
373  cpl_propertylist_update_string(aHeader, "ESO PRO TECH", "IFU");
374  /* add PRO.SCIENCE header for science recipes */
375  if (strstr(aProcessing->name, "muse_sci") ||
376  !strcmp(aProcessing->name, "muse_exp_combine")) {
377  cpl_propertylist_update_bool(aHeader, "ESO PRO SCIENCE", 1);
378  }
379 } /* muse_processing_setup_header() */
380 
381 /*---------------------------------------------------------------------------*/
404 /*---------------------------------------------------------------------------*/
405 cpl_frame *
407  cpl_propertylist *aHeader, const char *aTag,
408  cpl_frame_type aType)
409 {
410  cpl_ensure(aProcessing, CPL_ERROR_NULL_INPUT, NULL);
411  muse_processing_prepare_header(aProcessing->recipe, aTag, aHeader);
412  const char *prefix = cpl_propertylist_has(aHeader, "MUSE PRIVATE FILE PREFIX")
413  ? cpl_propertylist_get_string(aHeader,
414  "MUSE PRIVATE FILE PREFIX")
415  : aTag;
416 
417  cpl_frame *frame = cpl_frame_new();
418  /* some error from before might disturbe further error handling */
419  cpl_errorstate prestate = cpl_errorstate_get();
420 
421  /* see if we need to take all input frames, a specific frame based on its *
422  * number in the frameset, and/or a specific frame based on observing date */
423  int fmode = muse_processing_get_frame_mode(aProcessing->recipe, aTag),
424  framecounter = 0;
425  if (fmode != MUSE_FRAME_MODE_MASTER) {
426  framecounter = muse_processing_get_framecounter(aProcessing, prefix, aIFU);
427  }
428  const char *dateobs = NULL;
429  if (fmode == MUSE_FRAME_MODE_DATEOBS) {
430  dateobs = muse_pfits_get_dateobs(aHeader);
431  }
432  cpl_boolean sequence = fmode == MUSE_FRAME_MODE_SEQUENCE;
433 
434  /* Create product frame */
435  char filename[FILENAME_MAX];
436  if (aIFU >= 0) {
437  if (framecounter == 0) {
438  snprintf(filename, FILENAME_MAX, "%s-%02d.fits", prefix, aIFU);
439  } else {
440  snprintf(filename, FILENAME_MAX, "%s_%04d-%02d.fits", prefix,
441  framecounter, aIFU);
442  }
443  } else {
444  if (framecounter == 0) {
445  snprintf(filename, FILENAME_MAX, "%s.fits", prefix);
446  } else {
447  snprintf(filename, FILENAME_MAX, "%s_%04d.fits", prefix, framecounter);
448  }
449  }
450  cpl_frame_set_filename(frame, filename);
451  cpl_frame_set_tag(frame, aTag);
452  cpl_frame_set_type(frame, aType);
453  cpl_frame_set_group(frame, CPL_FRAME_GROUP_PRODUCT);
454  cpl_frame_set_level(frame,
456  aTag));
457  if (!cpl_errorstate_is_equal(prestate)) {
458  cpl_msg_error(__func__, "Error while initialising the product frame: %s",
459  cpl_error_get_message());
460  cpl_frame_delete(frame);
461  return NULL;
462  }
463  cpl_propertylist_erase_regexp(aHeader, "MUSE PRIVATE.*", 0);
464  /* select specific frame index to use */
465  int idx = dateobs || fmode == MUSE_FRAME_MODE_SUBSET ? -1 : framecounter - 1;
466  muse_processing_setup_header(aProcessing, aHeader, frame, idx, dateobs,
467  sequence);
468  return frame;
469 } /* muse_processing_new_frame() */
470 
471 /*---------------------------------------------------------------------------*/
487 /*---------------------------------------------------------------------------*/
488 int
490  muse_image *aImage, const char *aTag)
491 {
492  cpl_ensure_code(aProcessing && aImage && aTag, CPL_ERROR_NULL_INPUT);
493 
494  cpl_frame *frame = muse_processing_new_frame(aProcessing, aIFU,
495  aImage->header, aTag,
496  CPL_FRAME_TYPE_IMAGE);
497  cpl_msg_info(__func__, "Saving image as %s", cpl_frame_get_filename(frame));
498  int r = muse_image_save(aImage, cpl_frame_get_filename(frame));
499  if (r == CPL_ERROR_NONE) {
500 #pragma omp critical(muse_processing_output_frames)
501  cpl_frameset_insert(aProcessing->outframes, frame);
502  } else {
503  cpl_frame_delete(frame);
504  }
505  return r;
506 } /* muse_processing_save_image() */
507 
508 /*---------------------------------------------------------------------------*/
528 /*---------------------------------------------------------------------------*/
529 cpl_error_code
531  void *aCube, const char *aTag, muse_cube_type aType)
532 {
533  cpl_ensure_code(aProcessing && aCube && aTag, CPL_ERROR_NULL_INPUT);
534  cpl_ensure_code((aType == MUSE_CUBE_TYPE_EURO3D) ||
535  (aType == MUSE_CUBE_TYPE_FITS) ||
536  (aType == MUSE_CUBE_TYPE_SDP) ||
537  (aType == MUSE_CUBE_TYPE_LSF), CPL_ERROR_ILLEGAL_INPUT);
538 
539  /* both cube structures contain the header component as *
540  * first element, so it's safe to cast it to one of them */
541  cpl_frame *frame = muse_processing_new_frame(aProcessing, aIFU,
542  ((muse_datacube *)aCube)->header,
543  aTag, CPL_FRAME_TYPE_IMAGE);
544 
545  /* For science data cubes collect IDP properties from the raw data frames *
546  * and the data cube header, and update the data cube product header so *
547  * that they are saved in the next step. */
548  if (aType == MUSE_CUBE_TYPE_SDP) {
549  cpl_errorstate status = cpl_errorstate_get();
550  muse_idp_properties *properties =
551  muse_idp_properties_collect(aProcessing, (muse_datacube *)aCube, aTag);
552  if (properties) {
553  muse_idp_properties_update(((muse_datacube *)aCube)->header, properties);
554  }
555  muse_idp_properties_delete(properties);
556  if (!cpl_errorstate_is_equal(status)) {
557  cpl_frame_delete(frame);
558  return cpl_error_get_code();
559  }
560  }
561 
562  cpl_msg_info(__func__, "Saving %s cube as \"%s\"",
563  aType == MUSE_CUBE_TYPE_EURO3D ? "Euro3D" : "FITS",
564  cpl_frame_get_filename(frame));
565  cpl_error_code rc = CPL_ERROR_NONE;
566  if (aType == MUSE_CUBE_TYPE_EURO3D) {
568  cpl_frame_get_filename(frame));
569  } else if ((aType == MUSE_CUBE_TYPE_FITS) || (aType == MUSE_CUBE_TYPE_SDP)) {
570  rc = muse_datacube_save((muse_datacube *)aCube,
571  cpl_frame_get_filename(frame));
572  } else {
573  rc = muse_lsf_cube_save((muse_lsf_cube *)aCube,
574  cpl_frame_get_filename(frame));
575  }
576  if (rc == CPL_ERROR_NONE) {
577 #pragma omp critical(muse_processing_output_frames)
578  cpl_frameset_insert(aProcessing->outframes, frame);
579  } else {
580  cpl_frame_delete(frame);
581  }
582  return rc;
583 } /* muse_processing_save_cube() */
584 
585 /*---------------------------------------------------------------------------*/
604 /*---------------------------------------------------------------------------*/
605 cpl_table *
606 muse_processing_load_ctable(muse_processing *aProcessing, const char *aTag,
607  unsigned char aIFU)
608 {
609  cpl_ensure(aProcessing, CPL_ERROR_NULL_INPUT, NULL);
610 
611  /* load the table, with header */
612  muse_table *table = muse_processing_load_table(aProcessing, aTag, aIFU);
613  if (!table) {
614  return NULL;
615  }
616  /* strip the header part to return a "plain" CPL table */
617  cpl_table *ctable = table->table;
618  table->table = NULL;
619  muse_table_delete(table);
620 
621  return ctable;
622 } /* muse_processing_load_ctable() */
623 
624 /*---------------------------------------------------------------------------*/
642 /*---------------------------------------------------------------------------*/
643 muse_table *
644 muse_processing_load_table(muse_processing *aProcessing, const char *aTag,
645  unsigned char aIFU)
646 {
647  cpl_ensure(aProcessing, CPL_ERROR_NULL_INPUT, NULL);
648 
649  /* find the (first) table that matches the given tag */
650  cpl_frame *frame = muse_frameset_find_master(aProcessing->inframes, aTag,
651  aIFU);
652  if (!frame) {
653  if (aIFU > 0) {
654  cpl_msg_debug(__func__, "No table found for tag %s and IFU %hhu", aTag,
655  aIFU);
656  } else {
657  /* swallow the IFU part, if it's not a real IFU number */
658  cpl_msg_debug(__func__, "No table found for tag %s", aTag);
659  }
660  return NULL;
661  }
662 
663  /* try to load the extension table for the given IFU, then just return */
664  cpl_errorstate state = cpl_errorstate_get();
665  muse_table *table = muse_table_load(cpl_frame_get_filename(frame), aIFU);
666  if (!cpl_errorstate_is_equal(state)) {
667  cpl_error_set(__func__, cpl_error_get_code());
668  muse_table_delete(table);
669  return NULL;
670  }
671 
672  /* tables are calibrations of some sort, so append them as such into *
673  * the used frames for the FITS header of the output product, if they exist */
674  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_CALIB, 0);
675 
676  return table;
677 } /* muse_processing_load_table() */
678 
679 /*---------------------------------------------------------------------------*/
700 /*---------------------------------------------------------------------------*/
701 cpl_error_code
703  void *aTable, cpl_propertylist *aHeader,
704  const char *aTag, muse_table_type aType)
705 {
706  cpl_ensure_code(aProcessing && aTable && aTag, CPL_ERROR_NULL_INPUT);
707  cpl_ensure_code(aType <= MUSE_TABLE_TYPE_MUSE, CPL_ERROR_ILLEGAL_INPUT);
708 
709  /* for plain tables, the separate header is needed */
710  cpl_propertylist *header;
711  if (aType == MUSE_TABLE_TYPE_CPL) {
712  cpl_ensure_code(aHeader, CPL_ERROR_NULL_INPUT);
713  header = aHeader;
714  } else if (aType == MUSE_TABLE_TYPE_MUSE) {
715  header = ((muse_table *)aTable)->header;
716  } else {
717  header = ((muse_pixtable *)aTable)->header;
718  }
719  cpl_frame *frame = muse_processing_new_frame(aProcessing, aIFU, header,
720  aTag, CPL_FRAME_TYPE_TABLE);
721  cpl_msg_info(__func__, "Saving %stable as \"%s\"",
722  aType == MUSE_TABLE_TYPE_PIXTABLE ? "pixel " : "",
723  cpl_frame_get_filename(frame));
724  cpl_error_code rc = CPL_ERROR_NONE;
725  if (aType == MUSE_TABLE_TYPE_CPL) {
726  cpl_size nrow = cpl_table_get_nrow((cpl_table *)aTable);
727  rc = cpl_table_save((cpl_table *)aTable, aHeader, NULL,
728  cpl_frame_get_filename(frame), CPL_IO_CREATE);
729  if (nrow < 1) {
730  cpl_msg_warning(__func__, "Table saved as \"%s\" has no rows!", aTag);
731  }
732  } else if (aType == MUSE_TABLE_TYPE_MUSE) {
733  rc = muse_table_save((muse_table *)aTable, cpl_frame_get_filename(frame));
734  } else {
735  rc = muse_pixtable_save((muse_pixtable *)aTable,
736  cpl_frame_get_filename(frame));
737  }
738  if (rc == CPL_ERROR_NONE) {
739 #pragma omp critical(muse_processing_output_frames)
740  cpl_frameset_insert(aProcessing->outframes, frame);
741  } else {
742  cpl_msg_error(__func__, "Saving %stable failed: %s",
743  aType == MUSE_TABLE_TYPE_PIXTABLE ? "pixel " : "",
744  cpl_error_get_message());
745  cpl_frame_delete(frame);
746  }
747  return rc;
748 } /* muse_processing_save_table() */
749 
750 /*---------------------------------------------------------------------------*/
767 /*---------------------------------------------------------------------------*/
768 int
770  cpl_image *aImage, cpl_propertylist *aHeader,
771  const char *aTag)
772 {
773  cpl_ensure_code(aProcessing && aImage && aHeader && aTag, CPL_ERROR_NULL_INPUT);
774 
775  cpl_frame *frame = muse_processing_new_frame(aProcessing, aIFU,
776  aHeader, aTag,
777  CPL_FRAME_TYPE_IMAGE);
778  cpl_msg_info(__func__, "Saving image as %s", cpl_frame_get_filename(frame));
779  int r = cpl_image_save(aImage, cpl_frame_get_filename(frame),
780  CPL_TYPE_UNSPECIFIED, aHeader, CPL_IO_CREATE);
781  if (r == CPL_ERROR_NONE) {
782 #pragma omp critical(muse_processing_output_frames)
783  cpl_frameset_insert(aProcessing->outframes, frame);
784  } else {
785  cpl_msg_error(__func__, "Saving image failed: %s", cpl_error_get_message());
786  cpl_frame_delete(frame);
787  }
788  return r;
789 }
790 
791 /*---------------------------------------------------------------------------*/
803 /*---------------------------------------------------------------------------*/
804 cpl_propertylist *
805 muse_processing_load_header(muse_processing *aProcessing, const char *aTag)
806 {
807  cpl_ensure(aProcessing, CPL_ERROR_NULL_INPUT, NULL);
808 
809  cpl_frame *frame = muse_frameset_find_master(aProcessing->inframes, aTag, 0);
810  if (!frame) {
811  cpl_msg_debug(__func__, "No propertylist found for tag %s", aTag);
812  return NULL;
813  }
814 
815  const char *filename = cpl_frame_get_filename(frame);
816  cpl_propertylist *pl = cpl_propertylist_load(filename, 0);
817  if (!pl) {
818  cpl_msg_info(__func__, "loading %s from file %s failed: %s", aTag,
819  filename, cpl_error_get_message());
820  cpl_frame_delete(frame);
821  return NULL;
822  }
823  cpl_msg_info(__func__, "loaded %s from file \"%s\"", aTag, filename);
824  /* property lists are calibrations of some sort, so append them as such into *
825  * the used frames for the FITS header of the output product, if they exist */
826  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_CALIB, 0);
827 
828  return pl;
829 } /* muse_processing_load_header() */
830 
831 /*---------------------------------------------------------------------------*/
847 /*---------------------------------------------------------------------------*/
848 cpl_error_code
850  cpl_propertylist *aHeader, const char *aTag)
851 {
852  cpl_ensure_code(aProcessing && aHeader && aTag, CPL_ERROR_NULL_INPUT);
853 
854  cpl_frame *frame = muse_processing_new_frame(aProcessing, aIFU,
855  aHeader, aTag,
856  CPL_FRAME_TYPE_IMAGE);
857  cpl_msg_info(__func__, "Saving header as %s", cpl_frame_get_filename(frame));
858  cpl_error_code rc = cpl_propertylist_save(aHeader,
859  cpl_frame_get_filename(frame),
860  CPL_IO_CREATE);
861  if (rc == CPL_ERROR_NONE) {
862 #pragma omp critical(muse_processing_output_frames)
863  cpl_frameset_insert(aProcessing->outframes, frame);
864  } else {
865  cpl_msg_error(__func__, "Saving header failed: %s", cpl_error_get_message());
866  cpl_frame_delete(frame);
867  }
868  return rc;
869 } /* muse_processing_save_header() */
870 
871 /*---------------------------------------------------------------------------*/
885 /*---------------------------------------------------------------------------*/
886 muse_mask *
887 muse_processing_load_mask(muse_processing *aProcessing, const char *aTag)
888 {
889  cpl_frameset *frames = muse_frameset_find(aProcessing->inframes,
890  aTag, 0, CPL_FALSE);
891  if (frames == NULL || cpl_frameset_get_size(frames) <= 0) {
892  cpl_frameset_delete(frames);
893  return NULL;
894  }
895  cpl_frame *frame = cpl_frameset_get_position(frames, 0);
896  muse_mask *mask = muse_mask_load(cpl_frame_get_filename(frame));
897  if (!mask) {
898  cpl_msg_warning(__func__, "loading mask \"%s\" failed!",
899  cpl_frame_get_filename(frame));
900  cpl_frameset_delete(frames);
901  return NULL;
902  }
903  cpl_msg_info(__func__, "using mask \"%s\" (%"CPL_SIZE_FORMAT" pixels)",
904  cpl_frame_get_filename(frame), cpl_mask_count(mask->mask));
905  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_CALIB, 1);
906  cpl_frameset_delete(frames);
907  return mask;
908 } /* muse_processing_load_mask() */
909 
910 /*---------------------------------------------------------------------------*/
926 /*---------------------------------------------------------------------------*/
927 int
929  muse_mask *aMask, const char *aTag)
930 {
931  cpl_ensure_code(aProcessing && aMask && aTag, CPL_ERROR_NULL_INPUT);
932 
933  cpl_frame *frame = muse_processing_new_frame(aProcessing, aIFU,
934  aMask->header, aTag,
935  CPL_FRAME_TYPE_IMAGE);
936  cpl_msg_info(__func__, "Saving mask as %s", cpl_frame_get_filename(frame));
937  int r = muse_mask_save(aMask, cpl_frame_get_filename(frame));
938  if (r == CPL_ERROR_NONE) {
939 #pragma omp critical(muse_processing_output_frames)
940  cpl_frameset_insert(aProcessing->outframes, frame);
941  } else {
942  cpl_frame_delete(frame);
943  }
944  return r;
945 } /* muse_processing_save_mask() */
946 
947 /*---------------------------------------------------------------------------*/
960 /*---------------------------------------------------------------------------*/
961 cpl_boolean
962 muse_processing_check_intags(muse_processing *aProcessing, const char *aTag,
963  int aNChars)
964 {
965  cpl_ensure(aProcessing && aTag, CPL_ERROR_NULL_INPUT, CPL_FALSE);
966 
967  cpl_boolean hastag = CPL_FALSE;
968  /* loop through all tags, looking for a matching one */
969  cpl_size itag, ntags = cpl_array_get_size(aProcessing->intags);
970  for (itag = 0; itag < ntags; itag++) {
971  const char *tag = cpl_array_get_string(aProcessing->intags, itag);
972  if (tag && !strncmp(tag, aTag, aNChars)) {
973  hastag = CPL_TRUE;
974  break; /* found one match, that's enough */
975  } /* if found */
976  } /* for itag */
977  return hastag;
978 } /* muse_processing_check_intags() */
979 
980 /*---------------------------------------------------------------------------*/
997 /*---------------------------------------------------------------------------*/
998 cpl_error_code
999 muse_processing_check_input(muse_processing *aProcessing, unsigned char aIFU)
1000 {
1001  if (!aProcessing) {
1002  cpl_msg_error(__func__, "NULL processing struct");
1003  return CPL_ERROR_NULL_INPUT;
1004  }
1005  cpl_recipeconfig *recipeconfig
1006  = muse_processing_get_recipeconfig(aProcessing->recipe);
1007  if (!recipeconfig) {
1008  cpl_msg_error(__func__, "No recipeconfig found!");
1009  return CPL_ERROR_ILLEGAL_INPUT;
1010  }
1011 
1012  cpl_boolean output = CPL_TRUE; /* start with outputting debug messages */
1013  cpl_size itag, ntags = cpl_array_get_size(aProcessing->intags);
1014  unsigned int errors = 0;
1015  for (itag = 0; itag < ntags; itag++) {
1016  const char *intag = cpl_array_get_string(aProcessing->intags, itag);
1017  cpl_frameset *frames = muse_frameset_find(aProcessing->inframes,
1018  intag, aIFU, CPL_FALSE);
1019  int nframes = cpl_frameset_count_tags(frames, intag),
1020  nmin = cpl_recipeconfig_get_min_count(recipeconfig, intag, intag),
1021  nmax = cpl_recipeconfig_get_max_count(recipeconfig, intag, intag);
1022  cpl_frameset_delete(frames);
1023  if (nmin >= 0 && nframes < nmin) {
1024  cpl_msg_error(__func__, "Required %d, found %d input frames with tag "
1025  "\"%s\" with IFU %hhu", nmin, nframes, intag, aIFU);
1026  errors++;
1027  }
1028  if (nmax >= 0 && nframes > nmax) {
1029  cpl_msg_error(__func__, "At most %d allowed, found %d input frames with "
1030  "tag \"%s\" with IFU %hhu", nmax, nframes, intag, aIFU);
1031  errors++;
1032  }
1033  char **tags = cpl_recipeconfig_get_inputs(recipeconfig, intag);
1034  if (!tags) {
1035  cpl_msg_error(__func__, "Input frames with tag \"%s\" cannot be used with"
1036  " this recipe", intag);
1037  errors++;
1038  continue;
1039  }
1040  /* check all other tags related to this raw input file */
1041  int i;
1042  for (i = 0; tags[i]; i++) {
1043  frames = muse_frameset_find(aProcessing->inframes, tags[i], aIFU,
1044  CPL_FALSE);
1045  nframes = cpl_frameset_count_tags(frames, tags[i]);
1046  cpl_frameset_delete(frames);
1047 
1048  nmin = cpl_recipeconfig_get_min_count(recipeconfig, intag, tags[i]);
1049  nmax = cpl_recipeconfig_get_max_count(recipeconfig, intag, tags[i]);
1050  if (nmin >= 0 && nframes < nmin) {
1051  if (output) {
1052  cpl_msg_error(__func__, "Required %d, found %d frames with tag \"%s\" "
1053  "with IFU %hhu", nmin, nframes, tags[i], aIFU);
1054  }
1055  errors++;
1056  }
1057  if (nframes == 0 && nmin <= 0) {
1058  if (output) {
1059  cpl_msg_debug(__func__, "Optional frame with tag \"%s\" not given",
1060  tags[i]);
1061  }
1062  }
1063  if (nmax >= 0 && nframes > nmax) {
1064  if (output) {
1065  cpl_msg_error(__func__, "At most %d allowed, found %d frames with tag "
1066  "\"%s\" with IFU %hhu", nmax, nframes, tags[i], aIFU);
1067  }
1068  errors++;
1069  }
1070  cpl_free(tags[i]);
1071  } /* for i (all tags) */
1072  cpl_free(tags);
1073  output = CPL_FALSE; /* printing the output for one raw file is enough */
1074  } /* for itag */
1075 
1076  if (errors) {
1077  cpl_msg_error(__func__, "Found %u error(s)", errors);
1078  return CPL_ERROR_DATA_NOT_FOUND;
1079  }
1080  return CPL_ERROR_NONE;
1081 } /* muse_processing_check_input() */
1082 
1083 /*----------------------------------------------------------------------------*/
1111 /*----------------------------------------------------------------------------*/
1112 cpl_table *
1114 {
1115  cpl_ensure(aProcessing, CPL_ERROR_NULL_INPUT, NULL);
1116  cpl_size nframes = cpl_frameset_get_size(aProcessing->inframes);
1117  cpl_ensure(nframes, CPL_ERROR_DATA_NOT_FOUND, NULL);
1118 
1119  /* now that we have at least one frame, we can assume that the frameset is *
1120  * valid and we have at least one exposure, so we can create the empty table */
1121  cpl_table *exptable = cpl_table_new(0);
1122  /* create columns for DATE-OBS and all IFUs */
1123  cpl_table_new_column(exptable, "DATE-OBS", CPL_TYPE_STRING);
1124  char colname[3]; /* further used in this function, define outside loop */
1125  int i;
1126  for (i = 0; i <= kMuseNumIFUs; i++) {
1127  snprintf(colname, 3, "%02d", i);
1128  cpl_table_new_column(exptable, colname, CPL_TYPE_STRING);
1129  }
1130 
1131  /* loop over all input frames */
1132  cpl_size iframe;
1133  for (iframe = 0; iframe < nframes; iframe++) {
1134  cpl_frame *frame = cpl_frameset_get_position(aProcessing->inframes,
1135  iframe);
1136  const char *tag = cpl_frame_get_tag(frame);
1137  if (muse_processing_check_intags(aProcessing, tag, strlen(tag))) {
1138  const char *filename = cpl_frame_get_filename(frame);
1139 
1140  /* load the primary header and get DATE-OBS and the IFU number */
1141  cpl_propertylist *header = cpl_propertylist_load(filename, 0);
1142  if (!header) {
1143  cpl_msg_warning(__func__, "\"%s\" could not be loaded, it will be "
1144  "ignored!", filename);
1145  continue; /* skip on to the next frame */
1146  }
1147  const char *date = muse_pfits_get_dateobs(header);
1148  if (!date) {
1149  cpl_msg_warning(__func__, "\"%s\" does not contain the DATE-OBS "
1150  "keyword, it will be ignored!", filename);
1151  cpl_propertylist_delete(header);
1152  continue; /* skip on to the next frame */
1153  }
1154  int ifu = muse_utils_get_ifu(header);
1155  if (!ifu) {
1156  cpl_msg_debug(__func__, "\"%s\" seems to contain merged data (no "
1157  "EXTNAME=CHANnn)", filename);
1158  }
1159 #if DEBUG_EXPOSURES_SORT
1160  cpl_msg_debug(__func__, "\"%s\": IFU %2d, DATE-OBS=\"%s\"", filename, ifu, date);
1161 #endif
1162 
1163  /* loop through all exposures (=rows) in the table and compare the date */
1164  int irow = -1;
1165  for (i = 0; i < cpl_table_get_nrow(exptable); i++) {
1166 #if DEBUG_EXPOSURES_SORT
1167  cpl_msg_debug(__func__, "i=%d, DATE-OBS=\"%s\"", i,
1168  cpl_table_get_string(exptable, "DATE-OBS", i));
1169 #endif
1170  if (!strcmp(date, cpl_table_get_string(exptable, "DATE-OBS", i))) {
1171  irow = i;
1172  }
1173  }
1174 
1175  /* didn't find a matching entry, add a new exposure (=row) */
1176  if (irow < 0) {
1177  cpl_table_set_size(exptable, cpl_table_get_nrow(exptable)+1);
1178  irow = cpl_table_get_nrow(exptable) - 1;
1179  cpl_table_set_string(exptable, "DATE-OBS", irow, date);
1180  }
1181 
1182  /* check, if table cell for this exposure is still empty and, *
1183  * if so, add the filename */
1184  snprintf(colname, 3, "%02d", ifu);
1185  if (cpl_table_is_valid(exptable, colname, irow)) {
1186  cpl_msg_warning(__func__, "we already have IFU %d of exposure %d (\"%s\")!"
1187  " Ignoring \"%s\"", ifu, irow+1,
1188  cpl_table_get_string(exptable, colname, irow),
1189  filename);
1190  cpl_propertylist_delete(header);
1191  continue; /* skip on to the next frame */
1192  }
1193  cpl_table_set_string(exptable, colname, irow, filename);
1194  /* now we can assume that this is valid and will be used; it is not a *
1195  * RAW file, but it's the main input, so the RAW group will satisfy the *
1196  * DFS-related functions in CPL */
1197  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_RAW, 1);
1198 
1199  cpl_propertylist_delete(header);
1200  } /* if correct tag */
1201 #if DEBUG_EXPOSURES_SORT
1202  else {
1203  cpl_msg_debug(__func__, "wrong tag \"%s\" for \"%s\"", tag,
1204  cpl_frame_get_filename(frame));
1205  }
1206 #endif
1207  } /* for iframe (all input frames) */
1208 
1209  if (cpl_table_get_nrow(exptable) <= 0) {
1210  /* no exposures found */
1211  cpl_table_delete(exptable);
1212  cpl_error_set(__func__, CPL_ERROR_DATA_NOT_FOUND);
1213  return NULL;
1214  }
1215  /* loop through all exposures and print the number of valid IFUs found */
1216  for (i = 0; i < cpl_table_get_nrow(exptable); i++) {
1217  int nmerged = 0;
1218  if (cpl_table_is_valid(exptable, "00", i)) {
1219  nmerged++;
1220  }
1221  int j, nvalid = 0;
1222  for (j = 1; j <= kMuseNumIFUs; j++) {
1223  snprintf(colname, 3, "%02d", j);
1224  if (cpl_table_is_valid(exptable, colname, i)) {
1225  nvalid++;
1226  }
1227  } /* for i (table columns) */
1228  cpl_msg_debug(__func__, "Data from exposure %2d is contained in %2d "
1229  "IFU%s/%d merged file%s", i+1, nvalid, nvalid == 1 ? "" : "s",
1230  nmerged, nmerged == 1 ? "" : "s");
1231  } /* for i (table rows) */
1232 
1233  /* sort table by increasing DATE-OBS, so that we process the exposures *
1234  * in chronological order */
1235  cpl_propertylist *sorting = cpl_propertylist_new();
1236  cpl_propertylist_append_bool(sorting, "DATE-OBS",
1237  CPL_FALSE); /* sort ascending */
1238  cpl_table_sort(exptable, sorting);
1239  cpl_propertylist_delete(sorting);
1240 
1241  return exptable;
1242 } /* muse_processing_sort_exposures() */
1243 
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.
Structure definition of a MUSE datacube.
Definition: muse_datacube.h:48
int muse_processing_get_frame_mode(const cpl_recipe *, const char *)
Get the mode for a product frame with a certain tag.
void muse_processing_delete(muse_processing *aProcessing)
Free the muse_processing structure.
int muse_processing_save_mask(muse_processing *aProcessing, int aIFU, muse_mask *aMask, const char *aTag)
Save a computed MUSE mask to disk.
cpl_error_code muse_euro3dcube_save(muse_euro3dcube *aEuro3D, const char *aFilename)
Save a Euro3D cube object to a file.
const char * name
unsigned char muse_utils_get_ifu(const cpl_propertylist *aHeaders)
Find out the IFU/channel from which this header originated.
Definition: muse_utils.c:98
muse_processing_framecounter * counter
muse_table_type
const char * muse_pfits_get_dateobs(const cpl_propertylist *aHeaders)
find out the date of observations
Definition: muse_pfits.c:364
cpl_error_code muse_table_save(muse_table *aTable, const char *aFilename)
Save the data and the FITS headers of a MUSE table to a file.
Definition: muse_table.c:173
Data cube/stacked image list containing the LSF for one IFU.
Definition: muse_lsf.h:49
Structure definition of MUSE three extension FITS file.
Definition: muse_image.h:40
cpl_error_code muse_lsf_cube_save(muse_lsf_cube *aLsfCube, const char *aFileName)
Save the LSF cube to disk.
cpl_propertylist * header
the FITS header
Definition: muse_image.h:72
cpl_boolean muse_processing_check_intags(muse_processing *aProcessing, const char *aTag, int aNChars)
Check that a tag is part of the input tags of a processing structure.
cpl_error_code muse_datacube_save(muse_datacube *aCube, const char *aFilename)
Save the three cube extensions and the FITS headers of a MUSE datacube to a file. ...
cpl_frameset * usedframes
muse_processing * muse_processing_new(const char *aName, cpl_recipe *aRecipe)
Create a new processing structure.
cpl_error_code muse_processing_prepare_header(const cpl_recipe *, const char *, cpl_propertylist *)
Prepare and check a FITS header for a certain frame tag.
cpl_error_code muse_processing_save_header(muse_processing *aProcessing, int aIFU, cpl_propertylist *aHeader, const char *aTag)
Save a FITS header to disk.
Structure definition of MUSE pixel table.
cpl_frame * muse_processing_new_frame(muse_processing *aProcessing, int aIFU, cpl_propertylist *aHeader, const char *aTag, cpl_frame_type aType)
Create a new frame for a result file.
muse_mask * muse_mask_load(const char *aFilename)
Load a mask file and its FITS header.
Definition: muse_mask.c:103
cpl_error_code muse_mask_save(muse_mask *aMask, const char *aFilename)
Save the data and the FITS headers of a MUSE mask to a file.
Definition: muse_mask.c:143
cpl_frameset * outframes
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_frame_level muse_processing_get_frame_level(const cpl_recipe *, const char *)
Get the level for a product frame with a certain tag.
cpl_recipe * recipe
cpl_frameset * muse_frameset_sort_raw_other(const cpl_frameset *aFrames, int aIndex, const char *aDateObs, cpl_boolean aSequence)
Create a new frameset containing all relevant raw frames first then all other frames.
Definition: muse_utils.c:418
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.
muse_cube_type
cpl_recipeconfig * muse_processing_get_recipeconfig(cpl_recipe *)
Get the recipe (frame) configuration.
Structure definition of a Euro3D datacube.
Definition: muse_datacube.h:97
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_mask * muse_processing_load_mask(muse_processing *aProcessing, const char *aTag)
Load a mask file and its FITS header.
void muse_table_delete(muse_table *aTable)
Deallocate memory associated to a muse_table object.
Definition: muse_table.c:80
cpl_error_code muse_pixtable_save(muse_pixtable *aPixtable, const char *aFilename)
Save a MUSE pixel table to a file on disk.
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.
cpl_parameterlist * muse_cplparameterlist_duplicate(const cpl_parameterlist *aPList)
Duplicate a CPL parameter list.
cpl_error_code muse_image_save(muse_image *aImage, const char *aFilename)
Save the three image extensions and the FITS headers of a MUSE image to a file.
Definition: muse_image.c:405
Handling of "mask" files.
Definition: muse_mask.h:43
cpl_array * intags
cpl_frameset * inframes
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.
cpl_error_code muse_processing_check_input(muse_processing *aProcessing, unsigned char aIFU)
Check the input files for completeness.
cpl_propertylist * header
the FITS header
Definition: muse_mask.h:56
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_table * muse_processing_sort_exposures(muse_processing *aProcessing)
Sort input frames (containing lists of pixel table filenames) into different exposures.
cpl_mask * mask
The mask data.
Definition: muse_mask.h:49
cpl_propertylist * muse_processing_load_header(muse_processing *aProcessing, const char *aTag)
load a header according to its tag
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
cpl_parameterlist * parameters
muse_table * muse_table_load(const char *aFilename, unsigned char aIFU)
Load a table file and its primary FITS header.
Definition: muse_table.c:106