Loading autocnet/graph/edge.py +2 −18 Original line number Diff line number Diff line Loading @@ -59,24 +59,8 @@ class Edge(dict, MutableMapping): """.format(self.source, self.destination, self.masks) def __eq__(self, other): eq = True d = self.__dict__ o = other.__dict__ for k, v in d.items(): # If the attribute key is missing they can not be equal if not k in o.keys(): eq = False return eq if isinstance(v, pd.DataFrame): if not v.equals(o[k]): eq = False print(k) elif isinstance(v, np.ndarray): if not v.all() == o[k].all(): eq = False print(k) return eq return utils.compare_dicts(self.__dict__, other.__dict__) *\ utils.compare_dicts(self, other) def match(self, k=2, **kwargs): Loading autocnet/graph/network.py +7 −4 Original line number Diff line number Diff line Loading @@ -89,15 +89,18 @@ class CandidateGraph(nx.Graph): self.graph['modifieddate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) def __eq__(self, other): eq = True # Check the nodes if sorted(self.nodes()) != sorted(other.nodes()): return False for n in self.nodes_iter(): if not self.node[n] == other.node[n]: eq = False return False if sorted(self.edges()) != sorted(other.edges()): return False for s, d in self.edges_iter(): if not self.edge[s][d] == other.edge[s][d]: eq = False return eq return False return True def _order_adjacency(self): # pragma: no cover self.adj = OrderedDict(sorted(self.adj.items())) Loading autocnet/graph/node.py +15 −43 Original line number Diff line number Diff line Loading @@ -65,7 +65,6 @@ class Node(dict, MutableMapping): self['image_path'] = image_path self['node_id'] = node_id self['hash'] = image_name self._mask_arrays = {} self.descriptors = None self.keypoints = pd.DataFrame() self.masks = pd.DataFrame() Loading @@ -81,7 +80,7 @@ class Node(dict, MutableMapping): """.format(self['node_id'], self['image_name'], self['image_path'], self.nkeypoints, self.masks, self.__class__) def __hash__(self): def __hash__(self): #pragma: no cover return hash(repr(self)) def __gt__(self, other): Loading @@ -89,7 +88,7 @@ class Node(dict, MutableMapping): oid = other['node_id'] return myid > oid def __geq__(self, other): def __ge__(self, other): myid = self['node_id'] oid = other['node_id'] return myid >= oid Loading @@ -99,7 +98,7 @@ class Node(dict, MutableMapping): oid = other['node_id'] return myid < oid def __leq__(self, other): def __le__(self, other): myid = self['node_id'] oid = other['node_id'] return myid <= oid Loading @@ -108,19 +107,9 @@ class Node(dict, MutableMapping): return str(self['node_id']) def __eq__(self, other): eq = True d = self.__dict__ o = other.__dict__ for k, v in d.items(): if isinstance(v, pd.DataFrame): if not v.equals(o[k]): print('NODE', k) eq = False elif isinstance(v, np.ndarray): if not v.all() == o[k].all(): print('NODE', k) eq = False return eq return utils.compare_dicts(self.__dict__, other.__dict__) *\ utils.compare_dicts(self, other) @property def geodata(self): Loading Loading @@ -169,7 +158,7 @@ class Node(dict, MutableMapping): Returns ------- coverage_area : float Area covered by the generated percentage area covered by the generated keypoints """ Loading @@ -182,9 +171,7 @@ class Node(dict, MutableMapping): total_area = max_x * max_y self.coverage_area = (hull_area/total_area)*100 return self.coverage_area return hull_area / total_area def get_byte_array(self, band=1): """ Loading Loading @@ -258,17 +245,21 @@ class Node(dict, MutableMapping): return keypoints def get_raw_keypoint_coordinates(self, index): def get_raw_keypoint_coordinates(self, index=slice(None)): """ The performance of get_keypoint_coordinates can be slow due to the ability for fancier indexing. This method returns coordinates using numpy array accessors. Parameters ---------- index : iterable positional indices to return from the global keypoints dataframe """ index = index.astype(np.int) return self.keypoints.values[index,:2] @staticmethod def _extract_features(array, *args, **kwargs): def _extract_features(array, *args, **kwargs): # pragma: no cover """ Extract features for the node Loading Loading @@ -417,25 +408,6 @@ class Node(dict, MutableMapping): io_keypoints.to_npy(self.keypoints, self.descriptors, out_path + '_{}.npz'.format(self['node_id'])) def coverage_ratio(self, clean_keys=[]): """ Compute the ratio $area_{convexhull} / area_{total}$ Returns ------- ratio : float The ratio of convex hull area to total area. """ ideal_area = self.geodata.pixel_area if not hasattr(self, 'keypoints'): raise AttributeError('Keypoints must be extracted already, they have not been.') #TODO: clean_keys are disabled - re-enable. keypoints = self.get_keypoint_coordinates() ratio = convex_hull_ratio(keypoints, ideal_area) return ratio def plot(self, clean_keys=[], **kwargs): # pragma: no cover return plot_node(self, clean_keys=clean_keys, **kwargs) Loading autocnet/graph/tests/test_network.py +64 −2 Original line number Diff line number Diff line import copy import os import time import sys import pandas as pd import pytest import unittest from unittest.mock import patch, PropertyMock, MagicMock Loading Loading @@ -41,13 +42,49 @@ def geo_graph(): def disconnected_graph(): return network.CandidateGraph.from_adjacency(get_path('adjacency.json')) @pytest.fixture() def candidategraph(node_a, node_b, node_c): # TODO: Getting this fixture from the global conf is causing deepycopy # to fail. Why? cg = network.CandidateGraph() # Create a candidategraph object - we instantiate a real CandidateGraph to # have access of networkx functionality we do not want to test and then # mock all autocnet functionality to control test behavior. edges = [(0,1), (0,2), (1,2)] cg.add_edges_from(edges) match_indices = [([0,1,2,3,4,5,6,7], [0,1,2,3,4,5,6,7]), ([0,1,2,3,4,5,8,9], [0,1,2,3,4,5,8,9]), ([0,1,2,3,4,5,8,9], [0,1,2,3,4,5,6,7])] matches = [] for i, e in enumerate(edges): c = match_indices[i] source_image = np.repeat(e[0], 8) destin_image = np.repeat(e[1], 8) coords = np.zeros(8) data = np.vstack((source_image, c[0], destin_image, c[1], coords, coords, coords, coords)).T matches_df = pd.DataFrame(data, columns=['source_image', 'source_idx', 'destination_image', 'destination_idx', 'source_x', 'source_y', 'destination_x', 'destination_y']) matches.append(matches_df) # Mock in autocnet methods cg.get_matches = MagicMock(return_value=matches) # Mock in the node objects onto the candidate graph cg.node[0] = node_a cg.node[1] = node_b cg.node[2] = node_c return cg def test_get_name(graph): node_number = graph.graph['node_name_map']['AS15-M-0297_SML.png'] name = graph.get_name(node_number) assert name == 'AS15-M-0297_SML.png' def test_size(graph): assert graph.size() == graph.number_of_edges() for u, v, e in graph.edges_iter(data=True): Loading Loading @@ -79,6 +116,31 @@ def test_add_image(graph): with pytest.raises(NotImplementedError): graph.add_image() def test_equal(candidategraph): cg = copy.deepcopy(candidategraph) assert candidategraph == cg cg = copy.deepcopy(candidategraph) cg.remove_edge(0,1) assert candidategraph != cg cg = copy.deepcopy(candidategraph) cg.remove_node(0) assert candidategraph != cg cg = copy.deepcopy(candidategraph) cg.node[0]['image_name'] = 'foo' assert candidategraph != cg cg = copy.deepcopy(candidategraph) cg.edge[0][1]['fundamental_matrix'] = np.random.random((3,3)) assert candidategraph != cg def test_get_matches(candidategraph): matches = candidategraph.get_matches() assert len(matches) == 3 assert len(matches[0]) == 8 assert isinstance(matches[0], pd.DataFrame) def test_island_nodes(disconnected_graph): assert len(disconnected_graph.island_nodes()) == 1 Loading autocnet/graph/tests/test_node.py +47 −2 Original line number Diff line number Diff line Loading @@ -40,6 +40,18 @@ class TestNode(object): assert (1012, 1012) == image.shape assert np.uint8 == image.dtype def test_equalities(self,node_a, node_b): assert node_a < node_b assert node_a <= node_b assert not (node_a > node_b) assert not (node_a >= node_b) assert not (node_a == node_b) node_a.random_attr = np.arange(10) node_b.random_attr = np.arange(10) assert not (node_a == node_b) def test_get_array(self, node): image = node.get_array() assert (1012, 1012) == image.shape Loading Loading @@ -74,6 +86,9 @@ class TestNode(object): assert kps['y'].min() < tilesize assert len(kps) == pytest.approx(90, 3) with pytest.raises(ValueError) as e_info: node.extract_features_with_tiling(tilesize=10, overlap=20) def test_masks(self, node): image = node.get_array() node.extract_features(image, extractor_parameters={'nfeatures': 5}) Loading Loading @@ -120,7 +135,7 @@ class TestNode(object): image = node.get_array() node.extract_features(image, extractor_method='sift', extractor_parameters={'nfeatures': 10}) coverage_percn = node.coverage() assert coverage_percn == pytest.approx(38.06139557, 2) assert coverage_percn == pytest.approx(0.3806139557, 2) def test_clean(self, node): with pytest.raises(AttributeError): Loading @@ -132,6 +147,36 @@ class TestNode(object): matches, mask = node._clean(clean_keys=['a']) assert mask.equals(pd.Series([True, True, True, False, False])) def test_footprint(self, geo_node): def test_get_keypoints(self, node): image = node.get_array() node.extract_features(image, extractor_parameters={'nfeatures':5}) kps = node.get_keypoints(index=[1,3]) assert len(kps) == 2 assert 1 in kps.index and 3 in kps.index def test_get_keypoint_coordinates(self, node): image = node.get_array() node.extract_features(image, extractor_parameters={'nfeatures':5}) kpc = node.get_keypoint_coordinates() assert 'x' in kpc.columns assert 'y' in kpc.columns kpc = node.get_keypoint_coordinates(index=[2,4]) assert len(kpc) == 2 kpc = node.get_keypoint_coordinates(homogeneous=True) assert (kpc.homogeneous == 1).all() def test_get_raw_keypoint_coordinates(self, node): image = node.get_array() node.extract_features(image, extractor_parameters={'nfeatures':5}) kpc = node.get_raw_keypoint_coordinates() assert isinstance(kpc, np.ndarray) assert kpc.shape == (5,2) kpc = node.get_raw_keypoint_coordinates(-1) assert kpc.shape == (2,) def test_footprint(self, geo_node, node_a): # Esnure that a shapely compliant poly is being returned assert isinstance(geo_node.footprint, Polygon) assert node_a.footprint == None Loading
autocnet/graph/edge.py +2 −18 Original line number Diff line number Diff line Loading @@ -59,24 +59,8 @@ class Edge(dict, MutableMapping): """.format(self.source, self.destination, self.masks) def __eq__(self, other): eq = True d = self.__dict__ o = other.__dict__ for k, v in d.items(): # If the attribute key is missing they can not be equal if not k in o.keys(): eq = False return eq if isinstance(v, pd.DataFrame): if not v.equals(o[k]): eq = False print(k) elif isinstance(v, np.ndarray): if not v.all() == o[k].all(): eq = False print(k) return eq return utils.compare_dicts(self.__dict__, other.__dict__) *\ utils.compare_dicts(self, other) def match(self, k=2, **kwargs): Loading
autocnet/graph/network.py +7 −4 Original line number Diff line number Diff line Loading @@ -89,15 +89,18 @@ class CandidateGraph(nx.Graph): self.graph['modifieddate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) def __eq__(self, other): eq = True # Check the nodes if sorted(self.nodes()) != sorted(other.nodes()): return False for n in self.nodes_iter(): if not self.node[n] == other.node[n]: eq = False return False if sorted(self.edges()) != sorted(other.edges()): return False for s, d in self.edges_iter(): if not self.edge[s][d] == other.edge[s][d]: eq = False return eq return False return True def _order_adjacency(self): # pragma: no cover self.adj = OrderedDict(sorted(self.adj.items())) Loading
autocnet/graph/node.py +15 −43 Original line number Diff line number Diff line Loading @@ -65,7 +65,6 @@ class Node(dict, MutableMapping): self['image_path'] = image_path self['node_id'] = node_id self['hash'] = image_name self._mask_arrays = {} self.descriptors = None self.keypoints = pd.DataFrame() self.masks = pd.DataFrame() Loading @@ -81,7 +80,7 @@ class Node(dict, MutableMapping): """.format(self['node_id'], self['image_name'], self['image_path'], self.nkeypoints, self.masks, self.__class__) def __hash__(self): def __hash__(self): #pragma: no cover return hash(repr(self)) def __gt__(self, other): Loading @@ -89,7 +88,7 @@ class Node(dict, MutableMapping): oid = other['node_id'] return myid > oid def __geq__(self, other): def __ge__(self, other): myid = self['node_id'] oid = other['node_id'] return myid >= oid Loading @@ -99,7 +98,7 @@ class Node(dict, MutableMapping): oid = other['node_id'] return myid < oid def __leq__(self, other): def __le__(self, other): myid = self['node_id'] oid = other['node_id'] return myid <= oid Loading @@ -108,19 +107,9 @@ class Node(dict, MutableMapping): return str(self['node_id']) def __eq__(self, other): eq = True d = self.__dict__ o = other.__dict__ for k, v in d.items(): if isinstance(v, pd.DataFrame): if not v.equals(o[k]): print('NODE', k) eq = False elif isinstance(v, np.ndarray): if not v.all() == o[k].all(): print('NODE', k) eq = False return eq return utils.compare_dicts(self.__dict__, other.__dict__) *\ utils.compare_dicts(self, other) @property def geodata(self): Loading Loading @@ -169,7 +158,7 @@ class Node(dict, MutableMapping): Returns ------- coverage_area : float Area covered by the generated percentage area covered by the generated keypoints """ Loading @@ -182,9 +171,7 @@ class Node(dict, MutableMapping): total_area = max_x * max_y self.coverage_area = (hull_area/total_area)*100 return self.coverage_area return hull_area / total_area def get_byte_array(self, band=1): """ Loading Loading @@ -258,17 +245,21 @@ class Node(dict, MutableMapping): return keypoints def get_raw_keypoint_coordinates(self, index): def get_raw_keypoint_coordinates(self, index=slice(None)): """ The performance of get_keypoint_coordinates can be slow due to the ability for fancier indexing. This method returns coordinates using numpy array accessors. Parameters ---------- index : iterable positional indices to return from the global keypoints dataframe """ index = index.astype(np.int) return self.keypoints.values[index,:2] @staticmethod def _extract_features(array, *args, **kwargs): def _extract_features(array, *args, **kwargs): # pragma: no cover """ Extract features for the node Loading Loading @@ -417,25 +408,6 @@ class Node(dict, MutableMapping): io_keypoints.to_npy(self.keypoints, self.descriptors, out_path + '_{}.npz'.format(self['node_id'])) def coverage_ratio(self, clean_keys=[]): """ Compute the ratio $area_{convexhull} / area_{total}$ Returns ------- ratio : float The ratio of convex hull area to total area. """ ideal_area = self.geodata.pixel_area if not hasattr(self, 'keypoints'): raise AttributeError('Keypoints must be extracted already, they have not been.') #TODO: clean_keys are disabled - re-enable. keypoints = self.get_keypoint_coordinates() ratio = convex_hull_ratio(keypoints, ideal_area) return ratio def plot(self, clean_keys=[], **kwargs): # pragma: no cover return plot_node(self, clean_keys=clean_keys, **kwargs) Loading
autocnet/graph/tests/test_network.py +64 −2 Original line number Diff line number Diff line import copy import os import time import sys import pandas as pd import pytest import unittest from unittest.mock import patch, PropertyMock, MagicMock Loading Loading @@ -41,13 +42,49 @@ def geo_graph(): def disconnected_graph(): return network.CandidateGraph.from_adjacency(get_path('adjacency.json')) @pytest.fixture() def candidategraph(node_a, node_b, node_c): # TODO: Getting this fixture from the global conf is causing deepycopy # to fail. Why? cg = network.CandidateGraph() # Create a candidategraph object - we instantiate a real CandidateGraph to # have access of networkx functionality we do not want to test and then # mock all autocnet functionality to control test behavior. edges = [(0,1), (0,2), (1,2)] cg.add_edges_from(edges) match_indices = [([0,1,2,3,4,5,6,7], [0,1,2,3,4,5,6,7]), ([0,1,2,3,4,5,8,9], [0,1,2,3,4,5,8,9]), ([0,1,2,3,4,5,8,9], [0,1,2,3,4,5,6,7])] matches = [] for i, e in enumerate(edges): c = match_indices[i] source_image = np.repeat(e[0], 8) destin_image = np.repeat(e[1], 8) coords = np.zeros(8) data = np.vstack((source_image, c[0], destin_image, c[1], coords, coords, coords, coords)).T matches_df = pd.DataFrame(data, columns=['source_image', 'source_idx', 'destination_image', 'destination_idx', 'source_x', 'source_y', 'destination_x', 'destination_y']) matches.append(matches_df) # Mock in autocnet methods cg.get_matches = MagicMock(return_value=matches) # Mock in the node objects onto the candidate graph cg.node[0] = node_a cg.node[1] = node_b cg.node[2] = node_c return cg def test_get_name(graph): node_number = graph.graph['node_name_map']['AS15-M-0297_SML.png'] name = graph.get_name(node_number) assert name == 'AS15-M-0297_SML.png' def test_size(graph): assert graph.size() == graph.number_of_edges() for u, v, e in graph.edges_iter(data=True): Loading Loading @@ -79,6 +116,31 @@ def test_add_image(graph): with pytest.raises(NotImplementedError): graph.add_image() def test_equal(candidategraph): cg = copy.deepcopy(candidategraph) assert candidategraph == cg cg = copy.deepcopy(candidategraph) cg.remove_edge(0,1) assert candidategraph != cg cg = copy.deepcopy(candidategraph) cg.remove_node(0) assert candidategraph != cg cg = copy.deepcopy(candidategraph) cg.node[0]['image_name'] = 'foo' assert candidategraph != cg cg = copy.deepcopy(candidategraph) cg.edge[0][1]['fundamental_matrix'] = np.random.random((3,3)) assert candidategraph != cg def test_get_matches(candidategraph): matches = candidategraph.get_matches() assert len(matches) == 3 assert len(matches[0]) == 8 assert isinstance(matches[0], pd.DataFrame) def test_island_nodes(disconnected_graph): assert len(disconnected_graph.island_nodes()) == 1 Loading
autocnet/graph/tests/test_node.py +47 −2 Original line number Diff line number Diff line Loading @@ -40,6 +40,18 @@ class TestNode(object): assert (1012, 1012) == image.shape assert np.uint8 == image.dtype def test_equalities(self,node_a, node_b): assert node_a < node_b assert node_a <= node_b assert not (node_a > node_b) assert not (node_a >= node_b) assert not (node_a == node_b) node_a.random_attr = np.arange(10) node_b.random_attr = np.arange(10) assert not (node_a == node_b) def test_get_array(self, node): image = node.get_array() assert (1012, 1012) == image.shape Loading Loading @@ -74,6 +86,9 @@ class TestNode(object): assert kps['y'].min() < tilesize assert len(kps) == pytest.approx(90, 3) with pytest.raises(ValueError) as e_info: node.extract_features_with_tiling(tilesize=10, overlap=20) def test_masks(self, node): image = node.get_array() node.extract_features(image, extractor_parameters={'nfeatures': 5}) Loading Loading @@ -120,7 +135,7 @@ class TestNode(object): image = node.get_array() node.extract_features(image, extractor_method='sift', extractor_parameters={'nfeatures': 10}) coverage_percn = node.coverage() assert coverage_percn == pytest.approx(38.06139557, 2) assert coverage_percn == pytest.approx(0.3806139557, 2) def test_clean(self, node): with pytest.raises(AttributeError): Loading @@ -132,6 +147,36 @@ class TestNode(object): matches, mask = node._clean(clean_keys=['a']) assert mask.equals(pd.Series([True, True, True, False, False])) def test_footprint(self, geo_node): def test_get_keypoints(self, node): image = node.get_array() node.extract_features(image, extractor_parameters={'nfeatures':5}) kps = node.get_keypoints(index=[1,3]) assert len(kps) == 2 assert 1 in kps.index and 3 in kps.index def test_get_keypoint_coordinates(self, node): image = node.get_array() node.extract_features(image, extractor_parameters={'nfeatures':5}) kpc = node.get_keypoint_coordinates() assert 'x' in kpc.columns assert 'y' in kpc.columns kpc = node.get_keypoint_coordinates(index=[2,4]) assert len(kpc) == 2 kpc = node.get_keypoint_coordinates(homogeneous=True) assert (kpc.homogeneous == 1).all() def test_get_raw_keypoint_coordinates(self, node): image = node.get_array() node.extract_features(image, extractor_parameters={'nfeatures':5}) kpc = node.get_raw_keypoint_coordinates() assert isinstance(kpc, np.ndarray) assert kpc.shape == (5,2) kpc = node.get_raw_keypoint_coordinates(-1) assert kpc.shape == (2,) def test_footprint(self, geo_node, node_a): # Esnure that a shapely compliant poly is being returned assert isinstance(geo_node.footprint, Polygon) assert node_a.footprint == None