MUSE Pipeline Reference Manual  0.18.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  *
5  * This file is part of the MUSE Instrument Pipeline
6  * Copyright (C) 2005-2011 European Southern Observatory
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 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_dfs.h"
40 #include "muse_pfits.h"
41 #include "muse_resampling.h"
42 #include "muse_utils.h"
43 
44 /*----------------------------------------------------------------------------*
45  * Debugging Macros *
46  * Set these to 1 or higher for (lots of) debugging output *
47  *----------------------------------------------------------------------------*/
48 #define DEBUG_EXPOSURES_SORT 0 /* debug muse_processing_sort_exposures() */
49 
50 /*----------------------------------------------------------------------------*/
57 /*----------------------------------------------------------------------------*/
58 
61 /*----------------------------------------------------------------------------*
62  * Functions *
63  *----------------------------------------------------------------------------*/
64 
65 /*---------------------------------------------------------------------------*/
75 /*---------------------------------------------------------------------------*/
76 static const char *
77 muse_processing_get_rawtag(cpl_recipe *aRecipe)
78 {
79  cpl_recipeconfig *recipeconfig = muse_processing_get_recipeconfig(aRecipe);
80  if (recipeconfig == NULL) {
81  cpl_msg_error(__func__, "No recipeconfig found!");
82  return MUSE_TAG_EMPTY;
83  }
84  const char *rawTag = NULL;
85  cpl_size iframe, nframes = cpl_frameset_get_size(aRecipe->frames);
86  for (iframe = 0; iframe < nframes; iframe++) {
87  cpl_frame *frame = cpl_frameset_get_position(aRecipe->frames, iframe);
88  rawTag = cpl_frame_get_tag(frame);
89  cpl_errorstate state = cpl_errorstate_get();
90  char**calibTags = cpl_recipeconfig_get_inputs(recipeconfig, rawTag);
91  if (calibTags != NULL) {
92  int i;
93  for (i = 0; calibTags[i] != NULL; i++) {
94  cpl_free(calibTags[i]);
95  }
96  cpl_free(calibTags);
97  break;
98  } else {
99  cpl_errorstate_set(state);
100  }
101  } /* for iframe (all recipe frames) */
102  if (iframe >= nframes) {
103  cpl_msg_error(__func__, "No valid raw tag found!");
104  return MUSE_TAG_EMPTY;
105  } else {
106  return rawTag;
107  }
108 }
109 
110 /*---------------------------------------------------------------------------*/
117 /*---------------------------------------------------------------------------*/
119 muse_processing_new(const char *aRecipeName, cpl_recipe *aRecipe)
120 {
121  muse_processing *processing = cpl_malloc(sizeof(muse_processing));
122  processing->recipeName = aRecipeName;
123  processing->inputFrames = cpl_frameset_duplicate(aRecipe->frames);
124  processing->recipe = aRecipe;
125  processing->usedFrames = cpl_frameset_new();
126  processing->outputFrames = cpl_frameset_new();
127  processing->parameters = aRecipe->parameters;
128  processing->inputTag = muse_processing_get_rawtag(aRecipe);
129  processing->framecounter = cpl_malloc(sizeof(muse_framecounter_s));
130  processing->framecounter[0].tag = NULL;
131  return processing;
132 }
133 
134 /*---------------------------------------------------------------------------*/
139 /*---------------------------------------------------------------------------*/
140 void
142 {
143  if (!aProcessing) {
144  return;
145  }
146  cpl_frameset_delete(aProcessing->inputFrames);
147  cpl_frameset_delete(aProcessing->usedFrames);
148  cpl_frameset_delete(aProcessing->outputFrames);
149  int i;
150  for (i = 0; aProcessing->framecounter[i].tag != NULL; i++) {
151  cpl_free((char *)aProcessing->framecounter[i].tag);
152  }
153  cpl_free(aProcessing->framecounter);
154  cpl_free(aProcessing);
155 }
156 
157 /*---------------------------------------------------------------------------*/
173 /*---------------------------------------------------------------------------*/
174 void
176  cpl_frame *aFrame, cpl_frame_group aGroup,
177  int aDuplicate)
178 {
179  if (!aProcessing) {
180  cpl_msg_error(__func__, "NULL processing struct!");
181  return;
182  }
183 #if 0
184  if (aGroup == CPL_FRAME_GROUP_CALIB) {
185  cpl_msg_debug(__func__, "using %s %s frame %s", "calibration",
186  cpl_frame_get_tag(aFrame), cpl_frame_get_filename(aFrame));
187  } else if (aGroup == CPL_FRAME_GROUP_RAW) {
188  cpl_msg_debug(__func__, "using %s %s frame %s", "raw",
189  cpl_frame_get_tag(aFrame), cpl_frame_get_filename(aFrame));
190  }
191 #endif
192  /* check, if this frame is already listed */
193  const char *fn = cpl_frame_get_filename(aFrame),
194  *tag = cpl_frame_get_tag(aFrame);
195  cpl_size iframe, nframes = cpl_frameset_get_size(aProcessing->usedFrames);
196  for (iframe = 0;
197  (iframe < nframes) && fn && tag; /* only check with valid info */
198  iframe++) {
199  cpl_frame *frame = cpl_frameset_get_position(aProcessing->usedFrames, iframe);
200  const char *fn2 = cpl_frame_get_filename(frame),
201  *tag2 = cpl_frame_get_tag(frame);
202  if (fn2 && !strncmp(fn, fn2, strlen(fn) + 1) &&
203  tag2 && !strncmp(tag, tag2, strlen(tag) + 1)) {
204  if (!aDuplicate) { /* destroy frame, assuming it was duplicated elsewhere */
205  cpl_frame_delete(aFrame);
206  }
207  return;
208  }
209  } /* for iframe (all usedFrames) */
210  cpl_frame_set_group(aFrame, aGroup);
211  if (aDuplicate) {
212  cpl_frameset_insert(aProcessing->usedFrames, cpl_frame_duplicate(aFrame));
213  } else {
214  cpl_frameset_insert(aProcessing->usedFrames, aFrame);
215  }
216 }
217 
218 /*---------------------------------------------------------------------------*/
231 /*---------------------------------------------------------------------------*/
232 static int
233 muse_processing_get_framecounter(muse_processing *aProcessing,
234  const char *aTag, int aIFU)
235 {
236  if (!aProcessing) {
237  cpl_msg_error(__func__, "NULL processing struct!");
238  return 0;
239  }
240  int i;
241  for (i = 0; aProcessing->framecounter[i].tag != NULL; i++) {
242  if (strcmp(aProcessing->framecounter[i].tag, aTag) == 0 &&
243  aProcessing->framecounter[i].ifu == aIFU) {
244  return ++aProcessing->framecounter[i].counter;
245  }
246  }
247  aProcessing->framecounter = cpl_realloc(aProcessing->framecounter,
248  (i+2)*sizeof(muse_framecounter_s));
249  aProcessing->framecounter[i].tag = cpl_strdup(aTag);
250  aProcessing->framecounter[i].ifu = aIFU;
251  aProcessing->framecounter[i].counter = 1;
252  aProcessing->framecounter[i+1].tag = NULL;
253  return 1;
254 }
255 
256 /*---------------------------------------------------------------------------*/
269 /*---------------------------------------------------------------------------*/
270 static void
271 muse_processing_setup_header(muse_processing *aProcessing,
272  cpl_propertylist *aHeader, cpl_frame *aFrame,
273  int aIndex, const char *aDateObs,
274  cpl_boolean aSequence)
275 {
276  if (!aProcessing) {
277  cpl_msg_error(__func__, "NULL processing struct!");
278  return;
279  }
280  if (!aProcessing->inputFrames ||
281  cpl_frameset_is_empty(aProcessing->inputFrames)) {
282  cpl_msg_warning(__func__, "No raw input files, no DFS product header added");
283  return;
284  }
285  /* save some headers which are be over overwritten by cpl_dfs, *
286  * including OBJECT and ESO.DRS.MUSE */
287  cpl_propertylist *hkeep = cpl_propertylist_new();
288  cpl_propertylist_copy_property_regexp(hkeep, aHeader,
289  MUSE_HEADERS_KEEP_REGEXP, 0);
290  /* don't want any DFS-generated ESO.PRO keywords before calling cpl_dfs *
291  * to create them, so delete all ESO.PRO stuff and COMMENTS */
292  cpl_propertylist_erase_regexp(aHeader, "^ESO PRO|^COMMENT", 0);
293  /* remove temporary stuff that is not meant to be saved to disk */
294  cpl_propertylist_erase_regexp(aHeader, MUSE_HDR_TMP_REGEXP, 0);
295 
296  /* make sure that the raw frame(s) are first in the list of used frames */
297  cpl_frameset *fset = muse_frameset_sort_raw_other(aProcessing->usedFrames,
298  aIndex, aDateObs, aSequence);
299 
300  /* XXX workaround for the EQUINOX warning, replace with double-type keyword */
301  if (cpl_propertylist_has(aHeader, "EQUINOX") &&
302  cpl_propertylist_get_type(aHeader, "EQUINOX") < CPL_TYPE_FLOAT) {
303  cpl_property *equinox = cpl_propertylist_get_property(aHeader, "EQUINOX");
304  double equvalue = cpl_property_get_long_long(equinox);
305  const char *equcomment = cpl_property_get_comment(equinox);
306  cpl_property_set_name(equinox, "EQUIBRK");
307  cpl_propertylist_insert_after_double(aHeader, "EQUIBRK", "EQUINOX",
308  equvalue);
309  cpl_propertylist_set_comment(aHeader, "EQUINOX", equcomment);
310  cpl_propertylist_erase(aHeader, "EQUIBRK");
311  } /* if non-float EQUINOX */
312 
313  char *pkgstring = cpl_sprintf("%s/%s", PACKAGE, PACKAGE_VERSION);
314  if (cpl_dfs_setup_product_header(aHeader, aFrame, fset,
315  aProcessing->parameters,
316  aProcessing->recipeName,
317  pkgstring, MUSE_PRO_DID, NULL)
318  != CPL_ERROR_NONE) {
319  cpl_msg_error(__func__, "Could not add DFS product header: %s",
320  cpl_error_get_message());
321  }
322  cpl_free(pkgstring);
323  cpl_frameset_delete(fset);
324 
325  /* reset overwritten headers (if they were there before) */
326  int i, n = cpl_propertylist_get_size(hkeep);
327  for (i = 0; i < n; i++) {
328  const cpl_property *p = cpl_propertylist_get_const(hkeep, i);
329  cpl_propertylist_erase(aHeader, cpl_property_get_name(p));
330  cpl_propertylist_append_property(aHeader, p);
331  } /* for i (all properties in hkeep) */
332  cpl_propertylist_delete(hkeep);
333 
334  /* MUSE data is always taken with an IFU, so override PRO.TECH */
335  cpl_propertylist_update_string(aHeader, "ESO PRO TECH", "IFU");
336  /* add PRO.SCIENCE header for science recipes */
337  if (strstr(aProcessing->recipeName, "sci")) {
338  cpl_propertylist_update_bool(aHeader, "ESO PRO SCIENCE", 1);
339  }
340 } /* muse_processing_setup_header() */
341 
342 /*---------------------------------------------------------------------------*/
365 /*---------------------------------------------------------------------------*/
366 cpl_frame *
368  cpl_propertylist *aHeader, const char *aTag,
369  cpl_frame_type aType)
370 {
371  cpl_ensure(aProcessing, CPL_ERROR_NULL_INPUT, NULL);
372  muse_processing_prepare_header(aProcessing->recipe, aTag, aHeader);
373  const char *prefix = cpl_propertylist_has(aHeader, "MUSE PRIVATE FILE PREFIX")
374  ? cpl_propertylist_get_string(aHeader,
375  "MUSE PRIVATE FILE PREFIX")
376  : aTag;
377 
378  cpl_frame *frame = cpl_frame_new();
379  /* some error from before might disturbe further error handling */
380  cpl_errorstate prestate = cpl_errorstate_get();
381 
382  /* see if we need to take all input frames, a specific frame based on its *
383  * number in the frameset, and/or a specific frame based on observing date */
384  int fmode = muse_processing_get_frame_mode(aProcessing->recipe, aTag),
385  framecounter = 0;
386  if (fmode != MUSE_FRAME_MODE_MASTER) {
387  framecounter = muse_processing_get_framecounter(aProcessing, prefix, aIFU);
388  }
389  const char *dateobs = NULL;
390  if (fmode == MUSE_FRAME_MODE_DATEOBS) {
391  dateobs = muse_pfits_get_dateobs(aHeader);
392  }
393  cpl_boolean sequence = fmode == MUSE_FRAME_MODE_SEQUENCE;
394 
395  /* Create product frame */
396  char filename[FILENAME_MAX];
397  if (aIFU >= 0) {
398  if (framecounter == 0) {
399  snprintf(filename, FILENAME_MAX, "%s-%02d.fits", prefix, aIFU);
400  } else {
401  snprintf(filename, FILENAME_MAX, "%s_%04d-%02d.fits", prefix,
402  framecounter, aIFU);
403  }
404  } else {
405  if (framecounter == 0) {
406  snprintf(filename, FILENAME_MAX, "%s.fits", prefix);
407  } else {
408  snprintf(filename, FILENAME_MAX, "%s_%04d.fits", prefix, framecounter);
409  }
410  }
411  cpl_frame_set_filename(frame, filename);
412  cpl_frame_set_tag(frame, aTag);
413  cpl_frame_set_type(frame, aType);
414  cpl_frame_set_group(frame, CPL_FRAME_GROUP_PRODUCT);
415  cpl_frame_set_level(frame,
417  aTag));
418  if (cpl_errorstate_get() != prestate) {
419  cpl_msg_error(__func__, "Error while initialising the product frame: %s",
420  cpl_error_get_message());
421  cpl_frame_delete(frame);
422  return NULL;
423  }
424  cpl_propertylist_erase_regexp(aHeader, "MUSE PRIVATE.*", 0);
425  /* select specific frame index to use */
426  int idx = dateobs || fmode == MUSE_FRAME_MODE_SUBSET ? -1 : framecounter - 1;
427  muse_processing_setup_header(aProcessing, aHeader, frame, idx, dateobs,
428  sequence);
429  return frame;
430 } /* muse_processing_new_frame() */
431 
432 /*---------------------------------------------------------------------------*/
448 /*---------------------------------------------------------------------------*/
449 int
451  muse_image *aImage, const char *aTag)
452 {
453  cpl_ensure_code(aProcessing && aImage && aTag, CPL_ERROR_NULL_INPUT);
454 
455  cpl_frame *frame = muse_processing_new_frame(aProcessing, aIFU,
456  aImage->header, aTag,
457  CPL_FRAME_TYPE_IMAGE);
458  cpl_msg_info(__func__, "Saving image as %s", cpl_frame_get_filename(frame));
459  int r = muse_image_save(aImage, cpl_frame_get_filename(frame));
460  if (r == CPL_ERROR_NONE) {
461  cpl_frameset_insert(aProcessing->outputFrames, frame);
462  } else {
463  cpl_frame_delete(frame);
464  }
465  return r;
466 } /* muse_processing_save_image() */
467 
468 /*---------------------------------------------------------------------------*/
487 /*---------------------------------------------------------------------------*/
488 cpl_error_code
490  void *aCube, const char *aTag, muse_cube_type aType)
491 {
492  cpl_ensure_code(aProcessing && aCube && aTag, CPL_ERROR_NULL_INPUT);
493  cpl_ensure_code(aType == MUSE_CUBE_TYPE_EURO3D || aType == MUSE_CUBE_TYPE_FITS,
494  CPL_ERROR_ILLEGAL_INPUT);
495 
496  /* both cube structures contain the header component as *
497  * first element, so it's safe to cast it to one of them */
498  cpl_frame *frame = muse_processing_new_frame(aProcessing, aIFU,
499  ((muse_datacube *)aCube)->header,
500  aTag, CPL_FRAME_TYPE_IMAGE);
501  cpl_msg_info(__func__, "Saving %s cube as \"%s\"",
502  aType == MUSE_CUBE_TYPE_EURO3D ? "Euro3D" : "FITS",
503  cpl_frame_get_filename(frame));
504  cpl_error_code rc = CPL_ERROR_NONE;
505  if (aType == MUSE_CUBE_TYPE_EURO3D) {
507  cpl_frame_get_filename(frame));
508  } else {
509  rc = muse_datacube_save((muse_datacube *)aCube,
510  cpl_frame_get_filename(frame));
511  }
512  if (rc == CPL_ERROR_NONE) {
513  cpl_frameset_insert(aProcessing->outputFrames, frame);
514  } else {
515  cpl_frame_delete(frame);
516  }
517  return rc;
518 } /* muse_processing_save_cube() */
519 
520 /*---------------------------------------------------------------------------*/
541 /*---------------------------------------------------------------------------*/
542 cpl_error_code
544  void *aTable, cpl_propertylist *aHeader,
545  const char *aTag, muse_table_type aType)
546 {
547  cpl_ensure_code(aProcessing && aTable && aTag, CPL_ERROR_NULL_INPUT);
548  cpl_ensure_code(aType == MUSE_TABLE_TYPE_CPL || aType == MUSE_TABLE_TYPE_PIXTABLE,
549  CPL_ERROR_ILLEGAL_INPUT);
550 
551  /* for plain tables, the separate header is needed */
552  cpl_propertylist *header;
553  if (aType == MUSE_TABLE_TYPE_CPL) {
554  cpl_ensure_code(aHeader, CPL_ERROR_NULL_INPUT);
555  header = aHeader;
556  } else {
557  header = ((muse_pixtable *)aTable)->header;
558  }
559  cpl_frame *frame = muse_processing_new_frame(aProcessing, aIFU, header,
560  aTag, CPL_FRAME_TYPE_TABLE);
561  cpl_msg_info(__func__, "Saving %stable as \"%s\"",
562  aType == MUSE_TABLE_TYPE_PIXTABLE ? "pixel " : "",
563  cpl_frame_get_filename(frame));
564  cpl_error_code rc = CPL_ERROR_NONE;
565  if (aType == MUSE_TABLE_TYPE_CPL) {
566  rc = cpl_table_save((cpl_table *)aTable, aHeader, NULL,
567  cpl_frame_get_filename(frame), CPL_IO_CREATE);
568  } else {
569  rc = muse_pixtable_save((muse_pixtable *)aTable,
570  cpl_frame_get_filename(frame));
571  }
572  if (rc == CPL_ERROR_NONE) {
573  cpl_frameset_insert(aProcessing->outputFrames, frame);
574  } else {
575  cpl_msg_error(__func__, "Saving %stable failed: %s",
576  aType == MUSE_TABLE_TYPE_PIXTABLE ? "pixel " : "",
577  cpl_error_get_message());
578  cpl_frame_delete(frame);
579  }
580  return rc;
581 } /* muse_processing_save_table() */
582 
583 /*---------------------------------------------------------------------------*/
600 /*---------------------------------------------------------------------------*/
601 int
603  cpl_image *aImage, cpl_propertylist *aHeader,
604  const char *aTag)
605 {
606  cpl_ensure_code(aProcessing && aImage && aHeader && aTag, CPL_ERROR_NULL_INPUT);
607 
608  cpl_frame *frame = muse_processing_new_frame(aProcessing, aIFU,
609  aHeader, aTag,
610  CPL_FRAME_TYPE_IMAGE);
611  cpl_msg_info(__func__, "Saving image as %s", cpl_frame_get_filename(frame));
612  int r = cpl_image_save(aImage, cpl_frame_get_filename(frame),
613  CPL_TYPE_UNSPECIFIED, aHeader, CPL_IO_CREATE);
614  if (r == CPL_ERROR_NONE) {
615  cpl_frameset_insert(aProcessing->outputFrames, frame);
616  } else {
617  cpl_msg_error(__func__, "Saving image failed: %s", cpl_error_get_message());
618  cpl_frame_delete(frame);
619  }
620  return r;
621 }
622 
623 /*---------------------------------------------------------------------------*/
639 /*---------------------------------------------------------------------------*/
640 cpl_error_code
642  cpl_propertylist *aHeader, const char *aTag)
643 {
644  cpl_ensure_code(aProcessing && aHeader && aTag, CPL_ERROR_NULL_INPUT);
645 
646  cpl_frame *frame = muse_processing_new_frame(aProcessing, aIFU,
647  aHeader, aTag,
648  CPL_FRAME_TYPE_IMAGE);
649  cpl_msg_info(__func__, "Saving header as %s", cpl_frame_get_filename(frame));
650  cpl_error_code rc = cpl_propertylist_save(aHeader,
651  cpl_frame_get_filename(frame),
652  CPL_IO_CREATE);
653  if (rc == CPL_ERROR_NONE) {
654  cpl_frameset_insert(aProcessing->outputFrames, frame);
655  } else {
656  cpl_msg_error(__func__, "Saving header failed: %s", cpl_error_get_message());
657  cpl_frame_delete(frame);
658  }
659  return rc;
660 }
661 
662 /*---------------------------------------------------------------------------*/
678 /*---------------------------------------------------------------------------*/
679 int
681  muse_mask *aMask, const char *aTag)
682 {
683  cpl_ensure_code(aProcessing && aMask && aTag, CPL_ERROR_NULL_INPUT);
684 
685  cpl_frame *frame = muse_processing_new_frame(aProcessing, aIFU,
686  aMask->header, aTag,
687  CPL_FRAME_TYPE_IMAGE);
688  cpl_msg_info(__func__, "Saving mask as %s", cpl_frame_get_filename(frame));
689  int r = muse_mask_save(aMask, cpl_frame_get_filename(frame));
690  if (r == CPL_ERROR_NONE) {
691  cpl_frameset_insert(aProcessing->outputFrames, frame);
692  } else {
693  cpl_frame_delete(frame);
694  }
695  return r;
696 } /* muse_processing_save_mask() */
697 
698 /*---------------------------------------------------------------------------*/
714 /*---------------------------------------------------------------------------*/
715 cpl_error_code
716 muse_processing_check_input(muse_processing *aProcessing, unsigned char aIFU)
717 {
718  if (!aProcessing) {
719  cpl_msg_error(__func__, "NULL processing struct");
720  return CPL_ERROR_NULL_INPUT;
721  }
722  cpl_recipeconfig *recipeconfig
724  if (!recipeconfig) {
725  cpl_msg_error(__func__, "No recipeconfig found!");
726  return CPL_ERROR_ILLEGAL_INPUT;
727  }
728  int errors = 0;
729 
730  cpl_frameset *frames = muse_frameset_find(aProcessing->inputFrames,
731  aProcessing->inputTag, aIFU,
732  CPL_FALSE);
733  int nFrames = cpl_frameset_count_tags(frames, aProcessing->inputTag);
734  cpl_frameset_delete(frames);
735  int minFrames = cpl_recipeconfig_get_min_count(recipeconfig,
736  aProcessing->inputTag,
737  aProcessing->inputTag);
738  int maxFrames = cpl_recipeconfig_get_max_count(recipeconfig,
739  aProcessing->inputTag,
740  aProcessing->inputTag);
741  if (minFrames >= 0 && nFrames < minFrames) {
742  cpl_msg_error(__func__, "Required %i, found %i input frames "
743  "with tag \"%s\" with IFU %hhu",
744  minFrames, nFrames, aProcessing->inputTag, aIFU);
745  errors++;
746  }
747  if (maxFrames >= 0 && nFrames > maxFrames) {
748  cpl_msg_error(__func__, "Maximal %i, found %i input frames "
749  "with tag \"%s\" with IFU %hhu",
750  maxFrames, nFrames, aProcessing->inputTag, aIFU);
751  errors++;
752  }
753  char **tags = cpl_recipeconfig_get_inputs(recipeconfig,
754  aProcessing->inputTag);
755  if (tags == NULL) {
756  cpl_msg_error(__func__, "Input frames %s cannot be used with this recipe",
757  aProcessing->inputTag);
758  errors++;
759  }
760  int i;
761  for (i = 0; tags != NULL && tags[i] != NULL; i++) {
762  frames = muse_frameset_find(aProcessing->inputFrames, tags[i], aIFU,
763  CPL_FALSE);
764  nFrames = cpl_frameset_count_tags(frames, tags[i]);
765  cpl_frameset_delete(frames);
766 
767  minFrames = cpl_recipeconfig_get_min_count(recipeconfig,
768  aProcessing->inputTag,
769  tags[i]);
770  maxFrames = cpl_recipeconfig_get_max_count(recipeconfig,
771  aProcessing->inputTag,
772  tags[i]);
773  if (minFrames >= 0 && nFrames < minFrames) {
774  cpl_msg_error(__func__, "Required %i, found %i frames "
775  "with tag \"%s\" with IFU %hhu",
776  minFrames, nFrames, tags[i], aIFU);
777  errors++;
778  }
779  if (nFrames == 0 && minFrames <= 0) {
780  cpl_msg_debug(__func__, "Optional frame with tag %s not given", tags[i]);
781  }
782  if (maxFrames >= 0 && nFrames > maxFrames) {
783  cpl_msg_error(__func__, "Maximal %i, found %i frames "
784  "with tag \"%s\" with IFU %hhu",
785  maxFrames, nFrames, tags[i], aIFU);
786  errors++;
787  }
788  cpl_free(tags[i]);
789  }
790  cpl_free(tags);
791 
792  if (errors > 0) {
793  cpl_msg_error(__func__, "Found %i error(s)", errors);
794  return CPL_ERROR_DATA_NOT_FOUND;
795  }
796  return CPL_ERROR_NONE;
797 } /* muse_processing_check_input() */
798 
799 /*----------------------------------------------------------------------------*/
822 /*----------------------------------------------------------------------------*/
823 cpl_table *
825 {
826  cpl_ensure(aProcessing, CPL_ERROR_NULL_INPUT, NULL);
827  cpl_size nframes = cpl_frameset_get_size(aProcessing->inputFrames);
828  cpl_ensure(nframes, CPL_ERROR_DATA_NOT_FOUND, NULL);
829 
830  /* now that we have at least one frame, we can assume that the frameset is *
831  * valid and we have at least one exposure, so we can create the empty table */
832  cpl_table *exptable = cpl_table_new(0);
833  /* create columns for DATE-OBS and all IFUs */
834  cpl_table_new_column(exptable, "DATE-OBS", CPL_TYPE_STRING);
835  char colname[3]; /* further used in this function, define outside loop */
836  int i;
837  for (i = 0; i <= kMuseNumIFUs; i++) {
838  snprintf(colname, 3, "%02d", i);
839  cpl_table_new_column(exptable, colname, CPL_TYPE_STRING);
840  }
841 
842  /* loop over all input frames */
843  cpl_size iframe;
844  for (iframe = 0; iframe < nframes; iframe++) {
845  cpl_frame *frame = cpl_frameset_get_position(aProcessing->inputFrames,
846  iframe);
847  if (!strcmp(cpl_frame_get_tag(frame), aProcessing->inputTag)) {
848  const char *filename = cpl_frame_get_filename(frame);
849 
850  /* load the primary header and get DATE-OBS and the IFU number */
851  cpl_propertylist *header = cpl_propertylist_load(filename, 0);
852  const char *date = muse_pfits_get_dateobs(header);
853  if (!date) {
854  cpl_msg_warning(__func__, "\"%s\" does not contain the DATE-OBS "
855  "keyword, it will be ignored!", filename);
856  cpl_propertylist_delete(header);
857  continue; /* skip on to the next frame */
858  }
859  int ifu = muse_utils_get_ifu(header);
860  if (!ifu) {
861  cpl_msg_debug(__func__, "\"%s\" seems to contain merged data (no "
862  "EXTNAME=CHANnn)", filename);
863  }
864 #if DEBUG_EXPOSURES_SORT
865  cpl_msg_debug(__func__, "\"%s\": IFU %2d, DATE-OBS=\"%s\"", filename, ifu, date);
866 #endif
867 
868  /* loop through all exposures (=rows) in the table and compare the date */
869  int irow = -1;
870  for (i = 0; i < cpl_table_get_nrow(exptable); i++) {
871 #if DEBUG_EXPOSURES_SORT
872  cpl_msg_debug(__func__, "i=%d, DATE-OBS=\"%s\"", i,
873  cpl_table_get_string(exptable, "DATE-OBS", i));
874 #endif
875  if (!strcmp(date, cpl_table_get_string(exptable, "DATE-OBS", i))) {
876  irow = i;
877  }
878  }
879 
880  /* didn't find a matching entry, add a new exposure (=row) */
881  if (irow < 0) {
882  cpl_table_set_size(exptable, cpl_table_get_nrow(exptable)+1);
883  irow = cpl_table_get_nrow(exptable) - 1;
884  cpl_table_set_string(exptable, "DATE-OBS", irow, date);
885  }
886 
887  /* check, if table cell for this exposure is still empty and, *
888  * if so, add the filename */
889  snprintf(colname, 3, "%02d", ifu);
890  if (cpl_table_is_valid(exptable, colname, irow)) {
891  cpl_msg_warning(__func__, "we already have IFU %d of exposure %d (\"%s\")!"
892  " Ignoring \"%s\"", ifu, irow+1,
893  cpl_table_get_string(exptable, colname, irow),
894  filename);
895  cpl_propertylist_delete(header);
896  continue; /* skip on to the next frame */
897  }
898  cpl_table_set_string(exptable, colname, irow, filename);
899  /* now we can assume that this is valid and will be used; it is not a *
900  * RAW file, but it's the main input, so the RAW group will satisfy the *
901  * DFS-related functions in CPL */
902  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_RAW, 1);
903 
904  cpl_propertylist_delete(header);
905  } /* if correct tag */
906 #if DEBUG_EXPOSURES_SORT
907  else {
908  cpl_msg_debug(__func__, "wrong tag \"%s\" for \"%s\"",
909  cpl_frame_get_tag(frame), cpl_frame_get_filename(frame));
910  }
911 #endif
912  } /* for iframe (all input frames) */
913 
914  if (cpl_table_get_nrow(exptable) <= 0) {
915  /* no exposures found */
916  cpl_table_delete(exptable);
917  cpl_error_set(__func__, CPL_ERROR_DATA_NOT_FOUND);
918  return NULL;
919  }
920  /* loop through all exposures and print the number of valid IFUs found */
921  for (i = 0; i < cpl_table_get_nrow(exptable); i++) {
922  int nmerged = 0;
923  if (cpl_table_is_valid(exptable, "00", i)) {
924  nmerged++;
925  }
926  int j, nvalid = 0;
927  for (j = 1; j <= kMuseNumIFUs; j++) {
928  snprintf(colname, 3, "%02d", j);
929  if (cpl_table_is_valid(exptable, colname, i)) {
930  nvalid++;
931  }
932  } /* for i (table columns) */
933  cpl_msg_debug(__func__, "Data from exposure %2d is contained in %2d "
934  "IFU%s/%d merged file%s", i+1, nvalid, nvalid == 1 ? "" : "s",
935  nmerged, nmerged == 1 ? "" : "s");
936  } /* for i (table rows) */
937 
938  /* sort table by increasing DATE-OBS, so that we process the exposures *
939  * in chronological order */
940  cpl_propertylist *sorting = cpl_propertylist_new();
941  cpl_propertylist_append_bool(sorting, "DATE-OBS",
942  CPL_FALSE); /* sort ascending */
943  cpl_table_sort(exptable, sorting);
944  cpl_propertylist_delete(sorting);
945 
946  return exptable;
947 } /* muse_processing_sort_exposures() */
948 
949 /*---------------------------------------------------------------------------*/
962 /*---------------------------------------------------------------------------*/
963 muse_mask *
964 muse_processing_mask_load(muse_processing *aProcessing, const char *aTag)
965 {
966  cpl_frameset *frames = muse_frameset_find(aProcessing->inputFrames,
967  aTag, 0, CPL_FALSE);
968  if (frames == NULL || cpl_frameset_get_size(frames) <= 0) {
969  cpl_frameset_delete(frames);
970  return NULL;
971  }
972  cpl_frame *frame = cpl_frameset_get_position(frames, 0);
973  muse_mask *skymask = muse_mask_load(cpl_frame_get_filename(frame));
974  cpl_msg_info(__func__, "using sky mask \"%s\" (%"CPL_SIZE_FORMAT" pixels)",
975  cpl_frame_get_filename(frame), cpl_mask_count(skymask->mask));
976  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_CALIB, 1);
977  cpl_frameset_delete(frames);
978  return skymask;
979 }
980 
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.
const char * inputTag
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.
muse_framecounter_s * framecounter
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.
unsigned char muse_utils_get_ifu(const cpl_propertylist *aHeaders)
Find out the IFU/channel from which this header originated.
Definition: muse_utils.c:95
muse_table_type
const char * muse_pfits_get_dateobs(const cpl_propertylist *aHeaders)
find out the exposure time
Definition: muse_pfits.c:260
const char * recipeName
Structure definition of MUSE three extension FITS file.
Definition: muse_image.h:41
muse_mask * muse_processing_mask_load(muse_processing *aProcessing, const char *aTag)
Load a mask file and its FITS header.
cpl_propertylist * header
the FITS header
Definition: muse_image.h:73
muse_mask * muse_mask_load(const char *aFilename)
Load a mask file and its FITS header.
Definition: muse_mask.c:92
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_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.
muse_processing * muse_processing_new(const char *aRecipeName, cpl_recipe *aRecipe)
Create a new processing structure.
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.
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:133
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:356
muse_cube_type
cpl_frameset * outputFrames
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.
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_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:396
Handling of "mask" files.
Definition: muse_mask.h:43
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:154
cpl_table * muse_processing_sort_exposures(muse_processing *aProcessing)
Sort input frames (containing lists of pixel table filenames) into different exposures.
cpl_frameset * inputFrames
cpl_mask * mask
The mask data.
Definition: muse_mask.h:49
cpl_frameset * usedFrames
cpl_parameterlist * parameters