Commit 13d819f7 authored by Adam Paquette's avatar Adam Paquette
Browse files

Updated mutual information matcher based on PR feedback

parent 39f744b0
Loading
Loading
Loading
Loading
+66 −64
Original line number Diff line number Diff line
@@ -2,42 +2,9 @@ from math import floor

import numpy as np

def mutual_information(d_template, s_image, subpixel_size=3, max_scaler=0.2,
                       bins=100, func=None):
    """
    Applys the mutual information matcher function over a search image using a
    defined template. Where the search area is 2x the size of the template image


    Parameters
    ----------
    template : ndarray
               The input search template used to 'query' the destination
               image

    image : ndarray
            The image or sub-image to be searched

    bins : int
           Number of bins to use when computing the histograms

    Returns
    -------
    x : float
        The x offset

    y : float
        The y offset

    max_corr : float
               The strength of the correlation in the range [-1, 1].

    corr_map : ndarray
               Map of corrilation coefficients when comparing the template to
               locations within the search area
    """
from scipy.ndimage.measurements import center_of_mass

    def mutual_information_match(t1, t2, **kwargs):
def mutual_information(t1, t2, **kwargs):
    """
    Computes the correlation coefficient between two images using a histogram
    comparison (Mutual information for joint histograms). The corr_map coefficient
@@ -58,6 +25,10 @@ def mutual_information(d_template, s_image, subpixel_size=3, max_scaler=0.2,
    : float
      Correlation coefficient computed between the two images being compared
      between 0 and 4

    See Also
    --------
    numpy.histogram2d : for the kwargs that can be passed to the comparison
    """
    hgram, x_edges, y_edges = np.histogram2d(t1.ravel(),t2.ravel(), **kwargs)

@@ -70,14 +41,56 @@ def mutual_information(d_template, s_image, subpixel_size=3, max_scaler=0.2,
    nzs = pxy > 0 # Only non-zero pxy values contribute to the sum
    return np.sum(pxy[nzs] * np.log(pxy[nzs] / px_py[nzs]))

def mutual_information_match(d_template, s_image, subpixel_size=3, bins=100,
                       func=None, **kwargs):
    """
    Applys the mutual information matcher function over a search image using a
    defined template


    Parameters
    ----------
    d_template : ndarray
                 The input search template used to 'query' the destination
                 image

    s_image : ndarray
              The image or sub-image to be searched

    subpixel_size : int
                    Subpixel area size to search for the center of mass
                    calculation

    bins : int
           Number of bins to use when computing the histograms

    func : function
           Function object to be used to compute the histogram comparison

    Returns
    -------
    x : float
        The x offset

    y : float
        The y offset

    max_corr : float
               The strength of the correlation in the range [0, 4].

    corr_map : ndarray
               Map of corrilation coefficients when comparing the template to
               locations within the search area
    """

    if func == None:
        func = mutual_information_match
        func = mutual_information

    image_size = s_image.shape
    template_size = d_template.shape

    y_diff = abs(template_size[0] - image_size[0])
    x_diff = abs(template_size[1] - image_size[1])
    y_diff = image_size[0] - template_size[0]
    x_diff = image_size[1] - template_size[1]

    max_corr = -np.inf
    corr_map = np.zeros((y_diff+1, x_diff+1))
@@ -87,15 +100,13 @@ def mutual_information(d_template, s_image, subpixel_size=3, max_scaler=0.2,
        for j in range(x_diff+1):
            sub_image = s_image[i:i+template_size[1],  # y
                                j:j+template_size[0]]  # x
            corr = func(sub_image, d_template, bins=bins)
            corr = func(sub_image, d_template, bins=bins, **kwargs)
            if corr > max_corr:
                max_corr = corr
                max_i = i
                max_j = j
            corr_map[i, j] = corr

    # This is still operating at the pixel scale. Use the template_match_autoreg
    # logic to achieve submpixel weighting.
    y, x = np.unravel_index(np.argmax(corr_map, axis=None), corr_map.shape)

    upper = int(2 + floor(subpixel_size / 2))
@@ -104,27 +115,18 @@ def mutual_information(d_template, s_image, subpixel_size=3, max_scaler=0.2,
    area = corr_map[y-lower:y+upper,
                    x-lower:x+upper]

    # Compute the y, x shift (subpixel) using scipys center_of_mass function
    cmass  = center_of_mass(area)

    if area.shape != (subpixel_size+2, subpixel_size+2):
        print("Max correlation is too close to the boundary.")
        return None, None, 0, None

    # Find the max on the edges, scale just like autoreg (but why?)
    edge_max = np.max(np.vstack([area[0], area[-1], area[:,0], area[:,-1]]))
    internal = area[1:-1, 1:-1]
    mask = (internal > edge_max + max_scaler * (edge_max-max_corr)).flatten()

    empty = np.column_stack([np.repeat(np.arange(0,subpixel_size),subpixel_size),
                             np.tile(np.arange(0,subpixel_size),subpixel_size),
                             np.zeros(subpixel_size*subpixel_size)])
    empty[:,-1] = internal.ravel()

    to_weight = empty[mask, :]
    # Average is the shift from y, x form
    average = np.average(to_weight[:,:2], axis=0, weights=to_weight[:,2])
    subpixel_y_shift = subpixel_size - 1 - cmass[0]
    subpixel_x_shift = subpixel_size - 1 - cmass[1]

    # The center of the 3x3 window is 1.5,1.5, so the shift needs to be recentered to 0,0
    y += (subpixel_size/2 - average[0])
    x += (subpixel_size/2 - average[1])
    y += subpixel_y_shift
    x += subpixel_x_shift

    # Compute the idealized shift (image center)
    y -= (s_image.shape[0] / 2) - (d_template.shape[0] / 2)
+6 −7
Original line number Diff line number Diff line
@@ -11,14 +11,13 @@ from .. import mutual_information

def test_good_mi():
    test_image1 = np.array([[i for i in range(50)] for j in range(50)])
    # test_image2 = np.ones((50, 50))
    corrilation = mutual_information.mi(test_image1, test_image1)
    corrilation = mutual_information.mutual_information(test_image1, test_image1)
    assert corrilation == 2.3025850929940455

def test_bad_mi():
    test_image1 = np.array([[i for i in range(50)] for j in range(50)])
    test_image2 = np.ones((50, 50))
    corrilation = mutual_information.mi(test_image1, test_image2)
    corrilation = mutual_information.mutual_information(test_image1, test_image2)
    assert corrilation == pytest.approx(0)

def test_mutual_information():
@@ -27,9 +26,9 @@ def test_mutual_information():

    s_image[25:75, 25:75] = d_template

    x_offset, y_offset, max_corr, corr_map = mutual_information.mutual_information(d_template, s_image, bins=20)
    assert x_offset == 0.9633527901853505
    assert y_offset == 0.5
    x_offset, y_offset, max_corr, corr_map = mutual_information.mutual_information_match(d_template, s_image, bins=20)
    assert x_offset == 0.01711861257171421
    assert y_offset == 0.0
    assert max_corr == 2.9755967600033015
    assert corr_map.shape == (51, 51)
    assert np.average(corr_map) == 1.3199548152066989
    assert np.min(corr_map) >= 0.0