MUSE Pipeline Reference Manual  2.1.1
muse_findstars.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) 2001-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 <inttypes.h>
30 #include <complex.h>
31 #include <math.h>
32 
33 #include "muse_cplwrappers.h"
34 #include "muse_utils.h"
35 #include "muse_findstars.h"
36 
37 /*----------------------------------------------------------------------------*
38  * Debugging/feature Macros *
39  *----------------------------------------------------------------------------*/
40 #define DEBUG_MUSE_FINDSTARS 0 /* if 1 enable debugging of muse_find_stars() */
41 
42 /*----------------------------------------------------------------------------*/
53 /*----------------------------------------------------------------------------*/
54 
57 /*----------------------------------------------------------------------------*
58  * Candidate CPL extensions (use CPL coding style!) *
59  *----------------------------------------------------------------------------*/
60 
78 static cpl_matrix *
79 _cpl_matrix_sum_columns(const cpl_matrix *self)
80 {
81 
82  cpl_matrix *vector = NULL;
83 
84  if (self) {
85 
86  cpl_size nr = cpl_matrix_get_nrow(self);
87  cpl_size nc = cpl_matrix_get_ncol(self);
88 
89  vector = cpl_matrix_extract(self, 0, 0, 1, 1, 1, nc);
90 
91  const double *_self = cpl_matrix_get_data_const(self);
92 
93  double *_vector = cpl_matrix_get_data(vector);
94 
95  register cpl_size ir;
96 
97 
98  for (ir = 1; ir < nr; ++ir) {
99 
100  register cpl_size ic;
101 
102  for (ic = 0; ic < nc; ++ic) {
103  _vector[ic] += _self[ir * nc + ic];
104  }
105 
106  }
107 
108  }
109 
110  return vector;
111 
112 }
113 
114 
132 static cpl_matrix *
133 _cpl_matrix_sum_rows(const cpl_matrix *self)
134 {
135 
136  cpl_matrix *vector = NULL;
137 
138  if (self) {
139 
140  cpl_size nr = cpl_matrix_get_nrow(self);
141  cpl_size nc = cpl_matrix_get_ncol(self);
142 
143  vector = cpl_matrix_extract(self, 0, 0, 1, 1, nr, 1);
144 
145  const double *_self = cpl_matrix_get_data_const(self);
146 
147  double *_vector = cpl_matrix_get_data(vector);
148 
149  register cpl_size ir;
150 
151 
152  for (ir = 0; ir < nr; ++ir) {
153 
154  register cpl_size ic;
155 
156  for (ic = 1; ic < nc; ++ic) {
157  _vector[ir] += _self[ir * nc + ic];
158  }
159 
160  }
161 
162  }
163 
164  return vector;
165 
166 }
167 
168 
183 static double
184 _cpl_matrix_sum(const cpl_matrix *self)
185 {
186 
187  double sum = 0.;
188 
189  if (self) {
190 
191  register cpl_size i;
192 
193  cpl_size sz = cpl_matrix_get_nrow(self) * cpl_matrix_get_ncol(self);
194 
195  const double *_self = cpl_matrix_get_data_const(self);
196 
197 
198  for (i = 0; i < sz; ++i) {
199  sum += _self[i];
200  }
201 
202  }
203 
204  return sum;
205 
206 }
207 
208 
227 static cpl_error_code
228 _cpl_matrix_modulo(cpl_matrix *self, long value)
229 {
230 
231  if (value == 0.) {
232  return CPL_ERROR_DIVISION_BY_ZERO;
233  }
234 
235  cpl_size sz = cpl_matrix_get_nrow(self) * cpl_matrix_get_ncol(self);
236 
237  double *m = cpl_matrix_get_data(self);
238 
239 
240  while (sz--) {
241  *m = (long)(*m) % value;
242  ++m;
243  }
244 
245  return CPL_ERROR_NONE;
246 
247 }
248 
249 
250 /*----------------------------------------------------------------------------*
251  * Private Functions *
252  *----------------------------------------------------------------------------*/
253 
254 typedef int (*_cpl_matrix_element_compare_func)(double aValue1, double aValue2);
255 
256 
257 /*----------------------------------------------------------------------------*/
266 /*----------------------------------------------------------------------------*/
267 static int
268 _muse_condition_not_equal(double aValue1, double aValue2)
269 {
270  return (aValue1 != aValue2) ? TRUE : FALSE;
271 }
272 
273 /*----------------------------------------------------------------------------*/
282 /*----------------------------------------------------------------------------*/
283 static int
284 _muse_condition_greater_equal(double aValue1, double aValue2)
285 {
286  return (aValue1 >= aValue2) ? TRUE : FALSE;
287 }
288 
289 
290 /* XXX: Do style cleanup and improve documentation */
291 /*----------------------------------------------------------------------------*/
322 /*----------------------------------------------------------------------------*/
323 static int
324 _muse_centroid_offset(double *offset, unsigned short axis,
325  const cpl_matrix *mdata, const cpl_matrix *mwt,
326  const cpl_matrix *mwtxy, const cpl_matrix *msgxy,
327  const cpl_matrix *mdg, double sumg, double sumgsqr,
328  double sgdg, double sdg, double sdgs,
329  double p, double sigsqr)
330 {
331  double sddg = 0.;
332  double sumd = 0.;
333  double sumgd = 0.;
334 
335  if ((axis != 1) && (axis != 2)) {
336  return -1;
337  }
338 
339  cpl_matrix *mtmp = muse_cplmatrix_multiply_create(mdata, mwtxy);
340 
341  cpl_matrix *sd;
342  if (axis == 1) {
343  sd = _cpl_matrix_sum_columns(mtmp);
344  }
345  else {
346  cpl_matrix *_sd = _cpl_matrix_sum_rows(mtmp);
347  sd = cpl_matrix_transpose_create(_sd);
348  cpl_matrix_delete(_sd);
349  }
350  cpl_matrix_delete(mtmp);
351 
352 
353  mtmp = muse_cplmatrix_multiply_create(mwt, sd);
354  sumd = _cpl_matrix_sum(mtmp);
355  cpl_matrix_delete(mtmp);
356 
357  mtmp = muse_cplmatrix_multiply_create(mwt, msgxy);
358  cpl_matrix_multiply(mtmp, sd);
359  sumgd = _cpl_matrix_sum(mtmp);
360  cpl_matrix_delete(mtmp);
361 
362  mtmp = muse_cplmatrix_multiply_create(mwt, sd);
363  cpl_matrix_multiply(mtmp, mdg);
364  sddg = _cpl_matrix_sum(mtmp);
365  cpl_matrix_delete(mtmp);
366  cpl_matrix_delete(sd);
367 
368  /* Compute height of the best-fitting marginal Gaussian. If this is not *
369  * greater than 0, the centroid makes no sense */
370  double height = (sumgd - sumg * sumd / p) / (sumgsqr - (sumg * sumg) / p);
371  if (height <= 0.) {
372  return 1;
373  }
374 
375  double skylvl = (sumd - height * sumg) / p;
376  double _offset = (sgdg - (sddg - sdg * (height * sumg + skylvl * p))) /
377  (height * sdgs / sigsqr);
378 
379  if (isnan(_offset)) {
380  return 2;
381  }
382 
383  *offset =_offset;
384  return 0;
385 }
386 
387 
388 /* Type independent shortcut for numerous computations of the square of *
389  * a value in the following function. */
390 #define MUSE_SQR(x) ((x) * (x))
391 
392 /*----------------------------------------------------------------------------*/
453 /*----------------------------------------------------------------------------*/
454 cpl_table *
455 muse_find_stars(const cpl_image *aImage, double aHmin, double aFwhm,
456  const double *aRoundLimits, const double *aSharpLimits)
457 {
458  cpl_ensure(aImage, CPL_ERROR_NULL_INPUT, NULL);
459 
460  /* Maximum size of the convolution box in pixels */
461  const int kMaxBox = 13;
462 
463  /* Default values for sharpness and roundness statistics */
464  const double kSharpLimits[2] = {0.2, 1.};
465  const double kRoundLimits[2] = {-1., 1.};
466 
467  cpl_size nx = cpl_image_get_size_x(aImage);
468  cpl_size ny = cpl_image_get_size_y(aImage);
469 
470  cpl_ensure(nx > 0 && ny > 0, CPL_ERROR_ILLEGAL_INPUT, NULL);
471  cpl_ensure(nx % 2 == 0, CPL_ERROR_ILLEGAL_INPUT, NULL);
472  cpl_ensure(aFwhm >= 0.5, CPL_ERROR_ILLEGAL_INPUT, NULL);
473 
474 
475  /* If no sharpness and/or roundness limits are given use the built-in *
476  * defaults */
477  if (!aRoundLimits) {
478  aRoundLimits = kRoundLimits;
479  }
480 
481  if (!aSharpLimits) {
482  aSharpLimits = kSharpLimits;
483  }
484 
485  cpl_msg_debug(__func__, "Settings: FWHM = %g, HMIN = %g, "
486  "Roundness limits = [%g, %g], Sharpness limits = [%g, %g]",
487  aFwhm, aHmin, aRoundLimits[0], aRoundLimits[1],
488  aSharpLimits[0], aSharpLimits[1]);
489 
490  const double radius = CPL_MAX(0.637 * aFwhm, 2.001); /* radius is 1.5 sigma */
491  const double radsqr = MUSE_SQR(radius);
492  const double sigsqr = MUSE_SQR(aFwhm / 2.35482);
493 
494  cpl_size ipos;
495  cpl_size nhalf = (cpl_size)CPL_MIN(radius, 0.5 * (kMaxBox - 1));
496 
497  /* Edge length of the convolution box in pixels */
498  cpl_size nbox = 2 * nhalf + 1;
499 
500  /* Initial setup of the Gaussian convolution kernel */
501  double *row2 = cpl_malloc(nbox * sizeof *row2);
502  for (ipos = 0; ipos < nbox; ++ipos) {
503  row2[ipos] = MUSE_SQR(ipos - nhalf);
504  }
505 
506  cpl_matrix *ckernel = cpl_matrix_new(nbox, nbox);
507  for (ipos = 0; ipos <= nhalf; ++ipos) {
508  cpl_size jpos;
509  for (jpos = 0; jpos < nbox; ++jpos) {
510  register double tmp = row2[jpos] + MUSE_SQR(ipos);
511  cpl_matrix_set(ckernel, nhalf - ipos, jpos, tmp);
512  cpl_matrix_set(ckernel, nhalf + ipos, jpos, tmp);
513  }
514  }
515 
516  /* Mask identifies valid pixels in the convolution box */
517  cpl_matrix *mask = cpl_matrix_new(nbox, nbox);
518 
519  for (ipos = 0; ipos < nbox; ++ipos) {
520  cpl_size jpos;
521  for (jpos = 0; jpos < nbox; ++jpos) {
522  if (cpl_matrix_get(ckernel, ipos, jpos) > radsqr) {
523  cpl_matrix_set(mask, ipos, jpos, 0.);
524  }
525  else {
526  cpl_matrix_set(mask, ipos, jpos, 1.);
527  }
528  }
529  }
530 
531  cpl_array *positions = muse_cplmatrix_where(mask, 0.,
532  _muse_condition_not_equal);
533  cpl_size npixels = cpl_array_get_size(positions);
534 
535  cpl_array_delete(positions);
536  positions = NULL;
537 
538  /* Compute quantities for centroid computations that can be *
539  * used for all stars */
540  cpl_matrix_multiply_scalar(ckernel, -0.5 / sigsqr);
541  cpl_matrix_exponential(ckernel, CPL_MATH_E);
542 
543  /* In fitting Gaussians to the marginal sums, pixels will arbitrarily be *
544  * assigned weights ranging from unity at the corners of the box to *
545  * nhalf^2 at the center (e.g. if nbox = 5 or 7, the weights will be *
546  * *
547  * 1 2 3 4 3 2 1 *
548  * 1 2 3 2 1 2 4 6 8 6 4 2 *
549  * 2 4 6 4 2 3 6 9 12 9 6 3 *
550  * 3 6 9 6 3 4 8 12 16 12 8 4 *
551  * 2 4 6 4 2 3 6 9 12 9 6 3 *
552  * 1 2 3 2 1 2 4 6 8 6 4 2 *
553  * 1 2 3 4 3 2 1 *
554  * *
555  * respectively). This is done to desensitize the derived parameters to *
556  * possible neighboring, brighter stars. */
557 
558  cpl_matrix *xwt = cpl_matrix_new(nbox, nbox);
559 
560  for (ipos = 0; ipos < nbox; ++ipos) {
561  cpl_size jpos;
562  double tmp = nhalf - imaxabs(ipos - nhalf) + 1;
563  for(jpos = 0; jpos < nbox; ++jpos) {
564  cpl_matrix_set(xwt, jpos, ipos, tmp);
565  }
566  }
567  cpl_matrix *ywt = cpl_matrix_transpose_create(xwt);
568  cpl_matrix *wt = cpl_matrix_extract_row(xwt, 0);
569  double p = _cpl_matrix_sum(wt);
570 
571  cpl_matrix *mtmp = NULL;
572 
573  mtmp = muse_cplmatrix_multiply_create(ckernel, xwt);
574  cpl_matrix *sgx = _cpl_matrix_sum_rows(mtmp);
575  cpl_matrix_delete(mtmp);
576 
577  /* Turn sgx into a row vector for the following computations */
578  mtmp = sgx;
579  sgx = cpl_matrix_transpose_create(mtmp);
580  cpl_matrix_delete(mtmp);
581 
582  mtmp = muse_cplmatrix_multiply_create(ckernel, ywt);
583  cpl_matrix *sgy = _cpl_matrix_sum_columns(mtmp);
584  cpl_matrix_delete(mtmp);
585 
586  cpl_matrix *wsgy = muse_cplmatrix_multiply_create(wt, sgy);
587  double sumgx = _cpl_matrix_sum(wsgy);
588 
589  mtmp = muse_cplmatrix_multiply_create(wsgy, sgy);
590  double sumgsqy = _cpl_matrix_sum(mtmp);
591  cpl_matrix_delete(mtmp);
592 
593  cpl_matrix *wsgx = muse_cplmatrix_multiply_create(wt, sgx);
594  double sumgy = _cpl_matrix_sum(wsgx);
595 
596  mtmp = muse_cplmatrix_multiply_create(wsgx, sgx);
597  double sumgsqx = _cpl_matrix_sum(mtmp);
598  cpl_matrix_delete(mtmp);
599 
600 
601  cpl_matrix *vec = cpl_matrix_new(1, nbox);
602  for (ipos = 0; ipos < nbox; ++ipos) {
603  cpl_matrix_set(vec, 0, ipos, nhalf - ipos);
604  }
605  cpl_matrix *dgdx = muse_cplmatrix_multiply_create(sgy, vec);
606  cpl_matrix *dgdy = muse_cplmatrix_multiply_create(sgx, vec);
607  cpl_matrix_delete(vec);
608 
609  cpl_matrix *wdgdx = muse_cplmatrix_multiply_create(wt, dgdx);
610  cpl_matrix *wdgdy = muse_cplmatrix_multiply_create(wt, dgdy);
611 
612  mtmp = muse_cplmatrix_multiply_create(dgdx, dgdx);
613  cpl_matrix *wdgdxs = muse_cplmatrix_multiply_create(wt, mtmp);
614  cpl_matrix_delete(mtmp);
615 
616  mtmp = muse_cplmatrix_multiply_create(dgdy, dgdy);
617  cpl_matrix *wdgdys = muse_cplmatrix_multiply_create(wt, mtmp);
618  cpl_matrix_delete(mtmp);
619 
620 
621  double sdgdx = _cpl_matrix_sum(wdgdx);
622  cpl_matrix_delete(wdgdx);
623  wdgdx = NULL;
624 
625  double sdgdy = _cpl_matrix_sum(wdgdy);
626  cpl_matrix_delete(wdgdy);
627  wdgdy = NULL;
628 
629  double sdgdxs = _cpl_matrix_sum(wdgdxs);
630  cpl_matrix_delete(wdgdxs);
631  wdgdxs = NULL;
632 
633  double sdgdys = _cpl_matrix_sum(wdgdys);
634  cpl_matrix_delete(wdgdys);
635  wdgdys = NULL;
636 
637  mtmp = muse_cplmatrix_multiply_create(wsgy, dgdx);
638  double sgdgdx = _cpl_matrix_sum(mtmp);
639  cpl_matrix_delete(mtmp);
640  cpl_matrix_delete(wsgy);
641  wsgy = NULL;
642 
643  mtmp = muse_cplmatrix_multiply_create(wsgx, dgdy);
644  double sgdgdy = _cpl_matrix_sum(mtmp);
645  cpl_matrix_delete(mtmp);
646  cpl_matrix_delete(wsgx);
647  wsgx = NULL;
648 
649  /* Finalize convolution kernel: Mask (i.e. set to zero) unused elements *
650  * in the kernel matrix, so that they do not contribute to the sums *
651  * computed in the following. *
652  * Note that as a last step the mask has to be re-applied to the *
653  * convolution kernel because subtracting 'sumc' from the kernel matrix *
654  * elements also alters the masked elememts so that they have to be set *
655  * to zero again in order to obtain a correct convolution kernel! */
656  cpl_matrix_multiply(ckernel, mask);
657 
658  double sumc = _cpl_matrix_sum(ckernel);
659 
660  mtmp = muse_cplmatrix_multiply_create(ckernel, ckernel);
661  double sumcsq = -MUSE_SQR(sumc) / npixels + _cpl_matrix_sum(mtmp);
662  sumc /= npixels;
663  cpl_matrix_delete(mtmp);
664 
665  cpl_matrix *c1 = cpl_matrix_new(1, nbox);
666  double sumc1 = 0.;
667  double sumc1sq = 0.;
668  for (ipos = 0; ipos < nbox; ++ipos) {
669  double tmp = exp(-0.5 * row2[ipos] / sigsqr);
670  sumc1 += tmp;
671  sumc1sq += MUSE_SQR(tmp);
672  cpl_matrix_set(c1, 0, ipos, tmp);
673  }
674  sumc1 /= nbox;
675  sumc1sq -= sumc1;
676  cpl_free(row2);
677  row2 = NULL;
678 
679  cpl_matrix_subtract_scalar(c1, sumc1);
680  cpl_matrix_divide_scalar(c1, sumc1sq);
681 
682  cpl_matrix_subtract_scalar(ckernel, sumc);
683  cpl_matrix_divide_scalar(ckernel, sumcsq);
684  cpl_matrix_multiply(ckernel, mask);
685 
686  mtmp = muse_cplmatrix_multiply_create(ckernel, ckernel);
687  cpl_msg_debug(__func__, "Relative error computed from the FWHM: %g",
688  sqrt(_cpl_matrix_sum(mtmp)));
689  cpl_matrix_delete(mtmp);
690 
691  /* Convolve the image with the kernel*/
692  cpl_msg_debug(__func__, "Beginning convolution of image.");
693  cpl_image *himage = muse_convolve_image(aImage, ckernel);
694 
695  cpl_matrix_delete(ckernel);
696  ckernel = NULL;
697 
698  if (!himage) {
699  cpl_matrix_delete(c1);
700  cpl_matrix_delete(dgdy);
701  cpl_matrix_delete(dgdx);
702  cpl_matrix_delete(sgy);
703  cpl_matrix_delete(sgx);
704  cpl_matrix_delete(wt);
705  cpl_matrix_delete(ywt);
706  cpl_matrix_delete(xwt);
707  cpl_matrix_delete(mask);
708 
709  cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
710  "Convolution of the input image failed!");
711  return NULL;
712  }
713 
714 #if DEBUG_MUSE_FINDSTARS > 0
715  cpl_image_save(himage, "convolved_image_initial.fits", CPL_TYPE_DOUBLE, NULL,
716  CPL_IO_CREATE);
717 #endif
718 
719  /* Pad an nhalf pixel wide border with the minimum value found in the *
720  * convoluted image on each side of the image. */
721  double hmin = cpl_image_get_min(himage);
722 
723  cpl_image_fill_window(himage, 1, 1, nhalf, ny, hmin);
724  cpl_image_fill_window(himage, nx - nhalf + 1, 1, nx, ny, hmin);
725  cpl_image_fill_window(himage, nhalf + 1, 1, nx - nhalf, nhalf, hmin);
726  cpl_image_fill_window(himage, nhalf + 1, ny - nhalf + 1, nx - nhalf, ny, hmin);
727 
728 #if DEBUG_MUSE_FINDSTARS > 0
729  cpl_image_save(himage, "convolved_image_padded.fits", CPL_TYPE_DOUBLE, NULL,
730  CPL_IO_CREATE);
731 #endif
732 
733  cpl_msg_debug(__func__, "Finished convolution of image.");
734 
735  /* From now on the central pixel will be excluded.*/
736  cpl_matrix_set(mask, nhalf, nhalf, 0.);
737  positions = muse_cplmatrix_where(mask, 0., _muse_condition_not_equal);
738  npixels = cpl_array_get_size(positions);
739 
740  /* Array offset of valid pixels relative to the center */
741  cpl_matrix *offset = cpl_matrix_new(1, npixels);
742  for (ipos = 0; ipos < npixels; ++ipos) {
743  cpl_matrix_set(offset, 0 , ipos,
744  cpl_array_get_cplsize(positions, ipos, NULL));
745  }
746 
747  cpl_array_delete(positions);
748  positions = NULL;
749 
750  cpl_matrix *xx = cpl_matrix_duplicate(offset);
751  _cpl_matrix_modulo(xx, nbox);
752 
753  mtmp = cpl_matrix_duplicate(xx);
754  cpl_matrix_multiply_scalar(mtmp, -1.);
755  cpl_matrix_add(offset, mtmp);
756  cpl_matrix_divide_scalar(offset, nbox);
757  cpl_matrix_add_scalar(offset, -nhalf);
758  cpl_matrix_delete(mtmp);
759 
760  cpl_matrix_add_scalar(xx, -nhalf);
761  cpl_matrix_multiply_scalar(offset, nx);
762  cpl_matrix_add(offset, xx);
763  cpl_matrix_delete(xx);
764  xx = NULL;
765 
766  /* Begin threshold dependent search */
767  cpl_matrix *hmatrix = cpl_matrix_wrap(ny, nx, cpl_image_get_data_double(himage));
768 
769  /* Valid pixels are greater than or equal to aHmin */
770  cpl_msg_debug(__func__, "Selecting pixels exceeding threshold value %.6g",
771  aHmin);
772 
773  positions = muse_cplmatrix_where(hmatrix, aHmin, _muse_condition_greater_equal);
774 
775  cpl_size nfound = cpl_array_get_size(positions);
776  if (nfound == 0) {
777  cpl_array_delete(positions);
778  cpl_matrix_unwrap(hmatrix);
779  cpl_matrix_delete(offset);
780  cpl_image_delete(himage);
781  cpl_matrix_delete(c1);
782  cpl_matrix_delete(dgdy);
783  cpl_matrix_delete(dgdx);
784  cpl_matrix_delete(sgy);
785  cpl_matrix_delete(sgx);
786  cpl_matrix_delete(wt);
787  cpl_matrix_delete(ywt);
788  cpl_matrix_delete(xwt);
789  cpl_matrix_delete(mask);
790 
791  cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
792  "No image pixel value exceeds threshold value!");
793  return NULL;
794  }
795  else {
796  cpl_msg_debug(__func__, "Found %" CPL_SIZE_FORMAT " pixels with values "
797  "above the threshold value %.6g", nfound, aHmin);
798  }
799 
800  for (ipos = 0; ipos < npixels; ++ipos) {
801 
802  /* Get values above the threshold */
803  cpl_matrix *hindex = muse_cplmatrix_extract_selected(hmatrix, positions);
804 
805  cpl_array *atmp = cpl_array_duplicate(positions);
806  cpl_array_add_scalar(atmp, cpl_matrix_get(offset, 0, ipos));
807 
808  cpl_matrix *hoffset = muse_cplmatrix_extract_selected(hmatrix, atmp);
809  cpl_array_delete(atmp);
810  atmp = NULL;
811 
812  cpl_size tsize = cpl_matrix_get_ncol(hoffset);
813  if (cpl_matrix_get_ncol(hindex) < tsize) {
814  tsize = cpl_matrix_get_ncol(hindex);
815  }
816 
817  nfound = 0;
818  cpl_size jpos = tsize;
819  while (jpos--) {
820  if (cpl_matrix_get(hindex, 0, jpos) < cpl_matrix_get(hoffset, 0, jpos)) {
821  cpl_array_set_invalid(positions, jpos);
822  }
823  else {
824  ++nfound;
825  }
826 
827  }
828  cpl_matrix_delete(hoffset);
829  cpl_matrix_delete(hindex);
830 
831  if (nfound == 0) {
832  cpl_array_delete(positions);
833  cpl_matrix_unwrap(hmatrix);
834  cpl_matrix_delete(offset);
835  cpl_image_delete(himage);
836  cpl_matrix_delete(c1);
837  cpl_matrix_delete(dgdy);
838  cpl_matrix_delete(dgdx);
839  cpl_matrix_delete(sgy);
840  cpl_matrix_delete(sgx);
841  cpl_matrix_delete(wt);
842  cpl_matrix_delete(ywt);
843  cpl_matrix_delete(xwt);
844  cpl_matrix_delete(mask);
845 
846  cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
847  "No local maximum exceeding the threshold value "
848  "was found!");
849  return NULL;
850  }
851 
852  muse_cplarray_erase_invalid(positions);
853  }
854  cpl_matrix_delete(offset);
855  offset = NULL;
856 
857  cpl_table *_sources = cpl_table_new(cpl_array_get_size(positions));
858 #if DEBUG_MUSE_FINDSTARS > 0
859  cpl_table_new_column(_sources, "xidx", CPL_TYPE_LONG_LONG);
860  cpl_table_new_column(_sources, "yidx", CPL_TYPE_LONG_LONG);
861 #endif
862  cpl_table_new_column(_sources, "X", CPL_TYPE_DOUBLE);
863  cpl_table_new_column(_sources, "Y", CPL_TYPE_DOUBLE);
864  cpl_table_new_column(_sources, "Flux", CPL_TYPE_DOUBLE);
865  cpl_table_new_column(_sources, "Sharpness", CPL_TYPE_DOUBLE);
866  cpl_table_new_column(_sources, "Roundness", CPL_TYPE_DOUBLE);
867 
868  /* Loop over the detections and compute statistics at each position */
869  for (ipos = 0; ipos < cpl_table_get_nrow(_sources); ++ipos) {
870  cpl_size _offset = cpl_array_get_cplsize(positions, ipos, NULL);
871  cpl_size ix = _offset % nx;
872  cpl_size iy = (_offset - ix) / nx;
873 
874  cpl_size imin = ix - nhalf + 1;
875  cpl_size imax = ix + nhalf + 1;
876  cpl_size jmin = iy - nhalf + 1;
877  cpl_size jmax = iy + nhalf + 1;
878 
879 #if DEBUG_MUSE_FINDSTARS > 0
880  cpl_table_set_long_long(_sources, "xidx", ipos, ix);
881  cpl_table_set_long_long(_sources, "yidx", ipos, iy);
882 #endif
883 
884  if ((imin < 1) || (jmin < 1) || (imax >= nx) || (jmax >= ny)) {
885  /* Discard detection, search box does not fit within image boundaries */
886  cpl_table_unselect_row(_sources, ipos);
887  continue;
888  }
889 
890  double flux = cpl_matrix_get(hmatrix, iy, ix);
891  cpl_image *_image = cpl_image_extract(aImage, imin + 1, jmin + 1,
892  imax + 1, jmax + 1);
893  cpl_matrix *mimg = cpl_matrix_wrap(nbox, nbox,
894  cpl_image_get_data_double(_image));
895 
896  /* Validate sharpness */
897  mtmp = muse_cplmatrix_multiply_create(mask, mimg);
898  double sharpness = (cpl_matrix_get(mimg, nhalf, nhalf) -
899  _cpl_matrix_sum(mtmp) / npixels) / flux;
900  cpl_matrix_delete(mtmp);
901 
902  if ((sharpness < kSharpLimits[0]) || (sharpness > kSharpLimits[1])) {
903  cpl_matrix_unwrap(mimg);
904  cpl_image_delete(_image);
905 
906  /* Deselect detection, does not meet the sharpness criterion */
907  cpl_table_unselect_row(_sources, ipos);
908  continue;
909  }
910 
911  /* Validate roundness */
912  mtmp = _cpl_matrix_sum_columns(mimg);
913  cpl_matrix_multiply(mtmp, c1);
914  double dx = _cpl_matrix_sum(mtmp);
915  cpl_matrix_delete(mtmp);
916 
917  cpl_matrix *_mtmp = _cpl_matrix_sum_rows(mimg);
918  mtmp = cpl_matrix_transpose_create(_mtmp);
919  cpl_matrix_multiply(mtmp, c1);
920  double dy = _cpl_matrix_sum(mtmp);
921  cpl_matrix_delete(mtmp);
922  cpl_matrix_delete(_mtmp);
923 
924  if ((dx <= 0.) || (dy <= 0.)) {
925  cpl_matrix_unwrap(mimg);
926  cpl_image_delete(_image);
927 
928  /* Deselect detection, invalid roundness */
929  cpl_table_unselect_row(_sources, ipos);
930  continue;
931  }
932 
933  double roundness = 2. * (dx - dy) / (dx + dy);
934  if ((roundness < kRoundLimits[0]) || (roundness > kRoundLimits[1])) {
935  cpl_matrix_unwrap(mimg);
936  cpl_image_delete(_image);
937 
938  /* Deselect detection, does not meet the roundness criterion */
939  cpl_table_unselect_row(_sources, ipos);
940  continue;
941  }
942 
943  /* Centroid computation: the centroid computation differs from *
944  * DAOPHOT which multiplies the correction dx by 1/(1+abs(dx)). *
945  * The DAOPHOT method is more robust (e.g. two different sources *
946  * will not merge) especially in a package where the centroid will *
947  * subsequently be re-determined using PSF fitting. However, it is *
948  * less accurate, and introduces biases in the centroid histogram. *
949  * The change here is the same made in the IRAF DAOFIND routine *
950  * (see http://iraf.net/article.php?story=7211&query=daofind ) */
951 
952  int status = 0;
953 
954  double xoffset = 0;
955  status = _muse_centroid_offset(&xoffset, 1, mimg, wt, ywt, sgy, dgdx,
956  sumgx, sumgsqy, sgdgdx, sdgdx, sdgdxs, p,
957  sigsqr);
958  if (status != 0) {
959  cpl_matrix_unwrap(mimg);
960  cpl_image_delete(_image);
961  cpl_table_unselect_row(_sources, ipos);
962  continue;
963  }
964 
965  double yoffset = 0;
966  status = _muse_centroid_offset(&yoffset, 2, mimg, wt, xwt, sgx, dgdy,
967  sumgy, sumgsqx, sgdgdy, sdgdy, sdgdys, p,
968  sigsqr);
969  if (status != 0) {
970  cpl_matrix_unwrap(mimg);
971  cpl_image_delete(_image);
972  cpl_table_unselect_row(_sources, ipos);
973  continue;
974  }
975 
976  /* Bad centroid position. Deselect detection */
977  if ((fabs(xoffset) >= nhalf) || (fabs(yoffset) >= nhalf)) {
978  cpl_matrix_unwrap(mimg);
979  cpl_image_delete(_image);
980  cpl_table_unselect_row(_sources, ipos);
981  continue;
982  }
983  cpl_matrix_unwrap(mimg);
984  cpl_image_delete(_image);
985 
986 
987  /* Compute the final centroid X and Y coordinates. Add 1 to ix and iy *
988  * to convert matrix element indices to image pixel positions, since *
989  * the latter count pixels starting from 1! */
990  double xcenter = (ix + 1) + xoffset;
991  double ycenter = (iy + 1) + yoffset;
992 
993  cpl_table_set_double(_sources, "X", ipos, xcenter);
994  cpl_table_set_double(_sources, "Y", ipos, ycenter);
995  cpl_table_set_double(_sources, "Flux", ipos, flux);
996  cpl_table_set_double(_sources, "Sharpness", ipos, sharpness);
997  cpl_table_set_double(_sources, "Roundness", ipos, roundness);
998  }
999 
1000  /* Cleanup */
1001  cpl_array_delete(positions);
1002  cpl_matrix_unwrap(hmatrix);
1003  cpl_image_delete(himage);
1004  cpl_matrix_delete(c1);
1005  cpl_matrix_delete(dgdy);
1006  cpl_matrix_delete(dgdx);
1007  cpl_matrix_delete(sgy);
1008  cpl_matrix_delete(sgx);
1009  cpl_matrix_delete(wt);
1010  cpl_matrix_delete(ywt);
1011  cpl_matrix_delete(xwt);
1012  cpl_matrix_delete(mask);
1013 
1014 
1015 #if DEBUG_MUSE_FINDSTARS > 0
1016  cpl_table_save(_sources, NULL, NULL, "detections.fits", CPL_IO_CREATE);
1017 #endif
1018 
1019  cpl_table *sources = cpl_table_extract_selected(_sources);
1020  cpl_size ncandidates = cpl_table_get_nrow(_sources);
1021  cpl_size nselected = cpl_table_get_nrow(sources);
1022 
1023  cpl_table_delete(_sources);
1024 
1025 #if DEBUG_MUSE_FINDSTARS > 0
1026  cpl_table_erase_column(sources, "xidx");
1027  cpl_table_erase_column(sources, "yidx");
1028 #endif
1029 
1030  if (ncandidates == 0) {
1031  cpl_msg_debug(__func__, "No candidate stars were found!");
1032  }
1033  else {
1034  cpl_msg_debug(__func__, "Total number of candidate stars %" CPL_SIZE_FORMAT
1035  "; no. selected candidates %" CPL_SIZE_FORMAT, ncandidates,
1036  nselected);
1037  }
1038 
1039  if (nselected == 0) {
1040  cpl_table_delete(sources);
1041  sources = NULL;
1042  }
1043 
1044  return sources;
1045 }
1046 #undef MUSE_SQR
1047 
cpl_error_code muse_cplarray_erase_invalid(cpl_array *aArray)
Erase all invalid values from an array.
cpl_matrix * muse_cplmatrix_extract_selected(const cpl_matrix *aMatrix, const cpl_array *aIndices)
Extract the elements given by the index array.
cpl_matrix * muse_cplmatrix_multiply_create(const cpl_matrix *aMatrix1, const cpl_matrix *aMatrix2)
Compute the element-wise product of two matrices.
cpl_array * muse_cplmatrix_where(const cpl_matrix *aMatrix, double aValue, muse_cplmatrix_element_compare_func aCondition)
Select matrix elements according to a condition.
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.
cpl_image * muse_convolve_image(const cpl_image *aImage, const cpl_matrix *aKernel)
Compute the convolution of an image with a kernel.
Definition: muse_utils.c:2748