Loading autocnet/examples/Apollo15/adjacency.json +24 −24 Original line number Diff line number Diff line {"autocnet/examples/Apollo15/AS15-M-0297_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0300_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png", "autocnet/examples/Apollo15/AS15-M-0296_SML.png"], "autocnet/examples/Apollo15/AS15-M-0300_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png"], "autocnet/examples/Apollo15/AS15-M-0299_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0300_SML.png", "autocnet/examples/Apollo15/AS15-M-0296_SML.png"], "autocnet/examples/Apollo15/AS15-M-0295_SML.png" {"AS15-M-0297_SML.png" : ["AS15-M-0298_SML.png", "AS15-M-0300_SML.png", "AS15-M-0299_SML.png", "AS15-M-0296_SML.png"], "AS15-M-0300_SML.png" : ["AS15-M-0298_SML.png", "AS15-M-0297_SML.png", "AS15-M-0299_SML.png"], "AS15-M-0299_SML.png" : ["AS15-M-0298_SML.png", "AS15-M-0297_SML.png", "AS15-M-0300_SML.png", "AS15-M-0296_SML.png"], "AS15-M-0295_SML.png" : [], "autocnet/examples/Apollo15/AS15-M-0296_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png"], "autocnet/examples/Apollo15/AS15-M-0298_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0300_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png", "autocnet/examples/Apollo15/AS15-M-0296_SML.png"]} "AS15-M-0296_SML.png" : ["AS15-M-0298_SML.png", "AS15-M-0297_SML.png", "AS15-M-0299_SML.png"], "AS15-M-0298_SML.png" : ["AS15-M-0297_SML.png", "AS15-M-0300_SML.png", "AS15-M-0299_SML.png", "AS15-M-0296_SML.png"]} autocnet/graph/edge.py +43 −33 Original line number Diff line number Diff line from collections import MutableMapping import warnings from collections import MutableMapping import numpy as np import pandas as pd from pysal.cg.shapes import Polygon from autocnet.matcher import subpixel as sp from autocnet.matcher.homography import Homography from autocnet.cg.cg import convex_hull_ratio from autocnet.cg.cg import overlapping_polygon_area from autocnet.vis.graph_view import plot_edge from autocnet.matcher import health from autocnet.matcher import outlier_detector as od from autocnet.cg.cg import convex_hull_ratio from autocnet.matcher import subpixel as sp from autocnet.transformation.transformations import FundamentalMatrix, Homography from autocnet.vis.graph_view import plot_edge class Edge(dict, MutableMapping): Loading @@ -35,10 +36,14 @@ class Edge(dict, MutableMapping): self.source = source self.destination = destination self._homography = None self.homography = None self.fundamental_matrix = None self._subpixel_offsets = None self.provenance = {} self._pid = 0 self._observers = set() #Subscribe the heatlh observer self._health = health.EdgeHealth() def __repr__(self): return """ Loading @@ -49,34 +54,29 @@ class Edge(dict, MutableMapping): @property def masks(self): mask_lookup = {'fundamental': 'fundamental_matrix'} if not hasattr(self, '_masks'): self._masks = pd.Panel({self._pid: pd.DataFrame(index=self.matches.index)}) if hasattr(self, 'matches'): self._masks = pd.DataFrame(True, columns=['symmetry', 'ratio'], index=self.matches.index) else: self._masks = pd.DataFrame() # If the mask is coming form another object that tracks # state, dynamically draw the mask from the object. for c in self._masks.columns: if c in mask_lookup: self._masks[c] = getattr(self, mask_lookup[c]).mask return self._masks @masks.setter def masks(self, v): column_name = v[0] boolean_mask = v[1] current = self.masks[self._pid] current[column_name] = boolean_mask @property def error(self): if not hasattr(self, '_error'): self._error = pd.Panel({self._pid: pd.DataFrame(index=self.matches.index)}) return self._error @error.setter def error(self, v): pass self.masks[column_name] = boolean_mask @property def homography(self): return self._homography @homography.setter def homography(self, v): self._homography = v def health(self): return self._health.health def keypoints(self, clean_keys=[]): """ Loading Loading @@ -124,6 +124,9 @@ class Edge(dict, MutableMapping): else: raise AttributeError('Matches have not been computed for this edge') all_source_keypoints = self.source.keypoints.iloc[matches['source_idx']] all_destin_keypoints = self.destination.keypoints.iloc[matches['destination_idx']] if clean_keys: matches, mask = self._clean(clean_keys) Loading @@ -140,8 +143,17 @@ class Edge(dict, MutableMapping): mask[mask == True] = fundam_mask else: mask = fundam_mask self.fundamental_matrix = FundamentalMatrix(transformation_matrix, all_source_keypoints[['x', 'y']], all_destin_keypoints[['x', 'y']], mask=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 self.masks = ('fundamental', mask) self.fundamental_matrix = transformation_matrix def compute_homography(self, method='ransac', clean_keys=[], pid=None, **kwargs): """ Loading Loading @@ -188,7 +200,7 @@ class Edge(dict, MutableMapping): self.homography = Homography(transformation_matrix, s_keypoints[ransac_mask][['x', 'y']], d_keypoints[ransac_mask][['x', 'y']], index=mask[mask == True].index) mask=mask[mask == True].index) # Finalize the array to get custom attrs to propagate self.homography.__array_finalize__(self.homography) Loading Loading @@ -362,9 +374,7 @@ class Edge(dict, MutableMapping): mask : series A boolean series to inflate back to the full match set """ if not pid: pid = self._pid panel = self.masks[pid] panel = self.masks mask = panel[clean_keys].all(axis=1) matches = self.matches[mask] return matches, mask autocnet/graph/network.py +8 −6 Original line number Diff line number Diff line Loading @@ -175,7 +175,6 @@ class CandidateGraph(nx.Graph): raise NotImplementedError self.add_node(self.node_counter, *args, **kwargs) #self.node_labels[self.node[self.node_counter]['image_name']] = self.node_counter self.node_counter += 1 def extract_features(self, method='orb', extractor_parameters={}): Loading Loading @@ -225,7 +224,7 @@ class CandidateGraph(nx.Graph): def add_matches(self, matches): """ Adds match data to a node and attributes the data to the appropriate edges, e.g. if A-B have a match, edge A-B is attributes appropriate edges, e.g. if A-B have a match, edge A-B is attributed with the pandas dataframe. Parameters Loading @@ -233,13 +232,16 @@ class CandidateGraph(nx.Graph): matches : dataframe The pandas dataframe containing the matches """ edges = self.edges() 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] if (source_key, destination_key) in edges: edge = self.edge[source_key][destination_key] else: edge = self.edge[destination_key][source_key] if hasattr(edge, 'matches'): df = edge.matches Loading autocnet/graph/tests/test_network.py +12 −6 Original line number Diff line number Diff line Loading @@ -15,7 +15,10 @@ class TestCandidateGraph(unittest.TestCase): @classmethod def setUpClass(cls): cls.graph = network.CandidateGraph.from_adjacency(get_path('adjacency.json')) basepath = get_path('Apollo15') cls.graph = network.CandidateGraph.from_adjacency(get_path('three_image_adjacency.json'), basepath=basepath) cls.disconnected_graph = network.CandidateGraph.from_adjacency(get_path('adjacency.json')) def test_get_name(self): node_number = self.graph.node_name_map['AS15-M-0297_SML.png'] Loading @@ -35,18 +38,21 @@ class TestCandidateGraph(unittest.TestCase): pass def test_island_nodes(self): self.assertEqual(len(self.graph.island_nodes()), 1) self.assertEqual(len(self.disconnected_graph.island_nodes()), 1) def test_connected_subgraphs(self): subgraph_list = self.graph.connected_subgraphs() subgraph_list = self.disconnected_graph.connected_subgraphs() self.assertEqual(len(subgraph_list), 2) island = self.graph.island_nodes()[0] self.assertTrue(island in subgraph_list[1]) islands = self.disconnected_graph.island_nodes() self.assertTrue(islands[0] in subgraph_list[1]) subgraph_list = self.graph.connected_subgraphs() self.assertEqual(len(subgraph_list), 1) def test_save_load(self): self.graph.save('test_save.cg') loaded = self.graph.from_graph('test_save.cg') self.assertEqual(self.graph.node[0].nkeypoints, loaded.node[0].nkeypoints) self.assertEqual(self.graph.edge[0][1], loaded.edge[0][1]) Loading autocnet/matcher/health.py 0 → 100644 +29 −0 Original line number Diff line number Diff line class EdgeHealth(object): """ Storage and computation of the health of a graph edge using the metric: """ def __init__(self): self.FundamentalMatrix = 0.0 @property def health(self): return self.recompute_health() def update(self, *args, **kwargs): """ Pass through called when the observable (model) changes. *args and **kwargs are passed through from the observable. """ for a in args: if hasattr(self, a.__class__.__name__): setattr(self, a.__class__.__name__, a) def recompute_health(self): """ Recompute the health of the edge. """ return self.FundamentalMatrix.error.mean() Loading
autocnet/examples/Apollo15/adjacency.json +24 −24 Original line number Diff line number Diff line {"autocnet/examples/Apollo15/AS15-M-0297_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0300_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png", "autocnet/examples/Apollo15/AS15-M-0296_SML.png"], "autocnet/examples/Apollo15/AS15-M-0300_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png"], "autocnet/examples/Apollo15/AS15-M-0299_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0300_SML.png", "autocnet/examples/Apollo15/AS15-M-0296_SML.png"], "autocnet/examples/Apollo15/AS15-M-0295_SML.png" {"AS15-M-0297_SML.png" : ["AS15-M-0298_SML.png", "AS15-M-0300_SML.png", "AS15-M-0299_SML.png", "AS15-M-0296_SML.png"], "AS15-M-0300_SML.png" : ["AS15-M-0298_SML.png", "AS15-M-0297_SML.png", "AS15-M-0299_SML.png"], "AS15-M-0299_SML.png" : ["AS15-M-0298_SML.png", "AS15-M-0297_SML.png", "AS15-M-0300_SML.png", "AS15-M-0296_SML.png"], "AS15-M-0295_SML.png" : [], "autocnet/examples/Apollo15/AS15-M-0296_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png"], "autocnet/examples/Apollo15/AS15-M-0298_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0300_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png", "autocnet/examples/Apollo15/AS15-M-0296_SML.png"]} "AS15-M-0296_SML.png" : ["AS15-M-0298_SML.png", "AS15-M-0297_SML.png", "AS15-M-0299_SML.png"], "AS15-M-0298_SML.png" : ["AS15-M-0297_SML.png", "AS15-M-0300_SML.png", "AS15-M-0299_SML.png", "AS15-M-0296_SML.png"]}
autocnet/graph/edge.py +43 −33 Original line number Diff line number Diff line from collections import MutableMapping import warnings from collections import MutableMapping import numpy as np import pandas as pd from pysal.cg.shapes import Polygon from autocnet.matcher import subpixel as sp from autocnet.matcher.homography import Homography from autocnet.cg.cg import convex_hull_ratio from autocnet.cg.cg import overlapping_polygon_area from autocnet.vis.graph_view import plot_edge from autocnet.matcher import health from autocnet.matcher import outlier_detector as od from autocnet.cg.cg import convex_hull_ratio from autocnet.matcher import subpixel as sp from autocnet.transformation.transformations import FundamentalMatrix, Homography from autocnet.vis.graph_view import plot_edge class Edge(dict, MutableMapping): Loading @@ -35,10 +36,14 @@ class Edge(dict, MutableMapping): self.source = source self.destination = destination self._homography = None self.homography = None self.fundamental_matrix = None self._subpixel_offsets = None self.provenance = {} self._pid = 0 self._observers = set() #Subscribe the heatlh observer self._health = health.EdgeHealth() def __repr__(self): return """ Loading @@ -49,34 +54,29 @@ class Edge(dict, MutableMapping): @property def masks(self): mask_lookup = {'fundamental': 'fundamental_matrix'} if not hasattr(self, '_masks'): self._masks = pd.Panel({self._pid: pd.DataFrame(index=self.matches.index)}) if hasattr(self, 'matches'): self._masks = pd.DataFrame(True, columns=['symmetry', 'ratio'], index=self.matches.index) else: self._masks = pd.DataFrame() # If the mask is coming form another object that tracks # state, dynamically draw the mask from the object. for c in self._masks.columns: if c in mask_lookup: self._masks[c] = getattr(self, mask_lookup[c]).mask return self._masks @masks.setter def masks(self, v): column_name = v[0] boolean_mask = v[1] current = self.masks[self._pid] current[column_name] = boolean_mask @property def error(self): if not hasattr(self, '_error'): self._error = pd.Panel({self._pid: pd.DataFrame(index=self.matches.index)}) return self._error @error.setter def error(self, v): pass self.masks[column_name] = boolean_mask @property def homography(self): return self._homography @homography.setter def homography(self, v): self._homography = v def health(self): return self._health.health def keypoints(self, clean_keys=[]): """ Loading Loading @@ -124,6 +124,9 @@ class Edge(dict, MutableMapping): else: raise AttributeError('Matches have not been computed for this edge') all_source_keypoints = self.source.keypoints.iloc[matches['source_idx']] all_destin_keypoints = self.destination.keypoints.iloc[matches['destination_idx']] if clean_keys: matches, mask = self._clean(clean_keys) Loading @@ -140,8 +143,17 @@ class Edge(dict, MutableMapping): mask[mask == True] = fundam_mask else: mask = fundam_mask self.fundamental_matrix = FundamentalMatrix(transformation_matrix, all_source_keypoints[['x', 'y']], all_destin_keypoints[['x', 'y']], mask=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 self.masks = ('fundamental', mask) self.fundamental_matrix = transformation_matrix def compute_homography(self, method='ransac', clean_keys=[], pid=None, **kwargs): """ Loading Loading @@ -188,7 +200,7 @@ class Edge(dict, MutableMapping): self.homography = Homography(transformation_matrix, s_keypoints[ransac_mask][['x', 'y']], d_keypoints[ransac_mask][['x', 'y']], index=mask[mask == True].index) mask=mask[mask == True].index) # Finalize the array to get custom attrs to propagate self.homography.__array_finalize__(self.homography) Loading Loading @@ -362,9 +374,7 @@ class Edge(dict, MutableMapping): mask : series A boolean series to inflate back to the full match set """ if not pid: pid = self._pid panel = self.masks[pid] panel = self.masks mask = panel[clean_keys].all(axis=1) matches = self.matches[mask] return matches, mask
autocnet/graph/network.py +8 −6 Original line number Diff line number Diff line Loading @@ -175,7 +175,6 @@ class CandidateGraph(nx.Graph): raise NotImplementedError self.add_node(self.node_counter, *args, **kwargs) #self.node_labels[self.node[self.node_counter]['image_name']] = self.node_counter self.node_counter += 1 def extract_features(self, method='orb', extractor_parameters={}): Loading Loading @@ -225,7 +224,7 @@ class CandidateGraph(nx.Graph): def add_matches(self, matches): """ Adds match data to a node and attributes the data to the appropriate edges, e.g. if A-B have a match, edge A-B is attributes appropriate edges, e.g. if A-B have a match, edge A-B is attributed with the pandas dataframe. Parameters Loading @@ -233,13 +232,16 @@ class CandidateGraph(nx.Graph): matches : dataframe The pandas dataframe containing the matches """ edges = self.edges() 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] if (source_key, destination_key) in edges: edge = self.edge[source_key][destination_key] else: edge = self.edge[destination_key][source_key] if hasattr(edge, 'matches'): df = edge.matches Loading
autocnet/graph/tests/test_network.py +12 −6 Original line number Diff line number Diff line Loading @@ -15,7 +15,10 @@ class TestCandidateGraph(unittest.TestCase): @classmethod def setUpClass(cls): cls.graph = network.CandidateGraph.from_adjacency(get_path('adjacency.json')) basepath = get_path('Apollo15') cls.graph = network.CandidateGraph.from_adjacency(get_path('three_image_adjacency.json'), basepath=basepath) cls.disconnected_graph = network.CandidateGraph.from_adjacency(get_path('adjacency.json')) def test_get_name(self): node_number = self.graph.node_name_map['AS15-M-0297_SML.png'] Loading @@ -35,18 +38,21 @@ class TestCandidateGraph(unittest.TestCase): pass def test_island_nodes(self): self.assertEqual(len(self.graph.island_nodes()), 1) self.assertEqual(len(self.disconnected_graph.island_nodes()), 1) def test_connected_subgraphs(self): subgraph_list = self.graph.connected_subgraphs() subgraph_list = self.disconnected_graph.connected_subgraphs() self.assertEqual(len(subgraph_list), 2) island = self.graph.island_nodes()[0] self.assertTrue(island in subgraph_list[1]) islands = self.disconnected_graph.island_nodes() self.assertTrue(islands[0] in subgraph_list[1]) subgraph_list = self.graph.connected_subgraphs() self.assertEqual(len(subgraph_list), 1) def test_save_load(self): self.graph.save('test_save.cg') loaded = self.graph.from_graph('test_save.cg') self.assertEqual(self.graph.node[0].nkeypoints, loaded.node[0].nkeypoints) self.assertEqual(self.graph.edge[0][1], loaded.edge[0][1]) Loading
autocnet/matcher/health.py 0 → 100644 +29 −0 Original line number Diff line number Diff line class EdgeHealth(object): """ Storage and computation of the health of a graph edge using the metric: """ def __init__(self): self.FundamentalMatrix = 0.0 @property def health(self): return self.recompute_health() def update(self, *args, **kwargs): """ Pass through called when the observable (model) changes. *args and **kwargs are passed through from the observable. """ for a in args: if hasattr(self, a.__class__.__name__): setattr(self, a.__class__.__name__, a) def recompute_health(self): """ Recompute the health of the edge. """ return self.FundamentalMatrix.error.mean()