Source code for eloy.alignment

"""
Alignment utilities for astronomical image processing.

This module provides functions for matching, aligning, and transforming
coordinate sets, including cross-matching, reference asterism generation,
and rotation matrix calculation using the twirl package.
"""

from scipy.spatial import cKDTree
from twirl import quads
from twirl.geometry import get_transform_matrix, pad
from twirl.match import count_cross_match
import numpy as np


[docs] def cross_match(S1, S2, tolerance=10, return_idxs=False, none=True): """ Matches points from two sets of points based on proximity. Parameters ---------- S1 : numpy.ndarray A 2D array where each row represents a point in the first set. S2 : numpy.ndarray A 2D array where each row represents a point in the second set. tolerance : float, optional The maximum distance for a point to be considered a match. Defaults to 10. return_idxs : bool, optional If True, returns the indices of the matched points. Defaults to False. none : bool, optional If True, appends a NaN value for unmatched points in S1. Defaults to True. Returns ------- numpy.ndarray If return_idxs is True, returns a 2D array of indices. Otherwise, returns two 2D arrays of matched points from S1 and S2, respectively. If no matches are found, returns empty arrays. """ # cleaning s1 = S1.copy() s2 = S2.copy() s1[np.any(np.isnan(s1), 1)] = (1e15, 1e15) s2[np.any(np.isnan(s2), 1)] = (1e15, 1e15) # matching matches = [] for i, s in enumerate(s1): distances = np.linalg.norm(s - s2, axis=1) closest = np.argmin(distances) if distances[closest] < tolerance: matches.append([i, closest]) else: if none: matches.append([i, np.nan]) matches = np.array(matches) matches = matches[np.all(~np.isnan(matches), 1)] matches = matches.astype(int) if return_idxs: return matches else: if len(matches) > 0: return s1[matches[:, 0]], s2[matches[:, 1]] else: return np.array([]), np.array([])
[docs] def twirl_reference(coords): """ Creates a cKDTree and stores asterisms for reference coordinates. Parameters ---------- coords : numpy.ndarray A 2D array of reference coordinates. Returns ------- tuple Tuple (cKDTree, numpy.ndarray) containing the tree and asterisms. """ quads_ref, asterisms_ref = quads.hashes(coords) tree_ref = cKDTree(quads_ref) return tree_ref, asterisms_ref
[docs] def rotation_matrix(coords, ref_coords, reference, tolerance=2, refine=True, rtol=0.02): """ Calculates a rotation matrix aligning two sets of coordinates. Parameters ---------- coords : numpy.ndarray A 2D array of coordinates to be aligned. ref_coords : numpy.ndarray A 2D array of reference coordinates. reference : tuple Tuple (cKDTree, numpy.ndarray) for the reference coordinates. tolerance : float, optional Tolerance for matching points. refine : bool, optional Whether to refine the rotation matrix. rtol : float, optional Relative tolerance for the cKDTree query. Returns ------- numpy.ndarray or None A 3x3 numpy array representing the rotation matrix, or None if no matches are found. """ tree_ref, asterisms_ref = reference quads_image, asterisms_image = quads.hashes(coords) tree_image = cKDTree(quads_image) min_match = 0.7 ball_query = tree_image.query_ball_tree(tree_ref, r=rtol) pairs = [] for i, j in enumerate(ball_query): if len(j) > 0: pairs += [[i, k] for k in j] matches = [] for i, j in pairs: M = get_transform_matrix(asterisms_ref[j], asterisms_image[i]) test = (M @ pad(ref_coords).T)[0:2].T match = count_cross_match(coords, test, tolerance) matches.append(match) if min_match is not None: if isinstance(min_match, float): if match >= min_match * len(coords): break if len(matches) == 0: return None else: i, j = pairs[np.argmax(matches)] if refine: M = get_transform_matrix(asterisms_ref[j], asterisms_image[i]) test = (M @ pad(ref_coords).T)[0:2].T s1, s2 = cross_match(coords, test, tolerance=tolerance, return_idxs=True).T M = get_transform_matrix(ref_coords[s2], coords[s1]) else: M = get_transform_matrix(asterisms_ref[j], asterisms_image[i]) return M