def matchStarsResiduals(config, platepar, catalog_stars, star_dict, match_radius, ret_nmatch=False, \ sky_coords=False, lim_mag=None, verbose=False): """ Match the image and catalog stars with the given astrometry solution and estimate the residuals between them. Arguments: config: [Config structure] platepar: [Platepar structure] Astrometry parameters. catalog_stars: [ndarray] An array of catalog stars (ra, dec, mag). star_dict: [ndarray] A dictionary where the keys are JDs when the stars were recorded and values are 2D list of stars, each entry is (X, Y, bg_level, level). match_radius: [float] Maximum radius for star matching (pixels). min_matched_stars: [int] Minimum number of matched stars on the image for the image to be accepted. Keyword arguments: ret_nmatch: [bool] If True, the function returns the number of matched stars and the average deviation. False by default. sky_coords: [bool] If True, sky coordinate residuals in RA, dec will be used to compute the cost, function, not image coordinates. lim_mag: [float] Override the limiting magnitude from config. None by default. verbose: [bool] Print results. True by default. Return: cost: [float] The cost function which weights the number of matched stars and the average deviation. """ if lim_mag is None: lim_mag = config.catalog_mag_limit # Estimate the FOV radius fov_w = platepar.X_res/platepar.F_scale fov_h = platepar.Y_res/platepar.F_scale fov_radius = np.sqrt((fov_w/2)**2 + (fov_h/2)**2) # print('fscale', platepar.F_scale) # print('FOV w:', fov_w) # print('FOV h:', fov_h) # print('FOV radius:', fov_radius) # Dictionary containing the matched stars, the keys are JDs of every image matched_stars = {} # Go through every FF image and its stars for jd in star_dict: # Estimate RA,dec of the centre of the FOV _, RA_c, dec_c, _ = xyToRaDecPP([jd2Date(jd)], [platepar.X_res/2], [platepar.Y_res/2], [1], platepar) RA_c = RA_c[0] dec_c = dec_c[0] # Get stars from the catalog around the defined center in a given radius _, extracted_catalog = subsetCatalog(catalog_stars, RA_c, dec_c, fov_radius, lim_mag) ra_catalog, dec_catalog, mag_catalog = extracted_catalog.T # Extract stars for the given Julian date stars_list = star_dict[jd] stars_list = np.array(stars_list) # Convert all catalog stars to image coordinates cat_x_array, cat_y_array = raDecToXYPP(ra_catalog, dec_catalog, jd, platepar) # Take only those stars which are within the FOV x_indices = np.argwhere((cat_x_array >= 0) & (cat_x_array < platepar.X_res)) y_indices = np.argwhere((cat_y_array >= 0) & (cat_y_array < platepar.Y_res)) cat_good_indices = np.intersect1d(x_indices, y_indices).astype(np.uint32) # cat_x_array = cat_x_array[good_indices] # cat_y_array = cat_y_array[good_indices] # # Plot image stars # im_y, im_x, _, _ = stars_list.T # plt.scatter(im_y, im_x, facecolors='none', edgecolor='g') # # Plot catalog stars # plt.scatter(cat_y_array[cat_good_indices], cat_x_array[cat_good_indices], c='r', s=20, marker='+') # plt.show() # Match image and catalog stars matched_indices = matchStars(stars_list, cat_x_array, cat_y_array, cat_good_indices, match_radius) # Skip this image is no stars were matched if len(matched_indices) < config.min_matched_stars: continue matched_indices = np.array(matched_indices) matched_img_inds, matched_cat_inds, dist_list = matched_indices.T # Extract data from matched stars matched_img_stars = stars_list[matched_img_inds.astype(np.int)] matched_cat_stars = extracted_catalog[matched_cat_inds.astype(np.int)] # Put the matched stars to a dictionary matched_stars[jd] = [matched_img_stars, matched_cat_stars, dist_list] # # Plot matched stars # im_y, im_x, _, _ = matched_img_stars.T # cat_y = cat_y_array[matched_cat_inds.astype(np.int)] # cat_x = cat_x_array[matched_cat_inds.astype(np.int)] # plt.scatter(im_x, im_y, c='r', s=5) # plt.scatter(cat_x, cat_y, facecolors='none', edgecolor='g') # plt.xlim([0, platepar.X_res]) # plt.ylim([platepar.Y_res, 0]) # plt.show() # If residuals on the image should be computed if not sky_coords: unit_label = 'px' # Extract all distances global_dist_list = [] # level_list = [] # mag_list = [] for jd in matched_stars: # matched_img_stars, matched_cat_stars, dist_list = matched_stars[jd] _, _, dist_list = matched_stars[jd] global_dist_list += dist_list.tolist() # # TEST # level_list += matched_img_stars[:, 3].tolist() # mag_list += matched_cat_stars[:, 2].tolist() # # Plot levels vs. magnitudes # plt.scatter(mag_list, np.log10(level_list)) # plt.xlabel('Magnitude') # plt.ylabel('Log10 level') # plt.show() # Compute the residuals on the sky else: unit_label = 'arcmin' global_dist_list = [] # Go through all matched stars for jd in matched_stars: matched_img_stars, matched_cat_stars, dist_list = matched_stars[jd] # Go through all stars on the image for img_star_entry, cat_star_entry in zip(matched_img_stars, matched_cat_stars): # Extract star coords star_y = img_star_entry[0] star_x = img_star_entry[1] cat_ra = cat_star_entry[0] cat_dec = cat_star_entry[1] # Convert image coordinates to RA/Dec _, star_ra, star_dec, _ = xyToRaDecPP([jd2Date(jd)], [star_x], [star_y], [1], \ platepar) # Compute angular distance between the predicted and the catalog position ang_dist = np.degrees(angularSeparation(np.radians(cat_ra), np.radians(cat_dec), \ np.radians(star_ra[0]), np.radians(star_dec[0]))) # Store the angular separation in arc minutes global_dist_list.append(ang_dist*60) # Number of matched stars n_matched = len(global_dist_list) if n_matched == 0: if verbose: print('No matched stars with radius {:.2f} px!'.format(match_radius)) if ret_nmatch: return 0, 9999.0, 9999.0, {} else: return 9999.0 # Calculate the average distance avg_dist = np.mean(global_dist_list) cost = (avg_dist**2)*(1.0/np.sqrt(n_matched + 1)) if verbose: print('Matched {:d} stars with radius of {:.2f} px'.format(n_matched, match_radius)) print('Avg dist', avg_dist, unit_label) print('Cost:', cost) print('-----') if ret_nmatch: return n_matched, avg_dist, cost, matched_stars else: return cost
def matchStarsResiduals(config, platepar, catalog_stars, star_dict, match_radius, ret_nmatch=False, \ sky_coords=False, lim_mag=None, verbose=False): """ Match the image and catalog stars with the given astrometry solution and estimate the residuals between them. Arguments: config: [Config structure] platepar: [Platepar structure] Astrometry parameters. catalog_stars: [ndarray] An array of catalog stars (ra, dec, mag). star_dict: [ndarray] A dictionary where the keys are JDs when the stars were recorded and values are 2D list of stars, each entry is (X, Y, bg_level, level, fwhm). match_radius: [float] Maximum radius for star matching (pixels). min_matched_stars: [int] Minimum number of matched stars on the image for the image to be accepted. Keyword arguments: ret_nmatch: [bool] If True, the function returns the number of matched stars and the average deviation. False by default. sky_coords: [bool] If True, sky coordinate residuals in RA, dec will be used to compute the cost, function, not image coordinates. lim_mag: [float] Override the limiting magnitude from config. None by default. verbose: [bool] Print results. True by default. Return: cost: [float] The cost function which weights the number of matched stars and the average deviation. """ if lim_mag is None: lim_mag = config.catalog_mag_limit # Estimate the FOV radius fov_radius = getFOVSelectionRadius(platepar) # Dictionary containing the matched stars, the keys are JDs of every image matched_stars = {} # Go through every FF image and its stars for jd in star_dict: # Estimate RA,dec of the centre of the FOV _, RA_c, dec_c, _ = xyToRaDecPP([jd2Date(jd)], [platepar.X_res/2], [platepar.Y_res/2], [1], \ platepar, extinction_correction=False) RA_c = RA_c[0] dec_c = dec_c[0] # Get stars from the catalog around the defined center in a given radius _, extracted_catalog = subsetCatalog(catalog_stars, RA_c, dec_c, jd, platepar.lat, platepar.lon, \ fov_radius, lim_mag) ra_catalog, dec_catalog, mag_catalog = extracted_catalog.T # Extract stars for the given Julian date stars_list = star_dict[jd] stars_list = np.array(stars_list) # Convert all catalog stars to image coordinates cat_x_array, cat_y_array = raDecToXYPP(ra_catalog, dec_catalog, jd, platepar) # Take only those stars which are within the FOV x_indices = np.argwhere((cat_x_array >= 0) & (cat_x_array < platepar.X_res)) y_indices = np.argwhere((cat_y_array >= 0) & (cat_y_array < platepar.Y_res)) cat_good_indices = np.intersect1d(x_indices, y_indices).astype(np.uint32) # cat_x_array = cat_x_array[good_indices] # cat_y_array = cat_y_array[good_indices] # # Plot image stars # im_y, im_x, _, _ = stars_list.T # plt.scatter(im_y, im_x, facecolors='none', edgecolor='g') # # Plot catalog stars # plt.scatter(cat_y_array[cat_good_indices], cat_x_array[cat_good_indices], c='r', s=20, marker='+') # plt.show() # Match image and catalog stars matched_indices = matchStars(stars_list, cat_x_array, cat_y_array, cat_good_indices, match_radius) # Skip this image is no stars were matched if len(matched_indices) < config.min_matched_stars: continue matched_indices = np.array(matched_indices) matched_img_inds, matched_cat_inds, dist_list = matched_indices.T # Extract data from matched stars matched_img_stars = stars_list[matched_img_inds.astype(np.int)] matched_cat_stars = extracted_catalog[matched_cat_inds.astype(np.int)] # Put the matched stars to a dictionary matched_stars[jd] = [matched_img_stars, matched_cat_stars, dist_list] # # Plot matched stars # im_y, im_x, _, _ = matched_img_stars.T # cat_y = cat_y_array[matched_cat_inds.astype(np.int)] # cat_x = cat_x_array[matched_cat_inds.astype(np.int)] # plt.scatter(im_x, im_y, c='r', s=5) # plt.scatter(cat_x, cat_y, facecolors='none', edgecolor='g') # plt.xlim([0, platepar.X_res]) # plt.ylim([platepar.Y_res, 0]) # plt.show() # If residuals on the image should be computed if not sky_coords: unit_label = 'px' # Extract all distances global_dist_list = [] # level_list = [] # mag_list = [] for jd in matched_stars: # matched_img_stars, matched_cat_stars, dist_list = matched_stars[jd] _, _, dist_list = matched_stars[jd] global_dist_list += dist_list.tolist() # # TEST # level_list += matched_img_stars[:, 3].tolist() # mag_list += matched_cat_stars[:, 2].tolist() # # Plot levels vs. magnitudes # plt.scatter(mag_list, np.log10(level_list)) # plt.xlabel('Magnitude') # plt.ylabel('Log10 level') # plt.show() # Compute the residuals on the sky else: unit_label = 'arcmin' global_dist_list = [] # Go through all matched stars for jd in matched_stars: matched_img_stars, matched_cat_stars, dist_list = matched_stars[jd] # Go through all stars on the image for img_star_entry, cat_star_entry in zip(matched_img_stars, matched_cat_stars): # Extract star coords star_y = img_star_entry[0] star_x = img_star_entry[1] cat_ra = cat_star_entry[0] cat_dec = cat_star_entry[1] # Convert image coordinates to RA/Dec _, star_ra, star_dec, _ = xyToRaDecPP([jd2Date(jd)], [star_x], [star_y], [1], \ platepar, extinction_correction=False) # Compute angular distance between the predicted and the catalog position ang_dist = np.degrees(angularSeparation(np.radians(cat_ra), np.radians(cat_dec), \ np.radians(star_ra[0]), np.radians(star_dec[0]))) # Store the angular separation in arc minutes global_dist_list.append(ang_dist * 60) # Number of matched stars n_matched = len(global_dist_list) if n_matched == 0: if verbose: print( 'No matched stars with radius {:.1f} px!'.format(match_radius)) if ret_nmatch: return 0, 9999.0, 9999.0, {} else: return 9999.0 # Calculate the average distance avg_dist = np.median(global_dist_list) cost = (avg_dist**2) * (1.0 / np.sqrt(n_matched + 1)) if verbose: print() print("Matched {:d} stars with radius of {:.1f} px".format( n_matched, match_radius)) print(" Average distance = {:.3f} {:s}".format( avg_dist, unit_label)) print(" Cost function = {:.5f}".format(cost)) if ret_nmatch: return n_matched, avg_dist, cost, matched_stars else: return cost
def matchStarsResiduals(config, platepar, catalog_stars, star_dict, match_radius, ret_nmatch=False): """ Match the image and catalog stars with the given astrometry solution and estimate the residuals between them. Arguments: config: [Config structure] platepar: [Platepar structure] Astrometry parameters. catalog_stars: [ndarray] An array of catalog stars (ra, dec, mag). star_dict: [ndarray] A dictionary where the keys are JDs when the stars were recorded and values are 2D list of stars, each entry is (X, Y, bg_level, level). match_radius: [float] Maximum radius for star matching (pixels). min_matched_stars: [int] Minimum number of matched stars on the image for the image to be accepted. Keyword arguments: ret_nmatch: [bool] If True, the function returns the number of matched stars and the average deviation. False by defualt. Return: cost: [float] The cost function which weights the number of matched stars and the average deviation. """ # Estimate the FOV radius fov_w = platepar.X_res / platepar.F_scale fov_h = platepar.Y_res / platepar.F_scale fov_radius = np.sqrt((fov_w / 2)**2 + (fov_h / 2)**2) # print('fscale', platepar.F_scale) # print('FOV w:', fov_w) # print('FOV h:', fov_h) # print('FOV radius:', fov_radius) # Dictionary containing the matched stars, the keys are JDs of every image matched_stars = {} # Go through every FF image and its stars for jd in star_dict: # Estimate RA,dec of the centre of the FOV _, RA_c, dec_c, _ = XY2CorrectedRADecPP([jd2Date(jd)], [platepar.X_res / 2], [platepar.Y_res / 2], [0], platepar) RA_c = RA_c[0] dec_c = dec_c[0] # Get stars from the catalog around the defined center in a given radius _, extracted_catalog = subsetCatalog(catalog_stars, RA_c, dec_c, fov_radius, config.catalog_mag_limit) ra_catalog, dec_catalog, mag_catalog = extracted_catalog.T # Extract stars for the given Julian date stars_list = star_dict[jd] stars_list = np.array(stars_list) # Convert all catalog stars to image coordinates cat_x_array, cat_y_array = raDecToCorrectedXYPP( ra_catalog, dec_catalog, jd, platepar) # Take only those stars which are within the FOV x_indices = np.argwhere((cat_x_array >= 0) & (cat_x_array < platepar.X_res)) y_indices = np.argwhere((cat_y_array >= 0) & (cat_y_array < platepar.Y_res)) cat_good_indices = np.intersect1d(x_indices, y_indices).astype(np.uint32) # cat_x_array = cat_x_array[good_indices] # cat_y_array = cat_y_array[good_indices] # # Plot image stars # im_y, im_x, _, _ = stars_list.T # plt.scatter(im_y, im_x, facecolors='none', edgecolor='g') # # Plot catalog stars # plt.scatter(cat_y_array[cat_good_indices], cat_x_array[cat_good_indices], c='r', s=20, marker='+') # plt.show() # Match image and catalog stars matched_indices = matchStars(stars_list, cat_x_array, cat_y_array, cat_good_indices, match_radius) # Skip this image is no stars were matched if len(matched_indices) < config.min_matched_stars: continue matched_indices = np.array(matched_indices) matched_img_inds, matched_cat_inds, dist_list = matched_indices.T # Extract data from matched stars matched_img_stars = stars_list[matched_img_inds.astype(np.int)] matched_cat_stars = extracted_catalog[matched_cat_inds.astype(np.int)] # Put the matched stars to a dictionary matched_stars[jd] = [matched_img_stars, matched_cat_stars, dist_list] # # Plot matched stars # im_y, im_x, _, _ = matched_img_stars.T # cat_y = cat_y_array[matched_cat_inds.astype(np.int)] # cat_x = cat_x_array[matched_cat_inds.astype(np.int)] # plt.scatter(im_x, im_y, c='r', s=5) # plt.scatter(cat_x, cat_y, facecolors='none', edgecolor='g') # plt.xlim([0, platepar.X_res]) # plt.ylim([platepar.Y_res, 0]) # plt.show() # Extract all distances global_dist_list = [] # level_list = [] # mag_list = [] for jd in matched_stars: # matched_img_stars, matched_cat_stars, dist_list = matched_stars[jd] _, _, dist_list = matched_stars[jd] global_dist_list += dist_list.tolist() # # TEST # level_list += matched_img_stars[:, 3].tolist() # mag_list += matched_cat_stars[:, 2].tolist() # # Plot levels vs. magnitudes # plt.scatter(mag_list, np.log10(level_list)) # plt.xlabel('Magnitude') # plt.ylabel('Log10 level') # plt.show() # Number of matched stars n_matched = len(global_dist_list) if n_matched == 0: if ret_nmatch: return 0, 9999.0, 9999.0, {} else: return 9999.0 # Calculate the average distance avg_dist = np.mean(global_dist_list) cost = (avg_dist**2) * (1.0 / np.sqrt(n_matched + 1)) print('Matched {:d} stars with radius of {:.2f} px'.format( n_matched, match_radius)) print('Avg dist', avg_dist) print('Cost:', cost) print('-----') if ret_nmatch: return n_matched, avg_dist, cost, matched_stars else: return cost