SINFONI Pipeline Reference Manual  2.6.0
irplib_strehl.c
1 /* $Id: irplib_strehl.c,v 1.43 2009-11-18 21:37:48 llundin Exp $
2  *
3  * This file is part of the irplib package
4  * Copyright (C) 2002,2003 European Southern Observatory
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1307 USA
19  */
20 
21 /*
22  * $Author: llundin $
23  * $Date: 2009-11-18 21:37:48 $
24  * $Revision: 1.43 $
25  * $Name: not supported by cvs2svn $
26  */
27 
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31 
32 /*-----------------------------------------------------------------------------
33  Includes
34  -----------------------------------------------------------------------------*/
35 
36 #include "irplib_strehl.h"
37 #include "irplib_utils.h"
38 
39 #include <assert.h>
40 #include <stdint.h>
41 #include <math.h>
42 
43 /*----------------------------------------------------------------------------*/
47 /*----------------------------------------------------------------------------*/
48 
49 /*-----------------------------------------------------------------------------
50  Define
51  -----------------------------------------------------------------------------*/
52 
53 #ifndef IRPLIB_STREHL_RAD_CENTRAL
54 #define IRPLIB_STREHL_RAD_CENTRAL 5
55 #endif
56 
57 #ifndef IRPLIB_STREHL_DETECT_LEVEL
58 #define IRPLIB_STREHL_DETECT_LEVEL 5.0
59 #endif
60 
61 #define IRPLIB_DISK_BG_MIN_PIX_NB 30
62 #define IRPLIB_DISK_BG_REJ_LOW 0.1
63 #define IRPLIB_DISK_BG_REJ_HIGH 0.1
64 
65 #ifdef CPL_MIN
66 #define IRPLIB_MIN CPL_MIN
67 #else
68 #define IRPLIB_MIN(A,B) (((A) < (B)) ? (A) : (B))
69 #endif
70 
71 #ifdef CPL_MAX
72 #define IRPLIB_MAX CPL_MAX
73 #else
74 #define IRPLIB_MAX(A,B) (((A) > (B)) ? (A) : (B))
75 #endif
76 
77 /*-----------------------------------------------------------------------------
78  Functions prototypes
79  -----------------------------------------------------------------------------*/
80 
81 static cpl_image * irplib_strehl_generate_otf(double, double, double, double,
82  int, double);
83 static double PSF_H1(double, double, double);
84 static double PSF_H2(double, double);
85 static double PSF_G(double, double);
86 static double PSF_sinc_norm(double);
87 static double PSF_TelOTF(double, double);
88 
89 #ifndef IRPLIB_NO_FIT_GAUSSIAN
90 #ifdef IRPLIB_STREHL_USE_CPL_IMAGE_FIT_GAUSSIAN
91 static double irplib_gaussian_2d(double, double, double, double, double);
92 #endif
93 
94 #if defined CPL_VERSION_CODE && CPL_VERSION_CODE >= CPL_VERSION(6, 9, 1)
95 #define irplib_gaussian_eval_2d cpl_gaussian_eval_2d
96 #else
97 static double irplib_gaussian_eval_2d(const cpl_array *, double, double);
98 #endif
99 
100 static uint32_t irplib_roundup_power2(uint32_t v) CPL_ATTR_CONST;
101 
102 static
103 cpl_error_code irplib_gaussian_maxpos(const cpl_image *,
104  double,
105  double *,
106  double *,
107  double *);
108 #endif
109 
110 /*-----------------------------------------------------------------------------
111  Functions code
112  -----------------------------------------------------------------------------*/
115 /*----------------------------------------------------------------------------*/
145 /*----------------------------------------------------------------------------*/
146 cpl_error_code irplib_strehl_compute(const cpl_image * im,
147  double m1,
148  double m2,
149  double lam,
150  double dlam,
151  double pscale,
152  int size,
153  double xpos,
154  double ypos,
155  double r1,
156  double r2,
157  double r3,
158  int noise_box_sz,
159  int noise_nsamples,
160  double * strehl,
161  double * strehl_err,
162  double * star_bg,
163  double * star_peak,
164  double * star_flux,
165  double * psf_peak,
166  double * psf_flux,
167  double * bg_noise)
168 {
169  cpl_image * psf;
170  double star_radius, max_radius;
171 
172  /* FIXME: Arbitrary choice of image border */
173  const double window_size = (double)(IRPLIB_STREHL_RAD_CENTRAL);
174 
175  /* Determined empirically by C. Lidman for Strehl error computation */
176  const double strehl_error_coefficient = CPL_MATH_PI * 0.007 / 0.0271;
177  double ring[4];
178  /* cpl_flux_get_noise_ring() must succeed with this many tries */
179  int ring_tries = 3;
180 #ifndef IRPLIB_NO_FIT_GAUSSIAN
181  double xposfit = 0.0, yposfit = 0.0, peak = 0.0;
182  cpl_error_code code;
183 #endif
184  cpl_errorstate prestate = cpl_errorstate_get();
185 
186  /* Check compile-time constant */
187  cpl_ensure_code(window_size > 0.0, CPL_ERROR_ILLEGAL_INPUT);
188 
189  /* Test inputs */
190  cpl_ensure_code(im != NULL, CPL_ERROR_NULL_INPUT);
191  cpl_ensure_code(strehl != NULL, CPL_ERROR_NULL_INPUT);
192  cpl_ensure_code(strehl_err != NULL, CPL_ERROR_NULL_INPUT);
193  cpl_ensure_code(star_bg != NULL, CPL_ERROR_NULL_INPUT);
194  cpl_ensure_code(star_peak != NULL, CPL_ERROR_NULL_INPUT);
195  cpl_ensure_code(star_flux != NULL, CPL_ERROR_NULL_INPUT);
196  cpl_ensure_code(psf_peak != NULL, CPL_ERROR_NULL_INPUT);
197  cpl_ensure_code(psf_flux != NULL, CPL_ERROR_NULL_INPUT);
198 
199  cpl_ensure_code(pscale > 0.0, CPL_ERROR_ILLEGAL_INPUT);
200 
201  cpl_ensure_code(r1 > 0.0, CPL_ERROR_ILLEGAL_INPUT);
202  cpl_ensure_code(r2 > 0.0, CPL_ERROR_ILLEGAL_INPUT);
203  cpl_ensure_code(r3 > r2, CPL_ERROR_ILLEGAL_INPUT);
204 
205  /* Computing a Strehl ratio is a story between an ideal PSF */
206  /* and a candidate image supposed to approximate this ideal PSF. */
207 
208  /* Generate first appropriate PSF to find max peak */
209  psf = irplib_strehl_generate_psf(m1, m2, lam, dlam, pscale, size);
210  if (psf == NULL) {
211  return cpl_error_set_where(cpl_func);
212  }
213 
214  /* Compute flux in PSF and find max peak */
215  *psf_peak = cpl_image_get_max(psf);
216  cpl_image_delete(psf);
217 
218  assert( *psf_peak > 0.0); /* The ideal PSF has a positive maximum */
219  *psf_flux = 1.0; /* The psf flux, cpl_image_get_flux(psf), is always 1 */
220 
221 #ifndef IRPLIB_NO_FIT_GAUSSIAN
222  code = irplib_gaussian_maxpos(im, IRPLIB_STREHL_DETECT_LEVEL,
223  &xposfit, &yposfit, &peak);
224  if (code) {
225  cpl_errorstate_set(prestate);
226  } else {
227  xpos = xposfit;
228  ypos = yposfit;
229  }
230 #endif
231 
232  /* Measure the background in the candidate image */
233  *star_bg = irplib_strehl_ring_background(im, xpos, ypos,
234  r2/pscale, r3/pscale,
235  IRPLIB_BG_METHOD_AVER_REJ);
236  if (!cpl_errorstate_is_equal(prestate)) {
237  return cpl_error_set_where(cpl_func);
238  }
239 
240  /* Compute star_radius in pixels */
241  star_radius = r1/pscale;
242 
243  /* Measure the flux on the candidate image */
244  *star_flux = irplib_strehl_disk_flux(im, xpos, ypos, star_radius, *star_bg);
245 
246  if (*star_flux <= 0.0) {
247  return cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_OUTPUT,
248  "Non-positive star flux=%g (Star "
249  "background=%g)", *star_flux, *star_bg);
250  }
251 
252  /* Find the peak value on the central part of the candidate image */
253  max_radius = window_size < star_radius ? window_size : star_radius;
254  cpl_ensure_code(!irplib_strehl_disk_max(im, xpos, ypos, max_radius,
255  star_peak), cpl_error_get_code());
256  *star_peak -= *star_bg;
257 
258  if (*star_flux <= 0.0) {
259  return cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_OUTPUT,
260  "Non-positive star peak=%g (Star "
261  "background=%g, Star flux=%g)",
262  *star_flux, *star_bg, *star_flux);
263  }
264 
265  /* Compute Strehl */
266  /* (StarPeak / StarFlux) / (PsfPeak / PsfFlux) */
267  *strehl = (*star_peak * *psf_flux ) / ( *star_flux * *psf_peak);
268 
269 #ifndef IRPLIB_NO_FIT_GAUSSIAN
270  if (code == CPL_ERROR_NONE && peak > *star_peak && *star_peak > 0.0 &&
271  *strehl * peak / *star_peak <= 1.0) {
272  cpl_msg_debug(cpl_func, "Increasing Strehl from %g: %g (%g)",
273  *strehl, *strehl * peak / *star_peak,
274  peak / *star_peak);
275  *strehl *= peak / *star_peak;
276  *star_peak = peak;
277  }
278 #endif
279 
280  /* Compute Strehl error */
281  ring[0] = xpos;
282  ring[1] = ypos;
283  ring[2] = r2/pscale;
284  ring[3] = r3/pscale;
285 
286  while (cpl_flux_get_noise_ring(im, ring, noise_box_sz, noise_nsamples,
287  bg_noise, NULL) && --ring_tries > 0);
288  if (ring_tries > 0) {
289  cpl_errorstate_set(prestate); /* Recover, if an error happened */
290  } else {
291  return cpl_error_set_where(cpl_func);
292  }
293 
294  *strehl_err = strehl_error_coefficient * (*bg_noise) * pscale *
295  star_radius * star_radius / *star_flux;
296 
297  if (*strehl > 1.0) {
298  cpl_msg_warning(cpl_func, "Extreme Strehl-ratio=%g (strehl-error=%g, "
299  "star_peak=%g, star_flux=%g, psf_peak=%g, psf_flux=%g)",
300  *strehl, *strehl_err, *star_peak, *star_flux, *psf_peak,
301  *psf_flux);
302  }
303 
304  /* This check should not be able to fail, but just to be sure */
305  return *strehl_err >= 0.0
306  ? CPL_ERROR_NONE
307  : cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_OUTPUT,
308  "Negative strehl-error=%g (Strehl-ratio=%g, "
309  "star_peak=%g, star_flux=%g, psf_peak=%g, "
310  "psf_flux=%g", *strehl_err, *strehl,
311  *star_peak, *star_flux, *psf_peak, *psf_flux);
312 }
313 
314 /*----------------------------------------------------------------------------*/
327 /*----------------------------------------------------------------------------*/
328 double irplib_strehl_disk_flux(const cpl_image * im,
329  double xpos,
330  double ypos,
331  double rad,
332  double bg)
333 {
334  const int nx = cpl_image_get_size_x(im);
335  const int ny = cpl_image_get_size_y(im);
336  /* Round down */
337  const int lx = (int)(xpos - rad);
338  const int ly = (int)(ypos - rad);
339  /* Round up */
340  const int ux = (int)(xpos + rad) + 1;
341  const int uy = (int)(ypos + rad) + 1;
342 
343  const double sqr = rad * rad;
344  double flux = 0.0;
345  int i, j;
346 
347 
348  /* Check entries */
349  cpl_ensure(im != NULL, CPL_ERROR_NULL_INPUT, 0.0);
350  cpl_ensure(rad > 0.0, CPL_ERROR_ILLEGAL_INPUT, 0.0);
351 
352  for (j = IRPLIB_MAX(ly, 0); j < IRPLIB_MIN(uy, ny); j++) {
353  const double yj = (double)j - ypos;
354  for (i = IRPLIB_MAX(lx, 0); i < IRPLIB_MIN(ux, nx); i++) {
355  const double xi = (double)i - xpos;
356  const double dist = yj * yj + xi * xi;
357  if (dist <= sqr) {
358  int isbad;
359  const double value = cpl_image_get(im, i+1, j+1, &isbad);
360 
361  if (!isbad ) {
362 
363  flux += value - bg;
364 
365  }
366  }
367  }
368  }
369 
370  return flux;
371 }
372 
373 /*----------------------------------------------------------------------------*/
385 /*----------------------------------------------------------------------------*/
386 double irplib_strehl_ring_background(const cpl_image * im,
387  double xpos,
388  double ypos,
389  double rad_int,
390  double rad_ext,
391  irplib_strehl_bg_method mode)
392 {
393  const int nx = cpl_image_get_size_x(im);
394  const int ny = cpl_image_get_size_y(im);
395  /* Round down */
396  const int lx = (int)(xpos - rad_ext);
397  const int ly = (int)(ypos - rad_ext);
398  /* Round up */
399  const int ux = (int)(xpos + rad_ext) + 1;
400  const int uy = (int)(ypos + rad_ext) + 1;
401  int mpix, npix;
402  const double sqr_int = rad_int * rad_int;
403  const double sqr_ext = rad_ext * rad_ext;
404  cpl_vector * pix_arr;
405  double flux = 0.0;
406  int i, j;
407 
408  /* Check entries */
409  cpl_ensure(im != NULL, CPL_ERROR_NULL_INPUT, 0.0);
410  cpl_ensure(rad_int > 0.0, CPL_ERROR_ILLEGAL_INPUT, 0.0);
411  cpl_ensure(rad_ext > rad_int, CPL_ERROR_ILLEGAL_INPUT, 0.0);
412 
413  cpl_ensure(mode == IRPLIB_BG_METHOD_AVER_REJ ||
414  mode == IRPLIB_BG_METHOD_MEDIAN,
415  CPL_ERROR_UNSUPPORTED_MODE, 0.0);
416 
417  mpix = (int)((2.0 * rad_ext + 1.0) * (2.0 * rad_ext + 1.0));
418 
419  /* Allocate pixel array to hold values in the ring */
420  pix_arr = cpl_vector_new(mpix);
421 
422  /* Count number of pixels in the ring */
423  /* Retrieve all pixels which belong to the ring */
424  npix = 0;
425  for (j = IRPLIB_MAX(ly, 0); j < IRPLIB_MIN(uy, ny); j++) {
426  const double yj = (double)j - ypos;
427  for (i = IRPLIB_MAX(lx, 0); i < IRPLIB_MIN(ux, nx); i++) {
428  const double xi = (double)i - xpos;
429  const double dist = yj * yj + xi * xi;
430  if (sqr_int <= dist && dist <= sqr_ext) {
431  int isbad;
432  const double value = cpl_image_get(im, i+1, j+1, &isbad);
433 
434  if (!isbad) {
435  cpl_vector_set(pix_arr, npix, value);
436  npix++;
437  }
438  }
439  }
440  }
441 
442  assert(npix <= mpix);
443 
444  if (npix < IRPLIB_DISK_BG_MIN_PIX_NB) {
445  cpl_vector_delete(pix_arr);
446  (void)cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND, "Need "
447  "at least %d (not %d <= %d) samples to "
448  "compute noise", IRPLIB_DISK_BG_MIN_PIX_NB,
449  npix, mpix);
450  return 0.0;
451  }
452 
453  /* Should not be able to fail now */
454 
455  /* Resize pixel array to actual number of values within the ring */
456  pix_arr = cpl_vector_wrap(npix, (double*)cpl_vector_unwrap(pix_arr));
457 
458  if (mode == IRPLIB_BG_METHOD_AVER_REJ) {
459  const int low_ind = (int)((double)npix * IRPLIB_DISK_BG_REJ_LOW);
460  const int high_ind = (int)((double)npix
461  * (1.0 - IRPLIB_DISK_BG_REJ_HIGH));
462 
463  /* Sort the array */
464  cpl_vector_sort(pix_arr, CPL_SORT_ASCENDING);
465 
466  for (i=low_ind; i<high_ind; i++) {
467  flux += cpl_vector_get(pix_arr, i);
468  }
469  if (high_ind - low_ind > 1) flux /= (double)(high_ind - low_ind);
470  } else /* if (mode == IRPLIB_BG_METHOD_MEDIAN) */ {
471  flux = cpl_vector_get_median(pix_arr);
472  }
473 
474  cpl_vector_delete(pix_arr);
475 
476  return flux;
477 }
478 
479 /*----------------------------------------------------------------------------*/
499 /*----------------------------------------------------------------------------*/
500 cpl_image * irplib_strehl_generate_psf(double m1,
501  double m2,
502  double lam,
503  double dlam,
504  double pscale,
505  int size)
506 {
507  cpl_image * otf_image = irplib_strehl_generate_otf(m1, m2, lam, dlam,
508  size, pscale);
509 
510  if (otf_image == NULL ||
511 
512  /* Transform back to real space
513  - Normalization is unnecessary, due to the subsequent normalisation.
514  - An OTF is point symmetric about its center, i.e. it is even,
515  i.e. the real space image is real.
516  - Because of this a forward FFT works as well.
517  - If the PSF ever needs to have its images halves swapped add
518  CPL_FFT_SWAP_HALVES to the FFT call.
519  */
520 
521  cpl_image_fft(otf_image, NULL, CPL_FFT_UNNORMALIZED) ||
522 
523  /* Compute absolute values of PSF */
524  cpl_image_abs(otf_image) ||
525 
526  /* Normalize PSF to get flux=1 */
527  cpl_image_normalise(otf_image, CPL_NORM_FLUX)) {
528 
529  (void)cpl_error_set_where(cpl_func);
530  cpl_image_delete(otf_image);
531  otf_image = NULL;
532  }
533 
534  return otf_image;
535 }
536 
539 /*----------------------------------------------------------------------------*/
555 /*----------------------------------------------------------------------------*/
556 static cpl_image * irplib_strehl_generate_otf(double m1,
557  double m2,
558  double lam,
559  double dlam,
560  int size,
561  double pscale)
562 {
563  double * otf_data;
564  /* Obscuration ratio, m1 / m2 */
565  const double obs_ratio = m1 != 0.0 ? m2 / m1 : 0.0;
566  /* pixel scale converted from Arsecond to radian */
567  const double rpscale = pscale * CPL_MATH_2PI / (double)(360 * 60 * 60);
568  /* Cut-off frequency in pixels per central wavelength (in m) */
569  const double f_max = m1 * rpscale * (double)size;
570 
571  /* Pixel corresponding to the zero frequency */
572  const int pix0 = size / 2;
573  int i, j;
574 
575 
576  cpl_ensure(m2 > 0.0, CPL_ERROR_ILLEGAL_INPUT, NULL);
577  cpl_ensure(m1 > m2, CPL_ERROR_ILLEGAL_INPUT, NULL);
578  cpl_ensure(dlam > 0.0, CPL_ERROR_ILLEGAL_INPUT, NULL);
579  cpl_ensure(pscale > 0.0, CPL_ERROR_ILLEGAL_INPUT, NULL);
580  cpl_ensure(size > 0, CPL_ERROR_ILLEGAL_INPUT, NULL);
581  /* Due the the FFT, size is actually required to be a power of two */
582  cpl_ensure(size % 2 == 0, CPL_ERROR_ILLEGAL_INPUT, NULL);
583 
584  /* Ensure positive lambda */
585  cpl_ensure(2.0 * lam > dlam, CPL_ERROR_ILLEGAL_INPUT, NULL);
586 
587  /* Convert wavelengths from micron to meter */
588  lam /= 1.0e6;
589  dlam /= 1.0e6;
590 
591  /* Allocate the output pixel buffer */
592  otf_data = (double*)cpl_malloc(size * size * sizeof(*otf_data));
593 
594  /* Convolution with the detector pixels */
595  /* The OTF is point symmetric so the whole image can be computed from the
596  values of a single octant. */
597  /* The image could be created with calloc() and j limited by
598  f_max / (mlam - mdlam * 0.5) but this is not faster */
599  for (j = 0; j <= pix0; j++) {
600  double sinc_y_9 = 0.0; /* Avoid uninit warning */
601  for (i = 0; i <= j; i++) {
602  if (i == 0 && j == 0) {
603  otf_data[size * pix0 + pix0] = 1.0;
604  } else {
605  const double x = (double)i;
606  const double y = (double)j;
607  const double sqdist = x * x + y * y;
608  double f_lambda = 0.0;
609  double sinc_xy_9 = 0.0; /* Zero if OTF is zero */
610  double otfxy = 0.0;
611  int k;
612 
613  assert( j > 0 );
614 
615  /* 9 iterations on the wavelength */
616  /* Unrolling the loop is not faster (due to the break?) */
617  for (k = 4; k >= -4; k--) {
618  /* Compute intermediate cut-off frequency */
619  const double lambda = lam - dlam * (double)k / 8.0;
620 
621  /* A decreasing k ensures that we either enter on the first
622  iteration or not at all */
623  if (sqdist * lambda * lambda >= f_max * f_max) break;
624 
625  if (k == 4) {
626  f_lambda = sqrt(sqdist) / f_max;
627  if (i == 0) {
628  /* Sinc(x = 0) == 1 */
629  sinc_xy_9 = sinc_y_9 =
630  PSF_sinc_norm(y / (double)size) / 9.0;
631  } else {
632  sinc_xy_9 = sinc_y_9 *
633  PSF_sinc_norm(x / (double)size);
634  }
635  }
636 
637  otfxy += PSF_TelOTF(f_lambda * lambda, obs_ratio);
638  }
639  otfxy *= sinc_xy_9;
640 
641  /* When i == j the same value is written to the same
642  position twice. That's probably faster than a guard */
643  otf_data[size * (pix0 - j) + pix0 - i] = otfxy;
644  otf_data[size * (pix0 - i) + pix0 - j] = otfxy;
645  if (i < pix0) {
646  otf_data[size * (pix0 - j) + pix0 + i] = otfxy;
647  otf_data[size * (pix0 + i) + pix0 - j] = otfxy;
648  if (j < pix0) {
649  otf_data[size * (pix0 + j) + pix0 - i] = otfxy;
650  otf_data[size * (pix0 - i) + pix0 + j] = otfxy;
651  otf_data[size * (pix0 + j) + pix0 + i] = otfxy;
652  otf_data[size * (pix0 + i) + pix0 + j] = otfxy;
653  }
654  }
655  }
656  }
657  }
658 
659  return cpl_image_wrap_double(size, size, otf_data);
660 }
661 
662 /*----------------------------------------------------------------------------*
663  * H1 function
664  *----------------------------------------------------------------------------*/
665 static double PSF_H1(
666  double f,
667  double u,
668  double v)
669 {
670  const double e = fabs(1.0-v) > 0.0 ? -1.0 : 1.0; /* e = 1.0 iff v = 1.0 */
671 
672  return((v*v/CPL_MATH_PI)*acos((f/v)*(1.0+e*(1.0-u*u)/(4.0*f*f))));
673 }
674 
675 /*----------------------------------------------------------------------------*
676  * H2 function
677  *----------------------------------------------------------------------------*/
678 static double PSF_H2(double f,
679  double u)
680 {
681  const double tmp1 = (2.0 * f) / (1.0 + u);
682  const double tmp2 = (1.0 - u) / (2.0 * f);
683 
684  return -1.0 * (f/CPL_MATH_PI) * (1.0+u)
685  * sqrt((1.0-tmp1*tmp1)*(1.0-tmp2*tmp2));
686 }
687 
688 /*----------------------------------------------------------------------------*
689  * G function
690  *----------------------------------------------------------------------------*/
691 static double PSF_G(double f,
692  double u)
693 {
694  if (f <= (1.0-u)/2.0) return(u*u);
695  if (f >= (1.0+u)/2.0) return(0.0);
696  else return(PSF_H1(f,u,1.0) + PSF_H1(f,u,u) + PSF_H2(f,u));
697 }
698 
699 /*----------------------------------------------------------------------------*/
707 /*----------------------------------------------------------------------------*/
708 static double PSF_sinc_norm(double x)
709 {
710  return sin(x * CPL_MATH_PI) / (x * CPL_MATH_PI);
711 }
712 
713 /*----------------------------------------------------------------------------*
714  * Telescope OTF function
715  *----------------------------------------------------------------------------*/
716 static double PSF_TelOTF(double f,
717  double u)
718 {
719  return((PSF_G(f,1.0)+u*u*PSF_G(f/u,1.0)-2.0*PSF_G(f,u))/(1.0-u*u));
720 }
721 
722 /*----------------------------------------------------------------------------*/
733 /*----------------------------------------------------------------------------*/
734 cpl_error_code irplib_strehl_disk_max(const cpl_image * self,
735  double xpos,
736  double ypos,
737  double radius,
738  double * ppeak)
739 {
740 
741  const int nx = cpl_image_get_size_x(self);
742  const int ny = cpl_image_get_size_y(self);
743  /* Round down */
744  const int lx = (int)(xpos - radius);
745  const int ly = (int)(ypos - radius);
746  /* Round up */
747  const int ux = (int)(xpos + radius) + 1;
748  const int uy = (int)(ypos + radius) + 1;
749 
750  const double sqr = radius * radius;
751  cpl_boolean first = CPL_TRUE;
752  int i, j;
753 
754 
755  /* Check entries */
756  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
757  cpl_ensure_code(ppeak != NULL, CPL_ERROR_NULL_INPUT);
758  cpl_ensure_code(radius > 0.0, CPL_ERROR_ILLEGAL_INPUT);
759 
760 
761  for (j = IRPLIB_MAX(ly, 0); j < IRPLIB_MIN(uy, ny); j++) {
762  const double yj = (double)j - ypos;
763  for (i = IRPLIB_MAX(lx, 0); i < IRPLIB_MIN(ux, nx); i++) {
764  const double xi = (double)i - xpos;
765  const double dist = yj * yj + xi * xi;
766  if (dist <= sqr) {
767  int isbad;
768  const double value = cpl_image_get(self, i+1, j+1, &isbad);
769 
770  if (!isbad &&
771  (first || value > *ppeak)) {
772  first = CPL_FALSE;
773  *ppeak = value;
774  }
775  }
776  }
777  }
778 
779  return first
780  ? cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND)
781  : CPL_ERROR_NONE;
782 }
783 
784 #ifndef IRPLIB_NO_FIT_GAUSSIAN
785 #ifdef IRPLIB_STREHL_USE_CPL_IMAGE_FIT_GAUSSIAN
786 /*----------------------------------------------------------------------------*/
802 /*----------------------------------------------------------------------------*/
803 static double irplib_gaussian_2d(double x,
804  double y,
805  double norm,
806  double sig_x,
807  double sig_y)
808 {
809 
810  /* Copied from CPL */
811  return norm / (sig_x * sig_y * CPL_MATH_2PI *
812  exp(x * x / (2.0 * sig_x * sig_x) +
813  y * y / (2.0 * sig_y * sig_y)));
814 }
815 #endif
816 
817 #if defined CPL_VERSION_CODE && CPL_VERSION_CODE >= CPL_VERSION(6, 9, 1)
818 #else
819 /*----------------------------------------------------------------------------*/
838 /*----------------------------------------------------------------------------*/
839 static
840 double irplib_gaussian_eval_2d(const cpl_array * self, double x, double y)
841 {
842  cpl_errorstate prestate = cpl_errorstate_get();
843  const double B = cpl_array_get_double(self, 0, NULL);
844  const double A = cpl_array_get_double(self, 1, NULL);
845  const double R = cpl_array_get_double(self, 2, NULL);
846  const double M_x = cpl_array_get_double(self, 3, NULL);
847  const double M_y = cpl_array_get_double(self, 4, NULL);
848  const double S_x = cpl_array_get_double(self, 5, NULL);
849  const double S_y = cpl_array_get_double(self, 6, NULL);
850 
851  double value = 0.0;
852 
853  if (!cpl_errorstate_is_equal(prestate)) {
854  (void)cpl_error_set_where(cpl_func);
855  } else if (cpl_array_get_size(self) != 7) {
856  (void)cpl_error_set(cpl_func, CPL_ERROR_ILLEGAL_INPUT);
857  } else if (fabs(R) < 1.0 && S_x != 0.0 && S_y != 0.0) {
858  const double x_n = (x - M_x) / S_x;
859  const double y_n = (y - M_y) / S_y;
860 
861  value = B + A / (CPL_MATH_2PI * S_x * S_y * sqrt(1 - R * R)) *
862  exp(-0.5 / (1 - R * R) * ( x_n * x_n + y_n * y_n
863  - 2.0 * R * x_n * y_n));
864  } else if (fabs(R) > 1.0) {
865  (void)cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_OUTPUT,
866  "fabs(R=%g) > 1", R);
867  } else {
868  (void)cpl_error_set_message(cpl_func, CPL_ERROR_DIVISION_BY_ZERO,
869  "R=%g. Sigma=(%g, %g)", R, S_x, S_y);
870  }
871 
872  return value;
873 }
874 #endif
875 
876 /*----------------------------------------------------------------------------*/
883 /*----------------------------------------------------------------------------*/
884 static uint32_t irplib_roundup_power2(uint32_t v)
885 {
886  v |= v >> 1;
887  v |= v >> 2;
888  v |= v >> 4;
889  v |= v >> 8;
890  v |= v >> 16;
891 
892  return v + 1;
893 }
894 
895 
896 /*----------------------------------------------------------------------------*/
907 /*----------------------------------------------------------------------------*/
908 static
909 cpl_error_code irplib_gaussian_maxpos(const cpl_image * self,
910  double sigma,
911  double * pxpos,
912  double * pypos,
913  double * ppeak)
914 {
915 
916  const cpl_size nx = cpl_image_get_size_x(self);
917  const cpl_size ny = cpl_image_get_size_y(self);
918  int iretry = 3; /* Number retries with decreasing sigma */
919  int ifluxapert = 0;
920  double med_dist;
921  const double median = cpl_image_get_median_dev(self, &med_dist);
922  cpl_mask * selection;
923  cpl_size nlabels = 0;
924  cpl_image * labels = NULL;
925  cpl_apertures * aperts;
926  cpl_size npixobj;
927  double objradius;
928  cpl_size winsize;
929  cpl_size xposmax, yposmax;
930  double xposcen, yposcen;
931  double valmax, valfit = -1.0;
932 #ifdef IRPLIB_STREHL_USE_CPL_IMAGE_FIT_GAUSSIAN
933  double norm, xcen, ycen, sig_x, sig_y, fwhm_x, fwhm_y;
934 #endif
935  cpl_array * gauss_parameters = NULL;
936  cpl_errorstate prestate = cpl_errorstate_get();
937  cpl_error_code code = CPL_ERROR_NONE;
938 
939 
940  cpl_ensure_code( sigma > 0.0, CPL_ERROR_ILLEGAL_INPUT);
941 
942  selection = cpl_mask_new(nx, ny);
943 
944  for (; iretry > 0 && nlabels == 0; iretry--, sigma *= 0.5) {
945 
946  /* Compute the threshold */
947  const double threshold = median + sigma * med_dist;
948 
949 
950  /* Select the pixel above the threshold */
951  code = cpl_mask_threshold_image(selection, self, threshold, DBL_MAX,
952  CPL_BINARY_1);
953 
954  if (code) break;
955 
956  /* Labelise the thresholded selection */
957  cpl_image_delete(labels);
958  labels = cpl_image_labelise_mask_create(selection, &nlabels);
959  }
960  sigma *= 2.0; /* FIXME: unelegant */
961 
962  cpl_mask_delete(selection);
963 
964  if (code) {
965  cpl_image_delete(labels);
966  return cpl_error_set_where(cpl_func);
967  } else if (nlabels == 0) {
968  cpl_image_delete(labels);
969  return cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
970  }
971 
972  aperts = cpl_apertures_new_from_image(self, labels);
973 
974  /* Find the aperture with the greatest flux */
975  code = irplib_apertures_find_max_flux(aperts, &ifluxapert, 1);
976 
977  if (code) {
978  cpl_apertures_delete(aperts);
979  cpl_image_delete(labels);
980  return cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
981  }
982 
983  npixobj = cpl_apertures_get_npix(aperts, ifluxapert);
984  objradius = sqrt((double)npixobj * CPL_MATH_1_PI);
985  /* Size is power of two for future noise filtering w. fft */
986  winsize = IRPLIB_MIN(IRPLIB_MIN(nx, ny), irplib_roundup_power2
987  ((uint32_t)(3.0 * objradius + 0.5)));
988 
989  xposmax = cpl_apertures_get_maxpos_x(aperts, ifluxapert);
990  yposmax = cpl_apertures_get_maxpos_y(aperts, ifluxapert);
991  xposcen = cpl_apertures_get_centroid_x(aperts, ifluxapert);
992  yposcen = cpl_apertures_get_centroid_y(aperts, ifluxapert);
993  valmax = cpl_apertures_get_max(aperts, ifluxapert);
994 
995  cpl_apertures_delete(aperts);
996  cpl_image_delete(labels);
997 
998  cpl_msg_debug(cpl_func, "Object radius at S/R=%g: %g (window-size=%u)",
999  sigma, objradius, (unsigned)winsize);
1000  cpl_msg_debug(cpl_func, "Object-peak @ (%d, %d) = %g", (int)xposmax,
1001  (int)yposmax, valmax);
1002 
1003  gauss_parameters = cpl_array_new(7, CPL_TYPE_DOUBLE);
1004  cpl_array_set_double(gauss_parameters, 0, median);
1005 
1006  code = cpl_fit_image_gaussian(self, NULL, xposcen, yposcen,
1007  winsize, winsize, gauss_parameters,
1008  NULL, NULL, NULL,
1009  NULL, NULL, NULL,
1010  NULL, NULL, NULL);
1011  if (!code) {
1012  const double M_x = cpl_array_get_double(gauss_parameters, 3, NULL);
1013  const double M_y = cpl_array_get_double(gauss_parameters, 4, NULL);
1014 
1015  valfit = irplib_gaussian_eval_2d(gauss_parameters, M_x, M_y);
1016 
1017  if (!cpl_errorstate_is_equal(prestate)) {
1018  code = cpl_error_get_code();
1019  } else {
1020  *pxpos = M_x;
1021  *pypos = M_y;
1022  *ppeak = valfit;
1023 
1024  cpl_msg_debug(cpl_func, "Gauss-fit @ (%g, %g) = %g",
1025  M_x, M_y, valfit);
1026  }
1027  }
1028  cpl_array_delete(gauss_parameters);
1029 
1030 #ifdef IRPLIB_STREHL_USE_CPL_IMAGE_FIT_GAUSSIAN
1031  if (code || valfit < valmax) {
1032  cpl_errorstate_set(prestate);
1033 
1034  code = cpl_image_fit_gaussian(self, xposcen, yposcen,
1035  (int)(2.0 * objradius),
1036  &norm,
1037  &xcen,
1038  &ycen,
1039  &sig_x,
1040  &sig_y,
1041  &fwhm_x,
1042  &fwhm_y);
1043 
1044  if (!code) {
1045  valfit = irplib_gaussian_2d(0.0, 0.0, norm, sig_x, sig_y);
1046 
1047  cpl_msg_debug(cpl_func, "Gauss-Fit @ (%g, %g) = %g. norm=%g, "
1048  "sigma=(%g, %g)", xcen, ycen, valfit, norm,
1049  sig_x, sig_y);
1050 
1051  if (valfit > valmax) {
1052  *pxpos = xcen;
1053  *pypos = ycen;
1054  *ppeak = valfit;
1055  }
1056  }
1057  }
1058 #endif
1059 
1060  if (code || valfit < valmax) {
1061  cpl_errorstate_set(prestate);
1062  *pxpos = xposcen;
1063  *pypos = yposcen;
1064  *ppeak = valmax;
1065  }
1066 
1067  return code ? cpl_error_set_where(cpl_func) : CPL_ERROR_NONE;
1068 }
1069 #endif
cpl_error_code irplib_apertures_find_max_flux(const cpl_apertures *self, int *ind, int nfind)
Find the aperture(s) with the greatest flux.