Loading autocnet/graph/edge.py +13 −42 Changes for autocnet/graph/edge.py: 13 added lines, 42 removed lines. Original line number Diff line number Diff line Loading @@ -30,11 +30,6 @@ class Edge(dict, MutableMapping): masks : set A list of the available masking arrays provenance : dict With key equal to an autoincrementing integer and value equal to a dict of parameters used to generate this realization. weight : dict Dictionary with two keys overlap_area, and overlap_percn overlap_area returns the area overlaped by both images Loading @@ -44,19 +39,10 @@ class Edge(dict, MutableMapping): def __init__(self, source=None, destination=None): self.source = source self.destination = destination self.homography = None self.fundamental_matrix = None self.matches = None self._subpixel_offsets = None self.provenance = {} self.weight = {} self._observers = set() # Subscribe the heatlh observer self._health = health.EdgeHealth() self['weight'] = {} def __repr__(self): return """ Loading @@ -69,8 +55,7 @@ class Edge(dict, MutableMapping): attribute_dict = {'source': self.source, 'destination': self.destination, 'masks': self.masks, 'provenance': self.provenance, 'weight': self.weight} 'weight': self['weight']} if item in attribute_dict.keys(): return attribute_dict[item] else: Loading @@ -78,8 +63,7 @@ class Edge(dict, MutableMapping): @property def masks(self): mask_lookup = {'fundamental': 'fundamental_matrix', 'ratio': 'distance_ratio'} mask_lookup = {'fundamental': 'fundamental_matrix'} if not hasattr(self, '_masks'): if self.matches is not None: self._masks = pd.DataFrame(True, columns=['symmetry'], Loading @@ -101,10 +85,6 @@ class Edge(dict, MutableMapping): boolean_mask = v[1] self.masks[column_name] = boolean_mask @property def health(self): return self._health.health def decompose_and_match(self, k=2, maxiteration=3, size=18, buf_dist=3,**kwargs): """ Similar to match, this method first decomposed the image into Loading Loading @@ -179,9 +159,9 @@ class Edge(dict, MutableMapping): bd = b.descriptors # Load, train, and match fl.add(ad, a.node_id, index=aidx) fl.add(ad, a['node_id'], index=aidx) fl.train() matches = fl.query(bd, b.node_id, k, index=bidx) matches = fl.query(bd, b['node_id'], k, index=bidx) self._add_matches(matches) fl.clear() Loading Loading @@ -238,7 +218,7 @@ class Edge(dict, MutableMapping): bsub = ddata[mindy:maxdy, mindx:maxdx] # Utilize the FLANN matcher to find a match to approximate a center fl.add(self.destination.descriptors, self.destination.node_id) fl.add(self.destination.descriptors, self.destination['node_id']) fl.train() scounter = 0 Loading @@ -252,7 +232,7 @@ class Edge(dict, MutableMapping): size = len(sub_skp) candidate_idx = np.random.choice(sub_skp.index, size=size, replace=False) candidates = self.source.descriptors[candidate_idx] matches = fl.query(candidates, self.source.node_id, k=3, index=candidate_idx) matches = fl.query(candidates, self.source['node_id'], k=3, index=candidate_idx) # Apply Lowe's ratio test to try to find a 'good' starting point mask = matches.groupby('source_idx')['distance'].transform(func).astype('bool') Loading Loading @@ -385,9 +365,9 @@ class Edge(dict, MutableMapping): bd = b.descriptors # Load, train, and match fl.add(ad, a.node_id, index=aidx) fl.add(ad, a['node_id'], index=aidx) fl.train() matches = fl.query(bd, b.node_id, k, index=bidx) matches = fl.query(bd, b['node_id'], k, index=bidx) self._add_matches(matches) fl.clear() Loading Loading @@ -424,16 +404,9 @@ class Edge(dict, MutableMapping): def ratio_check(self, clean_keys=[], **kwargs): if hasattr(self, 'matches'): matches, mask = self.clean(clean_keys) self.distance_ratio = od.DistanceRatio(matches) self.distance_ratio.compute(mask=mask, **kwargs) # Setup to be notified self.distance_ratio._notify_subscribers(self.distance_ratio) self.masks = ('ratio', self.distance_ratio.mask) distance_mask = od.distance_ratio(matches, **kwargs) self.masks = ('ratio', distance_mask) else: raise AttributeError('No matches have been computed for this edge.') Loading Loading @@ -480,7 +453,6 @@ class Edge(dict, MutableMapping): mask[mask] = self.fundamental_matrix.mask # Subscribe the health watcher to the fundamental matrix observable self.fundamental_matrix.subscribe(self._health.update) self.fundamental_matrix._notify_subscribers(self.fundamental_matrix) # Set the initial state of the fundamental mask in the masks Loading Loading @@ -737,8 +709,8 @@ class Edge(dict, MutableMapping): overlapinfo = cg.two_poly_overlap(poly1, poly2) self.weight['overlap_area'] = overlapinfo[1] self.weight['overlap_percn'] = overlapinfo[0] self['weight']['overlap_area'] = overlapinfo[1] self['weight']['overlap_percn'] = overlapinfo[0] def coverage(self, clean_keys = []): """ Loading Loading @@ -795,4 +767,3 @@ class Edge(dict, MutableMapping): raise AttributeError('Matches have not been computed for this edge') voronoi = cg.vor(self, clean_keys, **kwargs) self.matches = pd.concat([self.matches, voronoi[1]['vor_weights']], axis=1) autocnet/graph/network.py +11 −12 Changes for autocnet/graph/network.py: 11 added lines, 12 removed lines. Original line number Diff line number Diff line Loading @@ -21,10 +21,9 @@ class CandidateGraph(nx.Graph): """ A NetworkX derived directed graph to store candidate overlap images. Parameters Attributes ---------- Attributes node_counter : int The number of nodes in the graph. node_name_map : dict Loading @@ -43,21 +42,21 @@ class CandidateGraph(nx.Graph): def __init__(self, *args, basepath=None, **kwargs): super(CandidateGraph, self).__init__(*args, **kwargs) self.node_counter = 0 self.graph['node_counter'] = 0 node_labels = {} self.node_name_map = {} self.graph['node_name_map'] = {} for node_name in self.nodes(): image_name = os.path.basename(node_name) image_path = node_name # Replace the default attr dict with a Node object self.node[node_name] = Node(image_name, image_path, self.node_counter) self.node[node_name] = Node(image_name, image_path, self.graph['node_counter']) # fill the dictionary used for relabelling nodes with relative path keys node_labels[node_name] = self.node_counter node_labels[node_name] = self.graph['node_counter'] # fill the dictionary used for mapping base name to node index self.node_name_map[self.node[node_name].image_name] = self.node_counter self.node_counter += 1 self.graph['node_name_map'][self.node[node_name]['image_name']] = self.graph['node_counter'] self.graph['node_counter'] += 1 nx.relabel_nodes(self, node_labels, copy=False) Loading @@ -68,8 +67,8 @@ class CandidateGraph(nx.Graph): e.source = self.node[s] e.destination = self.node[d] self.creationdate = strftime("%Y-%m-%d %H:%M:%S", gmtime()) self.modifieddate = strftime("%Y-%m-%d %H:%M:%S", gmtime()) self.graph['creationdate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) self.graph['modifieddate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) @classmethod def from_graph(cls, graph): Loading Loading @@ -175,7 +174,7 @@ class CandidateGraph(nx.Graph): """ Update the last modified date attribute. """ self.modifieddate = strftime("%Y-%m-%d %H:%M:%S", gmtime()) self.graph['modifieddate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) def get_name(self, node_index): """ Loading @@ -193,7 +192,7 @@ class CandidateGraph(nx.Graph): """ return self.node[node_index].image_name return self.node[node_index]['image_name'] def add_image(self, *args, **kwargs): """ Loading autocnet/graph/node.py +16 −14 Changes for autocnet/graph/node.py: 16 added lines, 14 removed lines. Original line number Diff line number Diff line Loading @@ -58,9 +58,10 @@ class Node(dict, MutableMapping): """ def __init__(self, image_name=None, image_path=None, node_id=None): self.image_name = image_name self.image_path = image_path self.node_id = node_id self['image_name'] = image_name self['image_path'] = image_path self['node_id'] = node_id self['hash'] = self['image_name'] #TODO: Repalce with farmhash self._mask_arrays = {} self.point_to_correspondence = defaultdict(set) self.point_to_correspondence_df = None Loading @@ -73,9 +74,10 @@ class Node(dict, MutableMapping): Number Keypoints: {} Available Masks : {} Type: {} """.format(self.node_id, self.image_name, self.image_path, """.format(self['node_id'], self['image_name'], self['image_path'], self.nkeypoints, self.masks, self.__class__) """ def __getitem__(self, item): attribute_dict = {'image_name': self.image_name, 'image_path': self.image_path, Loading @@ -89,11 +91,11 @@ class Node(dict, MutableMapping): return attribute_dict[item] else: return super(Node, self).__getitem__(item) """ @property def geodata(self): if not getattr(self, '_geodata', None) and self.image_path is not None: self._geodata = GeoDataset(self.image_path) if not getattr(self, '_geodata', None) and self['image_path'] is not None: self._geodata = GeoDataset(self['image_path']) return self._geodata if hasattr(self, '_geodata'): return self._geodata Loading Loading @@ -133,7 +135,7 @@ class Node(dict, MutableMapping): """ if not hasattr(self, '_isis_serial'): try: self._isis_serial = generate_serial_number(self.image_path) self._isis_serial = generate_serial_number(self['image_path']) except: self._isis_serial = None return self._isis_serial Loading Loading @@ -288,8 +290,8 @@ class Node(dict, MutableMapping): else: hdf = in_path self._descriptors = hdf['{}/descriptors'.format(self.image_name)][:] raw_kps = hdf['{}/keypoints'.format(self.image_name)][:] self._descriptors = hdf['{}/descriptors'.format(self['image_name'])][:] raw_kps = hdf['{}/keypoints'.format(self['image_name'])][:] index = raw_kps['index'] clean_kps = utils.remove_field_name(raw_kps, 'index') columns = clean_kps.dtype.names Loading Loading @@ -329,16 +331,16 @@ class Node(dict, MutableMapping): hdf = out_path try: hdf.create_dataset('{}/descriptors'.format(self.image_name), hdf.create_dataset('{}/descriptors'.format(self['image_name']), data=self._descriptors, compression=io_hdf.DEFAULT_COMPRESSION, compression_opts=io_hdf.DEFAULT_COMPRESSION_VALUE) hdf.create_dataset('{}/keypoints'.format(self.image_name), hdf.create_dataset('{}/keypoints'.format(self['image_name']), data=hdf.df_to_sarray(self._keypoints.reset_index()), compression=io_hdf.DEFAULT_COMPRESSION, compression_opts=io_hdf.DEFAULT_COMPRESSION_VALUE) except: warnings.warn('Descriptors for the node {} are already stored'.format(self.image_name)) warnings.warn('Descriptors for the node {} are already stored'.format(self['image_name'])) # If the out_path is a string, assume this method is being called as a singleton # and close the hdf file gracefully. If an object, let the instantiator of the Loading @@ -357,7 +359,7 @@ class Node(dict, MutableMapping): deepen : bool If True, attempt to punch matches through to all incident edges. Default: False """ node = self.node_id node = self['node_id'] # Get the edges incident to the current node incident_edges = set(cg.edges(node)).intersection(set(cg.edges())) Loading autocnet/matcher/feature_extractor.py +2 −0 Changes for autocnet/matcher/feature_extractor.py: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -77,4 +77,6 @@ def extract_features(array, method='orb', extractor_parameters={}): if descriptors.dtype != np.float32: descriptors = descriptors.astype(np.float32) descriptors = pd.DataFrame(descriptors) return keypoints, descriptors autocnet/matcher/outlier_detector.py +11 −71 Changes for autocnet/matcher/outlier_detector.py: 11 added lines, 71 removed lines. Original line number Diff line number Diff line Loading @@ -7,52 +7,7 @@ import pandas as pd from autocnet.utils.observable import Observable class DistanceRatio(Observable): """ A stateful object to store ratio test results and provenance. Attributes ---------- nvalid : int The number of valid entries in the mask mask : series Pandas boolean series indexed by the match id matches : dataframe The matches dataframe from an edge. This dataframe must have 'source_idx' and 'distance' columns. single : bool If True, then single entries in the distance ratio mask are assumed to have passed the ratio test. Else False. References ---------- [Lowe2004]_ """ def __init__(self, matches): self._action_stack = deque(maxlen=10) self._current_action_stack = 0 self._observers = set() self.matches = matches self.mask = None self.clean_keys = None self.single = None self.attrs = ['mask', 'ratio', 'clean_keys', 'single'] @property def nvalid(self): return self.mask.sum() def compute(self, ratio=0.8, mask=None, mask_name=None, single=False): def distance_ratio(matches, ratio=0.8, single=False): """ Compute and return a mask for a matches dataframe using Lowe's ratio test. If keypoints have a single Loading @@ -65,15 +20,15 @@ class DistanceRatio(Observable): for each keypoint to use as a bound for marking the first keypoint as "good". Default: 0.8 mask : series A pandas boolean series to initially mask the matches array mask_name : list or str An arbitrary mask name for provenance tracking single : bool If True, points with only a single entry are included (True) in the result mask, else False. Returns ------- mask : pd.dataframe A Pandas DataFrame mask for the matches with those failing the ratio test set to False. """ def func(group): res = [False] * len(group) Loading @@ -83,27 +38,12 @@ class DistanceRatio(Observable): res[0] = True return res if mask is not None: self.mask = mask.copy() mask_s = self.matches[mask].groupby('source_idx')['distance'].transform(func).astype('bool') mask_s = matches.groupby('source_idx')['distance'].transform(func).astype('bool') single = True mask_d = self.matches[mask].groupby('destination_idx')['distance'].transform(func).astype('bool') self.mask[mask] = mask_s & mask_d else: mask_s = self.matches.groupby('source_idx')['distance'].transform(func).astype('bool') single = True mask_d = self.matches.groupby('destination_idx')['distance'].transform(func).astype('bool') self.mask = mask_s & mask_d mask_d = matches.groupby('destination_idx')['distance'].transform(func).astype('bool') mask = mask_s & mask_d state_package = {'ratio': ratio, 'mask': self.mask.copy(), 'clean_keys': mask_name, 'single': single } self._action_stack.append(state_package) self._current_action_stack = len(self._action_stack) - 1 return mask class SpatialSuppression(Observable): Loading Loading
autocnet/graph/edge.py +13 −42 Changes for autocnet/graph/edge.py: 13 added lines, 42 removed lines. Original line number Diff line number Diff line Loading @@ -30,11 +30,6 @@ class Edge(dict, MutableMapping): masks : set A list of the available masking arrays provenance : dict With key equal to an autoincrementing integer and value equal to a dict of parameters used to generate this realization. weight : dict Dictionary with two keys overlap_area, and overlap_percn overlap_area returns the area overlaped by both images Loading @@ -44,19 +39,10 @@ class Edge(dict, MutableMapping): def __init__(self, source=None, destination=None): self.source = source self.destination = destination self.homography = None self.fundamental_matrix = None self.matches = None self._subpixel_offsets = None self.provenance = {} self.weight = {} self._observers = set() # Subscribe the heatlh observer self._health = health.EdgeHealth() self['weight'] = {} def __repr__(self): return """ Loading @@ -69,8 +55,7 @@ class Edge(dict, MutableMapping): attribute_dict = {'source': self.source, 'destination': self.destination, 'masks': self.masks, 'provenance': self.provenance, 'weight': self.weight} 'weight': self['weight']} if item in attribute_dict.keys(): return attribute_dict[item] else: Loading @@ -78,8 +63,7 @@ class Edge(dict, MutableMapping): @property def masks(self): mask_lookup = {'fundamental': 'fundamental_matrix', 'ratio': 'distance_ratio'} mask_lookup = {'fundamental': 'fundamental_matrix'} if not hasattr(self, '_masks'): if self.matches is not None: self._masks = pd.DataFrame(True, columns=['symmetry'], Loading @@ -101,10 +85,6 @@ class Edge(dict, MutableMapping): boolean_mask = v[1] self.masks[column_name] = boolean_mask @property def health(self): return self._health.health def decompose_and_match(self, k=2, maxiteration=3, size=18, buf_dist=3,**kwargs): """ Similar to match, this method first decomposed the image into Loading Loading @@ -179,9 +159,9 @@ class Edge(dict, MutableMapping): bd = b.descriptors # Load, train, and match fl.add(ad, a.node_id, index=aidx) fl.add(ad, a['node_id'], index=aidx) fl.train() matches = fl.query(bd, b.node_id, k, index=bidx) matches = fl.query(bd, b['node_id'], k, index=bidx) self._add_matches(matches) fl.clear() Loading Loading @@ -238,7 +218,7 @@ class Edge(dict, MutableMapping): bsub = ddata[mindy:maxdy, mindx:maxdx] # Utilize the FLANN matcher to find a match to approximate a center fl.add(self.destination.descriptors, self.destination.node_id) fl.add(self.destination.descriptors, self.destination['node_id']) fl.train() scounter = 0 Loading @@ -252,7 +232,7 @@ class Edge(dict, MutableMapping): size = len(sub_skp) candidate_idx = np.random.choice(sub_skp.index, size=size, replace=False) candidates = self.source.descriptors[candidate_idx] matches = fl.query(candidates, self.source.node_id, k=3, index=candidate_idx) matches = fl.query(candidates, self.source['node_id'], k=3, index=candidate_idx) # Apply Lowe's ratio test to try to find a 'good' starting point mask = matches.groupby('source_idx')['distance'].transform(func).astype('bool') Loading Loading @@ -385,9 +365,9 @@ class Edge(dict, MutableMapping): bd = b.descriptors # Load, train, and match fl.add(ad, a.node_id, index=aidx) fl.add(ad, a['node_id'], index=aidx) fl.train() matches = fl.query(bd, b.node_id, k, index=bidx) matches = fl.query(bd, b['node_id'], k, index=bidx) self._add_matches(matches) fl.clear() Loading Loading @@ -424,16 +404,9 @@ class Edge(dict, MutableMapping): def ratio_check(self, clean_keys=[], **kwargs): if hasattr(self, 'matches'): matches, mask = self.clean(clean_keys) self.distance_ratio = od.DistanceRatio(matches) self.distance_ratio.compute(mask=mask, **kwargs) # Setup to be notified self.distance_ratio._notify_subscribers(self.distance_ratio) self.masks = ('ratio', self.distance_ratio.mask) distance_mask = od.distance_ratio(matches, **kwargs) self.masks = ('ratio', distance_mask) else: raise AttributeError('No matches have been computed for this edge.') Loading Loading @@ -480,7 +453,6 @@ class Edge(dict, MutableMapping): mask[mask] = self.fundamental_matrix.mask # Subscribe the health watcher to the fundamental matrix observable self.fundamental_matrix.subscribe(self._health.update) self.fundamental_matrix._notify_subscribers(self.fundamental_matrix) # Set the initial state of the fundamental mask in the masks Loading Loading @@ -737,8 +709,8 @@ class Edge(dict, MutableMapping): overlapinfo = cg.two_poly_overlap(poly1, poly2) self.weight['overlap_area'] = overlapinfo[1] self.weight['overlap_percn'] = overlapinfo[0] self['weight']['overlap_area'] = overlapinfo[1] self['weight']['overlap_percn'] = overlapinfo[0] def coverage(self, clean_keys = []): """ Loading Loading @@ -795,4 +767,3 @@ class Edge(dict, MutableMapping): raise AttributeError('Matches have not been computed for this edge') voronoi = cg.vor(self, clean_keys, **kwargs) self.matches = pd.concat([self.matches, voronoi[1]['vor_weights']], axis=1)
autocnet/graph/network.py +11 −12 Changes for autocnet/graph/network.py: 11 added lines, 12 removed lines. Original line number Diff line number Diff line Loading @@ -21,10 +21,9 @@ class CandidateGraph(nx.Graph): """ A NetworkX derived directed graph to store candidate overlap images. Parameters Attributes ---------- Attributes node_counter : int The number of nodes in the graph. node_name_map : dict Loading @@ -43,21 +42,21 @@ class CandidateGraph(nx.Graph): def __init__(self, *args, basepath=None, **kwargs): super(CandidateGraph, self).__init__(*args, **kwargs) self.node_counter = 0 self.graph['node_counter'] = 0 node_labels = {} self.node_name_map = {} self.graph['node_name_map'] = {} for node_name in self.nodes(): image_name = os.path.basename(node_name) image_path = node_name # Replace the default attr dict with a Node object self.node[node_name] = Node(image_name, image_path, self.node_counter) self.node[node_name] = Node(image_name, image_path, self.graph['node_counter']) # fill the dictionary used for relabelling nodes with relative path keys node_labels[node_name] = self.node_counter node_labels[node_name] = self.graph['node_counter'] # fill the dictionary used for mapping base name to node index self.node_name_map[self.node[node_name].image_name] = self.node_counter self.node_counter += 1 self.graph['node_name_map'][self.node[node_name]['image_name']] = self.graph['node_counter'] self.graph['node_counter'] += 1 nx.relabel_nodes(self, node_labels, copy=False) Loading @@ -68,8 +67,8 @@ class CandidateGraph(nx.Graph): e.source = self.node[s] e.destination = self.node[d] self.creationdate = strftime("%Y-%m-%d %H:%M:%S", gmtime()) self.modifieddate = strftime("%Y-%m-%d %H:%M:%S", gmtime()) self.graph['creationdate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) self.graph['modifieddate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) @classmethod def from_graph(cls, graph): Loading Loading @@ -175,7 +174,7 @@ class CandidateGraph(nx.Graph): """ Update the last modified date attribute. """ self.modifieddate = strftime("%Y-%m-%d %H:%M:%S", gmtime()) self.graph['modifieddate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) def get_name(self, node_index): """ Loading @@ -193,7 +192,7 @@ class CandidateGraph(nx.Graph): """ return self.node[node_index].image_name return self.node[node_index]['image_name'] def add_image(self, *args, **kwargs): """ Loading
autocnet/graph/node.py +16 −14 Changes for autocnet/graph/node.py: 16 added lines, 14 removed lines. Original line number Diff line number Diff line Loading @@ -58,9 +58,10 @@ class Node(dict, MutableMapping): """ def __init__(self, image_name=None, image_path=None, node_id=None): self.image_name = image_name self.image_path = image_path self.node_id = node_id self['image_name'] = image_name self['image_path'] = image_path self['node_id'] = node_id self['hash'] = self['image_name'] #TODO: Repalce with farmhash self._mask_arrays = {} self.point_to_correspondence = defaultdict(set) self.point_to_correspondence_df = None Loading @@ -73,9 +74,10 @@ class Node(dict, MutableMapping): Number Keypoints: {} Available Masks : {} Type: {} """.format(self.node_id, self.image_name, self.image_path, """.format(self['node_id'], self['image_name'], self['image_path'], self.nkeypoints, self.masks, self.__class__) """ def __getitem__(self, item): attribute_dict = {'image_name': self.image_name, 'image_path': self.image_path, Loading @@ -89,11 +91,11 @@ class Node(dict, MutableMapping): return attribute_dict[item] else: return super(Node, self).__getitem__(item) """ @property def geodata(self): if not getattr(self, '_geodata', None) and self.image_path is not None: self._geodata = GeoDataset(self.image_path) if not getattr(self, '_geodata', None) and self['image_path'] is not None: self._geodata = GeoDataset(self['image_path']) return self._geodata if hasattr(self, '_geodata'): return self._geodata Loading Loading @@ -133,7 +135,7 @@ class Node(dict, MutableMapping): """ if not hasattr(self, '_isis_serial'): try: self._isis_serial = generate_serial_number(self.image_path) self._isis_serial = generate_serial_number(self['image_path']) except: self._isis_serial = None return self._isis_serial Loading Loading @@ -288,8 +290,8 @@ class Node(dict, MutableMapping): else: hdf = in_path self._descriptors = hdf['{}/descriptors'.format(self.image_name)][:] raw_kps = hdf['{}/keypoints'.format(self.image_name)][:] self._descriptors = hdf['{}/descriptors'.format(self['image_name'])][:] raw_kps = hdf['{}/keypoints'.format(self['image_name'])][:] index = raw_kps['index'] clean_kps = utils.remove_field_name(raw_kps, 'index') columns = clean_kps.dtype.names Loading Loading @@ -329,16 +331,16 @@ class Node(dict, MutableMapping): hdf = out_path try: hdf.create_dataset('{}/descriptors'.format(self.image_name), hdf.create_dataset('{}/descriptors'.format(self['image_name']), data=self._descriptors, compression=io_hdf.DEFAULT_COMPRESSION, compression_opts=io_hdf.DEFAULT_COMPRESSION_VALUE) hdf.create_dataset('{}/keypoints'.format(self.image_name), hdf.create_dataset('{}/keypoints'.format(self['image_name']), data=hdf.df_to_sarray(self._keypoints.reset_index()), compression=io_hdf.DEFAULT_COMPRESSION, compression_opts=io_hdf.DEFAULT_COMPRESSION_VALUE) except: warnings.warn('Descriptors for the node {} are already stored'.format(self.image_name)) warnings.warn('Descriptors for the node {} are already stored'.format(self['image_name'])) # If the out_path is a string, assume this method is being called as a singleton # and close the hdf file gracefully. If an object, let the instantiator of the Loading @@ -357,7 +359,7 @@ class Node(dict, MutableMapping): deepen : bool If True, attempt to punch matches through to all incident edges. Default: False """ node = self.node_id node = self['node_id'] # Get the edges incident to the current node incident_edges = set(cg.edges(node)).intersection(set(cg.edges())) Loading
autocnet/matcher/feature_extractor.py +2 −0 Changes for autocnet/matcher/feature_extractor.py: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -77,4 +77,6 @@ def extract_features(array, method='orb', extractor_parameters={}): if descriptors.dtype != np.float32: descriptors = descriptors.astype(np.float32) descriptors = pd.DataFrame(descriptors) return keypoints, descriptors
autocnet/matcher/outlier_detector.py +11 −71 Changes for autocnet/matcher/outlier_detector.py: 11 added lines, 71 removed lines. Original line number Diff line number Diff line Loading @@ -7,52 +7,7 @@ import pandas as pd from autocnet.utils.observable import Observable class DistanceRatio(Observable): """ A stateful object to store ratio test results and provenance. Attributes ---------- nvalid : int The number of valid entries in the mask mask : series Pandas boolean series indexed by the match id matches : dataframe The matches dataframe from an edge. This dataframe must have 'source_idx' and 'distance' columns. single : bool If True, then single entries in the distance ratio mask are assumed to have passed the ratio test. Else False. References ---------- [Lowe2004]_ """ def __init__(self, matches): self._action_stack = deque(maxlen=10) self._current_action_stack = 0 self._observers = set() self.matches = matches self.mask = None self.clean_keys = None self.single = None self.attrs = ['mask', 'ratio', 'clean_keys', 'single'] @property def nvalid(self): return self.mask.sum() def compute(self, ratio=0.8, mask=None, mask_name=None, single=False): def distance_ratio(matches, ratio=0.8, single=False): """ Compute and return a mask for a matches dataframe using Lowe's ratio test. If keypoints have a single Loading @@ -65,15 +20,15 @@ class DistanceRatio(Observable): for each keypoint to use as a bound for marking the first keypoint as "good". Default: 0.8 mask : series A pandas boolean series to initially mask the matches array mask_name : list or str An arbitrary mask name for provenance tracking single : bool If True, points with only a single entry are included (True) in the result mask, else False. Returns ------- mask : pd.dataframe A Pandas DataFrame mask for the matches with those failing the ratio test set to False. """ def func(group): res = [False] * len(group) Loading @@ -83,27 +38,12 @@ class DistanceRatio(Observable): res[0] = True return res if mask is not None: self.mask = mask.copy() mask_s = self.matches[mask].groupby('source_idx')['distance'].transform(func).astype('bool') mask_s = matches.groupby('source_idx')['distance'].transform(func).astype('bool') single = True mask_d = self.matches[mask].groupby('destination_idx')['distance'].transform(func).astype('bool') self.mask[mask] = mask_s & mask_d else: mask_s = self.matches.groupby('source_idx')['distance'].transform(func).astype('bool') single = True mask_d = self.matches.groupby('destination_idx')['distance'].transform(func).astype('bool') self.mask = mask_s & mask_d mask_d = matches.groupby('destination_idx')['distance'].transform(func).astype('bool') mask = mask_s & mask_d state_package = {'ratio': ratio, 'mask': self.mask.copy(), 'clean_keys': mask_name, 'single': single } self._action_stack.append(state_package) self._current_action_stack = len(self._action_stack) - 1 return mask class SpatialSuppression(Observable): Loading