Loading autocnet/graph/edge.py +14 −9 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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. Loading Loading @@ -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: Loading @@ -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']) Loading @@ -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 Loading @@ -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 Loading autocnet/graph/network.py +18 −15 Original line number Diff line number Diff line Loading @@ -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): Loading Loading @@ -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) Loading Loading @@ -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): Loading Loading @@ -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 Loading @@ -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'] Loading @@ -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) Loading autocnet/matcher/matcher.py +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) Loading autocnet/matcher/outlier_detector.py +3 −1 Original line number Diff line number Diff line from collections import deque import math import warnings import cv2 import numpy as np Loading Loading @@ -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 Loading autocnet/matcher/subpixel.py +140 −8 Original line number Diff line number Diff line Loading @@ -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() ''' Loading
autocnet/graph/edge.py +14 −9 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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. Loading Loading @@ -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: Loading @@ -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']) Loading @@ -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 Loading @@ -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 Loading
autocnet/graph/network.py +18 −15 Original line number Diff line number Diff line Loading @@ -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): Loading Loading @@ -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) Loading Loading @@ -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): Loading Loading @@ -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 Loading @@ -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'] Loading @@ -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) Loading
autocnet/matcher/matcher.py +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) Loading
autocnet/matcher/outlier_detector.py +3 −1 Original line number Diff line number Diff line from collections import deque import math import warnings import cv2 import numpy as np Loading Loading @@ -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 Loading
autocnet/matcher/subpixel.py +140 −8 Original line number Diff line number Diff line Loading @@ -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() '''