Commit e3784558 authored by Jay's avatar Jay Committed by jay
Browse files

Bug fix for large image sets with unordered edge indices and support for reference index tracking.

parent 725d21fe
Loading
Loading
Loading
Loading
+14 −9
Original line number Diff line number Diff line
@@ -151,8 +151,10 @@ class Edge(dict, MutableMapping):
        transformation_matrix, fundam_mask = od.compute_fundamental_matrix(s_keypoints[['x', 'y']].values,
                                                                           d_keypoints[['x', 'y']].values,
                                                                           **kwargs)

        try:
            fundam_mask = fundam_mask.ravel()
        except:
            return
        # Convert the truncated RANSAC mask back into a full length mask
        if clean_keys:
            mask[mask == True] = fundam_mask
@@ -220,9 +222,9 @@ class Edge(dict, MutableMapping):
        # Finalize the array to get custom attrs to propagate
        self.homography.__array_finalize__(self.homography)

    def subpixel_register(self, clean_keys=[], threshold=0.8, upsampling=16,
    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):
                          max_y_shift=1.0, tiled=False, **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.
@@ -257,9 +259,9 @@ class Edge(dict, MutableMapping):
                      without being considered an outlier
        """
        matches = self.matches
        for column in ['x_offset', 'y_offset', 'correlation']:
        for column, default in {'x_offset': 0, 'y_offset': 0, 'correlation': 0, 'reference': -1}.items():
            if not column in self.matches.columns:
                self.matches[column] = 0
                self.matches[column] = default

        # Build up a composite mask from all of the user specified masks
        if clean_keys:
@@ -273,6 +275,8 @@ class Edge(dict, MutableMapping):
            s_img = self.source.handle.read_array()
            d_img = self.destination.handle.read_array()

        source_image = (matches.iloc[0]['source_image'])

        # for each edge, calculate this for each keypoint pair
        for i, (idx, row) in enumerate(matches.iterrows()):
            s_idx = int(row['source_idx'])
@@ -285,8 +289,9 @@ class Edge(dict, MutableMapping):
            s_template = sp.clip_roi(s_img, s_keypoint, template_size)
            d_search = sp.clip_roi(d_img, d_keypoint, search_size)
            try:
                x_offset, y_offset, strength = sp.subpixel_offset(s_template, d_search, upsampling=upsampling)
                self.matches.loc[idx, ('x_offset', 'y_offset', 'correlation')] = [x_offset, y_offset, strength]
                x_offset, y_offset, strength = sp.subpixel_offset(s_template, d_search, **kwargs)
                self.matches.loc[idx, ('x_offset', 'y_offset',
                                       'correlation', 'reference')] = [x_offset, y_offset, strength, source_image]
            except:
                warnings.warn('Template-Search size mismatch, failing for this correspondence point.')
                continue
@@ -299,7 +304,7 @@ class Edge(dict, MutableMapping):
                                                                                                           max_y_shift)
        sp_shift_outliers = self.matches.query(query_string)
        shift_mask = pd.Series(True, index=self.matches.index)
        shift_mask[sp_shift_outliers.index] = False
        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
+18 −15
Original line number Diff line number Diff line
@@ -55,15 +55,9 @@ class CandidateGraph(nx.Graph):

        nx.relabel_nodes(self, node_labels, copy=False)


        # Add the Edge class as a edge data structure
        for s, d, edge in self.edges_iter(data=True):
            if s < d:
            self.edge[s][d] = Edge(self.node[s], self.node[d])
            else:
                self.remove_edge(s, d)
                self.add_edge(d, s)
                self.edge[d][s] = Edge(self.node[d], self.node[s])

    @classmethod
    def from_graph(cls, graph):
@@ -253,13 +247,17 @@ class CandidateGraph(nx.Graph):
        source_groups = matches.groupby('source_image')
        for i, source_group in source_groups:
            for j, dest_group in source_group.groupby('destination_image'):
                source_key = dest_group['source_image'].values[0]
                destination_key = dest_group['destination_image'].values[0]
                destination_key = int(dest_group['destination_image'].values[0])
                source_key = int(dest_group['source_image'].values[0])
                if (source_key, destination_key) in edges:
                    edge = self.edge[source_key][destination_key]
                else:
                    edge = self.edge[destination_key][source_key]

                    dest_group.rename(columns={'source_image': 'destination_image',
                                               'source_idx': 'destination_idx',
                                               'destination_image': 'source_image',
                                               'destination_idx': 'source_idx'},
                                      inplace=False)
                if hasattr(edge, 'matches'):
                    df = edge.matches
                    edge.matches = df.append(dest_group, ignore_index=True)
@@ -309,14 +307,14 @@ class CandidateGraph(nx.Graph):
            edge.compute_fundamental_matrix(clean_keys=clean_keys, **kwargs)

    def subpixel_register(self, clean_keys=[], threshold=0.8, upsampling=10,
                                 template_size=9, search_size=27, tiled=False):
                                 template_size=9, search_size=27, tiled=False, **kwargs):
         """
         Compute subpixel offsets for all edges using identical parameters
         """
         for s, d, edge in self.edges_iter(data=True):
             edge.subpixel_register(clean_keys=clean_keys, threshold=threshold,
                                    upsampling=upsampling, template_size=template_size,
                                    search_size=search_size, tiled=tiled)
                                    search_size=search_size, tiled=tiled, **kwargs)

    def suppress(self, clean_keys=[], func=spf.correlation, **kwargs):
        for s, d, e in self.edges_iter(data=True):
@@ -394,8 +392,10 @@ class CandidateGraph(nx.Graph):
                matches, mask = edge._clean(clean_keys)

            subpixel = False
            point_type = 2
            if 'subpixel' in clean_keys:
                subpixel = True
                point_type = 3

            kp1 = self.node[source].keypoints
            kp2 = self.node[destination].keypoints
@@ -408,12 +408,14 @@ class CandidateGraph(nx.Graph):
                m1 = (source, int(row['source_idx']))
                m2 = (destination, int(row['destination_idx']))


                values.append([kp1.loc[m1_pid]['x'],
                               kp1.loc[m1_pid]['y'],
                               m1,
                               pt_idx,
                               source,
                               idx])
                               idx,
                               point_type])

                if subpixel:
                    kp2x = kp2.loc[m2_pid]['x'] + row['x_offset']
@@ -427,10 +429,11 @@ class CandidateGraph(nx.Graph):
                               m2,
                               pt_idx,
                               destination,
                               idx])
                               idx,
                               point_type])
                pt_idx += 1

            columns = ['x', 'y', 'idx', 'pid', 'nid', 'mid']
            columns = ['x', 'y', 'idx', 'pid', 'nid', 'mid', 'point_type']

            cnet = C(values, columns=columns)

+3 −2
Original line number Diff line number Diff line
from collections import deque
import warnings

import cv2
import pandas as pd

import numpy as np
from skimage.feature import match_template
from scipy.ndimage.interpolation import zoom

from autocnet.utils.observable import Observable

FLANN_INDEX_KDTREE = 1  # Algorithm to set centers,
DEFAULT_FLANN_PARAMETERS = dict(algorithm=FLANN_INDEX_KDTREE,
                                trees=3)
+3 −1
Original line number Diff line number Diff line
from collections import deque
import math
import warnings

import cv2
import numpy as np
@@ -196,7 +197,8 @@ class SpatialSuppression(Observable):
                  [0,1) The acceptable epsilon
        """
        if self.k > len(self.df):
           raise ValueError('Only {} valid points, but {} points requested'.format(len(self.df), self.k))
            warnings.warn('Only {} valid points, but {} points requested'.format(len(self.df), self.k))
            self.k = len(self.df)
        search_space = np.linspace(self.min_radius, self.max_radius / 16, 250)
        cell_sizes = (search_space / math.sqrt(2)).astype(np.int)
        min_idx = 0
