MUSE Pipeline Reference Manual  2.1.1
muse_utils.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-2017 European Southern Observatory
6  * (C) 2001-2008 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 #if HAVE_DRAND48
31 #define _XOPEN_SOURCE /* force drand48 definition from stdlib */
32 #endif
33 #include <cpl.h>
34 #include <cpl_multiframe.h>
35 #include <float.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <strings.h> /* strncasecmp() */
39 #ifdef HAVE_GETTIMEOFDAY
40 #include <sys/time.h> /* gettimeofday */
41 #endif
42 
43 #include "muse_utils.h"
44 #include "muse_instrument.h"
45 
46 #include "muse_cplwrappers.h"
47 #include "muse_flux.h"
48 #include "muse_pfits.h"
49 #include "muse_resampling.h"
50 #include "muse_tracing.h"
51 #include "muse_wcs.h"
52 
53 /*----------------------------------------------------------------------------*
54  * Debugging Macros *
55  *----------------------------------------------------------------------------*/
56 #define MOFFAT_USE_MUSE_OPTIMIZE 0 /* if non-zero, use the muse_cpl_optimize *
57  * interface to fit the moffat function */
58 #if MOFFAT_USE_MUSE_OPTIMIZE
59 #include "muse_optimize.h"
60 #endif
61 
62 /*----------------------------------------------------------------------------*/
68 /*----------------------------------------------------------------------------*/
69 
72 /*----------------------------------------------------------------------------*/
81 /*----------------------------------------------------------------------------*/
82 const char *
84 {
85  return cpl_get_license(PACKAGE_NAME, "2005, 2017");
86 } /* muse_get_license() */
87 
88 /*----------------------------------------------------------------------------*/
96 /*----------------------------------------------------------------------------*/
97 unsigned char
98 muse_utils_get_ifu(const cpl_propertylist *aHeaders)
99 {
100  unsigned char n;
101  for (n = 1; n <= kMuseNumIFUs; n++) {
102  if (muse_pfits_has_ifu(aHeaders, n)) {
103  return n;
104  } /* if */
105  } /* for n (all IFU numbers) */
106  return 0;
107 } /* muse_utils_get_ifu() */
108 
109 /*---------------------------------------------------------------------------*/
116 /*---------------------------------------------------------------------------*/
117 int
118 muse_utils_get_extension_for_ifu(const char *aFilename, unsigned char aIFU)
119 {
120  cpl_errorstate prestate = cpl_errorstate_get();
121  int i, next = cpl_fits_count_extensions(aFilename);
122  for (i = 0; i <= next; i++) {
123  cpl_propertylist *properties = cpl_propertylist_load(aFilename, i);
124  if (muse_pfits_has_ifu(properties, aIFU)) {
125  cpl_propertylist_delete(properties);
126  return i;
127  }
128  cpl_propertylist_delete(properties);
129  } /* for i (primary header and extensions) */
130  cpl_errorstate_set(prestate);
131  return -1;
132 } /* muse_utils_get_extension_for_ifu() */
133 
134 /*---------------------------------------------------------------------------*/
158 /*---------------------------------------------------------------------------*/
159 cpl_frameset *
160 muse_frameset_find(const cpl_frameset *aFrames, const char *aTag,
161  unsigned char aIFU, cpl_boolean aInvert)
162 {
163  cpl_ensure(aFrames, CPL_ERROR_NULL_INPUT, NULL);
164  cpl_frameset *newFrames = cpl_frameset_new();
165 
166  /* loop through all frames to determine properties */
167  cpl_size iframe, nframes = cpl_frameset_get_size(aFrames);
168  for (iframe = 0; iframe < nframes; iframe++) {
169  const cpl_frame *frame = cpl_frameset_get_position_const(aFrames, iframe);
170  const char *fn = cpl_frame_get_filename(frame),
171  *tag = cpl_frame_get_tag(frame);
172 
173  /* we are happy, if we want matched frames and found them, or we *
174  * want non-matching frames and found one (or one without a tag) */
175  cpl_boolean matched = !aInvert && (!aTag || (aTag && !strcmp(tag, aTag))),
176  unmatched = aInvert
177  && ((aTag && !tag) || (aTag && strcmp(tag, aTag)));
178  if (matched || unmatched) {
179  /* files produced outside the pipeline are generic, *
180  * so find out if this is one */
181  cpl_errorstate prestate = cpl_errorstate_get();
182  int extension = muse_utils_get_extension_for_ifu(fn, aIFU);
183  /* if we haven't found an extension, we should *
184  * still try to load the primary header */
185  if (extension == -1) {
186  extension = 0;
187  cpl_errorstate_set(prestate);
188  }
189  cpl_propertylist *header = cpl_propertylist_load(fn, extension);
190  if (!header) {
191  /* ignore files where the header cannot be loaded, but do not *
192  * swallow the error state that this failure has already set */
193  continue;
194  }
195  unsigned char ifu = muse_utils_get_ifu(header);
196  prestate = cpl_errorstate_get();
197  const char *pipefile = muse_pfits_get_pipefile(header);
198  if (!cpl_errorstate_is_equal(prestate)) {
199  /* recover from missing PIPEFILE which does not exist in raw data */
200  cpl_errorstate_set(prestate);
201  }
202  if (ifu == aIFU) {
203  /* a pipeline produced file which contains data from the correct IFU */
204  cpl_frameset_insert(newFrames, cpl_frame_duplicate(frame));
205  } else if (ifu == 0 && !pipefile) {
206  /* not specific to any IFU, this is the case for external *
207  * calibration files, like EXTINCT_TABLE or LINE_CATALOG */
208  cpl_frameset_insert(newFrames, cpl_frame_duplicate(frame));
209  } else if (aIFU == 0) {
210  /* no IFU specified, this happens in the post-processing recipes */
211  cpl_frameset_insert(newFrames, cpl_frame_duplicate(frame));
212  } else if (aTag && (!strncmp(aTag, MUSE_TAG_GEOMETRY_TABLE,
213  strlen(MUSE_TAG_GEOMETRY_TABLE) + 1) ||
214  !strncmp(aTag, MUSE_TAG_TWILIGHT_CUBE,
215  strlen(MUSE_TAG_TWILIGHT_CUBE) + 1))) {
216  /* since the geometry table and twilight cube are not IFU-specific, *
217  * they should always match, even if a specific IFU is requested */
218  cpl_frameset_insert(newFrames, cpl_frame_duplicate(frame));
219  } else {
220  /* do nothing */
221 #if 0
222  cpl_msg_debug(__func__, "not added %s / %s / %d", fn,
223  cpl_frame_get_tag(frame), ifu);
224 #endif
225  }
226  cpl_propertylist_delete(header);
227  } /* if */
228  } /* for iframe (all frames) */
229 
230  return newFrames;
231 } /* muse_frameset_find() */
232 
233 /*---------------------------------------------------------------------------*/
251 /*---------------------------------------------------------------------------*/
252 cpl_frameset *
253 muse_frameset_find_tags(const cpl_frameset *aFrames, const cpl_array *aTags,
254  unsigned char aIFU, cpl_boolean aInvert)
255 {
256  cpl_ensure(aFrames && aTags, CPL_ERROR_NULL_INPUT, NULL);
257  cpl_ensure(cpl_array_get_type(aTags) == CPL_TYPE_STRING,
258  CPL_ERROR_ILLEGAL_INPUT, NULL);
259 
260  cpl_frameset *outframes = cpl_frameset_new();
261  cpl_size itag, ntags = cpl_array_get_size(aTags);
262  for (itag = 0; itag < ntags; itag++) {
263  const char *tag = cpl_array_get_string(aTags, itag);
264  cpl_frameset *frames = muse_frameset_find(aFrames, tag, aIFU, aInvert);
265  cpl_frameset_join(outframes, frames);
266  cpl_frameset_delete(frames);
267  } /* for itag */
268  return outframes;
269 } /* muse_frameset_find_tags() */
270 
271 /*---------------------------------------------------------------------------*/
282 /*---------------------------------------------------------------------------*/
283 cpl_frameset *
284 muse_frameset_check_raw(const cpl_frameset *aFrames, const cpl_array *aTags,
285  unsigned char aIFU)
286 {
287  cpl_frameset *foundFrames = muse_frameset_find_tags(aFrames, aTags, aIFU,
288  CPL_FALSE);
289  cpl_frameset *newFrames = cpl_frameset_new();
290 
291  /* loop through all frames to determine properties */
292  cpl_size iframe, nframes = cpl_frameset_get_size(foundFrames);
293  cpl_msg_debug(__func__, "Determine properties of all %"CPL_SIZE_FORMAT
294  " raw frames of IFU %hhu", nframes, aIFU);
295  int binx = -1, biny = -1, readid = -1;
296  char *fn = NULL, *readname = NULL, *chipname = NULL, *chipid = NULL;
297  for (iframe = 0; iframe < nframes; iframe++) {
298  const cpl_frame *frame = cpl_frameset_get_position_const(foundFrames, iframe);
299  /* load primary and append IFU extension header (extension is *
300  * necessary to get the binning keywords related to raw data) */
301  const char *fn2 = cpl_frame_get_filename(frame);
302  if (!fn) {
303  fn = cpl_strdup(fn2);
304  }
305  cpl_propertylist *header = cpl_propertylist_load(fn2, 0);
306  if (!header) {
307  cpl_msg_warning(__func__, "Cannot read primary FITS header of file "
308  "\"%s\"!", fn2);
309  continue;
310  }
311  int extension = muse_utils_get_extension_for_ifu(fn2, aIFU);
312  if (extension > 0) {
313  cpl_propertylist *exthead = cpl_propertylist_load(fn2, extension);
314  cpl_propertylist_append(header, exthead);
315  cpl_propertylist_delete(exthead);
316  }
317  cpl_boolean isOK = CPL_TRUE;
318  if (binx < 0) {
319  binx = muse_pfits_get_binx(header);
320  }
321  if (biny < 0) {
322  biny = muse_pfits_get_biny(header);
323  }
324  if (!readname) {
325  readname = cpl_strdup(muse_pfits_get_read_name(header));
326  }
327  if (readid < 0) {
328  readid = muse_pfits_get_read_id(header);
329  }
330  if (!chipname) {
331  chipname = cpl_strdup(muse_pfits_get_chip_name(header));
332  }
333  if (!chipid) {
334  chipid = cpl_strdup(muse_pfits_get_chip_id(header));
335  }
336  int binx2 = muse_pfits_get_binx(header),
337  biny2 = muse_pfits_get_biny(header),
338  readid2 = muse_pfits_get_read_id(header);
339  const char *chipname2 = muse_pfits_get_chip_name(header),
340  *chipid2 = muse_pfits_get_chip_id(header);
341  if (binx2 != binx) {
342  cpl_msg_warning(__func__, "File \"%s\" (IFU %hhu) was taken with a different"
343  " x-binning factor (reference \"%s\", %d instead of %d)!",
344  fn2, aIFU, fn, binx2, binx);
345  isOK = 0;
346  }
347  if (biny2 != biny) {
348  cpl_msg_warning(__func__, "File \"%s\" (IFU %hhu) was taken with a different"
349  " y-binning factor (reference \"%s\", %d instead of %d)!",
350  fn2, aIFU, fn, biny2, biny);
351  isOK = 0;
352  }
353  if (readid2 != readid) {
354  cpl_msg_warning(__func__, "File \"%s\" (IFU %hhu) was taken with a different"
355  " read-out mode (reference \"%s\", %d/%s instead of %d/%s)!",
356  fn2, aIFU, fn, readid2, muse_pfits_get_read_name(header),
357  readid, readname);
358  isOK = 0;
359  }
360  if (!chipname2 || !chipid2 ||
361  strcmp(chipname, chipname2) || strcmp(chipid, chipid2)) {
362  cpl_msg_warning(__func__, "File \"%s\" (IFU %hhu) has a different chip "
363  "setup (reference \"%s\", name %s vs %s, id %s vs %s)",
364  fn2, aIFU, fn, chipname2, chipname, chipid2, chipid);
365  isOK = 0;
366  }
367 
368  if (!cpl_frame_get_tag(frame) ||
369  !strncmp(cpl_frame_get_tag(frame), MUSE_TAG_EMPTY, 1) ) {
370  /* non-fatal, continue normally and check for other possible *
371  * problems, that should override this exclude reason */
372  cpl_msg_warning(__func__, "File \"%s\" (IFU %hhu) is not tagged!", fn2,
373  aIFU);
374  }
375  cpl_propertylist_delete(header);
376  if (isOK) {
377  cpl_frameset_insert(newFrames, cpl_frame_duplicate(frame));
378  }
379  } /* for nframes */
380  cpl_free(fn);
381  cpl_free(readname);
382  cpl_free(chipname);
383  cpl_free(chipid);
384  cpl_frameset_delete(foundFrames);
385 
386  return newFrames;
387 } /* muse_frameset_check_raw() */
388 
389 /*---------------------------------------------------------------------------*/
416 /*---------------------------------------------------------------------------*/
417 cpl_frameset *
418 muse_frameset_sort_raw_other(const cpl_frameset *aFrames, int aIndex,
419  const char *aDateObs, cpl_boolean aSequence)
420 {
421  cpl_ensure(aFrames, CPL_ERROR_NULL_INPUT, NULL);
422 
423  cpl_frameset *fraw = cpl_frameset_new(),
424  *fillum = cpl_frameset_new(),
425  *fother = cpl_frameset_new();
426  int iraw = 0, /* raw frame index */
427  nillum = 0; /* counter for ILLUM frames */
428  cpl_size iframe, nframes = cpl_frameset_get_size(aFrames);
429  for (iframe = 0; iframe < nframes; iframe++) {
430  const cpl_frame *frame = cpl_frameset_get_position_const(aFrames, iframe);
431  if (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_RAW) {
432  const char *tag = cpl_frame_get_tag(frame);
433  if (!tag || (tag && strncmp(tag, "ILLUM", 6))) {
434  /* "normal" raw frame: *
435  * if the index matches, insert it, otherwise ignore */
436  int datematch = 1; /* pretend to match when aDateObs == NULL */
437  if (aDateObs) {
438  cpl_propertylist *header
439  = cpl_propertylist_load(cpl_frame_get_filename(frame), 0);
440  const char *dateobs = muse_pfits_get_dateobs(header);
441  datematch = dateobs && !strncmp(aDateObs, dateobs, strlen(aDateObs));
442  cpl_propertylist_delete(header);
443  }
444  if ((aIndex < 0 && datematch) || aIndex == iraw || aSequence) {
445  cpl_frameset_insert(fraw, cpl_frame_duplicate(frame));
446  }
447  iraw++;
448  } else {
449  /* raw ILLUM: *
450  * if it's the first one, insert it, otherwise ignore */
451  if (!nillum) {
452  cpl_frameset_insert(fillum, cpl_frame_duplicate(frame));
453  }
454  nillum++;
455  } /* else: is ILLUM */
456  } else {
457  cpl_frameset_insert(fother, cpl_frame_duplicate(frame));
458  } /* else: not GROUP_RAW */
459  } /* for all incoming frames */
460 #if 0
461  printf("incoming frames (index=%d, DATE-OBS=%s):\n", aIndex, aDateObs);
462  cpl_frameset_dump(aFrames, stdout);
463  fflush(stdout);
464  printf("=============================================================\n"
465  "raw frames:\n");
466  cpl_frameset_dump(fraw, stdout);
467  printf("illum frames:\n");
468  cpl_frameset_dump(fillum, stdout);
469  printf("-------------------------------------------------------------\n"
470  "other frames:\n");
471  cpl_frameset_dump(fother, stdout);
472  printf("^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^\n");
473  fflush(stdout);
474 #endif
475 
476  /* transfer all other frames into the raw = output frameset */
477  cpl_frameset_join(fraw, fillum);
478  cpl_frameset_join(fraw, fother);
479  cpl_frameset_delete(fother);
480  cpl_frameset_delete(fillum);
481 #if 0
482  printf("=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=\n"
483  "all sorted frames:\n");
484  cpl_frameset_dump(fraw, stdout);
485  printf("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n");
486  fflush(stdout);
487 #endif
488 
489  return fraw;
490 } /* muse_frameset_sort_raw_other() */
491 
492 /*---------------------------------------------------------------------------*/
503 /*---------------------------------------------------------------------------*/
504 cpl_frame *
505 muse_frameset_find_master(const cpl_frameset *aFrames, const char *aTag,
506  unsigned char aIFU)
507 {
508  cpl_frameset *frames = muse_frameset_find(aFrames, aTag, aIFU, CPL_FALSE);
509  cpl_frame *frame = NULL;
510  if (cpl_frameset_count_tags(frames, aTag) == 1) {
511  frame = cpl_frame_duplicate(cpl_frameset_get_position_const(frames, 0));
512  }
513  cpl_frameset_delete(frames);
514  return frame;
515 } /* muse_frameset_find_master() */
516 
517 /* Remove .fits and possibly -nn from a filename of the style *
518  * "SOME_TAG_iiii-nn.fits". *
519  * The returned string has to be deallocated using cpl_free(). */
520 static char *
521 muse_utils_frame_get_basefilename(const cpl_frame *aFrame)
522 {
523  char *filename = cpl_strdup(cpl_frame_get_filename(aFrame)),
524  *end = strstr(filename, ".fits");
525  if (end) {
526  *end = '\0'; /* strip off ".fits" */
527  }
528  end = strrchr(filename, '-');
529  if (end) {
530  *end = '\0'; /* strip off the "-nn" extension number */
531  }
532  return filename;
533 } /* muse_utils_frame_get_basefilename() */
534 
535 /* Compare the base filenames of two frames. *
536  * To be used with cpl_frameset_labelise(). */
537 static int
538 muse_utils_frames_compare_basenames(const cpl_frame *aF1, const cpl_frame *aF2)
539 {
540  cpl_ensure(aF1 && aF2, CPL_ERROR_NULL_INPUT, -1);
541  cpl_ensure(cpl_frame_get_filename(aF1) && cpl_frame_get_filename(aF2),
542  CPL_ERROR_DATA_NOT_FOUND, -1);
543  char *fn1 = muse_utils_frame_get_basefilename(aF1),
544  *fn2 = muse_utils_frame_get_basefilename(aF2);
545  int cmp = strcmp(fn1, fn2);
546  cpl_free(fn1);
547  cpl_free(fn2);
548  return cmp == 0 ? 1 : 0;
549 } /* muse_utils_frames_compare_basenames() */
550 
551 /* Compare the full filenames of two frames. *
552  * To be used with cpl_frameset_sort(). */
553 static int
554 muse_utils_frame_compare(const cpl_frame *aF1, const cpl_frame *aF2)
555 {
556  const char *fn1 = cpl_frame_get_filename(aF1),
557  *fn2 = cpl_frame_get_filename(aF2);
558  int cmp = strcmp(fn1, fn2);
559  return (cmp < 0) ? -1 : (cmp > 0) ? 1 : 0;
560 }
561 
562 /*---------------------------------------------------------------------------*/
587 /*---------------------------------------------------------------------------*/
588 cpl_error_code
589 muse_utils_frameset_merge_frames(cpl_frameset *aFrames, cpl_boolean aDelete)
590 {
591  cpl_ensure_code(aFrames, CPL_ERROR_NULL_INPUT);
592  /* better fail here on empty frameset than in cpl_frameset_labelise() */
593  cpl_ensure_code(cpl_frameset_get_size(aFrames) > 0, CPL_ERROR_ILLEGAL_INPUT);
594 #if 0
595  printf("final out frames:\n");
596  cpl_frameset_dump(aFrames, stdout);
597  fflush(stdout);
598 #endif
599 
600  /* set up keys and regular expression (positive and negative ones!) *
601  * for the merged extensions */
602 #define EXTKEYS1 MUSE_WCS_KEYS"|(ESO DET (CHIP|OUT[1-9]*) |ESO QC|ESO DRS)"
603 #define EXTKEYS2 MUSE_WCS_KEYS"|^B(UNIT|SCALE|ZERO)"
604  cpl_regex *regex = cpl_regex_new(EXTKEYS1, TRUE, CPL_REGEX_EXTENDED),
605  *nregex = cpl_regex_new(EXTKEYS1, FALSE, CPL_REGEX_EXTENDED),
606  *nregex2 = cpl_regex_new(EXTKEYS1"|"EXTKEYS2, FALSE,
607  CPL_REGEX_EXTENDED);
608 
609  /* check sets of output frames */
610  cpl_frameset *frames = cpl_frameset_new();
611 
612  cpl_size ilabel;
613  cpl_size nlabels = 0;
614  cpl_size *labels = cpl_frameset_labelise(aFrames,
615  muse_utils_frames_compare_basenames,
616  &nlabels);
617  for (ilabel = 0; ilabel < nlabels; ilabel++) {
618  cpl_frameset *fset = cpl_frameset_extract(aFrames, labels, ilabel);
619  /* sort the frameset to get a nice ordering in the merged output, *
620  * and to get the primary header propagated from the first IFU */
621  cpl_frameset_sort(fset, muse_utils_frame_compare);
622 
623  cpl_frame *frame = cpl_frameset_get_position(fset, 0);
624  const char *tag = cpl_frame_get_tag(frame);
625  /* XXX This is a workaround to exclude pixel tables from being *
626  * merged, since merged pixel tables can not yet be used by *
627  * the post-processing recipes. */
628  if (strncmp(tag, "PIXTABLE_", 9) == 0) {
629  cpl_frameset_delete(fset);
630  continue;
631  }
632 
633  int n = cpl_frameset_get_size(fset);
634  if (n <= 1) {
635  cpl_msg_warning(__func__, "Nothing to merge for tag %s (%d frames)!",
636  tag, n);
637  cpl_frameset_delete(fset);
638  continue;
639  }
640 
641  cpl_multiframe *mf = cpl_multiframe_new(frame, "", regex);
642  if (!mf) {
643  cpl_frameset_delete(fset);
644  continue;
645  }
646 
647  /* now loop through all frames with this tag and append them to the group */
648  int i;
649  for (i = 0; i < n; i++) {
650  frame = cpl_frameset_get_position(fset, i);
651  const char *fn = cpl_frame_get_filename(frame);
652  cpl_msg_debug(__func__, "Merging \"%s\".", fn);
653  /* check, if this is a three-extension muse_image or something else */
654  int extdata = cpl_fits_find_extension(fn, EXTNAME_DATA),
655  extdq = cpl_fits_find_extension(fn, EXTNAME_DQ),
656  extstat = cpl_fits_find_extension(fn, EXTNAME_STAT);
657  cpl_errorstate state = cpl_errorstate_get();
658  if (extdata > 0 && extdq > 0 && extstat > 0) {
659  /* is a three-extension muse_image */
660  const char *extnames[] = { EXTNAME_DATA, EXTNAME_DQ, EXTNAME_STAT },
661  *updkeys[] = { "SCIDATA", "ERRDATA", "QUALDATA", NULL };
662  const cpl_regex *filters[] = { nregex, nregex, nregex };
663  cpl_multiframe_append_datagroup(mf, ".", frame, 3, extnames, filters,
664  NULL, updkeys, CPL_MULTIFRAME_ID_JOIN);
665  } else if (cpl_fits_count_extensions(fn) == 0) {
666  /* is an image in the primary HDU *
667  * needs a different set of keywords to transfer the relevant stuff */
668  cpl_multiframe_append_dataset_from_position(mf, ".", frame, 0,
669  nregex2, NULL,
670  CPL_MULTIFRAME_ID_JOIN);
671  } else {
672  /* is a table or multi-extension file */
673  int iext, next = cpl_fits_count_extensions(fn);
674  for (iext = 1; iext <= next; iext++) {
675  cpl_multiframe_append_dataset_from_position(mf, ".", frame, iext,
676  nregex, NULL,
677  CPL_MULTIFRAME_ID_JOIN);
678  } /* for iext (all extensions) */
679  } /* else */
680 
681  if (!cpl_errorstate_is_equal(state)) {
682  cpl_msg_error(__func__, "Appending data of \"%s\" for merging failed: %s",
683  fn, cpl_error_get_message());
684  } /* if error */
685  } /* for i (all frames with this tag) */
686 
687  /* now finally write the merged file */
688  char *basefn = muse_utils_frame_get_basefilename(frame),
689  *outfn = cpl_sprintf("%s.fits", basefn);
690  cpl_free(basefn);
691  cpl_errorstate state = cpl_errorstate_get();
692  cpl_multiframe_write(mf, outfn);
693  if (!cpl_errorstate_is_equal(state)) {
694  cpl_msg_error(__func__, "Writing merged data to \"%s\" failed: %s",
695  outfn, cpl_error_get_message());
696  } else {
697  cpl_frame_set_filename(frame, outfn);
698  /* set the frame's group to product, to easily *
699  * set them apart from other input frames */
700  cpl_frame_set_group(frame, CPL_FRAME_GROUP_PRODUCT);
701  /* record the merged file in the resulting frameset */
702  cpl_frameset_insert(frames, cpl_frame_duplicate(frame));
703  }
704  cpl_free(outfn);
705  cpl_multiframe_delete(mf);
706  cpl_frameset_delete(fset);
707  } /* for ilabel (all frame labels) */
708  cpl_regex_delete(regex);
709  cpl_regex_delete(nregex);
710  cpl_regex_delete(nregex2);
711  cpl_free(labels);
712 #if 0
713  printf("\naFrames (trying to remove these):\n");
714  cpl_frameset_dump(aFrames, stdout);
715  fflush(stdout);
716  printf("\nmerged frames:\n");
717  cpl_frameset_dump(frames, stdout);
718  fflush(stdout);
719 #endif
720 
721  /* remove all frames for the successfully merged tags */
722  int iframe, nframes = cpl_frameset_get_size(frames);
723  for (iframe = 0; aDelete == CPL_TRUE && iframe < nframes; iframe++) {
724  cpl_frame *frame = cpl_frameset_get_position(frames, iframe);
725 #if 1
726  cpl_msg_debug(__func__, "===== Starting to compare \"%s\" =====",
727  cpl_frame_get_filename(frame));
728 #endif
729  int iframe2;
730  for (iframe2 = 0; iframe2 < cpl_frameset_get_size(aFrames); iframe2++) {
731  /* duplicate, so that we can use it to remove "itself" */
732  cpl_frame *frame2 = cpl_frameset_get_position(aFrames, iframe2);
733  if (muse_utils_frames_compare_basenames(frame, frame2) == 1) {
734  const char *fn = cpl_frame_get_filename(frame2);
735 #if 1
736  char *fb1 = muse_utils_frame_get_basefilename(frame),
737  *fb2 = muse_utils_frame_get_basefilename(frame2);
738  cpl_msg_debug(__func__, "Removing \"%s\" (\"%s\" vs \"%s\").",
739  fn, fb1, fb2);
740  cpl_free(fb1);
741  cpl_free(fb2);
742 #endif
743  remove(fn);
744  cpl_frameset_erase_frame(aFrames, frame2);
745  iframe2--; /* stay here to see what moved here */
746  } /* if */
747  } /* for iframe2 */
748  } /* for iframe (all frames in new frameset) */
749  cpl_frameset_join(aFrames, frames);
750  cpl_frameset_delete(frames);
751 #if 0
752  printf("\nmerged out frames:\n");
753  cpl_frameset_dump(aFrames, stdout);
754  fflush(stdout);
755 #endif
756  return CPL_ERROR_NONE;
757 } /* muse_utils_frameset_merge_frames() */
758 
759 /*----------------------------------------------------------------------------*/
767 /*----------------------------------------------------------------------------*/
769  { "lambda", CPL_TYPE_DOUBLE, "Angstrom", "%7.2f", "wavelength", CPL_TRUE},
770  { "throughput", CPL_TYPE_DOUBLE, "", "%.4e",
771  "filter response (in fractions of 1)", CPL_TRUE},
772  { NULL, 0, NULL, NULL, NULL, CPL_FALSE }
773 };
774 
775 /*---------------------------------------------------------------------------*/
797 /*---------------------------------------------------------------------------*/
798 muse_table *
799 muse_table_load_filter(muse_processing *aProcessing, const char *aFilterName)
800 {
801  cpl_ensure(aFilterName, CPL_ERROR_NULL_INPUT, NULL);
802  if (!strncasecmp(aFilterName, "none", 4)) {
803  cpl_msg_debug(__func__, "No filter wanted (filter \"%s\")", aFilterName);
804  return NULL;
805  }
806  if (!strcmp(aFilterName, "white")) {
807  cpl_msg_debug(__func__, "White-light integration wanted (filter \"%s\")",
808  aFilterName);
809  /* create minimal rectangular filter function table */
810  cpl_table *table = muse_cpltable_new(muse_filtertable_def, 4);
811  cpl_table_set(table, "lambda", 0, kMuseNominalLambdaMin - 1e-5);
812  cpl_table_set(table, "lambda", 1, kMuseNominalLambdaMin);
813  cpl_table_set(table, "lambda", 2, kMuseNominalLambdaMax);
814  cpl_table_set(table, "lambda", 3, kMuseNominalLambdaMax - 1e-5);
815  cpl_table_set(table, "throughput", 0, 0.);
816  cpl_table_set(table, "throughput", 1, 1.);
817  cpl_table_set(table, "throughput", 2, 1.);
818  cpl_table_set(table, "throughput", 3, 0.);
819  /* now convert into MUSE table with header */
820  muse_table *mt = muse_table_new();
821  mt->table = table;
822  mt->header = cpl_propertylist_new();
823  cpl_propertylist_append_string(mt->header, "EXTNAME", "white");
824  return mt;
825  } /* if white-light filter */
826  cpl_frame *frame = muse_frameset_find_master(aProcessing->inframes,
827  MUSE_TAG_FILTER_LIST, 0);
828  if (!frame) {
829  cpl_error_set_message(__func__, CPL_ERROR_FILE_NOT_FOUND, "%s (for filter "
830  "\"%s\") is missing", MUSE_TAG_FILTER_LIST,
831  aFilterName);
832  return NULL;
833  }
834 
835  /* try to load the extension table for the given filter, using EXTNAME */
836  char *filename = (char *)cpl_frame_get_filename(frame);
837  int ext = cpl_fits_find_extension(filename, aFilterName);
838  if (ext <= 0) {
839  cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND, "\"%s\" does not "
840  "contain filter \"%s\"", filename, aFilterName);
841  cpl_frame_delete(frame);
842  return NULL;
843  }
844 
845  /* load table, and create header composed of primary plus extension keys */
846  muse_table *table = muse_table_new();
847  table->header = cpl_propertylist_load(filename, 0); /* primay header */
848  if (!table->header) {
849  cpl_error_set_message(__func__, cpl_error_get_code(), "loading filter "
850  "\"%s\" from file \"%s\" (ext %d) failed",
851  aFilterName, filename, ext);
852  cpl_frame_delete(frame);
853  muse_table_delete(table);
854  return NULL;
855  }
856  /* load the table itself */
857  table->table = cpl_table_load(filename, ext, 1);
858  if (!table->table || !cpl_table_get_nrow(table->table)) {
859  cpl_error_set_message(__func__, cpl_error_get_code(), "loading filter "
860  "\"%s\" from file \"%s\" (ext %d) failed",
861  aFilterName, filename, ext);
862  cpl_frame_delete(frame);
863  muse_table_delete(table);
864  return NULL;
865  }
866  /* merge in important keywords from the extension header */
867  cpl_propertylist *hext = cpl_propertylist_load(filename, ext);
868  cpl_propertylist_copy_property_regexp(table->header, hext,
869  "^EXTNAME$|^Z|^COMMENT", 0);
870  cpl_propertylist_delete(hext);
871 
872  cpl_msg_info(__func__, "loaded filter \"%s\" from file \"%s\" (ext %d)",
873  aFilterName, filename, ext);
874  /* filters from the input frameset should be listed, append them into *
875  * the used frames for the FITS header of the output product, if they exist */
876  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_CALIB, 0);
877  return table;
878 } /* muse_table_load_filter() */
879 
880 /*---------------------------------------------------------------------------*/
891 /*---------------------------------------------------------------------------*/
892 double
893 muse_utils_filter_fraction(const muse_table *aFilter, double aLambda1,
894  double aLambda2)
895 {
896  cpl_ensure(aFilter && aFilter->table, CPL_ERROR_NULL_INPUT, -1.);
897 
898  /* get the extreme wavelengths of the filter function */
899  const cpl_table *table = aFilter->table; /* shortcut */
900  int nrow = cpl_table_get_nrow(table);
901  double lbda1 = cpl_table_get(table, "lambda", 0, NULL),
902  lbda2 = cpl_table_get(table, "lambda", nrow - 1, NULL);
903 
904  /* go through all wavelengths with constant delta-lambda = 1 *
905  * and compute the summed filter area, over the total filter *
906  * and over the the wavelength range given (by the data) */
907  double sumt = 0., /* the total sum */
908  sum2 = 0., /* the sum over the wavelength range */
909  lambda;
910  for (lambda = lbda1; lambda <= lbda2; lambda++) {
911  double thruput = muse_flux_response_interpolate(table, lambda, NULL,
913  sumt += thruput;
914  if (lambda >= aLambda1 && lambda <= aLambda2) {
915  sum2 += thruput;
916  }
917  } /* for lambda (all wavelengths in filter) */
918  return sum2 / sumt; /* return the fraction */
919 } /* muse_utils_filter_fraction() */
920 
921 /*---------------------------------------------------------------------------*/
932 /*---------------------------------------------------------------------------*/
933 cpl_error_code
934 muse_utils_filter_copy_properties(cpl_propertylist *aHeader,
935  const muse_table *aFilter, double aFraction)
936 {
937  cpl_ensure_code(aHeader && aFilter && aFilter->header, CPL_ERROR_NULL_INPUT);
938 
939  /* if a filter was passed, propagate name and photometric zeropoints *
940  * from filter and add coverage fraction */
941  cpl_propertylist_update_string(aHeader, MUSE_HDR_FILTER,
942  cpl_propertylist_get_string(aFilter->header,
943  "EXTNAME"));
944  cpl_propertylist_set_comment(aHeader, MUSE_HDR_FILTER, MUSE_HDR_FILTER_C);
945  if (cpl_propertylist_has(aFilter->header, "ZP_VEGA")) {
946  cpl_propertylist_update_double(aHeader, MUSE_HDR_FILTER_ZPVEGA,
947  cpl_propertylist_get_double(aFilter->header,
948  "ZP_VEGA"));
949  cpl_propertylist_set_comment(aHeader, MUSE_HDR_FILTER_ZPVEGA,
950  MUSE_HDR_FILTER_ZPVEGA_C);
951  }
952  if (cpl_propertylist_has(aFilter->header, "ZP_AB")) {
953  cpl_propertylist_update_double(aHeader, MUSE_HDR_FILTER_ZPAB,
954  cpl_propertylist_get_double(aFilter->header,
955  "ZP_AB"));
956  cpl_propertylist_set_comment(aHeader, MUSE_HDR_FILTER_ZPAB,
957  MUSE_HDR_FILTER_ZPAB_C);
958  }
959  cpl_propertylist_update_float(aHeader, MUSE_HDR_FILTER_FFRAC,
960  aFraction * 100.);
961  cpl_propertylist_set_comment(aHeader, MUSE_HDR_FILTER_FFRAC,
962  MUSE_HDR_FILTER_FFRAC_C);
963 
964  return CPL_ERROR_NONE;
965 } /* muse_utils_filter_copy_properties() */
966 
967 /*---------------------------------------------------------------------------*/
987 /*---------------------------------------------------------------------------*/
988 char *
989 muse_utils_header_get_lamp_names(cpl_propertylist *aHeader, char aSep)
990 {
991  cpl_ensure(aHeader, CPL_ERROR_NULL_INPUT, NULL);
992 
993  char *lampname = NULL;
994  int n, nlamps = muse_pfits_get_lampnum(aHeader);
995  for (n = 1; n <= nlamps; n++) {
996  cpl_errorstate prestate = cpl_errorstate_get();
997  int st1 = muse_pfits_get_lamp_status(aHeader, n),
998  st2 = muse_pfits_get_shut_status(aHeader, n);
999  if (!cpl_errorstate_is_equal(prestate)) {
1000  cpl_errorstate_set(prestate); /* lamp headers may be missing */
1001  }
1002  if (!st1 || !st2) {
1003  continue; /* lamp off or its shutter closed */
1004  }
1005  char *name = /* XXX ! */(char *)muse_pfits_get_lamp_name(aHeader, n);
1006  if (!strncmp(name, "CU-LAMP-", 8)) {
1007  name += 8; /* ignore the lamp prefix */
1008  }
1009  if (!strcmp(name, "CU-LAMP3") || !strcmp(name, "CU-LAMP6")) { /* XXX overwrite read-only locations */
1010  /* 3 is standard Neon, 6 is high-power Neon */
1011  name[0] = 'N';
1012  name[1] = 'e';
1013  name[2] = '\0';
1014  } else if (!strcmp(name, "CU-LAMP4")) {
1015  name[0] = 'X';
1016  name[1] = 'e';
1017  name[2] = '\0';
1018  } else if (!strcmp(name, "CU-LAMP5")) {
1019  name[0] = 'H';
1020  name[1] = 'g';
1021  name[2] = 'C';
1022  name[3] = 'd';
1023  name[4] = '\0';
1024  }
1025  if (lampname) {
1026  char *temp = lampname;
1027  lampname = cpl_sprintf("%s%c%s", temp, aSep, name);
1028  cpl_free(temp);
1029  } else {
1030  lampname = cpl_sprintf("%s", name);
1031  }
1032  } /* for n (all possible lamps) */
1033  return lampname;
1034 } /* muse_utils_header_get_lamp_names() */
1035 
1036 /*---------------------------------------------------------------------------*/
1054 /*---------------------------------------------------------------------------*/
1055 cpl_array *
1056 muse_utils_header_get_lamp_numbers(cpl_propertylist *aHeader)
1057 {
1058  cpl_ensure(aHeader, CPL_ERROR_NULL_INPUT, NULL);
1059 
1060  /* start with an array of zero length */
1061  cpl_array *lampnumbers = cpl_array_new(0, CPL_TYPE_INT);
1062  int n, nlamps = muse_pfits_get_lampnum(aHeader);
1063  for (n = 1; n <= nlamps; n++) {
1064  cpl_errorstate prestate = cpl_errorstate_get();
1065  int st1 = muse_pfits_get_lamp_status(aHeader, n),
1066  st2 = muse_pfits_get_shut_status(aHeader, n);
1067  if (!cpl_errorstate_is_equal(prestate)) {
1068  cpl_errorstate_set(prestate); /* lamp headers may be missing */
1069  }
1070  if (!st1 || !st2) {
1071  continue; /* lamp off or its shutter closed */
1072  }
1073  /* now enlarge the array and save the lamp numbers as last element */
1074  cpl_array_set_size(lampnumbers, cpl_array_get_size(lampnumbers) + 1);
1075  cpl_array_set_int(lampnumbers, cpl_array_get_size(lampnumbers) - 1,
1076  n);
1077  } /* for n (all possible lamps) */
1078  if (cpl_array_get_size(lampnumbers) < 1) {
1079  cpl_array_delete(lampnumbers);
1080  lampnumbers = NULL;
1081  }
1082  return lampnumbers;
1083 } /* muse_utils_header_get_lamp_numbers() */
1084 
1085 /*----------------------------------------------------------------------------*/
1097 /*----------------------------------------------------------------------------*/
1098 cpl_matrix *
1099 muse_matrix_new_gaussian_2d(int aXHalfwidth, int aYHalfwidth, double aSigma)
1100 {
1101  cpl_matrix *kernel = cpl_matrix_new(2*aXHalfwidth+1, 2*aYHalfwidth+1);
1102  if (!kernel) {
1103  cpl_msg_error(__func__, "Could not create matrix: %s",
1104  cpl_error_get_message());
1105  return NULL;
1106  }
1107  double sum = 0.;
1108  int i;
1109  for (i = -aXHalfwidth; i <= aXHalfwidth; i++) {
1110  int j;
1111  for (j = -aYHalfwidth; j <= aYHalfwidth; j++) {
1112  /* set Gaussian kernel */
1113  double gauss = 1. / (aSigma*sqrt(2.*CPL_MATH_PI))
1114  * exp(-(i*i + j*j) / (2.*aSigma*aSigma));
1115  cpl_matrix_set(kernel, i+aXHalfwidth, j+aYHalfwidth, gauss);
1116  sum += gauss;
1117  }
1118  }
1119  /* normalize the matrix, the sum of the elements should be 1 */
1120  cpl_matrix_divide_scalar(kernel, sum);
1121 
1122  return kernel;
1123 } /* muse_matrix_new_gaussian_2d */
1124 
1125 /*----------------------------------------------------------------------------*/
1145 /*----------------------------------------------------------------------------*/
1146 cpl_image *
1147 muse_utils_image_fit_polynomial(const cpl_image *aImage, unsigned short aXOrder,
1148  unsigned short aYOrder)
1149 {
1150  cpl_ensure(aImage, CPL_ERROR_NULL_INPUT, NULL);
1151 
1152  /* transfer all pixel positions and their values *
1153  * into matrix and vector for the polynomial fit */
1154  int nx = cpl_image_get_size_x(aImage),
1155  ny = cpl_image_get_size_y(aImage);
1156  cpl_matrix *pos = cpl_matrix_new(2, nx * ny);
1157  cpl_vector *val = cpl_vector_new(nx * ny);
1158  int i, j, np = 0;
1159  for (i = 1; i < nx; i++) {
1160  for (j = 1; j < ny; j++) {
1161  if (cpl_image_is_rejected(aImage, i, j)) {
1162  continue;
1163  }
1164  cpl_matrix_set(pos, 0, np, i);
1165  cpl_matrix_set(pos, 1, np, j);
1166  int err;
1167  cpl_vector_set(val, np, cpl_image_get(aImage, i, j, &err));
1168  np++;
1169  } /* for j (vertical pixels) */
1170  } /* for i (horizontal pixels) */
1171  /* vectors and matrices cannot be resized to have zero elements, exit early */
1172  if (!np) {
1173  cpl_matrix_delete(pos);
1174  cpl_vector_delete(val);
1175  cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND, "No good pixels "
1176  "found in image, polynomial fit cannot be performed!");
1177  return NULL;
1178  }
1179  /* re-set sizes depending on the number of valid pixels found */
1180  cpl_matrix_set_size(pos, 2, np);
1181  cpl_vector_set_size(val, np);
1182 
1183  /* carry out the fit */
1184  cpl_polynomial *poly = cpl_polynomial_new(2);
1185  const cpl_boolean sym = CPL_FALSE;
1186  const cpl_size mindeg[] = { 0, 0 },
1187  maxdeg[] = { aXOrder, aYOrder };
1188  cpl_error_code rc = cpl_polynomial_fit(poly, pos, &sym, val, NULL, CPL_TRUE,
1189  mindeg, maxdeg);
1190  cpl_matrix_delete(pos);
1191  cpl_vector_delete(val);
1192 
1193  /* create the output image from the polynomial, if the fit succeeded */
1194  cpl_image *fit = NULL;
1195  if (rc == CPL_ERROR_NONE) {
1196  fit = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
1197  cpl_image_fill_polynomial(fit, poly, 1, 1, 1, 1);
1198  if (cpl_image_get_bpm_const(aImage)) {
1199  cpl_image_reject_from_mask(fit, cpl_image_get_bpm_const(aImage));
1200  } /* if input has bpm */
1201  } /* if fit without error */
1202  cpl_polynomial_delete(poly);
1203  return fit;
1204 } /* muse_utils_image_fit_polynomial() */
1205 
1206 /*----------------------------------------------------------------------------*/
1232 /*----------------------------------------------------------------------------*/
1233 cpl_error_code
1234 muse_utils_image_get_centroid_window(cpl_image *aImage, int aX1, int aY1,
1235  int aX2, int aY2, double *aX, double *aY,
1236  muse_utils_centroid_type aBgType)
1237 {
1238  cpl_ensure_code(aImage, CPL_ERROR_NULL_INPUT);
1239  cpl_ensure_code(aX || aY, CPL_ERROR_NULL_INPUT);
1240 
1241  cpl_image *im = cpl_image_extract(aImage, aX1, aY1, aX2, aY2);
1242  cpl_ensure_code(im, cpl_error_get_code());
1243 
1244  double bg = 0.; /* subtract the background */
1245  if (aBgType == MUSE_UTILS_CENTROID_MEAN) {
1246  bg = cpl_image_get_mean(im);
1247  } else if (aBgType == MUSE_UTILS_CENTROID_MEDIAN) {
1248  bg = cpl_image_get_median(im);
1249  } else {
1250  cpl_ensure_code(aBgType == MUSE_UTILS_CENTROID_NORMAL,
1251  CPL_ERROR_ILLEGAL_INPUT);
1252  }
1253  cpl_image_subtract_scalar(im, bg);
1254 
1255  /* centroid in x direction */
1256  if (aX) {
1257  cpl_image *row = cpl_image_collapse_create(im, 0);
1258  double w = 0., /* weight */
1259  f = 0.; /* flux */
1260  int i, n = cpl_image_get_size_x(row);
1261  for (i = 1; i <= n; i++) {
1262  int err;
1263  double value = cpl_image_get(row, i, 1, &err);
1264  if (err || (value < 0 && aBgType != MUSE_UTILS_CENTROID_NORMAL)) {
1265  continue;
1266  }
1267  w += i * value;
1268  f += value;
1269  } /* for i */
1270  *aX = w / f + aX1 - 1;
1271  cpl_image_delete(row);
1272  } /* if aX */
1273  /* centroid in y direction */
1274  if (aY) {
1275  cpl_image *col = cpl_image_collapse_create(im, 1);
1276  double w = 0., /* weight */
1277  f = 0.; /* flux */
1278  int j, n = cpl_image_get_size_y(col);
1279  for (j = 1; j <= n; j++) {
1280  int err;
1281  double value = cpl_image_get(col, 1, j, &err);
1282  if (err || (value < 0 && aBgType != MUSE_UTILS_CENTROID_NORMAL)) {
1283  continue;
1284  }
1285  w += j * value;
1286  f += value;
1287  } /* for j */
1288  *aY = w / f + aY1 - 1;
1289  cpl_image_delete(col);
1290  } /* if aY */
1291  cpl_image_delete(im);
1292 
1293  return CPL_ERROR_NONE;
1294 } /* muse_utils_image_get_centroid_window() */
1295 
1296 /*---------------------------------------------------------------------------*/
1326 /*---------------------------------------------------------------------------*/
1327 cpl_error_code
1328 muse_utils_get_centroid(const cpl_matrix *aPositions,
1329  const cpl_vector *aValues, const cpl_vector *aErrors,
1330  double *aX, double *aY, muse_utils_centroid_type aBgType)
1331 {
1332  cpl_ensure_code(aPositions && aValues, CPL_ERROR_NULL_INPUT);
1333  cpl_ensure_code(cpl_matrix_get_ncol(aPositions) == 2, CPL_ERROR_ILLEGAL_INPUT);
1334  int npoints = cpl_matrix_get_nrow(aPositions);
1335  cpl_ensure_code(npoints == cpl_vector_get_size(aValues), CPL_ERROR_ILLEGAL_INPUT);
1336  if (aErrors) {
1337  cpl_ensure_code(cpl_vector_get_size(aValues) == cpl_vector_get_size(aErrors),
1338  CPL_ERROR_ILLEGAL_INPUT);
1339  }
1340  cpl_ensure_code(aX || aY, CPL_ERROR_NULL_INPUT);
1341 
1342  const double *values = cpl_vector_get_data_const(aValues);
1343  double xcen = 0., ycen = 0.,
1344  weight = 0., bg = 0.;
1345  if (aBgType == MUSE_UTILS_CENTROID_MEAN) {
1346  bg = cpl_vector_get_mean(aValues);
1347  } else if (aBgType == MUSE_UTILS_CENTROID_MEDIAN) {
1348  bg = cpl_vector_get_median_const(aValues);
1349  } else {
1350  cpl_ensure_code(aBgType == MUSE_UTILS_CENTROID_NORMAL,
1351  CPL_ERROR_ILLEGAL_INPUT);
1352  }
1353 
1354  int i;
1355  for (i = 0; i < npoints; i++) {
1356  double w = values[i] - bg;
1357  if (w < 0 && aBgType != MUSE_UTILS_CENTROID_NORMAL) {
1358  continue;
1359  }
1360  if (aErrors) {
1361  w /= cpl_vector_get(aErrors, i);
1362  }
1363  xcen += cpl_matrix_get(aPositions, i, 0) * w;
1364  ycen += cpl_matrix_get(aPositions, i, 1) * w;
1365  weight += w;
1366  } /* for i */
1367  xcen /= weight;
1368  ycen /= weight;
1369 
1370  if (aX) {
1371  *aX = xcen;
1372  }
1373  if (aY) {
1374  *aY = ycen;
1375  }
1376  return CPL_ERROR_NONE;
1377 } /* muse_utils_get_centroid() */
1378 
1379 /*----------------------------------------------------------------------------*/
1402 /*----------------------------------------------------------------------------*/
1403 static int
1404 muse_utils_multigauss(const double x[], const double p[], double *f)
1405 {
1406  const double xp = x[0]; /* evaluation point */
1407  const cpl_size ncoeffs = p[0],
1408  npeaks = p[1];
1409  const double sigma = p[2 + ncoeffs];
1410  if (sigma == 0.0) { /* special case, Dirac deltas */
1411  cpl_size i;
1412  for (i = 0; i < npeaks; i++) {
1413  if (p[2 + ncoeffs + 1 + i] == xp) {
1414  *f = DBL_MAX;
1415  return 0;
1416  }
1417  }
1418  *f = 0.;
1419  return 0;
1420  }
1421 
1422  /* compute the function value for the normal case */
1423  *f = 0.;
1424  /* evaluate the polynomial, adding all orders to the function value */
1425  cpl_size ic;
1426  for (ic = 0; ic < ncoeffs; ic++) {
1427  *f += p[2 + ic] * pow(xp, ic);
1428  }
1429  /* evaluate the Gaussians, adding all peaks to the function value */
1430  cpl_size ip;
1431  for (ip = 0; ip < npeaks; ip++) {
1432  const double xi = p[3 + ncoeffs + ip],
1433  Ai = p[3 + ncoeffs + npeaks + ip],
1434  exponent = (xi - xp) / sigma;
1435  *f += Ai / CPL_MATH_SQRT2PI / sigma * exp(-0.5 * exponent*exponent);
1436  }
1437 #if 0
1438  printf("eval at %f --> %f\n", xp, *f);
1439  int i;
1440  for (i = 0; i < ncoeffs + 2*npeaks + 3; i++) {
1441  printf(" [%02d] %f\n", i, p[i]);
1442  }
1443  fflush(stdout);
1444 #endif
1445  return 0;
1446 } /* muse_utils_multigauss() */
1447 
1448 /*----------------------------------------------------------------------------*/
1458 /*----------------------------------------------------------------------------*/
1459 static int
1460 muse_utils_dmultigauss(const double x[], const double p[], double f[])
1461 {
1462  const cpl_size ncoeffs = p[0],
1463  npeaks = p[1];
1464  const double sigma = p[2 + ncoeffs];
1465  if (sigma == 0.0) { /* special case, Dirac deltas */
1466  memset(f, 0, sizeof(double) * (ncoeffs + 2*npeaks + 3));
1467  return 0;
1468  }
1469 
1470  const double xp = x[0]; /* evaluation point */
1471  f[0] = f[1] = 0.; /* derivatives of the number of parameters are always zero! */
1472  /* the derivatives by the coefficients of the polynomial */
1473  cpl_size ic;
1474  for (ic = 0; ic < ncoeffs; ic++) {
1475  f[2 + ic] = pow(xp, ic);
1476  }
1477  /* derivative regarding sigma */
1478  f[2 + ncoeffs] = 0.;
1479  cpl_size ip;
1480  for (ip = 0; ip < npeaks; ip++) {
1481  const double xi = p[3 + ncoeffs + ip],
1482  Ai = p[3 + ncoeffs + npeaks + ip],
1483  exponent = (xi - xp) / sigma,
1484  expsq = exponent * exponent,
1485  expfunc = exp(-0.5 * expsq);
1486  f[2 + ncoeffs] -= Ai / CPL_MATH_SQRT2PI / (sigma*sigma)
1487  * (1 - expsq) * expfunc;
1488  /* derivative regarding centers */
1489  f[3 + ncoeffs + ip] = -Ai / CPL_MATH_SQRT2PI / (sigma*sigma*sigma)
1490  * (xi - xp) * expfunc;
1491  /* derivative regarding fluxes */
1492  f[3 + ncoeffs + npeaks + ip] = 1 / CPL_MATH_SQRT2PI / sigma * expfunc;
1493  } /* for ip (peak indices) */
1494 #if 0
1495  printf("deval at %f -->\n", xp);
1496  int i;
1497  for (i = 0; i < ncoeffs + 2*npeaks + 3; i++) {
1498  printf(" [%02d] %e %f\n", i, f[i], p[i]);
1499  }
1500  fflush(stdout);
1501 #endif
1502  return 0;
1503 } /* muse_utils_dmultigauss() */
1504 
1505 /*----------------------------------------------------------------------------*/
1556 /*----------------------------------------------------------------------------*/
1557 cpl_error_code
1558 muse_utils_fit_multigauss_1d(const cpl_vector *aX, const cpl_bivector *aY,
1559  cpl_vector *aCenter, double *aSigma,
1560  cpl_vector *aFlux, cpl_vector *aPoly,
1561  double *aMSE, double *aRedChisq,
1562  cpl_matrix **aCovariance)
1563 {
1564  if (aCovariance) {
1565  *aCovariance = NULL;
1566  }
1567  cpl_ensure_code(aX && aY && aCenter && aSigma, CPL_ERROR_NULL_INPUT);
1568  cpl_size npoints = cpl_vector_get_size(aX);
1569  cpl_ensure_code(npoints == cpl_bivector_get_size(aY), CPL_ERROR_INCOMPATIBLE_INPUT);
1570  cpl_size npeaks = cpl_vector_get_size(aCenter);
1571  cpl_ensure_code(!aFlux || npeaks == cpl_vector_get_size(aFlux),
1572  CPL_ERROR_INCOMPATIBLE_INPUT);
1573  cpl_size ncoeffs = aPoly ? cpl_vector_get_size(aPoly) : 0,
1574  npars = ncoeffs /* poly coeffs */ + 1 /* sigma */
1575  + 2 * npeaks /* centers and fluxes */;
1576  cpl_ensure_code(!aRedChisq || npoints >= npars, CPL_ERROR_ILLEGAL_INPUT);
1577 
1578  /* "transform" the input data into the right structures for cpl_fit_lvmq() */
1579  cpl_matrix *x = cpl_matrix_wrap(npoints, 1, (double *)cpl_vector_get_data_const(aX));
1580  const cpl_vector *y = cpl_bivector_get_x_const(aY),
1581  *ye = cpl_bivector_get_y_const(aY);
1582  /* set up and fill the parameters structure */
1583  cpl_vector *p = cpl_vector_new(npars + 2);
1584  int *pflags = cpl_calloc(npars + 2, sizeof(int));
1585  /* first the numbers (of polynomial coefficients and peaks) */
1586  cpl_vector_set(p, 0, ncoeffs);
1587  cpl_vector_set(p, 1, npeaks);
1588  cpl_size i; /* all but these two first parameters participate in the fit */
1589  for (i = 2; i < npars + 2; i++) {
1590  pflags[i] = 1; /* fit this parameter, set to non-zero value */
1591  } /* for i (all but the first two parameters) */
1592 #if 0
1593  cpl_array *aflags = cpl_array_wrap_int(pflags, npars + 2);
1594  printf("aflags, non-zero means parameter is fitted by cpl_fit_lvmq():\n");
1595  cpl_array_dump(aflags, 0, 1000, stdout);
1596  fflush(stdout);
1597  cpl_array_unwrap(aflags);
1598 #endif
1599  /* then the polynomial */
1600  cpl_size j;
1601  for (j = 0, i = 2; j < ncoeffs; j++, i++) {
1602  cpl_vector_set(p, i, cpl_vector_get(aPoly, j));
1603  }
1604  /* the common sigma value */
1605  double sigma = fabs(*aSigma);
1606  if (*aSigma < 0) {
1607  pflags[i] = 0; /* fix the sigma parameter */
1608  }
1609  cpl_vector_set(p, i++, sigma);
1610  /* the first-guess centers as passed into this function */
1611  for (j = 0; j < npeaks; j++, i++) {
1612  cpl_vector_set(p, i, cpl_vector_get(aCenter, j));
1613  }
1614  /* the first-guess fluxes, if passed into this function */
1615  for (j = 0; j < npeaks; j++, i++) {
1616  if (aFlux) {
1617  cpl_vector_set(p, i, cpl_vector_get(aFlux, j));
1618  } else {
1619  cpl_vector_set(p, i, 1.);
1620  }
1621  }
1622 #if 0
1623  printf("input parameters p and their pflags:\n");
1624  for (j = 0; j < cpl_vector_get_size(p); j++) {
1625  printf(" [%02d] %f %s\n", j, cpl_vector_get(p, j),
1626  pflags[j] ? "\tfitted" : "constant");
1627  }
1628 #endif
1629  cpl_matrix *covariance = NULL;
1630  cpl_error_code rc = cpl_fit_lvmq(x, NULL, y, ye, p, pflags,
1631  muse_utils_multigauss, muse_utils_dmultigauss,
1632  CPL_FIT_LVMQ_TOLERANCE, CPL_FIT_LVMQ_COUNT,
1633  CPL_FIT_LVMQ_MAXITER, aMSE, aRedChisq,
1634  &covariance);
1635  cpl_matrix_unwrap(x);
1636  cpl_free(pflags);
1637 #if 0
1638  printf("output parameters vector p (%e, %e):\n",
1639  aMSE ? sqrt(*aMSE) : 0.0, aRedChisq ? *aRedChisq : 0.0);
1640  cpl_vector_dump(p, stdout);
1641  fflush(stdout);
1642 #endif
1643  /* get all parameters back into the input structures, same order as above */
1644  for (j = 0, i = 2; j < ncoeffs; j++, i++) {
1645  cpl_vector_set(aPoly, j, cpl_vector_get(p, i));
1646  }
1647  /* In principle, the LM algorithm might have converged to a negative sigma *
1648  * (even if the guess value was positive). Make sure that the returned *
1649  * sigma is positive (by convention), see cpl_vector_fit_gaussian(). */
1650  *aSigma = fabs(cpl_vector_get(p, i++));
1651  for (j = 0; j < npeaks; j++, i++) {
1652  cpl_vector_set(aCenter, j, cpl_vector_get(p, i));
1653  }
1654  /* the first-guess fluxes, if passed into this function */
1655  if (aFlux) {
1656  for (j = 0; j < npeaks; j++, i++) {
1657  cpl_vector_set(aFlux, j, cpl_vector_get(p, i));
1658  }
1659  }
1660  /* extract the relevant part of the covariance matrix, if needed *
1661  * (the number of coefficients and peaks are not relevant parameters!) */
1662  if (aCovariance) {
1663  *aCovariance = cpl_matrix_extract(covariance, 2, 2, 1, 1,
1664  cpl_matrix_get_nrow(covariance) - 2,
1665  cpl_matrix_get_ncol(covariance) - 2);
1666  }
1667  cpl_matrix_delete(covariance);
1668  cpl_vector_delete(p);
1669  return rc;
1670 } /* muse_utils_fit_multigauss_1d() */
1671 
1672 #if MOFFAT_USE_MUSE_OPTIMIZE
1673 /* structure to pass around data needed for the *
1674  * evaluation using muse_moffat_2d_optfunc() */
1675 typedef struct {
1676  const cpl_matrix *positions;
1677  const cpl_vector *values;
1678  const cpl_vector *errors;
1679 } fitdata_t;
1680 
1681 /*---------------------------------------------------------------------------*/
1693 /*---------------------------------------------------------------------------*/
1694 static cpl_error_code
1695 muse_moffat_2d_optfunc(void *aData, cpl_array *aParams, cpl_array *aResiduals)
1696 {
1697  const cpl_matrix *pos = ((fitdata_t *)aData)->positions;
1698  const cpl_vector *val = ((fitdata_t *)aData)->values,
1699  *err = ((fitdata_t *)aData)->errors;
1700  /* Compute function residuals */
1701  const double *p = cpl_array_get_data_double_const(aParams);
1702  double *residuals = cpl_array_get_data_double(aResiduals);
1703  int i, npoints = cpl_vector_get_size(val);
1704  for (i = 0; i < npoints; i++) {
1705  double xterm = (cpl_matrix_get(pos, i, 0) - p[2]) / p[4],
1706  yterm = (cpl_matrix_get(pos, i, 1) - p[3]) / p[5],
1707  crossterm = 2 * p[7] * xterm * yterm,
1708  base = 1 + (xterm*xterm + crossterm + yterm*yterm) / (1 + p[7]*p[7]),
1709  moffat = p[0] + p[1] * (p[6] - 1)
1710  / (CPL_MATH_PI * p[4]*p[5] * sqrt(1 - p[7]*p[7]))
1711  * pow(base, -p[6]);
1712  /* set to the actual value */
1713  residuals[i] = cpl_vector_get(val, i) - moffat;
1714  /* finally weight by the error on this point */
1715  residuals[i] /= cpl_vector_get(err, i);
1716  } /* for i (all points) */
1717  return CPL_ERROR_NONE;
1718 } /* muse_moffat_2d_optfunc() */
1719 
1720 #else /* MOFFAT_USE_MUSE_OPTIMIZE follows */
1721 
1722 /*---------------------------------------------------------------------------*/
1734 /*---------------------------------------------------------------------------*/
1735 static int
1736 muse_moffat_2d_function(const double xy[], const double p[], double *f)
1737 {
1738  double xterm = (xy[0] - p[2]) / p[4], /* xy[0] is x */
1739  yterm = (xy[1] - p[3]) / p[5], /* xy[1] is y */
1740  crossterm = 2 * p[7] * xterm * yterm,
1741  rhoterm = 1. - p[7]*p[7],
1742  base = 1. + (xterm*xterm + crossterm + yterm*yterm) / rhoterm;
1743  *f = p[0] + p[1] * (p[6] - 1.) / (CPL_MATH_PI * p[4]*p[5] * sqrt(rhoterm))
1744  * pow(base, -p[6]);
1745 //printf("%s(%f,%f [%e %e %e %e %e %e %e %e]) = %e\n", __func__, xy[0], xy[1], p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], *f);
1746 //fflush(stdout);
1747  return 0;
1748 } /* muse_moffat_2d_function() */
1749 
1750 /*---------------------------------------------------------------------------*/
1762 /*---------------------------------------------------------------------------*/
1763 static int
1764 muse_moffat_2d_derivative(const double xy[], const double p[], double f[])
1765 {
1766  double xterm = (xy[0] - p[2]) / p[4], /* xy[0] is x */
1767  yterm = (xy[1] - p[3]) / p[5], /* xy[1] is y */
1768  crossterm = 2 * p[7] * xterm * yterm,
1769  rhoterm = 1. - p[7]*p[7],
1770  base = 1. + (xterm*xterm + crossterm + yterm*yterm) / (rhoterm);
1771  f[0] = 1; /* dM(x,y)/dB */
1772  f[1] = (p[6] - 1.) / (CPL_MATH_PI * p[4]*p[5] * sqrt(rhoterm))/* dM(x,y)/dA */
1773  * pow(base, -p[6]);
1774  f[2] = 2 * p[1] * p[6]*(p[6] - 1.) /* dM(x,y)/dxc */
1775  / (CPL_MATH_PI * p[4]*p[4] * p[5] * pow(rhoterm, 3./2.))
1776  * (xterm + p[7] * yterm) * pow(base, -p[6]-1.);
1777  f[3] = 2 * p[1] * p[6]*(p[6] - 1.) /* dM(x,y)/dyc */
1778  / (CPL_MATH_PI * p[4] * p[5]*p[5] * pow(rhoterm, 3./2.))
1779  * (yterm + p[7] * xterm) * pow(base, -p[6]-1.);
1780  f[4] = p[1] * (p[6] - 1.) /* dM(x,y)/dalphax */
1781  / (CPL_MATH_PI * p[4]*p[4] * p[5] * sqrt(rhoterm))
1782  * (-pow(base, -p[6]) + 2 * p[6] / (rhoterm) * pow(base, -p[6]-1.)
1783  * (xterm*xterm + 0.5*crossterm));
1784  f[5] = p[1] * (p[6] - 1.) /* dM(x,y)/dalphay */
1785  / (CPL_MATH_PI * p[4] * p[5]*p[5] * sqrt(rhoterm))
1786  * (-pow(base, -p[6]) + 2 * p[6] / (rhoterm) * pow(base, -p[6]-1.)
1787  * (yterm*yterm + 0.5*crossterm));
1788  f[6] = p[1] / (CPL_MATH_PI * p[4]*p[5] * sqrt(rhoterm)) /* dM(x,y)/dbeta */
1789  * pow(base, -p[6]) * (1. + (p[6] - 1.) * log(base));
1790  f[7] = p[1] * (p[6] - 1.) /* dM(x,y)/drho */
1791  / (CPL_MATH_PI * p[4]*p[5] * pow(rhoterm, 3./2.))
1792  * (p[7] * pow(base, -p[6])
1793  - 2. * p[6] * pow(base, -p[6]-1.)
1794  * (xterm*yterm * (1 + 2*p[7]*p[7] / rhoterm)
1795  + p[7] / rhoterm * (xterm*xterm + yterm*yterm) ));
1796 //printf("%s = %e, %e, %e, %e, %e, %e, %e, %e\n", __func__, f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7]);
1797 //fflush(stdout);
1798  return 0;
1799 } /* muse_moffat_2d_derivative() */
1800 
1801 #endif
1802 
1803 /*----------------------------------------------------------------------------*/
1876 /*----------------------------------------------------------------------------*/
1877 cpl_error_code
1878 muse_utils_fit_moffat_2d(const cpl_matrix *aPositions,
1879  const cpl_vector *aValues, const cpl_vector *aErrors,
1880  cpl_array *aParams, cpl_array *aPErrors,
1881  const cpl_array *aPFlags,
1882  double *aRMS, double *aRedChisq)
1883 {
1884  /* lots of error checking, almost exactly taken from CPL */
1885  if (!aPositions) {
1886  return cpl_error_set_message(__func__, CPL_ERROR_NULL_INPUT,
1887  "Missing input positions.");
1888  }
1889  if (!aValues) {
1890  return cpl_error_set_message(__func__, CPL_ERROR_NULL_INPUT,
1891  "Missing input values / errors.");
1892  }
1893  if (cpl_matrix_get_ncol(aPositions) != 2) {
1894  return cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
1895  "Input positions are not for two-dimensional data.");
1896  }
1897  if (cpl_vector_get_size(aValues) != cpl_matrix_get_nrow(aPositions)) {
1898  return cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
1899  "Input positions and values data must have same size.");
1900  }
1901  if (aErrors && (cpl_vector_get_size(aValues) != cpl_vector_get_size(aErrors))) {
1902  return cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
1903  "Input vectors must have same size.");
1904  }
1905  if (!aParams) {
1906  return cpl_error_set_message(__func__, CPL_ERROR_NULL_INPUT,
1907  "Missing input parameters array.");
1908  }
1909  if (cpl_array_get_type(aParams) != CPL_TYPE_DOUBLE) {
1910  return cpl_error_set_message(__func__, CPL_ERROR_INVALID_TYPE,
1911  "Parameters array should be CPL_TYPE_DOUBLE.");
1912  }
1913  if (aPErrors && (cpl_array_get_type(aPErrors) != CPL_TYPE_DOUBLE)) {
1914  return cpl_error_set_message(__func__, CPL_ERROR_INVALID_TYPE,
1915  "Parameters error array should be CPL_TYPE_DOUBLE.");
1916  }
1917  if (aPFlags && (cpl_array_get_type(aPFlags) != CPL_TYPE_INT)) {
1918  return cpl_error_set_message(__func__, CPL_ERROR_INVALID_TYPE,
1919  "Parameters error array should be CPL_TYPE_INT.");
1920  }
1921  if ((aPErrors || aRedChisq) && !aErrors) {
1922  return cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
1923  "Missing input parameters errors.");
1924  }
1925  int npoints = cpl_matrix_get_nrow(aPositions);
1926  if (npoints < 8) {
1927  /* Too few positions for 8 free parameters! */
1928  return cpl_error_set_message(__func__, CPL_ERROR_SINGULAR_MATRIX,
1929  "%d are not enough points to fit a Moffat profile.",
1930  npoints);
1931  }
1932 
1933  int pflags[8] = { 1, 1, 1, 1, 1, 1, 1, 1 };
1934  /* Ensure that frozen parameters have a value (first-guess) */
1935  if (aPFlags) {
1936  int idx, nparam = 0;
1937  for (idx = 0; idx < 8; idx++) {
1938  int err, flag = cpl_array_get_int(aPFlags, idx, &err);
1939  if (err || flag) {
1940  continue;
1941  }
1942  pflags[idx] = 0; /* Flag it as frozen */
1943  nparam++;
1944  cpl_array_get_double(aParams, idx, &err);
1945  if (err) {
1946  return cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_INPUT,
1947  "Missing frozen value for parameter %d.", idx);
1948  }
1949  } /* for idx (all parameters) */
1950  /* Ensure that not all parameters are frozen */
1951  if (nparam == 8) {
1952  return cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_INPUT,
1953  "No free parameters");
1954  }
1955  } /* if aPFlags */
1956 
1957  /* Determine first-guess for gaussian parameters. Check if *
1958  * provided by caller - if not build own guesses... *
1959  * *
1960  * 0) Background level: if not given taken as median value within *
1961  * fitting domain. It can be negative... */
1962  int invalid;
1963  double bg = cpl_array_get_double(aParams, 0, &invalid);
1964  if (invalid) {
1965  bg = cpl_vector_get_median_const(aValues);
1966  }
1967 
1968  /* 1) Volume is really set later-on. Here is just a quick estimate, to know *
1969  * whether there is a peak or a hole. If it is flat, leave quickly... */
1970  double volguess = (cpl_vector_get_mean(aValues) - bg) * npoints;
1971  if (fabs(volguess) < FLT_EPSILON) {
1972  /* Data are flat: return a flat Moffat, with undefined center and widths */
1973  cpl_array_set_double(aParams, 0, bg);
1974  cpl_array_set_double(aParams, 1, 0.);
1975  cpl_array_set_invalid(aParams, 2);
1976  cpl_array_set_invalid(aParams, 3);
1977  cpl_array_set_invalid(aParams, 4);
1978  cpl_array_set_invalid(aParams, 5);
1979  cpl_array_set_double(aParams, 6, 1.); /* beta = 1 --> Moffat = 0 */
1980 #if 0
1981  printf("flat Moffat:\n");
1982  cpl_array_dump(aParams, 0, 10, stdout);
1983  fflush(stdout);
1984 #endif
1985  return CPL_ERROR_NONE;
1986  }
1987 
1988  /* 2), 3) Position of center. Compute it as the centroid if not given. *
1989  * This likely does not waste time, as this center is also used *
1990  * to determine the alpha parameters below. */
1991  double xcen, ycen;
1992  muse_utils_get_centroid(aPositions, aValues, aErrors, &xcen, &ycen,
1994  double xc = cpl_array_get_double(aParams, 2, &invalid);
1995  if (invalid) {
1996  xc = xcen;
1997  }
1998  double yc = cpl_array_get_double(aParams, 3, &invalid);
1999  if (invalid) {
2000  yc = ycen;
2001  }
2002 
2003  /* 6) Beta, seeing related Moffat parameter: if not given by the user, *
2004  * it is set to 2.5, which seems to be typical for stellar images. *
2005  * There does not seem to be a simple way to estimate it from the *
2006  * data, independently of the alphax,alphay parameters. */
2007  double beta = cpl_array_get_double(aParams, 6, &invalid);
2008  if (invalid) {
2009  beta = 2.5;
2010  }
2011 
2012  /* 4), 5) Widths: if neither alphax nor alphay are given by the caller the *
2013  * estimate for both is the radius of an approximate half-peak point *
2014  * from the peak point. Very rough, but accuracy is not an issue at *
2015  * this stage, just need a rough starting value. If only one width *
2016  * is given by the caller, the other one is set to the same value. */
2017  double alphay, alphax = cpl_array_get_double(aParams, 4, &invalid);
2018  if (invalid) {
2019  alphay = cpl_array_get_double(aParams, 5, &invalid);
2020  if (invalid) {
2021  double amplitude = 0.;
2022  if (volguess > 0.) {
2023  amplitude = cpl_vector_get_max(aValues) - bg;
2024  } else {
2025  amplitude = cpl_vector_get_min(aValues) - bg;
2026  }
2027  double halfpeak = amplitude / 2. + bg,
2028  limit = amplitude * 0.01; /* 1% accuracy for the start */
2029  const double *values = cpl_vector_get_data_const(aValues);
2030  cpl_vector *vradius = cpl_vector_new(1); /* store radius values */
2031  int i, nfound;
2032  do {
2033  nfound = 0;
2034  for (i = 0; i < npoints; i++) {
2035  if (values[i] > halfpeak - limit && values[i] < halfpeak + limit) {
2036  cpl_vector_set_size(vradius, nfound + 1);
2037  double radius = sqrt(pow(cpl_matrix_get(aPositions, i, 0) - xcen, 2)
2038  + pow(cpl_matrix_get(aPositions, i, 1) - ycen, 2));
2039 #if 0
2040  printf("radius(%d, %d) = %f\n", i, nfound+1, radius);
2041 #endif
2042  cpl_vector_set(vradius, nfound++, radius);
2043  }
2044  } /* for i (all values) */
2045  /* if we go through this loop again, we will find more points and *
2046  * so all previous vector entries will be completely overwritten */
2047  limit *= 2; /* be twice as tolerant to find the points next time */
2048 #if 0
2049  printf("found %d points (limit = %f)\n", nfound, limit / 2);
2050 #endif
2051  } while (nfound < 3 && isfinite(limit));
2052  if (!isfinite(limit)) {
2053  /* if the data is so weird that we don't find points within *
2054  * finite limit, then any alpha is a good first guess... */
2055  alphax = alphay = 1.;
2056  } else {
2057  /* the radius of found points from 1st-guess center is the *
2058  * HWHM, the alpha parameter of a Moffat function is then *
2059  * alpha = HWHM / sqrt(2^(1/beta) - 1) */
2060  alphax = alphay = cpl_vector_get_mean(vradius) / sqrt(pow(2., 1./beta)-1);
2061 #if 0
2062  printf("estimated alphas = %f from radius %f\n", alphax, cpl_vector_get_mean(vradius));
2063  fflush(stdout);
2064 #endif
2065  }
2066  cpl_vector_delete(vradius);
2067  } else {
2068  alphax = alphay;
2069  }
2070  } else {
2071  alphay = cpl_array_get_double(aParams, 5, &invalid);
2072  if (invalid) {
2073  alphay = alphax;
2074  }
2075  }
2076 
2077  /* 1) Volume. If not given by the user, it is derived *
2078  * from the max (min) value of the data distribution. */
2079  double volume = cpl_array_get_double(aParams, 1, &invalid);
2080  if (invalid) {
2081  /* The above seems to be a good enough first-guess. *
2082  * Deriving a better guess from the amplitude would *
2083  * require more solid guesses of the other parameters. */
2084  volume = volguess;
2085  }
2086 
2087  /* 7) Rho, x-y correlation parameter: if not given by the user, *
2088  * it is set to zero (no correlation, i.e. circular Moffat or *
2089  * one that is elongated along x or y). */
2090  double rho = cpl_array_get_double(aParams, 7, &invalid);
2091  if (invalid) {
2092  rho = 0.;
2093  }
2094 
2095  /* Yay! Now all parameters are set to initial values, and we can do the fit! */
2096  cpl_vector *params = cpl_vector_new(8);
2097  cpl_vector_set(params, 0, bg);
2098  cpl_vector_set(params, 1, volume);
2099  cpl_vector_set(params, 2, xc);
2100  cpl_vector_set(params, 3, yc);
2101  cpl_vector_set(params, 4, alphax);
2102  cpl_vector_set(params, 5, alphay);
2103  cpl_vector_set(params, 6, beta);
2104  cpl_vector_set(params, 7, rho);
2105 #if 0
2106  printf("initial guess values for Moffat (vol %e):\n", (cpl_vector_get_mean(aValues) - bg) * npoints);
2107  cpl_vector_dump(params, stdout);
2108  fflush(stdout);
2109 #endif
2110  cpl_matrix *covariance = NULL;
2111 
2112  cpl_error_code rc = CPL_ERROR_NONE;
2113 #if MOFFAT_USE_MUSE_OPTIMIZE
2114  fitdata_t fitdata;
2115  fitdata.positions = aPositions;
2116  fitdata.values = aValues;
2117  fitdata.errors = aErrors;
2118  cpl_array *optparams = cpl_array_wrap_double(cpl_vector_get_data(params), 8);
2119  rc = muse_cpl_optimize_lvmq(&fitdata, optparams, npoints,
2120  muse_moffat_2d_optfunc, NULL);
2121  cpl_array_unwrap(optparams);
2122 #else /* MOFFAT_USE_MUSE_OPTIMIZE follows */
2123  cpl_errorstate prestate = cpl_errorstate_get();
2124  rc = cpl_fit_lvmq(aPositions, NULL, aValues, aErrors, params, pflags,
2125  muse_moffat_2d_function, muse_moffat_2d_derivative,
2126  CPL_FIT_LVMQ_TOLERANCE, CPL_FIT_LVMQ_COUNT,
2127  CPL_FIT_LVMQ_MAXITER, aRMS,
2128  aErrors ? aRedChisq : NULL, aErrors ? &covariance : NULL);
2129 #endif
2130  if (aRMS) {
2131  *aRMS = sqrt(*aRMS);
2132  }
2133 
2134 #if 0
2135  printf("Moffat fit (rc=%d, %s):\n", rc, cpl_error_get_message());
2136  cpl_vector_dump(params, stdout);
2137  fflush(stdout);
2138 #endif
2139  if (rc == CPL_ERROR_NONE || rc == CPL_ERROR_SINGULAR_MATRIX ||
2140  rc == CPL_ERROR_CONTINUE) {
2141  /* The LM algorithm converged. The computation of the covariance *
2142  * matrix might have failed. All the above errors must be ignored *
2143  * because of ticket DFS06126. */
2144 
2145  /* check whether the result makes sense at all... (unlike CPL we use C99) */
2146  if (isfinite(cpl_vector_get(params, 0)) &&
2147  isfinite(cpl_vector_get(params, 1)) &&
2148  isfinite(cpl_vector_get(params, 2)) &&
2149  isfinite(cpl_vector_get(params, 3)) &&
2150  isfinite(cpl_vector_get(params, 4)) &&
2151  isfinite(cpl_vector_get(params, 5)) &&
2152  isfinite(cpl_vector_get(params, 6)) &&
2153  isfinite(cpl_vector_get(params, 7))) {
2154  /* Betting all errors are really to be ignored... */
2155  rc = CPL_ERROR_NONE;
2156  cpl_errorstate_set(prestate);
2157 
2158  /* Save best fit parameters: image coordinates, evaluations *
2159  * of widths are forced positive (they might be both negative *
2160  * -- it would generate the same profile). */
2161  cpl_array_set_double(aParams, 0, cpl_vector_get(params, 0));
2162  cpl_array_set_double(aParams, 1, cpl_vector_get(params, 1));
2163  cpl_array_set_double(aParams, 2, cpl_vector_get(params, 2));
2164  cpl_array_set_double(aParams, 3, cpl_vector_get(params, 3));
2165  cpl_array_set_double(aParams, 4, fabs(cpl_vector_get(params, 4)));
2166  cpl_array_set_double(aParams, 5, fabs(cpl_vector_get(params, 5)));
2167  cpl_array_set_double(aParams, 6, cpl_vector_get(params, 6));
2168  cpl_array_set_double(aParams, 7, cpl_vector_get(params, 7));
2169 
2170  if (aErrors && covariance) {
2171  int idx;
2172  for (idx = 0; idx < 8; idx++) {
2173  if (pflags[idx] && aPErrors) {
2174  cpl_array_set_double(aPErrors, idx,
2175  sqrt(cpl_matrix_get(covariance, idx, idx)));
2176  } /* if */
2177  } /* for idx (all parameters) */
2178  } /* if */
2179 
2180  /* we don't care about the physical parameters like CPL does, so this was it :-) */
2181  } /* if isfinite() */
2182  } /* if rc */
2183  cpl_matrix_delete(covariance);
2184  cpl_vector_delete(params);
2185 
2186  return rc;
2187 } /* muse_utils_fit_moffat_2d() */
2188 
2189 /*----------------------------------------------------------------------------*/
2232 /*----------------------------------------------------------------------------*/
2233 cpl_polynomial *
2234 muse_utils_iterate_fit_polynomial(cpl_matrix *aPos, cpl_vector *aVal,
2235  cpl_vector *aErr, cpl_table *aExtra,
2236  const unsigned int aOrder, const double aRSigma,
2237  double *aMSE, double *aChiSq)
2238 {
2239  /* pre-fill diagnistics to high values in case of errors */
2240  if (aMSE) {
2241  *aMSE = DBL_MAX;
2242  }
2243  if (aChiSq) {
2244  *aChiSq = DBL_MAX;
2245  }
2246  cpl_ensure(aPos && aVal, CPL_ERROR_NULL_INPUT, NULL);
2247  cpl_ensure(cpl_matrix_get_ncol(aPos) == cpl_vector_get_size(aVal),
2248  CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
2249  if (aErr) {
2250  cpl_ensure(cpl_vector_get_size(aVal) == cpl_vector_get_size(aErr),
2251  CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
2252  }
2253  if (aExtra) {
2254  cpl_ensure(cpl_vector_get_size(aVal) == cpl_table_get_nrow(aExtra),
2255  CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
2256  }
2257 
2258  /* XXX erase positions with NAN values upfront */
2259  int idx;
2260  for (idx = 0; idx < cpl_vector_get_size(aVal); idx++) {
2261  /* compare this residual value to the RMS value */
2262  if (isfinite(cpl_vector_get(aVal, idx))) {
2263  continue; /* want to keep all finite numbers */
2264  }
2265  /* guard against removing the last element */
2266  if (cpl_vector_get_size(aVal) == 1) {
2267  cpl_msg_warning(__func__, "Input vector only contained non-finite elements!");
2268  break;
2269  }
2270  /* remove bad element from the fit structures... */
2271  cpl_matrix_erase_columns(aPos, idx, 1);
2272  muse_cplvector_erase_element(aVal, idx);
2273  if (aErr) { /* check to not generate CPL error */
2274  muse_cplvector_erase_element(aErr, idx);
2275  }
2276  /* ...and from the input matrix, if it's there */
2277  if (aExtra) {
2278  cpl_table_erase_window(aExtra, idx, 1);
2279  }
2280  idx--; /* we stay at this position to see what moved here */
2281  } /* for idx */
2282 
2283  /* create the polynomial, using matrix rows to determine dimensions */
2284  int ndim = cpl_matrix_get_nrow(aPos);
2285  cpl_polynomial *fit = cpl_polynomial_new(ndim);
2286  int large_residuals = 1; /* init to force a first fit */
2287  while (large_residuals > 0) {
2288  const cpl_boolean sym = CPL_FALSE;
2289  cpl_size *mindeg = cpl_calloc(ndim, sizeof(cpl_size)),
2290  *maxdeg = cpl_malloc(ndim * sizeof(cpl_size));
2291  int i;
2292  for (i = 0; i < ndim; i++) {
2293  maxdeg[i] = aOrder;
2294  }
2295  cpl_error_code rc = cpl_polynomial_fit(fit, aPos, &sym, aVal, NULL,
2296  CPL_FALSE, mindeg, maxdeg);
2297  cpl_free(mindeg);
2298  cpl_free(maxdeg);
2299  const cpl_size coeff = 0;
2300  if (rc != CPL_ERROR_NONE || !isfinite(cpl_polynomial_get_coeff(fit, &coeff))) {
2301  /* don't try to recover from an error, instead clean up and return NULL */
2302  cpl_errorstate prestate = cpl_errorstate_get();
2303 #if 0
2304  printf("%s: output polynomial:\n", __func__);
2305  cpl_polynomial_dump(fit, stdout);
2306  printf("%s: positions and values that we tried to fit:\n", __func__);
2307  cpl_matrix_dump(aPos, stdout);
2308  cpl_vector_dump(aVal, stdout);
2309  fflush(stdout);
2310 #endif
2311  cpl_polynomial_delete(fit);
2312  /* make sure to expose the important error to the caller */
2313  if (!cpl_errorstate_is_equal(prestate)) {
2314  cpl_errorstate_set(prestate);
2315  }
2316  return NULL;
2317  }
2318 
2319  /* compute residuals and mean squared error */
2320  cpl_vector *res = cpl_vector_new(cpl_vector_get_size(aVal));
2321  cpl_vector_fill_polynomial_fit_residual(res, aVal, NULL, fit, aPos, aChiSq);
2322  double rms = sqrt(cpl_vector_product(res, res) / cpl_vector_get_size(res));
2323  if (rms == 0.) { /* otherwise everything will be rejected! */
2324  rms = DBL_MIN;
2325  }
2326 
2327 #if 0
2328  printf("%s: polynomial fit (RMS %g chisq %g aRSigma %f -> limit %g):\n",
2329  __func__, rms, aChiSq ? *aChiSq : 0., aRSigma, aRSigma * rms);
2330  cpl_polynomial_dump(fit, stdout);
2331  fflush(stdout);
2332  char *title = cpl_sprintf("set title \"%s: RMS %g\"\n"
2333  "unset key\n", __func__, rms);
2334  cpl_plot_vector(title, "", "", res);
2335  cpl_free(title);
2336 #endif
2337 
2338  large_residuals = 0;
2339  for (i = 0; i < cpl_vector_get_size(res); i++) {
2340  /* compare this residual value to the RMS value */
2341  if (fabs(cpl_vector_get(res, i)) < (aRSigma * rms)) {
2342  /* good fit at this point */
2343  continue;
2344  }
2345 
2346  /* bad residual, remove element, from residuals vector and the data vectors */
2347 #if 0
2348  cpl_msg_debug(__func__, "residual %f RMS %f aRSigma %f -> limit %f",
2349  cpl_vector_get(res, i), rms, aRSigma, aRSigma * rms);
2350 #endif
2351  /* guard against removing the last element */
2352  if (cpl_vector_get_size(res) == 1) {
2353  cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_OUTPUT, "tried to "
2354  "remove the last vector/matrix element when "
2355  "checking fit residuals (residual %g RMS %g "
2356  "aRSigma %f -> limit %g)", cpl_vector_get(res, i),
2357  rms, aRSigma, aRSigma * rms);
2358  cpl_polynomial_delete(fit);
2359  fit = NULL;
2360  rms = sqrt(DBL_MAX); /* so that aMSE gets to be DBL_MAX below */
2361  if (aChiSq) {
2362  *aChiSq = DBL_MAX;
2363  }
2364  large_residuals = 0; /* don't try to fit again */
2365  break;
2366  }
2367  /* remove bad element from the fit structures... */
2369  cpl_matrix_erase_columns(aPos, i, 1);
2371  if (aErr) { /* check to not generate CPL error */
2373  }
2374  /* ...and from the input matrix, if it's there */
2375  if (aExtra) {
2376  cpl_table_erase_window(aExtra, i, 1);
2377  }
2378  large_residuals++;
2379  i--; /* we stay at this position to see what moved here */
2380  } /* for i */
2381  cpl_vector_delete(res);
2382  if (aMSE) {
2383  *aMSE = rms * rms;
2384  }
2385  } /* while large_residuals */
2386 
2387  return fit;
2388 } /* muse_utils_iterate_fit_polynomial() */
2389 
2390 /*----------------------------------------------------------------------------*/
2440 /*----------------------------------------------------------------------------*/
2441 double
2443  double aHalfWidth, double aBinSize,
2444  float aLo, float aHi, unsigned char aIter,
2445  cpl_array *aResults, cpl_array *aErrors)
2446 {
2447  cpl_ensure(aPixtable && aPixtable->table && aPixtable->header,
2448  CPL_ERROR_NULL_INPUT, 0.);
2449 
2450  /* make sure we are dealing with positive wavelengthts for comparison *
2451  * with the pixel table; split the sign off into a separate variable *
2452  * that determines, if we flip the spectrum before doing the fit */
2453  cpl_boolean flip = aLambda < 0.;
2454  double lambda = fabs(aLambda);
2455 
2456  /* select (only) the relevant part of the pixel table */
2457  cpl_table_unselect_all(aPixtable->table);
2458  cpl_table_or_selected_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA,
2459  CPL_NOT_LESS_THAN, lambda - aHalfWidth);
2460  cpl_table_and_selected_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA,
2461  CPL_NOT_GREATER_THAN, lambda + aHalfWidth);
2462  cpl_size nsel = cpl_table_count_selected(aPixtable->table);
2463  cpl_ensure(nsel > 0, CPL_ERROR_DATA_NOT_FOUND, 0.);
2464 
2465  /* now resample all the selected pixels into a spectrum *
2466  * with hopefully high resolution given by aBinSize */
2467  cpl_errorstate state = cpl_errorstate_get();
2468  cpl_table *spec = muse_resampling_spectrum_iterate(aPixtable, aBinSize,
2469  aLo, aHi, aIter);
2470  cpl_table_unselect_all(aPixtable->table);
2471  if (!cpl_errorstate_is_equal(state)) {
2472  /* something went wrong with the creation of the spectrum */
2473  cpl_table_delete(spec);
2474  cpl_error_set(__func__, cpl_error_get_code());
2475  return 0.;
2476  }
2477  if (flip) {
2478  /* just change the sign of the data column of the spectrum; it does not *
2479  * seem to be necessary to move the spectrum above the zero level again */
2480  cpl_table_multiply_scalar(spec, "data", -1.);
2481  }
2482 
2483  /* check that the spectrum is contiguous */
2484  cpl_size nbins = cpl_table_get_nrow(spec);
2485  /* turn the stat column (in sigma^2) into an error column (in sigma) */
2486  cpl_table_power_column(spec, "stat", 0.5); /* sqrt() */
2487  cpl_table_name_column(spec, "stat", "error");
2488  cpl_table_set_column_unit(spec, "error",
2489  cpl_table_get_column_unit(spec, "data"));
2490 #if 0
2491  char *fn = cpl_sprintf("spec_ifu%02hhu_%p_%.3f.fits",
2492  muse_utils_get_ifu(aPixtable->header),
2493  (void *)aPixtable, aLambda);
2494  cpl_msg_debug(__func__, "----> saving spectrum as \"%s\"", fn);
2495  cpl_table_save(spec, NULL, NULL, fn, CPL_IO_CREATE);
2496  cpl_free(fn);
2497 #endif
2498  /* wrap table columns into vectors for the Gaussian fit */
2499  cpl_vector *pos = cpl_vector_wrap(nbins, cpl_table_get_data_double(spec, "lambda")),
2500  *val = cpl_vector_wrap(nbins, cpl_table_get_data_double(spec, "data")),
2501  *err = cpl_vector_wrap(nbins, cpl_table_get_data_double(spec, "error"));
2502  state = cpl_errorstate_get();
2503  double xc, xerr = 2 * aHalfWidth, /* default error as large as the window */
2504  sigma, area, bglevel, mse;
2505  cpl_matrix *covariance = NULL;
2506  cpl_error_code rc = cpl_vector_fit_gaussian(pos, NULL, val, err, CPL_FIT_ALL,
2507  &xc, &sigma, &area, &bglevel,
2508  &mse, NULL, &covariance);
2509  cpl_vector_unwrap(pos);
2510  cpl_vector_unwrap(val);
2511  cpl_vector_unwrap(err);
2512  cpl_table_delete(spec);
2513  if (rc == CPL_ERROR_CONTINUE) { /* fit didn't converge */
2514  /* estimate position error as sigma^2/area as CPL docs suggest */
2515  xerr = sqrt(sigma * sigma / area);
2516  cpl_errorstate_set(state); /* error handled, reset it */
2517  } else if (rc == CPL_ERROR_SINGULAR_MATRIX || !covariance) {
2518  xerr = sqrt(sigma * sigma / area);
2519  cpl_errorstate_set(state); /* error handled, reset it */
2520  } else {
2521  xerr = sqrt(cpl_matrix_get(covariance, 0, 0));
2522 #if 0
2523  cpl_msg_debug(__func__, "covariance matrix:");
2524  cpl_matrix_dump(covariance, stdout);
2525  fflush(stdout);
2526 #endif
2527  }
2528  if (aResults && cpl_array_get_type(aResults) == CPL_TYPE_DOUBLE) {
2529  cpl_array_set_size(aResults, 4);
2530  cpl_array_set_double(aResults, 0, xc);
2531  cpl_array_set_double(aResults, 1, sigma);
2532  cpl_array_set_double(aResults, 2, area);
2533  cpl_array_set_double(aResults, 3, bglevel);
2534  } /* valid aResults array */
2535  if (aErrors && cpl_array_get_type(aErrors) == CPL_TYPE_DOUBLE) {
2536  cpl_array_set_size(aErrors, 4);
2537  cpl_array_set_double(aErrors, 0, xerr);
2538  if (rc != CPL_ERROR_NONE || !covariance) {
2539  cpl_array_fill_window_invalid(aErrors, 1, 3);
2540  } else {
2541  cpl_array_set_double(aErrors, 1, sqrt(cpl_matrix_get(covariance, 1, 1)));
2542  cpl_array_set_double(aErrors, 2, sqrt(cpl_matrix_get(covariance, 2, 2)));
2543  cpl_array_set_double(aErrors, 3, sqrt(cpl_matrix_get(covariance, 3, 3)));
2544  } /* else */
2545  } /* valid aErrors array */
2546  cpl_matrix_delete(covariance);
2547  cpl_msg_debug(__func__, "Gaussian fit (%s): %f +/- %f Angstrom, %f, %f, %f "
2548  "(RMS %f)", rc != CPL_ERROR_NONE ? cpl_error_get_message() : "",
2549  xc, xerr, bglevel, area, sigma, sqrt(mse));
2550  return xc;
2551 } /* muse_utils_pixtable_fit_line_gaussian() */
2552 
2553 /*----------------------------------------------------------------------------*/
2568 /*----------------------------------------------------------------------------*/
2569 cpl_error_code
2570 muse_utils_copy_modified_header(cpl_propertylist *aH1, cpl_propertylist *aH2,
2571  const char *aKeyword, const char *aString)
2572 {
2573  cpl_ensure_code(aH1 && aH2 && aKeyword && aString, CPL_ERROR_NULL_INPUT);
2574  const char *hin = cpl_propertylist_get_string(aH1, aKeyword);
2575  cpl_ensure_code(hin, CPL_ERROR_ILLEGAL_INPUT);
2576  char *hstring = cpl_sprintf("%s (%s)", hin, aString);
2577  cpl_error_code rc = cpl_propertylist_update_string(aH2, aKeyword, hstring);
2578  cpl_free(hstring);
2579 
2580  return rc;
2581 } /* muse_utils_copy_modified_header() */
2582 
2583 /*---------------------------------------------------------------------------*/
2609 /*---------------------------------------------------------------------------*/
2610 cpl_error_code
2611 muse_utils_set_hduclass(cpl_propertylist *aHeader, const char *aClass2,
2612  const char *aExtData, const char *aExtDQ,
2613  const char *aExtStat)
2614 {
2615  cpl_ensure_code(aHeader && aClass2 && aExtData, CPL_ERROR_NULL_INPUT);
2616  cpl_ensure_code(!strcmp(aClass2, "DATA") || !strcmp(aClass2, "ERROR") ||
2617  !strcmp(aClass2, "QUALITY"), CPL_ERROR_ILLEGAL_INPUT);
2618 
2619  /* first clean up existing entries */
2620 #define ESO_HDU_HEADERS_REGEXP "HDU(CLASS|CLAS1|CLAS2|CLAS3|DOC|VERS)$" \
2621  "|^SCIDATA$|^QUAL(DATA|MASK)$|^ERRDATA$"
2622  cpl_propertylist_erase_regexp(aHeader, ESO_HDU_HEADERS_REGEXP, 0);
2623 
2624  if (cpl_propertylist_has(aHeader, "EXTNAME")) {
2625  cpl_propertylist_insert_after_string(aHeader, "EXTNAME", "HDUCLASS", "ESO");
2626  } else {
2627  cpl_propertylist_append_string(aHeader, "HDUCLASS", "ESO");
2628  }
2629  cpl_propertylist_set_comment(aHeader, "HDUCLASS", "class name (ESO format)");
2630  cpl_propertylist_insert_after_string(aHeader, "HDUCLASS", "HDUDOC", "DICD");
2631  cpl_propertylist_set_comment(aHeader, "HDUDOC", "document with class description");
2632  cpl_propertylist_insert_after_string(aHeader, "HDUDOC", "HDUVERS",
2633  "DICD version 6");
2634  cpl_propertylist_set_comment(aHeader, "HDUVERS",
2635  "version number (according to spec v2.5.1)");
2636  cpl_propertylist_insert_after_string(aHeader, "HDUVERS", "HDUCLAS1", "IMAGE");
2637  cpl_propertylist_set_comment(aHeader, "HDUCLAS1", "Image data format");
2638  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS1", "HDUCLAS2", aClass2);
2639  if (!strcmp(aClass2, "DATA")) { /* this is the STAT / variance extension */
2640  cpl_propertylist_set_comment(aHeader, "HDUCLAS2",
2641  "this extension contains the data itself");
2642  /* no HDUCLAS3 for the data itself */
2643  if (aExtDQ) {
2644  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS2", "QUALDATA", aExtDQ);
2645  }
2646  if (aExtStat) { /* do this second, so that it goes first, if given */
2647  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS2", "ERRDATA", aExtStat);
2648  }
2649  } else if (!strcmp(aClass2, "ERROR")) { /* this is the STAT / variance extension */
2650  cpl_propertylist_set_comment(aHeader, "HDUCLAS2", "this extension contains variance");
2651  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS2", "HDUCLAS3", "MSE");
2652  cpl_propertylist_set_comment(aHeader, "HDUCLAS3",
2653  "the extension contains variances (sigma**2)");
2654  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS3", "SCIDATA", aExtData);
2655  if (aExtDQ) {
2656  cpl_propertylist_insert_after_string(aHeader, "SCIDATA", "QUALDATA", aExtDQ);
2657  }
2658  } else { /* "QUALITY", this is the DQ / bad pixel extension */
2659  cpl_propertylist_set_comment(aHeader, "HDUCLAS2",
2660  "this extension contains bad pixel mask");
2661  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS2", "HDUCLAS3", "FLAG32BIT");
2662  cpl_propertylist_set_comment(aHeader, "HDUCLAS3", "extension contains 32bit"
2663  " Euro3D bad pixel information");
2664  /* all non-zero values in the bad pixel mask are "bad" */
2665  cpl_propertylist_insert_after_long(aHeader, "HDUCLAS3", "QUALMASK", 0xFFFFFFFF);
2666  cpl_propertylist_set_comment(aHeader, "QUALMASK", "all non-zero values are bad");
2667  cpl_propertylist_insert_after_string(aHeader, "QUALMASK", "SCIDATA", aExtData);
2668  if (aExtStat) {
2669  cpl_propertylist_insert_after_string(aHeader, "SCIDATA", "ERRDATA", aExtStat);
2670  }
2671  }
2672 
2673  if (cpl_propertylist_has(aHeader, "SCIDATA")) {
2674  cpl_propertylist_set_comment(aHeader, "SCIDATA", "pointer to the data extension");
2675  }
2676  if (cpl_propertylist_has(aHeader, "ERRDATA")) {
2677  cpl_propertylist_set_comment(aHeader, "ERRDATA", "pointer to the variance extension");
2678  }
2679  if (cpl_propertylist_has(aHeader, "QUALDATA")) {
2680  cpl_propertylist_set_comment(aHeader, "QUALDATA",
2681  "pointer to the bad pixel mask extension");
2682  }
2683 
2684  return CPL_ERROR_NONE;
2685 } /* muse_utils_set_hduclass() */
2686 
2687 /*----------------------------------------------------------------------------*/
2700 /*----------------------------------------------------------------------------*/
2701 void
2702 muse_utils_memory_dump(const char *aMarker)
2703 {
2704  char *exe = getenv("MUSE_DEBUG_MEMORY_PROGRAM");
2705  if (!exe) {
2706  return;
2707  }
2708 
2709  printf("=== %s ===\n", aMarker);
2710  fflush(stdout);
2711  char command[1000];
2712  if (strlen(exe) > 0) {
2713  snprintf(command, 999,
2714  "ps -C %s -o comm,start_time,pid,tid,pcpu,stat,rss,size,vsize",
2715  exe);
2716  } else {
2717  /* no program name given, generic output */
2718  snprintf(command, 999,
2719  "ps -o comm,start_time,pid,tid,pcpu,stat,rss,size,vsize");
2720  }
2721  cpl_memory_dump();
2722  fflush(stderr);
2723  int rc = system(command);
2724  UNUSED_ARGUMENT(rc); /* new glibc mandates use of system() return code */
2725 } /* muse_utils_memory_dump() */
2726 
2727 /*----------------------------------------------------------------------------*/
2746 /*----------------------------------------------------------------------------*/
2747 cpl_image *
2748 muse_convolve_image(const cpl_image *aImage, const cpl_matrix *aKernel)
2749 {
2750  cpl_ensure(aImage && aKernel, CPL_ERROR_NULL_INPUT, NULL);
2751 
2752  cpl_size nx = cpl_image_get_size_x(aImage);
2753  cpl_size ny = cpl_image_get_size_y(aImage);
2754  cpl_size nc = cpl_matrix_get_ncol(aKernel);
2755  cpl_size nr = cpl_matrix_get_nrow(aKernel);
2756 
2757  cpl_ensure(cpl_image_get_type(aImage) == CPL_TYPE_DOUBLE,
2758  CPL_ERROR_INVALID_TYPE, NULL);
2759  cpl_ensure(nx % 2 == 0, CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
2760 
2761  cpl_size kstart[2] = {(nx - nc) / 2, (ny - nr) / 2};
2762  cpl_size kend[2] = {kstart[0] + nc, kstart[1] + nr};
2763 
2764  cpl_image *kernel = cpl_image_new(nx, ny, CPL_TYPE_DOUBLE);
2765  double *_kernel = cpl_image_get_data_double(kernel);
2766 
2767  const double *_aKernel = cpl_matrix_get_data_const(aKernel);
2768  cpl_size iy;
2769  for (iy = 0; iy < ny; ++iy) {
2770  cpl_size ix;
2771  for (ix = 0; ix < nx; ++ix) {
2772  cpl_size idx = nx * iy + ix;
2773  cpl_boolean inside = ((ix >= kstart[0]) && (ix < kend[0])) &&
2774  ((iy >= kstart[1]) && (iy < kend[1]));
2775  if (inside) {
2776  _kernel[idx] = _aKernel[nc * (iy - kstart[1]) + (ix - kstart[0])];
2777  }
2778  }
2779  }
2780 
2781  cpl_size nxhalf = nx / 2 + 1;
2782  cpl_image *fft_image = cpl_image_new(nxhalf, ny, CPL_TYPE_DOUBLE_COMPLEX);
2783  cpl_image *fft_kernel = cpl_image_new(nxhalf, ny, CPL_TYPE_DOUBLE_COMPLEX);
2784 
2785  cpl_error_code status;
2786  status = cpl_fft_image(fft_image, aImage, CPL_FFT_FORWARD);
2787  if (status != CPL_ERROR_NONE) {
2788  cpl_image_delete(fft_kernel);
2789  cpl_image_delete(fft_image);
2790  cpl_image_delete(kernel);
2791  cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
2792  "FFT of input image failed!");
2793  return NULL;
2794  }
2795  status = cpl_fft_image(fft_kernel, kernel, CPL_FFT_FORWARD);
2796  if (status != CPL_ERROR_NONE) {
2797  cpl_image_delete(fft_kernel);
2798  cpl_image_delete(fft_image);
2799  cpl_image_delete(kernel);
2800  cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
2801  "FFT of convolution kernel failed!");
2802  return NULL;
2803  }
2804  cpl_image_delete(kernel);
2805 
2806  double complex *_fft_image = cpl_image_get_data_double_complex(fft_image);
2807  double complex *_fft_kernel = cpl_image_get_data_double_complex(fft_kernel);
2808 
2809  /* Convolve the input image with the kernel. Compute the two FFTs, for *
2810  * the image and the kernel and multiply the results. *
2811  * *
2812  * After the forward transform the zero frequency is stored in the 4 *
2813  * corners of the result image, and not in the center as one may *
2814  * expect. In order to get a correct convolution of the image the input *
2815  * buffer of the inverse transformation must be rearranged such that *
2816  * the zero frequency appears in the center. This is done by multiplying *
2817  * all results at odd index positions in the buffer by -1, which *
2818  * effectively swaps the quadrants of the frequency plane. *
2819  * see: www.mvkonnik.info/2014/06/fft-ifft-and-why-do-we-need-fftshift.html */
2820 
2821  for (iy = 0; iy < ny; ++iy) {
2822  cpl_size ix;
2823  for (ix = 0; ix < nxhalf; ++ix) {
2824  cpl_size idx = nxhalf * iy + ix;
2825  int sign = ((ix + iy) % 2) ? -1 : 1;
2826  _fft_image[idx] *= sign * _fft_kernel[idx] / (nx * ny);
2827  }
2828  }
2829  cpl_image_delete(fft_kernel);
2830 
2831  cpl_image *result = cpl_image_new(nx, ny, CPL_TYPE_DOUBLE);
2832  status = cpl_fft_image(result, fft_image, CPL_FFT_BACKWARD | CPL_FFT_NOSCALE);
2833  if (status != CPL_ERROR_NONE) {
2834  cpl_image_delete(result);
2835  cpl_image_delete(fft_image);
2836  cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
2837  "Backward FFT of convolution result failed!");
2838  return NULL;
2839  }
2840  cpl_image_delete(fft_image);
2841 
2842  return result;
2843 }
2844 
2845 
2846 /*----------------------------------------------------------------------------*/
2862 /*----------------------------------------------------------------------------*/
2863 muse_image *
2864 muse_fov_load(const char *aFilename)
2865 {
2866  muse_image *image = muse_image_new();
2867 
2868  image->header = cpl_propertylist_load(aFilename, 0);
2869  if (!image->header) {
2870  cpl_error_set_message(__func__, cpl_error_get_code(), "Loading primary FITS "
2871  "header of \"%s\" did not succeed", aFilename);
2872  muse_image_delete(image);
2873  return NULL;
2874  }
2875 
2876  /* assuming an IMAGE_FOV input, start by searching for the DATA extension */
2877  int extension = cpl_fits_find_extension(aFilename, EXTNAME_DATA);
2878  cpl_propertylist *hdata = cpl_propertylist_load(aFilename, extension);
2879  while (muse_pfits_get_naxis(hdata, 0) != 2) {
2880  /* Not an image, this means we were given a cube, possibly *
2881  * with image extensions; search for the first of those. */
2882  cpl_msg_debug(__func__, "Skipping extension %d [%s]", extension,
2883  muse_pfits_get_extname(hdata));
2884  cpl_propertylist_delete(hdata);
2885  extension++;
2886  hdata = cpl_propertylist_load(aFilename, extension);
2887  } /* while */
2888  cpl_msg_debug(__func__, "Taking extension %d [%s]", extension,
2889  muse_pfits_get_extname(hdata));
2890  /* keep the extension name, to check if we loaded *
2891  * from an IMAGE_FOV or from a cube extension */
2892  char *extname = cpl_strdup(muse_pfits_get_extname(hdata));
2893  image->data = cpl_image_load(aFilename, CPL_TYPE_FLOAT, 0, extension);
2894  if (!image->data) {
2895  cpl_error_set_message(__func__, MUSE_ERROR_READ_DATA, "Could not load "
2896  "extension %s from \"%s\"", extname, aFilename);
2897  muse_image_delete(image);
2898  cpl_free(extname);
2899  return NULL;
2900  }
2901 
2902  if (cpl_propertylist_has(hdata, "BUNIT")) {
2903  cpl_propertylist_append_string(image->header, "BUNIT",
2904  muse_pfits_get_bunit(hdata));
2905  cpl_propertylist_set_comment(image->header, "BUNIT",
2906  cpl_propertylist_get_comment(hdata, "BUNIT"));
2907  } else {
2908  cpl_msg_warning(__func__, "No BUNIT given in extension %d [%s] of \"%s\"!",
2909  extension, extname, aFilename);
2910  }
2911 
2912  if (!cpl_propertylist_has(hdata, "CUNIT1") ||
2913  !cpl_propertylist_has(hdata, "CUNIT2")) {
2914  cpl_msg_warning(__func__, "No WCS found in extension %d [%s] of \"%s\"!",
2915  extension, extname, aFilename);
2916  }
2917 
2918  /* Merge WCS keywords and ESO hierarchical keywords from the data *
2919  * extension into the image header */
2920  cpl_propertylist_erase_regexp(hdata, "(^ESO |" MUSE_WCS_KEYS ")", 1);
2921  cpl_propertylist_append(image->header, hdata);
2922  cpl_propertylist_delete(hdata);
2923 
2924  /* Load STAT extension if it is found and contains data, ignore it *
2925  * otherwise */
2926  if (extname && !strncmp(extname, EXTNAME_DATA, strlen(EXTNAME_DATA)+1)) {
2927  /* if this is an IMAGE_FOV, then a STAT extension might be there */
2928  extension = cpl_fits_find_extension(aFilename, EXTNAME_STAT);
2929  } else {
2930  /* otherwise we might have an extension <filtername>_STAT */
2931  char *statname = cpl_sprintf("%s_STAT", extname);
2932  extension = cpl_fits_find_extension(aFilename, statname);
2933  cpl_free(statname);
2934  }
2935  if (extension > 0) {
2936  cpl_errorstate status = cpl_errorstate_get();
2937  image->stat = cpl_image_load(aFilename, CPL_TYPE_INT, 0, extension);
2938  if (!cpl_errorstate_is_equal(status)) {
2939  if (cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND) {
2940  cpl_errorstate_set(status);
2941  cpl_msg_debug(__func__, "Ignoring empty extension %s in \"%s\"",
2942  EXTNAME_STAT, aFilename);
2943  } else {
2944  cpl_error_set_message(__func__, MUSE_ERROR_READ_STAT, "Could not load "
2945  "extension %s from \"%s\"", EXTNAME_STAT, aFilename);
2946  muse_image_delete(image);
2947  cpl_free(extname);
2948  return NULL;
2949  }
2950  }
2951  }
2952 
2953  /* If present load the DQ extension and encode flagged pixels in the *
2954  * image data (and possibly the statistics as NaNs. */
2955  if (extname && !strncmp(extname, EXTNAME_DATA, strlen(EXTNAME_DATA)+1)) {
2956  /* if this is an IMAGE_FOV, then a DQ extension might be there */
2957  extension = cpl_fits_find_extension(aFilename, EXTNAME_DQ);
2958  } else {
2959  /* otherwise we might have an extension <filtername>_DQ */
2960  char *dqname = cpl_sprintf("%s_DQ", extname);
2961  extension = cpl_fits_find_extension(aFilename, dqname);
2962  cpl_free(dqname);
2963  }
2964  if (extension > 0) {
2965  cpl_errorstate status = cpl_errorstate_get();
2966  image->dq = cpl_image_load(aFilename, CPL_TYPE_INT, 0, extension);
2967  if (!cpl_errorstate_is_equal(status)) {
2968  cpl_errorstate_set(status);
2969  cpl_error_set_message(__func__, MUSE_ERROR_READ_DQ, "Could not load "
2970  "extension %s from \"%s\"", EXTNAME_DQ, aFilename);
2971  muse_image_delete(image);
2972  cpl_free(extname);
2973  return NULL;
2974  }
2975  muse_image_dq_to_nan(image);
2976  }
2977 
2978  cpl_free(extname);
2979  return image;
2980 }
const muse_cpltable_def muse_filtertable_def[]
MUSE filter table definition.
Definition: muse_utils.c:768
cpl_boolean muse_pfits_has_ifu(const cpl_propertylist *aHeaders, unsigned char aIFU)
Find out the whether this header related to a certain IFU.
Definition: muse_pfits.c:181
void muse_image_delete(muse_image *aImage)
Deallocate memory associated to a muse_image object.
Definition: muse_image.c:85
const char * muse_pfits_get_extname(const cpl_propertylist *aHeaders)
find out the extension name
Definition: muse_pfits.c:205
double muse_utils_pixtable_fit_line_gaussian(muse_pixtable *aPixtable, double aLambda, double aHalfWidth, double aBinSize, float aLo, float aHi, unsigned char aIter, cpl_array *aResults, cpl_array *aErrors)
Fit a 1D Gaussian to a given wavelength range in a pixel table.
Definition: muse_utils.c:2442
int muse_pfits_get_read_id(const cpl_propertylist *aHeaders)
find out the readout mode id
Definition: muse_pfits.c:512
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
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
cpl_table * muse_resampling_spectrum_iterate(muse_pixtable *aPixtable, double aBinwidth, float aLo, float aHi, unsigned char aIter)
Iteratively resample selected pixels of a pixel table into spectrum.
cpl_size muse_pfits_get_naxis(const cpl_propertylist *aHeaders, unsigned int aAxis)
find out the size of a given axis
Definition: muse_pfits.c:242
cpl_image * data
the data extension
Definition: muse_image.h:46
int muse_pfits_get_shut_status(const cpl_propertylist *aHeaders, int aShutter)
query the status of one shutter
Definition: muse_pfits.c:1569
muse_utils_centroid_type
Background handling when computing centroids.
Definition: muse_utils.h:102
cpl_error_code muse_cpl_optimize_lvmq(void *aData, cpl_array *aPar, int aSize, muse_cpl_evaluate_func *aFunction, muse_cpl_optimize_control_t *aCtrl)
Minimize a function with the Levenberg-Marquardt algorithm.
cpl_error_code muse_utils_filter_copy_properties(cpl_propertylist *aHeader, const muse_table *aFilter, double aFraction)
Add/propagate filter properties to header of collapsed image.
Definition: muse_utils.c:934
void muse_utils_memory_dump(const char *aMarker)
Display the current memory usage of the given program.
Definition: muse_utils.c:2702
cpl_image * stat
the statistics extension
Definition: muse_image.h:64
const char * muse_pfits_get_dateobs(const cpl_propertylist *aHeaders)
find out the date of observations
Definition: muse_pfits.c:364
muse_image * muse_fov_load(const char *aFilename)
Load a FOV image into a MUSE image.
Definition: muse_utils.c:2864
const char * muse_pfits_get_bunit(const cpl_propertylist *aHeaders)
find out the unit string
Definition: muse_pfits.c:223
muse_table * muse_table_new(void)
Allocate memory for a new muse_table object.
Definition: muse_table.c:63
Structure definition of MUSE three extension FITS file.
Definition: muse_image.h:40
cpl_table * table
The pixel table.
int muse_pfits_get_lampnum(const cpl_propertylist *aHeaders)
query the number of lamps installed
Definition: muse_pfits.c:1483
cpl_propertylist * header
the FITS header
Definition: muse_image.h:72
cpl_error_code muse_utils_set_hduclass(cpl_propertylist *aHeader, const char *aClass2, const char *aExtData, const char *aExtDQ, const char *aExtStat)
Set HDU headers for the ESO FITS data format.
Definition: muse_utils.c:2611
cpl_image * dq
the data quality extension
Definition: muse_image.h:56
cpl_table * muse_cpltable_new(const muse_cpltable_def *aDef, cpl_size aLength)
Create an empty table according to the specified definition.
double muse_flux_response_interpolate(const cpl_table *aResponse, double aLambda, double *aError, muse_flux_interpolation_type aType)
Compute linearly interpolated response of some kind at given wavelength.
Definition: muse_flux.c:338
const char * muse_get_license(void)
Get the pipeline copyright and license.
Definition: muse_utils.c:83
int muse_pfits_get_biny(const cpl_propertylist *aHeaders)
find out the binning factor in y direction
Definition: muse_pfits.c:566
Structure definition of MUSE pixel table.
#define MUSE_WCS_KEYS
regular expression for WCS properties
Definition: muse_wcs.h:48
const char * muse_pfits_get_lamp_name(const cpl_propertylist *aHeaders, int aLamp)
query the name of one lamp
Definition: muse_pfits.c:1506
cpl_error_code muse_cplvector_erase_element(cpl_vector *aVector, int aElement)
delete the given element from the input vector
cpl_error_code muse_utils_fit_moffat_2d(const cpl_matrix *aPositions, const cpl_vector *aValues, const cpl_vector *aErrors, cpl_array *aParams, cpl_array *aPErrors, const cpl_array *aPFlags, double *aRMS, double *aRedChisq)
Fit a 2D Moffat function to a given set of data.
Definition: muse_utils.c:1878
const char * muse_pfits_get_pipefile(const cpl_propertylist *aHeaders)
find out the pipefile
Definition: muse_pfits.c:88
cpl_polynomial * muse_utils_iterate_fit_polynomial(cpl_matrix *aPos, cpl_vector *aVal, cpl_vector *aErr, cpl_table *aExtra, const unsigned int aOrder, const double aRSigma, double *aMSE, double *aChiSq)
Iterate a polynomial fit.
Definition: muse_utils.c:2234
const char * muse_pfits_get_read_name(const cpl_propertylist *aHeaders)
find out the readout mode name
Definition: muse_pfits.c:530
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_table_load_filter(muse_processing *aProcessing, const char *aFilterName)
Load a table for a given filter name.
Definition: muse_utils.c:799
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
cpl_error_code muse_utils_fit_multigauss_1d(const cpl_vector *aX, const cpl_bivector *aY, cpl_vector *aCenter, double *aSigma, cpl_vector *aFlux, cpl_vector *aPoly, double *aMSE, double *aRedChisq, cpl_matrix **aCovariance)
Carry out a multi-Gaussian fit of data in a vector.
Definition: muse_utils.c:1558
void muse_table_delete(muse_table *aTable)
Deallocate memory associated to a muse_table object.
Definition: muse_table.c:80
char * muse_utils_header_get_lamp_names(cpl_propertylist *aHeader, char aSep)
Concatenate names of all active calibration lamps.
Definition: muse_utils.c:989
double muse_utils_filter_fraction(const muse_table *aFilter, double aLambda1, double aLambda2)
Compute fraction of filter area covered by the data range.
Definition: muse_utils.c:893
const char * muse_pfits_get_chip_id(const cpl_propertylist *aHeaders)
find out the chip id
Definition: muse_pfits.c:602
cpl_propertylist * header
the header
Definition: muse_table.h:56
int muse_pfits_get_binx(const cpl_propertylist *aHeaders)
find out the binning factor in x direction
Definition: muse_pfits.c:548
cpl_array * muse_utils_header_get_lamp_numbers(cpl_propertylist *aHeader)
List numbers of all active calibration lamps.
Definition: muse_utils.c:1056
cpl_image * muse_utils_image_fit_polynomial(const cpl_image *aImage, unsigned short aXOrder, unsigned short aYOrder)
Create a smooth version of a 2D image by fitting it with a 2D polynomial.
Definition: muse_utils.c:1147
int muse_pfits_get_lamp_status(const cpl_propertylist *aHeaders, int aLamp)
query the status of one lamp
Definition: muse_pfits.c:1527
#define MUSE_PIXTABLE_LAMBDA
Definition: muse_pixtable.h:53
cpl_frameset * inframes
cpl_image * muse_convolve_image(const cpl_image *aImage, const cpl_matrix *aKernel)
Compute the convolution of an image with a kernel.
Definition: muse_utils.c:2748
cpl_error_code muse_utils_image_get_centroid_window(cpl_image *aImage, int aX1, int aY1, int aX2, int aY2, double *aX, double *aY, muse_utils_centroid_type aBgType)
Compute centroid over an image window, optionally marginalizing over the background.
Definition: muse_utils.c:1234
Definition of a cpl table structure.
muse_image * muse_image_new(void)
Allocate memory for a new muse_image object.
Definition: muse_image.c:66
cpl_matrix * muse_matrix_new_gaussian_2d(int aXHalfwidth, int aYHalfwidth, double aSigma)
Create a matrix that contains a normalized 2D Gaussian.
Definition: muse_utils.c:1099
cpl_frameset * muse_frameset_check_raw(const cpl_frameset *aFrames, const cpl_array *aTags, unsigned char aIFU)
return frameset containing good raw input data
Definition: muse_utils.c:284
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
const char * muse_pfits_get_chip_name(const cpl_propertylist *aHeaders)
find out the chip name
Definition: muse_pfits.c:584
cpl_error_code muse_utils_frameset_merge_frames(cpl_frameset *aFrames, cpl_boolean aDelete)
Merge IFU-specific files from a frameset to create multi-IFU outputs.
Definition: muse_utils.c:589
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_error_code muse_utils_copy_modified_header(cpl_propertylist *aH1, cpl_propertylist *aH2, const char *aKeyword, const char *aString)
Copy a modified header keyword from one header to another.
Definition: muse_utils.c:2570
cpl_error_code muse_image_dq_to_nan(muse_image *aImage)
Convert pixels flagged in the DQ extension to NANs in DATA (and STAT, if present).
Definition: muse_image.c:904
cpl_error_code muse_utils_get_centroid(const cpl_matrix *aPositions, const cpl_vector *aValues, const cpl_vector *aErrors, double *aX, double *aY, muse_utils_centroid_type aBgType)
Compute centroid of a two-dimensional dataset.
Definition: muse_utils.c:1328
cpl_propertylist * header
The FITS header.
cpl_frameset * muse_frameset_find_tags(const cpl_frameset *aFrames, const cpl_array *aTags, unsigned char aIFU, cpl_boolean aInvert)
return frameset containing data from an IFU/channel with the given tag(s)
Definition: muse_utils.c:253