32 #include <fors_identify.h>
34 #include <fors_image.h>
35 #include <fors_pattern.h>
36 #include <fors_point.h>
38 #include <fors_utils.h>
39 #include <fors_double.h>
68 const fors_std_star_list *std,
86 const char *full_name = NULL;
164 full_name = cpl_sprintf(
"%s.%s", context, name);
165 p = cpl_parameter_new_value(full_name,
167 "Maximum acceptable offset between the image and catalogue WCS (pixels)",
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);
181 cpl_free((void *)name); \
195 identify_method *im = cpl_malloc(
sizeof(*im));
196 const char *name = NULL;
198 cpl_msg_info(cpl_func,
"Identification parameters:");
235 im->max_search = 5.0;
240 cpl_msg_indent_more();
241 name = cpl_sprintf(
"%s.%s", context,
"maxoffset");
243 cpl_free((
void *)name); name = NULL;
244 cpl_msg_indent_less();
247 assure( !cpl_error_get_code(),
return NULL, NULL );
259 cpl_free(*em); *em = NULL;
277 return fors_std_star_brighter_than(s1, s2, NULL);
306 const fors_std_star *std,
311 std->pixel->y + shifty);
334 double shift_x, shift_y;
335 const fors_std_star *ref;
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); \
373 fors_std_star_list *cat,
374 const identify_method *im,
375 cpl_image **histogram)
377 fors_std_star_list *std_ccd = NULL;
380 fors_std_star_list *std_ccd_bright = NULL;
382 fors_star_list *source_bright = NULL;
391 assure( stars != NULL,
return, NULL );
393 cpl_msg_info(cpl_func,
"Identifying sources");
394 cpl_msg_indent_more();
402 cpl_msg_info(cpl_func,
"Pattern matching");
403 cpl_msg_indent_more();
407 double tolerance = 100;
412 if (fors_star_list_size(stars) > 0) {
415 NULL), NULL) - tolerance;
418 NULL), NULL) - tolerance;
421 NULL), NULL) + tolerance;
424 NULL), NULL) + tolerance;
432 cpl_msg_debug(cpl_func,
"Search region = (%d, %d) - (%d, %d)",
433 region.xlo, region.ylo,
434 region.xhi, region.yhi);
436 std_ccd = fors_std_star_list_extract(
439 int multiple_entries = 0;
441 if (fors_std_star_list_size(std_ccd) > 1) {
444 found_double =
false;
448 fors_std_star_list *tmp =
449 fors_std_star_list_duplicate(std_ccd,
450 fors_std_star_duplicate);
452 cpl_msg_debug(cpl_func,
"%d stars left", fors_std_star_list_size(tmp));
456 for (std = fors_std_star_list_first(tmp);
457 std != NULL && !found_double;
458 std = fors_std_star_list_next(tmp)) {
460 fors_std_star *
self = fors_std_star_list_kth_val(
462 (fors_std_star_list_func_eval)
463 fors_std_star_dist_arcsec,
466 fors_std_star *nn = fors_std_star_list_kth_val(
468 (fors_std_star_list_func_eval)
469 fors_std_star_dist_arcsec,
472 double min_dist = fors_std_star_dist_arcsec(std, nn);
474 cpl_msg_debug(cpl_func,
"dist = %f arcseconds", min_dist);
483 multiple_entries += 1;
485 if (std->dmagnitude > nn->dmagnitude) {
486 fors_std_star_list_remove(std_ccd,
self);
487 fors_std_star_delete(&
self);
489 fors_std_star_list_remove(std_ccd, nn);
490 fors_std_star_delete(&nn);
496 fors_std_star_list_delete(&tmp,
497 fors_std_star_delete);
499 }
while (found_double);
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",
508 multiple_entries == 1 ?
"" :
"s");
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);
520 fors_std_star_list_kth(std_ccd,
522 fors_std_star_brighter_than, NULL);
526 std_ccd_bright = fors_std_star_list_extract(
527 std_ccd, fors_std_star_duplicate,
531 if (fors_std_star_list_size(std_ccd_bright) < 3) {
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));
543 double med_scale, med_angle;
545 cpl_msg_info(cpl_func,
"Using %d brightest standards",
546 fors_std_star_list_size(std_ccd_bright));
548 fors_std_star_print_list(CPL_MSG_DEBUG, std_ccd_bright);
552 (int) (fors_std_star_list_size(std_ccd_bright)*im->nsource + 0.5);
554 if (fors_star_list_size(stars) <= n_sources) {
555 source_bright = fors_star_list_duplicate(stars,
560 fors_star_list_kth(stars,
564 source_bright = fors_star_list_extract(
569 cpl_msg_info(cpl_func,
"Using %d brightest sources",
570 fors_star_list_size(source_bright));
577 &sx_00, &sy_00, &med_scale, &med_angle, &status);
579 assure( !cpl_error_get_code(),
return,
"Pattern matching failed" );
583 cpl_msg_warning(cpl_func,
584 "BAD pattern matching solution rejected.");
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");
591 offset = sqrt(sx_00 * sx_00 + sy_00 * sy_00);
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,
605 cpl_msg_indent_less();
607 cpl_msg_info(cpl_func,
608 "Average shift from pattern matching = (%.2f, %.2f) pixels",
616 double search_radius = im->max_offset;
617 const fors_std_star *catalog_star;
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);
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)) {
631 for (ccd_star = fors_star_list_first_const(stars);
633 ccd_star = fors_star_list_next_const(stars)) {
635 dx = catalog_star->pixel->x - ccd_star->pixel->x;
636 dy = catalog_star->pixel->y - ccd_star->pixel->y;
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];
649 for (i = 0; i < npix * npix; i++) {
650 if (max < dhisto[i]) {
656 cpl_msg_warning(cpl_func,
657 "WCS offset determination failed.");
663 for (i = 0; i < npix * npix; i++) {
664 if (max == dhisto[i]) {
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);
674 dhisto = cpl_image_get_data(histo);
675 npix = cpl_image_get_size_x(histo);
687 for (i = 0; i < npix * npix; i++) {
688 if (max < dhisto[i]) {
693 for (i = 0; i < npix * npix; i++) {
694 if (max == dhisto[i]) {
704 if (max == dhisto[i-1]) {
709 if (max == dhisto[i-npix-1] ||
710 max == dhisto[i-npix] ||
711 max == dhisto[i-npix+1]) {
718 cpl_msg_warning(cpl_func,
719 "WCS offset determination failed.");
734 cpl_image_get_maxpos(histo, &xpos, &ypos);
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];
758 sx_00 = search_radius - xmean / count - 0.5;
759 sy_00 = search_radius - ymean / count - 0.5;
781 cpl_image_delete(histo);
783 offset = sqrt(sx_00 * sx_00 + sy_00 * sy_00);
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);
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();
811 int number_of_ids = 0;
812 bool require_unique =
true;
813 search_radius = im->search;
815 while (number_of_ids == 0 && search_radius <= im->max_search) {
817 cpl_msg_info(cpl_func,
"Identification");
819 cpl_msg_indent_more();
820 cpl_msg_info(cpl_func,
"Average shift with WCS = (%.2f, %.2f) pixels",
824 if (fabs(sx_00) > 10.0) {
825 cpl_msg_warning(cpl_func,
"Large x-shift");
827 if (fabs(sy_00) > 10.0) {
828 cpl_msg_warning(cpl_func,
"Large y-shift");
831 cpl_msg_info(cpl_func,
"search_radius = %.2f pixels", search_radius);
834 double shift_x, shift_y;
835 const fors_std_star *ref;
838 data.shift_x = sx_00;
839 data.shift_y = sy_00;
841 if (fors_star_list_size(stars) > 0) {
842 for (data.ref = fors_std_star_list_first_const(std_ccd);
844 data.ref = fors_std_star_list_next_const(std_ccd)) {
846 fors_star *nearest_1 = fors_star_list_kth(stars,
851 if (fors_star_list_size(stars) > 1) {
853 fors_star *nearest_2 = fors_star_list_kth(stars,
858 cpl_msg_debug(cpl_func,
"Nearest candidates at "
859 "distance %f and %f pixels",
868 data.shift_x, data.shift_y) <=
869 search_radius * search_radius) {
871 if (!require_unique ||
873 data.shift_x, data.shift_y) >
874 search_radius * search_radius) {
876 cpl_msg_debug(cpl_func,
877 "unique source inside %f pixels",
880 nearest_1->id = fors_std_star_duplicate(data.ref);
886 cpl_msg_debug(cpl_func,
"Nearest candidate at "
887 "distance %f pixels",
892 data.shift_x, data.shift_y) <=
893 search_radius * search_radius) {
895 cpl_msg_debug(cpl_func,
896 "unique source inside %f pixels",
899 nearest_1->id = fors_std_star_duplicate(data.ref);
906 cpl_msg_info(cpl_func,
"Identified %d star%s",
907 number_of_ids, (number_of_ids == 1) ?
"" :
"s");
909 if (number_of_ids == 0) {
911 if (fabs(sx_00) > 0.1 &&
914 cpl_msg_debug(cpl_func,
915 "No identifications made, "
916 "set shift to zero and try again");
918 require_unique =
false;
924 require_unique =
false;
928 cpl_msg_debug(cpl_func,
929 "No identifications made, "
930 "double search radius and try again");
934 cpl_msg_indent_less();
938 if (number_of_ids == 0) {
939 cpl_msg_warning(cpl_func,
940 "No identifications made, "
941 "within search radius %f pixels",
961 cpl_msg_indent_less();
985 region->xlo <= std->pixel->x && std->pixel->x <= region->xhi &&
986 region->ylo <= std->pixel->y && std->pixel->y <= region->yhi;
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); \
1024 const fors_std_star_list *std,
1032 fors_point_list *std_points = NULL;
1033 fors_point_list *source_points = NULL;
1035 fors_pattern_list *std_patterns = NULL;
1036 fors_pattern_list *source_patterns = NULL;
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;
1048 assure( sx_00 != NULL,
return, NULL );
1049 assure( sy_00 != NULL,
return, NULL );
1052 std_points = fors_point_list_new();
1054 const fors_std_star *s;
1056 for (s = fors_std_star_list_first_const(std);
1058 s = fors_std_star_list_next_const(std)) {
1060 fors_point_list_insert(std_points,
1066 source_points = fors_point_list_new();
1070 for (s = fors_star_list_first_const(stars);
1072 s = fors_star_list_next_const(stars)) {
1074 fors_point_list_insert(source_points,
1080 const double min_dist = 1.0;
1082 double sigma_std = 0.0;
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));
1088 double sigma_source;
1089 if (fors_star_list_size(stars) > 0) {
1090 sigma_source = fors_star_list_median(stars,
1092 cpl_msg_info(cpl_func,
"Average source extension = %.2f pixels", sigma_source);
1097 fors_pattern_new_from_points(source_points, min_dist, sigma_source);
1099 cpl_msg_info(cpl_func,
"Created %d source patterns",
1100 fors_pattern_list_size(source_patterns));
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();
1108 if ( fors_pattern_list_size(source_patterns) > 0) {
1113 for (p = fors_pattern_list_first(std_patterns);
1115 p = fors_pattern_list_next(std_patterns)) {
1118 fors_pattern_list_min_val(source_patterns,
1119 (fors_pattern_list_func_eval) fors_pattern_distsq,
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);
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));
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));
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;
1152 cpl_msg_info(cpl_func,
"Median scale = %.4f +- %.4f",
1153 scale_avg, scale_stdev);
1155 *med_scale = scale_avg;
1157 if (scale_stdev > 0.2) {
1158 cpl_msg_warning(cpl_func,
"Uncertain scale determination");
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);
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));
1176 *med_angle = angle_avg;
1178 if (angle_avg > M_PI/4 || angle_avg < -M_PI/4) {
1179 cpl_msg_warning(cpl_func,
"Expected orientation = 0 degrees");
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));
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");
1199 shiftx = double_list_new();
1200 shifty = double_list_new();
1204 for (p = fors_pattern_list_first(std_patterns);
1206 p = fors_pattern_list_next(std_patterns)) {
1209 fors_pattern_list_min_val(
1211 (fors_pattern_list_func_eval) fors_pattern_distsq,
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);
1219 cpl_msg_debug(cpl_func,
"scale, orientation, distance, norm.distance "
1221 scale, angle * 360/(2*M_PI), dist, dist_per_error);
1223 if (dist_per_error < 1.0 &&
1224 fabs(scale - scale_avg) <= kappa * scale_stdev &&
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;
1231 cpl_msg_debug(cpl_func,
"Accepted, shift = (%f, %f) pixels",
1234 double_list_insert(shiftx, double_duplicate(&shift_x));
1235 double_list_insert(shifty, double_duplicate(&shift_y));
1240 if (double_list_size(shiftx) > 0) {
1241 *sx_00 = double_list_median(shiftx, double_eval, NULL);
1250 cpl_msg_warning(cpl_func,
"No standardstar identifications!");
1254 if (double_list_size(shiftx) > 0) {
1255 *sy_00 = double_list_median(shifty, double_eval, NULL);
1258 cpl_msg_warning(cpl_func,
"No standardstar identifications!");
1263 cpl_msg_warning(cpl_func,
1264 "Too few (%d) matching patterns: assuming zero shift",
1265 double_list_size(scales));
fors_point * fors_point_new(double x, double y)
Constructor.
double fors_star_get_x(const fors_star *s, void *data)
Get position.
void fors_star_print_list(cpl_msg_severity level, const fors_star_list *sl)
Print list of stars.
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.
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.
static bool std_brighter_than(const fors_std_star *s1, void *s2)
Compare brightness.
void fors_point_delete(fors_point **p)
Destructor.
fors_star * fors_star_duplicate(const fors_star *star)
Copy constructor.
double fors_star_get_y(const fors_star *s, void *data)
Get position.
double fors_star_extension(const fors_star *s, void *data)
Get star size.
bool fors_star_brighter_than(const fors_star *s1, const fors_star *s2, void *data)
Compare star brightness.
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.
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)
void fors_identify_define_parameters(cpl_parameterlist *parameters, const char *context)
Define recipe parameters.