Loading autocnet/graph/edge.py +87 −45 Original line number Diff line number Diff line Loading @@ -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) Loading Loading @@ -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): Loading Loading @@ -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. Loading Loading @@ -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): """ Loading autocnet/matcher/subpixel.py +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 Loading @@ -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. Loading @@ -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): """ Loading Loading @@ -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. Loading Loading
autocnet/graph/edge.py +87 −45 Original line number Diff line number Diff line Loading @@ -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) Loading Loading @@ -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): Loading Loading @@ -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. Loading Loading @@ -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): """ Loading
autocnet/matcher/subpixel.py +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 Loading @@ -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. Loading @@ -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): """ Loading Loading @@ -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. Loading