MUSE Pipeline Reference Manual  2.1.1
muse_ao_perf.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) 2015 European Southern Observatory
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 /*---------------------------------------------------------------------------*
27  * Includes *
28  *---------------------------------------------------------------------------*/
29 #include <unistd.h>
30 #include <getopt.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <math.h>
34 #include <cpl.h>
35 #include <muse.h>
36 
37 
38 #define MUSE_TAG_IMAGE_AO "IMAGE_AO"
39 #define MUSE_EXT_IMAGE_AO "AO_IMAGE"
40 
41 // Unique detector chip name of the AO camera
42 
43 #define AO_CAMERA_CHIP_ID "CCD47-20I"
44 
45 
46 // AO camera pixel scale: conversion factor pixel to arcsec
47 
48 static const double AO_CAMERA_PIXEL_SCALE = 0.0833; // [arcsec / pixel]
49 
50 
51 // Conversion factor Gaussian sigma to FWHM: 2. * sqrt(2. * log(2.))
52 
53 static const double SIGMA_TO_FWHM = 2.3548200450309493;
54 
55 
56 struct muse_ao_bkg
57 {
58  double mean;
59  double sdev;
60  double median;
61 };
62 
63 typedef struct muse_ao_bkg muse_ao_bkg;
64 
65 struct muse_ao_ee
66 {
67  cpl_array *radius;
68  cpl_array *ee;
69 };
70 
71 typedef struct muse_ao_ee muse_ao_ee;
72 
73 
74 // Supported image formats
75 enum {
76  MUSE_AO_IMAGE_AOC = 0, // Stand-alone AO camera image
77  MUSE_AO_IMAGE_ATT = 1, // AO camera image attached to a MUSE observation
78  MUSE_AO_IMAGE_FOV = 2 // Pipeline processed FOV image
79 };
80 
81 
82 /*---------------------------------------------------------------------------*
83  * Functions code *
84  *---------------------------------------------------------------------------*/
85 
86 inline static void
87 usage(const char *name)
88 {
89  fprintf(stderr, "Usage: %s [-hs] [-T threshold] -F fwhm -S scale "
90  "[-G gap] [-K kappa] [-B -m offset -M fraction] inImage outTable\n",
91  name);
92 }
93 
94 
95 inline static cpl_error_code
96 muse_ao_replace_rejected(cpl_image *aImage, double aValue)
97 {
98  cpl_ensure(aImage, CPL_ERROR_NULL_INPUT, CPL_ERROR_NULL_INPUT);
99 
100  cpl_size nx = cpl_image_get_size_x(aImage);
101  cpl_size ny = cpl_image_get_size_y(aImage);
102 
103  cpl_size iy;
104  for (iy = 0; iy < ny; ++iy) {
105  cpl_size ix;
106  for (ix = 0; ix < nx; ++ix) {
107  if (cpl_image_is_rejected(aImage, ix + 1, iy + 1) == 1) {
108  cpl_image_set(aImage, ix + 1, iy + 1, aValue);
109  }
110  }
111  }
112 
113  return CPL_ERROR_NONE;
114 }
115 
116 
117 /*---------------------------------------------------------------------------*/
129 /*---------------------------------------------------------------------------*/
130 inline static int
131 muse_moffat_2d_function(const double xy[], const double p[], double *f)
132 {
133  double xterm = (xy[0] - p[2]) / p[4]; /* xy[0] is x */
134  double yterm = (xy[1] - p[3]) / p[5]; /* xy[1] is y */
135  double crossterm = 2 * p[7] * xterm * yterm;
136  double rhoterm = 1. - p[7]*p[7];
137  double base = 1. + (xterm*xterm + crossterm + yterm*yterm) / rhoterm;
138 
139  *f = p[0] + p[1] * (p[6] - 1.) / (CPL_MATH_PI * p[4]*p[5] * sqrt(rhoterm))
140  * pow(base, -p[6]);
141 
142  return 0;
143 } /* muse_moffat_2d_function() */
144 
145 
146 inline static cpl_error_code
147 muse_ao_estimate_bkg(muse_ao_bkg *bkg, const cpl_image *aImage,
148  double x, double y, double inner, double outer)
149 {
150  cpl_size rmax = (cpl_size)ceil(0.5 * outer);
151  cpl_size rmin = (cpl_size)ceil(0.5 * inner);
152  cpl_size xs = (cpl_size)floor(x);
153  cpl_size ys = (cpl_size)floor(y);
154 
155  cpl_size xl = xs - rmax;
156  cpl_size yl = ys - rmax;
157  cpl_size xu = xs + rmax;
158  cpl_size yu = ys + rmax;
159 
160  cpl_size nx = cpl_image_get_size_x(aImage);
161  cpl_size ny = cpl_image_get_size_y(aImage);
162 
163 
164  cpl_ensure((outer > inner), CPL_ERROR_ILLEGAL_INPUT, CPL_ERROR_ILLEGAL_INPUT);
165 
166  if ((xl < 0) || (xu >= nx)) {
167  cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
168  "Box is out of image bounds along X");
169  return CPL_ERROR_INCOMPATIBLE_INPUT;
170  }
171 
172  if ((yl < 0) || (yu >= ny)) {
173  cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
174  "Box is out of image bounds along Y");
175  return CPL_ERROR_INCOMPATIBLE_INPUT;
176  }
177 
178  cpl_msg_debug(cpl_func, "box position: lower left at (%" CPL_SIZE_FORMAT
179  ", %" CPL_SIZE_FORMAT "), upper right at (%" CPL_SIZE_FORMAT ", %"
180  CPL_SIZE_FORMAT ")", xl, yl, xu, yu);
181 
182  const double *image = cpl_image_get_data_double_const(aImage);
183 
184  cpl_size nbkg = (2 * rmax + 1) * (2 * rmax + 1) - (2 * rmin + 1) * (2 * rmin + 1);
185  cpl_array *_bkg = cpl_array_new(nbkg, CPL_TYPE_DOUBLE);
186 
187  //cpl_mask *mask = cpl_mask_new(nx, ny);
188 
189  cpl_size ix;
190  cpl_size iy;
191  cpl_size idx = 0;
192 
193  for (iy = yl; iy <= yu; ++iy) {
194  for (ix = xl; ix <= xu; ++ix) {
195  if ((labs(ix - xs) > rmin) || (labs(iy - ys) > rmin)) {
196  cpl_array_set_double(_bkg, idx, image[iy * nx + ix]);
197  ++idx;
198  //cpl_mask_set(mask, ix + 1, iy + 1, CPL_BINARY_1);
199  }
200  }
201  }
202 
203  //cpl_mask_save(mask, "bkgmask.fits", 0, CPL_IO_CREATE);
204  //cpl_mask_delete(mask);
205 
206  bkg->mean = cpl_array_get_mean(_bkg);
207  bkg->sdev = cpl_array_get_stdev(_bkg);
208  bkg->median = cpl_array_get_median(_bkg);
209 
210  return CPL_ERROR_NONE;
211 }
212 
213 
214 /*----------------------------------------------------------------------------*/
222 /*----------------------------------------------------------------------------*/
223 
224 static cpl_table *
225 muse_ao_calculate_ee_sum(const cpl_image *aImage,
226  double x, double y, double size, double bkg)
227 {
228  cpl_size radius = (cpl_size)ceil(0.5 * size);
229  cpl_size xs = (cpl_size)floor(x);
230  cpl_size ys = (cpl_size)floor(y);
231 
232  cpl_size xl = xs - radius;
233  cpl_size yl = ys - radius;
234  cpl_size xu = xs + radius;
235  cpl_size yu = ys + radius;
236 
237  cpl_size nx = cpl_image_get_size_x(aImage);
238  cpl_size ny = cpl_image_get_size_y(aImage);
239 
240  if ((xl < 0) || (xu >= nx)) {
241  cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
242  "Box is out of image bounds along X");
243  return NULL;
244  }
245 
246  if ((yl < 0) || (yu >= ny)) {
247  cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
248  "Box is out of image bounds along Y");
249  return NULL;
250  }
251 
252  cpl_msg_debug(cpl_func, "source position at (%" CPL_SIZE_FORMAT ", %"
253  CPL_SIZE_FORMAT ")", xs, ys);
254  cpl_msg_debug(cpl_func, "box size: %" CPL_SIZE_FORMAT ", box position: "
255  "lower left at (%" CPL_SIZE_FORMAT ", %" CPL_SIZE_FORMAT "), upper "
256  "right at (%" CPL_SIZE_FORMAT ", %" CPL_SIZE_FORMAT ")",
257  2 *radius + 1, xl, yl, xu, yu);
258 
259  cpl_size nring = radius + 1;
260  cpl_table *ee = cpl_table_new(nring + 1);
261 
262  cpl_table_new_column(ee, "Radius", CPL_TYPE_DOUBLE);
263  cpl_table_new_column(ee, "EE", CPL_TYPE_DOUBLE);
264 
265  const double *image = cpl_image_get_data_double_const(aImage);
266 
267  cpl_table_set_double(ee, "Radius", 0, 0.);
268  cpl_table_set_double(ee, "EE", 0, 0.);
269 
270  for (cpl_size iring = 0; iring < nring; ++iring) {
271 
272  xl = xs - iring;
273  yl = ys - iring;
274  xu = xs + iring;
275  yu = ys + iring;
276 
277  cpl_size ix;
278  cpl_size iy;
279 
280  double esum = 0.;
281 
282  for (iy = yl; iy <= yu; ++iy) {
283  for (ix = xl; ix <= xu; ++ix) {
284  cpl_size idx = iy * nx + ix;
285  double epix = image[idx] - bkg;
286  esum += epix;
287  //esum += ((epix < 0.) ? 0. : epix);
288  }
289  }
290 
291  cpl_table_set_double(ee, "Radius", iring + 1, 0.5 + (double)iring);
292  cpl_table_set_double(ee, "EE", iring + 1, esum);
293 
294  }
295 
296  return ee;
297 
298 }
299 
300 
301 static cpl_array *
302 muse_ao_calculate_ee_psf(const cpl_image *aImage, double x, double y, double size, double bkg)
303 {
304  cpl_size radius = (cpl_size)ceil(0.5 * size);
305  cpl_size xs = (cpl_size)floor(x);
306  cpl_size ys = (cpl_size)floor(y);
307 
308  cpl_size xl = xs - radius;
309  cpl_size yl = ys - radius;
310  cpl_size xu = xs + radius;
311  cpl_size yu = ys + radius;
312 
313  cpl_size nx = cpl_image_get_size_x(aImage);
314  cpl_size ny = cpl_image_get_size_y(aImage);
315 
316  if ((xl < 0) || (xu >= nx)) {
317  cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
318  "Box is out of image bounds along X");
319  return NULL;
320  }
321 
322  if ((yl < 0) || (yu >= ny)) {
323  cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
324  "Box is out of image bounds along Y");
325  return NULL;
326  }
327 
328  cpl_msg_debug(cpl_func, "source position at (%" CPL_SIZE_FORMAT ", %"
329  CPL_SIZE_FORMAT ")", xs, ys);
330  cpl_msg_debug(cpl_func, "box size: %" CPL_SIZE_FORMAT ", box position: "
331  "lower left at (%" CPL_SIZE_FORMAT ", %" CPL_SIZE_FORMAT "), upper "
332  "right at (%" CPL_SIZE_FORMAT ", %" CPL_SIZE_FORMAT ")",
333  2 *radius + 1, xl, yl, xu, yu);
334 
335 
336  // Get FWHM estimate of the pixel value distribution at the source location
337 
338  double xfwhm = 0.;
339  double yfwhm = 0.;
340 
341  cpl_error_code status = cpl_image_get_fwhm(aImage, xs + 1, ys + 1,
342  &xfwhm, &yfwhm);
343 
344  if (status != CPL_ERROR_NONE) {
345  cpl_msg_error(cpl_func, "Cannot determine FWHM for source at "
346  "(%.6f, %.6f)", x, y);
347  return NULL;
348  }
349 
350  cpl_msg_info(cpl_func, "Measured FWHM for source at (%.6f, %.6f): "
351  "%.6f, %.6f", x, y, xfwhm, yfwhm);
352 
353  cpl_size npixel = (xu - xl + 1) * (yu - yl + 1);
354  cpl_matrix *positions = cpl_matrix_new(npixel, 2);
355  cpl_vector *flux = cpl_vector_new(npixel);
356  cpl_vector *rms = cpl_vector_new(npixel);
357 
358  cpl_size ndata = 0;
359  for (cpl_size ix = xl; ix <= xu; ++ix) {
360  for (cpl_size iy = yl; iy <= yu; ++iy) {
361  int rejected = 0;
362  double value = cpl_image_get(aImage, ix, iy, &rejected);
363 
364  if (rejected) {
365  continue;
366  }
367  cpl_matrix_set(positions, ndata, 0, ix);
368  cpl_matrix_set(positions, ndata, 1, iy);
369  cpl_vector_set(flux, ndata, value);
370 
371  // TODO: Use real variances, i.e. if available the ones attached to
372  // the image data! For now just use N = sqrt(S) here.
373  cpl_vector_set(rms, ndata, sqrt(value));
374  ++ndata;
375  }
376  }
377 
378  // Resize data buffers to the actual number of valid data points
379  cpl_matrix_set_size(positions, ndata, 2);
380  cpl_vector_set_size(flux, ndata);
381  cpl_vector_set_size(rms, ndata);
382 
383  // Setup initial profile parameters for the subsequent fit
384  const double moffatBeta = 2.5;
385  const double moffatAlphaToFwhm = 1. / (2. * sqrt(pow(2., 1. / moffatBeta) - 1.));
386 
387  cpl_array *profile = cpl_array_new(8, CPL_TYPE_DOUBLE);
388  cpl_array *perror = cpl_array_new(8, CPL_TYPE_DOUBLE);
389  cpl_array *pflags = cpl_array_new(8, CPL_TYPE_INT);
390 
391  // First pass: Only fit the source position
392  cpl_array_fill_window(pflags, 0, 8, 0.);
393  cpl_array_set(pflags, 2, 1.);
394  cpl_array_set(pflags, 3, 1.);
395 
396  cpl_array_set(profile, 0, bkg);
397  cpl_array_set(profile, 1, cpl_vector_get_sum(flux));
398  cpl_array_set(profile, 2, xs + 0.5);
399  cpl_array_set(profile, 3, ys + 0.5);
400  cpl_array_set(profile, 4, xfwhm * moffatAlphaToFwhm);
401  cpl_array_set(profile, 5, yfwhm * moffatAlphaToFwhm);
402  cpl_array_set(profile, 6, moffatBeta);
403  cpl_array_set(profile, 7, 0.);
404 
405  double fitRms = -1.;
406  double fitChiSqr = -1.;
407 
408  muse_utils_fit_moffat_2d(positions, flux, rms, profile, perror, pflags,
409  &fitRms, &fitChiSqr);
410 
411  if (cpl_error_get_code() != CPL_ERROR_NONE) {
412  cpl_array_delete(pflags);
413  cpl_array_delete(perror);
414  cpl_array_delete(profile);
415 
416  cpl_vector_delete(rms);
417  cpl_vector_delete(flux);
418  cpl_matrix_delete(positions);
419 
420  cpl_msg_error(cpl_func, "Fitting Moffat profile (location) to source "
421  "at (%.6f, %.6f) failed!", x, y);
422 
423  return NULL;
424  }
425 
426  double xcenter = cpl_array_get(profile, 2, NULL);
427  double ycenter = cpl_array_get(profile, 3, NULL);
428  double xerror = cpl_array_get(perror, 2, NULL);
429  double yerror = cpl_array_get(perror, 3, NULL);
430 
431  cpl_msg_info(cpl_func, "Fitted source position (%.6f, %.6f) +/- "
432  "(%.6f, %.6f), RMS = %.6f, chi-square = %.6f", xcenter, ycenter,
433  xerror, yerror, fitRms, fitChiSqr);
434 
435  // Second pass: Fit background and profile width, and beta
436  cpl_array_fill_window(pflags, 0, 8, 0.);
437  cpl_array_set(pflags, 0, 1.);
438  cpl_array_set(pflags, 4, 1.);
439  cpl_array_set(pflags, 5, 1.);
440  cpl_array_set(pflags, 6, 1.);
441 
442  fitRms = -1;
443  fitChiSqr = -1;
444 
445  muse_utils_fit_moffat_2d(positions, flux, rms, profile, perror, pflags,
446  &fitRms, &fitChiSqr);
447 
448  if (cpl_error_get_code() != CPL_ERROR_NONE) {
449  cpl_array_delete(pflags);
450  cpl_array_delete(perror);
451  cpl_array_delete(profile);
452 
453  cpl_vector_delete(rms);
454  cpl_vector_delete(flux);
455  cpl_matrix_delete(positions);
456 
457  cpl_msg_error(cpl_func, "Fitting Moffat profile (bkg, width, beta) to "
458  "source at (%.6f, %.6f) +/- (%.6f, %.6f) failed!", xcenter, ycenter,
459  xerror, yerror);
460 
461  return NULL;
462  }
463 
464  double xwidth = cpl_array_get(profile, 4, NULL) / moffatAlphaToFwhm;
465  double ywidth = cpl_array_get(profile, 5, NULL) / moffatAlphaToFwhm;
466  double xwerror = cpl_array_get(perror, 4, NULL) / moffatAlphaToFwhm;
467  double ywerror = cpl_array_get(perror, 5, NULL) / moffatAlphaToFwhm;
468 
469  cpl_msg_info(cpl_func, "Profile fit parameters: bkg = %.6f +/- %.6f, "
470  "width = (%.6f, %.6f) +/- (%.6f, %.6f), beta = %.6f +/- %.6f,"
471  "RMS = %.6f, chi-square = %.6f",
472  cpl_array_get(profile, 0, NULL), cpl_array_get(perror, 0, NULL),
473  xwidth, ywidth, xwerror, ywerror,
474  cpl_array_get(profile, 6, NULL), cpl_array_get(perror, 6, NULL),
475  fitRms, fitChiSqr);
476 
477  cpl_array_delete(pflags);
478  cpl_array_delete(perror);
479  cpl_array_delete(profile);
480 
481  cpl_vector_delete(rms);
482  cpl_vector_delete(flux);
483  cpl_matrix_delete(positions);
484 
485  return NULL;
486 }
487 
488 
489 
490 int main(int argc, char *argv[])
491 {
492 
493  const char *const progname = "muse_ao_perf";
494 
495  // Parameters
496 
497  struct option options[] =
498  {
499  {"help", no_argument, 0, 'h'},
500  {"verbose", no_argument, 0, 'v'},
501  {"srclist", required_argument, 0, 's'},
502  {"threshold", required_argument, 0, 'T'},
503  {"fwhm", required_argument, 0, 'F'},
504  {"fwhm-scale", required_argument, 0, 'S'},
505  {"gap", required_argument, 0, 'G'},
506  {"bkg-kappa", required_argument, 0, 'K'},
507  {"bkg-percentile", no_argument, 0, 'B'},
508  {"bkg-ignore", required_argument, 0, 'm'},
509  {"bkg-fraction", required_argument, 0, 'M'},
510  {0, 0, 0, 0}
511  };
512 
513 
514  struct {
515  double threshold;
516  double fwhm;
517  double scale;
518  double srcGap;
519  double bkgKappa;
520  double bkgOffset;
521  double bkgFraction;
522  int bkgMethod;
523  char srcList[1024];
524  int logDebug;
525  } config = {15., 0.5, 10., 0.4, 3., 0.01, 0.25, 0, {'\0'}, 0};
526 
527  int fwhmDefault = 1;
528 
529  int option_index;
530  int opt;
531 
532  while ((opt = getopt_long(argc, argv, ":hvs:T:S:F:G:Bm:M:K:", options,
533  &option_index)) != -1) {
534  switch (opt) {
535  case 'h':
536  usage(progname);
537  return 0;
538  break;
539 
540  case 'v':
541  config.logDebug = 1;
542  break;
543 
544  case 's':
545  strncpy(config.srcList, optarg, 1023);
546  config.srcList[1023] = '\0';
547  break;
548 
549  case 'T':
550  config.threshold = strtod(optarg, NULL);
551  break;
552 
553  case 'S':
554  config.scale = strtod(optarg, NULL);
555  break;
556 
557  case 'F':
558  config.fwhm = strtod(optarg, NULL);
559  fwhmDefault = 0;
560  break;
561 
562  case 'G':
563  config.srcGap = strtod(optarg, NULL);
564  break;
565 
566  case 'K':
567  config.bkgKappa = strtod(optarg, NULL);
568  break;
569 
570  case 'B':
571  config.bkgMethod = 1;
572  break;
573 
574  case 'm':
575  config.bkgOffset = strtod(optarg, NULL);
576  break;
577 
578  case 'M':
579  config.bkgFraction = strtod(optarg, NULL);
580  break;
581 
582  case ':':
583  fprintf(stderr, "error: option '%c' requires an argument\n", optopt);
584  usage(progname);
585  return -1;
586  break;
587 
588  case '?':
589  fprintf(stderr, "error: unknown option '%c'\n", optopt);
590  usage(progname);
591  return -1;
592  break;
593 
594  default:
595  usage(progname);
596  return -1;
597  break;
598  }
599  }
600 
601  if (argc <= optind) {
602  fprintf(stderr, "error: missing input image argument\n");
603  usage(progname);
604  return -1;
605  }
606 
607  if (argc - optind <= 1) {
608  fprintf(stderr, "error: missing output table argument\n");
609  usage(progname);
610  return -1;
611  }
612 
613  const char *inImage = argv[optind];
614  const char *outTable = argv[optind + 1];
615 
616  cpl_init(CPL_INIT_DEFAULT);
617 
618  if (config.logDebug) {
619  cpl_msg_set_level(CPL_MSG_DEBUG);
620  }
621 
622 
623  // Load image: Use the header to figure out whether the input file is
624  // a standalone AO Camera test image, an MUSE raw observation with the
625  // AO Camera image attached as an extension, or a FOV image created by
626  // the Pipeline.
627 
628  cpl_msg_info(progname, "Locating AO test image in data file '%s'", inImage);
629 
630  cpl_propertylist *header = cpl_propertylist_load(inImage, 0);
631 
632  if (!header) {
633  cpl_error_set_message(cpl_func, cpl_error_get_code(), "Loading primary FITS "
634  "header of \"%s\" did not succeed", inImage);
635  cpl_end();
636  return -1;
637  }
638 
639 
640  int imageFormat = -1;
641  cpl_size extNum = -1;
642 
643  if (cpl_propertylist_has(header, "ESO PRO CATG")) {
644  const char *format = muse_pfits_get_pro_catg(header);
645 
646  if (strncmp(format, "IMAGE_FOV", 9) == 0) {
647  imageFormat = MUSE_AO_IMAGE_FOV;
648  cpl_msg_debug(progname, "Found MUSE FOV image in '%s'", inImage);
649  }
650  }
651  else {
652  extNum = cpl_fits_find_extension(inImage, MUSE_EXT_IMAGE_AO);
653 
654  if (extNum > 0) {
655  imageFormat = MUSE_AO_IMAGE_ATT;
656  cpl_msg_debug(progname, "Found attached AO camera image in '%s'",
657  inImage);
658  }
659  else {
660  const char *chipId = cpl_propertylist_get_string(header, "ESO DET CHIP1 ID");
661 
662  if (chipId && (strncmp(chipId, AO_CAMERA_CHIP_ID, 9) == 0) &&
663  (cpl_propertylist_get_bool(header, "EXTEND") == CPL_FALSE)) {
664  cpl_msg_debug(progname, "Found stand-alone AO camera image in '%s'",
665  inImage);
666  imageFormat = MUSE_AO_IMAGE_AOC;
667  extNum = 0;
668  }
669  else {
670  extNum = -1;
671  }
672  }
673  }
674 
675  cpl_propertylist_delete(header);
676  header = NULL;
677 
678  if (imageFormat < 0) {
679  cpl_msg_error(progname, "Unsupported file format '%s': cannot find AO "
680  "test image", inImage);
681  cpl_end();
682  return -1;
683  }
684 
685 
686  // Load AO test image
687 
688  cpl_msg_info(progname, "Loading AO test image from '%s'", inImage);
689 
690  double pixelScale = AO_CAMERA_PIXEL_SCALE;
691 
692  cpl_image *aoImage = NULL;
693 
694  switch (imageFormat) {
695  case MUSE_AO_IMAGE_FOV:
696  {
697  muse_image *fov = muse_fov_load(inImage);
698  if (!fov) {
699  cpl_msg_error(progname, "Cannot load FOV image '%s'", inImage);
700  }
701  else {
702  const char *insMode = muse_pfits_get_insmode(fov->header);
703 
704  if (insMode) {
705  if (strncmp(insMode, "NFM", 3) == 0) {
706  pixelScale = kMuseSpaxelSizeX_NFM;
707  }
708  else {
709  pixelScale = kMuseSpaxelSizeX_WFM;
710  }
711 
712  aoImage = cpl_image_cast(fov->data, CPL_TYPE_DOUBLE);
713  }
714 
715  if (fwhmDefault) {
716  double fwhm = muse_pfits_get_ia_fwhm(fov->header);
717 
718  if (cpl_error_get_code() == CPL_ERROR_NONE) {
719  cpl_msg_info(progname, "Taking seeing estimate from image header: fwhm = %.6f", fwhm);
720  config.fwhm = fwhm;
721  }
722  }
723 
724  muse_image_delete(fov);
725  }
726  }
727  break;
728 
729  case MUSE_AO_IMAGE_ATT:
730  case MUSE_AO_IMAGE_AOC:
731  aoImage = cpl_image_load(inImage, CPL_TYPE_DOUBLE, 0, extNum);
732  break;
733 
734  default:
735  break;
736  }
737 
738  if (!aoImage) {
739  cpl_msg_error(progname, "Loading AO test image from '%s' failed!",
740  inImage);
741  cpl_end();
742  return -1;
743  }
744 
745  // Reject NaN and infinity values to exclude them from the subsequent
746  // statistics. In particular loading a FOV image can generates this to
747  // flag bad pixels.
748 
749  cpl_image_reject_value(aoImage, CPL_VALUE_NOTFINITE);
750 
751 
752  // Compute image statistics
753 
754  struct {
755  double min;
756  double max;
757  double mean;
758  double sdev;
759  double median;
760  double mdev;
761  } statistics;
762 
763  statistics.min = cpl_image_get_min(aoImage);
764  statistics.max = cpl_image_get_max(aoImage);
765  statistics.mean = cpl_image_get_mean(aoImage);
766  statistics.sdev = cpl_image_get_stdev(aoImage);
767  statistics.median = cpl_image_get_median_dev(aoImage, &statistics.mdev);
768 
769  if (cpl_error_get_code() != CPL_ERROR_NONE) {
770  cpl_image_delete(aoImage);
771 
772  cpl_msg_error(progname, "Calculating image statistics failed!");
773  cpl_end();
774 
775  return -1;
776  }
777 
778  cpl_msg_info(progname, "Image statistics: min = %.6f, max = %.6f, "
779  "mean = %.6f, rms = %.6f, median = %.6f, mdev = %.6f",
780  statistics.min, statistics.max, statistics.mean,
781  statistics.sdev, statistics.median, statistics.mdev);
782 
783 
784  // Detect point sources in the image. Remove background before
785  // To ignore outliers at the lower end of the pixel values, values
786  // with less than 5. the RMS from the median of the pixel values are
787  // ignored.
788 
789  // TODO: How should the background be determined?
790 
791 
792  double lower = statistics.median - config.bkgKappa * statistics.mdev;
793  double upper = statistics.median + config.bkgKappa * statistics.mdev;
794 
795  if (config.bkgMethod == 1) {
796 
797  double bkgFraction = config.bkgOffset + config.bkgFraction *
798  (1. - config.bkgOffset);
799 
800  lower = muse_cplimage_get_percentile(aoImage, config.bkgOffset);
801  upper = muse_cplimage_get_percentile(aoImage, bkgFraction);
802  upper = (statistics.median < upper) ? statistics.median : upper;
803 
804  }
805 
806  cpl_mask *bkgMask = cpl_mask_threshold_image_create(aoImage, lower, upper);
807  cpl_mask_not(bkgMask);
808 
809  cpl_mask *bpmMask = cpl_image_get_bpm(aoImage);
810  if (bpmMask) {
811  cpl_mask_or(bkgMask, bpmMask);
812  }
813 
814  bpmMask = cpl_image_set_bpm(aoImage, bkgMask);
815 
816  double rms = cpl_image_get_stdev(aoImage);
817  double bkgMean = cpl_image_get_mean(aoImage);
818  double bkgMedian = cpl_image_get_median(aoImage);
819 
820  cpl_msg_info(progname, "Background statistics: min = %.6f, max = %.6f, "
821  "mean = %.6f, sdev = %.6f, median = %.6f", lower, upper, bkgMean, rms,
822  bkgMedian);
823 
824  if (bpmMask) {
825  cpl_mask_delete(cpl_image_set_bpm(aoImage, bpmMask));
826  bpmMask = NULL;
827  }
828  else {
829  cpl_mask_delete(cpl_image_unset_bpm(aoImage));
830  }
831 
832  muse_ao_replace_rejected(aoImage, bkgMedian);
833 
834 
835  // If the image has an odd number of pixels along the x and/or the y axis
836  // trim it, by removing pixels at the right edge and at the top edge, so
837  // that the image width and height is an even number.
838 
839  cpl_size nx = cpl_image_get_size_x(aoImage);
840  cpl_size ny = cpl_image_get_size_y(aoImage);
841 
842  if ((nx % 2 != 0) || (ny % 2 != 0)) {
843  cpl_msg_debug(progname, "Trimming image even number of pixels along "
844  "the axes.");
845  if (nx % 2 != 0) {
846  --nx;
847  }
848  if (ny % 2 != 0) {
849  --ny;
850  }
851  cpl_image *_aoImage = cpl_image_extract(aoImage, 1, 1, nx, ny);
852  if (!_aoImage) {
853  cpl_image_delete(aoImage);
854  cpl_msg_error(progname, "Trimming image failed with error code '%d'",
855  cpl_error_get_code());
856  cpl_end();
857 
858  return -1;
859  }
860  cpl_image_delete(aoImage);
861  aoImage = _aoImage;
862  }
863 
864 
865  // Detect the sources in the field
866 
867  cpl_table *aoSources = muse_find_stars(aoImage, config.threshold * rms,
868  config.fwhm / pixelScale, NULL, NULL);
869 
870  if (!aoSources) {
871  cpl_table_delete(aoSources);
872  cpl_image_delete(aoImage);
873 
874  cpl_msg_error(progname, "Point source detection failed with error code '%d'",
875  cpl_error_get_code());
876  cpl_end();
877 
878  return -1;
879  }
880 
881  cpl_size nsrc = cpl_table_get_nrow(aoSources);
882  cpl_msg_info(progname, "%" CPL_SIZE_FORMAT " sources detected", nsrc);
883 
884  double xCenter = 0.5 * cpl_image_get_size_x(aoImage);
885  double yCenter = 0.5 * cpl_image_get_size_y(aoImage);
886 
887  cpl_table_new_column(aoSources, "Distance", CPL_TYPE_DOUBLE);
888 
889  for (cpl_size isrc = 0; isrc < nsrc; ++isrc) {
890  double x = cpl_table_get_double(aoSources, "X", isrc, NULL);
891  double y = cpl_table_get_double(aoSources, "Y", isrc, NULL);
892 
893  double distance = sqrt(pow(x - xCenter, 2.) + pow(y - yCenter, 2.));
894 
895  cpl_table_set_double(aoSources, "Distance", isrc, distance);
896  }
897 
898 
899  if (config.srcList[0]) {
900  cpl_table_save(aoSources, NULL, NULL, config.srcList, CPL_IO_CREATE);
901  }
902 
903 
904  // Compute ensquared energy of the central source
905 
906  cpl_size cpos;
907  cpl_table_get_column_minpos(aoSources, "Distance", &cpos);
908 
909  double xSource = cpl_table_get_double(aoSources, "X", cpos, NULL);
910  double ySource = cpl_table_get_double(aoSources, "Y", cpos, NULL);
911  double dSource = cpl_table_get_double(aoSources, "Distance", cpos, NULL);
912 
913  cpl_msg_info(progname, "Central source at row %" CPL_SIZE_FORMAT ": "
914  "x = %.6f, y = %.6f, offset = %.6f", cpos, xSource, ySource, dSource);
915 
916 
917  double sz = config.scale * config.fwhm / pixelScale;
918 
919  double szmin = sz + config.srcGap / pixelScale;
920  double szmax = szmin * sqrt(2.);
921 
922  muse_ao_bkg bkg;
923  muse_ao_estimate_bkg(&bkg, aoImage, xSource, ySource, szmin, szmax);
924 
925  if (cpl_error_get_code() != CPL_ERROR_NONE) {
926  cpl_table_delete(aoSources);
927  cpl_image_delete(aoImage);
928 
929  cpl_msg_error(progname, "Failed to estimate local background for source "
930  "at (%.6f, %.6f) and rectangular annulus size (%.6f, %.6f).", xSource,
931  ySource, szmin, szmax);
932 
933  cpl_end();
934 
935  return -1;
936  }
937 
938  cpl_msg_info(progname, "Local background estimate from rectangular "
939  "annulus (%.6f, %.6f) for source at (%.6f, %.6f): mean = %.6f, "
940  "rms = %.6f, median = %.6f",
941  szmin, szmax, xSource, ySource, bkg.mean, bkg.sdev, bkg.median);
942 
943 
944  cpl_table *ee = muse_ao_calculate_ee_sum(aoImage, xSource, ySource, sz, bkg.median);
945 
946  if (cpl_error_get_code() != CPL_ERROR_NONE) {
947  cpl_table_delete(ee);
948  cpl_table_delete(aoSources);
949  cpl_image_delete(aoImage);
950 
951  cpl_msg_error(progname, "Failed to calculate ensquared energy for source "
952  "at (%.6f, %.6f) and box size %.6f.", xSource, ySource, sz);
953  cpl_end();
954 
955  return -1;
956  }
957 
958  cpl_msg_info(progname, "Calculated ensquared energy for source "
959  "at (%.6f, %.6f) and box size %.6f: EE = %.6f.",
960  xSource, ySource, sz,
961  cpl_table_get_double(ee, "EE", cpl_table_get_nrow(ee) - 1, NULL));
962 
963 
964  // Fit the PSF using a Moffat profile to the central source and
965  // compute the ensquared energy from the fitted, oversampled PSF.
966 
967  cpl_array *ee_psf = muse_ao_calculate_ee_psf(aoImage, xSource, ySource, sz,
968  bkg.median);
969 
970  // Create output table
971 
972  cpl_size nrow = cpl_table_get_nrow(ee);
973  cpl_table *eeTable = cpl_table_new(nrow);
974 
975  cpl_table_new_column(eeTable, "Rpixel", CPL_TYPE_DOUBLE);
976  cpl_table_new_column(eeTable, "Rtheta", CPL_TYPE_DOUBLE);
977  cpl_table_new_column(eeTable, "EE", CPL_TYPE_DOUBLE);
978  cpl_table_new_column(eeTable, "EEF", CPL_TYPE_DOUBLE);
979 
980  double EEinf = cpl_table_get_column_max(ee, "EE");
981 
982  for (cpl_size irow = 0; irow < nrow; ++irow) {
983 
984  double _radius = cpl_table_get_double(ee, "Radius", irow, NULL);
985  double _EE = cpl_table_get_double(ee, "EE", irow, NULL);
986 
987  cpl_table_set_double(eeTable, "Rpixel", irow, _radius);
988  cpl_table_set_double(eeTable, "Rtheta", irow, _radius * pixelScale);
989  cpl_table_set_double(eeTable, "EE", irow, _EE);
990  cpl_table_set_double(eeTable, "EEF", irow, _EE / EEinf);
991 
992  }
993 
994  cpl_table_save(eeTable, NULL, NULL, outTable, CPL_IO_CREATE);
995 
996  cpl_table_delete(eeTable);
997  cpl_table_delete(ee);
998  cpl_table_delete(aoSources);
999  cpl_image_delete(aoImage);
1000 
1001  cpl_end();
1002 
1003  return 0;
1004 } /* muse_ao_perf */
void muse_image_delete(muse_image *aImage)
Deallocate memory associated to a muse_image object.
Definition: muse_image.c:85
const char * muse_pfits_get_insmode(const cpl_propertylist *aHeaders)
find out the observation mode
Definition: muse_pfits.c:1383
cpl_image * data
the data extension
Definition: muse_image.h:46
double muse_pfits_get_ia_fwhm(const cpl_propertylist *aHeaders)
find out the image analysis FWHM corrected by airmass (in arcsec)
Definition: muse_pfits.c:1242
muse_image * muse_fov_load(const char *aFilename)
Load a FOV image into a MUSE image.
Definition: muse_utils.c:2864
Structure definition of MUSE three extension FITS file.
Definition: muse_image.h:40
cpl_propertylist * header
the FITS header
Definition: muse_image.h:72
cpl_error_code muse_utils_fit_moffat_2d(const cpl_matrix *aPositions, const cpl_vector *aValues, const cpl_vector *aErrors, cpl_array *aParams, cpl_array *aPErrors, const cpl_array *aPFlags, double *aRMS, double *aRedChisq)
Fit a 2D Moffat function to a given set of data.
Definition: muse_utils.c:1878
double muse_cplimage_get_percentile(const cpl_image *aImage, double aFraction)
Get the percentile of an image.
cpl_table * muse_find_stars(const cpl_image *aImage, double aHmin, double aFwhm, const double *aRoundLimits, const double *aSharpLimits)
Find positive brightness perturbations (i.e stars) in an image.
const char * muse_pfits_get_pro_catg(const cpl_propertylist *aHeaders)
find out the PRO category
Definition: muse_pfits.c:159