Commit 9835900a authored by jay's avatar jay
Browse files

adds subpixel register and costs df

parent 71452dca
Loading
Loading
Loading
Loading
+87 −45
Original line number Diff line number Diff line
@@ -77,6 +77,22 @@ class Edge(dict, MutableMapping):
    def matches(self, value):
        if isinstance(value, pd.DataFrame):
            self._matches = value
            # Ensure that the costs df remains in sync with the matches df
            if not self.costs.index.equals(value.index):
                self.costs = pd.DataFrame(index=value.index)
        else:
            raise(TypeError)
    
    @property
    def costs(self):
        if not hasattr(self, '_costs'):
            self._costs = pd.DataFrame(index=self.matches.index)
        return self._costs

    @costs.setter
    def costs(self, value):
        if isinstance(value, pd.DataFrame):
            self._costs = value
        else:
            raise(TypeError)

@@ -150,15 +166,17 @@ class Edge(dict, MutableMapping):
        #Set the columns of the matches df
        matches = np.empty((pidx.shape[0], 4))
        matches[:,0] = self.source['node_id']
        matches[:,1] = pidx[:,0]
        matches[:,1] = ref_kps.index[pidx[:,0]].values
        matches[:,2] = self.destination['node_id']
        matches[:,3] = pidx[:,1]
        matches[:,3] = tar_kps.index[pidx[:,1]].values

        matches = pd.DataFrame(matches, columns=['source',
                                                 'source_idx',
                                                 'destination',
                                                 'destination_idx']).astype(np.float32)
        
        matches = matches.drop_duplicates()

        self.matches = matches

    def add_coordinates_to_matches(self):
@@ -377,9 +395,8 @@ class Edge(dict, MutableMapping):
        mask[mask] = hmask
        self.masks['homography'] = mask

    def subpixel_register(self, clean_keys=[], threshold=0.8,
                          template_size=19, search_size=53, max_x_shift=1.0,
                          max_y_shift=1.0, tiled=False, **kwargs):
    def subpixel_register(self, method='phase', clean_keys=[],
                          template_size=251, search_size=251, **kwargs):
        """
        For the entire graph, compute the subpixel offsets using pattern-matching and add the result
        as an attribute to each edge of the graph.
@@ -413,59 +430,84 @@ class Edge(dict, MutableMapping):
                      The maximum (positive) value that a pixel can shift in the y direction
                      without being considered an outlier
        """
        for column, default in {'x_offset': 0, 'y_offset': 0, 'correlation': 0, 'reference': -1}.items():
            if column not in self.subpixel_matches.columns:
                self.subpixel_matches[column] = default

        # Build up a composite mask from all of the user specified masks
        matches, mask = self.clean(clean_keys)

        # Grab the full images, or handles
        if tiled is True:
        # Get the img handles
        s_img = self.source.geodata
        d_img = self.destination.geodata
        else:
            s_img = self.source.geodata.read_array()
            d_img = self.destination.geodata.read_array()

        source_image = (matches.iloc[0]['source_image'])
        # Setup to store output to append to dataframes
        shifts_x = np.empty(len(matches))
        shifts_x[:] = np.nan
        shifts_y = np.empty(len(matches))
        shifts_y[:] = np.nan

        # Determine which algorithm is going ot be used.
        if method == 'phase':
            func = sp.subpixel_phase
            strengths = np.empty((len(matches), 2))
        elif method == 'template':
            func = sp.subpixel_template
            strengths = np.empty(len(matches))
        strengths[:] = np.nan

        pts = []
        # for each edge, calculate this for each keypoint pair
        for i, (idx, row) in enumerate(matches.iterrows()):
            s_idx = int(row['source_idx'])
            d_idx = int(row['destination_idx'])

            s_keypoint = self.source.get_keypoint_coordinates(s_idx)
            d_keypoint = self.destination.get_keypoint_coordinates(d_idx)
            s_keypoint = self.source.get_keypoint_coordinates([s_idx])
            d_keypoint = self.destination.get_keypoint_coordinates([d_idx])

            s_template, sx, sy = sp.clip_roi(s_img, s_keypoint.x, s_keypoint.y,
                                     size_x=template_size, size_y=template_size)
            d_search, dx, dy = sp.clip_roi(d_img, d_keypoint.x, d_keypoint.y,
                                   size_x=search_size, size_y=search_size)
            
            # Now check to see if these are the same size.
            if method == 'phase' and (s_template.shape != d_search.shape):
                s_size = s_template.shape
                d_size = d_search.shape
                updated_size = int(min(s_size + d_size) / 2)
                s_template, sx, sy = sp.clip_roi(s_img, s_keypoint.x, s_keypoint.y,
                                     size_x=updated_size, size_y=updated_size)
                d_search, dx, dy = sp.clip_roi(d_img, d_keypoint.x, d_keypoint.y,
                                    size_x=updated_size, size_y=updated_size)         
            
            shift_x, shift_y, metrics = func(s_template, d_search, **kwargs)

            # ROIs and clipping all work using whole pixels. The clip_roi func returns
            # the subpixel components that are lost when converting to whole pixels
            # reapply those here.
            shift_x += dx
            shift_y += dy

            shifts_x[i] = shift_x
            shifts_y[i] = shift_y
            strengths[i] = metrics
        
        matches['shift_x'] = shifts_x
        matches['shift_y'] = shifts_y
        
        costs = self.costs
        if method == 'phase':
            costs['phase'] = [i[0] for i in strengths]
            costs['rmse'] = [i[1] for i in strengths]
        elif method == 'template':
            costs['correlation'] = strengths

        c = self.costs
        # Set the defaults for the columns
        for column in costs.columns:
            c[column] = np.nan
        c[mask.values] = costs
        self.costs = c

        m = self.matches
        m[mask.values] = matches
        self.matches = m 

            # Get the template and search window
            s_template = sp.clip_roi(s_img, s_keypoint, template_size)
            d_search = sp.clip_roi(d_img, d_keypoint, search_size)
            if 0 in s_template.shape or 0 in d_search.shape:
                continue
            try:
                (x_offset, y_offset, strength),ref = sp.subpixel_offset(s_template, d_search, **kwargs)
                self.subpixel_matches.loc[idx, ('x_offset', 'y_offset', 'correlation', 'reference')]= [x_offset, y_offset, strength, source_image]
                pts.append([s_template, d_search, ref, x_offset, y_offset])
            except:
                warnings.warn('Template-Search size mismatch, failing for this correspondence point.')

        # Compute the mask for correlations less than the threshold
        threshold_mask = self.subpixel_matches['correlation'] >= threshold

        # Compute the mask for the point shifts that are too large
        query_string = 'x_offset <= -{0} or x_offset >= {0} or y_offset <= -{1} or y_offset >= {1}'.format(max_x_shift,max_y_shift)
        sp_shift_outliers = self.subpixel_matches.query(query_string)
        shift_mask = pd.Series(True, index=self.subpixel_matches.index)
        shift_mask.loc[sp_shift_outliers.index] = False

        # Generate the composite mask and write the masks to the mask data structure
        mask = threshold_mask & shift_mask
        self.masks['shift'] = shift_mask
        self.masks['threshold'] = threshold_mask
        self.masks['subpixel'] = mask
        return pts

    def suppress(self, suppression_func=spf.correlation, clean_keys=[], maskname='suppression', **kwargs):
        """
+20 −27
Original line number Diff line number Diff line
from math import modf, floor
import numpy as np

from skimage.feature import register_translation
@@ -8,8 +9,7 @@ from autocnet.matcher import ciratefi

# TODO: look into KeyPoint.size and perhaps use to determine an appropriately-sized search/template.


def clip_roi(img, center, img_size):
def clip_roi(img, center_x, center_y, size_x=200, size_y=200):
    """
    Given an input image, clip a square region of interest
    centered on some pixel at some size.
