36 #include "irplib_strehl.h"
37 #include "irplib_utils.h"
53 #ifndef IRPLIB_STREHL_RAD_CENTRAL
54 #define IRPLIB_STREHL_RAD_CENTRAL 5
57 #ifndef IRPLIB_STREHL_DETECT_LEVEL
58 #define IRPLIB_STREHL_DETECT_LEVEL 5.0
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
66 #define IRPLIB_MIN CPL_MIN
68 #define IRPLIB_MIN(A,B) (((A) < (B)) ? (A) : (B))
72 #define IRPLIB_MAX CPL_MAX
74 #define IRPLIB_MAX(A,B) (((A) > (B)) ? (A) : (B))
81 static cpl_image * irplib_strehl_generate_otf(
double,
double,
double,
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);
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);
94 #if defined CPL_VERSION_CODE && CPL_VERSION_CODE >= CPL_VERSION(6, 9, 1)
95 #define irplib_gaussian_eval_2d cpl_gaussian_eval_2d
97 static double irplib_gaussian_eval_2d(
const cpl_array *,
double,
double);
100 static uint32_t irplib_roundup_power2(uint32_t v) CPL_ATTR_CONST;
103 cpl_error_code irplib_gaussian_maxpos(
const cpl_image *,
146 cpl_error_code irplib_strehl_compute(
const cpl_image * im,
170 double star_radius, max_radius;
173 const double window_size = (double)(IRPLIB_STREHL_RAD_CENTRAL);
176 const double strehl_error_coefficient = CPL_MATH_PI * 0.007 / 0.0271;
180 #ifndef IRPLIB_NO_FIT_GAUSSIAN
181 double xposfit = 0.0, yposfit = 0.0, peak = 0.0;
184 cpl_errorstate prestate = cpl_errorstate_get();
187 cpl_ensure_code(window_size > 0.0, CPL_ERROR_ILLEGAL_INPUT);
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);
199 cpl_ensure_code(pscale > 0.0, CPL_ERROR_ILLEGAL_INPUT);
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);
209 psf = irplib_strehl_generate_psf(m1, m2, lam, dlam, pscale, size);
211 return cpl_error_set_where(cpl_func);
215 *psf_peak = cpl_image_get_max(psf);
216 cpl_image_delete(psf);
218 assert( *psf_peak > 0.0);
221 #ifndef IRPLIB_NO_FIT_GAUSSIAN
222 code = irplib_gaussian_maxpos(im, IRPLIB_STREHL_DETECT_LEVEL,
223 &xposfit, &yposfit, &peak);
225 cpl_errorstate_set(prestate);
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);
241 star_radius = r1/pscale;
244 *star_flux = irplib_strehl_disk_flux(im, xpos, ypos, star_radius, *star_bg);
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);
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;
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);
267 *strehl = (*star_peak * *psf_flux ) / ( *star_flux * *psf_peak);
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,
275 *strehl *= peak / *star_peak;
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);
291 return cpl_error_set_where(cpl_func);
294 *strehl_err = strehl_error_coefficient * (*bg_noise) * pscale *
295 star_radius * star_radius / *star_flux;
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,
305 return *strehl_err >= 0.0
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);
328 double irplib_strehl_disk_flux(
const cpl_image * im,
334 const int nx = cpl_image_get_size_x(im);
335 const int ny = cpl_image_get_size_y(im);
337 const int lx = (int)(xpos - rad);
338 const int ly = (int)(ypos - rad);
340 const int ux = (int)(xpos + rad) + 1;
341 const int uy = (int)(ypos + rad) + 1;
343 const double sqr = rad * rad;
349 cpl_ensure(im != NULL, CPL_ERROR_NULL_INPUT, 0.0);
350 cpl_ensure(rad > 0.0, CPL_ERROR_ILLEGAL_INPUT, 0.0);
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;
359 const double value = cpl_image_get(im, i+1, j+1, &isbad);
386 double irplib_strehl_ring_background(
const cpl_image * im,
391 irplib_strehl_bg_method mode)
393 const int nx = cpl_image_get_size_x(im);
394 const int ny = cpl_image_get_size_y(im);
396 const int lx = (int)(xpos - rad_ext);
397 const int ly = (int)(ypos - rad_ext);
399 const int ux = (int)(xpos + rad_ext) + 1;
400 const int uy = (int)(ypos + rad_ext) + 1;
402 const double sqr_int = rad_int * rad_int;
403 const double sqr_ext = rad_ext * rad_ext;
404 cpl_vector * pix_arr;
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);
413 cpl_ensure(mode == IRPLIB_BG_METHOD_AVER_REJ ||
414 mode == IRPLIB_BG_METHOD_MEDIAN,
415 CPL_ERROR_UNSUPPORTED_MODE, 0.0);
417 mpix = (int)((2.0 * rad_ext + 1.0) * (2.0 * rad_ext + 1.0));
420 pix_arr = cpl_vector_new(mpix);
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) {
432 const double value = cpl_image_get(im, i+1, j+1, &isbad);
435 cpl_vector_set(pix_arr, npix, value);
442 assert(npix <= mpix);
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,
456 pix_arr = cpl_vector_wrap(npix, (
double*)cpl_vector_unwrap(pix_arr));
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));
464 cpl_vector_sort(pix_arr, CPL_SORT_ASCENDING);
466 for (i=low_ind; i<high_ind; i++) {
467 flux += cpl_vector_get(pix_arr, i);
469 if (high_ind - low_ind > 1) flux /= (double)(high_ind - low_ind);
471 flux = cpl_vector_get_median(pix_arr);
474 cpl_vector_delete(pix_arr);
500 cpl_image * irplib_strehl_generate_psf(
double m1,
507 cpl_image * otf_image = irplib_strehl_generate_otf(m1, m2, lam, dlam,
510 if (otf_image == NULL ||
521 cpl_image_fft(otf_image, NULL, CPL_FFT_UNNORMALIZED) ||
524 cpl_image_abs(otf_image) ||
527 cpl_image_normalise(otf_image, CPL_NORM_FLUX)) {
529 (void)cpl_error_set_where(cpl_func);
530 cpl_image_delete(otf_image);
556 static cpl_image * irplib_strehl_generate_otf(
double m1,
565 const double obs_ratio = m1 != 0.0 ? m2 / m1 : 0.0;
567 const double rpscale = pscale * CPL_MATH_2PI / (double)(360 * 60 * 60);
569 const double f_max = m1 * rpscale * (double)size;
572 const int pix0 = size / 2;
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);
582 cpl_ensure(size % 2 == 0, CPL_ERROR_ILLEGAL_INPUT, NULL);
585 cpl_ensure(2.0 * lam > dlam, CPL_ERROR_ILLEGAL_INPUT, NULL);
592 otf_data = (
double*)cpl_malloc(size * size *
sizeof(*otf_data));
599 for (j = 0; j <= pix0; j++) {
600 double sinc_y_9 = 0.0;
601 for (i = 0; i <= j; i++) {
602 if (i == 0 && j == 0) {
603 otf_data[size * pix0 + pix0] = 1.0;
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;
617 for (k = 4; k >= -4; k--) {
619 const double lambda = lam - dlam * (double)k / 8.0;
623 if (sqdist * lambda * lambda >= f_max * f_max)
break;
626 f_lambda = sqrt(sqdist) / f_max;
629 sinc_xy_9 = sinc_y_9 =
630 PSF_sinc_norm(y / (
double)size) / 9.0;
632 sinc_xy_9 = sinc_y_9 *
633 PSF_sinc_norm(x / (
double)size);
637 otfxy += PSF_TelOTF(f_lambda * lambda, obs_ratio);
643 otf_data[size * (pix0 - j) + pix0 - i] = otfxy;
644 otf_data[size * (pix0 - i) + pix0 - j] = otfxy;
646 otf_data[size * (pix0 - j) + pix0 + i] = otfxy;
647 otf_data[size * (pix0 + i) + pix0 - j] = otfxy;
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;
659 return cpl_image_wrap_double(size, size, otf_data);
665 static double PSF_H1(
670 const double e = fabs(1.0-v) > 0.0 ? -1.0 : 1.0;
672 return((v*v/CPL_MATH_PI)*acos((f/v)*(1.0+e*(1.0-u*u)/(4.0*f*f))));
678 static double PSF_H2(
double f,
681 const double tmp1 = (2.0 * f) / (1.0 + u);
682 const double tmp2 = (1.0 - u) / (2.0 * f);
684 return -1.0 * (f/CPL_MATH_PI) * (1.0+u)
685 * sqrt((1.0-tmp1*tmp1)*(1.0-tmp2*tmp2));
691 static double PSF_G(
double f,
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));
708 static double PSF_sinc_norm(
double x)
710 return sin(x * CPL_MATH_PI) / (x * CPL_MATH_PI);
716 static double PSF_TelOTF(
double f,
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));
734 cpl_error_code irplib_strehl_disk_max(
const cpl_image *
self,
741 const int nx = cpl_image_get_size_x(
self);
742 const int ny = cpl_image_get_size_y(
self);
744 const int lx = (int)(xpos - radius);
745 const int ly = (int)(ypos - radius);
747 const int ux = (int)(xpos + radius) + 1;
748 const int uy = (int)(ypos + radius) + 1;
750 const double sqr = radius * radius;
751 cpl_boolean first = CPL_TRUE;
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);
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;
768 const double value = cpl_image_get(
self, i+1, j+1, &isbad);
771 (first || value > *ppeak)) {
780 ? cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND)
784 #ifndef IRPLIB_NO_FIT_GAUSSIAN
785 #ifdef IRPLIB_STREHL_USE_CPL_IMAGE_FIT_GAUSSIAN
803 static double irplib_gaussian_2d(
double x,
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)));
817 #if defined CPL_VERSION_CODE && CPL_VERSION_CODE >= CPL_VERSION(6, 9, 1)
840 double irplib_gaussian_eval_2d(
const cpl_array *
self,
double x,
double y)
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);
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;
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);
868 (void)cpl_error_set_message(cpl_func, CPL_ERROR_DIVISION_BY_ZERO,
869 "R=%g. Sigma=(%g, %g)", R, S_x, S_y);
884 static uint32_t irplib_roundup_power2(uint32_t v)
909 cpl_error_code irplib_gaussian_maxpos(
const cpl_image *
self,
916 const cpl_size nx = cpl_image_get_size_x(
self);
917 const cpl_size ny = cpl_image_get_size_y(
self);
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;
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;
935 cpl_array * gauss_parameters = NULL;
936 cpl_errorstate prestate = cpl_errorstate_get();
937 cpl_error_code code = CPL_ERROR_NONE;
940 cpl_ensure_code( sigma > 0.0, CPL_ERROR_ILLEGAL_INPUT);
942 selection = cpl_mask_new(nx, ny);
944 for (; iretry > 0 && nlabels == 0; iretry--, sigma *= 0.5) {
947 const double threshold = median + sigma * med_dist;
951 code = cpl_mask_threshold_image(selection,
self, threshold, DBL_MAX,
957 cpl_image_delete(labels);
958 labels = cpl_image_labelise_mask_create(selection, &nlabels);
962 cpl_mask_delete(selection);
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);
972 aperts = cpl_apertures_new_from_image(
self, labels);
978 cpl_apertures_delete(aperts);
979 cpl_image_delete(labels);
980 return cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
983 npixobj = cpl_apertures_get_npix(aperts, ifluxapert);
984 objradius = sqrt((
double)npixobj * CPL_MATH_1_PI);
986 winsize = IRPLIB_MIN(IRPLIB_MIN(nx, ny), irplib_roundup_power2
987 ((uint32_t)(3.0 * objradius + 0.5)));
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);
995 cpl_apertures_delete(aperts);
996 cpl_image_delete(labels);
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);
1003 gauss_parameters = cpl_array_new(7, CPL_TYPE_DOUBLE);
1004 cpl_array_set_double(gauss_parameters, 0, median);
1006 code = cpl_fit_image_gaussian(
self, NULL, xposcen, yposcen,
1007 winsize, winsize, gauss_parameters,
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);
1015 valfit = irplib_gaussian_eval_2d(gauss_parameters, M_x, M_y);
1017 if (!cpl_errorstate_is_equal(prestate)) {
1018 code = cpl_error_get_code();
1024 cpl_msg_debug(cpl_func,
"Gauss-fit @ (%g, %g) = %g",
1028 cpl_array_delete(gauss_parameters);
1030 #ifdef IRPLIB_STREHL_USE_CPL_IMAGE_FIT_GAUSSIAN
1031 if (code || valfit < valmax) {
1032 cpl_errorstate_set(prestate);
1034 code = cpl_image_fit_gaussian(
self, xposcen, yposcen,
1035 (
int)(2.0 * objradius),
1045 valfit = irplib_gaussian_2d(0.0, 0.0, norm, sig_x, sig_y);
1047 cpl_msg_debug(cpl_func,
"Gauss-Fit @ (%g, %g) = %g. norm=%g, "
1048 "sigma=(%g, %g)", xcen, ycen, valfit, norm,
1051 if (valfit > valmax) {
1060 if (code || valfit < valmax) {
1061 cpl_errorstate_set(prestate);
1067 return code ? cpl_error_set_where(cpl_func) : CPL_ERROR_NONE;
cpl_error_code irplib_apertures_find_max_flux(const cpl_apertures *self, int *ind, int nfind)
Find the aperture(s) with the greatest flux.