def setUpClass(cls): img = imread(get_path('AS15-M-0298_SML.png'), flatten=True) img_coord = (482.09783936, 652.40679932) cls.template = sp.clip_roi(img, img_coord, 5) cls.template = rotate(cls.template, 90) cls.template = imresize(cls.template, 1.) cls.search = sp.clip_roi(img, img_coord, 21) cls.search = rotate(cls.search, 0) cls.search = imresize(cls.search, 1.) cls.offset = (1, 1) cls.offset_template = sp.clip_roi(img, np.add(img_coord, cls.offset), 5) cls.offset_template = rotate(cls.offset_template, 0) cls.offset_template = imresize(cls.offset_template, 1.) cls.search_center = [math.floor(cls.search.shape[0]/2), math.floor(cls.search.shape[1]/2)] cls.upsampling = 10 cls.alpha = math.pi/2 cls.cifi_thresh = 90 cls.rafi_thresh = 90 cls.tefi_thresh = 100 cls.use_percentile = True cls.radii = list(range(1, 3)) cls.cifi_number_of_warnings = 2 cls.rafi_number_of_warnings = 2
def setUpClass(cls): img = imread(get_path('AS15-M-0298_SML.png'), flatten=True) img_coord = (482.09783936, 652.40679932) cls.template = sp.clip_roi(img, img_coord, 5) cls.template = rotate(cls.template, 90) cls.template = imresize(cls.template, 1.) cls.search = sp.clip_roi(img, img_coord, 21) cls.search = rotate(cls.search, 0) cls.search = imresize(cls.search, 1.) cls.offset = (1, 1) cls.offset_template = sp.clip_roi(img, np.add(img_coord, cls.offset), 5) cls.offset_template = rotate(cls.offset_template, 0) cls.offset_template = imresize(cls.offset_template, 1.) cls.search_center = [ math.floor(cls.search.shape[0] / 2), math.floor(cls.search.shape[1] / 2) ] cls.upsampling = 10 cls.alpha = math.pi / 2 cls.cifi_thresh = 90 cls.rafi_thresh = 90 cls.tefi_thresh = 100 cls.use_percentile = True cls.radii = list(range(1, 3)) cls.cifi_number_of_warnings = 2 cls.rafi_number_of_warnings = 2
def offset_template(img, img_coord): coord_x, coord_y = img_coord coord_x += 1 coord_y += 1 offset_template, _, _ = sp.clip_roi(img, coord_x, coord_y, 5, 5) offset_template = rotate(offset_template, 0) offset_template = imresize(offset_template, 1.) return offset_template
def search(img, img_coord): coord_x, coord_y = img_coord search, _, _ = sp.clip_roi(img, coord_x, coord_y, 21, 21) search = rotate(search, 0) search = imresize(search, 1.) return search
def template(img, img_coord): coord_x, coord_y = img_coord template, _, _ = sp.clip_roi(img, coord_x, coord_y, 5, 5) template = rotate(template, 90) template = imresize(template, 1.) return template
def subpixel_register(self, clean_keys=[], threshold=0.8, upsampling=16, template_size=19, search_size=53, max_x_shift=1.0, max_y_shift=1.0, tiled=False): """ For the entire graph, compute the subpixel offsets using pattern-matching and add the result as an attribute to each edge of the graph. Parameters ---------- clean_keys : list of string keys to masking arrays (created by calling outlier detection) threshold : float On the range [-1, 1]. Values less than or equal to this threshold are masked and can be considered outliers upsampling : int The multiplier to the template and search shapes to upsample for subpixel accuracy template_size : int The size of the template in pixels, must be odd search_size : int The size of the search max_x_shift : float The maximum (positive) value that a pixel can shift in the x direction without being considered an outlier max_y_shift : float The maximum (positive) value that a pixel can shift in the y direction without being considered an outlier """ matches = self.matches self.subpixel_offsets = pd.DataFrame(0, index=matches.index, columns=['x_offset', 'y_offset', 'correlation', 's_idx', 'd_idx']) # Build up a composite mask from all of the user specified masks if clean_keys: matches, mask = self._clean(clean_keys) if tiled is True: s_img = self.source.handle d_img = self.destination.handle else: s_img = self.source.handle.read_array() d_img = self.destination.handle.read_array() # 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.keypoints.iloc[s_idx][['x', 'y']].values d_keypoint = self.destination.keypoints.iloc[d_idx][['x', 'y']].values # 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) try: x_off, y_off, strength = sp.subpixel_offset(s_template, d_search, upsampling=upsampling) self.subpixel_offsets.loc[idx] = [x_off, y_off, strength,s_idx, d_idx] except: warnings.warn('Template-Search size mismatch, failing for this correspondence point.') continue self.subpixel_offsets.to_sparse(fill_value=0.0) # Compute the mask for correlations less than the threshold threshold_mask = self.subpixel_offsets['correlation'] >= threshold # Compute the mask for the point shifts that are too large subp= self.subpixel_offsets 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 = subp.query(query_string) shift_mask = pd.Series(True, index=self.subpixel_offsets.index) shift_mask[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)
def test_clip_roi(center_x, center_y, size, expected): img = np.arange(10000).reshape(100, 100) clip, axr, ayr = sp.clip_roi(img, center_x, center_y, size) assert clip.mean() == expected
def offset_template(img, img_coord): coord_x, coord_y = img_coord coord_x += 1 coord_y += 1 offset_template, _, _ = sp.clip_roi(img, coord_x, coord_y, 5, 5) return offset_template
def compute_subpixel_offset(self, clean_keys=[], threshold=0.8, upsampling=16, template_size=19, search_size=53): """ For the entire graph, compute the subpixel offsets using pattern-matching and add the result as an attribute to each edge of the graph. Parameters ---------- clean_keys : list of string keys to masking arrays (created by calling outlier detection) threshold : float On the range [-1, 1]. Values less than or equal to this threshold are masked and can be considered outliers upsampling : int The multiplier to the template and search shapes to upsample for subpixel accuracy template_size : int The size of the template in pixels, must be odd search_size : int The size of the search """ matches = self.matches full_offsets = np.zeros((len(matches), 3)) # Build up a composite mask from all of the user specified masks if clean_keys: mask = np.prod([self._mask_arrays[i] for i in clean_keys], axis=0, dtype=np.bool) matches = matches[mask] full_mask = np.where(mask == True) # Preallocate the numpy array to avoid appending and type conversion edge_offsets = np.empty((len(matches), 3)) # 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.keypoints.iloc[s_idx][['x', 'y']].values d_keypoint = self.destination.keypoints.iloc[d_idx][['x', 'y']].values # Get the template and search windows s_template = sp.clip_roi(self.source.handle, s_keypoint, template_size) d_search = sp.clip_roi(self.destination.handle, d_keypoint, search_size) try: edge_offsets[i] = sp.subpixel_offset(s_template, d_search, upsampling=upsampling) except: warnings.warn( 'Template-Search size mismatch, failing for this correspondence point.' ) continue # Compute the mask for correlations less than the threshold threshold_mask = edge_offsets[edge_offsets[:, -1] >= threshold] # Convert the truncated mask back into a full length mask if clean_keys: mask[full_mask] = threshold_mask full_offsets[full_mask] = edge_offsets else: mask = threshold_mask self.subpixel_offsets = pd.DataFrame( full_offsets, columns=['x_offset', 'y_offset', 'correlation']) self.masks = ('subpixel', mask)
def template(img, img_coord): coord_x, coord_y = img_coord template, _, _ = sp.clip_roi(img, coord_x, coord_y, 5, 5) template = rotate(template, 90) return template
def search(): coord_x, coord_y = (482.09783936, 652.40679932) img = imread(get_path('AS15-M-0298_SML.png'), as_gray=True) search, _, _ = sp.clip_roi(img, coord_x, coord_y, 21, 21) return search
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): """ For the entire graph, compute the subpixel offsets using pattern-matching and add the result as an attribute to each edge of the graph. Parameters ---------- clean_keys : list of string keys to masking arrays (created by calling outlier detection) threshold : float On the range [-1, 1]. Values less than or equal to this threshold are masked and can be considered outliers upsampling : int The multiplier to the template and search shapes to upsample for subpixel accuracy template_size : int The size of the template in pixels, must be odd search_size : int The size of the search max_x_shift : float The maximum (positive) value that a pixel can shift in the x direction without being considered an outlier max_y_shift : float The maximum (positive) value that a pixel can shift in the y direction without being considered an outlier """ matches = self.matches for column, default in { 'x_offset': 0, 'y_offset': 0, 'correlation': 0, 'reference': -1 }.items(): if column not in self.matches.columns: self.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: 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']) # 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) # 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) try: 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 # Compute the mask for correlations less than the threshold threshold_mask = self.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.matches.query(query_string) shift_mask = pd.Series(True, index=self.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)
def refine_subpixel(sx, sy, dx, dy, s_img, d_img, size=100, reduction=25, convergence_threshold=.5): """ Iteratively apply a subpixel phase matcher to source (s_img) amd destination (d_img) images. The size parameter is used to set the initial search space. The algorithm is recursively applied to reduce the total search space by reduction until the convergence criteria are met. Convergence is defined as the point at which the computed shifts (x_shift,y_shift) are less than the convergence_threshold. In instances where the size is reducted to 1 pixel the algorithm terminates and returns None. Parameters ---------- sx : numeric The x position of the center of the template to be matched to sy : numeric The y position of the center of the template to be matched to dx : numeric The x position of the center of the search to be matched from dy : numeric The y position of the center of the search to be matched to s_img : object A plio geodata object from which the template is extracted d_img : object A plio geodata object from which the search is extracted size : int One half of the total size of the template, so a 251 default results in a 502 pixel search space reduction : int With each recursive call to this func, the size is reduced by this amount convergence_threshold : float The value under which the result can shift in the x and y directions to force a break Returns ------- dx : float The new x value for the match in the destination (d) image dy : float The new y value for the match in the destination (d) image metrics : tuple A tuple of metrics. In the case of the phase matcher this are difference and RMSE in the phase dimension. """ s_template, _, _ = clip_roi(s_img, sx, sy, size_x=size, size_y=size) d_search, dxr, dyr = clip_roi(d_img, dx, dy, size_x=size, size_y=size) if 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, _, _ = clip_roi(s_img, sx, sy, size_x=updated_size, size_y=updated_size) d_search, dxr, dyr = clip_roi(d_img, dx, dy, size_x=updated_size, size_y=updated_size) # Apply the phase matcher shift_x, shift_y, metrics = subpixel_phase(s_template, d_search, upsample_factor=100) # Apply the shift to d_search and compute the new correspondence location dx += (shift_x + dxr) dy += (shift_y + dyr) # Break if the solution has converged if abs(shift_x) < convergence_threshold and abs( shift_y) < convergence_threshold: return dx, dy, metrics else: size -= reduction if size < 1: return return refine_subpixel(sx, sy, dx, dy, s_img, d_img, size)
def compute_subpixel_offsets(self, clean_keys=[], threshold=0.8, upsampling=10, template_size=9, search_size=27): """ For the entire graph, compute the subpixel offsets using pattern-matching and add the result as an attribute to each edge of the graph. Parameters ---------- clean_keys : list of string keys to masking arrays (created by calling outlier detection) threshold : float On the range [-1, 1]. Values less than or equal to this threshold are masked and can be considered outliers upsampling : int The multiplier to the template and search shapes to upsample for subpixel accuracy template_size : int The size of the template in pixels, must be odd search_size : int The size of the search """ for source, destination, attributes in self.edges_iter(data=True): matches = attributes['matches'] full_offsets = np.zeros((len(matches), 3)) # Build up a composite mask from all of the user specified masks if clean_keys: mask = np.prod([attributes[i] for i in clean_keys], axis=0, dtype=np.bool) matches = matches[mask] full_mask = np.where(mask == True) src_image = self.node[source]['image'] dest_image = self.node[destination]['image'] # Preallocate the numpy array to avoid appending and type conversion edge_offsets = np.empty((len(matches),3)) # 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_node = self.node[source] d_node = self.node[destination] s_keypoint = s_node['keypoints'][s_idx].pt d_keypoint = d_node['keypoints'][d_idx].pt # Get the template and search windows s_template = sp.clip_roi(src_image, s_keypoint, template_size) d_search = sp.clip_roi(dest_image, d_keypoint, search_size) edge_offsets[i] = sp.subpixel_offset(s_template, d_search, upsampling=upsampling) # Compute the mask for correlations less than the threshold threshold_mask = edge_offsets[edge_offsets[:,-1] >= threshold] # Convert the truncated mask back into a full length mask if clean_keys: mask[full_mask] = threshold_mask full_offsets[full_mask] = edge_offsets else: mask = threshold_mask attributes['subpixel_offsets'] = pd.DataFrame(full_offsets, columns=['x_offset', 'y_offset', 'correlation']) attributes['subpixel'] = mask
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. Parameters ---------- clean_keys : list of string keys to masking arrays (created by calling outlier detection) threshold : float On the range [-1, 1]. Values less than or equal to this threshold are masked and can be considered outliers upsampling : int The multiplier to the template and search shapes to upsample for subpixel accuracy template_size : int The size of the template in pixels, must be odd search_size : int The size of the search max_x_shift : float The maximum (positive) value that a pixel can shift in the x direction without being considered an outlier max_y_shift : float The maximum (positive) value that a pixel can shift in the y direction without being considered an outlier """ # Build up a composite mask from all of the user specified masks matches, mask = self.clean(clean_keys) # Get the img handles s_img = self.source.geodata d_img = self.destination.geodata # Determine which algorithm is going ot be used. if method == 'phase': func = sp.subpixel_phase shifts_x, shifts_y, strengths, new_x, new_y = sp._prep_subpixel( len(matches), 2) elif method == 'template': func = sp.subpixel_template shifts_x, shifts_y, strengths, new_x, new_y = sp._prep_subpixel( len(matches), 1) # 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']) if 'source_x' in row.index: sx = row.source_x sy = row.source_y else: s_keypoint = self.source.get_keypoint_coordinates([s_idx]) sx = s_keypoint.x sy = s_keypoint.y if 'destination_x' in row.index: dx = row.destination_x dy = row.destination_y else: d_keypoint = self.destination.get_keypoint_coordinates([d_idx]) dx = d_keypoint.x dy = d_keypoint.y s_template, _, _ = sp.clip_roi(s_img, sx, sy, size_x=template_size, size_y=template_size) d_search, dxr, dyr = sp.clip_roi(d_img, dx, dy, 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, _, _ = sp.clip_roi(s_img, sx, sy, size_x=updated_size, size_y=updated_size) d_search, dxr, dyr = sp.clip_roi(d_img, dx, dy, 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. shifts_x[i] = shift_x + dxr shifts_y[i] = shift_y + dyr new_x[i] = dx - shift_x new_y[i] = dy - shift_y strengths[i] = metrics self.matches.loc[mask, 'shift_x'] = shifts_x self.matches.loc[mask, 'shift_y'] = shifts_y self.matches.loc[mask, 'destination_x'] = new_x self.matches.loc[mask, 'destination_y'] = new_y if method == 'phase': self.costs.loc[mask, 'phase_diff'] = strengths[:, 0] self.costs.loc[mask, 'rmse'] = strengths[:, 1] elif method == 'template': self.costs.loc[mask, 'correlation'] = strengths[:, 0]
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): """ For the entire graph, compute the subpixel offsets using pattern-matching and add the result as an attribute to each edge of the graph. Parameters ---------- clean_keys : list of string keys to masking arrays (created by calling outlier detection) threshold : float On the range [-1, 1]. Values less than or equal to this threshold are masked and can be considered outliers upsampling : int The multiplier to the template and search shapes to upsample for subpixel accuracy template_size : int The size of the template in pixels, must be odd search_size : int The size of the search max_x_shift : float The maximum (positive) value that a pixel can shift in the x direction without being considered an outlier max_y_shift : float The maximum (positive) value that a pixel can shift in the y direction without being considered an outlier """ matches = self.matches for column, default in {'x_offset': 0, 'y_offset': 0, 'correlation': 0, 'reference': -1}.items(): if column not in self.matches.columns: self.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: 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']) # 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) # 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) try: 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 # Compute the mask for correlations less than the threshold threshold_mask = self.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.matches.query(query_string) shift_mask = pd.Series(True, index=self.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)
def compute_subpixel_offset(self, clean_keys=[], threshold=0.8, upsampling=16, template_size=19, search_size=53): """ For the entire graph, compute the subpixel offsets using pattern-matching and add the result as an attribute to each edge of the graph. Parameters ---------- clean_keys : list of string keys to masking arrays (created by calling outlier detection) threshold : float On the range [-1, 1]. Values less than or equal to this threshold are masked and can be considered outliers upsampling : int The multiplier to the template and search shapes to upsample for subpixel accuracy template_size : int The size of the template in pixels, must be odd search_size : int The size of the search """ matches = self.matches full_offsets = np.zeros((len(matches), 3)) # Build up a composite mask from all of the user specified masks if clean_keys: mask = np.prod([self._mask_arrays[i] for i in clean_keys], axis=0, dtype=np.bool) matches = matches[mask] full_mask = np.where(mask == True) # Preallocate the numpy array to avoid appending and type conversion edge_offsets = np.empty((len(matches),3)) # 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.keypoints.iloc[s_idx][['x', 'y']].values d_keypoint = self.destination.keypoints.iloc[d_idx][['x', 'y']].values # Get the template and search windows s_template = sp.clip_roi(self.source.handle, s_keypoint, template_size) d_search = sp.clip_roi(self.destination.handle, d_keypoint, search_size) try: edge_offsets[i] = sp.subpixel_offset(s_template, d_search, upsampling=upsampling) except: warnings.warn('Template-Search size mismatch, failing for this correspondence point.') continue # Compute the mask for correlations less than the threshold threshold_mask = edge_offsets[edge_offsets[:, -1] >= threshold] # Convert the truncated mask back into a full length mask if clean_keys: mask[full_mask] = threshold_mask full_offsets[full_mask] = edge_offsets else: mask = threshold_mask self.subpixel_offsets = pd.DataFrame(full_offsets, columns=['x_offset', 'y_offset', 'correlation']) self.masks = ('subpixel', mask)