@@ -32,30 +32,23 @@ def clip_roi(img, center, img_size):
    clipped_img : ndarray
                  The clipped image
    """
    if img_size % 2 == 0:
        raise ValueError('Image size must be odd.')

    i = int((img_size - 1) / 2)

    x, y = map(int, center)

    y_start = y - i
    x_start = x - i
    x_stop = (x + i) - x_start
    y_stop = (y + i) - y_start

    if x_start < 0:
        x_start = 0
    if y_start < 0:
        y_start = 0

    if isinstance(img, np.ndarray):
        clipped_img = img[y_start:y_start + y_stop + 1,
                          x_start:x_start + x_stop + 1]
    else:
        clipped_img = img.read_array(pixels=[x_start, y_start,
                                             x_stop + 1, y_stop + 1])
    return clipped_img
    raster_size = img.raster_size
    axr, ax = modf(center_x)
    ayr, ay = modf(center_y)

    if ax + size_x > raster_size[0]:
        size_x = floor(raster_size[0] - center_x)
    if ax - size_x < 0:
        size_x = int(ax)
    if ay + size_y > raster_size[1]:
        size_y = floor(raster_size[1] - center_y)
    if ay - size_y < 0:
        size_y = int(ay)

    # Read from the upper left origin
    pixels=(int(ax-size_x), int(ay-size_y), size_x * 2, size_y * 2)
    subarray = img.read_array(pixels=pixels)
    return subarray, axr, ayr

def subpixel_phase(template, search, **kwargs):
    """
@@ -90,7 +83,7 @@ def subpixel_phase(template, search, **kwargs):
    (y_shift, x_shift), error, diffphase = register_translation(search, template, **kwargs)
    return x_shift, y_shift, (error, diffphase)

def subpixel_offset(template, search, **kwargs):
def subpixel_template(template, search, **kwargs):
    """
    Uses a pattern-matcher on subsets of two images determined from the passed-in keypoints and optional sizes to
    compute an x and y offset from the search keypoint to the template keypoint and an associated strength.