CR2RE Pipeline Reference Manual 1.6.7
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
81static cpl_image * irplib_strehl_generate_otf(double, double, double, double,
82 int, double);
83static double PSF_H1(double, double, double);
84static double PSF_H2(double, double);
85static double PSF_G(double, double);
86static double PSF_sinc_norm(double);
87static double PSF_TelOTF(double, double);
88
89#ifndef IRPLIB_NO_FIT_GAUSSIAN
90#ifdef IRPLIB_STREHL_USE_CPL_IMAGE_FIT_GAUSSIAN
91static 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
97static double irplib_gaussian_eval_2d(const cpl_array *, double, double);
98#endif
99
100static uint32_t irplib_roundup_power2(uint32_t v) CPL_ATTR_CONST;
101
102static
103cpl_error_code irplib_gaussian_maxpos(const cpl_image *,
104 double,
105 double,
106 double,
107 double *,
108 double *,
109 double *);
110
111static cpl_error_code
112irplib_closeset_aperture(const cpl_apertures * self,
113 const double x, const double y, int * ind);
114#endif
115
116/*-----------------------------------------------------------------------------
117 Functions code
118 -----------------------------------------------------------------------------*/
121/*----------------------------------------------------------------------------*/
151/*----------------------------------------------------------------------------*/
152cpl_error_code irplib_strehl_compute(const cpl_image * im,
153 double m1,
154 double m2,
155 double lam,
156 double dlam,
157 double pscale,
158 int size,
159 double xpos,
160 double ypos,
161 double r1,
162 double r2,
163 double r3,
164 int noise_box_sz,
165 int noise_nsamples,
166 double * strehl,
167 double * strehl_err,
168 double * star_bg,
169 double * star_peak,
170 double * star_flux,
171 double * psf_peak,
172 double * psf_flux,
173 double * bg_noise)
174{
175 cpl_image * psf;
176 double star_radius, max_radius;
177
178 /* FIXME: Arbitrary choice of image border */
179 const double window_size = (double)(IRPLIB_STREHL_RAD_CENTRAL);
180
181 /* Determined empirically by C. Lidman for Strehl error computation */
182 const double strehl_error_coefficient = CPL_MATH_PI * 0.007 / 0.0271;
183 double ring[4];
184 /* cpl_flux_get_noise_ring() must succeed with this many tries */
185 int ring_tries = 3;
186#ifndef IRPLIB_NO_FIT_GAUSSIAN
187 double xposfit = 0.0, yposfit = 0.0, peak = 0.0;
188 cpl_error_code code;
189#endif
190 cpl_errorstate prestate = cpl_errorstate_get();
191
192 /* Check compile-time constant */
193 cpl_ensure_code(window_size > 0.0, CPL_ERROR_ILLEGAL_INPUT);
194
195 /* Test inputs */
196 cpl_ensure_code(im != NULL, CPL_ERROR_NULL_INPUT);
197 cpl_ensure_code(strehl != NULL, CPL_ERROR_NULL_INPUT);
198 cpl_ensure_code(strehl_err != NULL, CPL_ERROR_NULL_INPUT);
199 cpl_ensure_code(star_bg != NULL, CPL_ERROR_NULL_INPUT);
200 cpl_ensure_code(star_peak != NULL, CPL_ERROR_NULL_INPUT);
201 cpl_ensure_code(star_flux != NULL, CPL_ERROR_NULL_INPUT);
202 cpl_ensure_code(psf_peak != NULL, CPL_ERROR_NULL_INPUT);
203 cpl_ensure_code(psf_flux != NULL, CPL_ERROR_NULL_INPUT);
204
205 cpl_ensure_code(pscale > 0.0, CPL_ERROR_ILLEGAL_INPUT);
206
207 cpl_ensure_code(r1 > 0.0, CPL_ERROR_ILLEGAL_INPUT);
208 cpl_ensure_code(r2 > 0.0, CPL_ERROR_ILLEGAL_INPUT);
209 cpl_ensure_code(r3 > r2, CPL_ERROR_ILLEGAL_INPUT);
210
211 /* Computing a Strehl ratio is a story between an ideal PSF */
212 /* and a candidate image supposed to approximate this ideal PSF. */
213
214 /* Generate first appropriate PSF to find max peak */
215 psf = irplib_strehl_generate_psf(m1, m2, lam, dlam, pscale, size);
216 if (psf == NULL) {
217 return cpl_error_set_where(cpl_func);
218 }
219
220 /* Compute flux in PSF and find max peak */
221 *psf_peak = cpl_image_get_max(psf);
222 cpl_image_delete(psf);
223
224 assert( *psf_peak > 0.0); /* The ideal PSF has a positive maximum */
225 *psf_flux = 1.0; /* The psf flux, cpl_image_get_flux(psf), is always 1 */
226
227#ifndef IRPLIB_NO_FIT_GAUSSIAN
228 code = irplib_gaussian_maxpos(im, IRPLIB_STREHL_DETECT_LEVEL, xpos, ypos,
229 &xposfit, &yposfit, &peak);
230 if (code) {
231 cpl_errorstate_set(prestate);
232 } else {
233 xpos = xposfit;
234 ypos = yposfit;
235 }
236#endif
237
238 /* Measure the background in the candidate image */
239 *star_bg = irplib_strehl_ring_background(im, xpos, ypos,
240 r2/pscale, r3/pscale,
241 IRPLIB_BG_METHOD_AVER_REJ);
242 if (!cpl_errorstate_is_equal(prestate)) {
243 return cpl_error_set_where(cpl_func);
244 }
245
246 /* Compute star_radius in pixels */
247 star_radius = r1/pscale;
248
249 /* Measure the flux on the candidate image */
250 *star_flux = irplib_strehl_disk_flux(im, xpos, ypos, star_radius, *star_bg);
251
252 if (*star_flux <= 0.0) {
253 return cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_OUTPUT,
254 "Non-positive star flux=%g (Star "
255 "background=%g)", *star_flux, *star_bg);
256 }
257
258 /* Find the peak value on the central part of the candidate image */
259 max_radius = window_size < star_radius ? window_size : star_radius;
260 cpl_ensure_code(!irplib_strehl_disk_max(im, xpos, ypos, max_radius,
261 star_peak), cpl_error_get_code());
262 *star_peak -= *star_bg;
263
264 if (*star_flux <= 0.0) {
265 return cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_OUTPUT,
266 "Non-positive star peak=%g (Star "
267 "background=%g, Star flux=%g)",
268 *star_flux, *star_bg, *star_flux);
269 }
270
271 /* Compute Strehl */
272 /* (StarPeak / StarFlux) / (PsfPeak / PsfFlux) */
273 *strehl = (*star_peak * *psf_flux ) / ( *star_flux * *psf_peak);
274
275#ifndef IRPLIB_NO_FIT_GAUSSIAN
276 if (code == CPL_ERROR_NONE && peak > *star_peak && *star_peak > 0.0 &&
277 *strehl * peak / *star_peak <= 1.0) {
278 cpl_msg_debug(cpl_func, "Increasing Strehl from %g: %g (%g)",
279 *strehl, *strehl * peak / *star_peak,
280 peak / *star_peak);
281 *strehl *= peak / *star_peak;
282 *star_peak = peak;
283 }
284#endif
285
286 /* Compute Strehl error */
287 ring[0] = xpos;
288 ring[1] = ypos;
289 ring[2] = r2/pscale;
290 ring[3] = r3/pscale;
291
292 while (cpl_flux_get_noise_ring(im, ring, noise_box_sz, noise_nsamples,
293 bg_noise, NULL) && --ring_tries > 0);
294 if (ring_tries > 0) {
295 cpl_errorstate_set(prestate); /* Recover, if an error happened */
296 } else {
297 return cpl_error_set_where(cpl_func);
298 }
299
300 *strehl_err = strehl_error_coefficient * (*bg_noise) * pscale *
301 star_radius * star_radius / *star_flux;
302
303 if (*strehl > 1.0) {
304 cpl_msg_warning(cpl_func, "Extreme Strehl-ratio=%g (strehl-error=%g, "
305 "star_peak=%g, star_flux=%g, psf_peak=%g, psf_flux=%g)",
306 *strehl, *strehl_err, *star_peak, *star_flux, *psf_peak,
307 *psf_flux);
308 }
309
310 /* This check should not be able to fail, but just to be sure */
311 return *strehl_err >= 0.0
312 ? CPL_ERROR_NONE
313 : cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_OUTPUT,
314 "Negative strehl-error=%g (Strehl-ratio=%g, "
315 "star_peak=%g, star_flux=%g, psf_peak=%g, "
316 "psf_flux=%g", *strehl_err, *strehl,
317 *star_peak, *star_flux, *psf_peak, *psf_flux);
318}
319
320/*----------------------------------------------------------------------------*/
333/*----------------------------------------------------------------------------*/
334double irplib_strehl_disk_flux(const cpl_image * im,
335 double xpos,
336 double ypos,
337 double rad,
338 double bg)
339{
340 const int nx = cpl_image_get_size_x(im);
341 const int ny = cpl_image_get_size_y(im);
342 /* Round down */
343 const int lx = (int)(xpos - rad);
344 const int ly = (int)(ypos - rad);
345 /* Round up */
346 const int ux = (int)(xpos + rad) + 1;
347 const int uy = (int)(ypos + rad) + 1;
348
349 const double sqr = rad * rad;
350 double flux = 0.0;
351 int i, j;
352
353
354 /* Check entries */
355 cpl_ensure(im != NULL, CPL_ERROR_NULL_INPUT, 0.0);
356 cpl_ensure(rad > 0.0, CPL_ERROR_ILLEGAL_INPUT, 0.0);
357
358 for (j = IRPLIB_MAX(ly, 0); j < IRPLIB_MIN(uy, ny); j++) {
359 const double yj = (double)j - ypos;
360 for (i = IRPLIB_MAX(lx, 0); i < IRPLIB_MIN(ux, nx); i++) {
361 const double xi = (double)i - xpos;
362 const double dist = yj * yj + xi * xi;
363 if (dist <= sqr) {
364 int isbad;
365 const double value = cpl_image_get(im, i+1, j+1, &isbad);
366
367 if (!isbad ) {
368
369 flux += value - bg;
370
371 }
372 }
373 }
374 }
375
376 return flux;
377}
378
379/*----------------------------------------------------------------------------*/
391/*----------------------------------------------------------------------------*/
392double irplib_strehl_ring_background(const cpl_image * im,
393 double xpos,
394 double ypos,
395 double rad_int,
396 double rad_ext,
397 irplib_strehl_bg_method mode)
398{
399 const int nx = cpl_image_get_size_x(im);
400 const int ny = cpl_image_get_size_y(im);
401 /* Round down */
402 const int lx = (int)(xpos - rad_ext);
403 const int ly = (int)(ypos - rad_ext);
404 /* Round up */
405 const int ux = (int)(xpos + rad_ext) + 1;
406 const int uy = (int)(ypos + rad_ext) + 1;
407 int mpix, npix;
408 const double sqr_int = rad_int * rad_int;
409 const double sqr_ext = rad_ext * rad_ext;
410 cpl_vector * pix_arr;
411 double flux = 0.0;
412 int i, j;
413
414 /* Check entries */
415 cpl_ensure(im != NULL, CPL_ERROR_NULL_INPUT, 0.0);
416 cpl_ensure(rad_int > 0.0, CPL_ERROR_ILLEGAL_INPUT, 0.0);
417 cpl_ensure(rad_ext > rad_int, CPL_ERROR_ILLEGAL_INPUT, 0.0);
418
419 cpl_ensure(mode == IRPLIB_BG_METHOD_AVER_REJ ||
420 mode == IRPLIB_BG_METHOD_MEDIAN,
421 CPL_ERROR_UNSUPPORTED_MODE, 0.0);
422
423 mpix = (int)((2.0 * rad_ext + 1.0) * (2.0 * rad_ext + 1.0));
424
425 /* Allocate pixel array to hold values in the ring */
426 pix_arr = cpl_vector_new(mpix);
427
428 /* Count number of pixels in the ring */
429 /* Retrieve all pixels which belong to the ring */
430 npix = 0;
431 for (j = IRPLIB_MAX(ly, 0); j < IRPLIB_MIN(uy, ny); j++) {
432 const double yj = (double)j - ypos;
433 for (i = IRPLIB_MAX(lx, 0); i < IRPLIB_MIN(ux, nx); i++) {
434 const double xi = (double)i - xpos;
435 const double dist = yj * yj + xi * xi;
436 if (sqr_int <= dist && dist <= sqr_ext) {
437 int isbad;
438 const double value = cpl_image_get(im, i+1, j+1, &isbad);
439
440 if (!isbad) {
441 cpl_vector_set(pix_arr, npix, value);
442 npix++;
443 }
444 }
445 }
446 }
447
448 assert(npix <= mpix);
449
450 if (npix < IRPLIB_DISK_BG_MIN_PIX_NB) {
451 cpl_vector_delete(pix_arr);
452 (void)cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND, "Need "
453 "at least %d (not %d <= %d) samples to "
454 "compute noise", IRPLIB_DISK_BG_MIN_PIX_NB,
455 npix, mpix);
456 return 0.0;
457 }
458
459 /* Should not be able to fail now */
460
461 /* Resize pixel array to actual number of values within the ring */
462 pix_arr = cpl_vector_wrap(npix, (double*)cpl_vector_unwrap(pix_arr));
463
464 if (mode == IRPLIB_BG_METHOD_AVER_REJ) {
465 const int low_ind = (int)((double)npix * IRPLIB_DISK_BG_REJ_LOW);
466 const int high_ind = (int)((double)npix
467 * (1.0 - IRPLIB_DISK_BG_REJ_HIGH));
468
469 /* Sort the array */
470 cpl_vector_sort(pix_arr, CPL_SORT_ASCENDING);
471
472 for (i=low_ind; i<high_ind; i++) {
473 flux += cpl_vector_get(pix_arr, i);
474 }
475 if (high_ind - low_ind > 1) flux /= (double)(high_ind - low_ind);
476 } else /* if (mode == IRPLIB_BG_METHOD_MEDIAN) */ {
477 flux = cpl_vector_get_median(pix_arr);
478 }
479
480 cpl_vector_delete(pix_arr);
481
482 return flux;
483}
484
485/*----------------------------------------------------------------------------*/
505/*----------------------------------------------------------------------------*/
506cpl_image * irplib_strehl_generate_psf(double m1,
507 double m2,
508 double lam,
509 double dlam,
510 double pscale,
511 int size)
512{
513 cpl_image * otf_image = irplib_strehl_generate_otf(m1, m2, lam, dlam,
514 size, pscale);
515
516 if (otf_image == NULL ||
517
518 /* Transform back to real space
519 - Normalization is unnecessary, due to the subsequent normalisation.
520 - An OTF is point symmetric about its center, i.e. it is even,
521 i.e. the real space image is real.
522 - Because of this a forward FFT works as well.
523 - If the PSF ever needs to have its images halves swapped add
524 CPL_FFT_SWAP_HALVES to the FFT call.
525 */
526
527 cpl_image_fft(otf_image, NULL, CPL_FFT_UNNORMALIZED) ||
528
529 /* Compute absolute values of PSF */
530 cpl_image_abs(otf_image) ||
531
532 /* Normalize PSF to get flux=1 */
533 cpl_image_normalise(otf_image, CPL_NORM_FLUX)) {
534
535 (void)cpl_error_set_where(cpl_func);
536 cpl_image_delete(otf_image);
537 otf_image = NULL;
538 }
539
540 return otf_image;
541}
542
545/*----------------------------------------------------------------------------*/
561/*----------------------------------------------------------------------------*/
562static cpl_image * irplib_strehl_generate_otf(double m1,
563 double m2,
564 double lam,
565 double dlam,
566 int size,
567 double pscale)
568{
569 double * otf_data;
570 /* Obscuration ratio, m1 / m2 */
571 const double obs_ratio = m1 != 0.0 ? m2 / m1 : 0.0;
572 /* pixel scale converted from Arsecond to radian */
573 const double rpscale = pscale * CPL_MATH_2PI / (double)(360 * 60 * 60);
574 /* Cut-off frequency in pixels per central wavelength (in m) */
575 const double f_max = m1 * rpscale * (double)size;
576
577 /* Pixel corresponding to the zero frequency */
578 const int pix0 = size / 2;
579 int i, j;
580
581
582 cpl_ensure(m2 > 0.0, CPL_ERROR_ILLEGAL_INPUT, NULL);
583 cpl_ensure(m1 > m2, CPL_ERROR_ILLEGAL_INPUT, NULL);
584 cpl_ensure(dlam > 0.0, CPL_ERROR_ILLEGAL_INPUT, NULL);
585 cpl_ensure(pscale > 0.0, CPL_ERROR_ILLEGAL_INPUT, NULL);
586 cpl_ensure(size > 0, CPL_ERROR_ILLEGAL_INPUT, NULL);
587 /* Due the the FFT, size is actually required to be a power of two */
588 cpl_ensure(size % 2 == 0, CPL_ERROR_ILLEGAL_INPUT, NULL);
589
590 /* Ensure positive lambda */
591 cpl_ensure(2.0 * lam > dlam, CPL_ERROR_ILLEGAL_INPUT, NULL);
592
593 /* Convert wavelengths from micron to meter */
594 lam /= 1.0e6;
595 dlam /= 1.0e6;
596
597 /* Allocate the output pixel buffer */
598 otf_data = (double*)cpl_malloc(size * size * sizeof(*otf_data));
599
600 /* Convolution with the detector pixels */
601 /* The OTF is point symmetric so the whole image can be computed from the
602 values of a single octant. */
603 /* The image could be created with calloc() and j limited by
604 f_max / (mlam - mdlam * 0.5) but this is not faster */
605 for (j = 0; j <= pix0; j++) {
606 double sinc_y_9 = 0.0; /* Avoid uninit warning */
607 for (i = 0; i <= j; i++) {
608 if (i == 0 && j == 0) {
609 otf_data[size * pix0 + pix0] = 1.0;
610 } else {
611 const double x = (double)i;
612 const double y = (double)j;
613 const double sqdist = x * x + y * y;
614 double f_lambda = 0.0;
615 double sinc_xy_9 = 0.0; /* Zero if OTF is zero */
616 double otfxy = 0.0;
617 int k;
618
619 assert( j > 0 );
620
621 /* 9 iterations on the wavelength */
622 /* Unrolling the loop is not faster (due to the break?) */
623 for (k = 4; k >= -4; k--) {
624 /* Compute intermediate cut-off frequency */
625 const double lambda = lam - dlam * (double)k / 8.0;
626
627 /* A decreasing k ensures that we either enter on the first
628 iteration or not at all */
629 if (sqdist * lambda * lambda >= f_max * f_max) break;
630
631 if (k == 4) {
632 f_lambda = sqrt(sqdist) / f_max;
633 if (i == 0) {
634 /* Sinc(x = 0) == 1 */
635 sinc_xy_9 = sinc_y_9 =
636 PSF_sinc_norm(y / (double)size) / 9.0;
637 } else {
638 sinc_xy_9 = sinc_y_9 *
639 PSF_sinc_norm(x / (double)size);
640 }
641 }
642
643 otfxy += PSF_TelOTF(f_lambda * lambda, obs_ratio);
644 }
645 otfxy *= sinc_xy_9;
646
647 /* When i == j the same value is written to the same
648 position twice. That's probably faster than a guard */
649 otf_data[size * (pix0 - j) + pix0 - i] = otfxy;
650 otf_data[size * (pix0 - i) + pix0 - j] = otfxy;
651 if (i < pix0) {
652 otf_data[size * (pix0 - j) + pix0 + i] = otfxy;
653 otf_data[size * (pix0 + i) + pix0 - j] = otfxy;
654 if (j < pix0) {
655 otf_data[size * (pix0 + j) + pix0 - i] = otfxy;
656 otf_data[size * (pix0 - i) + pix0 + j] = otfxy;
657 otf_data[size * (pix0 + j) + pix0 + i] = otfxy;
658 otf_data[size * (pix0 + i) + pix0 + j] = otfxy;
659 }
660 }
661 }
662 }
663 }
664
665 return cpl_image_wrap_double(size, size, otf_data);
666}
667
668/*----------------------------------------------------------------------------*
669 * H1 function
670 *----------------------------------------------------------------------------*/
671static double PSF_H1(
672 double f,
673 double u,
674 double v)
675{
676 const double e = fabs(1.0-v) > 0.0 ? -1.0 : 1.0; /* e = 1.0 iff v = 1.0 */
677
678 return((v*v/CPL_MATH_PI)*acos((f/v)*(1.0+e*(1.0-u*u)/(4.0*f*f))));
679}
680
681/*----------------------------------------------------------------------------*
682 * H2 function
683 *----------------------------------------------------------------------------*/
684static double PSF_H2(double f,
685 double u)
686{
687 const double tmp1 = (2.0 * f) / (1.0 + u);
688 const double tmp2 = (1.0 - u) / (2.0 * f);
689
690 return -1.0 * (f/CPL_MATH_PI) * (1.0+u)
691 * sqrt((1.0-tmp1*tmp1)*(1.0-tmp2*tmp2));
692}
693
694/*----------------------------------------------------------------------------*
695 * G function
696 *----------------------------------------------------------------------------*/
697static double PSF_G(double f,
698 double u)
699{
700 if (f <= (1.0-u)/2.0) return(u*u);
701 if (f >= (1.0+u)/2.0) return(0.0);
702 else return(PSF_H1(f,u,1.0) + PSF_H1(f,u,u) + PSF_H2(f,u));
703}
704
705/*----------------------------------------------------------------------------*/
713/*----------------------------------------------------------------------------*/
714static double PSF_sinc_norm(double x)
715{
716 /* This static function should not be called with zero, but handle it
717 anyway. */
718 return x != 0.0 ? sin(x * CPL_MATH_PI) / (x * CPL_MATH_PI) : 1.0;
719}
720
721/*----------------------------------------------------------------------------*
722 * Telescope OTF function
723 *----------------------------------------------------------------------------*/
724static double PSF_TelOTF(double f,
725 double u)
726{
727 return((PSF_G(f,1.0)+u*u*PSF_G(f/u,1.0)-2.0*PSF_G(f,u))/(1.0-u*u));
728}
729
730/*----------------------------------------------------------------------------*/
741/*----------------------------------------------------------------------------*/
742cpl_error_code irplib_strehl_disk_max(const cpl_image * self,
743 double xpos,
744 double ypos,
745 double radius,
746 double * ppeak)
747{
748
749 const int nx = cpl_image_get_size_x(self);
750 const int ny = cpl_image_get_size_y(self);
751 /* Round down */
752 const int lx = (int)(xpos - radius);
753 const int ly = (int)(ypos - radius);
754 /* Round up */
755 const int ux = (int)(xpos + radius) + 1;
756 const int uy = (int)(ypos + radius) + 1;
757
758 const double sqr = radius * radius;
759 cpl_boolean first = CPL_TRUE;
760 int i, j;
761
762
763 /* Check entries */
764 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
765 cpl_ensure_code(ppeak != NULL, CPL_ERROR_NULL_INPUT);
766 cpl_ensure_code(radius > 0.0, CPL_ERROR_ILLEGAL_INPUT);
767
768
769 for (j = IRPLIB_MAX(ly, 0); j < IRPLIB_MIN(uy, ny); j++) {
770 const double yj = (double)j - ypos;
771 for (i = IRPLIB_MAX(lx, 0); i < IRPLIB_MIN(ux, nx); i++) {
772 const double xi = (double)i - xpos;
773 const double dist = yj * yj + xi * xi;
774 if (dist <= sqr) {
775 int isbad;
776 const double value = cpl_image_get(self, i+1, j+1, &isbad);
777
778 if (!isbad &&
779 (first || value > *ppeak)) {
780 first = CPL_FALSE;
781 *ppeak = value;
782 }
783 }
784 }
785 }
786
787 return first
788 ? cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND)
789 : CPL_ERROR_NONE;
790}
791
792#ifndef IRPLIB_NO_FIT_GAUSSIAN
793#ifdef IRPLIB_STREHL_USE_CPL_IMAGE_FIT_GAUSSIAN
794/*----------------------------------------------------------------------------*/
810/*----------------------------------------------------------------------------*/
811static double irplib_gaussian_2d(double x,
812 double y,
813 double norm,
814 double sig_x,
815 double sig_y)
816{
817
818 /* Copied from CPL */
819 return norm / (sig_x * sig_y * CPL_MATH_2PI *
820 exp(x * x / (2.0 * sig_x * sig_x) +
821 y * y / (2.0 * sig_y * sig_y)));
822}
823#endif
824
825#if defined CPL_VERSION_CODE && CPL_VERSION_CODE >= CPL_VERSION(6, 9, 1)
826#else
827/*----------------------------------------------------------------------------*/
846/*----------------------------------------------------------------------------*/
847static
848double irplib_gaussian_eval_2d(const cpl_array * self, double x, double y)
849{
850 cpl_errorstate prestate = cpl_errorstate_get();
851 const double B = cpl_array_get_double(self, 0, NULL);
852 const double A = cpl_array_get_double(self, 1, NULL);
853 const double R = cpl_array_get_double(self, 2, NULL);
854 const double M_x = cpl_array_get_double(self, 3, NULL);
855 const double M_y = cpl_array_get_double(self, 4, NULL);
856 const double S_x = cpl_array_get_double(self, 5, NULL);
857 const double S_y = cpl_array_get_double(self, 6, NULL);
858
859 double value = 0.0;
860
861 if (!cpl_errorstate_is_equal(prestate)) {
862 (void)cpl_error_set_where(cpl_func);
863 } else if (cpl_array_get_size(self) != 7) {
864 (void)cpl_error_set(cpl_func, CPL_ERROR_ILLEGAL_INPUT);
865 } else if (fabs(R) < 1.0 && S_x != 0.0 && S_y != 0.0) {
866 const double x_n = (x - M_x) / S_x;
867 const double y_n = (y - M_y) / S_y;
868
869 value = B + A / (CPL_MATH_2PI * S_x * S_y * sqrt(1 - R * R)) *
870 exp(-0.5 / (1 - R * R) * ( x_n * x_n + y_n * y_n
871 - 2.0 * R * x_n * y_n));
872 } else if (fabs(R) > 1.0) {
873 (void)cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_OUTPUT,
874 "fabs(R=%g) > 1", R);
875 } else {
876 (void)cpl_error_set_message(cpl_func, CPL_ERROR_DIVISION_BY_ZERO,
877 "R=%g. Sigma=(%g, %g)", R, S_x, S_y);
878 }
879
880 return value;
881}
882#endif
883
884/*----------------------------------------------------------------------------*/
891/*----------------------------------------------------------------------------*/
892static uint32_t irplib_roundup_power2(uint32_t v)
893{
894 v |= v >> 1;
895 v |= v >> 2;
896 v |= v >> 4;
897 v |= v >> 8;
898 v |= v >> 16;
899
900 return v + 1;
901}
902
903
904static cpl_error_code
905irplib_closeset_aperture(const cpl_apertures * self,
906 const double x, const double y, int * ind){
907
908 double dist = INFINITY;
909
910 const int nsize = cpl_apertures_get_size(self);
911 int i;
912
913 cpl_ensure_code(nsize > 0, cpl_error_get_code());
914 cpl_ensure_code(ind, CPL_ERROR_NULL_INPUT);
915
916 *ind = -1;
917
918 for(i = 1; i <= nsize; ++i){
919 const double this_x = cpl_apertures_get_centroid_x(self, i);
920 const double this_y = cpl_apertures_get_centroid_y(self, i);
921 const double this_d_sq = pow(this_x - x, 2.0) + pow(this_y - y, 2.0);
922
923 if(this_d_sq < dist){
924 dist = this_d_sq;
925 *ind = i;
926 }
927
928 }
929 return CPL_ERROR_NONE;
930}
931
932/*----------------------------------------------------------------------------*/
943/*----------------------------------------------------------------------------*/
944static
945cpl_error_code irplib_gaussian_maxpos(const cpl_image * self,
946 double sigma,
947 double x_initial,
948 double y_initial,
949 double * pxpos,
950 double * pypos,
951 double * ppeak)
952{
953
954 const cpl_size nx = cpl_image_get_size_x(self);
955 const cpl_size ny = cpl_image_get_size_y(self);
956 int iretry = 3; /* Number retries with decreasing sigma */
957 int ifluxapert = 0;
958 double med_dist;
959 const double median = cpl_image_get_median_dev(self, &med_dist);
960 cpl_mask * selection;
961 cpl_size nlabels = 0;
962 cpl_image * labels = NULL;
963 cpl_apertures * aperts;
964 cpl_size npixobj;
965 double objradius;
966 cpl_size winsize;
967 cpl_size xposmax, yposmax;
968 double xposcen, yposcen;
969 double valmax, valfit = -1.0;
970#ifdef IRPLIB_STREHL_USE_CPL_IMAGE_FIT_GAUSSIAN
971 double norm, xcen, ycen, sig_x, sig_y, fwhm_x, fwhm_y;
972#endif
973 cpl_array * gauss_parameters = NULL;
974 cpl_errorstate prestate = cpl_errorstate_get();
975 cpl_error_code code = CPL_ERROR_NONE;
976
977
978 cpl_ensure_code( sigma > 0.0, CPL_ERROR_ILLEGAL_INPUT);
979
980 selection = cpl_mask_new(nx, ny);
981
982 for (; iretry > 0 && nlabels == 0; iretry--, sigma *= 0.5) {
983
984 /* Compute the threshold */
985 const double threshold = median + sigma * med_dist;
986
987
988 /* Select the pixel above the threshold */
989 code = cpl_mask_threshold_image(selection, self, threshold, DBL_MAX,
990 CPL_BINARY_1);
991
992 if (code) break;
993
994 /* Labelise the thresholded selection */
995 cpl_image_delete(labels);
996 labels = cpl_image_labelise_mask_create(selection, &nlabels);
997 }
998 sigma *= 2.0; /* FIXME: unelegant */
999
1000 cpl_mask_delete(selection);
1001
1002 if (code) {
1003 cpl_image_delete(labels);
1004 return cpl_error_set_where(cpl_func);
1005 } else if (nlabels == 0) {
1006 cpl_image_delete(labels);
1007 return cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
1008 }
1009
1010 aperts = cpl_apertures_new_from_image(self, labels);
1011
1012 /* Find the aperture closest to the provided coordinates */
1013 code = irplib_closeset_aperture(aperts, x_initial, y_initial, &ifluxapert);
1014
1015 if (code) {
1016 cpl_apertures_delete(aperts);
1017 cpl_image_delete(labels);
1018 return cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
1019 }
1020
1021 npixobj = cpl_apertures_get_npix(aperts, ifluxapert);
1022 objradius = sqrt((double)npixobj * CPL_MATH_1_PI);
1023 /* Size is power of two for future noise filtering w. fft */
1024 winsize = IRPLIB_MIN(IRPLIB_MIN(nx, ny), irplib_roundup_power2
1025 ((uint32_t)(3.0 * objradius + 0.5)));
1026
1027 xposmax = cpl_apertures_get_maxpos_x(aperts, ifluxapert);
1028 yposmax = cpl_apertures_get_maxpos_y(aperts, ifluxapert);
1029 xposcen = cpl_apertures_get_centroid_x(aperts, ifluxapert);
1030 yposcen = cpl_apertures_get_centroid_y(aperts, ifluxapert);
1031 valmax = cpl_apertures_get_max(aperts, ifluxapert);
1032
1033 cpl_apertures_delete(aperts);
1034 cpl_image_delete(labels);
1035
1036 cpl_msg_debug(cpl_func, "Object radius at S/R=%g: %g (window-size=%u)",
1037 sigma, objradius, (unsigned)winsize);
1038 cpl_msg_debug(cpl_func, "Object-peak @ (%d, %d) = %g", (int)xposmax,
1039 (int)yposmax, valmax);
1040
1041 gauss_parameters = cpl_array_new(7, CPL_TYPE_DOUBLE);
1042 cpl_array_set_double(gauss_parameters, 0, median);
1043
1044 code = cpl_fit_image_gaussian(self, NULL, xposcen, yposcen,
1045 winsize, winsize, gauss_parameters,
1046 NULL, NULL, NULL,
1047 NULL, NULL, NULL,
1048 NULL, NULL, NULL);
1049 if (!code) {
1050 const double M_x = cpl_array_get_double(gauss_parameters, 3, NULL);
1051 const double M_y = cpl_array_get_double(gauss_parameters, 4, NULL);
1052
1053 valfit = irplib_gaussian_eval_2d(gauss_parameters, M_x, M_y);
1054
1055 if (!cpl_errorstate_is_equal(prestate)) {
1056 code = cpl_error_get_code();
1057 } else {
1058 *pxpos = M_x;
1059 *pypos = M_y;
1060 *ppeak = valfit;
1061
1062 cpl_msg_debug(cpl_func, "Gauss-fit @ (%g, %g) = %g",
1063 M_x, M_y, valfit);
1064 }
1065 }
1066 cpl_array_delete(gauss_parameters);
1067
1068#ifdef IRPLIB_STREHL_USE_CPL_IMAGE_FIT_GAUSSIAN
1069 if (code || valfit < valmax) {
1070 cpl_errorstate_set(prestate);
1071
1072 code = cpl_image_fit_gaussian(self, xposcen, yposcen,
1073 (int)(2.0 * objradius),
1074 &norm,
1075 &xcen,
1076 &ycen,
1077 &sig_x,
1078 &sig_y,
1079 &fwhm_x,
1080 &fwhm_y);
1081
1082 if (!code) {
1083 valfit = irplib_gaussian_2d(0.0, 0.0, norm, sig_x, sig_y);
1084
1085 cpl_msg_debug(cpl_func, "Gauss-Fit @ (%g, %g) = %g. norm=%g, "
1086 "sigma=(%g, %g)", xcen, ycen, valfit, norm,
1087 sig_x, sig_y);
1088
1089 if (valfit > valmax) {
1090 *pxpos = xcen;
1091 *pypos = ycen;
1092 *ppeak = valfit;
1093 }
1094 }
1095 }
1096#endif
1097
1098 if (code || valfit < valmax) {
1099 cpl_errorstate_set(prestate);
1100 *pxpos = xposcen;
1101 *pypos = yposcen;
1102 *ppeak = valmax;
1103 }
1104
1105 return code ? cpl_error_set_where(cpl_func) : CPL_ERROR_NONE;
1106}
1107#endif