Source code for sunkit_image.coalignment.match_template

import numpy as np
from skimage.feature import match_template

from sunpy import log

from sunkit_image.coalignment.register import register_coalignment_method

__all__ = ["match_template_coalign"]


def _parabolic_turning_point(y):
    """
    Calculate the turning point of a parabola given three points.

    Parameters
    ----------
    y : `numpy.ndarray`
        An array of three points defining the parabola.

    Returns
    -------
    float
        The x-coordinate of the turning point.
    """
    return (-0.5 * y.dot([-1, 0, 1])) / y.dot([1, -2, 1])


def _get_correlation_shifts(array):
    """
    Calculate the shifts in x and y directions based on the correlation array.

    Parameters
    ----------
    array : `numpy.ndarray`
        A 2D array representing the correlation values.

    Returns
    -------
    tuple
        The shifts in y and x directions.

    Raises
    ------
    ValueError
        If the input array dimensions are greater than 3 in any direction.
    """
    ny, nx = array.shape
    if nx > 3 or ny > 3:
        msg = "Input array dimension should not be greater than 3 in any dimension."
        raise ValueError(msg)
    ij = np.unravel_index(np.argmax(array), array.shape)
    x_max_location, y_max_location = ij[::-1]
    y_location = _parabolic_turning_point(array[:, x_max_location]) if ny == 3 else 1.0 * y_max_location
    x_location = _parabolic_turning_point(array[y_max_location, :]) if nx == 3 else 1.0 * x_max_location
    return y_location, x_location


def _find_best_match_location(corr):
    """
    Find the best match location in the correlation array.

    Parameters
    ----------
    corr : `numpy.ndarray`
        The correlation array.

    Returns
    -------
    tuple
        The best match location in the y and x directions.
    """
    ij = np.unravel_index(np.argmax(corr), corr.shape)
    cor_max_x, cor_max_y = ij[::-1]
    array_maximum = corr[
        max(0, cor_max_y - 1) : min(cor_max_y + 2, corr.shape[0] - 1),
        max(0, cor_max_x - 1) : min(cor_max_x + 2, corr.shape[1] - 1),
    ]
    y_shift_maximum, x_shift_maximum = _get_correlation_shifts(array_maximum)
    y_shift_correlation_array = y_shift_maximum + cor_max_y
    x_shift_correlation_array = x_shift_maximum + cor_max_x
    return y_shift_correlation_array, x_shift_correlation_array


[docs] @register_coalignment_method("match_template") def match_template_coalign(target_array, reference_array, **kwargs): """ Perform coalignment by matching the input array to the target array. Coalign ``target_array`` to ``reference_array`` using normalized correlation via `skimage.feature.match_template`. For more details on this approach, please check the documentation of that function including the available keyword arguments and the details of the algorithm. .. warning:: This method only returns a translation. The scale and rotation parameters are unity. Parameters ---------- target_array : `numpy.ndarray` The input 2D array to be coaligned. reference_array : `numpy.ndarray` The template 2D array to align to. kwargs: dict Passed to `skimage.feature.match_template`. Returns ------- `sunkit_image.coalignment.interface.AffineParams` """ from sunkit_image.coalignment.interface import AffineParams # NOQA: PLC0415 corr = match_template(np.float64(reference_array), np.float64(target_array), **kwargs) if len(corr) < 2: raise ValueError( "match_template returned an array with fewer than two values, and cannot be used for coalignment. " "This implies the cross-correlation failed to find a match." ) # Find the best match location y_shift, x_shift = _find_best_match_location(corr) if kwargs.get("pad_input"): # For pad_input=True matches correspond to the center y_shift -= target_array.shape[0] // 2 x_shift -= target_array.shape[1] // 2 log.debug(f"Match template shift: x: {x_shift}, y: {y_shift}") # Particularly for this method, there is no change in the rotation or scaling, # hence the hardcoded values of scale to 1.0 & rotation to identity matrix scale = np.array([1.0, 1.0]) rotation_matrix = np.eye(2) return AffineParams(scale=scale, rotation_matrix=rotation_matrix, translation=(x_shift, y_shift))