MUSE Pipeline Reference Manual  0.18.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-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 #include <float.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <strings.h> /* strncasecmp() */
38 #ifdef HAVE_GETTIMEOFDAY
39 #include <sys/time.h> /* gettimeofday */
40 #endif
41 
42 #include "muse_utils.h"
43 #include "muse_cplwrappers.h"
44 #include "muse_instrument.h"
45 
46 #include "muse_pfits.h"
47 #include "muse_resampling.h"
48 #include "muse_tracing.h"
49 
50 /*----------------------------------------------------------------------------*
51  * Debugging Macros *
52  *----------------------------------------------------------------------------*/
53 #define MOFFAT_USE_MUSE_OPTIMIZE 0 /* if non-zero, use the muse_cpl_optimize *
54  * interface to fit the moffat function */
55 #if MOFFAT_USE_MUSE_OPTIMIZE
56 #include "muse_optimize.h"
57 #endif
58 
59 /*----------------------------------------------------------------------------*/
65 /*----------------------------------------------------------------------------*/
66 
69 /*----------------------------------------------------------------------------*/
78 /*----------------------------------------------------------------------------*/
79 const char *
81 {
82  return cpl_get_license(PACKAGE_NAME, "2005, 2014");
83 } /* muse_get_license() */
84 
85 /*----------------------------------------------------------------------------*/
93 /*----------------------------------------------------------------------------*/
94 unsigned char
95 muse_utils_get_ifu(const cpl_propertylist *aHeaders)
96 {
97  unsigned char n;
98  for (n = 1; n <= kMuseNumIFUs; n++) {
99  if (muse_pfits_has_ifu(aHeaders, n)) {
100  return n;
101  } /* if */
102  } /* for n (all IFU numbers) */
103  return 0;
104 } /* muse_utils_get_ifu() */
105 
106 /*---------------------------------------------------------------------------*/
113 /*---------------------------------------------------------------------------*/
114 int
115 muse_utils_get_extension_for_ifu(const char *aFilename, unsigned char aIFU)
116 {
117  cpl_errorstate prestate = cpl_errorstate_get();
118  int i, next = cpl_fits_count_extensions(aFilename);
119  for (i = 0; i <= next; i++) {
120  cpl_propertylist *properties = cpl_propertylist_load(aFilename, i);
121  if (muse_pfits_has_ifu(properties, aIFU)) {
122  cpl_propertylist_delete(properties);
123  return i;
124  }
125  cpl_propertylist_delete(properties);
126  } /* for i (primary header and extensions) */
127  cpl_errorstate_set(prestate);
128  return -1;
129 } /* muse_utils_get_extension_for_ifu() */
130 
131 /*---------------------------------------------------------------------------*/
152 /*---------------------------------------------------------------------------*/
153 cpl_frameset *
154 muse_frameset_find(const cpl_frameset *aFrames, const char *aTag,
155  unsigned char aIFU, cpl_boolean aInvert)
156 {
157  cpl_ensure(aFrames, CPL_ERROR_NULL_INPUT, NULL);
158  cpl_frameset *newFrames = cpl_frameset_new();
159 
160  /* loop through all frames to determine properties */
161  cpl_size iframe, nframes = cpl_frameset_get_size(aFrames);
162  for (iframe = 0; iframe < nframes; iframe++) {
163  const cpl_frame *frame = cpl_frameset_get_position_const(aFrames, iframe);
164  const char *fn = cpl_frame_get_filename(frame),
165  *tag = cpl_frame_get_tag(frame);
166 
167  /* we are happy, if we want matched frames and found them, or we *
168  * want non-matching frames and found one (or one without a tag) */
169  cpl_boolean matched = !aInvert && (!aTag || (aTag && !strcmp(tag, aTag))),
170  unmatched = aInvert
171  && ((aTag && !tag) || (aTag && strcmp(tag, aTag)));
172  if (matched || unmatched) {
173  /* files produced outside the pipeline are generic, *
174  * so find out if this is one */
175  cpl_errorstate prestate = cpl_errorstate_get();
176  int extension = muse_utils_get_extension_for_ifu(fn, aIFU);
177  /* if we haven't found an extension, we should *
178  * still try to load the primary header */
179  if (extension == -1) {
180  extension = 0;
181  cpl_errorstate_set(prestate);
182  }
183  cpl_propertylist *header = cpl_propertylist_load(fn, extension);
184  unsigned char ifu = muse_utils_get_ifu(header);
185  prestate = cpl_errorstate_get();
186  const char *pipefile = muse_pfits_get_pipefile(header);
187  if (!cpl_errorstate_is_equal(prestate)) {
188  /* recover from missing PIPEFILE which does not exist in raw data */
189  cpl_errorstate_set(prestate);
190  }
191  if (ifu == aIFU) {
192  /* a pipeline produced file which contains data from the correct IFU */
193  cpl_frameset_insert(newFrames, cpl_frame_duplicate(frame));
194  } else if (ifu == 0 && !pipefile) {
195  /* not specific to any IFU, this is the case for external *
196  * calibration files, like EXTINCT_TABLE or LINE_CATALOG */
197  cpl_frameset_insert(newFrames, cpl_frame_duplicate(frame));
198  } else if (aIFU == 0) {
199  /* no IFU specified, this happens in the post-processing recipes */
200  cpl_frameset_insert(newFrames, cpl_frame_duplicate(frame));
201  } else if (!strncmp(aTag, "GEOMETRY_TABLE", 15)) {
202  /* since the geometry table is not IFU-specific, it should *
203  * always match, even if a specific IFU is requested */
204  cpl_frameset_insert(newFrames, cpl_frame_duplicate(frame));
205  } else {
206  /* do nothing */
207 #if 0
208  cpl_msg_debug(__func__, "not added %s / %s / %d", fn,
209  cpl_frame_get_tag(frame), ifu);
210 #endif
211  }
212  cpl_propertylist_delete(header);
213  } /* if */
214  } /* for iframe (all frames) */
215 
216  return newFrames;
217 } /* muse_frameset_find() */
218 
219 /*---------------------------------------------------------------------------*/
230 /*---------------------------------------------------------------------------*/
231 cpl_frameset *
232 muse_frameset_check_raw(const cpl_frameset *aFrames, const char *aTag,
233  unsigned char aIFU)
234 {
235  cpl_frameset *foundFrames = muse_frameset_find(aFrames, aTag, aIFU, CPL_FALSE);
236  cpl_frameset *newFrames = cpl_frameset_new();
237 
238  /* loop through all frames to determine properties */
239  cpl_size iframe, nframes = cpl_frameset_get_size(foundFrames);
240  cpl_msg_debug(__func__, "Determine properties of all %"CPL_SIZE_FORMAT
241  " raw frames of IFU %hhu", nframes, aIFU);
242  int binx = -1, biny = -1, readid = -1;
243  char *fn = NULL, *readname = NULL, *chipname = NULL, *chipid = NULL;
244  for (iframe = 0; iframe < nframes; iframe++) {
245  const cpl_frame *frame = cpl_frameset_get_position_const(foundFrames, iframe);
246  /* load primary and append IFU extension header (extension is *
247  * necessary to get the binning keywords related to raw data) */
248  const char *fn2 = cpl_frame_get_filename(frame);
249  if (!fn) {
250  fn = cpl_strdup(fn2);
251  }
252  cpl_propertylist *header = cpl_propertylist_load(fn2, 0);
253  int extension = muse_utils_get_extension_for_ifu(fn2, aIFU);
254  if (extension > 0) {
255  cpl_propertylist *exthead = cpl_propertylist_load(fn2, extension);
256  cpl_propertylist_append(header, exthead);
257  cpl_propertylist_delete(exthead);
258  }
259  if (!header) {
260  cpl_msg_warning(__func__, "Cannot read FITS header of file \"%s\"!", fn2);
261  continue;
262  }
263  cpl_boolean isOK = CPL_TRUE;
264  if (binx < 0) {
265  binx = muse_pfits_get_binx(header);
266  }
267  if (biny < 0) {
268  biny = muse_pfits_get_biny(header);
269  }
270  if (!readname) {
271  readname = cpl_strdup(muse_pfits_get_read_name(header));
272  }
273  if (readid < 0) {
274  readid = muse_pfits_get_read_id(header);
275  }
276  if (!chipname) {
277  chipname = cpl_strdup(muse_pfits_get_chip_name(header));
278  }
279  if (!chipid) {
280  chipid = cpl_strdup(muse_pfits_get_chip_id(header));
281  }
282  int binx2 = muse_pfits_get_binx(header),
283  biny2 = muse_pfits_get_biny(header),
284  readid2 = muse_pfits_get_read_id(header);
285  const char *chipname2 = muse_pfits_get_chip_name(header),
286  *chipid2 = muse_pfits_get_chip_id(header);
287  if (binx2 != binx) {
288  cpl_msg_warning(__func__, "File \"%s\" (IFU %hhu) was taken with a different"
289  " x-binning factor (reference \"%s\", %d instead of %d)!",
290  fn2, aIFU, fn, binx2, binx);
291  isOK = 0;
292  }
293  if (biny2 != biny) {
294  cpl_msg_warning(__func__, "File \"%s\" (IFU %hhu) was taken with a different"
295  " y-binning factor (reference \"%s\", %d instead of %d)!",
296  fn2, aIFU, fn, biny2, biny);
297  isOK = 0;
298  }
299  if (readid2 != readid) {
300  cpl_msg_warning(__func__, "File \"%s\" (IFU %hhu) was taken with a different"
301  " read-out mode (reference \"%s\", %d/%s instead of %d/%s)!",
302  fn2, aIFU, fn, readid2, muse_pfits_get_read_name(header),
303  readid, readname);
304  isOK = 0;
305  }
306  if (!chipname2 || !chipid2 ||
307  strcmp(chipname, chipname2) || strcmp(chipid, chipid2)) {
308  cpl_msg_warning(__func__, "File \"%s\" (IFU %hhu) has a different chip "
309  "setup (reference \"%s\", name %s vs %s, id %s vs %s)",
310  fn2, aIFU, fn, chipname2, chipname, chipid2, chipid);
311  isOK = 0;
312  }
313 
314  if (!cpl_frame_get_tag(frame) ||
315  !strncmp(cpl_frame_get_tag(frame), MUSE_TAG_EMPTY, 1) ) {
316  /* non-fatal, continue normally and check for other possible *
317  * problems, that should override this exclude reason */
318  cpl_msg_warning(__func__, "File \"%s\" (IFU %hhu) is not tagged!", fn2,
319  aIFU);
320  }
321  cpl_propertylist_delete(header);
322  if (isOK) {
323  cpl_frameset_insert(newFrames, cpl_frame_duplicate(frame));
324  }
325  } /* for nframes */
326  cpl_free(fn);
327  cpl_free(readname);
328  cpl_free(chipname);
329  cpl_free(chipid);
330  cpl_frameset_delete(foundFrames);
331 
332  return newFrames;
333 } /* muse_frameset_check_raw() */
334 
335 /*---------------------------------------------------------------------------*/
354 /*---------------------------------------------------------------------------*/
355 cpl_frameset *
356 muse_frameset_sort_raw_other(const cpl_frameset *aFrames, int aIndex,
357  const char *aDateObs, cpl_boolean aSequence)
358 {
359  cpl_ensure(aFrames, CPL_ERROR_NULL_INPUT, NULL);
360 
361  cpl_frameset *fraw = cpl_frameset_new(),
362  *fother = cpl_frameset_new();
363  int iraw = 0; /* raw frame index */
364  cpl_size iframe, nframes = cpl_frameset_get_size(aFrames);
365  for (iframe = 0; iframe < nframes; iframe++) {
366  const cpl_frame *frame = cpl_frameset_get_position_const(aFrames, iframe);
367  if (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_RAW) {
368  /* if the index matches, insert it, otherwise ignore */
369  int datematch = 1; /* pretend to match when aDateObs == NULL */
370  if (aDateObs) {
371  cpl_propertylist *header
372  = cpl_propertylist_load(cpl_frame_get_filename(frame), 0);
373  const char *dateobs = muse_pfits_get_dateobs(header);
374  datematch = !strncmp(aDateObs, dateobs, strlen(aDateObs));
375  cpl_propertylist_delete(header);
376  }
377  if ((aIndex < 0 && datematch) || aIndex == iraw || aSequence) {
378  cpl_frameset_insert(fraw, cpl_frame_duplicate(frame));
379  }
380  iraw++;
381  } else {
382  cpl_frameset_insert(fother, cpl_frame_duplicate(frame));
383  }
384  } /* for all incoming frames */
385 #if 0
386  printf("incoming frames (index=%d, DATE-OBS=%s):\n", aIndex, aDateObs);
387  cpl_frameset_dump(aFrames, stdout);
388  fflush(stdout);
389  printf("=============================================================\n"
390  "raw frames:\n");
391  cpl_frameset_dump(fraw, stdout);
392  fflush(stdout);
393  printf("-------------------------------------------------------------\n"
394  "other frames:\n");
395  cpl_frameset_dump(fother, stdout);
396  fflush(stdout);
397 #endif
398 
399  /* transfer all other frames into the raw = output frameset */
400  cpl_frameset_join(fraw, fother);
401  cpl_frameset_delete(fother);
402 #if 0
403  printf("=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=_=\n"
404  "all sorted frames:\n");
405  cpl_frameset_dump(fraw, stdout);
406  fflush(stdout);
407 #endif
408 
409  return fraw;
410 } /* muse_frameset_sort_raw_other() */
411 
412 /*---------------------------------------------------------------------------*/
423 /*---------------------------------------------------------------------------*/
424 cpl_frame *
425 muse_frameset_find_master(const cpl_frameset *aFrames, const char *aTag,
426  unsigned char aIFU)
427 {
428  cpl_frameset *frames = muse_frameset_find(aFrames, aTag, aIFU, CPL_FALSE);
429  cpl_frame *frame = NULL;
430  if (cpl_frameset_count_tags(frames, aTag) == 1) {
431  frame = cpl_frame_duplicate(cpl_frameset_get_position_const(frames, 0));
432  }
433  cpl_frameset_delete(frames);
434  return frame;
435 } /* muse_frameset_find_master() */
436 
437 /*---------------------------------------------------------------------------*/
450 /*---------------------------------------------------------------------------*/
451 cpl_table *
452 muse_table_load(muse_processing *aProcessing, const char *aTag,
453  unsigned char aIFU)
454 {
455  cpl_frame *frame = muse_frameset_find_master(aProcessing->inputFrames, aTag,
456  aIFU);
457  if (!frame) {
458  if (aIFU > 0) {
459  cpl_msg_debug(__func__, "No table found for tag %s and IFU %hhu", aTag,
460  aIFU);
461  } else {
462  /* swallow the IFU part, if it's not a real IFU number */
463  cpl_msg_debug(__func__, "No table found for tag %s", aTag);
464  }
465  return NULL;
466  }
467 
468  /* try to load the extension table for the given IFU */
469  const char *fn = cpl_frame_get_filename(frame);
470  int extension = muse_utils_get_extension_for_ifu(fn, aIFU);
471  /* other tables hopefully always have the data in the first extension */
472  if (extension <= 0) {
473  if (aIFU > 0) {
474  cpl_msg_debug(__func__, "didn't find a specific extension for IFU %hhu, "
475  "will just use the first one", aIFU);
476  }
477  extension = 1;
478  }
479  cpl_table *table = cpl_table_load(fn, extension, 0);
480  if (!table || !cpl_table_get_nrow(table)) {
481  cpl_msg_info(__func__, "loading %s from file \"%s\" (ext %d) failed: %s",
482  aTag, fn, extension, cpl_error_get_message());
483  cpl_frame_delete(frame);
484  cpl_table_delete(table); /* try to delete in case it was just empty */
485  return NULL;
486  }
487  cpl_propertylist *header = cpl_propertylist_load(fn, extension);
488  cpl_errorstate state = cpl_errorstate_get();
489  const char *en = muse_pfits_get_extname(header);
490  char *extname = NULL;
491  if (en && cpl_errorstate_is_equal(state)) {
492  extname = cpl_sprintf("[%s]", en);
493  } else {
494  cpl_errorstate_set(state);
495  extname = cpl_sprintf("%c", '\0');
496  }
497  cpl_msg_info(__func__, "loaded %s from file \"%s%s\" (ext %d)", aTag, fn,
498  extname, extension);
499  cpl_free(extname);
500  cpl_propertylist_delete(header);
501  /* tables are calibrations of some sort, so append them as such into *
502  * the used frames for the FITS header of the output product, if they exist */
503  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_CALIB, 0);
504 
505  return table;
506 } /* muse_table_load() */
507 
508 /*---------------------------------------------------------------------------*/
518 /*---------------------------------------------------------------------------*/
519 cpl_propertylist *
520 muse_propertylist_load(muse_processing *aProcessing, const char *aTag)
521 {
522  cpl_frame *frame = muse_frameset_find_master(aProcessing->inputFrames, aTag,
523  0);
524  if (!frame) {
525  cpl_msg_debug(__func__, "No propertylist found for tag %s", aTag);
526  return NULL;
527  }
528 
529  const char *filename = cpl_frame_get_filename(frame);
530  cpl_propertylist *pl = cpl_propertylist_load(filename, 0);
531  if (!pl) {
532  cpl_msg_info(__func__, "loading %s from file %s failed: %s", aTag,
533  filename, cpl_error_get_message());
534  cpl_frame_delete(frame);
535  return NULL;
536  }
537  cpl_msg_info(__func__, "loaded %s from file %s", aTag, filename);
538  /* property lists are calibrations of some sort, so append them as such into *
539  * the used frames for the FITS header of the output product, if they exist */
540  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_CALIB, 0);
541 
542  return pl;
543 } /* muse_propertylist_load() */
544 
545 /*----------------------------------------------------------------------------*/
553 /*----------------------------------------------------------------------------*/
555  { "lambda", CPL_TYPE_DOUBLE, "Angstrom", "%7.2f", "wavelength", CPL_TRUE},
556  { "throughput", CPL_TYPE_DOUBLE, "", "%.4e",
557  "filter response (in fractions of 1)", CPL_TRUE},
558  { NULL, 0, NULL, NULL, NULL, CPL_FALSE }
559 };
560 
561 /*---------------------------------------------------------------------------*/
583 /*---------------------------------------------------------------------------*/
584 cpl_table *
585 muse_table_load_filter(muse_processing *aProcessing, const char *aFilterName)
586 {
587  cpl_ensure(aFilterName, CPL_ERROR_NULL_INPUT, NULL);
588  if (!strncasecmp(aFilterName, "none", 4)) {
589  cpl_msg_debug(__func__, "No filter wanted (filter \"%s\")", aFilterName);
590  return NULL;
591  }
592  if (!strcmp(aFilterName, "white")) {
593  cpl_msg_debug(__func__, "White-light integration wanted (filter \"%s\")",
594  aFilterName);
595  /* create minimal rectangular filter function table */
596  cpl_table *table = muse_cpltable_new(muse_filtertable_def, 4);
597  cpl_table_set(table, "lambda", 0, kMuseNominalLambdaMin - 1e-5);
598  cpl_table_set(table, "lambda", 1, kMuseNominalLambdaMin);
599  cpl_table_set(table, "lambda", 2, kMuseNominalLambdaMax);
600  cpl_table_set(table, "lambda", 3, kMuseNominalLambdaMax - 1e-5);
601  cpl_table_set(table, "throughput", 0, 0.);
602  cpl_table_set(table, "throughput", 1, 1.);
603  cpl_table_set(table, "throughput", 2, 1.);
604  cpl_table_set(table, "throughput", 3, 0.);
605  return table;
606  } /* if white-light filter */
607  cpl_frame *frame = muse_frameset_find_master(aProcessing->inputFrames,
608  MUSE_TAG_FILTER_LIST, 0);
609  if (!frame) {
610  cpl_error_set_message(__func__, CPL_ERROR_FILE_NOT_FOUND, "%s (for filter "
611  "\"%s\") is missing", MUSE_TAG_FILTER_LIST,
612  aFilterName);
613  return NULL;
614  }
615 
616  /* try to load the extension table for the given filter, using EXTNAME */
617  char *filename = (char *)cpl_frame_get_filename(frame);
618  int ext = cpl_fits_find_extension(filename, aFilterName);
619  if (ext <= 0) {
620  cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND, "\"%s\" does not "
621  "contain filter \"%s\"", filename, aFilterName);
622  cpl_frame_delete(frame);
623  return NULL;
624  }
625 
626  cpl_table *table = cpl_table_load(filename, ext, 0);
627  if (!table || !cpl_table_get_nrow(table)) {
628  cpl_error_set_message(__func__, cpl_error_get_code(), "loading filter "
629  "\"%s\" from file \"%s\" (ext %d) failed",
630  aFilterName, filename, ext);
631  cpl_frame_delete(frame);
632  cpl_table_delete(table); /* try to delete in case it was just empty */
633  return NULL;
634  }
635  cpl_msg_info(__func__, "loaded filter \"%s\" from file \"%s\" (ext %d)",
636  aFilterName, filename, ext);
637  /* filters from the input frameset should be listed, append them into *
638  * the used frames for the FITS header of the output product, if they exist */
639  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_CALIB, 0);
640  return table;
641 } /* muse_table_load_filter() */
642 
643 /*---------------------------------------------------------------------------*/
663 /*---------------------------------------------------------------------------*/
664 char *
665 muse_utils_header_get_lamp_names(cpl_propertylist *aHeader, char aSep)
666 {
667  cpl_ensure(aHeader, CPL_ERROR_NULL_INPUT, NULL);
668 
669  char *lampname = NULL;
670  int n, nlamps = muse_pfits_get_lampnum(aHeader);
671  for (n = 1; n <= nlamps; n++) {
672  cpl_errorstate prestate = cpl_errorstate_get();
673  int st1 = muse_pfits_get_lamp_status(aHeader, n),
674  st2 = muse_pfits_get_shut_status(aHeader, n);
675  if (!cpl_errorstate_is_equal(prestate)) {
676  cpl_errorstate_set(prestate); /* lamp headers may be missing */
677  }
678  if (!st1 || !st2) {
679  continue; /* lamp off or its shutter closed */
680  }
681  char *name = /* XXX ! */(char *)muse_pfits_get_lamp_name(aHeader, n);
682  if (!strncmp(name, "CU-LAMP-", 8)) {
683  name += 8; /* ignore the lamp prefix */
684  }
685  if (!strcmp(name, "CU-LAMP3") || !strcmp(name, "CU-LAMP6")) { /* XXX overwrite read-only locations */
686  /* 3 is standard Neon, 6 is high-power Neon */
687  name[0] = 'N';
688  name[1] = 'e';
689  name[2] = '\0';
690  } else if (!strcmp(name, "CU-LAMP4")) {
691  name[0] = 'X';
692  name[1] = 'e';
693  name[2] = '\0';
694  } else if (!strcmp(name, "CU-LAMP5")) {
695  name[0] = 'H';
696  name[1] = 'g';
697  name[2] = 'C';
698  name[3] = 'd';
699  name[4] = '\0';
700  }
701  if (lampname) {
702  char *temp = lampname;
703  lampname = cpl_sprintf("%s%c%s", temp, aSep, name);
704  cpl_free(temp);
705  } else {
706  lampname = cpl_sprintf("%s", name);
707  }
708  } /* for n (all possible lamps) */
709  return lampname;
710 } /* muse_utils_header_get_lamp_names() */
711 
712 /*---------------------------------------------------------------------------*/
730 /*---------------------------------------------------------------------------*/
731 cpl_array *
732 muse_utils_header_get_lamp_numbers(cpl_propertylist *aHeader)
733 {
734  cpl_ensure(aHeader, CPL_ERROR_NULL_INPUT, NULL);
735 
736  /* start with an array of zero length */
737  cpl_array *lampnumbers = cpl_array_new(0, CPL_TYPE_INT);
738  int n, nlamps = muse_pfits_get_lampnum(aHeader);
739  for (n = 1; n <= nlamps; n++) {
740  cpl_errorstate prestate = cpl_errorstate_get();
741  int st1 = muse_pfits_get_lamp_status(aHeader, n),
742  st2 = muse_pfits_get_shut_status(aHeader, n);
743  if (!cpl_errorstate_is_equal(prestate)) {
744  cpl_errorstate_set(prestate); /* lamp headers may be missing */
745  }
746  if (!st1 || !st2) {
747  continue; /* lamp off or its shutter closed */
748  }
749  /* now enlarge the array and save the lamp numbers as last element */
750  cpl_array_set_size(lampnumbers, cpl_array_get_size(lampnumbers) + 1);
751  cpl_array_set_int(lampnumbers, cpl_array_get_size(lampnumbers) - 1,
752  n);
753  } /* for n (all possible lamps) */
754  if (cpl_array_get_size(lampnumbers) < 1) {
755  cpl_array_delete(lampnumbers);
756  lampnumbers = NULL;
757  }
758  return lampnumbers;
759 } /* muse_utils_header_get_lamp_numbers() */
760 
761 /*----------------------------------------------------------------------------*/
773 /*----------------------------------------------------------------------------*/
774 cpl_matrix *
775 muse_matrix_new_gaussian_2d(int aXHalfwidth, int aYHalfwidth, double aSigma)
776 {
777  cpl_matrix *kernel = cpl_matrix_new(2*aXHalfwidth+1, 2*aYHalfwidth+1);
778  if (!kernel) {
779  cpl_msg_error(__func__, "Could not create matrix: %s",
780  cpl_error_get_message());
781  return NULL;
782  }
783  double sum = 0.;
784  int i;
785  for (i = -aXHalfwidth; i <= aXHalfwidth; i++) {
786  int j;
787  for (j = -aYHalfwidth; j <= aYHalfwidth; j++) {
788  /* set Gaussian kernel */
789  double gauss = 1. / (aSigma*sqrt(2.*CPL_MATH_PI))
790  * exp(-(i*i + j*j) / (2.*aSigma*aSigma));
791  cpl_matrix_set(kernel, i+aXHalfwidth, j+aYHalfwidth, gauss);
792  sum += gauss;
793  }
794  }
795  /* normalize the matrix, the sum of the elements should be 1 */
796  cpl_matrix_divide_scalar(kernel, sum);
797 
798  return kernel;
799 } /* muse_matrix_new_gaussian_2d */
800 
801 /*----------------------------------------------------------------------------*/
827 /*----------------------------------------------------------------------------*/
828 cpl_error_code
829 muse_utils_image_get_centroid_window(cpl_image *aImage, int aX1, int aY1,
830  int aX2, int aY2, double *aX, double *aY,
831  muse_utils_centroid_type aBgType)
832 {
833  cpl_ensure_code(aImage, CPL_ERROR_NULL_INPUT);
834  cpl_ensure_code(aX || aY, CPL_ERROR_NULL_INPUT);
835 
836  cpl_image *im = cpl_image_extract(aImage, aX1, aY1, aX2, aY2);
837  cpl_ensure_code(im, cpl_error_get_code());
838 
839  double bg = 0.; /* subtract the background */
840  if (aBgType == MUSE_UTILS_CENTROID_MEAN) {
841  bg = cpl_image_get_mean(im);
842  } else if (aBgType == MUSE_UTILS_CENTROID_MEDIAN) {
843  bg = cpl_image_get_median(im);
844  } else {
845  cpl_ensure_code(aBgType == MUSE_UTILS_CENTROID_NORMAL,
846  CPL_ERROR_ILLEGAL_INPUT);
847  }
848  cpl_image_subtract_scalar(im, bg);
849 
850  /* centroid in x direction */
851  if (aX) {
852  cpl_image *row = cpl_image_collapse_create(im, 0);
853  double w = 0., /* weight */
854  f = 0.; /* flux */
855  int i, n = cpl_image_get_size_x(row);
856  for (i = 1; i <= n; i++) {
857  int err;
858  double value = cpl_image_get(row, i, 1, &err);
859  if (err || (value < 0 && aBgType != MUSE_UTILS_CENTROID_NORMAL)) {
860  continue;
861  }
862  w += i * value;
863  f += value;
864  } /* for i */
865  *aX = w / f + aX1 - 1;
866  cpl_image_delete(row);
867  } /* if aX */
868  /* centroid in y direction */
869  if (aY) {
870  cpl_image *col = cpl_image_collapse_create(im, 1);
871  double w = 0., /* weight */
872  f = 0.; /* flux */
873  int j, n = cpl_image_get_size_y(col);
874  for (j = 1; j <= n; j++) {
875  int err;
876  double value = cpl_image_get(col, 1, j, &err);
877  if (err || (value < 0 && aBgType != MUSE_UTILS_CENTROID_NORMAL)) {
878  continue;
879  }
880  w += j * value;
881  f += value;
882  } /* for j */
883  *aY = w / f + aY1 - 1;
884  cpl_image_delete(col);
885  } /* if aY */
886  cpl_image_delete(im);
887 
888  return CPL_ERROR_NONE;
889 } /* muse_utils_image_get_centroid_window() */
890 
891 /*---------------------------------------------------------------------------*/
921 /*---------------------------------------------------------------------------*/
922 cpl_error_code
923 muse_utils_get_centroid(const cpl_matrix *aPositions,
924  const cpl_vector *aValues, const cpl_vector *aErrors,
925  double *aX, double *aY, muse_utils_centroid_type aBgType)
926 {
927  cpl_ensure_code(aPositions && aValues, CPL_ERROR_NULL_INPUT);
928  cpl_ensure_code(cpl_matrix_get_ncol(aPositions) == 2, CPL_ERROR_ILLEGAL_INPUT);
929  int npoints = cpl_matrix_get_nrow(aPositions);
930  cpl_ensure_code(npoints == cpl_vector_get_size(aValues), CPL_ERROR_ILLEGAL_INPUT);
931  if (aErrors) {
932  cpl_ensure_code(cpl_vector_get_size(aValues) == cpl_vector_get_size(aErrors),
933  CPL_ERROR_ILLEGAL_INPUT);
934  }
935  cpl_ensure_code(aX || aY, CPL_ERROR_NULL_INPUT);
936 
937  const double *values = cpl_vector_get_data_const(aValues);
938  double xcen = 0., ycen = 0.,
939  weight = 0., bg = 0.;
940  if (aBgType == MUSE_UTILS_CENTROID_MEAN) {
941  bg = cpl_vector_get_mean(aValues);
942  } else if (aBgType == MUSE_UTILS_CENTROID_MEDIAN) {
943  bg = cpl_vector_get_median_const(aValues);
944  } else {
945  cpl_ensure_code(aBgType == MUSE_UTILS_CENTROID_NORMAL,
946  CPL_ERROR_ILLEGAL_INPUT);
947  }
948 
949  int i;
950  for (i = 0; i < npoints; i++) {
951  double w = values[i] - bg;
952  if (w < 0 && aBgType != MUSE_UTILS_CENTROID_NORMAL) {
953  continue;
954  }
955  if (aErrors) {
956  w /= cpl_vector_get(aErrors, i);
957  }
958  xcen += cpl_matrix_get(aPositions, i, 0) * w;
959  ycen += cpl_matrix_get(aPositions, i, 1) * w;
960  weight += w;
961  } /* for i */
962  xcen /= weight;
963  ycen /= weight;
964 
965  if (aX) {
966  *aX = xcen;
967  }
968  if (aY) {
969  *aY = ycen;
970  }
971  return CPL_ERROR_NONE;
972 } /* muse_utils_get_centroid() */
973 
974 /*----------------------------------------------------------------------------*/
997 /*----------------------------------------------------------------------------*/
998 static int
999 muse_utils_multigauss(const double x[], const double p[], double *f)
1000 {
1001  const double xp = x[0]; /* evaluation point */
1002  const cpl_size ncoeffs = p[0],
1003  npeaks = p[1];
1004  const double sigma = p[2 + ncoeffs];
1005  if (sigma == 0.0) { /* special case, Dirac deltas */
1006  cpl_size i;
1007  for (i = 0; i < npeaks; i++) {
1008  if (p[2 + ncoeffs + 1 + i] == xp) {
1009  *f = DBL_MAX;
1010  return 0;
1011  }
1012  }
1013  *f = 0.;
1014  return 0;
1015  }
1016 
1017  /* compute the function value for the normal case */
1018  *f = 0.;
1019  /* evaluate the polynomial, adding all orders to the function value */
1020  cpl_size ic;
1021  for (ic = 0; ic < ncoeffs; ic++) {
1022  *f += p[2 + ic] * pow(xp, ic);
1023  }
1024  /* evaluate the Gaussians, adding all peaks to the function value */
1025  cpl_size ip;
1026  for (ip = 0; ip < npeaks; ip++) {
1027  const double xi = p[3 + ncoeffs + ip],
1028  Ai = p[3 + ncoeffs + npeaks + ip],
1029  exponent = (xi - xp) / sigma;
1030  *f += Ai / CPL_MATH_SQRT2PI / sigma * exp(-0.5 * exponent*exponent);
1031  }
1032 #if 0
1033  printf("eval at %f --> %f\n", xp, *f);
1034  int i;
1035  for (i = 0; i < ncoeffs + 2*npeaks + 3; i++) {
1036  printf(" [%02d] %f\n", i, p[i]);
1037  }
1038  fflush(stdout);
1039 #endif
1040  return 0;
1041 } /* muse_utils_multigauss() */
1042 
1043 /*----------------------------------------------------------------------------*/
1053 /*----------------------------------------------------------------------------*/
1054 static int
1055 muse_utils_dmultigauss(const double x[], const double p[], double f[])
1056 {
1057  const cpl_size ncoeffs = p[0],
1058  npeaks = p[1];
1059  const double sigma = p[2 + ncoeffs];
1060  if (sigma == 0.0) { /* special case, Dirac deltas */
1061  memset(f, 0, sizeof(double) * (ncoeffs + 2*npeaks + 3));
1062  return 0;
1063  }
1064 
1065  const double xp = x[0]; /* evaluation point */
1066  f[0] = f[1] = 0.; /* derivatives of the number of parameters are always zero! */
1067  /* the derivatives by the coefficients of the polynomial */
1068  cpl_size ic;
1069  for (ic = 0; ic < ncoeffs; ic++) {
1070  f[2 + ic] = pow(xp, ic);
1071  }
1072  /* derivative regarding sigma */
1073  f[2 + ncoeffs] = 0.;
1074  cpl_size ip;
1075  for (ip = 0; ip < npeaks; ip++) {
1076  const double xi = p[3 + ncoeffs + ip],
1077  Ai = p[3 + ncoeffs + npeaks + ip],
1078  exponent = (xi - xp) / sigma,
1079  expsq = exponent * exponent,
1080  expfunc = exp(-0.5 * expsq);
1081  f[2 + ncoeffs] -= Ai / CPL_MATH_SQRT2PI / (sigma*sigma)
1082  * (1 - expsq) * expfunc;
1083  /* derivative regarding centers */
1084  f[3 + ncoeffs + ip] = -Ai / CPL_MATH_SQRT2PI / (sigma*sigma*sigma)
1085  * (xi - xp) * expfunc;
1086  /* derivative regarding fluxes */
1087  f[3 + ncoeffs + npeaks + ip] = 1 / CPL_MATH_SQRT2PI / sigma * expfunc;
1088  } /* for ip (peak indices) */
1089 #if 0
1090  printf("deval at %f -->\n", xp);
1091  int i;
1092  for (i = 0; i < ncoeffs + 2*npeaks + 3; i++) {
1093  printf(" [%02d] %e %f\n", i, f[i], p[i]);
1094  }
1095  fflush(stdout);
1096 #endif
1097  return 0;
1098 } /* muse_utils_dmultigauss() */
1099 
1100 /*----------------------------------------------------------------------------*/
1151 /*----------------------------------------------------------------------------*/
1152 cpl_error_code
1153 muse_utils_fit_multigauss_1d(const cpl_vector *aX, const cpl_bivector *aY,
1154  cpl_vector *aCenter, double *aSigma,
1155  cpl_vector *aFlux, cpl_vector *aPoly,
1156  double *aMSE, double *aRedChisq,
1157  cpl_matrix **aCovariance)
1158 {
1159  if (aCovariance) {
1160  *aCovariance = NULL;
1161  }
1162  cpl_ensure_code(aX && aY && aCenter && aSigma, CPL_ERROR_NULL_INPUT);
1163  cpl_size npoints = cpl_vector_get_size(aX);
1164  cpl_ensure_code(npoints == cpl_bivector_get_size(aY), CPL_ERROR_INCOMPATIBLE_INPUT);
1165  cpl_size npeaks = cpl_vector_get_size(aCenter);
1166  cpl_ensure_code(!aFlux || npeaks == cpl_vector_get_size(aFlux),
1167  CPL_ERROR_INCOMPATIBLE_INPUT);
1168  cpl_size ncoeffs = aPoly ? cpl_vector_get_size(aPoly) : 0,
1169  npars = ncoeffs /* poly coeffs */ + 1 /* sigma */
1170  + 2 * npeaks /* centers and fluxes */;
1171  cpl_ensure_code(!aRedChisq || npoints >= npars, CPL_ERROR_ILLEGAL_INPUT);
1172 
1173  /* "transform" the input data into the right structures for cpl_fit_lvmq() */
1174  cpl_matrix *x = cpl_matrix_wrap(npoints, 1, (double *)cpl_vector_get_data_const(aX));
1175  const cpl_vector *y = cpl_bivector_get_x_const(aY),
1176  *ye = cpl_bivector_get_y_const(aY);
1177  /* set up and fill the parameters structure */
1178  cpl_vector *p = cpl_vector_new(npars + 2);
1179  int *pflags = cpl_calloc(npars + 2, sizeof(int));
1180  /* first the numbers (of polynomial coefficients and peaks) */
1181  cpl_vector_set(p, 0, ncoeffs);
1182  cpl_vector_set(p, 1, npeaks);
1183  cpl_size i; /* all but these two first parameters participate in the fit */
1184  for (i = 2; i < npars + 2; i++) {
1185  pflags[i] = 1; /* fit this parameter, set to non-zero value */
1186  } /* for i (all but the first two parameters) */
1187 #if 0
1188  cpl_array *aflags = cpl_array_wrap_int(pflags, npars + 2);
1189  printf("aflags, non-zero means parameter is fitted by cpl_fit_lvmq():\n");
1190  cpl_array_dump(aflags, 0, 1000, stdout);
1191  fflush(stdout);
1192  cpl_array_unwrap(aflags);
1193 #endif
1194  /* then the polynomial */
1195  cpl_size j;
1196  for (j = 0, i = 2; j < ncoeffs; j++, i++) {
1197  cpl_vector_set(p, i, cpl_vector_get(aPoly, j));
1198  }
1199  /* the common sigma value */
1200  double sigma = fabs(*aSigma);
1201  if (*aSigma < 0) {
1202  pflags[i] = 0; /* fix the sigma parameter */
1203  }
1204  cpl_vector_set(p, i++, sigma);
1205  /* the first-guess centers as passed into this function */
1206  for (j = 0; j < npeaks; j++, i++) {
1207  cpl_vector_set(p, i, cpl_vector_get(aCenter, j));
1208  }
1209  /* the first-guess fluxes, if passed into this function */
1210  for (j = 0; j < npeaks; j++, i++) {
1211  if (aFlux) {
1212  cpl_vector_set(p, i, cpl_vector_get(aFlux, j));
1213  } else {
1214  cpl_vector_set(p, i, 1.);
1215  }
1216  }
1217 #if 0
1218  printf("input parameters p and their pflags:\n");
1219  for (j = 0; j < cpl_vector_get_size(p); j++) {
1220  printf(" [%02d] %f %s\n", j, cpl_vector_get(p, j),
1221  pflags[j] ? "\tfitted" : "constant");
1222  }
1223 #endif
1224  cpl_matrix *covariance = NULL;
1225  cpl_error_code rc = cpl_fit_lvmq(x, NULL, y, ye, p, pflags,
1226  muse_utils_multigauss, muse_utils_dmultigauss,
1227  CPL_FIT_LVMQ_TOLERANCE, CPL_FIT_LVMQ_COUNT,
1228  CPL_FIT_LVMQ_MAXITER, aMSE, aRedChisq,
1229  &covariance);
1230  cpl_matrix_unwrap(x);
1231  cpl_free(pflags);
1232 #if 0
1233  printf("output parameters vector p (%e, %e):\n",
1234  aMSE ? sqrt(*aMSE) : 0.0, aRedChisq ? *aRedChisq : 0.0);
1235  cpl_vector_dump(p, stdout);
1236  fflush(stdout);
1237 #endif
1238  /* get all parameters back into the input structures, same order as above */
1239  for (j = 0, i = 2; j < ncoeffs; j++, i++) {
1240  cpl_vector_set(aPoly, j, cpl_vector_get(p, i));
1241  }
1242  /* In principle, the LM algorithm might have converged to a negative sigma *
1243  * (even if the guess value was positive). Make sure that the returned *
1244  * sigma is positive (by convention), see cpl_vector_fit_gaussian(). */
1245  *aSigma = fabs(cpl_vector_get(p, i++));
1246  for (j = 0; j < npeaks; j++, i++) {
1247  cpl_vector_set(aCenter, j, cpl_vector_get(p, i));
1248  }
1249  /* the first-guess fluxes, if passed into this function */
1250  if (aFlux) {
1251  for (j = 0; j < npeaks; j++, i++) {
1252  cpl_vector_set(aFlux, j, cpl_vector_get(p, i));
1253  }
1254  }
1255  /* extract the relevant part of the covariance matrix, if needed *
1256  * (the number of coefficients and peaks are not relevant parameters!) */
1257  if (aCovariance) {
1258  *aCovariance = cpl_matrix_extract(covariance, 2, 2, 1, 1,
1259  cpl_matrix_get_nrow(covariance) - 2,
1260  cpl_matrix_get_ncol(covariance) - 2);
1261  }
1262  cpl_matrix_delete(covariance);
1263  cpl_vector_delete(p);
1264  return rc;
1265 } /* muse_utils_fit_multigauss_1d() */
1266 
1267 #if MOFFAT_USE_MUSE_OPTIMIZE
1268 /* structure to pass around data needed for the *
1269  * evaluation using muse_moffat_2d_optfunc() */
1270 typedef struct {
1271  const cpl_matrix *positions;
1272  const cpl_vector *values;
1273  const cpl_vector *errors;
1274 } fitdata_t;
1275 
1276 /*---------------------------------------------------------------------------*/
1288 /*---------------------------------------------------------------------------*/
1289 static cpl_error_code
1290 muse_moffat_2d_optfunc(void *aData, cpl_array *aParams, cpl_array *aResiduals)
1291 {
1292  const cpl_matrix *pos = ((fitdata_t *)aData)->positions;
1293  const cpl_vector *val = ((fitdata_t *)aData)->values,
1294  *err = ((fitdata_t *)aData)->errors;
1295  /* Compute function residuals */
1296  const double *p = cpl_array_get_data_double_const(aParams);
1297  double *residuals = cpl_array_get_data_double(aResiduals);
1298  int i, npoints = cpl_vector_get_size(val);
1299  for (i = 0; i < npoints; i++) {
1300  double xterm = (cpl_matrix_get(pos, i, 0) - p[2]) / p[4],
1301  yterm = (cpl_matrix_get(pos, i, 1) - p[3]) / p[5],
1302  crossterm = 2 * p[7] * xterm * yterm,
1303  base = 1 + (xterm*xterm + crossterm + yterm*yterm) / (1 + p[7]*p[7]),
1304  moffat = p[0] + p[1] * (p[6] - 1)
1305  / (CPL_MATH_PI * p[4]*p[5] * sqrt(1 - p[7]*p[7]))
1306  * pow(base, -p[6]);
1307  /* set to the actual value */
1308  residuals[i] = cpl_vector_get(val, i) - moffat;
1309  /* finally weight by the error on this point */
1310  residuals[i] /= cpl_vector_get(err, i);
1311  } /* for i (all points) */
1312  return CPL_ERROR_NONE;
1313 } /* muse_moffat_2d_optfunc() */
1314 
1315 #else /* MOFFAT_USE_MUSE_OPTIMIZE follows */
1316 
1317 /*---------------------------------------------------------------------------*/
1329 /*---------------------------------------------------------------------------*/
1330 static int
1331 muse_moffat_2d_function(const double xy[], const double p[], double *f)
1332 {
1333  double xterm = (xy[0] - p[2]) / p[4], /* xy[0] is x */
1334  yterm = (xy[1] - p[3]) / p[5], /* xy[1] is y */
1335  crossterm = 2 * p[7] * xterm * yterm,
1336  rhoterm = 1. - p[7]*p[7],
1337  base = 1. + (xterm*xterm + crossterm + yterm*yterm) / rhoterm;
1338  *f = p[0] + p[1] * (p[6] - 1.) / (CPL_MATH_PI * p[4]*p[5] * sqrt(rhoterm))
1339  * pow(base, -p[6]);
1340 //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);
1341 //fflush(stdout);
1342  return 0;
1343 } /* muse_moffat_2d_function() */
1344 
1345 /*---------------------------------------------------------------------------*/
1357 /*---------------------------------------------------------------------------*/
1358 static int
1359 muse_moffat_2d_derivative(const double xy[], const double p[], double f[])
1360 {
1361  double xterm = (xy[0] - p[2]) / p[4], /* xy[0] is x */
1362  yterm = (xy[1] - p[3]) / p[5], /* xy[1] is y */
1363  crossterm = 2 * p[7] * xterm * yterm,
1364  rhoterm = 1. - p[7]*p[7],
1365  base = 1. + (xterm*xterm + crossterm + yterm*yterm) / (rhoterm);
1366  f[0] = 1; /* dM(x,y)/dB */
1367  f[1] = (p[6] - 1.) / (CPL_MATH_PI * p[4]*p[5] * sqrt(rhoterm))/* dM(x,y)/dA */
1368  * pow(base, -p[6]);
1369  f[2] = 2 * p[1] * p[6]*(p[6] - 1.) /* dM(x,y)/dxc */
1370  / (CPL_MATH_PI * p[4]*p[4] * p[5] * pow(rhoterm, 3./2.))
1371  * (xterm + p[7] * yterm) * pow(base, -p[6]-1.);
1372  f[3] = 2 * p[1] * p[6]*(p[6] - 1.) /* dM(x,y)/dyc */
1373  / (CPL_MATH_PI * p[4] * p[5]*p[5] * pow(rhoterm, 3./2.))
1374  * (yterm + p[7] * xterm) * pow(base, -p[6]-1.);
1375  f[4] = p[1] * (p[6] - 1.) /* dM(x,y)/dalphax */
1376  / (CPL_MATH_PI * p[4]*p[4] * p[5] * sqrt(rhoterm))
1377  * (-pow(base, -p[6]) + 2 * p[6] / (rhoterm) * pow(base, -p[6]-1.)
1378  * (xterm*xterm + 0.5*crossterm));
1379  f[5] = p[1] * (p[6] - 1.) /* dM(x,y)/dalphay */
1380  / (CPL_MATH_PI * p[4] * p[5]*p[5] * sqrt(rhoterm))
1381  * (-pow(base, -p[6]) + 2 * p[6] / (rhoterm) * pow(base, -p[6]-1.)
1382  * (yterm*yterm + 0.5*crossterm));
1383  f[6] = p[1] / (CPL_MATH_PI * p[4]*p[5] * sqrt(rhoterm)) /* dM(x,y)/dbeta */
1384  * pow(base, -p[6]) * (1. + (p[6] - 1.) * log(base));
1385  f[7] = p[1] * (p[6] - 1.) /* dM(x,y)/drho */
1386  / (CPL_MATH_PI * p[4]*p[5] * pow(rhoterm, 3./2.))
1387  * (p[7] * pow(base, -p[6])
1388  - 2. * p[6] * pow(base, -p[6]-1.)
1389  * (xterm*yterm * (1 + 2*p[7]*p[7] / rhoterm)
1390  + p[7] / rhoterm * (xterm*xterm + yterm*yterm) ));
1391 //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]);
1392 //fflush(stdout);
1393  return 0;
1394 } /* muse_moffat_2d_derivative() */
1395 
1396 #endif
1397 
1398 /*----------------------------------------------------------------------------*/
1471 /*----------------------------------------------------------------------------*/
1472 cpl_error_code
1473 muse_utils_fit_moffat_2d(const cpl_matrix *aPositions,
1474  const cpl_vector *aValues, const cpl_vector *aErrors,
1475  cpl_array *aParams, cpl_array *aPErrors,
1476  const cpl_array *aPFlags,
1477  double *aRMS, double *aRedChisq)
1478 {
1479  /* lots of error checking, almost exactly taken from CPL */
1480  if (!aPositions) {
1481  return cpl_error_set_message(__func__, CPL_ERROR_NULL_INPUT,
1482  "Missing input positions.");
1483  }
1484  if (!aValues) {
1485  return cpl_error_set_message(__func__, CPL_ERROR_NULL_INPUT,
1486  "Missing input values / errors.");
1487  }
1488  if (cpl_matrix_get_ncol(aPositions) != 2) {
1489  return cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
1490  "Input positions are not for two-dimensional data.");
1491  }
1492  if (cpl_vector_get_size(aValues) != cpl_matrix_get_nrow(aPositions)) {
1493  return cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
1494  "Input positions and values data must have same size.");
1495  }
1496  if (aErrors && (cpl_vector_get_size(aValues) != cpl_vector_get_size(aErrors))) {
1497  return cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
1498  "Input vectors must have same size.");
1499  }
1500  if (!aParams) {
1501  return cpl_error_set_message(__func__, CPL_ERROR_NULL_INPUT,
1502  "Missing input parameters array.");
1503  }
1504  if (cpl_array_get_type(aParams) != CPL_TYPE_DOUBLE) {
1505  return cpl_error_set_message(__func__, CPL_ERROR_INVALID_TYPE,
1506  "Parameters array should be CPL_TYPE_DOUBLE.");
1507  }
1508  if (aPErrors && (cpl_array_get_type(aPErrors) != CPL_TYPE_DOUBLE)) {
1509  return cpl_error_set_message(__func__, CPL_ERROR_INVALID_TYPE,
1510  "Parameters error array should be CPL_TYPE_DOUBLE.");
1511  }
1512  if (aPFlags && (cpl_array_get_type(aPFlags) != CPL_TYPE_INT)) {
1513  return cpl_error_set_message(__func__, CPL_ERROR_INVALID_TYPE,
1514  "Parameters error array should be CPL_TYPE_INT.");
1515  }
1516  if ((aPErrors || aRedChisq) && !aErrors) {
1517  return cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
1518  "Missing input parameters errors.");
1519  }
1520  int npoints = cpl_matrix_get_nrow(aPositions);
1521  if (npoints < 8) {
1522  /* Too few positions for 8 free parameters! */
1523  return cpl_error_set_message(__func__, CPL_ERROR_SINGULAR_MATRIX,
1524  "%d are not enough points to fit a Moffat profile.",
1525  npoints);
1526  }
1527 
1528  int pflags[8] = { 1, 1, 1, 1, 1, 1, 1, 1 };
1529  /* Ensure that frozen parameters have a value (first-guess) */
1530  if (aPFlags) {
1531  int idx, nparam = 0;
1532  for (idx = 0; idx < 8; idx++) {
1533  int err, flag = cpl_array_get_int(aPFlags, idx, &err);
1534  if (err || flag) {
1535  continue;
1536  }
1537  pflags[idx] = 0; /* Flag it as frozen */
1538  nparam++;
1539  cpl_array_get_double(aParams, idx, &err);
1540  if (err) {
1541  return cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_INPUT,
1542  "Missing frozen value for parameter %d.", idx);
1543  }
1544  } /* for idx (all parameters) */
1545  /* Ensure that not all parameters are frozen */
1546  if (nparam == 8) {
1547  return cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_INPUT,
1548  "No free parameters");
1549  }
1550  } /* if aPFlags */
1551 
1552  /* Determine first-guess for gaussian parameters. Check if *
1553  * provided by caller - if not build own guesses... *
1554  * *
1555  * 0) Background level: if not given taken as median value within *
1556  * fitting domain. It can be negative... */
1557  int invalid;
1558  double bg = cpl_array_get_double(aParams, 0, &invalid);
1559  if (invalid) {
1560  bg = cpl_vector_get_median_const(aValues);
1561  }
1562 
1563  /* 1) Volume is really set later-on. Here is just a quick estimate, to know *
1564  * whether there is a peak or a hole. If it is flat, leave quickly... */
1565  double volguess = (cpl_vector_get_mean(aValues) - bg) * npoints;
1566  if (fabs(volguess) < FLT_EPSILON) {
1567  /* Data are flat: return a flat Moffat, with undefined center and widths */
1568  cpl_array_set_double(aParams, 0, bg);
1569  cpl_array_set_double(aParams, 1, 0.);
1570  cpl_array_set_invalid(aParams, 2);
1571  cpl_array_set_invalid(aParams, 3);
1572  cpl_array_set_invalid(aParams, 4);
1573  cpl_array_set_invalid(aParams, 5);
1574  cpl_array_set_double(aParams, 6, 1.); /* beta = 1 --> Moffat = 0 */
1575 #if 0
1576  printf("flat Moffat:\n");
1577  cpl_array_dump(aParams, 0, 10, stdout);
1578  fflush(stdout);
1579 #endif
1580  return CPL_ERROR_NONE;
1581  }
1582 
1583  /* 2), 3) Position of center. Compute it as the centroid if not given. *
1584  * This likely does not waste time, as this center is also used *
1585  * to determine the alpha parameters below. */
1586  double xcen, ycen;
1587  muse_utils_get_centroid(aPositions, aValues, aErrors, &xcen, &ycen,
1589  double xc = cpl_array_get_double(aParams, 2, &invalid);
1590  if (invalid) {
1591  xc = xcen;
1592  }
1593  double yc = cpl_array_get_double(aParams, 3, &invalid);
1594  if (invalid) {
1595  yc = ycen;
1596  }
1597 
1598  /* 6) Beta, seeing related Moffat parameter: if not given by the user, *
1599  * it is set to 2.5, which seems to be typical for stellar images. *
1600  * There does not seem to be a simple way to estimate it from the *
1601  * data, independently of the alphax,alphay parameters. */
1602  double beta = cpl_array_get_double(aParams, 6, &invalid);
1603  if (invalid) {
1604  beta = 2.5;
1605  }
1606 
1607  /* 4), 5) Widths: if neither alphax nor alphay are given by the caller the *
1608  * estimate for both is the radius of an approximate half-peak point *
1609  * from the peak point. Very rough, but accuracy is not an issue at *
1610  * this stage, just need a rough starting value. If only one width *
1611  * is given by the caller, the other one is set to the same value. */
1612  double alphay, alphax = cpl_array_get_double(aParams, 4, &invalid);
1613  if (invalid) {
1614  alphay = cpl_array_get_double(aParams, 5, &invalid);
1615  if (invalid) {
1616  double amplitude = 0.;
1617  if (volguess > 0.) {
1618  amplitude = cpl_vector_get_max(aValues) - bg;
1619  } else {
1620  amplitude = cpl_vector_get_min(aValues) - bg;
1621  }
1622  double halfpeak = amplitude / 2. + bg,
1623  limit = amplitude * 0.01; /* 1% accuracy for the start */
1624  const double *values = cpl_vector_get_data_const(aValues);
1625  cpl_vector *vradius = cpl_vector_new(1); /* store radius values */
1626  int i, nfound;
1627  do {
1628  nfound = 0;
1629  for (i = 0; i < npoints; i++) {
1630  if (values[i] > halfpeak - limit && values[i] < halfpeak + limit) {
1631  cpl_vector_set_size(vradius, nfound + 1);
1632  double radius = sqrt(pow(cpl_matrix_get(aPositions, i, 0) - xcen, 2)
1633  + pow(cpl_matrix_get(aPositions, i, 1) - ycen, 2));
1634 #if 0
1635  printf("radius(%d, %d) = %f\n", i, nfound+1, radius);
1636 #endif
1637  cpl_vector_set(vradius, nfound++, radius);
1638  }
1639  } /* for i (all values) */
1640  /* if we go through this loop again, we will find more points and *
1641  * so all previous vector entries will be completely overwritten */
1642  limit *= 2; /* be twice as tolerant to find the points next time */
1643 #if 0
1644  printf("found %d points (limit = %f)\n", nfound, limit / 2);
1645 #endif
1646  } while (nfound < 3 && isfinite(limit));
1647  if (!isfinite(limit)) {
1648  /* if the data is so weird that we don't find points within *
1649  * finite limit, then any alpha is a good first guess... */
1650  alphax = alphay = 1.;
1651  } else {
1652  /* the radius of found points from 1st-guess center is the *
1653  * HWHM, the alpha parameter of a Moffat function is then *
1654  * alpha = HWHM / sqrt(2^(1/beta) - 1) */
1655  alphax = alphay = cpl_vector_get_mean(vradius) / sqrt(pow(2., 1./beta)-1);
1656 #if 0
1657  printf("estimated alphas = %f from radius %f\n", alphax, cpl_vector_get_mean(vradius));
1658  fflush(stdout);
1659 #endif
1660  }
1661  cpl_vector_delete(vradius);
1662  } else {
1663  alphax = alphay;
1664  }
1665  } else {
1666  alphay = cpl_array_get_double(aParams, 5, &invalid);
1667  if (invalid) {
1668  alphay = alphax;
1669  }
1670  }
1671 
1672  /* 1) Volume. If not given by the user, it is derived *
1673  * from the max (min) value of the data distribution. */
1674  double volume = cpl_array_get_double(aParams, 1, &invalid);
1675  if (invalid) {
1676  /* The above seems to be a good enough first-guess. *
1677  * Deriving a better guess from the amplitude would *
1678  * require more solid guesses of the other parameters. */
1679  volume = volguess;
1680  }
1681 
1682  /* 7) Rho, x-y correlation parameter: if not given by the user, *
1683  * it is set to zero (no correlation, i.e. circular Moffat or *
1684  * one that is elongated along x or y). */
1685  double rho = cpl_array_get_double(aParams, 7, &invalid);
1686  if (invalid) {
1687  rho = 0.;
1688  }
1689 
1690  /* Yay! Now all parameters are set to initial values, and we can do the fit! */
1691  cpl_vector *params = cpl_vector_new(8);
1692  cpl_vector_set(params, 0, bg);
1693  cpl_vector_set(params, 1, volume);
1694  cpl_vector_set(params, 2, xc);
1695  cpl_vector_set(params, 3, yc);
1696  cpl_vector_set(params, 4, alphax);
1697  cpl_vector_set(params, 5, alphay);
1698  cpl_vector_set(params, 6, beta);
1699  cpl_vector_set(params, 7, rho);
1700 #if 0
1701  printf("initial guess values for Moffat (vol %e):\n", (cpl_vector_get_mean(aValues) - bg) * npoints);
1702  cpl_vector_dump(params, stdout);
1703  fflush(stdout);
1704 #endif
1705  cpl_matrix *covariance = NULL;
1706 
1707  cpl_error_code rc = CPL_ERROR_NONE;
1708 #if MOFFAT_USE_MUSE_OPTIMIZE
1709  fitdata_t fitdata;
1710  fitdata.positions = aPositions;
1711  fitdata.values = aValues;
1712  fitdata.errors = aErrors;
1713  cpl_array *optparams = cpl_array_wrap_double(cpl_vector_get_data(params), 8);
1714  rc = muse_cpl_optimize_lvmq(&fitdata, optparams, npoints,
1715  muse_moffat_2d_optfunc, NULL);
1716  cpl_array_unwrap(optparams);
1717 #else /* MOFFAT_USE_MUSE_OPTIMIZE follows */
1718  cpl_errorstate prestate = cpl_errorstate_get();
1719  rc = cpl_fit_lvmq(aPositions, NULL, aValues, aErrors, params, pflags,
1720  muse_moffat_2d_function, muse_moffat_2d_derivative,
1721  CPL_FIT_LVMQ_TOLERANCE, CPL_FIT_LVMQ_COUNT,
1722  CPL_FIT_LVMQ_MAXITER, aRMS,
1723  aErrors ? aRedChisq : NULL, aErrors ? &covariance : NULL);
1724 #endif
1725  if (aRMS) {
1726  *aRMS = sqrt(*aRMS);
1727  }
1728 
1729 #if 0
1730  printf("Moffat fit (rc=%d, %s):\n", rc, cpl_error_get_message());
1731  cpl_vector_dump(params, stdout);
1732  fflush(stdout);
1733 #endif
1734  if (rc == CPL_ERROR_NONE || rc == CPL_ERROR_SINGULAR_MATRIX ||
1735  rc == CPL_ERROR_CONTINUE) {
1736  /* The LM algorithm converged. The computation of the covariance *
1737  * matrix might have failed. All the above errors must be ignored *
1738  * because of ticket DFS06126. */
1739 
1740  /* check whether the result makes sense at all... (unlike CPL we use C99) */
1741  if (isfinite(cpl_vector_get(params, 0)) &&
1742  isfinite(cpl_vector_get(params, 1)) &&
1743  isfinite(cpl_vector_get(params, 2)) &&
1744  isfinite(cpl_vector_get(params, 3)) &&
1745  isfinite(cpl_vector_get(params, 4)) &&
1746  isfinite(cpl_vector_get(params, 5)) &&
1747  isfinite(cpl_vector_get(params, 6)) &&
1748  isfinite(cpl_vector_get(params, 7))) {
1749  /* Betting all errors are really to be ignored... */
1750  rc = CPL_ERROR_NONE;
1751  cpl_errorstate_set(prestate);
1752 
1753  /* Save best fit parameters: image coordinates, evaluations *
1754  * of widths are forced positive (they might be both negative *
1755  * -- it would generate the same profile). */
1756  cpl_array_set_double(aParams, 0, cpl_vector_get(params, 0));
1757  cpl_array_set_double(aParams, 1, cpl_vector_get(params, 1));
1758  cpl_array_set_double(aParams, 2, cpl_vector_get(params, 2));
1759  cpl_array_set_double(aParams, 3, cpl_vector_get(params, 3));
1760  cpl_array_set_double(aParams, 4, fabs(cpl_vector_get(params, 4)));
1761  cpl_array_set_double(aParams, 5, fabs(cpl_vector_get(params, 5)));
1762  cpl_array_set_double(aParams, 6, cpl_vector_get(params, 6));
1763  cpl_array_set_double(aParams, 7, cpl_vector_get(params, 7));
1764 
1765  if (aErrors && covariance) {
1766  int idx;
1767  for (idx = 0; idx < 8; idx++) {
1768  if (pflags[idx] && aPErrors) {
1769  cpl_array_set_double(aPErrors, idx,
1770  sqrt(cpl_matrix_get(covariance, idx, idx)));
1771  } /* if */
1772  } /* for idx (all parameters) */
1773  } /* if */
1774 
1775  /* we don't care about the physical parameters like CPL does, so this was it :-) */
1776  } /* if isfinite() */
1777  } /* if rc */
1778  cpl_matrix_delete(covariance);
1779  cpl_vector_delete(params);
1780 
1781  return rc;
1782 } /* muse_utils_fit_moffat_2d() */
1783 
1784 /*----------------------------------------------------------------------------*/
1827 /*----------------------------------------------------------------------------*/
1828 cpl_polynomial *
1829 muse_utils_iterate_fit_polynomial(cpl_matrix *aPos, cpl_vector *aVal,
1830  cpl_vector *aErr, cpl_table *aExtra,
1831  const unsigned int aOrder, const double aRSigma,
1832  double *aMSE, double *aChiSq)
1833 {
1834  /* pre-fill diagnistics to high values in case of errors */
1835  if (aMSE) {
1836  *aMSE = DBL_MAX;
1837  }
1838  if (aChiSq) {
1839  *aChiSq = DBL_MAX;
1840  }
1841  cpl_ensure(aPos && aVal, CPL_ERROR_NULL_INPUT, NULL);
1842  cpl_ensure(cpl_matrix_get_ncol(aPos) == cpl_vector_get_size(aVal),
1843  CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
1844  if (aErr) {
1845  cpl_ensure(cpl_vector_get_size(aVal) == cpl_vector_get_size(aErr),
1846  CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
1847  }
1848  if (aExtra) {
1849  cpl_ensure(cpl_vector_get_size(aVal) == cpl_table_get_nrow(aExtra),
1850  CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
1851  }
1852 
1853  /* XXX erase positions with NAN values upfront */
1854  int idx;
1855  for (idx = 0; idx < cpl_vector_get_size(aVal); idx++) {
1856  /* compare this residual value to the RMS value */
1857  if (isfinite(cpl_vector_get(aVal, idx))) {
1858  continue; /* want to keep all finite numbers */
1859  }
1860  /* guard against removing the last element */
1861  if (cpl_vector_get_size(aVal) == 1) {
1862  cpl_msg_warning(__func__, "Input vector only contained non-finite elements!");
1863  break;
1864  }
1865  /* remove bad element from the fit structures... */
1866  cpl_matrix_erase_columns(aPos, idx, 1);
1867  muse_cplvector_erase_element(aVal, idx);
1868  if (aErr) { /* check to not generate CPL error */
1869  muse_cplvector_erase_element(aErr, idx);
1870  }
1871  /* ...and from the input matrix, if it's there */
1872  if (aExtra) {
1873  cpl_table_erase_window(aExtra, idx, 1);
1874  }
1875  idx--; /* we stay at this position to see what moved here */
1876  } /* for idx */
1877 
1878  /* create the polynomial, using matrix rows to determine dimensions */
1879  int ndim = cpl_matrix_get_nrow(aPos);
1880  cpl_polynomial *fit = cpl_polynomial_new(ndim);
1881  int large_residuals = 1; /* init to force a first fit */
1882  while (large_residuals > 0) {
1883  const cpl_boolean sym = CPL_FALSE;
1884  cpl_size *mindeg = cpl_calloc(ndim, sizeof(cpl_size)),
1885  *maxdeg = cpl_malloc(ndim * sizeof(cpl_size));
1886  int i;
1887  for (i = 0; i < ndim; i++) {
1888  maxdeg[i] = aOrder;
1889  }
1890  cpl_error_code rc = cpl_polynomial_fit(fit, aPos, &sym, aVal, NULL,
1891  CPL_FALSE, mindeg, maxdeg);
1892  cpl_free(mindeg);
1893  cpl_free(maxdeg);
1894  const cpl_size coeff = 0;
1895  if (rc != CPL_ERROR_NONE || !isfinite(cpl_polynomial_get_coeff(fit, &coeff))) {
1896  /* don't try to recover from an error, instead clean up and return NULL */
1897  cpl_errorstate prestate = cpl_errorstate_get();
1898 #if 0
1899  printf("%s: output polynomial:\n", __func__);
1900  cpl_polynomial_dump(fit, stdout);
1901  printf("%s: positions and values that we tried to fit:\n", __func__);
1902  cpl_matrix_dump(aPos, stdout);
1903  cpl_vector_dump(aVal, stdout);
1904  fflush(stdout);
1905 #endif
1906  cpl_polynomial_delete(fit);
1907  /* make sure to expose the important error to the caller */
1908  if (!cpl_errorstate_is_equal(prestate)) {
1909  cpl_errorstate_set(prestate);
1910  }
1911  return NULL;
1912  }
1913 
1914  /* compute residuals and mean squared error */
1915  cpl_vector *res = cpl_vector_new(cpl_vector_get_size(aVal));
1916  cpl_vector_fill_polynomial_fit_residual(res, aVal, NULL, fit, aPos, aChiSq);
1917  double rms = sqrt(cpl_vector_product(res, res) / cpl_vector_get_size(res));
1918  if (rms == 0.) { /* otherwise everything will be rejected! */
1919  rms = DBL_MIN;
1920  }
1921 
1922 #if 0
1923  printf("%s: polynomial fit (RMS %g chisq %g aRSigma %f -> limit %g):\n",
1924  __func__, rms, aChiSq ? *aChiSq : 0., aRSigma, aRSigma * rms);
1925  cpl_polynomial_dump(fit, stdout);
1926  fflush(stdout);
1927  char *title = cpl_sprintf("set title \"%s: RMS %g\"\n"
1928  "unset key\n", __func__, rms);
1929  cpl_plot_vector(title, "", "", res);
1930  cpl_free(title);
1931 #endif
1932 
1933  large_residuals = 0;
1934  for (i = 0; i < cpl_vector_get_size(res); i++) {
1935  /* compare this residual value to the RMS value */
1936  if (fabs(cpl_vector_get(res, i)) < (aRSigma * rms)) {
1937  /* good fit at this point */
1938  continue;
1939  }
1940 
1941  /* bad residual, remove element, from residuals vector and the data vectors */
1942 #if 0
1943  cpl_msg_debug(__func__, "residual %f RMS %f aRSigma %f -> limit %f",
1944  cpl_vector_get(res, i), rms, aRSigma, aRSigma * rms);
1945 #endif
1946  /* guard against removing the last element */
1947  if (cpl_vector_get_size(res) == 1) {
1948  cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_OUTPUT, "tried to "
1949  "remove the last vector/matrix element when "
1950  "checking fit residuals (residual %g RMS %g "
1951  "aRSigma %f -> limit %g)", cpl_vector_get(res, i),
1952  rms, aRSigma, aRSigma * rms);
1953  cpl_polynomial_delete(fit);
1954  fit = NULL;
1955  rms = sqrt(DBL_MAX); /* so that aMSE gets to be DBL_MAX below */
1956  if (aChiSq) {
1957  *aChiSq = DBL_MAX;
1958  }
1959  large_residuals = 0; /* don't try to fit again */
1960  break;
1961  }
1962  /* remove bad element from the fit structures... */
1964  cpl_matrix_erase_columns(aPos, i, 1);
1966  if (aErr) { /* check to not generate CPL error */
1968  }
1969  /* ...and from the input matrix, if it's there */
1970  if (aExtra) {
1971  cpl_table_erase_window(aExtra, i, 1);
1972  }
1973  large_residuals++;
1974  i--; /* we stay at this position to see what moved here */
1975  } /* for i */
1976  cpl_vector_delete(res);
1977  if (aMSE) {
1978  *aMSE = rms * rms;
1979  }
1980  } /* while large_residuals */
1981 
1982  return fit;
1983 } /* muse_utils_iterate_fit_polynomial() */
1984 
1985 /*----------------------------------------------------------------------------*/
2031 /*----------------------------------------------------------------------------*/
2032 double
2034  double aLambda, double aHalfWidth,
2035  double aBinSize,
2036  cpl_array *aResults, cpl_array *aErrors)
2037 {
2038  cpl_ensure(aPixtable, CPL_ERROR_NULL_INPUT, 0.);
2039 
2040  /* select (only) the relevant part of the pixel table */
2041  cpl_table_unselect_all(aPixtable->table);
2042  cpl_table_or_selected_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA,
2043  CPL_NOT_LESS_THAN, aLambda - aHalfWidth);
2044  cpl_table_and_selected_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA,
2045  CPL_NOT_GREATER_THAN, aLambda + aHalfWidth);
2046  cpl_size nsel = cpl_table_count_selected(aPixtable->table);
2047  cpl_ensure(nsel > 0, CPL_ERROR_DATA_NOT_FOUND, 0.);
2048 
2049  /* now resample all the selected pixels into a spectrum *
2050  * with hopefully high resolution given by aBinSize */
2051  cpl_errorstate state = cpl_errorstate_get();
2052  cpl_table *spec = muse_resampling_spectrum(aPixtable, aBinSize);
2053  cpl_table_unselect_all(aPixtable->table);
2054  if (!cpl_errorstate_is_equal(state)) {
2055  /* something went wrong with the creation of the spectrum */
2056  cpl_table_delete(spec);
2057  cpl_error_set(__func__, cpl_error_get_code());
2058  return 0.;
2059  }
2060 
2061  /* check that the spectrum is contiguous */
2062  cpl_size nbins = cpl_table_get_nrow(spec);
2063  /* turn the stat column (in sigma^2) into an error column (in sigma) */
2064  cpl_table_power_column(spec, "stat", 0.5); /* sqrt() */
2065  cpl_table_name_column(spec, "stat", "error");
2066  cpl_table_set_column_unit(spec, "error",
2067  cpl_table_get_column_unit(spec, "data"));
2068 #if 0
2069  cpl_table_save(spec, NULL, NULL, "spec.fits", CPL_IO_CREATE);
2070 #endif
2071  /* wrap table columns into vectors for the Gaussian fit */
2072  cpl_vector *pos = cpl_vector_wrap(nbins, cpl_table_get_data_double(spec, "lambda")),
2073  *val = cpl_vector_wrap(nbins, cpl_table_get_data_double(spec, "data")),
2074  *err = cpl_vector_wrap(nbins, cpl_table_get_data_double(spec, "error"));
2075  double xc, xerr = 2 * aHalfWidth, /* default error as large as the window */
2076  sigma, area, bglevel, mse;
2077  cpl_matrix *covariance;
2078  cpl_error_code rc = cpl_vector_fit_gaussian(pos, NULL, val, err, CPL_FIT_ALL,
2079  &xc, &sigma, &area, &bglevel,
2080  &mse, NULL, &covariance);
2081  cpl_vector_unwrap(pos);
2082  cpl_vector_unwrap(val);
2083  cpl_vector_unwrap(err);
2084  cpl_table_delete(spec);
2085  if (rc == CPL_ERROR_CONTINUE) { /* fit didn't converge */
2086  /* estimate position error as sigma^2/area as CPL docs suggest */
2087  xerr = sqrt(sigma * sigma / area);
2088  } else if (rc == CPL_ERROR_SINGULAR_MATRIX || !covariance) {
2089  xerr = sqrt(sigma * sigma / area);
2090  } else {
2091  xerr = sqrt(cpl_matrix_get(covariance, 0, 0));
2092 #if 0
2093  cpl_msg_debug(__func__, "covariance matrix:");
2094  cpl_matrix_dump(covariance, stdout);
2095  fflush(stdout);
2096 #endif
2097  }
2098  if (aResults && cpl_array_get_type(aResults) == CPL_TYPE_DOUBLE) {
2099  cpl_array_set_size(aResults, 4);
2100  cpl_array_set_double(aResults, 0, xc);
2101  cpl_array_set_double(aResults, 1, sigma);
2102  cpl_array_set_double(aResults, 2, area);
2103  cpl_array_set_double(aResults, 3, bglevel);
2104  } /* valid aResults array */
2105  if (aErrors && cpl_array_get_type(aErrors) == CPL_TYPE_DOUBLE) {
2106  cpl_array_set_size(aErrors, 4);
2107  cpl_array_set_double(aErrors, 0, xerr);
2108  if (rc != CPL_ERROR_NONE || !covariance) {
2109  cpl_array_fill_window_invalid(aErrors, 1, 3);
2110  } else {
2111  cpl_array_set_double(aErrors, 1, sqrt(cpl_matrix_get(covariance, 1, 1)));
2112  cpl_array_set_double(aErrors, 2, sqrt(cpl_matrix_get(covariance, 2, 2)));
2113  cpl_array_set_double(aErrors, 3, sqrt(cpl_matrix_get(covariance, 3, 3)));
2114  } /* else */
2115  } /* valid aErrors array */
2116  cpl_matrix_delete(covariance);
2117  cpl_msg_debug(__func__, "Gaussian fit (%s): %f +/- %f Angstrom, %f, %f, %f "
2118  "(RMS %f)", rc != CPL_ERROR_NONE ? cpl_error_get_message() : "",
2119  xc, xerr, bglevel, area, sigma, sqrt(mse));
2120  return xc;
2121 } /* muse_utils_pixtable_fit_line_gaussian() */
2122 
2123 /*----------------------------------------------------------------------------*/
2138 /*----------------------------------------------------------------------------*/
2139 cpl_error_code
2140 muse_utils_copy_modified_header(cpl_propertylist *aH1, cpl_propertylist *aH2,
2141  const char *aKeyword, const char *aString)
2142 {
2143  cpl_ensure_code(aH1 && aH2 && aKeyword && aString, CPL_ERROR_NULL_INPUT);
2144  const char *hin = cpl_propertylist_get_string(aH1, aKeyword);
2145  cpl_ensure_code(hin, CPL_ERROR_ILLEGAL_INPUT);
2146  char *hstring = cpl_sprintf("%s (%s)", hin, aString);
2147  cpl_error_code rc = cpl_propertylist_update_string(aH2, aKeyword, hstring);
2148  cpl_free(hstring);
2149 
2150  return rc;
2151 } /* muse_utils_copy_modified_header() */
2152 
2153 /*---------------------------------------------------------------------------*/
2179 /*---------------------------------------------------------------------------*/
2180 cpl_error_code
2181 muse_utils_set_hduclass(cpl_propertylist *aHeader, const char *aClass2,
2182  const char *aExtData, const char *aExtDQ,
2183  const char *aExtStat)
2184 {
2185  cpl_ensure_code(aHeader && aClass2 && aExtData, CPL_ERROR_NULL_INPUT);
2186  cpl_ensure_code(!strcmp(aClass2, "DATA") || !strcmp(aClass2, "ERROR") ||
2187  !strcmp(aClass2, "QUALITY"), CPL_ERROR_ILLEGAL_INPUT);
2188 
2189  /* first clean up existing entries */
2190 #define ESO_HDU_HEADERS_REGEXP "HDU(CLASS|CLAS1|CLAS2|CLAS3|DOC|VERS)$" \
2191  "|^SCIDATA$|^QUAL(DATA|MASK)$|^ERRDATA$"
2192  cpl_propertylist_erase_regexp(aHeader, ESO_HDU_HEADERS_REGEXP, 0);
2193 
2194  if (cpl_propertylist_has(aHeader, "EXTNAME")) {
2195  cpl_propertylist_insert_after_string(aHeader, "EXTNAME", "HDUCLASS", "ESO");
2196  } else {
2197  cpl_propertylist_append_string(aHeader, "HDUCLASS", "ESO");
2198  }
2199  cpl_propertylist_set_comment(aHeader, "HDUCLASS", "class name (ESO format)");
2200  cpl_propertylist_insert_after_string(aHeader, "HDUCLASS", "HDUDOC", "DICD");
2201  cpl_propertylist_set_comment(aHeader, "HDUDOC", "document with class description");
2202  cpl_propertylist_insert_after_string(aHeader, "HDUDOC", "HDUVERS",
2203  "DICD version 6");
2204  cpl_propertylist_set_comment(aHeader, "HDUVERS",
2205  "version number (according to spec v2.5.1)");
2206  cpl_propertylist_insert_after_string(aHeader, "HDUVERS", "HDUCLAS1", "IMAGE");
2207  cpl_propertylist_set_comment(aHeader, "HDUCLAS1", "Image data format");
2208  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS1", "HDUCLAS2", aClass2);
2209  if (!strcmp(aClass2, "DATA")) { /* this is the STAT / variance extension */
2210  cpl_propertylist_set_comment(aHeader, "HDUCLAS2",
2211  "this extension contains the data itself");
2212  /* no HDUCLAS3 for the data itself */
2213  if (aExtDQ) {
2214  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS2", "QUALDATA", aExtDQ);
2215  }
2216  if (aExtStat) { /* do this second, so that it goes first, if given */
2217  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS2", "ERRDATA", aExtStat);
2218  }
2219  } else if (!strcmp(aClass2, "ERROR")) { /* this is the STAT / variance extension */
2220  cpl_propertylist_set_comment(aHeader, "HDUCLAS2", "this extension contains variance");
2221  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS2", "HDUCLAS3", "MSE");
2222  cpl_propertylist_set_comment(aHeader, "HDUCLAS3",
2223  "the extension contains variances (sigma**2)");
2224  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS3", "SCIDATA", aExtData);
2225  if (aExtDQ) {
2226  cpl_propertylist_insert_after_string(aHeader, "SCIDATA", "QUALDATA", aExtDQ);
2227  }
2228  } else { /* "QUALITY", this is the DQ / bad pixel extension */
2229  cpl_propertylist_set_comment(aHeader, "HDUCLAS2",
2230  "this extension contains bad pixel mask");
2231  cpl_propertylist_insert_after_string(aHeader, "HDUCLAS2", "HDUCLAS3", "FLAG32BIT");
2232  cpl_propertylist_set_comment(aHeader, "HDUCLAS3", "extension contains 32bit"
2233  " Euro3D bad pixel information");
2234  /* all non-zero values in the bad pixel mask are "bad" */
2235  cpl_propertylist_insert_after_long(aHeader, "HDUCLAS3", "QUALMASK", 0xFFFFFFFF);
2236  cpl_propertylist_set_comment(aHeader, "QUALMASK", "all non-zero values are bad");
2237  cpl_propertylist_insert_after_string(aHeader, "QUALMASK", "SCIDATA", aExtData);
2238  if (aExtStat) {
2239  cpl_propertylist_insert_after_string(aHeader, "SCIDATA", "ERRDATA", aExtStat);
2240  }
2241  }
2242 
2243  if (cpl_propertylist_has(aHeader, "SCIDATA")) {
2244  cpl_propertylist_set_comment(aHeader, "SCIDATA", "pointer to the data extension");
2245  }
2246  if (cpl_propertylist_has(aHeader, "ERRDATA")) {
2247  cpl_propertylist_set_comment(aHeader, "ERRDATA", "pointer to the variance extension");
2248  }
2249  if (cpl_propertylist_has(aHeader, "QUALDATA")) {
2250  cpl_propertylist_set_comment(aHeader, "QUALDATA",
2251  "pointer to the bad pixel mask extension");
2252  }
2253 
2254  return CPL_ERROR_NONE;
2255 } /* muse_utils_set_hduclass() */
2256 
2257 /*----------------------------------------------------------------------------*/
2270 /*----------------------------------------------------------------------------*/
2271 void
2272 muse_utils_memory_dump(const char *aMarker)
2273 {
2274  char *exe = getenv("MUSE_DEBUG_MEMORY_PROGRAM");
2275  if (!exe) {
2276  return;
2277  }
2278 
2279  printf("=== %s ===\n", aMarker);
2280  fflush(stdout);
2281  char command[1000];
2282  if (strlen(exe) > 0) {
2283  snprintf(command, 999,
2284  "ps -C %s -o comm,start_time,pid,tid,pcpu,stat,rss,size,vsize",
2285  exe);
2286  } else {
2287  /* no program name given, generic output */
2288  snprintf(command, 999,
2289  "ps -o comm,start_time,pid,tid,pcpu,stat,rss,size,vsize");
2290  }
2291  cpl_memory_dump();
2292  fflush(stderr);
2293  int rc = system(command);
2294  UNUSED_ARGUMENT(rc); /* new glibc mandates use of system() return code */
2295 } /* muse_utils_memory_dump() */
2296 
const muse_cpltable_def muse_filtertable_def[]
MUSE filter table definition.
Definition: muse_utils.c:554
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:296
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:115
unsigned char muse_utils_get_ifu(const cpl_propertylist *aHeaders)
Find out the IFU/channel from which this header originated.
Definition: muse_utils.c:95
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:2033
int muse_pfits_get_shut_status(const cpl_propertylist *aHeaders, int aShutter)
query the status of one shutter
Definition: muse_pfits.c:1199
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:2272
const char * muse_pfits_get_dateobs(const cpl_propertylist *aHeaders)
find out the exposure time
Definition: muse_pfits.c:260
cpl_frameset * muse_frameset_check_raw(const cpl_frameset *aFrames, const char *aTag, unsigned char aIFU)
return frameset containing good raw input data
Definition: muse_utils.c:232
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:1113
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:2181
cpl_table * muse_table_load_filter(muse_processing *aProcessing, const char *aFilterName)
Load a table for a given filter name.
Definition: muse_utils.c:585
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:80
int muse_pfits_get_biny(const cpl_propertylist *aHeaders)
find out the binning factor in y direction
Definition: muse_pfits.c:350
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:1136
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:1473
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:1829
const char * muse_pfits_get_read_name(const cpl_propertylist *aHeaders)
find out the readout mode name
Definition: muse_pfits.c:314
cpl_frameset * muse_frameset_sort_raw_other(const cpl_frameset *aFrames, int aIndex, const char *aDateObs, cpl_boolean aSequence)
Create a new frameset containing all relevant raw frames first then all other frames.
Definition: muse_utils.c:356
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:1153
char * muse_utils_header_get_lamp_names(cpl_propertylist *aHeader, char aSep)
Concatenate names of all active calibration lamps.
Definition: muse_utils.c:665
const char * muse_pfits_get_chip_id(const cpl_propertylist *aHeaders)
find out the chip id
Definition: muse_pfits.c:386
int muse_pfits_get_binx(const cpl_propertylist *aHeaders)
find out the binning factor in x direction
Definition: muse_pfits.c:332
cpl_array * muse_utils_header_get_lamp_numbers(cpl_propertylist *aHeader)
List numbers of all active calibration lamps.
Definition: muse_utils.c:732
int muse_pfits_get_lamp_status(const cpl_propertylist *aHeaders, int aLamp)
query the status of one lamp
Definition: muse_pfits.c:1157
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:452
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:829
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:520
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:775
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_find(const cpl_frameset *aFrames, const char *aTag, unsigned char aIFU, cpl_boolean aInvert)
return frameset containing data from an IFU/channel with a certain tag
Definition: muse_utils.c:154
cpl_frameset * inputFrames
const char * muse_pfits_get_chip_name(const cpl_propertylist *aHeaders)
find out the chip name
Definition: muse_pfits.c:368
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:425
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:2140
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:923