Loading autocnet/cg/cg.py +45 −44 Changes for autocnet/cg/cg.py: 45 added lines, 44 removed lines. Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ from scipy.spatial import ConvexHull from scipy.spatial import Voronoi from shapely.geometry import Polygon, Point from shapely.affinity import scale from shapely.ops import unary_union import cv2 from autocnet.utils import utils Loading Loading @@ -115,20 +116,9 @@ def get_area(poly1, poly2): def vor(graph, clean_keys, s=30): """ Creates a voronoi diagram for a complete graph using either the coordinate transformation or using the homography based around a source node. The coordinate transformation uses the footprint of each image to calculate an intersection between the image set, then transforms the vertices of the intersection back into pixel space. If a coordinate transform does not exist, use the homography to project the n - 1 images onto the source image, producing an area of intersection. The intersection vertices are then scaled by a factor of s (default 30), this accounts for the areas of the voronoi that would be missed if the scaled vertices were not included into the voronoi calculation. Creates a voronoi diagram for all edges in a graph, and assigns a given weight to each edge. This is based around voronoi polygons generated by scipy's voronoi method, to determine if an image has significant coverage. Parameters ---------- Loading @@ -139,27 +129,19 @@ def vor(graph, clean_keys, s=30): Of strings used to apply masks to omit correspondences s : int offset for the corners of the image Returns ------- vor : object Scipy Voronoi object voronoi_df : dataframe 3 column pandas dataframe of x, y, and weights Offset for the corners of the image """ neighbors_dict = nx.degree(graph) if not all(value == len(graph.neighbors(graph.nodes()[0])) for value in neighbors_dict.values()): warnings.warn('The given graph is not complete and may yield garbage.') intersection, proj_gdf, source_gdf = compute_intersection(graph.nodes()[0], graph, clean_keys) intersection, proj_gdf, source_gdf = compute_intersection(graph, graph.nodes()[0], clean_keys) intersection_ind = intersection.query('overlaps_all == True').index.values[0] source_node = graph.nodes()[0] for e in graph.edges(): # If the source node changes, change the projection space to match it if e[0] != source_node: source_node = e[0] intersection, proj_gdf, source_gdf = compute_intersection(source_node, graph, clean_keys) Loading @@ -169,19 +151,20 @@ def vor(graph, clean_keys, s=30): kps = edge.get_keypoints('source', clean_keys=clean_keys, homogeneous=True) kps['geometry'] = kps.apply(lambda x: Point(x['x'], x['y']), axis=1) kps.mask = kps['geometry'].apply(lambda x: intersection.geometry.contains(x).all()) kps.mask = kps['geometry'].apply(lambda x: intersection.geometry.contains(x).any()) # Creates a mask for displaying the voronoi points # Currently erronious and produces NaN values # Currently erronious and produces NaN values in place # of true # matches, mask = edge.clean(clean_keys = clean_keys) # mask[mask] = kps.mask # edge.masks = ('voronoi', mask) keypoints = [] kps[kps.mask].apply(lambda x: keypoints.append((x['x'], x['y'])), axis=1) coords = source_gdf.geometry.apply(lambda x:scale(x, s, s).exterior.coords) for point in coords: keypoints = np.vstack((keypoints, point)) scaled_coords = np.array(scale(source_gdf.geometry[0], s, s).exterior.coords) keypoints = np.vstack((keypoints, scaled_coords)) vor = Voronoi(keypoints) voronoi_df = pd.DataFrame(data=kps, columns=['x', 'y', 'weight']) Loading @@ -203,8 +186,12 @@ def vor(graph, clean_keys, s=30): edge['weights']['vor_weight'] = voronoi_df['weight'] def compute_intersection(source, graph, clean_keys=[]): def compute_intersection(graph, source, clean_keys=[]): """ Computes the intersections of images in a graph based on the connections between nodes. The method takes every node in a graph, sees who is connected to it, then computes an intersection based on those connections. Parameters ---------- Loading @@ -219,7 +206,15 @@ def compute_intersection(source, graph, clean_keys=[]): Returns ------- intersection : intersection : dataframe 4 column dataframe of source_node, proj_node, geometry, and overlaps_all proj_gdf : dataframe 2 column dataframe of proj_geom, proj_node source_gdf : dataframe 2 column dataframe of geometry, source_node """ if type(source) is int: source = graph.node[source] Loading @@ -227,7 +222,7 @@ def compute_intersection(source, graph, clean_keys=[]): source_corners = source.geodata.xy_corners source_poly = Polygon(source_corners) source_gdf = gpd.GeoDataFrame({'source_geom': [source_poly], 'source_node': [source['node_id']]}).set_geometry('source_geom') source_gdf = gpd.GeoDataFrame({'geometry': [source_poly], 'source_node': [source['node_id']]}) proj_list = [] proj_nodes = [] Loading @@ -240,8 +235,16 @@ def compute_intersection(source, graph, clean_keys=[]): destination = graph.node[n] destination_corners = destination.geodata.xy_corners try: edge = graph.edge[source['node_id']][destination['node_id']] # If the source image has coordinate transformation data if (source.geodata.coordinate_transformation.this is not None) and \ (destination.geodata.coordinate_transformation.this is not None): proj_poly = Polygon(destination.geodata.xy_corners) # Else, use the homography transform to get an intersection of the two images else: # Will still need the check but the helper function will make these calls much easier to understand if source['node_id'] > destination['node_id']: kp2 = edge.get_keypoints('source', clean_keys=clean_keys, homogeneous=True) Loading @@ -250,13 +253,6 @@ def compute_intersection(source, graph, clean_keys=[]): kp2 = edge.get_keypoints('destination', clean_keys=clean_keys, homogeneous=True) kp1 = edge.get_keypoints('source', clean_keys=clean_keys, homogeneous=True) # If the source image has coordinate transformation data if (source.geodata.coordinate_transformation.this is None) and \ (destination.geodata.coordinate_transformation.this is None): proj_poly = Polygon(destination.geodata.xy_corners) # Else, use the homography transform to get an intersection of the two images else: H, mask = cv2.findHomography(kp2.values, kp1.values, cv2.RANSAC, 2.0) proj_corners = [] for c in destination_corners: Loading @@ -267,14 +263,19 @@ def compute_intersection(source, graph, clean_keys=[]): proj_corners.append((x, y)) proj_poly = Polygon(proj_corners) except: continue proj_list.append(proj_poly) proj_nodes.append(n) proj_gdf = gpd.GeoDataFrame({'proj_geom': proj_list, 'proj_node': proj_nodes}).set_geometry('proj_geom') proj_gdf = gpd.GeoDataFrame({'geometry': proj_list, 'proj_node': proj_nodes}) intersect_gdf = gpd.overlay(source_gdf, proj_gdf, how='intersection') intersect_gdf = intersect_gdf.rename(columns = {'geometry':'intersect_geom'}).set_geometry('intersect_geom') intersect_gdf['overlaps_all'] = intersect_gdf.geometry.apply(lambda x:proj_gdf.geometry.contains(scale(x, .9, .9)).all()) intersection = intersect_gdf.query('overlaps_all == True').set_geometry('intersect_geom') intersect_gdf['overlaps_all'] = intersect_gdf.geometry.apply(lambda z: proj_gdf.geometry.contains(scale(z, .9, .9)).all()) intersection = intersect_gdf.query('overlaps_all == True') if len(intersection) == 0: new_poly = unary_union(intersect_gdf.geometry) intersection = gpd.GeoDataFrame({'source_node': source['node_id'], 'geometry': new_poly, 'overlaps_all': True}) return intersection, proj_gdf, source_gdf autocnet/cg/tests/test_cg.py +218 −0 Changes for autocnet/cg/tests/test_cg.py: 218 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -4,9 +4,17 @@ import unittest sys.path.insert(0, os.path.abspath('..')) import numpy as np import pandas as pd from .. import cg from osgeo import ogr from unittest.mock import Mock, MagicMock from plio.io import io_gdal from autocnet.graph.node import Node from autocnet.graph.network import CandidateGraph from autocnet.graph.edge import Edge from autocnet.utils.utils import array_to_poly class TestArea(unittest.TestCase): Loading @@ -33,3 +41,213 @@ class TestArea(unittest.TestCase): self.assertEqual(info[1], 400) self.assertAlmostEqual(info[0], 14.285714285) def test_voronoi_homography(self): source_keypoint_df = pd.DataFrame({'x': (15, 18, 18, 12, 12), 'y': (6, 10, 15, 15, 10)}) destination_keypoint_df = pd.DataFrame({'x': (5, 8, 8, 2, 2), 'y': (1, 5, 10, 10, 5)}) keypoint_matches = [[0, 0, 1, 0], [0, 1, 1, 1], [0, 2, 1, 2], [0, 3, 1, 3], [0, 4, 1, 4]] matches_df = pd.DataFrame(data=keypoint_matches, columns=['source_image', 'source_idx', 'destination_image', 'destination_idx']) # Source and Destination Node Setup source_node = MagicMock(spec=Node()) destination_node = MagicMock(spec=Node()) source_node.get_keypoint_coordinates = MagicMock(return_value=source_keypoint_df) destination_node.get_keypoint_coordinates = MagicMock(return_value=destination_keypoint_df) source_geodata = Mock(spec=io_gdal.GeoDataset) destination_geodata = Mock(spec=io_gdal.GeoDataset) source_node.geodata = source_geodata destination_node.geodata = destination_geodata source_node.geodata.coordinate_transformation.this = None destination_node.geodata.coordinate_transformation.this = None source_corners = [(0, 0), (20, 0), (20, 20), (0, 20)] destination_corners = [(0, 0), (20, 0), (20, 20), (0, 20)] source_node.geodata.xy_corners = source_corners destination_node.geodata.xy_corners = destination_corners # Edge Setup e = Edge(source=source_node, destination=destination_node) e.clean = MagicMock(return_value=(matches_df, None)) e.matches = matches_df def side_effect(node, clean_keys, **kwargs): if type(node) is str: node = node.lower() if isinstance(node, Node): node = node['node_id'] if node == "source" or node == "s" or node == source_node['node_id']: return e.source.get_keypoint_coordinates().copy(deep=True) if node == "destination" or node == "d" or node == destination_node['node_id']: return e.destination.get_keypoint_coordinates().copy(deep=True) my_dict_source = {'node_id':0} def getitem_source(name): return my_dict_source[name] my_dict_destination = {'node_id':1} def getitem_destination(name): return my_dict_destination[name] source_node.__getitem__.side_effect = getitem_source destination_node.__getitem__.side_effect = getitem_destination e.get_keypoints = MagicMock(side_effect=side_effect) cang = MagicMock(spec=CandidateGraph()) cang.nodes = MagicMock(return_value=([0, 1])) cang.node = [source_node, destination_node] cang.nodes_iter = MagicMock(return_value=([0, 1])) cang.neighbors = MagicMock(return_value=[1]) cang.edges = MagicMock(return_value=([(0, 1)])) cang.edge = {0: {1: e}, 1: {0: e}} cg.vor(cang, clean_keys=['fundamental']) self.assertAlmostEqual(e['weights']['vor_weight'][0], 22.5) self.assertAlmostEqual(e['weights']['vor_weight'][1], 26.25) self.assertAlmostEqual(e['weights']['vor_weight'][2], 37.5) self.assertAlmostEqual(e['weights']['vor_weight'][3], 37.5) self.assertAlmostEqual(e['weights']['vor_weight'][4], 26.25) def test_voronoi_coord(self): source_keypoint_df = pd.DataFrame({'x': (15, 18, 18, 12, 12), 'y': (6, 10, 15, 15, 10)}) destination_keypoint_df = pd.DataFrame({'x': (5, 8, 8, 2, 2), 'y': (1, 5, 10, 10, 5)}) keypoint_df = pd.DataFrame({'x': (15, 18, 18, 12, 12), 'y': (6, 10, 15, 15, 10)}) keypoint_matches = [[0, 0, 1, 0], [0, 1, 1, 1], [0, 2, 1, 2], [0, 3, 1, 3], [0, 4, 1, 4]] matches_df = pd.DataFrame(data=keypoint_matches, columns=['source_image', 'source_idx', 'destination_image', 'destination_idx']) source_node = MagicMock(spec=Node()) destination_node = MagicMock(spec=Node()) source_node.get_keypoint_coordinates = MagicMock(return_value=source_keypoint_df) destination_node.get_keypoint_coordinates = MagicMock(return_value=destination_keypoint_df) source_geodata = Mock(spec=io_gdal.GeoDataset) destination_geodata = Mock(spec=io_gdal.GeoDataset) source_node.geodata = source_geodata destination_node.geodata = destination_geodata e = Edge(source=source_node, destination = destination_node) e.clean = MagicMock(return_value=(matches_df, None)) e.matches = matches_df source_node = MagicMock(spec=Node()) destination_node = MagicMock(spec=Node()) cang = MagicMock(spec=CandidateGraph()) cang.nodes = MagicMock(return_value=([0, 1])) cang.node = [source_node, destination_node] cang.nodes_iter = MagicMock(return_value=([0, 1])) cang.neighbors = MagicMock(return_value=([0])) cang.edges = MagicMock(return_value=([(0, 1)])) cang.edge = {0: {1: e}, 1: {0: e}} source_node.get_keypoint_coordinates = MagicMock(return_value=keypoint_df) destination_node.get_keypoint_coordinates = MagicMock(return_value=keypoint_df) source_node['node_id'] = 0 destination_node['node_id'] = 1 def side_effect(node, clean_keys, **kwargs): if type(node) is str: node = node.lower() if isinstance(node, Node): node = node['node_id'] if node == "source" or node == "s" or node == source_node['node_id']: return e.source.get_keypoint_coordinates() if node == "destination" or node == "d" or node == destination_node['node_id']: return e.destination.get_keypoint_coordinates() e.get_keypoints = MagicMock(side_effect=side_effect) e.source = source_node e.destination = destination_node source_geodata = Mock(spec=io_gdal.GeoDataset) destination_geodata = Mock(spec=io_gdal.GeoDataset) e.source.geodata = source_geodata e.destination.geodata = destination_geodata source_corners = [(0, 0), (20, 0), (20, 20), (0, 20)] destination_corners = [(10, 5), (30, 5), (30, 25), (10, 25)] source_xy_extent = [(0, 20), (0, 20)] destination_xy_extent = [(10, 30), (5, 25)] source_poly = array_to_poly(source_corners) destination_poly = array_to_poly(destination_corners) vals = {(10, 5): (10, 5), (20, 5): (20, 5), (20, 20): (20, 20), (10, 20): (10, 20)} def latlon_to_pixel(i, j): return vals[(i, j)] my_dict_source = {'node_id': 0} def getitem_source(name): return my_dict_source[name] my_dict_destination = {'node_id': 1} def getitem_destination(name): return my_dict_destination[name] source_node.__getitem__.side_effect = getitem_source destination_node.__getitem__.side_effect = getitem_destination e.source.geodata.latlon_to_pixel = MagicMock(side_effect=latlon_to_pixel) e.destination.geodata.latlon_to_pixel = MagicMock(side_effect=latlon_to_pixel) e.source.geodata.footprint = source_poly e.source.geodata.xy_corners = source_corners e.source.geodata.xy_extent = source_xy_extent e.destination.geodata.footprint = destination_poly e.destination.geodata.xy_corners = destination_corners e.destination.geodata.xy_extent = destination_xy_extent cg.vor(cang, clean_keys=['fundamental']) self.assertAlmostEqual(e['weights']['vor_weight'][0], 22.5) self.assertAlmostEqual(e['weights']['vor_weight'][1], 26.25) self.assertAlmostEqual(e['weights']['vor_weight'][2], 37.5) self.assertAlmostEqual(e['weights']['vor_weight'][3], 37.5) self.assertAlmostEqual(e['weights']['vor_weight'][4], 26.25) autocnet/graph/network.py +0 −2 Changes for autocnet/graph/network.py: 0 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -701,7 +701,6 @@ class CandidateGraph(nx.Graph): : list A list of lists of node ids that make up maximum complete subgraphs of the given graph """ if node_id is not None: return list(nx.cliques_containing_node(self, nodes=node_id)) else: Loading @@ -718,5 +717,4 @@ class CandidateGraph(nx.Graph): Strings used to apply masks to omit correspondences """ vor(self, clean_keys) autocnet/graph/tests/test_edge.py +0 −121 Changes for autocnet/graph/tests/test_edge.py: 0 added lines, 121 removed lines. Original line number Diff line number Diff line Loading @@ -128,124 +128,3 @@ class TestEdge(unittest.TestCase): self.assertRaises(AttributeError, cg.edge[0][1].coverage) self.assertEqual(e.coverage(), 0.3) def test_voronoi_transform(self): keypoint_df = pd.DataFrame({'x': (15, 18, 18, 12, 12), 'y': (5, 10, 15, 15, 10)}) keypoint_matches = [[0, 0, 1, 0], [0, 1, 1, 1], [0, 2, 1, 2], [0, 3, 1, 3], [0, 4, 1, 4]] matches_df = pd.DataFrame(data=keypoint_matches, columns=['source_image', 'source_idx', 'destination_image', 'destination_idx']) e = edge.Edge() e.clean = MagicMock(return_value=(matches_df, None)) e.matches = matches_df source_node = MagicMock(spec=node.Node()) destination_node = MagicMock(spec=node.Node()) source_node.get_keypoint_coordinates = MagicMock(return_value=keypoint_df) destination_node.get_keypoint_coordinates = MagicMock(return_value=keypoint_df) e.source = source_node e.destination = destination_node source_geodata = Mock(spec=io_gdal.GeoDataset) destination_geodata = Mock(spec=io_gdal.GeoDataset) e.source.geodata = source_geodata e.destination.geodata = destination_geodata source_corners = [(0, 0), (20, 0), (20, 20), (0, 20)] destination_corners = [(10, 5), (30, 5), (30, 25), (10, 25)] source_poly = array_to_poly(source_corners) destination_poly = array_to_poly(destination_corners) def latlon_to_pixel(i, j): return vals[(i, j)] e.source.geodata.latlon_to_pixel = MagicMock(side_effect=latlon_to_pixel) e.destination.geodata.latlon_to_pixel = MagicMock(side_effect=latlon_to_pixel) e.source.geodata.footprint = source_poly e.source.geodata.xy_corners = source_corners e.destination.geodata.footprint = destination_poly e.destination.geodata.xy_corners = destination_corners vals = {(10, 5): (10, 5), (20, 5): (20, 5), (20, 20): (20, 20), (10, 20): (10, 20)} weights = pd.DataFrame({"vor_weights": (19, 28, 37.5, 37.5, 28)}) e.compute_weights(clean_keys=[]) k = 0 for i in e.matches['vor_weights']: self.assertAlmostEquals(i, weights['vor_weights'][k]) k += 1 def test_voronoi_homography(self): source_keypoint_df = pd.DataFrame({'x': (15, 18, 18, 12, 12), 'y': (5, 10, 15, 15, 10)}) destination_keypoint_df = pd.DataFrame({'x': (5, 8, 8, 2, 2), 'y': (0, 5, 10, 10, 5)}) keypoint_matches = [[0, 0, 1, 0], [0, 1, 1, 1], [0, 2, 1, 2], [0, 3, 1, 3], [0, 4, 1, 4]] matches_df = pd.DataFrame(data = keypoint_matches, columns=['source_image', 'source_idx', 'destination_image', 'destination_idx']) e = edge.Edge() e.clean = MagicMock(return_value=(matches_df, None)) e.matches = matches_df source_node = MagicMock(spec=node.Node()) destination_node = MagicMock(spec=node.Node()) source_node.get_keypoint_coordinates = MagicMock(return_value=source_keypoint_df) destination_node.get_keypoint_coordinates = MagicMock(return_value=destination_keypoint_df) e.source = source_node e.destination = destination_node source_geodata = Mock(spec=io_gdal.GeoDataset) destination_geodata = Mock(spec=io_gdal.GeoDataset) e.source.geodata = source_geodata e.destination.geodata = destination_geodata source_corners = [(0, 0), (20, 0), (20, 20), (0, 20)] destination_corners = [(0, 0), (20, 0), (20, 20), (0, 20)] e.source.geodata.coordinate_transformation.this = None e.destination.geodata.coordinate_transformation.this = None e.source.geodata.xy_corners = source_corners e.destination.geodata.xy_corners = destination_corners weights = pd.DataFrame({"vor_weights": (19, 28, 37.5, 37.5, 28)}) e.compute_weights(clean_keys=[]) k = 0 for i in e.matches['vor_weights']: self.assertAlmostEquals(i, weights['vor_weights'][k]) k += 1 autocnet/graph/tests/test_network.py +15 −0 Changes for autocnet/graph/tests/test_network.py: 15 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -165,3 +165,18 @@ def test_apply_func_to_edges(graph): assert not graph[0][2].masks['symmetry'].all() assert not graph[0][1].masks['symmetry'].all() def test_cliques(): graph = network.CandidateGraph() # for i in range(0, 8): # graph.add_node(i) graph.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7]) graph.add_edges_from([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3), (3, 4), (3, 7), (4, 5), (4, 6), (5, 6)]) cliques = graph.compute_cliques() limited_cliques = graph.compute_cliques(node_id=3) print(cliques, limited_cliques) assert len(cliques) == 4 assert len(limited_cliques) == 3 assert cliques[2][0] == 3 assert cliques[2][1] == 7 Loading
autocnet/cg/cg.py +45 −44 Changes for autocnet/cg/cg.py: 45 added lines, 44 removed lines. Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ from scipy.spatial import ConvexHull from scipy.spatial import Voronoi from shapely.geometry import Polygon, Point from shapely.affinity import scale from shapely.ops import unary_union import cv2 from autocnet.utils import utils Loading Loading @@ -115,20 +116,9 @@ def get_area(poly1, poly2): def vor(graph, clean_keys, s=30): """ Creates a voronoi diagram for a complete graph using either the coordinate transformation or using the homography based around a source node. The coordinate transformation uses the footprint of each image to calculate an intersection between the image set, then transforms the vertices of the intersection back into pixel space. If a coordinate transform does not exist, use the homography to project the n - 1 images onto the source image, producing an area of intersection. The intersection vertices are then scaled by a factor of s (default 30), this accounts for the areas of the voronoi that would be missed if the scaled vertices were not included into the voronoi calculation. Creates a voronoi diagram for all edges in a graph, and assigns a given weight to each edge. This is based around voronoi polygons generated by scipy's voronoi method, to determine if an image has significant coverage. Parameters ---------- Loading @@ -139,27 +129,19 @@ def vor(graph, clean_keys, s=30): Of strings used to apply masks to omit correspondences s : int offset for the corners of the image Returns ------- vor : object Scipy Voronoi object voronoi_df : dataframe 3 column pandas dataframe of x, y, and weights Offset for the corners of the image """ neighbors_dict = nx.degree(graph) if not all(value == len(graph.neighbors(graph.nodes()[0])) for value in neighbors_dict.values()): warnings.warn('The given graph is not complete and may yield garbage.') intersection, proj_gdf, source_gdf = compute_intersection(graph.nodes()[0], graph, clean_keys) intersection, proj_gdf, source_gdf = compute_intersection(graph, graph.nodes()[0], clean_keys) intersection_ind = intersection.query('overlaps_all == True').index.values[0] source_node = graph.nodes()[0] for e in graph.edges(): # If the source node changes, change the projection space to match it if e[0] != source_node: source_node = e[0] intersection, proj_gdf, source_gdf = compute_intersection(source_node, graph, clean_keys) Loading @@ -169,19 +151,20 @@ def vor(graph, clean_keys, s=30): kps = edge.get_keypoints('source', clean_keys=clean_keys, homogeneous=True) kps['geometry'] = kps.apply(lambda x: Point(x['x'], x['y']), axis=1) kps.mask = kps['geometry'].apply(lambda x: intersection.geometry.contains(x).all()) kps.mask = kps['geometry'].apply(lambda x: intersection.geometry.contains(x).any()) # Creates a mask for displaying the voronoi points # Currently erronious and produces NaN values # Currently erronious and produces NaN values in place # of true # matches, mask = edge.clean(clean_keys = clean_keys) # mask[mask] = kps.mask # edge.masks = ('voronoi', mask) keypoints = [] kps[kps.mask].apply(lambda x: keypoints.append((x['x'], x['y'])), axis=1) coords = source_gdf.geometry.apply(lambda x:scale(x, s, s).exterior.coords) for point in coords: keypoints = np.vstack((keypoints, point)) scaled_coords = np.array(scale(source_gdf.geometry[0], s, s).exterior.coords) keypoints = np.vstack((keypoints, scaled_coords)) vor = Voronoi(keypoints) voronoi_df = pd.DataFrame(data=kps, columns=['x', 'y', 'weight']) Loading @@ -203,8 +186,12 @@ def vor(graph, clean_keys, s=30): edge['weights']['vor_weight'] = voronoi_df['weight'] def compute_intersection(source, graph, clean_keys=[]): def compute_intersection(graph, source, clean_keys=[]): """ Computes the intersections of images in a graph based on the connections between nodes. The method takes every node in a graph, sees who is connected to it, then computes an intersection based on those connections. Parameters ---------- Loading @@ -219,7 +206,15 @@ def compute_intersection(source, graph, clean_keys=[]): Returns ------- intersection : intersection : dataframe 4 column dataframe of source_node, proj_node, geometry, and overlaps_all proj_gdf : dataframe 2 column dataframe of proj_geom, proj_node source_gdf : dataframe 2 column dataframe of geometry, source_node """ if type(source) is int: source = graph.node[source] Loading @@ -227,7 +222,7 @@ def compute_intersection(source, graph, clean_keys=[]): source_corners = source.geodata.xy_corners source_poly = Polygon(source_corners) source_gdf = gpd.GeoDataFrame({'source_geom': [source_poly], 'source_node': [source['node_id']]}).set_geometry('source_geom') source_gdf = gpd.GeoDataFrame({'geometry': [source_poly], 'source_node': [source['node_id']]}) proj_list = [] proj_nodes = [] Loading @@ -240,8 +235,16 @@ def compute_intersection(source, graph, clean_keys=[]): destination = graph.node[n] destination_corners = destination.geodata.xy_corners try: edge = graph.edge[source['node_id']][destination['node_id']] # If the source image has coordinate transformation data if (source.geodata.coordinate_transformation.this is not None) and \ (destination.geodata.coordinate_transformation.this is not None): proj_poly = Polygon(destination.geodata.xy_corners) # Else, use the homography transform to get an intersection of the two images else: # Will still need the check but the helper function will make these calls much easier to understand if source['node_id'] > destination['node_id']: kp2 = edge.get_keypoints('source', clean_keys=clean_keys, homogeneous=True) Loading @@ -250,13 +253,6 @@ def compute_intersection(source, graph, clean_keys=[]): kp2 = edge.get_keypoints('destination', clean_keys=clean_keys, homogeneous=True) kp1 = edge.get_keypoints('source', clean_keys=clean_keys, homogeneous=True) # If the source image has coordinate transformation data if (source.geodata.coordinate_transformation.this is None) and \ (destination.geodata.coordinate_transformation.this is None): proj_poly = Polygon(destination.geodata.xy_corners) # Else, use the homography transform to get an intersection of the two images else: H, mask = cv2.findHomography(kp2.values, kp1.values, cv2.RANSAC, 2.0) proj_corners = [] for c in destination_corners: Loading @@ -267,14 +263,19 @@ def compute_intersection(source, graph, clean_keys=[]): proj_corners.append((x, y)) proj_poly = Polygon(proj_corners) except: continue proj_list.append(proj_poly) proj_nodes.append(n) proj_gdf = gpd.GeoDataFrame({'proj_geom': proj_list, 'proj_node': proj_nodes}).set_geometry('proj_geom') proj_gdf = gpd.GeoDataFrame({'geometry': proj_list, 'proj_node': proj_nodes}) intersect_gdf = gpd.overlay(source_gdf, proj_gdf, how='intersection') intersect_gdf = intersect_gdf.rename(columns = {'geometry':'intersect_geom'}).set_geometry('intersect_geom') intersect_gdf['overlaps_all'] = intersect_gdf.geometry.apply(lambda x:proj_gdf.geometry.contains(scale(x, .9, .9)).all()) intersection = intersect_gdf.query('overlaps_all == True').set_geometry('intersect_geom') intersect_gdf['overlaps_all'] = intersect_gdf.geometry.apply(lambda z: proj_gdf.geometry.contains(scale(z, .9, .9)).all()) intersection = intersect_gdf.query('overlaps_all == True') if len(intersection) == 0: new_poly = unary_union(intersect_gdf.geometry) intersection = gpd.GeoDataFrame({'source_node': source['node_id'], 'geometry': new_poly, 'overlaps_all': True}) return intersection, proj_gdf, source_gdf
autocnet/cg/tests/test_cg.py +218 −0 Changes for autocnet/cg/tests/test_cg.py: 218 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -4,9 +4,17 @@ import unittest sys.path.insert(0, os.path.abspath('..')) import numpy as np import pandas as pd from .. import cg from osgeo import ogr from unittest.mock import Mock, MagicMock from plio.io import io_gdal from autocnet.graph.node import Node from autocnet.graph.network import CandidateGraph from autocnet.graph.edge import Edge from autocnet.utils.utils import array_to_poly class TestArea(unittest.TestCase): Loading @@ -33,3 +41,213 @@ class TestArea(unittest.TestCase): self.assertEqual(info[1], 400) self.assertAlmostEqual(info[0], 14.285714285) def test_voronoi_homography(self): source_keypoint_df = pd.DataFrame({'x': (15, 18, 18, 12, 12), 'y': (6, 10, 15, 15, 10)}) destination_keypoint_df = pd.DataFrame({'x': (5, 8, 8, 2, 2), 'y': (1, 5, 10, 10, 5)}) keypoint_matches = [[0, 0, 1, 0], [0, 1, 1, 1], [0, 2, 1, 2], [0, 3, 1, 3], [0, 4, 1, 4]] matches_df = pd.DataFrame(data=keypoint_matches, columns=['source_image', 'source_idx', 'destination_image', 'destination_idx']) # Source and Destination Node Setup source_node = MagicMock(spec=Node()) destination_node = MagicMock(spec=Node()) source_node.get_keypoint_coordinates = MagicMock(return_value=source_keypoint_df) destination_node.get_keypoint_coordinates = MagicMock(return_value=destination_keypoint_df) source_geodata = Mock(spec=io_gdal.GeoDataset) destination_geodata = Mock(spec=io_gdal.GeoDataset) source_node.geodata = source_geodata destination_node.geodata = destination_geodata source_node.geodata.coordinate_transformation.this = None destination_node.geodata.coordinate_transformation.this = None source_corners = [(0, 0), (20, 0), (20, 20), (0, 20)] destination_corners = [(0, 0), (20, 0), (20, 20), (0, 20)] source_node.geodata.xy_corners = source_corners destination_node.geodata.xy_corners = destination_corners # Edge Setup e = Edge(source=source_node, destination=destination_node) e.clean = MagicMock(return_value=(matches_df, None)) e.matches = matches_df def side_effect(node, clean_keys, **kwargs): if type(node) is str: node = node.lower() if isinstance(node, Node): node = node['node_id'] if node == "source" or node == "s" or node == source_node['node_id']: return e.source.get_keypoint_coordinates().copy(deep=True) if node == "destination" or node == "d" or node == destination_node['node_id']: return e.destination.get_keypoint_coordinates().copy(deep=True) my_dict_source = {'node_id':0} def getitem_source(name): return my_dict_source[name] my_dict_destination = {'node_id':1} def getitem_destination(name): return my_dict_destination[name] source_node.__getitem__.side_effect = getitem_source destination_node.__getitem__.side_effect = getitem_destination e.get_keypoints = MagicMock(side_effect=side_effect) cang = MagicMock(spec=CandidateGraph()) cang.nodes = MagicMock(return_value=([0, 1])) cang.node = [source_node, destination_node] cang.nodes_iter = MagicMock(return_value=([0, 1])) cang.neighbors = MagicMock(return_value=[1]) cang.edges = MagicMock(return_value=([(0, 1)])) cang.edge = {0: {1: e}, 1: {0: e}} cg.vor(cang, clean_keys=['fundamental']) self.assertAlmostEqual(e['weights']['vor_weight'][0], 22.5) self.assertAlmostEqual(e['weights']['vor_weight'][1], 26.25) self.assertAlmostEqual(e['weights']['vor_weight'][2], 37.5) self.assertAlmostEqual(e['weights']['vor_weight'][3], 37.5) self.assertAlmostEqual(e['weights']['vor_weight'][4], 26.25) def test_voronoi_coord(self): source_keypoint_df = pd.DataFrame({'x': (15, 18, 18, 12, 12), 'y': (6, 10, 15, 15, 10)}) destination_keypoint_df = pd.DataFrame({'x': (5, 8, 8, 2, 2), 'y': (1, 5, 10, 10, 5)}) keypoint_df = pd.DataFrame({'x': (15, 18, 18, 12, 12), 'y': (6, 10, 15, 15, 10)}) keypoint_matches = [[0, 0, 1, 0], [0, 1, 1, 1], [0, 2, 1, 2], [0, 3, 1, 3], [0, 4, 1, 4]] matches_df = pd.DataFrame(data=keypoint_matches, columns=['source_image', 'source_idx', 'destination_image', 'destination_idx']) source_node = MagicMock(spec=Node()) destination_node = MagicMock(spec=Node()) source_node.get_keypoint_coordinates = MagicMock(return_value=source_keypoint_df) destination_node.get_keypoint_coordinates = MagicMock(return_value=destination_keypoint_df) source_geodata = Mock(spec=io_gdal.GeoDataset) destination_geodata = Mock(spec=io_gdal.GeoDataset) source_node.geodata = source_geodata destination_node.geodata = destination_geodata e = Edge(source=source_node, destination = destination_node) e.clean = MagicMock(return_value=(matches_df, None)) e.matches = matches_df source_node = MagicMock(spec=Node()) destination_node = MagicMock(spec=Node()) cang = MagicMock(spec=CandidateGraph()) cang.nodes = MagicMock(return_value=([0, 1])) cang.node = [source_node, destination_node] cang.nodes_iter = MagicMock(return_value=([0, 1])) cang.neighbors = MagicMock(return_value=([0])) cang.edges = MagicMock(return_value=([(0, 1)])) cang.edge = {0: {1: e}, 1: {0: e}} source_node.get_keypoint_coordinates = MagicMock(return_value=keypoint_df) destination_node.get_keypoint_coordinates = MagicMock(return_value=keypoint_df) source_node['node_id'] = 0 destination_node['node_id'] = 1 def side_effect(node, clean_keys, **kwargs): if type(node) is str: node = node.lower() if isinstance(node, Node): node = node['node_id'] if node == "source" or node == "s" or node == source_node['node_id']: return e.source.get_keypoint_coordinates() if node == "destination" or node == "d" or node == destination_node['node_id']: return e.destination.get_keypoint_coordinates() e.get_keypoints = MagicMock(side_effect=side_effect) e.source = source_node e.destination = destination_node source_geodata = Mock(spec=io_gdal.GeoDataset) destination_geodata = Mock(spec=io_gdal.GeoDataset) e.source.geodata = source_geodata e.destination.geodata = destination_geodata source_corners = [(0, 0), (20, 0), (20, 20), (0, 20)] destination_corners = [(10, 5), (30, 5), (30, 25), (10, 25)] source_xy_extent = [(0, 20), (0, 20)] destination_xy_extent = [(10, 30), (5, 25)] source_poly = array_to_poly(source_corners) destination_poly = array_to_poly(destination_corners) vals = {(10, 5): (10, 5), (20, 5): (20, 5), (20, 20): (20, 20), (10, 20): (10, 20)} def latlon_to_pixel(i, j): return vals[(i, j)] my_dict_source = {'node_id': 0} def getitem_source(name): return my_dict_source[name] my_dict_destination = {'node_id': 1} def getitem_destination(name): return my_dict_destination[name] source_node.__getitem__.side_effect = getitem_source destination_node.__getitem__.side_effect = getitem_destination e.source.geodata.latlon_to_pixel = MagicMock(side_effect=latlon_to_pixel) e.destination.geodata.latlon_to_pixel = MagicMock(side_effect=latlon_to_pixel) e.source.geodata.footprint = source_poly e.source.geodata.xy_corners = source_corners e.source.geodata.xy_extent = source_xy_extent e.destination.geodata.footprint = destination_poly e.destination.geodata.xy_corners = destination_corners e.destination.geodata.xy_extent = destination_xy_extent cg.vor(cang, clean_keys=['fundamental']) self.assertAlmostEqual(e['weights']['vor_weight'][0], 22.5) self.assertAlmostEqual(e['weights']['vor_weight'][1], 26.25) self.assertAlmostEqual(e['weights']['vor_weight'][2], 37.5) self.assertAlmostEqual(e['weights']['vor_weight'][3], 37.5) self.assertAlmostEqual(e['weights']['vor_weight'][4], 26.25)
autocnet/graph/network.py +0 −2 Changes for autocnet/graph/network.py: 0 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -701,7 +701,6 @@ class CandidateGraph(nx.Graph): : list A list of lists of node ids that make up maximum complete subgraphs of the given graph """ if node_id is not None: return list(nx.cliques_containing_node(self, nodes=node_id)) else: Loading @@ -718,5 +717,4 @@ class CandidateGraph(nx.Graph): Strings used to apply masks to omit correspondences """ vor(self, clean_keys)
autocnet/graph/tests/test_edge.py +0 −121 Changes for autocnet/graph/tests/test_edge.py: 0 added lines, 121 removed lines. Original line number Diff line number Diff line Loading @@ -128,124 +128,3 @@ class TestEdge(unittest.TestCase): self.assertRaises(AttributeError, cg.edge[0][1].coverage) self.assertEqual(e.coverage(), 0.3) def test_voronoi_transform(self): keypoint_df = pd.DataFrame({'x': (15, 18, 18, 12, 12), 'y': (5, 10, 15, 15, 10)}) keypoint_matches = [[0, 0, 1, 0], [0, 1, 1, 1], [0, 2, 1, 2], [0, 3, 1, 3], [0, 4, 1, 4]] matches_df = pd.DataFrame(data=keypoint_matches, columns=['source_image', 'source_idx', 'destination_image', 'destination_idx']) e = edge.Edge() e.clean = MagicMock(return_value=(matches_df, None)) e.matches = matches_df source_node = MagicMock(spec=node.Node()) destination_node = MagicMock(spec=node.Node()) source_node.get_keypoint_coordinates = MagicMock(return_value=keypoint_df) destination_node.get_keypoint_coordinates = MagicMock(return_value=keypoint_df) e.source = source_node e.destination = destination_node source_geodata = Mock(spec=io_gdal.GeoDataset) destination_geodata = Mock(spec=io_gdal.GeoDataset) e.source.geodata = source_geodata e.destination.geodata = destination_geodata source_corners = [(0, 0), (20, 0), (20, 20), (0, 20)] destination_corners = [(10, 5), (30, 5), (30, 25), (10, 25)] source_poly = array_to_poly(source_corners) destination_poly = array_to_poly(destination_corners) def latlon_to_pixel(i, j): return vals[(i, j)] e.source.geodata.latlon_to_pixel = MagicMock(side_effect=latlon_to_pixel) e.destination.geodata.latlon_to_pixel = MagicMock(side_effect=latlon_to_pixel) e.source.geodata.footprint = source_poly e.source.geodata.xy_corners = source_corners e.destination.geodata.footprint = destination_poly e.destination.geodata.xy_corners = destination_corners vals = {(10, 5): (10, 5), (20, 5): (20, 5), (20, 20): (20, 20), (10, 20): (10, 20)} weights = pd.DataFrame({"vor_weights": (19, 28, 37.5, 37.5, 28)}) e.compute_weights(clean_keys=[]) k = 0 for i in e.matches['vor_weights']: self.assertAlmostEquals(i, weights['vor_weights'][k]) k += 1 def test_voronoi_homography(self): source_keypoint_df = pd.DataFrame({'x': (15, 18, 18, 12, 12), 'y': (5, 10, 15, 15, 10)}) destination_keypoint_df = pd.DataFrame({'x': (5, 8, 8, 2, 2), 'y': (0, 5, 10, 10, 5)}) keypoint_matches = [[0, 0, 1, 0], [0, 1, 1, 1], [0, 2, 1, 2], [0, 3, 1, 3], [0, 4, 1, 4]] matches_df = pd.DataFrame(data = keypoint_matches, columns=['source_image', 'source_idx', 'destination_image', 'destination_idx']) e = edge.Edge() e.clean = MagicMock(return_value=(matches_df, None)) e.matches = matches_df source_node = MagicMock(spec=node.Node()) destination_node = MagicMock(spec=node.Node()) source_node.get_keypoint_coordinates = MagicMock(return_value=source_keypoint_df) destination_node.get_keypoint_coordinates = MagicMock(return_value=destination_keypoint_df) e.source = source_node e.destination = destination_node source_geodata = Mock(spec=io_gdal.GeoDataset) destination_geodata = Mock(spec=io_gdal.GeoDataset) e.source.geodata = source_geodata e.destination.geodata = destination_geodata source_corners = [(0, 0), (20, 0), (20, 20), (0, 20)] destination_corners = [(0, 0), (20, 0), (20, 20), (0, 20)] e.source.geodata.coordinate_transformation.this = None e.destination.geodata.coordinate_transformation.this = None e.source.geodata.xy_corners = source_corners e.destination.geodata.xy_corners = destination_corners weights = pd.DataFrame({"vor_weights": (19, 28, 37.5, 37.5, 28)}) e.compute_weights(clean_keys=[]) k = 0 for i in e.matches['vor_weights']: self.assertAlmostEquals(i, weights['vor_weights'][k]) k += 1
autocnet/graph/tests/test_network.py +15 −0 Changes for autocnet/graph/tests/test_network.py: 15 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -165,3 +165,18 @@ def test_apply_func_to_edges(graph): assert not graph[0][2].masks['symmetry'].all() assert not graph[0][1].masks['symmetry'].all() def test_cliques(): graph = network.CandidateGraph() # for i in range(0, 8): # graph.add_node(i) graph.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7]) graph.add_edges_from([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3), (3, 4), (3, 7), (4, 5), (4, 6), (5, 6)]) cliques = graph.compute_cliques() limited_cliques = graph.compute_cliques(node_id=3) print(cliques, limited_cliques) assert len(cliques) == 4 assert len(limited_cliques) == 3 assert cliques[2][0] == 3 assert cliques[2][1] == 7