FORS Pipeline Reference Manual  4.12.5
fors_identify.c
1 /* $Id: fors_identify.c,v 1.58 2013-09-10 15:14:44 cgarcia Exp $
2  *
3  * This file is part of the FORS Library
4  * Copyright (C) 2002-2010 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 02110-1301 USA
19  */
20 
21 /*
22  * $Author: cgarcia $
23  * $Date: 2013-09-10 15:14:44 $
24  * $Revision: 1.58 $
25  * $Name: not supported by cvs2svn $
26  */
27 
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31 
32 #include <fors_identify.h>
33 
34 #include <fors_image.h>
35 #include <fors_pattern.h>
36 #include <fors_point.h>
37 #include <fors_dfs.h>
38 #include <fors_utils.h>
39 #include <fors_double.h>
40 
41 #include <cpl.h>
42 
43 #include <math.h>
44 #include <assert.h>
45 
53 {
54  int ncat;
55  double nsource;
56  double kappa;
57  double search;
58  double max_search;
59  double max_offset;
60 };
61 
62 static bool
63 inside_region(const fors_std_star *std,
64  void *reg);
65 
66 static void
67 match_patterns(const fors_star_list *stars,
68  const fors_std_star_list *std,
69  double kappa,
70  double *sx_00,
71  double *sy_00,
72  double *med_scale,
73  double *med_angle,
74  int *status);
75 
81 void
82 fors_identify_define_parameters(cpl_parameterlist *parameters,
83  const char *context)
84 {
85  cpl_parameter *p;
86  const char *full_name = NULL;
87  const char *name;
88 
89 /* Restore if pattern matching needs to be restored
90 
91  name = "ncat";
92  full_name = cpl_sprintf("%s.%s", context, name);
93  p = cpl_parameter_new_value(full_name,
94  CPL_TYPE_INT,
95  "Number of catalog stars to use in "
96  "pattern matching",
97  context,
98  10);
99 
100  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
101  cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
102  cpl_parameterlist_append(parameters, p);
103  cpl_free((void *)full_name);
104 
105 
106  name = "nsource";
107  full_name = cpl_sprintf("%s.%s", context, name);
108  p = cpl_parameter_new_value(full_name,
109  CPL_TYPE_DOUBLE,
110  "Number of sources to use in "
111  "pattern matching, pr. catalog star",
112  context,
113  15.0);
114 
115  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
116  cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
117  cpl_parameterlist_append(parameters, p);
118  cpl_free((void *)full_name);
119 
120  name = "kappa";
121  full_name = cpl_sprintf("%s.%s", context, name);
122  p = cpl_parameter_new_value(full_name,
123  CPL_TYPE_DOUBLE,
124  "Rejection parameter (scale, angle) used "
125  "in pattern matching ",
126  context,
127  5.0);
128  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
129  cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
130  cpl_parameterlist_append(parameters, p);
131  cpl_free((void *)full_name);
132 
133 End of restoring if pattern matching has to be restored */
134 
135 /*
136 
137  name = "search";
138  full_name = cpl_sprintf("%s.%s", context, name);
139  p = cpl_parameter_new_value(full_name,
140  CPL_TYPE_DOUBLE,
141  "Search radius (pixels)",
142  context,
143  5.0);
144  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
145  cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
146  cpl_parameterlist_append(parameters, p);
147  cpl_free((void *)full_name);
148 
149  name = "maxsearch";
150  full_name = cpl_sprintf("%s.%s", context, name);
151  p = cpl_parameter_new_value(full_name,
152  CPL_TYPE_DOUBLE,
153  "Maximum search radius (pixels)",
154  context,
155  20.0);
156  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
157  cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
158  cpl_parameterlist_append(parameters, p);
159  cpl_free((void *)full_name);
160 
161 */
162 
163  name = "maxoffset";
164  full_name = cpl_sprintf("%s.%s", context, name);
165  p = cpl_parameter_new_value(full_name,
166  CPL_TYPE_DOUBLE,
167  "Maximum acceptable offset between the image and catalogue WCS (pixels)",
168  context,
169  150.0);
170  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
171  cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
172  cpl_parameterlist_append(parameters, p);
173  cpl_free((void *)full_name);
174 
175  return;
176 }
177 
178 #undef cleanup
179 #define cleanup \
180 do { \
181  cpl_free((void *)name); \
182 } while (0)
183 
192 identify_method *
193 fors_identify_method_new(const cpl_parameterlist *parameters, const char *context)
194 {
195  identify_method *im = cpl_malloc(sizeof(*im));
196  const char *name = NULL;
197 
198  cpl_msg_info(cpl_func, "Identification parameters:");
199 
200 /* Restore if pattern matching to be restored
201 
202  cpl_msg_indent_more();
203  name = cpl_sprintf("%s.%s", context, "ncat");
204  im->ncat = dfs_get_parameter_int_const(parameters, name);
205  cpl_free((void *)name); name = NULL;
206  cpl_msg_indent_less();
207 
208 
209  cpl_msg_indent_more();
210  name = cpl_sprintf("%s.%s", context, "nsource");
211  im->nsource = dfs_get_parameter_double_const(parameters, name);
212  cpl_free((void *)name); name = NULL;
213  cpl_msg_indent_less();
214 
215 
216  cpl_msg_indent_more();
217  name = cpl_sprintf("%s.%s", context, "kappa");
218  im->kappa = dfs_get_parameter_double_const(parameters, name);
219  cpl_free((void *)name); name = NULL;
220  cpl_msg_indent_less();
221 
222 End of restore if pattern matching to be restored */
223 
224 
225  // cpl_msg_indent_more();
226  // name = cpl_sprintf("%s.%s", context, "search");
227  im->search = 5.0; // dfs_get_parameter_double_const(parameters, name);
228  // cpl_free((void *)name); name = NULL;
229  // cpl_msg_indent_less();
230 
231 
232  // cpl_msg_indent_more();
233  // name = cpl_sprintf("%s.%s", context, "maxsearch");
234  // name = cpl_sprintf("%s.%s", context, "search"); // Same value for max search
235  im->max_search = 5.0; // dfs_get_parameter_double_const(parameters, name);
236  // cpl_free((void *)name); name = NULL;
237  // cpl_msg_indent_less();
238 
239 
240  cpl_msg_indent_more();
241  name = cpl_sprintf("%s.%s", context, "maxoffset");
242  im->max_offset = dfs_get_parameter_double_const(parameters, name);
243  cpl_free((void *)name); name = NULL;
244  cpl_msg_indent_less();
245 
246 
247  assure( !cpl_error_get_code(), return NULL, NULL );
248 
249  return im;
250 }
251 
255 void
256 fors_identify_method_delete(identify_method **em)
257 {
258  if (em && *em) {
259  cpl_free(*em); *em = NULL;
260  }
261  return;
262 }
263 
273 static bool
274 std_brighter_than(const fors_std_star *s1,
275  void *s2)
276 {
277  return fors_std_star_brighter_than(s1, s2, NULL);
278 }
279 
289 static bool
291  void *s2)
292 {
293  return fors_star_brighter_than(s1, s2, NULL);
294 }
295 
304 static double
306  const fors_std_star *std,
307  double shiftx,
308  double shifty)
309 {
310  fors_point *shifted_pos = fors_point_new(std->pixel->x + shiftx,
311  std->pixel->y + shifty);
312 
313  double result = fors_point_distsq(s->pixel, shifted_pos);
314 
315  fors_point_delete(&shifted_pos);
316 
317  return result;
318 }
319 
328 static bool
330  const fors_star *s2,
331  void *data)
332 {
333  struct {
334  double shift_x, shift_y;
335  const fors_std_star *ref;
336  } *d = data;
337  /* Cast is safe, see caller */
338 
339  return
340  distsq_shift(s1, d->ref, d->shift_x, d->shift_y) <
341  distsq_shift(s2, d->ref, d->shift_x, d->shift_y);
342 }
343 
344 
345 #undef cleanup
346 #define cleanup \
347 do { \
348  fors_std_star_list_delete(&std_ccd , fors_std_star_delete); \
349  fors_std_star_list_delete(&std_ccd_bright, fors_std_star_delete); \
350  fors_star_list_delete(&source_bright, fors_star_delete); \
351 } while (0)
352 
371 void
372 fors_identify(fors_star_list *stars,
373  fors_std_star_list *cat,
374  const identify_method *im,
375  cpl_image **histogram)
376 {
377  fors_std_star_list *std_ccd = NULL; /* Subset of catalog stars
378  which are inside the CCD */
379 
380  fors_std_star_list *std_ccd_bright = NULL; /* Brightest std stars */
381 
382  fors_star_list *source_bright = NULL; /* Subset of brightest stars */
383 
384  double offset = 0.0;
385  double sx_00, sy_00;
386  int status;
387 
388  if (histogram)
389  *histogram = NULL;
390 
391  assure( stars != NULL, return, NULL );
392 
393  cpl_msg_info(cpl_func, "Identifying sources");
394  cpl_msg_indent_more();
395 
396 
397  /*
398  fors_star_print_list(CPL_MSG_ERROR, stars);
399  fors_std_star_print_list(CPL_MSG_ERROR, cat);
400  */
401 
402  cpl_msg_info(cpl_func, "Pattern matching");
403  cpl_msg_indent_more();
404 
405  /* Select standards inside CCD */
406  {
407  double tolerance = 100; /* pixels */
408  struct {
409  int xlo, ylo;
410  int xhi, yhi;
411  } region;
412  if (fors_star_list_size(stars) > 0) {
413  region.xlo = fors_star_get_x(fors_star_list_min_val(stars,
415  NULL), NULL) - tolerance;
416  region.ylo = fors_star_get_y(fors_star_list_min_val(stars,
418  NULL), NULL) - tolerance;
419  region.xhi = fors_star_get_x(fors_star_list_max_val(stars,
421  NULL), NULL) + tolerance;
422  region.yhi = fors_star_get_y(fors_star_list_max_val(stars,
424  NULL), NULL) + tolerance;
425 
426  } else {
427  region.xlo = 1;
428  region.ylo = 1;
429  region.xhi = 1000; /* Anything can go here, not used */
430  region.yhi = 1000;
431  }
432  cpl_msg_debug(cpl_func, "Search region = (%d, %d) - (%d, %d)",
433  region.xlo, region.ylo,
434  region.xhi, region.yhi);
435 
436  std_ccd = fors_std_star_list_extract(
437  cat, fors_std_star_duplicate, inside_region, &region);
438 
439  int multiple_entries = 0;
440 
441  if (fors_std_star_list_size(std_ccd) > 1) {
442  bool found_double;
443  do {
444  found_double = false;
445 
446  /* Searching for the nearest neighbour will permute the list,
447  do not do that while iterating the same list */
448  fors_std_star_list *tmp =
449  fors_std_star_list_duplicate(std_ccd,
450  fors_std_star_duplicate);
451 
452  cpl_msg_debug(cpl_func, "%d stars left", fors_std_star_list_size(tmp));
453 
454  fors_std_star *std;
455 
456  for (std = fors_std_star_list_first(tmp);
457  std != NULL && !found_double;
458  std = fors_std_star_list_next(tmp)) {
459 
460  fors_std_star *self = fors_std_star_list_kth_val(
461  std_ccd, 1,
462  (fors_std_star_list_func_eval)
463  fors_std_star_dist_arcsec,
464  std);
465 
466  fors_std_star *nn = fors_std_star_list_kth_val(
467  std_ccd, 2,
468  (fors_std_star_list_func_eval)
469  fors_std_star_dist_arcsec,
470  std);
471 
472  double min_dist = fors_std_star_dist_arcsec(std, nn);
473 
474  cpl_msg_debug(cpl_func, "dist = %f arcseconds", min_dist);
475 
476  /* If very close, remove the one with the largest magnitude
477  error.
478 
479  Do not try to combine the two magnitudes because
480  those estimates may or may not be correlated
481  */
482  if (min_dist < 5) {
483  multiple_entries += 1;
484 
485  if (std->dmagnitude > nn->dmagnitude) {
486  fors_std_star_list_remove(std_ccd, self);
487  fors_std_star_delete(&self);
488  } else {
489  fors_std_star_list_remove(std_ccd, nn);
490  fors_std_star_delete(&nn);
491  }
492  found_double = true;
493  }
494  }
495 
496  fors_std_star_list_delete(&tmp,
497  fors_std_star_delete);
498 
499  } while (found_double);
500  }
501 
502  cpl_msg_info(cpl_func,
503  "%d catalog star%s are expected inside detector, "
504  "ignored %d repeated source%s",
505  fors_std_star_list_size(std_ccd),
506  fors_std_star_list_size(std_ccd) == 1 ? "" : "s",
507  multiple_entries,
508  multiple_entries == 1 ? "" : "s");
509  }
510 
511 
512  if (0) {
513  /* Select brightest std */
514  if (fors_std_star_list_size(std_ccd) <= im->ncat) {
515  std_ccd_bright = fors_std_star_list_duplicate(std_ccd,
516  fors_std_star_duplicate);
517  }
518  else {
519  fors_std_star *kth =
520  fors_std_star_list_kth(std_ccd,
521  im->ncat + 1,
522  fors_std_star_brighter_than, NULL);
523 
524  //fors_std_star_print_list(std_ccd);
525 
526  std_ccd_bright = fors_std_star_list_extract(
527  std_ccd, fors_std_star_duplicate,
528  std_brighter_than, kth);
529  }
530 
531  if (fors_std_star_list_size(std_ccd_bright) < 3) {
532 
533  cpl_msg_warning(cpl_func,
534  "Too few catalog stars (%d) available for pattern "
535  "matching, assuming FITS header WCS solution",
536  fors_std_star_list_size(std_ccd_bright));
537 
538  sx_00 = 0;
539  sy_00 = 0;
540  }
541  else {
542 
543  double med_scale, med_angle;
544 
545  cpl_msg_info(cpl_func, "Using %d brightest standards",
546  fors_std_star_list_size(std_ccd_bright));
547 
548  fors_std_star_print_list(CPL_MSG_DEBUG, std_ccd_bright);
549 
550  /* Select sources */
551  int n_sources =
552  (int) (fors_std_star_list_size(std_ccd_bright)*im->nsource + 0.5);
553 
554  if (fors_star_list_size(stars) <= n_sources) {
555  source_bright = fors_star_list_duplicate(stars,
557  }
558  else {
559  fors_star *kth =
560  fors_star_list_kth(stars,
561  n_sources + 1,
563 
564  source_bright = fors_star_list_extract(
565  stars, fors_star_duplicate,
566  star_brighter_than, kth);
567  }
568 
569  cpl_msg_info(cpl_func, "Using %d brightest sources",
570  fors_star_list_size(source_bright));
571  fors_star_print_list(CPL_MSG_DEBUG, source_bright);
572 
573 
574  status = 0;
575  match_patterns(source_bright, std_ccd_bright,
576  im->kappa,
577  &sx_00, &sy_00, &med_scale, &med_angle, &status);
578 
579  assure( !cpl_error_get_code(), return, "Pattern matching failed" );
580 
581 
582  if (status) {
583  cpl_msg_warning(cpl_func,
584  "BAD pattern matching solution rejected.");
585 
586  if (med_scale > 1.1 || med_scale < 0.9) {
587  cpl_msg_warning(cpl_func, "Unexpected scale from pattern "
588  "matching (expected 1.0): assuming FITS header WCS solution");
589  }
590 
591  offset = sqrt(sx_00 * sx_00 + sy_00 * sy_00);
592 
593  if (offset > im->max_offset) {
594  cpl_msg_warning(cpl_func, "Pattern matching identifications "
595  "are more than %.2f pixel off their expected positions (max "
596  "allowed was: %.2f). Pattern matching solution is rejected, "
597  "FITS header WCS solution is used instead!", offset,
598  im->max_offset);
599  }
600 
601  sx_00 = 0;
602  sy_00 = 0;
603  }
604  }
605  cpl_msg_indent_less();
606 
607  cpl_msg_info(cpl_func,
608  "Average shift from pattern matching = (%.2f, %.2f) pixels",
609  sx_00, sy_00);
610  }
611 
612  /*
613  * Begin here alternative algorithm to pattern matching
614  */
615 
616  double search_radius = im->max_offset;
617  const fors_std_star *catalog_star;
618  const fors_star *ccd_star;
619  float dx, dy;
620  int npix = (2 * search_radius + 1);
621  cpl_image *histo = cpl_image_new(npix, npix, CPL_TYPE_INT);
622  int *dhisto = cpl_image_get_data(histo);
623  cpl_size xpos, ypos;
624  int i, rebin, max;
625  int found = 0;
626 
627  for (catalog_star = fors_std_star_list_first_const(std_ccd);
628  catalog_star != NULL;
629  catalog_star = fors_std_star_list_next_const(std_ccd)) {
630 
631  for (ccd_star = fors_star_list_first_const(stars);
632  ccd_star != NULL;
633  ccd_star = fors_star_list_next_const(stars)) {
634 
635  dx = catalog_star->pixel->x - ccd_star->pixel->x;
636  dy = catalog_star->pixel->y - ccd_star->pixel->y;
637 
638  if (fabs(dx) < search_radius) {
639  if (fabs(dy) < search_radius) {
640  xpos = search_radius + floor(dx + 0.5);
641  ypos = search_radius + floor(dy + 0.5);
642  ++dhisto[xpos + ypos * npix];
643  }
644  }
645  }
646  }
647 
648  max = 0;
649  for (i = 0; i < npix * npix; i++) {
650  if (max < dhisto[i]) {
651  max = dhisto[i];
652  }
653  }
654 
655  if (max == 0) {
656  cpl_msg_warning(cpl_func,
657  "WCS offset determination failed.");
658  sx_00 = 0.0;
659  sy_00 = 0.0;
660  }
661  else {
662  rebin = 0;
663  for (i = 0; i < npix * npix; i++) {
664  if (max == dhisto[i]) {
665  if (found) {
666  cpl_image *rebinned;
667  found = 2;
668  rebin = 1;
669  cpl_msg_warning(cpl_func,
670  "More than one WCS offset found, try rebinning...");
671  rebinned = cpl_image_rebin(histo, 1, 1, 3, 3);
672  cpl_image_delete(histo);
673  histo = rebinned;
674  dhisto = cpl_image_get_data(histo);
675  npix = cpl_image_get_size_x(histo);
676  search_radius /= 3;
677  break;
678  }
679  else {
680  found = 1;
681  }
682  }
683  }
684 
685  if (found > 1) {
686  found = max = 0;
687  for (i = 0; i < npix * npix; i++) {
688  if (max < dhisto[i]) {
689  max = dhisto[i];
690  }
691  }
692 
693  for (i = 0; i < npix * npix; i++) {
694  if (max == dhisto[i]) {
695  if (found) {
696 
697  /*
698  * Check if connected to previously found maxima
699  */
700 
701  int connected = 0;
702 
703  if (i > 0) {
704  if (max == dhisto[i-1]) {
705  connected = 1;
706  }
707  }
708  if (i > npix) {
709  if (max == dhisto[i-npix-1] ||
710  max == dhisto[i-npix] ||
711  max == dhisto[i-npix+1]) {
712  connected = 1;
713  }
714  }
715 
716  if (!connected) {
717  found = 2;
718  cpl_msg_warning(cpl_func,
719  "WCS offset determination failed.");
720  sx_00 = 0.0;
721  sy_00 = 0.0;
722  break;
723  }
724  }
725  else {
726  found = 1;
727  }
728  }
729  }
730  }
731  }
732 
733  if (found == 1) {
734  cpl_image_get_maxpos(histo, &xpos, &ypos);
735  xpos--;
736  ypos--;
737 
738  /*
739  * Refine peak position
740  */
741 
742  float xmean = 0.0;
743  float ymean = 0.0;
744  int i, j, count = 0;
745 
746  for (i = xpos - 1; i <= xpos; i++) {
747  for (j = ypos - 1; j <= ypos; j++) {
748  if (i >= 0 && i < npix) {
749  if (j >= 0 && j < npix) {
750  xmean += i * dhisto[i + j*npix];
751  ymean += j * dhisto[i + j*npix];
752  count += dhisto[i + j*npix];
753  }
754  }
755  }
756  }
757 
758  sx_00 = search_radius - xmean / count - 0.5;
759  sy_00 = search_radius - ymean / count - 0.5;
760 
761  if (rebin) {
762  sx_00 *= 3;
763  sy_00 *= 3;
764  }
765 
766  /*
767  cpl_msg_info(cpl_func,
768  "Average shift from histogram method = (%.2f, %.2f) pixels",
769  sx_00, sy_00);
770  */
771 
772  // cpl_image_save(histo, "histogram.fits", CPL_BPP_32_SIGNED, NULL,
773  // CPL_IO_DEFAULT);
774  //
775  // cpl_image_delete(histo);
776  }
777 
778  if (histogram)
779  *histogram = histo;
780  else
781  cpl_image_delete(histo);
782 
783  offset = sqrt(sx_00 * sx_00 + sy_00 * sy_00);
784 
785  if (offset > im->max_offset) {
786  cpl_msg_warning(cpl_func, "Offset with respect to WCS is %.2f pixel "
787  "(max allowed was: %.2f). This offset is rejected.",
788  offset, im->max_offset);
789 
790  sx_00 = 0;
791  sy_00 = 0;
792  }
793 
794  /*
795  * End here alternative algorithm to pattern matching
796  */
797 
798  /* Finally, make the identification if a unique source is found
799  within the corrected position search radius.
800 
801  Use all catalog stars (inside CCD).
802  */
803 
804  if (sx_00 == 0.0 && sy_00 == 0.0) {
805  cpl_msg_warning(cpl_func, "No standard star could be identified.");
806  cpl_msg_indent_less();
807  cleanup;
808  return;
809  }
810 
811  int number_of_ids = 0;
812  bool require_unique = true;
813  search_radius = im->search;
814 
815  while (number_of_ids == 0 && search_radius <= im->max_search) {
816 
817  cpl_msg_info(cpl_func, "Identification");
818 
819  cpl_msg_indent_more();
820  cpl_msg_info(cpl_func, "Average shift with WCS = (%.2f, %.2f) pixels",
821  sx_00,
822  sy_00);
823 
824  if (fabs(sx_00) > 10.0) {
825  cpl_msg_warning(cpl_func, "Large x-shift");
826  }
827  if (fabs(sy_00) > 10.0) {
828  cpl_msg_warning(cpl_func, "Large y-shift");
829  }
830 
831  cpl_msg_info(cpl_func, "search_radius = %.2f pixels", search_radius);
832 
833  struct {
834  double shift_x, shift_y;
835  const fors_std_star *ref;
836  } data;
837 
838  data.shift_x = sx_00;
839  data.shift_y = sy_00;
840 
841  if (fors_star_list_size(stars) > 0) {
842  for (data.ref = fors_std_star_list_first_const(std_ccd);
843  data.ref != NULL;
844  data.ref = fors_std_star_list_next_const(std_ccd)) {
845 
846  fors_star *nearest_1 = fors_star_list_kth(stars,
847  1,
848  star_nearer,
849  &data);
850 
851  if (fors_star_list_size(stars) > 1) {
852 
853  fors_star *nearest_2 = fors_star_list_kth(stars,
854  2,
855  star_nearer,
856  &data);
857 
858  cpl_msg_debug(cpl_func, "Nearest candidates at "
859  "distance %f and %f pixels",
860  sqrt(distsq_shift(nearest_1, data.ref,
861  data.shift_x,
862  data.shift_y)),
863  sqrt(distsq_shift(nearest_2, data.ref,
864  data.shift_x,
865  data.shift_y)));
866 
867  if (distsq_shift(nearest_1, data.ref,
868  data.shift_x, data.shift_y) <=
869  search_radius * search_radius) {
870 
871  if (!require_unique ||
872  distsq_shift(nearest_2, data.ref,
873  data.shift_x, data.shift_y) >
874  search_radius * search_radius) {
875 
876  cpl_msg_debug(cpl_func,
877  "unique source inside %f pixels",
878  search_radius);
879 
880  nearest_1->id = fors_std_star_duplicate(data.ref);
881  number_of_ids += 1;
882  }
883  }
884  }
885  else {
886  cpl_msg_debug(cpl_func, "Nearest candidate at "
887  "distance %f pixels",
888  sqrt(distsq_shift(nearest_1, data.ref,
889  data.shift_x,
890  data.shift_y)));
891  if (distsq_shift(nearest_1, data.ref,
892  data.shift_x, data.shift_y) <=
893  search_radius * search_radius) {
894 
895  cpl_msg_debug(cpl_func,
896  "unique source inside %f pixels",
897  search_radius);
898 
899  nearest_1->id = fors_std_star_duplicate(data.ref);
900  number_of_ids += 1;
901  }
902  }
903  }
904  }
905 
906  cpl_msg_info(cpl_func, "Identified %d star%s",
907  number_of_ids, (number_of_ids == 1) ? "" : "s");
908 
909  if (number_of_ids == 0) {
910 
911  if (fabs(sx_00) > 0.1 &&
912  fabs(sy_00) > 0.1) {
913 
914  cpl_msg_debug(cpl_func,
915  "No identifications made, "
916  "set shift to zero and try again");
917  search_radius = 20;
918  require_unique = false;
919 
920  sx_00 = 0;
921  sy_00 = 0;
922  }
923  else {
924  require_unique = false;
925 
926  search_radius *= 2;
927 
928  cpl_msg_debug(cpl_func,
929  "No identifications made, "
930  "double search radius and try again");
931  }
932  }
933 
934  cpl_msg_indent_less();
935 
936  } /* while no identifications made */
937 
938  if (number_of_ids == 0) {
939  cpl_msg_warning(cpl_func,
940  "No identifications made, "
941  "within search radius %f pixels",
942  im->max_search);
943  /* When maxsearchradius was a parameter
944 
945  cpl_msg_warning(cpl_func,
946  "No identifications made, "
947  "max search radius reached: %f pixels",
948  im->max_search);
949  */
950  }
951  else {
952  /* Sketch to recompute shift:
953  * Get median shifts (in x and y) of identified stars.
954  * Then do the equivalent of a single tour through the while() loop above
955  *
956  * This could perhaps be unified with the code above to avoid duplication
957  */
958  }
959 
960 
961  cpl_msg_indent_less();
962 
963  cleanup;
964  return;
965 }
966 
975 static bool
976 inside_region(const fors_std_star *std,
977  void *reg)
978 {
979  const struct {
980  int xlo, ylo;
981  int xhi, yhi;
982  } *region = reg;
983 
984  return
985  region->xlo <= std->pixel->x && std->pixel->x <= region->xhi &&
986  region->ylo <= std->pixel->y && std->pixel->y <= region->yhi;
987 }
988 
989 
990 
991 #undef cleanup
992 #define cleanup \
993 do { \
994  fors_point_list_delete(&std_points, fors_point_delete); \
995  fors_point_list_delete(&source_points, fors_point_delete); \
996  fors_pattern_list_delete(&std_patterns, fors_pattern_delete); \
997  fors_pattern_list_delete(&source_patterns, fors_pattern_delete); \
998  double_list_delete(&scales, double_delete); \
999  double_list_delete(&angles, double_delete); \
1000  double_list_delete(&angle_cos, double_delete); \
1001  double_list_delete(&angle_sin, double_delete); \
1002  double_list_delete(&match_dist, double_delete); \
1003  double_list_delete(&shiftx, double_delete); \
1004  double_list_delete(&shifty, double_delete); \
1005 } while (0)
1006 
1022 static void
1023 match_patterns(const fors_star_list *stars,
1024  const fors_std_star_list *std,
1025  double kappa,
1026  double *sx_00,
1027  double *sy_00,
1028  double *med_scale,
1029  double *med_angle,
1030  int *status)
1031 {
1032  fors_point_list *std_points = NULL;
1033  fors_point_list *source_points = NULL;
1034 
1035  fors_pattern_list *std_patterns = NULL;
1036  fors_pattern_list *source_patterns = NULL;
1037 
1038  double_list *scales = NULL;
1039  double_list *angles = NULL;
1040  double_list *angle_cos = NULL;
1041  double_list *angle_sin = NULL;
1042  double_list *match_dist = NULL;
1043  double_list *shiftx = NULL;
1044  double_list *shifty = NULL;
1045 
1046  *status = 0;
1047 
1048  assure( sx_00 != NULL, return, NULL );
1049  assure( sy_00 != NULL, return, NULL );
1050 
1051  /* Build patterns */
1052  std_points = fors_point_list_new();
1053  {
1054  const fors_std_star *s;
1055 
1056  for (s = fors_std_star_list_first_const(std);
1057  s != NULL;
1058  s = fors_std_star_list_next_const(std)) {
1059 
1060  fors_point_list_insert(std_points,
1061  fors_point_new(s->pixel->x,
1062  s->pixel->y));
1063  }
1064  }
1065 
1066  source_points = fors_point_list_new();
1067  {
1068  const fors_star *s;
1069 
1070  for (s = fors_star_list_first_const(stars);
1071  s != NULL;
1072  s = fors_star_list_next_const(stars)) {
1073 
1074  fors_point_list_insert(source_points,
1075  fors_point_new(s->pixel->x,
1076  s->pixel->y));
1077  }
1078  }
1079 
1080  const double min_dist = 1.0; /* minimum distance between two
1081  points in a pattern */
1082  double sigma_std = 0.0; /* No uncertainty of catalog patterns */
1083  std_patterns =
1084  fors_pattern_new_from_points(std_points, min_dist, sigma_std);
1085  cpl_msg_info(cpl_func, "Created %d catalog patterns",
1086  fors_pattern_list_size(std_patterns));
1087 
1088  double sigma_source;
1089  if (fors_star_list_size(stars) > 0) {
1090  sigma_source = fors_star_list_median(stars,
1091  fors_star_extension, NULL);
1092  cpl_msg_info(cpl_func, "Average source extension = %.2f pixels", sigma_source);
1093  } else {
1094  sigma_source = -1; /* not used in this case */
1095  }
1096  source_patterns =
1097  fors_pattern_new_from_points(source_points, min_dist, sigma_source);
1098 
1099  cpl_msg_info(cpl_func, "Created %d source patterns",
1100  fors_pattern_list_size(source_patterns));
1101 
1102  scales = double_list_new();
1103  angles = double_list_new();
1104  angle_cos = double_list_new();
1105  angle_sin = double_list_new();
1106  match_dist = double_list_new();
1107 
1108  if ( fors_pattern_list_size(source_patterns) > 0) {
1109  /* Identify,
1110  get average scale, orientation */
1111  fors_pattern *p;
1112 
1113  for (p = fors_pattern_list_first(std_patterns);
1114  p != NULL;
1115  p = fors_pattern_list_next(std_patterns)) {
1116 
1117  fors_pattern *nearest_source =
1118  fors_pattern_list_min_val(source_patterns,
1119  (fors_pattern_list_func_eval) fors_pattern_distsq,
1120  p);
1121 
1122  double scale = fors_pattern_get_scale(p, nearest_source);
1123  double angle = fors_pattern_get_angle(p, nearest_source);
1124  double angle_c = cos(angle);
1125  double angle_s = sin(angle);
1126  double dist = sqrt(fors_pattern_distsq(p, nearest_source));
1127  double dist_per_error = fors_pattern_dist_per_error(p, nearest_source);
1128 
1129  cpl_msg_debug(cpl_func, "dist, ndist, scale, orientation = %f, %f, %f, %f",
1130  dist, dist_per_error, scale, angle * 360/(2*M_PI));
1131 
1132  /* Make identification if patterns match within error bars
1133  (defined above as sigma_source)
1134  */
1135  if (dist_per_error < 1.0) {
1136  double_list_insert(scales , double_duplicate(&scale));
1137  double_list_insert(angles , double_duplicate(&angle));
1138  double_list_insert(angle_cos, double_duplicate(&angle_c));
1139  double_list_insert(angle_sin, double_duplicate(&angle_s));
1140  double_list_insert(match_dist, double_duplicate(&dist));
1141  }
1142  }
1143  }
1144  else {
1145  /* no source patterns to match */
1146  }
1147 
1148  if ( double_list_size(scales) >= 2 ) {
1149  double scale_avg = double_list_median(scales, double_eval, NULL);
1150  double scale_stdev = double_list_mad(scales, double_eval, NULL) * STDEV_PR_MAD;
1151 
1152  cpl_msg_info(cpl_func, "Median scale = %.4f +- %.4f",
1153  scale_avg, scale_stdev);
1154 
1155  *med_scale = scale_avg;
1156 
1157  if (scale_stdev > 0.2) {
1158  cpl_msg_warning(cpl_func, "Uncertain scale determination");
1159  *status = 1;
1160  }
1161 
1162  /*
1163  * Represent each angle as a unit vector, compute average
1164  * unit vector orientation.
1165  * Compute median absolute deviation with respect to this average
1166  */
1167  double angle_avg = atan2(double_list_mean(angle_sin, double_eval, NULL),
1168  double_list_mean(angle_cos, double_eval, NULL));
1169  double angle_stdev = STDEV_PR_MAD *
1170  double_list_median(angles, (double_list_func_eval) fors_angle_diff, &angle_avg);
1171 
1172  cpl_msg_info(cpl_func, "Average orientation = %.1f +- %.1f degrees",
1173  angle_avg * 360 / (2*M_PI),
1174  angle_stdev * 360 / (2*M_PI));
1175 
1176  *med_angle = angle_avg;
1177 
1178  if (angle_avg > M_PI/4 || angle_avg < -M_PI/4) {
1179  cpl_msg_warning(cpl_func, "Expected orientation = 0 degrees");
1180  *status = 1;
1181  /* To model any orientation, we should use higher order (than zero) polynomials
1182  for the shifts */
1183  }
1184 
1185  double avg_dist = double_list_mean(match_dist, double_eval, NULL);
1186  double false_dist = 1.0/sqrt(M_PI * fors_pattern_list_size(source_patterns));
1187  /* Average distance to nearest false match */
1188 
1189  cpl_msg_info(cpl_func, "Average match distance = %f pixel", avg_dist);
1190  cpl_msg_info(cpl_func, "False match distance = %f pixel", false_dist);
1191  cpl_msg_info(cpl_func, "Safety index = %.3f (should be >~ 5)",
1192  false_dist / avg_dist);
1193  if (false_dist / avg_dist < 1.5) {
1194  cpl_msg_warning(cpl_func, "Uncertain pattern matching");
1195  *status = 1;
1196  }
1197 
1198  /* Get shift from matches */
1199  shiftx = double_list_new();
1200  shifty = double_list_new();
1201  {
1202  fors_pattern *p;
1203 
1204  for (p = fors_pattern_list_first(std_patterns);
1205  p != NULL;
1206  p = fors_pattern_list_next(std_patterns)) {
1207 
1208  fors_pattern *nearest_source =
1209  fors_pattern_list_min_val(
1210  source_patterns,
1211  (fors_pattern_list_func_eval) fors_pattern_distsq,
1212  p);
1213 
1214  double dist = sqrt(fors_pattern_distsq(p, nearest_source));
1215  double dist_per_error = fors_pattern_dist_per_error(p, nearest_source);
1216  double scale = fors_pattern_get_scale(p, nearest_source);
1217  double angle = fors_pattern_get_angle(p, nearest_source);
1218 
1219  cpl_msg_debug(cpl_func, "scale, orientation, distance, norm.distance "
1220  "= %f, %f, %f, %f",
1221  scale, angle * 360/(2*M_PI), dist, dist_per_error);
1222 
1223  if (dist_per_error < 1.0 &&
1224  fabs(scale - scale_avg) <= kappa * scale_stdev &&
1225  fors_angle_diff(&angle, &angle_avg) <= kappa * angle_stdev) {
1226 
1227  /* Compute shift of the two patterns' reference stars */
1228  double shift_x = fors_pattern_get_ref(nearest_source)->x - fors_pattern_get_ref(p)->x;
1229  double shift_y = fors_pattern_get_ref(nearest_source)->y - fors_pattern_get_ref(p)->y;
1230 
1231  cpl_msg_debug(cpl_func, "Accepted, shift = (%f, %f) pixels",
1232  shift_x, shift_y);
1233 
1234  double_list_insert(shiftx, double_duplicate(&shift_x));
1235  double_list_insert(shifty, double_duplicate(&shift_y));
1236  }
1237  }
1238  }
1239 
1240  if (double_list_size(shiftx) > 0) {
1241  *sx_00 = double_list_median(shiftx, double_eval, NULL);
1242  }
1243  else {
1244  /* If this happens it is likely a bug, because
1245  we already checked that 'scales' is non-empty.
1246 
1247  But do not try to get the median of an empty list in any case,
1248  which would cause a crash
1249  */
1250  cpl_msg_warning(cpl_func, "No standardstar identifications!");
1251  *status = 1;
1252  }
1253 
1254  if (double_list_size(shiftx) > 0) {
1255  *sy_00 = double_list_median(shifty, double_eval, NULL);
1256  }
1257  else {
1258  cpl_msg_warning(cpl_func, "No standardstar identifications!");
1259  *status = 1;
1260  }
1261  }
1262  else {
1263  cpl_msg_warning(cpl_func,
1264  "Too few (%d) matching patterns: assuming zero shift",
1265  double_list_size(scales));
1266  *sx_00 = 0;
1267  *sy_00 = 0;
1268  *med_scale = 1.0;
1269  *med_angle = 0.0;
1270  }
1271 
1272  cleanup;
1273  return;
1274 }
1275 
1276 
1277 
1278 
fors_point * fors_point_new(double x, double y)
Constructor.
Definition: fors_point.c:53
double fors_star_get_x(const fors_star *s, void *data)
Get position.
Definition: fors_star.c:467
void fors_star_print_list(cpl_msg_severity level, const fors_star_list *sl)
Print list of stars.
Definition: fors_star.c:443
static void match_patterns(const fors_star_list *stars, const fors_std_star_list *std, double kappa, double *sx_00, double *sy_00, double *med_scale, double *med_angle, int *status)
Match patterns.
identify_method * fors_identify_method_new(const cpl_parameterlist *parameters, const char *context)
Get id method from parameter list.
void fors_identify_method_delete(identify_method **em)
Deallocate identifyion method and set the pointer to NULL.
double fors_angle_diff(const double *a1, const double *a2)
Difference between angles.
Definition: fors_utils.c:636
static bool inside_region(const fors_std_star *std, void *reg)
Determine if star is inside region.
static bool star_brighter_than(const fors_star *s1, void *s2)
Compare brightness.
static bool star_nearer(const fors_star *s1, const fors_star *s2, void *data)
Tell if a source is closest to a catalog star.
#define assure(EXPR)
Definition: list.c:101
static bool std_brighter_than(const fors_std_star *s1, void *s2)
Compare brightness.
void fors_point_delete(fors_point **p)
Destructor.
Definition: fors_point.c:87
fors_star * fors_star_duplicate(const fors_star *star)
Copy constructor.
Definition: fors_star.c:248
double fors_star_get_y(const fors_star *s, void *data)
Get position.
Definition: fors_star.c:485
double fors_star_extension(const fors_star *s, void *data)
Get star size.
Definition: fors_star.c:365
bool fors_star_brighter_than(const fors_star *s1, const fors_star *s2, void *data)
Compare star brightness.
Definition: fors_star.c:329
static double distsq_shift(const fors_star *s, const fors_std_star *std, double shiftx, double shifty)
Distance between source and shifted catalog star.
double fors_point_distsq(const fors_point *p, const fors_point *q)
Metric.
Definition: fors_point.c:103
void fors_identify(fors_star_list *stars, fors_std_star_list *cat, const identify_method *im, cpl_image **histogram)
Identify sources.
double dfs_get_parameter_double_const(const cpl_parameterlist *parlist, const char *name)
Definition: fors_dfs.c:801
void fors_identify_define_parameters(cpl_parameterlist *parameters, const char *context)
Define recipe parameters.
Definition: fors_identify.c:82