+140 −8
Original line number Diff line number Diff line
@@ -49,28 +49,160 @@ def clip_roi(img, center, img_size):
    return clipped_img


def subpixel_offset(template, search, upsampling=16):
def subpixel_offset(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.

    Parameters
    ----------
    template : numpy array
                   The entire image that the template chip to match to will be taken out of.
    search : numpy array
                 The entire image that the search chip to match to the template chip will be taken out of.
    upsampling: int
                The amount to upsample the image. 
    template : ndarray
               The template used to search

    search : ndarray
             The search image

    Returns
    -------
    x_offset : float
               Shift in the x-dimension

    y_offset : float
               Shift in the y-dimension

    strength : float
               Strength of the correspondence in the range [-1, 1]
    """

    x_offset, y_offset, strength = matcher.pattern_match(template, search, upsampling=upsampling)
    x_offset, y_offset, strength = matcher.pattern_match(template, search, **kwargs)
    return x_offset, y_offset, strength

'''
Stub for an observable subpixel class

class PatternMatch(Observable):

    """
    Attributes
    ----------
    df : dataframe
         A dataframe of point to be subpixel registered

    img1 : object or ndarray
           A file handle object or ndarray to use to subpixel register

    img2 : object or ndarray
           A file handle object or ndarray to use to subpixel register

    destination : object
                  Destination node

    threshold_mask : series
                     A pandas series masking values < threshold

    shift_mask : series
                 A pandas series masking values with shifts larger
                 than the allowed x, y shifts

    subpixel_mask : series
                    A composite mask, threshold_mask & shift_mask
    """

    def __init__(self, img1, img2, df, min_x_shift=-1.0, max_x_shift=1.0,
                 min_y_shift=-1.0, max_y_shift=1.0, threshold=0.8):
        self.img1 = img1
        self.img2 = img2
        self.df = df

        self._min_x_shift = min_x_shift
        self._min_y_shift = min_y_shift
        self._max_x_shift = max_x_shift
        self._max_y_shift = max_y_shift
        self._threshold = threshold

        self.threshold_mask = pd.Series(True, index=self.df.index)
        self.shift_mask = pd.Series(True, index=self.df.index)
        self.subpixel_mask = self.threshold_mask & self.subpixel_mask

        self._action_stack = deque(maxlen=20)
        self._current_action_stack = 0
        self._observers = set()
        self.attrs = ['threshold', 'min_x_shift', 'max_x_shift',
                      'min_y_shift', 'm_y_shift', 'threshold_mask',
                      'shift_mask', 'subpixel_mask']

    def clip_roi(self, img, center):

    @property
    def threshold(self):
        return self._threshold

    @threshold.setter
    def threshold(self, v):
        if 0 <= v <= 1:
            self._threshold = v

            # Update the mask here
            self.threshold_mask = self.d

            current_state = self._action_stack[self._current_action_stack]
            current_state['threshold'] = self.threshold

            self._update_stack(current_state)

    @property
    def min_x_shift(self):
        return self._min_x_shift

    @min_x_shift.setter
    def min_x_shift(self, v):
        self._min_x_shift = v

        # Update mask here

        current_state = self._action_stack[self._current_action_stack]
        current_state['min_x_shift'] = self.min_x_shift
        self._update_stack()

    @property
    def min_y_shift(self):
        return self._min_y_shift

    @min_x_shift.setter
    def min_y_shift(self, v):
        self._min_y_shift = v

        # Update mask here

        current_state = self._action_stack[self._current_action_stack]
        current_state['min_y_shift'] = self.min_y_shift
        self._update_stack()

    @property
    def max_x_shift(self):
        return self._max_x_shift

    @max_x_shift.setter
    def max_x_shift(self, v):
        self._max_x_shift = v

        # Update mask here

        current_state = self._action_stack[self._current_action_stack]
        current_state['max_x_shift'] = self.max_x_shift
        self._update_stack()

    @property
    def max_y_shift(self):
        return self._max_y_shift

    @max_y_shift.setter
    def max_y_shift(self, v):
        self._max_y_shift = v

        # Update mask here

        current_state = self._action_stack[self._current_action_stack]
        current_state['max_y_shift'] = self.max_y_shift
        self._update_stack()
'''