MUSE Pipeline Reference Manual  1.0.2
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-2014 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 #if CPL_VERSION_CODE >= 394496 /* 6.5a2 snapshot or newer */
35 #include <cpl_multiframe.h>
36 #endif /* code for CPL 6.5a2 snapshot or newer */
37 #include <float.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <strings.h> /* strncasecmp() */
41 #ifdef HAVE_GETTIMEOFDAY
42 #include <sys/time.h> /* gettimeofday */
43 #endif
44 
45 #include "muse_utils.h"
46 #include "muse_instrument.h"
47 
48 #include "muse_cplwrappers.h"
49 #include "muse_pfits.h"
50 #include "muse_resampling.h"
51 #include "muse_tracing.h"
52 #include "muse_wcs.h"
53 
54 /*----------------------------------------------------------------------------*
55  * Debugging Macros *
56  *----------------------------------------------------------------------------*/
57 #define MOFFAT_USE_MUSE_OPTIMIZE 0 /* if non-zero, use the muse_cpl_optimize *
58  * interface to fit the moffat function */
59 #if MOFFAT_USE_MUSE_OPTIMIZE
60 #include "muse_optimize.h"
61 #endif
62 
63 /*----------------------------------------------------------------------------*/
69 /*----------------------------------------------------------------------------*/
70 
73 /*----------------------------------------------------------------------------*/
82 /*----------------------------------------------------------------------------*/
83 const char *
85 {
86  return cpl_get_license(PACKAGE_NAME, "2005, 2014");
87 } /* muse_get_license() */
88 
89 /*----------------------------------------------------------------------------*/
97 /*----------------------------------------------------------------------------*/
98 unsigned char
99 muse_utils_get_ifu(const cpl_propertylist *aHeaders)
100 {
101  unsigned char n;
102  for (n = 1; n <= kMuseNumIFUs; n++) {
103  if (muse_pfits_has_ifu(aHeaders, n)) {
104  return n;
105  } /* if */
106  } /* for n (all IFU numbers) */
107  return 0;
108 } /* muse_utils_get_ifu() */
109 
110 /*---------------------------------------------------------------------------*/
117 /*---------------------------------------------------------------------------*/
118 int
119 muse_utils_get_extension_for_ifu(const char *aFilename, unsigned char aIFU)
120 {
121  cpl_errorstate prestate = cpl_errorstate_get();
122  int i, next = cpl_fits_count_extensions(aFilename);
123  for (i = 0; i <= next; i++) {
124  cpl_propertylist *properties = cpl_propertylist_load(aFilename, i);
125  if (muse_pfits_has_ifu(properties, aIFU)) {
126  cpl_propertylist_delete(properties);
127  return i;
128  }
129  cpl_propertylist_delete(properties);
130  } /* for i (primary header and extensions) */
131  cpl_errorstate_set(prestate);
132  return -1;
133 } /* muse_utils_get_extension_for_ifu() */
134 
135 /*---------------------------------------------------------------------------*/
156 /*---------------------------------------------------------------------------*/
157 cpl_frameset *
158 muse_frameset_find(const cpl_frameset *aFrames, const char *aTag,
159  unsigned char aIFU, cpl_boolean aInvert)
160 {
161  cpl_ensure(aFrames, CPL_ERROR_NULL_INPUT, NULL);
162  cpl_frameset *newFrames = cpl_frameset_new();
163 
164  /* loop through all frames to determine properties */
165  cpl_size iframe, nframes = cpl_frameset_get_size(aFrames);
166  for (iframe = 0; iframe < nframes; iframe++) {
167  const cpl_frame *frame = cpl_frameset_get_position_const(aFrames, iframe);
168  const char *fn = cpl_frame_get_filename(frame),
169  *tag = cpl_frame_get_tag(frame);
170 
171  /* we are happy, if we want matched frames and found them, or we *
172  * want non-matching frames and found one (or one without a tag) */
173  cpl_boolean matched = !aInvert && (!aTag || (aTag && !strcmp(tag, aTag))),
174  unmatched = aInvert
175  && ((aTag && !tag) || (aTag && strcmp(tag, aTag)));
176  if (matched || unmatched) {
177  /* files produced outside the pipeline are generic, *
178  * so find out if this is one */
179  cpl_errorstate prestate = cpl_errorstate_get();
180  int extension = muse_utils_get_extension_for_ifu(fn, aIFU);
181  /* if we haven't found an extension, we should *
182  * still try to load the primary header */
183  if (extension == -1) {
184  extension = 0;
185  cpl_errorstate_set(prestate);
186  }
187  cpl_propertylist *header = cpl_propertylist_load(fn, extension);
188  unsigned char ifu = muse_utils_get_ifu(header);
189  prestate = cpl_errorstate_get();
190  const char *pipefile = muse_pfits_get_pipefile(header);
191  if (!cpl_errorstate_is_equal(prestate)) {
192  /* recover from missing PIPEFILE which does not exist in raw data */
193  cpl_errorstate_set(prestate);
194  }
195  if (ifu == aIFU) {
196  /* a pipeline produced file which contains data from the correct IFU */
197  cpl_frameset_insert(newFrames, cpl_frame_duplicate(frame));
198  } else if (ifu == 0 && !pipefile) {
199  /* not specific to any IFU, this is the case for external *
200  * calibration files, like EXTINCT_TABLE or LINE_CATALOG */
201  cpl_frameset_insert(newFrames, cpl_frame_duplicate(frame));
202  } else if (aIFU == 0) {
203  /* no IFU specified, this happens in the post-processing recipes */
204  cpl_frameset_insert(newFrames, cpl_frame_duplicate(frame));
205  } else if (!strncmp(aTag, "GEOMETRY_TABLE", 15)) {
206  /* since the geometry table is not IFU-specific, it should *
207  * always match, even if a specific IFU is requested */
208  cpl_frameset_insert(newFrames, cpl_frame_duplicate(frame));
209  } else {
210  /* do nothing */
211 #if 0
212  cpl_msg_debug(__func__, "not added %s / %s / %d", fn,
213  cpl_frame_get_tag(frame), ifu);
214 #endif
215  }
216  cpl_propertylist_delete(header);
217  } /* if */
218  } /* for iframe (all frames) */
219 
220  return newFrames;
221 } /* muse_frameset_find() */
222 
223 /*---------------------------------------------------------------------------*/
241 /*---------------------------------------------------------------------------*/
242 cpl_frameset *
243 muse_frameset_find_tags(const cpl_frameset *aFrames, const cpl_array *aTags,
244  unsigned char aIFU, cpl_boolean aInvert)
245 {
246  cpl_ensure(aFrames && aTags, CPL_ERROR_NULL_INPUT, NULL);
247  cpl_ensure(cpl_array_get_type(aTags) == CPL_TYPE_STRING,
248  CPL_ERROR_ILLEGAL_INPUT, NULL);
249 
250  cpl_frameset *outframes = cpl_frameset_new();
251  cpl_size itag, ntags = cpl_array_get_size(aTags);
252  for (itag = 0; itag < ntags; itag++) {
253  const char *tag = cpl_array_get_string(aTags, itag);
254  cpl_frameset *frames = muse_frameset_find(aFrames, tag, aIFU, aInvert);
255  cpl_frameset_join(outframes, frames);
256  cpl_frameset_delete(frames);
257  } /* for itag */
258  return outframes;
259 } /* muse_frameset_find_tags() */
260 
261 /*---------------------------------------------------------------------------*/
272 /*---------------------------------------------------------------------------*/
273 cpl_frameset *
274 muse_frameset_check_raw(const cpl_frameset *aFrames, const cpl_array *aTags,
275  unsigned char aIFU)
276 {
277  cpl_frameset *foundFrames = muse_frameset_find_tags(aFrames, aTags, aIFU,
278  CPL_FALSE);
279  cpl_frameset *newFrames = cpl_frameset_new();
280 
281  /* loop through all frames to determine properties */
282  cpl_size iframe, nframes = cpl_frameset_get_size(foundFrames);
283  cpl_msg_debug(__func__, "Determine properties of all %"CPL_SIZE_FORMAT
284  " raw frames of IFU %hhu", nframes, aIFU);
285  int binx = -1, biny = -1, readid = -1;
286  char *fn = NULL, *readname = NULL, *chipname = NULL, *chipid = NULL;
287  for (iframe = 0; iframe < nframes; iframe++) {
288  const cpl_frame *frame = cpl_frameset_get_position_const(foundFrames, iframe);
289  /* load primary and append IFU extension header (extension is *
290  * necessary to get the binning keywords related to raw data) */
291  const char *fn2 = cpl_frame_get_filename(frame);
292  if (!fn) {
293  fn = cpl_strdup(fn2);
294  }
295  cpl_propertylist *header = cpl_propertylist_load(fn2, 0);
296  int extension = muse_utils_get_extension_for_ifu(fn2, aIFU);
297  if (extension > 0) {
298  cpl_propertylist *exthead = cpl_propertylist_load(fn2, extension);
299  cpl_propertylist_append(header, exthead);
300  cpl_propertylist_delete(exthead);
301  }
302  if (!header) {
303  cpl_msg_warning(__func__, "Cannot read FITS header of file \"%s\"!", fn2);
304  continue;
305  }
306  cpl_boolean isOK = CPL_TRUE;
307  if (binx < 0) {
308  binx = muse_pfits_get_binx(header);
309  }
310  if (biny < 0) {
311  biny = muse_pfits_get_biny(header);
312  }
313  if (!readname) {
314  readname = cpl_strdup(muse_pfits_get_read_name(header));
315  }
316  if (readid < 0) {
317  readid = muse_pfits_get_read_id(header);
318  }
319  if (!chipname) {
320  chipname = cpl_strdup(muse_pfits_get_chip_name(header));
321  }
322  if (!chipid) {
323  chipid = cpl_strdup(muse_pfits_get_chip_id(header));
324  }
325  int binx2 = muse_pfits_get_binx(header),
326  biny2 = muse_pfits_get_biny(header),
327  readid2 = muse_pfits_get_read_id(header);
328  const char *chipname2 = muse_pfits_get_chip_name(header),
329  *chipid2 = muse_pfits_get_chip_id(header);
330  if (binx2 != binx) {
331  cpl_msg_warning(__func__, "File \"%s\" (IFU %hhu) was taken with a different"
332  " x-binning factor (reference \"%s\", %d instead of %d)!",
333  fn2, aIFU, fn, binx2, binx);
334  isOK = 0;
335  }
336  if (biny2 != biny) {
337  cpl_msg_warning(__func__, "File \"%s\" (IFU %hhu) was taken with a different"
338  " y-binning factor (reference \"%s\", %d instead of %d)!",
339  fn2, aIFU, fn, biny2, biny);
340  isOK = 0;
341  }
342  if (readid2 != readid) {
343  cpl_msg_warning(__func__, "File \"%s\" (IFU %hhu) was taken with a different"
344  " read-out mode (reference \"%s\", %d/%s instead of %d/%s)!",
345  fn2, aIFU, fn, readid2, muse_pfits_get_read_name(header),
346  readid, readname);
347  isOK = 0;
348  }
349  if (!chipname2 || !chipid2 ||
350  strcmp(chipname, chipname2) || strcmp(chipid, chipid2)) {
351  cpl_msg_warning(__func__, "File \"%s\" (IFU %hhu) has a different chip "
352  "setup (reference \"%s\", name %s vs %s, id %s vs %s)",
353  fn2, aIFU, fn, chipname2, chipname, chipid2, chipid);
354  isOK = 0;
355  }
356 
357  if (!cpl_frame_get_tag(frame) ||
358  !strncmp(cpl_frame_get_tag(frame), MUSE_TAG_EMPTY, 1) ) {
359  /* non-fatal, continue normally and check for other possible *
360  * problems, that should override this exclude reason */
361  cpl_msg_warning(__func__, "File \"%s\" (IFU %hhu) is not tagged!", fn2,
362  aIFU);
363  }
364  cpl_propertylist_delete(header);
365  if (isOK) {
366  cpl_frameset_insert(newFrames, cpl_frame_duplicate(frame));
367  }
368  } /* for nframes */
369  cpl_free(fn);
370  cpl_free(readname);
371  cpl_free(chipname);
372  cpl_free(chipid);
373  cpl_frameset_delete(foundFrames);
374 
375  return newFrames;
376 } /* muse_frameset_check_raw() */
377 
378 /*---------------------------------------------------------------------------*/
397 /*---------------------------------------------------------------------------*/
398 cpl_frameset *
399 muse_frameset_sort_raw_other(const cpl_frameset *aFrames, int aIndex,
400  const char *aDateObs, cpl_boolean aSequence)
401 {
402  cpl_ensure(aFrames, CPL_ERROR_NULL_INPUT, NULL);
403 
404  cpl_frameset *fraw = cpl_frameset_new(),
405  *fother = cpl_frameset_new();
406  int iraw = 0; /* raw frame index */
407  cpl_size iframe, nframes = cpl_frameset_get_size(aFrames);
408  for (iframe = 0; iframe < nframes; iframe++) {
409  const cpl_frame *frame = cpl_frameset_get_position_const(aFrames, iframe);
410  if (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_RAW) {
411  /* if the index matches, insert it, otherwise ignore */
412  int datematch = 1; /* pretend to match when aDateObs == NULL */
413  if (aDateObs) {
414  cpl_propertylist *header
415  = cpl_propertylist_load(cpl_frame_get_filename(frame), 0);
416  const char *dateobs = muse_pfits_get_dateobs(header);
417  datematch = !strncmp(aDateObs, dateobs, strlen(aDateObs));
418  cpl_propertylist_delete(header);
419  }
420  if ((aIndex < 0 && datematch) || aIndex == iraw || aSequence) {
421  cpl_frameset_insert(fraw, cpl_frame_duplicate(frame));
422  }
423  iraw++;
424  } else {
425  cpl_frameset_insert(fother, cpl_frame_duplicate(frame));
426  }
427  } /* for all incoming frames */
428 #if 0
429  printf("incoming frames (index=%d, DATE-OBS=%s):\n", aIndex, aDateObs);
430  cpl_frameset_dump(aFrames, stdout);
431  fflush(stdout);
432  printf("=============================================================\n"
433  "raw frames:\n");
434  cpl_frameset_dump(fraw, stdout);
435  fflush(stdout);
436  printf("-------------------------------------------------------------\n"
437  "other frames:\n");
438  cpl_frameset_dump(fother, stdout);
439  fflush(stdout);
440 #endif
441 
442  /* transfer all other frames into the raw = output frameset */
443  cpl_frameset_join(fraw, fother);
444  cpl_frameset_delete(fother);
445 #if 0
446  printf("=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=\n"
447  "all sorted frames:\n");
448  cpl_frameset_dump(fraw, stdout);
449  fflush(stdout);
450 #endif
451 
452  return fraw;
453 } /* muse_frameset_sort_raw_other() */
454 
455 /*---------------------------------------------------------------------------*/
466 /*---------------------------------------------------------------------------*/
467 cpl_frame *
468 muse_frameset_find_master(const cpl_frameset *aFrames, const char *aTag,
469  unsigned char aIFU)
470 {
471  cpl_frameset *frames = muse_frameset_find(aFrames, aTag, aIFU, CPL_FALSE);
472  cpl_frame *frame = NULL;
473  if (cpl_frameset_count_tags(frames, aTag) == 1) {
474  frame = cpl_frame_duplicate(cpl_frameset_get_position_const(frames, 0));
475  }
476  cpl_frameset_delete(frames);
477  return frame;
478 } /* muse_frameset_find_master() */
479 
480 #if CPL_VERSION_CODE >= 394330 /* 6.4.90 snapshot or newer */
481 
482 /* Remove .fits and possibly -nn from a filename of the style *
483  * "SOME_TAG_iiii-nn.fits". *
484  * The returned string has to be deallocated using cpl_free(). */
485 static char *
486 muse_utils_frame_get_basefilename(const cpl_frame *aFrame)
487 {
488  char *filename = cpl_strdup(cpl_frame_get_filename(aFrame)),
489  *end = strstr(filename, ".fits");
490  if (end) {
491  *end = '\0'; /* strip off ".fits" */
492  }
493  end = strrchr(filename, '-');
494  if (end) {
495  *end = '\0'; /* strip off the "-nn" extension number */
496  }
497  char *fn = cpl_sprintf("%s.fits", filename);
498  cpl_free(filename);
499  return fn;
500 } /* muse_utils_frame_get_basefilename() */
501 
502 /* Compare the base filenames of two frames. *
503  * To be used with cpl_frameset_labelise(). */
504 static int
505 muse_utils_frames_compare_basenames(const cpl_frame *aF1, const cpl_frame *aF2)
506 {
507  cpl_ensure(aF1 && aF2, CPL_ERROR_NULL_INPUT, -1);
508  cpl_ensure(cpl_frame_get_filename(aF1) && cpl_frame_get_filename(aF2),
509  CPL_ERROR_DATA_NOT_FOUND, -1);
510  char *fn1 = muse_utils_frame_get_basefilename(aF1),
511  *fn2 = muse_utils_frame_get_basefilename(aF2);
512  int cmp = strcmp(fn1, fn2);
513  cpl_free(fn1);
514  cpl_free(fn2);
515  return cmp == 0 ? 1 : 0;
516 } /* muse_utils_frames_compare_basenames() */
517 #endif /* code for CPL 6.4.90 snapshot or newer */
518 
519 static int
520 _muse_utils_frame_compare(const cpl_frame *self,
521  const cpl_frame *other)
522 {
523  const char *_self = cpl_frame_get_filename(self);
524  const char *_other = cpl_frame_get_filename(other);
525 
526  int cmp = strcmp(_self, _other);
527 
528 
529  // FIXME: Bug in cpl_frameset_sort()! Needs negated comparison!
530 
531 #if CPL_VERSION_CODE < 394497
532  return (cmp < 0) ? 1 : (cmp > 0) ? -1 : 0;
533 #else
534  return (cmp < 0) ? -1 : (cmp > 0) ? 1 : 0;
535 #endif
536 
537 }
538 
539 
540 cpl_error_code
541 muse_utils_frameset_merge_frames(cpl_frameset *aFrames)
542 {
543  cpl_ensure_code(aFrames, CPL_ERROR_NULL_INPUT);
544 #if 0
545  printf("final out frames:\n");
546  cpl_frameset_dump(aFrames, stdout);
547  fflush(stdout);
548 #endif
549 
550 #if CPL_VERSION_CODE >= 394496 /* 6.5a2 snapshot or newer */
551  /* set up keys and regular expression (positive and negative ones!) *
552  * for the merged extensions */
553 #define EXTKEYS1 MUSE_WCS_KEYS"|(ESO DET (CHIP|OUT[1-9]*) |ESO QC|ESO DRS)"
554 #define EXTKEYS2 MUSE_WCS_KEYS"|^B(UNIT|SCALE|ZERO)"
555  cpl_regex *regex = cpl_regex_new(EXTKEYS1, TRUE, CPL_REGEX_EXTENDED),
556  *nregex = cpl_regex_new(EXTKEYS1, FALSE, CPL_REGEX_EXTENDED),
557  *nregex2 = cpl_regex_new(EXTKEYS1"|"EXTKEYS2, FALSE,
558  CPL_REGEX_EXTENDED);
559 
560  /* check sets of output frames */
561  cpl_frameset *frames = cpl_frameset_new();
562 
563  cpl_size ilabel;
564  cpl_size nlabels = 0;
565  cpl_size *labels = cpl_frameset_labelise(aFrames,
566  muse_utils_frames_compare_basenames,
567  &nlabels);
568  for (ilabel = 0; ilabel < nlabels; ilabel++) {
569  cpl_frameset *fset = cpl_frameset_extract(aFrames, labels, ilabel);
570 
571  int i, n = cpl_frameset_get_size(fset);
572 
573  cpl_frameset_sort(fset, _muse_utils_frame_compare);
574 
575  cpl_frame *frame = cpl_frameset_get_position(fset, 0);
576  const char *tag = cpl_frame_get_tag(frame);
577 
578  // FIXME: This is a workaround to exclude pixel tables from being
579  // merged, since merged pixel tables can not yet be used
580  // by the post-processing recipes.
581 
582  if (strncmp(tag, "PIXTABLE_", 9) == 0) {
583  cpl_frameset_delete(fset);
584  continue;
585  }
586 
587  if (n <= 1) {
588  cpl_msg_warning(__func__, "Nothing to merge for tag %s (%d frames)!",
589  tag, n);
590  cpl_frameset_delete(fset);
591  }
592 
593  cpl_multiframe *mf = cpl_multiframe_new(frame, "", regex);
594  if (!mf) {
595  cpl_frameset_delete(fset);
596  continue;
597  }
598 
599  /* now loop through all frames with this tag and append them to the group */
600  for (i = 0; i < n; i++) {
601  frame = cpl_frameset_get_position(fset, i);
602  const char *fn = cpl_frame_get_filename(frame);
603  cpl_msg_debug(__func__, "Merging \"%s\".", fn);
604  /* check, if this is a three-extension muse_image or something else */
605  int extdata = cpl_fits_find_extension(fn, EXTNAME_DATA),
606  extdq = cpl_fits_find_extension(fn, EXTNAME_DQ),
607  extstat = cpl_fits_find_extension(fn, EXTNAME_STAT);
608  cpl_errorstate state = cpl_errorstate_get();
609  if (extdata > 0 && extdq > 0 && extstat > 0) {
610  /* is a three-extension muse_image */
611  const char *extnames[] = { EXTNAME_DATA, EXTNAME_DQ, EXTNAME_STAT },
612  *updkeys[] = { "SCIDATA", "ERRDATA", "QUALDATA", NULL };
613  const cpl_regex *filters[] = { nregex, nregex, nregex };
614  cpl_multiframe_append_datagroup(mf, ".", frame, 3, extnames, filters,
615  NULL, updkeys, CPL_MULTIFRAME_ID_JOIN);
616  } else if (cpl_fits_count_extensions(fn) == 0) {
617  /* is an image in the primary HDU *
618  * needs a different set of keywords to transfer the relevant stuff */
619  cpl_multiframe_append_dataset_from_position(mf, ".", frame, 0,
620  nregex2, NULL,
621  CPL_MULTIFRAME_ID_JOIN);
622  } else {
623  /* is a table or multi-extension file */
624  int iext, next = cpl_fits_count_extensions(fn);
625  for (iext = 1; iext <= next; iext++) {
626  cpl_multiframe_append_dataset_from_position(mf, ".", frame, iext,
627  nregex, NULL,
628  CPL_MULTIFRAME_ID_JOIN);
629  } /* for iext (all extensions) */
630  } /* else */
631 
632  if (!cpl_errorstate_is_equal(state)) {
633  cpl_msg_error(__func__, "Appending data of \"%s\" for merging failed: %s",
634  fn, cpl_error_get_message());
635  } /* if error */
636  } /* for i (all frames with this tag) */
637 
638  /* now finally write the merged file */
639  char *outfn = muse_utils_frame_get_basefilename(frame);
640  cpl_errorstate state = cpl_errorstate_get();
641  cpl_multiframe_write(mf, outfn);
642  if (!cpl_errorstate_is_equal(state)) {
643  cpl_msg_error(__func__, "Writing merged data to \"%s\" failed: %s",
644  outfn, cpl_error_get_message());
645  } else {
646  cpl_frame_set_filename(frame, outfn);
647  /* record the merged file in the resulting frameset */
648  cpl_frameset_insert(frames, cpl_frame_duplicate(frame));
649  }
650  cpl_free(outfn);
651  cpl_multiframe_delete(mf);
652  cpl_frameset_delete(fset);
653  } /* for ilabel (all frame labels) */
654  cpl_regex_delete(regex);
655  cpl_regex_delete(nregex);
656  cpl_regex_delete(nregex2);
657  cpl_free(labels);
658 #if 0
659  printf("\naFrames (trying to remove these):\n");
660  cpl_frameset_dump(aFrames, stdout);
661  fflush(stdout);
662  printf("\nmerged frames:\n");
663  cpl_frameset_dump(frames, stdout);
664  fflush(stdout);
665 #endif
666 
667  /* remove all frames for the successfully merged tags */
668  int iframe, nframes = cpl_frameset_get_size(frames);
669  for (iframe = 0; iframe < nframes; iframe++) {
670  cpl_frame *frame = cpl_frameset_get_position(frames, iframe);
671 #if 1
672  cpl_msg_debug(__func__, "===== Starting to compare \"%s\" =====",
673  cpl_frame_get_filename(frame));
674 #endif
675  int iframe2;
676  for (iframe2 = 0; iframe2 < cpl_frameset_get_size(aFrames); iframe2++) {
677  /* duplicate, so that we can use it to remove "itself" */
678  cpl_frame *frame2 = cpl_frameset_get_position(aFrames, iframe2);
679  char *fb1 = muse_utils_frame_get_basefilename(frame),
680  *fb2 = muse_utils_frame_get_basefilename(frame2);
681  if (muse_utils_frames_compare_basenames(frame, frame2) == 1) {
682  const char *fn = cpl_frame_get_filename(frame2);
683 #if 1
684  cpl_msg_debug(__func__, "Removing \"%s\" (\"%s\" vs \"%s\").",
685  fn, fb1, fb2);
686 #endif
687  remove(fn);
688  cpl_frameset_erase_frame(aFrames, frame2);
689  iframe2--; /* stay here to see what moved here */
690  } /* if */
691  cpl_free(fb1);
692  cpl_free(fb2);
693  } /* for iframe2 */
694  } /* for iframe (all frames in new frameset) */
695  cpl_frameset_join(aFrames, frames);
696  cpl_frameset_delete(frames);
697 #if 0
698  printf("\nmerged out frames:\n");
699  cpl_frameset_dump(aFrames, stdout);
700  fflush(stdout);
701 #endif
702 #endif /* code for CPL 6.4.90 snapshot or newer */
703  return CPL_ERROR_NONE;
704 } /* muse_utils_frameset_merge_frames() */
705 
706 /*---------------------------------------------------------------------------*/
719 /*---------------------------------------------------------------------------*/
720 cpl_table *
721 muse_table_load(muse_processing *aProcessing, const char *aTag,
722  unsigned char aIFU)
723 {
724  cpl_frame *frame = muse_frameset_find_master(aProcessing->inframes, aTag,
725  aIFU);
726  if (!frame) {
727  if (aIFU > 0) {
728  cpl_msg_debug(__func__, "No table found for tag %s and IFU %hhu", aTag,
729  aIFU);
730  } else {
731  /* swallow the IFU part, if it's not a real IFU number */
732  cpl_msg_debug(__func__, "No table found for tag %s", aTag);
733  }
734  return NULL;
735  }
736 
737  /* try to load the extension table for the given IFU */
738  const char *fn = cpl_frame_get_filename(frame);
739  int extension = muse_utils_get_extension_for_ifu(fn, aIFU);
740  /* other tables hopefully always have the data in the first extension */
741  if (extension <= 0) {
742  if (aIFU > 0) {
743  cpl_msg_debug(__func__, "didn't find a specific extension for IFU %hhu, "
744  "will just use the first one", aIFU);
745  }
746  extension = 1;
747  }
748  cpl_table *table = cpl_table_load(fn, extension, 0);
749  if (!table || !cpl_table_get_nrow(table)) {
750  cpl_msg_info(__func__, "loading %s from file \"%s\" (ext %d) failed: %s",
751  aTag, fn, extension, cpl_error_get_message());
752  cpl_frame_delete(frame);
753  cpl_table_delete(table); /* try to delete in case it was just empty */
754  return NULL;
755  }
756  cpl_propertylist *header = cpl_propertylist_load(fn, extension);
757  cpl_errorstate state = cpl_errorstate_get();
758  const char *en = muse_pfits_get_extname(header);
759  char *extname = NULL;
760  if (en && cpl_errorstate_is_equal(state)) {
761  extname = cpl_sprintf("[%s]", en);
762  } else {
763  cpl_errorstate_set(state);
764  extname = cpl_sprintf("%c", '\0');
765  }
766  cpl_msg_info(__func__, "loaded %s from file \"%s%s\" (ext %d)", aTag, fn,
767  extname, extension);
768  cpl_free(extname);
769  cpl_propertylist_delete(header);
770  /* tables are calibrations of some sort, so append them as such into *
771  * the used frames for the FITS header of the output product, if they exist */
772  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_CALIB, 0);
773 
774  return table;
775 } /* muse_table_load() */
776 
777 /*---------------------------------------------------------------------------*/
787 /*---------------------------------------------------------------------------*/
788 cpl_propertylist *
789 muse_propertylist_load(muse_processing *aProcessing, const char *aTag)
790 {
791  cpl_frame *frame = muse_frameset_find_master(aProcessing->inframes, aTag, 0);
792  if (!frame) {
793  cpl_msg_debug(__func__, "No propertylist found for tag %s", aTag);
794  return NULL;
795  }
796 
797  const char *filename = cpl_frame_get_filename(frame);
798  cpl_propertylist *pl = cpl_propertylist_load(filename, 0);
799  if (!pl) {
800  cpl_msg_info(__func__, "loading %s from file %s failed: %s", aTag,
801  filename, cpl_error_get_message());
802  cpl_frame_delete(frame);
803  return NULL;
804  }
805  cpl_msg_info(__func__, "loaded %s from file %s", aTag, filename);
806  /* property lists are calibrations of some sort, so append them as such into *
807  * the used frames for the FITS header of the output product, if they exist */
808  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_CALIB, 0);
809 
810  return pl;
811 } /* muse_propertylist_load() */
812 
813 /*----------------------------------------------------------------------------*/
821 /*----------------------------------------------------------------------------*/
823  { "lambda", CPL_TYPE_DOUBLE, "Angstrom", "%7.2f", "wavelength", CPL_TRUE},
824  { "throughput", CPL_TYPE_DOUBLE, "", "%.4e",
825  "filter response (in fractions of 1)", CPL_TRUE},
826  { NULL, 0, NULL, NULL, NULL, CPL_FALSE }
827 };
828 
829 /*---------------------------------------------------------------------------*/
851 /*---------------------------------------------------------------------------*/
852 cpl_table *
853 muse_table_load_filter(muse_processing *aProcessing, const char *aFilterName)
854 {
855  cpl_ensure(aFilterName, CPL_ERROR_NULL_INPUT, NULL);
856  if (!strncasecmp(aFilterName, "none", 4)) {
857  cpl_msg_debug(__func__, "No filter wanted (filter \"%s\")", aFilterName);
858  return NULL;
859  }
860  if (!strcmp(aFilterName, "white")) {
861  cpl_msg_debug(__func__, "White-light integration wanted (filter \"%s\")",
862  aFilterName);
863  /* create minimal rectangular filter function table */
864  cpl_table *table = muse_cpltable_new(muse_filtertable_def, 4);
865  cpl_table_set(table, "lambda", 0, kMuseNominalLambdaMin - 1e-5);
866  cpl_table_set(table, "lambda", 1, kMuseNominalLambdaMin);
867  cpl_table_set(table, "lambda", 2, kMuseNominalLambdaMax);
868  cpl_table_set(table, "lambda", 3, kMuseNominalLambdaMax - 1e-5);
869  cpl_table_set(table, "throughput", 0, 0.);
870  cpl_table_set(table, "throughput", 1, 1.);
871  cpl_table_set(table, "throughput", 2, 1.);
872  cpl_table_set(table, "throughput", 3, 0.);
873  return table;
874  } /* if white-light filter */
875  cpl_frame *frame = muse_frameset_find_master(aProcessing->inframes,
876  MUSE_TAG_FILTER_LIST, 0);
877  if (!frame) {
878  cpl_error_set_message(__func__, CPL_ERROR_FILE_NOT_FOUND, "%s (for filter "
879  "\"%s\") is missing", MUSE_TAG_FILTER_LIST,
880  aFilterName);
881  return NULL;
882  }
883 
884  /* try to load the extension table for the given filter, using EXTNAME */
885  char *filename = (char *)cpl_frame_get_filename(frame);
886  int ext = cpl_fits_find_extension(filename, aFilterName);
887  if (ext <= 0) {
888  cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND, "\"%s\" does not "
889  "contain filter \"%s\"", filename, aFilterName);
890  cpl_frame_delete(frame);
891  return NULL;
892  }
893 
894  cpl_table *table = cpl_table_load(filename, ext, 0);
895  if (!table || !cpl_table_get_nrow(table)) {
896  cpl_error_set_message(__func__, cpl_error_get_code(), "loading filter "
897  "\"%s\" from file \"%s\" (ext %d) failed",
898  aFilterName, filename, ext);
899  cpl_frame_delete(frame);
900  cpl_table_delete(table); /* try to delete in case it was just empty */
901  return NULL;
902  }
903  cpl_msg_info(__func__, "loaded filter \"%s\" from file \"%s\" (ext %d)",
904  aFilterName, filename, ext);
905  /* filters from the input frameset should be listed, append them into *
906  * the used frames for the FITS header of the output product, if they exist */
907  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_CALIB, 0);
908  return table;
909 } /* muse_table_load_filter() */
910 
911 /*---------------------------------------------------------------------------*/
931 /*---------------------------------------------------------------------------*/
932 char *
933 muse_utils_header_get_lamp_names(cpl_propertylist *aHeader, char aSep)
934 {
935  cpl_ensure(aHeader, CPL_ERROR_NULL_INPUT, NULL);
936 
937  char *lampname = NULL;
938  int n, nlamps = muse_pfits_get_lampnum(aHeader);
939  for (n = 1; n <= nlamps; n++) {
940  cpl_errorstate prestate = cpl_errorstate_get();
941  int st1 = muse_pfits_get_lamp_status(aHeader, n),
942  st2 = muse_pfits_get_shut_status(aHeader, n);
943  if (!cpl_errorstate_is_equal(prestate)) {
944  cpl_errorstate_set(prestate); /* lamp headers may be missing */
945  }
946  if (!st1 || !st2) {
947  continue; /* lamp off or its shutter closed */
948  }
949  char *name = /* XXX ! */(char *)muse_pfits_get_lamp_name(aHeader, n);
950  if (!strncmp(name, "CU-LAMP-", 8)) {
951  name += 8; /* ignore the lamp prefix */
952  }
953  if (!strcmp(name, "CU-LAMP3") || !strcmp(name, "CU-LAMP6")) { /* XXX overwrite read-only locations */
954  /* 3 is standard Neon, 6 is high-power Neon */
955  name[0] = 'N';
956  name[1] = 'e';
957  name[2] = '\0';
958  } else if (!strcmp(name, "CU-LAMP4")) {
959  name[0] = 'X';
960  name[1] = 'e';
961  name[2] = '\0';
962  } else if (!strcmp(name, "CU-LAMP5")) {
963  name[0] = 'H';
964  name[1] = 'g';
965  name[2] = 'C';
966  name[3] = 'd';
967  name[4] = '\0';
968  }
969  if (lampname) {
970  char *temp = lampname;
971  lampname = cpl_sprintf("%s%c%s", temp, aSep, name);
972  cpl_free(temp);
973  } else {
974  lampname = cpl_sprintf("%s", name);
975  }
976  } /* for n (all possible lamps) */
977  return lampname;
978 } /* muse_utils_header_get_lamp_names() */
979 
980 /*---------------------------------------------------------------------------*/
998 /*---------------------------------------------------------------------------*/
999 cpl_array *
1000 muse_utils_header_get_lamp_numbers(cpl_propertylist *aHeader)
1001 {
1002  cpl_ensure(aHeader, CPL_ERROR_NULL_INPUT, NULL);
1003 
1004  /* start with an array of zero length */
1005  cpl_array *lampnumbers = cpl_array_new(0, CPL_TYPE_INT);
1006  int n, nlamps = muse_pfits_get_lampnum(aHeader);
1007  for (n = 1; n <= nlamps; n++) {
1008  cpl_errorstate prestate = cpl_errorstate_get();
1009  int st1 = muse_pfits_get_lamp_status(aHeader, n),
1010  st2 = muse_pfits_get_shut_status(aHeader, n);
1011  if (!cpl_errorstate_is_equal(prestate)) {
1012  cpl_errorstate_set(prestate); /* lamp headers may be missing */
1013  }
1014  if (!st1 || !st2) {
1015  continue; /* lamp off or its shutter closed */
1016  }
1017  /* now enlarge the array and save the lamp numbers as last element */
1018  cpl_array_set_size(lampnumbers, cpl_array_get_size(lampnumbers) + 1);
1019  cpl_array_set_int(lampnumbers, cpl_array_get_size(lampnumbers) - 1,
1020  n);
1021  } /* for n (all possible lamps) */
1022  if (cpl_array_get_size(lampnumbers) < 1) {
1023  cpl_array_delete(lampnumbers);
1024  lampnumbers = NULL;
1025  }
1026  return lampnumbers;
1027 } /* muse_utils_header_get_lamp_numbers() */
1028 
1029 /*----------------------------------------------------------------------------*/
1041 /*----------------------------------------------------------------------------*/
1042 cpl_matrix *
1043 muse_matrix_new_gaussian_2d(int aXHalfwidth, int aYHalfwidth, double aSigma)
1044 {
1045  cpl_matrix *kernel = cpl_matrix_new(2*aXHalfwidth+1, 2*aYHalfwidth+1);
1046  if (!kernel) {
1047  cpl_msg_error(__func__, "Could not create matrix: %s",
1048  cpl_error_get_message());
1049  return NULL;
1050  }
1051  double sum = 0.;
1052  int i;
1053  for (i = -aXHalfwidth; i <= aXHalfwidth; i++) {
1054  int j;
1055  for (j = -aYHalfwidth; j <= aYHalfwidth; j++) {
1056  /* set Gaussian kernel */
1057  double gauss = 1. / (aSigma*sqrt(2.*CPL_MATH_PI))
1058  * exp(-(i*i + j*j) / (2.*aSigma*aSigma));
1059  cpl_matrix_set(kernel, i+aXHalfwidth, j+aYHalfwidth, gauss);
1060  sum += gauss;
1061  }
1062  }
1063  /* normalize the matrix, the sum of the elements should be 1 */
1064  cpl_matrix_divide_scalar(kernel, sum);
1065 
1066  return kernel;
1067 } /* muse_matrix_new_gaussian_2d */
1068 
1069 /*----------------------------------------------------------------------------*/
1089 /*----------------------------------------------------------------------------*/
1090 cpl_image *
1091 muse_utils_image_fit_polynomial(const cpl_image *aImage, unsigned short aXOrder,
1092  unsigned short aYOrder)
1093 {
1094  cpl_ensure(aImage, CPL_ERROR_NULL_INPUT, NULL);
1095 
1096  /* transfer all pixel positions and their values *
1097  * into matrix and vector for the polynomial fit */
1098  int nx = cpl_image_get_size_x(aImage),
1099  ny = cpl_image_get_size_y(aImage);
1100  cpl_matrix *pos = cpl_matrix_new(2, nx * ny);
1101  cpl_vector *val = cpl_vector_new(nx * ny);
1102  int i, j, np = 0;
1103  for (i = 1; i < nx; i++) {
1104  for (j = 1; j < ny; j++) {
1105  if (cpl_image_is_rejected(aImage, i, j)) {
1106  continue;
1107  }
1108  cpl_matrix_set(pos, 0, np, i);
1109  cpl_matrix_set(pos, 1, np, j);
1110  int err;
1111  cpl_vector_set(val, np, cpl_image_get(aImage, i, j, &err));
1112  np++;
1113  } /* for j (vertical pixels) */
1114  } /* for i (horizontal pixels) */
1115  /* vectors and matrices cannot be resized to have zero elements, exit early */
1116  if (!np) {
1117  cpl_matrix_delete(pos);
1118  cpl_vector_delete(val);
1119  cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND, "No good pixels "
1120  "found in image, polynomial fit cannot be performed!");
1121  return NULL;
1122  }
1123  /* re-set sizes depending on the number of valid pixels found */
1124  cpl_matrix_set_size(pos, 2, np);
1125  cpl_vector_set_size(val, np);
1126 
1127  /* carry out the fit */
1128  cpl_polynomial *poly = cpl_polynomial_new(2);
1129  const cpl_boolean sym = CPL_FALSE;
1130  const cpl_size mindeg[] = { 0, 0 },
1131  maxdeg[] = { aXOrder, aYOrder };
1132  cpl_error_code rc = cpl_polynomial_fit(poly, pos, &sym, val, NULL, CPL_TRUE,
1133  mindeg, maxdeg);
1134  cpl_matrix_delete(pos);
1135  cpl_vector_delete(val);
1136 
1137  /* create the output image from the polynomial, if the fit succeeded */
1138  cpl_image *fit = NULL;
1139  if (rc == CPL_ERROR_NONE) {
1140  fit = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
1141  cpl_image_fill_polynomial(fit, poly, 1, 1, 1, 1);
1142  if (cpl_image_get_bpm_const(aImage)) {
1143  cpl_image_reject_from_mask(fit, cpl_image_get_bpm_const(aImage));
1144  } /* if input has bpm */
1145  } /* if fit without error */
1146  cpl_polynomial_delete(poly);
1147  return fit;
1148 } /* muse_utils_image_fit_polynomial() */
1149 
1150 /*----------------------------------------------------------------------------*/
1176 /*----------------------------------------------------------------------------*/
1177 cpl_error_code
1178 muse_utils_image_get_centroid_window(cpl_image *aImage, int aX1, int aY1,
1179  int aX2, int aY2, double *aX, double *aY,
1180  muse_utils_centroid_type aBgType)
1181 {
1182  cpl_ensure_code(aImage, CPL_ERROR_NULL_INPUT);
1183  cpl_ensure_code(aX || aY, CPL_ERROR_NULL_INPUT);
1184 
1185  cpl_image *im = cpl_image_extract(aImage, aX1, aY1, aX2, aY2);
1186  cpl_ensure_code(im, cpl_error_get_code());
1187 
1188  double bg = 0.; /* subtract the background */
1189  if (aBgType == MUSE_UTILS_CENTROID_MEAN) {
1190  bg = cpl_image_get_mean(im);
1191  } else if (aBgType == MUSE_UTILS_CENTROID_MEDIAN) {
1192  bg = cpl_image_get_median(im);
1193  } else {
1194  cpl_ensure_code(aBgType == MUSE_UTILS_CENTROID_NORMAL,
1195  CPL_ERROR_ILLEGAL_INPUT);
1196  }
1197  cpl_image_subtract_scalar(im, bg);
1198 
1199  /* centroid in x direction */
1200  if (aX) {
1201  cpl_image *row = cpl_image_collapse_create(im, 0);
1202  double w = 0., /* weight */
1203  f = 0.; /* flux */
1204  int i, n = cpl_image_get_size_x(row);
1205  for (i = 1; i <= n; i++) {
1206  int err;
1207  double value = cpl_image_get(row, i, 1, &err);
1208  if (err || (value < 0 && aBgType != MUSE_UTILS_CENTROID_NORMAL)) {
1209  continue;
1210  }
1211  w += i * value;
1212  f += value;
1213  } /* for i */
1214  *aX = w / f + aX1 - 1;
1215  cpl_image_delete(row);
1216  } /* if aX */
1217  /* centroid in y direction */
1218  if (aY) {
1219  cpl_image *col = cpl_image_collapse_create(im, 1);
1220  double w = 0., /* weight */
1221  f = 0.; /* flux */
1222  int j, n = cpl_image_get_size_y(col);
1223  for (j = 1; j <= n; j++) {
1224  int err;
1225  double value = cpl_image_get(col, 1, j, &err);
1226  if (err || (value < 0 && aBgType != MUSE_UTILS_CENTROID_NORMAL)) {
1227  continue;
1228  }
1229  w += j * value;
1230  f += value;
1231  } /* for j */
1232  *aY = w / f + aY1 - 1;
1233  cpl_image_delete(col);
1234  } /* if aY */
1235  cpl_image_delete(im);
1236 
1237  return CPL_ERROR_NONE;
1238 } /* muse_utils_image_get_centroid_window() */
1239 
1240 /*---------------------------------------------------------------------------*/
1270 /*---------------------------------------------------------------------------*/
1271 cpl_error_code
1272 muse_utils_get_centroid(const cpl_matrix *aPositions,
1273  const cpl_vector *aValues, const cpl_vector *aErrors,
1274  double *aX, double *aY, muse_utils_centroid_type aBgType)
1275 {
1276  cpl_ensure_code(aPositions && aValues, CPL_ERROR_NULL_INPUT);
1277  cpl_ensure_code(cpl_matrix_get_ncol(aPositions) == 2, CPL_ERROR_ILLEGAL_INPUT);
1278  int npoints = cpl_matrix_get_nrow(aPositions);
1279  cpl_ensure_code(npoints == cpl_vector_get_size(aValues), CPL_ERROR_ILLEGAL_INPUT);
1280  if (aErrors) {
1281  cpl_ensure_code(cpl_vector_get_size(aValues) == cpl_vector_get_size(aErrors),
1282  CPL_ERROR_ILLEGAL_INPUT);
1283  }
1284  cpl_ensure_code(aX || aY, CPL_ERROR_NULL_INPUT);
1285 
1286  const double *values = cpl_vector_get_data_const(aValues);
1287  double xcen = 0., ycen = 0.,
1288  weight = 0., bg = 0.;
1289  if (aBgType == MUSE_UTILS_CENTROID_MEAN) {
1290  bg = cpl_vector_get_mean(aValues);
1291  } else if (aBgType == MUSE_UTILS_CENTROID_MEDIAN) {
1292  bg = cpl_vector_get_median_const(aValues);
1293  } else {
1294  cpl_ensure_code(aBgType == MUSE_UTILS_CENTROID_NORMAL,
1295  CPL_ERROR_ILLEGAL_INPUT);
1296  }
1297 
1298  int i;
1299  for (i = 0; i < npoints; i++) {
1300  double w = values[i] - bg;
1301  if (w < 0 && aBgType != MUSE_UTILS_CENTROID_NORMAL) {
1302  continue;
1303  }
1304  if (aErrors) {
1305  w /= cpl_vector_get(aErrors, i);
1306  }
1307  xcen += cpl_matrix_get(aPositions, i, 0) * w;
1308  ycen += cpl_matrix_get(aPositions, i, 1) * w;
1309  weight += w;
1310  } /* for i */
1311  xcen /= weight;
1312  ycen /= weight;
1313 
1314  if (aX) {
1315  *aX = xcen;
1316  }
1317  if (aY) {
1318  *aY = ycen;
1319  }
1320  return CPL_ERROR_NONE;
1321 } /* muse_utils_get_centroid() */
1322 
1323 /*----------------------------------------------------------------------------*/
1346 /*----------------------------------------------------------------------------*/
1347 static int
1348 muse_utils_multigauss(const double x[], const double p[], double *f)
1349 {
1350  const double xp = x[0]; /* evaluation point */
1351  const cpl_size ncoeffs = p[0],
1352  npeaks = p[1];
1353  const double sigma = p[2 + ncoeffs];
1354  if (sigma == 0.0) { /* special case, Dirac deltas */
1355  cpl_size i;
1356  for (i = 0; i < npeaks; i++) {
1357  if (p[2 + ncoeffs + 1 + i] == xp) {
1358  *f = DBL_MAX;
1359  return 0;
1360  }
1361  }
1362  *f = 0.;
1363  return 0;
1364  }
1365 
1366  /* compute the function value for the normal case */
1367  *f = 0.;
1368  /* evaluate the polynomial, adding all orders to the function value */
1369  cpl_size ic;
1370  for (ic = 0; ic < ncoeffs; ic++) {
1371  *f += p[2 + ic] * pow(xp, ic);
1372  }
1373  /* evaluate the Gaussians, adding all peaks to the function value */
1374  cpl_size ip;
1375  for (ip = 0; ip < npeaks; ip++) {
1376  const double xi = p[3 + ncoeffs + ip],
1377  Ai = p[3 + ncoeffs + npeaks + ip],
1378  exponent = (xi - xp) / sigma;
1379  *f += Ai / CPL_MATH_SQRT2PI / sigma * exp(-0.5 * exponent*exponent);
1380  }
1381 #if 0
1382  printf("eval at %f --> %f\n", xp, *f);
1383  int i;
1384  for (i = 0; i < ncoeffs + 2*npeaks + 3; i++) {
1385  printf(" [%02d] %f\n", i, p[i]);
1386  }
1387  fflush(stdout);
1388 #endif
1389  return 0;
1390 } /* muse_utils_multigauss() */
1391 
1392 /*----------------------------------------------------------------------------*/
1402 /*----------------------------------------------------------------------------*/
1403 static int
1404 muse_utils_dmultigauss(const double x[], const double p[], double f[])
1405 {
1406  const cpl_size ncoeffs = p[0],
1407  npeaks = p[1];
1408  const double sigma = p[2 + ncoeffs];
1409  if (sigma == 0.0) { /* special case, Dirac deltas */
1410  memset(f, 0, sizeof(double) * (ncoeffs + 2*npeaks + 3));
1411  return 0;
1412  }
1413 
1414  const double xp = x[0]; /* evaluation point */
1415  f[0] = f[1] = 0.; /* derivatives of the number of parameters are always zero! */
1416  /* the derivatives by the coefficients of the polynomial */
1417  cpl_size ic;
1418  for (ic = 0; ic < ncoeffs; ic++) {
1419  f[2 + ic] = pow(xp, ic);
1420  }
1421  /* derivative regarding sigma */
1422  f[2 + ncoeffs] = 0.;
1423  cpl_size ip;
1424  for (ip = 0; ip < npeaks; ip++) {
1425  const double xi = p[3 + ncoeffs + ip],
1426  Ai = p[3 + ncoeffs + npeaks + ip],
1427  exponent = (xi - xp) / sigma,
1428  expsq = exponent * exponent,
1429  expfunc = exp(-0.5 * expsq);
1430  f[2 + ncoeffs] -= Ai / CPL_MATH_SQRT2PI / (sigma*sigma)
1431  * (1 - expsq) * expfunc;
1432  /* derivative regarding centers */
1433  f[3 + ncoeffs + ip] = -Ai / CPL_MATH_SQRT2PI / (sigma*sigma*sigma)
1434  * (xi - xp) * expfunc;
1435  /* derivative regarding fluxes */
1436  f[3 + ncoeffs + npeaks + ip] = 1 / CPL_MATH_SQRT2PI / sigma * expfunc;
1437  } /* for ip (peak indices) */
1438 #if 0
1439  printf("deval at %f -->\n", xp);
1440  int i;
1441  for (i = 0; i < ncoeffs + 2*npeaks + 3; i++) {
1442  printf(" [%02d] %e %f\n", i, f[i], p[i]);
1443  }
1444  fflush(stdout);
1445 #endif
1446  return 0;
1447 } /* muse_utils_dmultigauss() */
1448 
1449 /*----------------------------------------------------------------------------*/
1500 /*----------------------------------------------------------------------------*/
1501 cpl_error_code
1502 muse_utils_fit_multigauss_1d(const cpl_vector *aX, const cpl_bivector *aY,
1503  cpl_vector *aCenter, double *aSigma,
1504  cpl_vector *aFlux, cpl_vector *aPoly,
1505  double *aMSE, double *aRedChisq,
1506  cpl_matrix **aCovariance)
1507 {
1508  if (aCovariance) {
1509  *aCovariance = NULL;
1510  }
1511  cpl_ensure_code(aX && aY && aCenter && aSigma, CPL_ERROR_NULL_INPUT);
1512  cpl_size npoints = cpl_vector_get_size(aX);
1513  cpl_ensure_code(npoints == cpl_bivector_get_size(aY), CPL_ERROR_INCOMPATIBLE_INPUT);
1514  cpl_size npeaks = cpl_vector_get_size(aCenter);
1515  cpl_ensure_code(!aFlux || npeaks == cpl_vector_get_size(aFlux),
1516  CPL_ERROR_INCOMPATIBLE_INPUT);
1517  cpl_size ncoeffs = aPoly ? cpl_vector_get_size(aPoly) : 0,
1518  npars = ncoeffs /* poly coeffs */ + 1 /* sigma */
1519  + 2 * npeaks /* centers and fluxes */;
1520  cpl_ensure_code(!aRedChisq || npoints >= npars, CPL_ERROR_ILLEGAL_INPUT);
1521 
1522  /* "transform" the input data into the right structures for cpl_fit_lvmq() */
1523  cpl_matrix *x = cpl_matrix_wrap(npoints, 1, (double *)cpl_vector_get_data_const(aX));
1524  const cpl_vector *y = cpl_bivector_get_x_const(aY),
1525  *ye = cpl_bivector_get_y_const(aY);
1526  /* set up and fill the parameters structure */
1527  cpl_vector *p = cpl_vector_new(npars + 2);
1528  int *pflags = cpl_calloc(npars + 2, sizeof(int));
1529  /* first the numbers (of polynomial coefficients and peaks) */
1530  cpl_vector_set(p, 0, ncoeffs);
1531  cpl_vector_set(p, 1, npeaks);
1532  cpl_size i; /* all but these two first parameters participate in the fit */
1533  for (i = 2; i < npars + 2; i++) {
1534  pflags[i] = 1; /* fit this parameter, set to non-zero value */
1535  } /* for i (all but the first two parameters) */
1536 #if 0
1537  cpl_array *aflags = cpl_array_wrap_int(pflags, npars + 2);
1538  printf("aflags, non-zero means parameter is fitted by cpl_fit_lvmq():\n");
1539  cpl_array_dump(aflags, 0, 1000, stdout);
1540  fflush(stdout);
1541  cpl_array_unwrap(aflags);
1542 #endif
1543  /* then the polynomial */
1544  cpl_size j;
1545  for (j = 0, i = 2; j < ncoeffs; j++, i++) {
1546  cpl_vector_set(p, i, cpl_vector_get(aPoly, j));
1547  }
1548  /* the common sigma value */
1549  double sigma = fabs(*aSigma);
1550  if (*aSigma < 0) {
1551  pflags[i] = 0; /* fix the sigma parameter */
1552  }
1553  cpl_vector_set(p, i++, sigma);
1554  /* the first-guess centers as passed into this function */
1555  for (j = 0; j < npeaks; j++, i++) {
1556  cpl_vector_set(p, i, cpl_vector_get(aCenter, j));
1557  }
1558  /* the first-guess fluxes, if passed into this function */
1559  for (j = 0; j < npeaks; j++, i++) {
1560  if (aFlux) {
1561  cpl_vector_set(p, i, cpl_vector_get(aFlux, j));
1562  } else {
1563  cpl_vector_set(p, i, 1.);
1564  }
1565  }
1566 #if 0
1567  printf("input parameters p and their pflags:\n");
1568  for (j = 0; j < cpl_vector_get_size(p); j++) {
1569  printf(" [%02d] %f %s\n", j, cpl_vector_get(p, j),
1570  pflags[j] ? "\tfitted" : "constant");
1571  }
1572 #endif
1573  cpl_matrix *covariance = NULL;
1574  cpl_error_code rc = cpl_fit_lvmq(x, NULL, y, ye, p, pflags,
1575  muse_utils_multigauss, muse_utils_dmultigauss,
1576  CPL_FIT_LVMQ_TOLERANCE, CPL_FIT_LVMQ_COUNT,
1577  CPL_FIT_LVMQ_MAXITER, aMSE, aRedChisq,
1578  &covariance);
1579  cpl_matrix_unwrap(x);
1580  cpl_free(pflags);
1581 #if 0
1582  printf("output parameters vector p (%e, %e):\n",
1583  aMSE ? sqrt(*aMSE) : 0.0, aRedChisq ? *aRedChisq : 0.0);
1584  cpl_vector_dump(p, stdout);
1585  fflush(stdout);
1586 #endif
1587  /* get all parameters back into the input structures, same order as above */
1588  for (j = 0, i = 2; j < ncoeffs; j++, i++) {
1589  cpl_vector_set(aPoly, j, cpl_vector_get(p, i));
1590  }
1591  /* In principle, the LM algorithm might have converged to a negative sigma *
1592  * (even if the guess value was positive). Make sure that the returned *
1593  * sigma is positive (by convention), see cpl_vector_fit_gaussian(). */
1594  *aSigma = fabs(cpl_vector_get(p, i++));
1595  for (j = 0; j < npeaks; j++, i++) {
1596  cpl_vector_set(aCenter, j, cpl_vector_get(p, i));
1597  }
1598  /* the first-guess fluxes, if passed into this function */
1599  if (aFlux) {
1600  for (j = 0; j < npeaks; j++, i++) {
1601  cpl_vector_set(aFlux, j, cpl_vector_get(p, i));
1602  }
1603  }
1604  /* extract the relevant part of the covariance matrix, if needed *
1605  * (the number of coefficients and peaks are not relevant parameters!) */
1606  if (aCovariance) {
1607  *aCovariance = cpl_matrix_extract(covariance, 2, 2, 1, 1,
1608  cpl_matrix_get_nrow(covariance) - 2,
1609  cpl_matrix_get_ncol(covariance) - 2);
1610  }
1611  cpl_matrix_delete(covariance);
1612  cpl_vector_delete(p);
1613  return rc;
1614 } /* muse_utils_fit_multigauss_1d() */
1615 
1616 #if MOFFAT_USE_MUSE_OPTIMIZE
1617 /* structure to pass around data needed for the *
1618  * evaluation using muse_moffat_2d_optfunc() */
1619 typedef struct {
1620  const cpl_matrix *positions;
1621  const cpl_vector *values;
1622  const cpl_vector *errors;
1623 } fitdata_t;
1624 
1625 /*---------------------------------------------------------------------------*/
1637 /*---------------------------------------------------------------------------*/
1638 static cpl_error_code
1639 muse_moffat_2d_optfunc(void *aData, cpl_array *aParams, cpl_array *aResiduals)
1640 {
1641  const cpl_matrix *pos = ((fitdata_t *)aData)->positions;
1642  const cpl_vector *val = ((fitdata_t *)aData)->values,
1643  *err = ((fitdata_t *)aData)->errors;
1644  /* Compute function residuals */
1645  const double *p = cpl_array_get_data_double_const(aParams);
1646  double *residuals = cpl_array_get_data_double(aResiduals);
1647  int i, npoints = cpl_vector_get_size(val);
1648  for (i = 0; i < npoints; i++) {
1649  double xterm = (cpl_matrix_get(pos, i, 0) - p[2]) / p[4],
1650  yterm = (cpl_matrix_get(pos, i, 1) - p[3]) / p[5],
1651  crossterm = 2 * p[7] * xterm * yterm,
1652  base = 1 + (xterm*xterm + crossterm + yterm*yterm) / (1 + p[7]*p[7]),
1653  moffat = p[0] + p[1] * (p[6] - 1)
1654  / (CPL_MATH_PI * p[4]*p[5] * sqrt(1 - p[7]*p[7]))
1655  * pow(base, -p[6]);
1656  /* set to the actual value */
1657  residuals[i] = cpl_vector_get(val, i) - moffat;
1658  /* finally weight by the error on this point */
1659  residuals[i] /= cpl_vector_get(err, i);
1660  } /* for i (all points) */
1661  return CPL_ERROR_NONE;
1662 } /* muse_moffat_2d_optfunc() */
1663 
1664 #else /* MOFFAT_USE_MUSE_OPTIMIZE follows */
1665 
1666 /*---------------------------------------------------------------------------*/
1678 /*---------------------------------------------------------------------------*/
1679 static int
1680 muse_moffat_2d_function(const double xy[], const double p[], double *f)
1681 {
1682  double xterm = (xy[0] - p[2]) / p[4], /* xy[0] is x */
1683  yterm = (xy[1] - p[3]) / p[5], /* xy[1] is y */
1684  crossterm = 2 * p[7] * xterm * yterm,
1685  rhoterm = 1. - p[7]*p[7],
1686  base = 1. + (xterm*xterm + crossterm + yterm*yterm) / rhoterm;
1687  *f = p[0] + p[1] * (p[6] - 1.) / (CPL_MATH_PI * p[4]*p[5] * sqrt(rhoterm))
1688  * pow(base, -p[6]);
1689 //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);
1690 //fflush(stdout);
1691  return 0;
1692 } /* muse_moffat_2d_function() */
1693 
1694 /*---------------------------------------------------------------------------*/
1706 /*---------------------------------------------------------------------------*/
1707 static int
1708 muse_moffat_2d_derivative(const double xy[], const double p[], double f[])
1709 {
1710  double xterm = (xy[0] - p[2]) / p[4], /* xy[0] is x */
1711  yterm = (xy[1] - p[3]) / p[5], /* xy[1] is y */
1712  crossterm = 2 * p[7] * xterm * yterm,
1713  rhoterm = 1. - p[7]*p[7],
1714  base = 1. + (xterm*xterm + crossterm + yterm*yterm) / (rhoterm);
1715  f[0] = 1; /* dM(x,y)/dB */
1716  f[1] = (p[6] - 1.) / (CPL_MATH_PI * p[4]*p[5] * sqrt(rhoterm))/* dM(x,y)/dA */
1717  * pow(base, -p[6]);
1718  f[2] = 2 * p[1] * p[6]*(p[6] - 1.) /* dM(x,y)/dxc */
1719  / (CPL_MATH_PI * p[4]*p[4] * p[5] * pow(rhoterm, 3./2.))
1720  * (xterm + p[7] * yterm) * pow(base, -p[6]-1.);
1721  f[3] = 2 * p[1] * p[6]*(p[6] - 1.) /* dM(x,y)/dyc */
1722  / (CPL_MATH_PI * p[4] * p[5]*p[5] * pow(rhoterm, 3./2.))
1723  * (yterm + p[7] * xterm) * pow(base, -p[6]-1.);
1724  f[4] = p[1] * (p[6] - 1.) /* dM(x,y)/dalphax */
1725  / (CPL_MATH_PI * p[4]*p[4] * p[5] * sqrt(rhoterm))
1726  * (-pow(base, -p[6]) + 2 * p[6] / (rhoterm) * pow(base, -p[6]-1.)
1727  * (xterm*xterm + 0.5*crossterm));
1728  f[5] = p[1] * (p[6] - 1.) /* dM(x,y)/dalphay */
1729  / (CPL_MATH_PI * p[4] * p[5]*p[5] * sqrt(rhoterm))
1730  * (-pow(base, -p[6]) + 2 * p[6] / (rhoterm) * pow(base, -p[6]-1.)
1731  * (yterm*yterm + 0.5*crossterm));
1732  f[6] = p[1] / (CPL_MATH_PI * p[4]*p[5] * sqrt(rhoterm)) /* dM(x,y)/dbeta */
1733  * pow(base, -p[6]) * (1. + (p[6] - 1.) * log(base));
1734  f[7] = p[1] * (p[6] - 1.) /* dM(x,y)/drho */
1735  / (CPL_MATH_PI * p[4]*p[5] * pow(rhoterm, 3./2.))
1736  * (p[7] * pow(base, -p[6])
1737  - 2. * p[6] * pow(base, -p[6]-1.)
1738  * (xterm*yterm * (1 + 2*p[7]*p[7] / rhoterm)
1739  + p[7] / rhoterm * (xterm*xterm + yterm*yterm) ));
1740 //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]);
1741 //fflush(stdout);
1742  return 0;
1743 } /* muse_moffat_2d_derivative() */
1744 
1745 #endif
1746 
1747 /*----------------------------------------------------------------------------*/
1820 /*----------------------------------------------------------------------------*/
1821 cpl_error_code
1822 muse_utils_fit_moffat_2d(const cpl_matrix *aPositions,
1823  const cpl_vector *aValues, const cpl_vector *aErrors,
1824  cpl_array *aParams, cpl_array *aPErrors,
1825  const cpl_array *aPFlags,
1826  double *aRMS, double *aRedChisq)
1827 {
1828  /* lots of error checking, almost exactly taken from CPL */
1829  if (!aPositions) {
1830  return cpl_error_set_message(__func__, CPL_ERROR_NULL_INPUT,
1831  "Missing input positions.");
1832  }
1833  if (!aValues) {
1834  return cpl_error_set_message(__func__, CPL_ERROR_NULL_INPUT,
1835  "Missing input values / errors.");
1836  }
1837  if (cpl_matrix_get_ncol(aPositions) != 2) {
1838  return cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
1839  "Input positions are not for two-dimensional data.");
1840  }
1841  if (cpl_vector_get_size(aValues) != cpl_matrix_get_nrow(aPositions)) {
1842  return cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
1843  "Input positions and values data must have same size.");
1844  }
1845  if (aErrors && (cpl_vector_get_size(aValues) != cpl_vector_get_size(aErrors))) {
1846  return cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
1847  "Input vectors must have same size.");
1848  }
1849  if (!aParams) {
1850  return cpl_error_set_message(__func__, CPL_ERROR_NULL_INPUT,
1851  "Missing input parameters array.");
1852  }
1853  if (cpl_array_get_type(aParams) != CPL_TYPE_DOUBLE) {
1854  return cpl_error_set_message(__func__, CPL_ERROR_INVALID_TYPE,
1855  "Parameters array should be CPL_TYPE_DOUBLE.");
1856  }
1857  if (aPErrors && (cpl_array_get_type(aPErrors) != CPL_TYPE_DOUBLE)) {
1858  return cpl_error_set_message(__func__, CPL_ERROR_INVALID_TYPE,
1859  "Parameters error array should be CPL_TYPE_DOUBLE.");
1860  }
1861  if (aPFlags && (cpl_array_get_type(aPFlags) != CPL_TYPE_INT)) {
1862  return cpl_error_set_message(__func__, CPL_ERROR_INVALID_TYPE,
1863  "Parameters error array should be CPL_TYPE_INT.");
1864  }
1865  if ((aPErrors || aRedChisq) && !aErrors) {
1866  return cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
1867  "Missing input parameters errors.");
1868  }
1869  int npoints = cpl_matrix_get_nrow(aPositions);
1870  if (npoints < 8) {
1871  /* Too few positions for 8 free parameters! */
1872  return cpl_error_set_message(__func__, CPL_ERROR_SINGULAR_MATRIX,
1873  "%d are not enough points to fit a Moffat profile.",
1874  npoints);
1875  }
1876 
1877  int pflags[8] = { 1, 1, 1, 1, 1, 1, 1, 1 };
1878  /* Ensure that frozen parameters have a value (first-guess) */
1879  if (aPFlags) {
1880  int idx, nparam = 0;
1881  for (idx = 0; idx < 8; idx++) {
1882  int err, flag = cpl_array_get_int(aPFlags, idx, &err);
1883  if (err || flag) {
1884  continue;
1885  }
1886  pflags[idx] = 0; /* Flag it as frozen */
1887  nparam++;
1888  cpl_array_get_double(aParams, idx, &err);
1889  if (err) {
1890  return cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_INPUT,
1891  "Missing frozen value for parameter %d.", idx);
1892  }
1893  } /* for idx (all parameters) */
1894  /* Ensure that not all parameters are frozen */
1895  if (nparam == 8) {
1896  return cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_INPUT,
1897  "No free parameters");
1898  }
1899  } /* if aPFlags */
1900 
1901  /* Determine first-guess for gaussian parameters. Check if *
1902  * provided by caller - if not build own guesses... *
1903  * *
1904  * 0) Background level: if not given taken as median value within *
1905  * fitting domain. It can be negative... */
1906  int invalid;
1907  double bg = cpl_array_get_double(aParams, 0, &invalid);
1908  if (invalid) {
1909  bg = cpl_vector_get_median_const(aValues);
1910  }
1911 
1912  /* 1) Volume is really set later-on. Here is just a quick estimate, to know *
1913  * whether there is a peak or a hole. If it is flat, leave quickly... */
1914  double volguess = (cpl_vector_get_mean(aValues) - bg) * npoints;
1915  if (fabs(volguess) < FLT_EPSILON) {
1916  /* Data are flat: return a flat Moffat, with undefined center and widths */
1917  cpl_array_set_double(aParams, 0, bg);
1918  cpl_array_set_double(aParams, 1, 0.);
1919  cpl_array_set_invalid(aParams, 2);
1920  cpl_array_set_invalid(aParams, 3);
1921  cpl_array_set_invalid(aParams, 4);
1922  cpl_array_set_invalid(aParams, 5);
1923  cpl_array_set_double(aParams, 6, 1.); /* beta = 1 --> Moffat = 0 */
1924 #if 0
1925  printf("flat Moffat:\n");
1926  cpl_array_dump(aParams, 0, 10, stdout);
1927  fflush(stdout);
1928 #endif
1929  return CPL_ERROR_NONE;
1930  }
1931 
1932  /* 2), 3) Position of center. Compute it as the centroid if not given. *
1933  * This likely does not waste time, as this center is also used *
1934  * to determine the alpha parameters below. */
1935  double xcen, ycen;
1936  muse_utils_get_centroid(aPositions, aValues, aErrors, &xcen, &ycen,
1938  double xc = cpl_array_get_double(aParams, 2, &invalid);
1939  if (invalid) {
1940  xc = xcen;
1941  }
1942  double yc = cpl_array_get_double(aParams, 3, &invalid);
1943  if (invalid) {
1944  yc = ycen;
1945  }
1946 
1947  /* 6) Beta, seeing related Moffat parameter: if not given by the user, *
1948  * it is set to 2.5, which seems to be typical for stellar images. *
1949  * There does not seem to be a simple way to estimate it from the *
1950  * data, independently of the alphax,alphay parameters. */
1951  double beta = cpl_array_get_double(aParams, 6, &invalid);
1952  if (invalid) {
1953  beta = 2.5;
1954  }
1955 
1956  /* 4), 5) Widths: if neither alphax nor alphay are given by the caller the *
1957  * estimate for both is the radius of an approximate half-peak point *
1958  * from the peak point. Very rough, but accuracy is not an issue at *
1959  * this stage, just need a rough starting value. If only one width *
1960  * is given by the caller, the other one is set to the same value. */
1961  double alphay, alphax = cpl_array_get_double(aParams, 4, &invalid);
1962  if (invalid) {
1963  alphay = cpl_array_get_double(aParams, 5, &invalid);
1964  if (invalid) {
1965  double amplitude = 0.;
1966  if (volguess > 0.) {
1967  amplitude = cpl_vector_get_max(aValues) - bg;
1968  } else {
1969  amplitude = cpl_vector_get_min(aValues) - bg;
1970  }
1971  double halfpeak = amplitude / 2. + bg,
1972  limit = amplitude * 0.01; /* 1% accuracy for the start */
1973  const double *values = cpl_vector_get_data_const(aValues);
1974  cpl_vector *vradius = cpl_vector_new(1); /* store radius values */
1975  int i, nfound;
1976  do {
1977  nfound = 0;
1978  for (i = 0; i < npoints; i++) {
1979  if (values[i] > halfpeak - limit && values[i] < halfpeak + limit) {
1980  cpl_vector_set_size(vradius, nfound + 1);
1981  double radius = sqrt(pow(cpl_matrix_get(aPositions, i, 0) - xcen, 2)
1982  + pow(cpl_matrix_get(aPositions, i, 1) - ycen, 2));
1983 #if 0
1984  printf("radius(%d, %d) = %f\n", i, nfound+1, radius);
1985 #endif
1986  cpl_vector_set(vradius, nfound++, radius);
1987  }
1988  } /* for i (all values) */
1989  /* if we go through this loop again, we will find more points and *
1990  * so all previous vector entries will be completely overwritten */
1991  limit *= 2; /* be twice as tolerant to find the points next time */
1992 #if 0
1993  printf("found %d points (limit = %f)\n", nfound, limit / 2);
1994 #endif
1995  } while (nfound < 3 && isfinite(limit));
1996  if (!isfinite(limit)) {
1997  /* if the data is so weird that we don't find points within *
1998  * finite limit, then any alpha is a good first guess... */
1999  alphax = alphay = 1.;
2000  } else {
2001  /* the radius of found points from 1st-guess center is the *
2002  * HWHM, the alpha parameter of a Moffat function is then *
2003  * alpha = HWHM / sqrt(2^(1/beta) - 1) */
2004  alphax = alphay = cpl_vector_get_mean(vradius) / sqrt(pow(2., 1./beta)-1);
2005 #if 0
2006  printf("estimated alphas = %f from radius %f\n", alphax, cpl_vector_get_mean(vradius));
2007  fflush(stdout);
2008 #endif
2009  }
2010  cpl_vector_delete(vradius);
2011  } else {
2012  alphax = alphay;
2013  }
2014  } else {
2015  alphay = cpl_array_get_double(aParams, 5, &invalid);
2016  if (invalid) {
2017  alphay = alphax;
2018  }
2019  }
2020 
2021  /* 1) Volume. If not given by the user, it is derived *
2022  * from the max (min) value of the data distribution. */
2023  double volume = cpl_array_get_double(aParams, 1, &invalid);
2024  if (invalid) {
2025  /* The above seems to be a good enough first-guess. *
2026  * Deriving a better guess from the amplitude would *
2027  * require more solid guesses of the other parameters. */
2028  volume = volguess;
2029  }
2030 
2031  /* 7) Rho, x-y correlation parameter: if not given by the user, *
2032  * it is set to zero (no correlation, i.e. circular Moffat or *
2033  * one that is elongated along x or y). */
2034  double rho = cpl_array_get_double(aParams, 7, &invalid);
2035  if (invalid) {
2036  rho = 0.;
2037  }
2038 
2039  /* Yay! Now all parameters are set to initial values, and we can do the fit! */
2040  cpl_vector *params = cpl_vector_new(8);
2041  cpl_vector_set(params, 0, bg);
2042  cpl_vector_set(params, 1, volume);
2043  cpl_vector_set(params, 2, xc);
2044  cpl_vector_set(params, 3, yc);
2045  cpl_vector_set(params, 4, alphax);
2046  cpl_vector_set(params, 5, alphay);
2047  cpl_vector_set(params, 6, beta);
2048  cpl_vector_set(params, 7, rho);
2049 #if 0
2050  printf("initial guess values for Moffat (vol %e):\n", (cpl_vector_get_mean(aValues) - bg) * npoints);
2051  cpl_vector_dump(params, stdout);
2052  fflush(stdout);
2053 #endif
2054  cpl_matrix *covariance = NULL;
2055 
2056  cpl_error_code rc = CPL_ERROR_NONE;
2057 #if MOFFAT_USE_MUSE_OPTIMIZE
2058  fitdata_t fitdata;
2059  fitdata.positions = aPositions;
2060  fitdata.values = aValues;
2061  fitdata.errors = aErrors;
2062  cpl_array *optparams = cpl_array_wrap_double(cpl_vector_get_data(params), 8);
2063  rc = muse_cpl_optimize_lvmq(&fitdata, optparams, npoints,
2064  muse_moffat_2d_optfunc, NULL);
2065  cpl_array_unwrap(optparams);
2066 #else /* MOFFAT_USE_MUSE_OPTIMIZE follows */
2067  cpl_errorstate prestate = cpl_errorstate_get();
2068  rc = cpl_fit_lvmq(aPositions, NULL, aValues, aErrors, params, pflags,
2069  muse_moffat_2d_function, muse_moffat_2d_derivative,
2070  CPL_FIT_LVMQ_TOLERANCE, CPL_FIT_LVMQ_COUNT,
2071  CPL_FIT_LVMQ_MAXITER, aRMS,
2072  aErrors ? aRedChisq : NULL, aErrors ? &covariance : NULL);
2073 #endif
2074  if (aRMS) {
2075  *aRMS = sqrt(*aRMS);
2076  }
2077 
2078 #if 0
2079  printf("Moffat fit (rc=%d, %s):\n", rc, cpl_error_get_message());
2080  cpl_vector_dump(params, stdout);
2081  fflush(stdout);
2082 #endif
2083  if (rc == CPL_ERROR_NONE || rc == CPL_ERROR_SINGULAR_MATRIX ||
2084  rc == CPL_ERROR_CONTINUE) {
2085  /* The LM algorithm converged. The computation of the covariance *
2086  * matrix might have failed. All the above errors must be ignored *
2087  * because of ticket DFS06126. */
2088 
2089  /* check whether the result makes sense at all... (unlike CPL we use C99) */
2090  if (isfinite(cpl_vector_get(params, 0)) &&
2091  isfinite(cpl_vector_get(params, 1)) &&
2092  isfinite(cpl_vector_get(params, 2)) &&
2093  isfinite(cpl_vector_get(params, 3)) &&
2094  isfinite(cpl_vector_get(params, 4)) &&
2095  isfinite(cpl_vector_get(params, 5)) &&
2096  isfinite(cpl_vector_get(params, 6)) &&
2097  isfinite(cpl_vector_get(params, 7))) {
2098  /* Betting all errors are really to be ignored... */
2099  rc = CPL_ERROR_NONE;
2100  cpl_errorstate_set(prestate);
2101 
2102  /* Save best fit parameters: image coordinates, evaluations *
2103  * of widths are forced positive (they might be both negative *
2104  * -- it would generate the same profile). */
2105  cpl_array_set_double(aParams, 0, cpl_vector_get(params, 0));
2106  cpl_array_set_double(aParams, 1, cpl_vector_get(params, 1));
2107  cpl_array_set_double(aParams, 2, cpl_vector_get(params, 2));
2108  cpl_array_set_double(aParams, 3, cpl_vector_get(params, 3));
2109  cpl_array_set_double(aParams, 4, fabs(cpl_vector_get(params, 4)));
2110  cpl_array_set_double(aParams, 5, fabs(cpl_vector_get(params, 5)));
2111  cpl_array_set_double(aParams, 6, cpl_vector_get(params, 6));
2112  cpl_array_set_double(aParams, 7, cpl_vector_get(params, 7));
2113 
2114  if (aErrors && covariance) {
2115  int idx;
2116  for (idx = 0; idx < 8; idx++) {
2117  if (pflags[idx] && aPErrors) {
2118  cpl_array_set_double(aPErrors, idx,
2119  sqrt(cpl_matrix_get(covariance, idx, idx)));
2120  } /* if */
2121  } /* for idx (all parameters) */
2122  } /* if */
2123 
2124  /* we don't care about the physical parameters like CPL does, so this was it :-) */
2125  } /* if isfinite() */
2126  } /* if rc */
2127  cpl_matrix_delete(covariance);
2128  cpl_vector_delete(params);
2129 
2130  return rc;
2131 } /* muse_utils_fit_moffat_2d() */
2132 
2133 /*----------------------------------------------------------------------------*/
2176 /*----------------------------------------------------------------------------*/
2177 cpl_polynomial *
2178 muse_utils_iterate_fit_polynomial(cpl_matrix *aPos, cpl_vector *aVal,
2179  cpl_vector *aErr, cpl_table *aExtra,
2180  const unsigned int aOrder, const double aRSigma,
2181  double *aMSE, double *aChiSq)
2182 {
2183  /* pre-fill diagnistics to high values in case of errors */
2184  if (aMSE) {
2185  *aMSE = DBL_MAX;
2186  }
2187  if (aChiSq) {
2188  *aChiSq = DBL_MAX;
2189  }
2190  cpl_ensure(aPos && aVal, CPL_ERROR_NULL_INPUT, NULL);
2191  cpl_ensure(cpl_matrix_get_ncol(aPos) == cpl_vector_get_size(aVal),
2192  CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
2193  if (aErr) {
2194  cpl_ensure(cpl_vector_get_size(aVal) == cpl_vector_get_size(aErr),
2195  CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
2196  }
2197  if (aExtra) {
2198  cpl_ensure(cpl_vector_get_size(aVal) == cpl_table_get_nrow(aExtra),
2199  CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
2200  }
2201 
2202  /* XXX erase positions with NAN values upfront */
2203  int idx;
2204  for (idx = 0; idx < cpl_vector_get_size(aVal); idx++) {
2205  /* compare this residual value to the RMS value */
2206  if (isfinite(cpl_vector_get(aVal, idx))) {
2207  continue; /* want to keep all finite numbers */
2208  }
2209  /* guard against removing the last element */
2210  if (cpl_vector_get_size(aVal) == 1) {
2211  cpl_msg_warning(__func__, "Input vector only contained non-finite elements!");
2212  break;
2213  }
2214  /* remove bad element from the fit structures... */
2215  cpl_matrix_erase_columns(aPos, idx, 1);
2216  muse_cplvector_erase_element(aVal, idx);
2217  if (aErr) { /* check to not generate CPL error */
2218  muse_cplvector_erase_element(aErr, idx);
2219  }
2220  /* ...and from the input matrix, if it's there */
2221  if (aExtra) {
2222  cpl_table_erase_window(aExtra, idx, 1);
2223  }
2224  idx--; /* we stay at this position to see what moved here */
2225  } /* for idx */
2226 
2227  /* create the polynomial, using matrix rows to determine dimensions */
2228  int ndim = cpl_matrix_get_nrow(aPos);
2229  cpl_polynomial *fit = cpl_polynomial_new(ndim);
2230  int large_residuals = 1; /* init to force a first fit */
2231  while (large_residuals > 0) {
2232  const cpl_boolean sym = CPL_FALSE;
2233  cpl_size *mindeg = cpl_calloc(ndim, sizeof(cpl_size)),
2234  *maxdeg = cpl_malloc(ndim * sizeof(cpl_size));
2235  int i;
2236  for (i = 0; i < ndim; i++) {
2237  maxdeg[i] = aOrder;
2238  }
2239  cpl_error_code rc = cpl_polynomial_fit(fit, aPos, &sym, aVal, NULL,
2240  CPL_FALSE, mindeg, maxdeg);
2241  cpl_free(mindeg);
2242  cpl_free(maxdeg);
2243  const cpl_size coeff = 0;
2244  if (rc != CPL_ERROR_NONE || !isfinite(cpl_polynomial_get_coeff(fit, &coeff))) {
2245  /* don't try to recover from an error, instead clean up and return NULL */
2246  cpl_errorstate prestate = cpl_errorstate_get();
2247 #if 0
2248  printf("%s: output polynomial:\n", __func__);
2249  cpl_polynomial_dump(fit, stdout);
2250  printf("%s: positions and values that we tried to fit:\n", __func__);
2251  cpl_matrix_dump(aPos, stdout);
2252  cpl_vector_dump(aVal, stdout);
2253  fflush(stdout);
2254 #endif
2255  cpl_polynomial_delete(fit);
2256  /* make sure to expose the important error to the caller */
2257  if (!cpl_errorstate_is_equal(prestate)) {
2258  cpl_errorstate_set(prestate);
2259  }
2260  return NULL;
2261  }
2262 
2263  /* compute residuals and mean squared error */
2264  cpl_vector *res = cpl_vector_new(cpl_vector_get_size(aVal));
2265  cpl_vector_fill_polynomial_fit_residual(res, aVal, NULL, fit, aPos, aChiSq);
2266  double rms = sqrt(cpl_vector_product(res, res) / cpl_vector_get_size(res));
2267  if (rms == 0.) { /* otherwise everything will be rejected! */
2268  rms = DBL_MIN;
2269  }
2270 
2271 #if 0
2272  printf("%s: polynomial fit (RMS %g chisq %g aRSigma %f -> limit %g):\n",
2273  __func__, rms, aChiSq ? *aChiSq : 0., aRSigma, aRSigma * rms);
2274  cpl_polynomial_dump(fit, stdout);
2275  fflush(stdout);
2276  char *title = cpl_sprintf("set title \"%s: RMS %g\"\n"
2277  "unset key\n", __func__, rms);
2278  cpl_plot_vector(title, "", "", res);
2279  cpl_free(title);
2280 #endif
2281 
2282  large_residuals = 0;
2283  for (i = 0; i < cpl_vector_get_size(res); i++) {
2284  /* compare this residual value to the RMS value */
2285  if (fabs(cpl_vector_get(res, i)) < (aRSigma * rms)) {
2286  /* good fit at this point */
2287  continue;
2288  }
2289 
2290  /* bad residual, remove element, from residuals vector and the data vectors */
2291 #if 0
2292  cpl_msg_debug(__func__, "residual %f RMS %f aRSigma %f -> limit %f",
2293  cpl_vector_get(res, i), rms, aRSigma, aRSigma * rms);
2294 #endif
2295  /* guard against removing the last element */
2296  if (cpl_vector_get_size(res) == 1) {
2297  cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_OUTPUT, "tried to "
2298  "remove the last vector/matrix element when "
2299  "checking fit residuals (residual %g RMS %g "
2300  "aRSigma %f -> limit %g)", cpl_vector_get(res, i),
2301  rms, aRSigma, aRSigma * rms);
2302  cpl_polynomial_delete(fit);
2303  fit = NULL;
2304  rms = sqrt(DBL_MAX); /* so that aMSE gets to be DBL_MAX below */
2305  if (aChiSq) {
2306  *aChiSq = DBL_MAX;
2307  }
2308  large_residuals = 0; /* don't try to fit again */
2309  break;
2310  }
2311  /* remove bad element from the fit structures... */
2313  cpl_matrix_erase_columns(aPos, i, 1);
2315  if (aErr) { /* check to not generate CPL error */
2317  }
2318  /* ...and from the input matrix, if it's there */
2319  if (aExtra) {
2320  cpl_table_erase_window(aExtra, i, 1);
2321  }
2322  large_residuals++;
2323  i--; /* we stay at this position to see what moved here */
2324  } /* for i */
2325  cpl_vector_delete(res);
2326  if (aMSE) {
2327  *aMSE = rms * rms;
2328  }
2329  } /* while large_residuals */
2330 
2331  return fit;
2332 } /* muse_utils_iterate_fit_polynomial() */
2333 
2334 /*----------------------------------------------------------------------------*/
2380 /*----------------------------------------------------------------------------*/
2381 double
2383  double aLambda, double aHalfWidth,
2384  double aBinSize,
2385  cpl_array *aResults, cpl_array *aErrors)
2386 {
2387  cpl_ensure(aPixtable, CPL_ERROR_NULL_INPUT, 0.);
2388 
2389  /* select (only) the relevant part of the pixel table */
2390  cpl_table_unselect_all(aPixtable->table);
2391  cpl_table_or_selected_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA,
2392  CPL_NOT_LESS_THAN, aLambda - aHalfWidth);
2393  cpl_table_and_selected_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA,
2394  CPL_NOT_GREATER_THAN, aLambda + aHalfWidth);
2395  cpl_size nsel = cpl_table_count_selected(aPixtable->table);
2396  cpl_ensure(nsel > 0, CPL_ERROR_DATA_NOT_FOUND, 0.);
2397 
2398  /* now resample all the selected pixels into a spectrum *
2399  * with hopefully high resolution given by aBinSize */
2400  cpl_errorstate state = cpl_errorstate_get();
2401  cpl_table *spec = muse_resampling_spectrum(aPixtable, aBinSize);
2402  cpl_table_unselect_all(aPixtable->table);
2403  if (!cpl_errorstate_is_equal(state)) {
2404  /* something went wrong with the creation of the spectrum */
2405  cpl_table_delete(spec);
2406  cpl_error_set(__func__, cpl_error_get_code());
2407  return 0.;
2408  }
2409 
2410  /* check that the spectrum is contiguous */
2411  cpl_size nbins = cpl_table_get_nrow(spec);
2412  /* turn the stat column (in sigma^2) into an error column (in sigma) */
2413  cpl_table_power_column(spec, "stat", 0.5); /* sqrt() */
2414  cpl_table_name_column(spec, "stat", "error");
2415  cpl_table_set_column_unit(spec, "error",
2416  cpl_table_get_column_unit(spec, "data"));
2417 #if 0
2418  cpl_table_save(spec, NULL, NULL, "spec.fits", CPL_IO_CREATE);
2419 #endif
2420  /* wrap table columns into vectors for the Gaussian fit */
2421  cpl_vector *pos = cpl_vector_wrap(nbins, cpl_table_get_data_double(spec, "lambda")),
2422  *val = cpl_vector_wrap(nbins, cpl_table_get_data_double(spec, "data")),
2423  *err = cpl_vector_wrap(nbins, cpl_table_get_data_double(spec, "error"));
2424  double xc, xerr = 2 * aHalfWidth, /* default error as large as the window */
2425  sigma, area, bglevel, mse;
2426  cpl_matrix *covariance;
2427  cpl_error_code rc = cpl_vector_fit_gaussian(pos, NULL, val, err, CPL_FIT_ALL,
2428  &xc, &sigma, &area, &bglevel,
2429  &mse, NULL, &covariance);
2430  cpl_vector_unwrap(pos);
2431  cpl_vector_unwrap(val);
2432  cpl_vector_unwrap(err);
2433  cpl_table_delete(spec);
2434  if (rc == CPL_ERROR_CONTINUE) { /* fit didn't converge */
2435  /* estimate position error as sigma^2/area as CPL docs suggest */
2436  xerr = sqrt(sigma * sigma / area);
2437  } else if (rc == CPL_ERROR_SINGULAR_MATRIX || !covariance) {
2438  xerr = sqrt(sigma * sigma / area);
2439  } else {
2440  xerr = sqrt(cpl_matrix_get(covariance, 0, 0));
2441 #if 0
2442  cpl_msg_debug(__func__, "covariance matrix:");
2443  cpl_matrix_dump(covariance, stdout);
2444  fflush(stdout);
2445 #endif
2446  }
2447  if (aResults && cpl_array_get_type(aResults) == CPL_TYPE_DOUBLE) {
2448  cpl_array_set_size(aResults, 4);
2449  cpl_array_set_double(aResults, 0, xc);
2450  cpl_array_set_double(aResults, 1, sigma);
2451  cpl_array_set_double(aResults, 2, area);
2452  cpl_array_set_double(aResults, 3, bglevel);
2453  } /* valid aResults array */
2454  if (aErrors && cpl_array_get_type(aErrors) == CPL_TYPE_DOUBLE) {
2455  cpl_array_set_size(aErrors, 4);
2456  cpl_array_set_double(aErrors, 0, xerr);
2457  if (rc != CPL_ERROR_NONE || !covariance) {
2458  cpl_array_fill_window_invalid(aErrors, 1, 3);
2459  } else {
2460  cpl_array_set_double(aErrors, 1, sqrt(cpl_matrix_get(covariance, 1, 1)));
2461  cpl_array_set_double(aErrors, 2, sqrt(cpl_matrix_get(covariance, 2, 2)));
2462  cpl_array_set_double(aErrors, 3, sqrt(cpl_matrix_get(covariance, 3, 3)));
2463  } /* else */
2464  } /* valid aErrors array */
2465  cpl_matrix_delete(covariance);
2466  cpl_msg_debug(__func__, "Gaussian fit (%s): %f +/- %f Angstrom, %f, %f, %f "
2467  "(RMS %f)", rc != CPL_ERROR_NONE ? cpl_error_get_message() : "",
2468  xc, xerr, bglevel, area, sigma, sqrt(mse));
2469  return xc;
2470 } /* muse_utils_pixtable_fit_line_gaussian() */
2471 
2472 /*----------------------------------------------------------------------------*/
2487 /*----------------------------------------------------------------------------*/
2488 cpl_error_code
2489 muse_utils_copy_modified_header(cpl_propertylist *aH1, cpl_propertylist *aH2,
2490  const char *aKeyword, const char *aString)
2491 {
2492  cpl_ensure_code(aH1 && aH2 && aKeyword && aString, CPL_ERROR_NULL_INPUT);
2493  const char *hin = cpl_propertylist_get_string(aH1, aKeyword);
2494  cpl_ensure_code(hin, CPL_ERROR_ILLEGAL_INPUT);
2495  char *hstring = cpl_sprintf("%s (%s)", hin, aString);
2496  cpl_error_code rc = cpl_propertylist_update_string(aH2, aKeyword, hstring);
2497  cpl_free(hstring);
2498 
2499  return rc;
2500 } /* muse_utils_copy_modified_header() */
2501 
2502 /*---------------------------------------------------------------------------*/
2528 /*---------------------------------------------------------------------------*/
2529 cpl_error_code
2530 muse_utils_set_hduclass(cpl_propertylist *aHeader, const char *aClass2,
2531  const char *aExtData, const char *aExtDQ,
2532  const char *aExtStat)
2533 {
2534  cpl_ensure_code(aHeader && aClass2 && aExtData, CPL_ERROR_NULL_INPUT);
2535  cpl_ensure_code(!strcmp(aClass2, "DATA") || !strcmp(aClass2, "ERROR") ||
2536  !strcmp(aClass2, "QUALITY"), CPL_ERROR_ILLEGAL_INPUT);
2537 
2538  /* first clean up existing entries */
2539 #define ESO_HDU_HEADERS_REGEXP "HDU(CLASS|CLAS1|CLAS2|CLAS3|DOC|VERS)$" \
2540  "|^SCIDATA$|^QUAL(DATA|MASK)$|^ERRDATA$"
2541  cpl_propertylist_erase_regexp(aHeader, ESO_HDU_HEADERS_REGEXP, 0);
2542 
2543  if (cpl_propertylist_has(aHeader, "EXTNAME")) {
2544  cpl_propertylist_insert_after_string(aHeader, "EXTNAME", "HDUCLASS", "ESO");
2545  } else {
2546  cpl_propertylist_append_string(aHeader, "HDUCLASS", "ESO");
2547  }
2548  cpl_propertylist_set_comment(aHeader, "HDUCLASS", "class name (ESO format)");
2549  cpl_propertylist_insert_after_string(aHeader, "HDUCLASS", "HDUDOC", "DICD");
2550  cpl_propertylist_set_comment(aHeader, "HDUDOC", "document with class description");
2551  cpl_propertylist_insert_after_string(aHeader, "HDUDOC", "HDUVERS",
2552  "DICD version 6");
2553  cpl_propertylist_set_comment(aHeader, "HDUVERS",
2554  "version number (according to spec v2.5.1)");
2555  cpl_propertylist_insert_after_string(aHeader, "HDUVERS", "HDUCLAS1", "IMAGE");
2556  cpl_propertylist_set_comment(aHeader, "HDUCLAS1", "Image data format");
2557  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS1", "HDUCLAS2", aClass2);
2558  if (!strcmp(aClass2, "DATA")) { /* this is the STAT / variance extension */
2559  cpl_propertylist_set_comment(aHeader, "HDUCLAS2",
2560  "this extension contains the data itself");
2561  /* no HDUCLAS3 for the data itself */
2562  if (aExtDQ) {
2563  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS2", "QUALDATA", aExtDQ);
2564  }
2565  if (aExtStat) { /* do this second, so that it goes first, if given */
2566  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS2", "ERRDATA", aExtStat);
2567  }
2568  } else if (!strcmp(aClass2, "ERROR")) { /* this is the STAT / variance extension */
2569  cpl_propertylist_set_comment(aHeader, "HDUCLAS2", "this extension contains variance");
2570  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS2", "HDUCLAS3", "MSE");
2571  cpl_propertylist_set_comment(aHeader, "HDUCLAS3",
2572  "the extension contains variances (sigma**2)");
2573  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS3", "SCIDATA", aExtData);
2574  if (aExtDQ) {
2575  cpl_propertylist_insert_after_string(aHeader, "SCIDATA", "QUALDATA", aExtDQ);
2576  }
2577  } else { /* "QUALITY", this is the DQ / bad pixel extension */
2578  cpl_propertylist_set_comment(aHeader, "HDUCLAS2",
2579  "this extension contains bad pixel mask");
2580  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS2", "HDUCLAS3", "FLAG32BIT");
2581  cpl_propertylist_set_comment(aHeader, "HDUCLAS3", "extension contains 32bit"
2582  " Euro3D bad pixel information");
2583  /* all non-zero values in the bad pixel mask are "bad" */
2584  cpl_propertylist_insert_after_long(aHeader, "HDUCLAS3", "QUALMASK", 0xFFFFFFFF);
2585  cpl_propertylist_set_comment(aHeader, "QUALMASK", "all non-zero values are bad");
2586  cpl_propertylist_insert_after_string(aHeader, "QUALMASK", "SCIDATA", aExtData);
2587  if (aExtStat) {
2588  cpl_propertylist_insert_after_string(aHeader, "SCIDATA", "ERRDATA", aExtStat);
2589  }
2590  }
2591 
2592  if (cpl_propertylist_has(aHeader, "SCIDATA")) {
2593  cpl_propertylist_set_comment(aHeader, "SCIDATA", "pointer to the data extension");
2594  }
2595  if (cpl_propertylist_has(aHeader, "ERRDATA")) {
2596  cpl_propertylist_set_comment(aHeader, "ERRDATA", "pointer to the variance extension");
2597  }
2598  if (cpl_propertylist_has(aHeader, "QUALDATA")) {
2599  cpl_propertylist_set_comment(aHeader, "QUALDATA",
2600  "pointer to the bad pixel mask extension");
2601  }
2602 
2603  return CPL_ERROR_NONE;
2604 } /* muse_utils_set_hduclass() */
2605 
2606 /*----------------------------------------------------------------------------*/
2619 /*----------------------------------------------------------------------------*/
2620 void
2621 muse_utils_memory_dump(const char *aMarker)
2622 {
2623  char *exe = getenv("MUSE_DEBUG_MEMORY_PROGRAM");
2624  if (!exe) {
2625  return;
2626  }
2627 
2628  printf("=== %s ===\n", aMarker);
2629  fflush(stdout);
2630  char command[1000];
2631  if (strlen(exe) > 0) {
2632  snprintf(command, 999,
2633  "ps -C %s -o comm,start_time,pid,tid,pcpu,stat,rss,size,vsize",
2634  exe);
2635  } else {
2636  /* no program name given, generic output */
2637  snprintf(command, 999,
2638  "ps -o comm,start_time,pid,tid,pcpu,stat,rss,size,vsize");
2639  }
2640  cpl_memory_dump();
2641  fflush(stderr);
2642  int rc = system(command);
2643  UNUSED_ARGUMENT(rc); /* new glibc mandates use of system() return code */
2644 } /* muse_utils_memory_dump() */
2645 
const muse_cpltable_def muse_filtertable_def[]
MUSE filter table definition.
Definition: muse_utils.c:822
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:164
const char * muse_pfits_get_extname(const cpl_propertylist *aHeaders)
find out the extension name
Definition: muse_pfits.c:188
int muse_pfits_get_read_id(const cpl_propertylist *aHeaders)
find out the readout mode id
Definition: muse_pfits.c:365
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:119
unsigned char muse_utils_get_ifu(const cpl_propertylist *aHeaders)
Find out the IFU/channel from which this header originated.
Definition: muse_utils.c:99
double muse_utils_pixtable_fit_line_gaussian(muse_pixtable *aPixtable, double aLambda, double aHalfWidth, double aBinSize, cpl_array *aResults, cpl_array *aErrors)
Fit a 1D Gaussian to a given wavelength range in a pixel table.
Definition: muse_utils.c:2382
int muse_pfits_get_shut_status(const cpl_propertylist *aHeaders, int aShutter)
query the status of one shutter
Definition: muse_pfits.c:1311
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.
void muse_utils_memory_dump(const char *aMarker)
Display the current memory usage of the given program.
Definition: muse_utils.c:2621
const char * muse_pfits_get_dateobs(const cpl_propertylist *aHeaders)
find out the date of observations
Definition: muse_pfits.c:329
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:1225
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:2530
cpl_table * muse_table_load_filter(muse_processing *aProcessing, const char *aFilterName)
Load a table for a given filter name.
Definition: muse_utils.c:853
cpl_table * muse_cpltable_new(const muse_cpltable_def *aDef, cpl_size aLength)
Create an empty table according to the specified definition.
const char * muse_get_license(void)
Get the pipeline copyright and license.
Definition: muse_utils.c:84
int muse_pfits_get_biny(const cpl_propertylist *aHeaders)
find out the binning factor in y direction
Definition: muse_pfits.c:419
Structure definition of MUSE pixel table.
const char * muse_pfits_get_lamp_name(const cpl_propertylist *aHeaders, int aLamp)
query the name of one lamp
Definition: muse_pfits.c:1248
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:1822
const char * muse_pfits_get_pipefile(const cpl_propertylist *aHeaders)
find out the pipefile
Definition: muse_pfits.c:71
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:2178
const char * muse_pfits_get_read_name(const cpl_propertylist *aHeaders)
find out the readout mode name
Definition: muse_pfits.c:383
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:399
void muse_processing_append_used(muse_processing *aProcessing, cpl_frame *aFrame, cpl_frame_group aGroup, int aDuplicate)
Add a frame to the set of used frames.
cpl_error_code muse_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:1502
char * muse_utils_header_get_lamp_names(cpl_propertylist *aHeader, char aSep)
Concatenate names of all active calibration lamps.
Definition: muse_utils.c:933
const char * muse_pfits_get_chip_id(const cpl_propertylist *aHeaders)
find out the chip id
Definition: muse_pfits.c:455
int muse_pfits_get_binx(const cpl_propertylist *aHeaders)
find out the binning factor in x direction
Definition: muse_pfits.c:401
cpl_array * muse_utils_header_get_lamp_numbers(cpl_propertylist *aHeader)
List numbers of all active calibration lamps.
Definition: muse_utils.c:1000
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:1091
int muse_pfits_get_lamp_status(const cpl_propertylist *aHeaders, int aLamp)
query the status of one lamp
Definition: muse_pfits.c:1269
cpl_frameset * inframes
cpl_table * muse_table_load(muse_processing *aProcessing, const char *aTag, unsigned char aIFU)
load a table according to its tag and IFU/channel number
Definition: muse_utils.c:721
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:1178
Definition of a cpl table structure.
cpl_propertylist * muse_propertylist_load(muse_processing *aProcessing, const char *aTag)
load a propertylist according to its tag
Definition: muse_utils.c:789
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:1043
cpl_table * muse_resampling_spectrum(muse_pixtable *aPixtable, double aBinwidth)
Resample the selected pixels of a pixel table into a spectrum.
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:274
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:158
const char * muse_pfits_get_chip_name(const cpl_propertylist *aHeaders)
find out the chip name
Definition: muse_pfits.c:437
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:468
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:2489
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:1272
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:243