Loading autocnet/cg/cg.py +117 −0 Original line number Diff line number Diff line import pandas as pd import numpy as np from scipy.spatial import ConvexHull from scipy.spatial import Voronoi import cv2 from autocnet.utils import utils def convex_hull_ratio(points, ideal_area): Loading Loading @@ -99,3 +104,115 @@ def get_area(poly1, poly2): """ intersection_area = poly1.Intersection(poly2).GetArea() return intersection_area def vor(edge, clean_keys=[], s=30): """ Creates a voronoi diagram for an edge using either the coordinate transformation or using the homography between source and destination. The coordinate transformation uses the footprint of source and destination to calculate an intersection between the two images, then transforms the vertices of the intersection back into pixel space. If a coordinate transform does not exist, use the homography to project the destination image 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. Parameters ---------- edge : object An edge object clean_keys : list 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 """ source_corners = edge.source.geodata.xy_corners destination_corners = edge.destination.geodata.xy_corners matches, _ = edge.clean(clean_keys=clean_keys) source_keypoints_pd = edge.source.get_keypoint_coordinates(index=matches['source_idx'], homogeneous=True) destination_keypoints_pd = edge.destination.get_keypoint_coordinates(index=matches['destination_idx'], homogeneous=True) if edge.source.geodata.coordinate_transformation.this is not None: source_footprint_poly = edge.source.geodata.footprint destination_footprint_poly = edge.destination.geodata.footprint intersection_poly = destination_footprint_poly.Intersection(source_footprint_poly) intersection_geom = intersection_poly.GetGeometryRef(0) intersect_points = intersection_geom.GetPoints() intersection_points = [edge.source.geodata.latlon_to_pixel(lat, lon) for lat, lon in intersect_points] else: H, mask = cv2.findHomography(destination_keypoints_pd.values, source_keypoints_pd.values, cv2.RANSAC, 2.0) proj_corners = [] for c in destination_corners: x, y, h = utils.reproj_corner(H, c) x /= h y /= h h /= h proj_corners.append((x, y)) orig_poly = utils.array_to_poly(source_corners) proj_poly = utils.array_to_poly(proj_corners) intersection_poly = orig_poly.Intersection(proj_poly) intersection_geom = intersection_poly.GetGeometryRef(0) intersection_points = intersection_geom.GetPoints() centroid = intersection_poly.Centroid().GetPoint() voronoi_df = pd.DataFrame(data=source_keypoints_pd, columns=["x", "y", "vor_weights"]) voronoi_df["x"] = source_keypoints_pd['x'] voronoi_df["y"] = source_keypoints_pd['y'] keypoints = np.asarray(source_keypoints_pd) inters = np.empty((len(intersection_points), 2)) for g, (i, j) in enumerate(intersection_points): scaledx, scaledy = utils.scale_point((i, j), centroid, s) point = np.array([scaledx, scaledy]) inters[g] = point keypoints = np.vstack((keypoints[:, :2], inters)) vor = Voronoi(keypoints) poly_array = [] for i, region in enumerate(vor.regions): region_point = vor.points[np.argwhere(vor.point_region==i)] if -1 not in region: polygon_points = [vor.vertices[i] for i in region] if len(polygon_points) != 0: polygon = utils.array_to_poly(polygon_points) intersection = polygon.Intersection(intersection_poly) poly_array = np.append(poly_array, intersection) polygon_area = intersection.GetArea() voronoi_df.loc[(voronoi_df["x"] == region_point[0][0][0]) & (voronoi_df["y"] == region_point[0][0][1]), 'vor_weights'] = polygon_area return vor, voronoi_df autocnet/examples/Apollo15/cube_adjacency.json 0 → 100644 +3 −0 Original line number Diff line number Diff line {"AS15-M-0297_crop.cub": ["AS15-M-0298_crop.cub", "AS15-M-0299_crop.cub"], "AS15-M-0298_crop.cub": ["AS15-M-0299_crop.cub", "AS15-M-0297_crop.cub"], "AS15-M-0299_crop.cub": ["AS15-M-0298_crop.cub", "AS15-M-0297_crop.cub"]} autocnet/graph/edge.py +19 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ from collections import MutableMapping import numpy as np import pandas as pd from scipy.spatial.distance import cdist from autocnet.utils import utils Loading Loading @@ -775,3 +776,21 @@ class Edge(dict, MutableMapping): total_overlap_coverage = (convex_poly.GetArea()/intersection_area) return total_overlap_coverage def compute_weights(self, clean_keys, **kwargs): """ Computes a voronoi diagram for the overlap between two images then gets the area of each polygon resulting in a voronoi weight. These weights are then appended to the matches dataframe. Parameters ---------- clean_keys : list Of strings used to apply masks to omit correspondences """ if self.matches is None: 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 +19 −1 Original line number Diff line number Diff line Loading @@ -14,7 +14,7 @@ from plio.io.io_gdal import GeoDataset from autocnet.graph import markov_cluster from autocnet.graph.edge import Edge from autocnet.graph.node import Node from autocnet.vis.graph_view import plot_graph from autocnet.vis.graph_view import plot_graph, cluster_plot class CandidateGraph(nx.Graph): Loading Loading @@ -570,6 +570,24 @@ class CandidateGraph(nx.Graph): """ return plot_graph(self, ax=ax, **kwargs) def plot_cluster(self, ax=None, **kwargs): """ Plot the graph based on the clusters generated by the markov clustering algorithm Parameters ---------- ax : object A MatPlotLib axes object. Returns ------- ax : object A MatPlotLib axes object. """ return cluster_plot(self, ax, **kwargs) def create_edge_subgraph(self, edges): """ Create a subgraph using a list of edges. Loading autocnet/graph/tests/test_edge.py +126 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ from plio.io import io_gdal from autocnet.examples import get_path from autocnet.graph.network import CandidateGraph from autocnet.utils.utils import array_to_poly from .. import edge from .. import node Loading Loading @@ -129,3 +130,128 @@ 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 Loading
autocnet/cg/cg.py +117 −0 Original line number Diff line number Diff line import pandas as pd import numpy as np from scipy.spatial import ConvexHull from scipy.spatial import Voronoi import cv2 from autocnet.utils import utils def convex_hull_ratio(points, ideal_area): Loading Loading @@ -99,3 +104,115 @@ def get_area(poly1, poly2): """ intersection_area = poly1.Intersection(poly2).GetArea() return intersection_area def vor(edge, clean_keys=[], s=30): """ Creates a voronoi diagram for an edge using either the coordinate transformation or using the homography between source and destination. The coordinate transformation uses the footprint of source and destination to calculate an intersection between the two images, then transforms the vertices of the intersection back into pixel space. If a coordinate transform does not exist, use the homography to project the destination image 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. Parameters ---------- edge : object An edge object clean_keys : list 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 """ source_corners = edge.source.geodata.xy_corners destination_corners = edge.destination.geodata.xy_corners matches, _ = edge.clean(clean_keys=clean_keys) source_keypoints_pd = edge.source.get_keypoint_coordinates(index=matches['source_idx'], homogeneous=True) destination_keypoints_pd = edge.destination.get_keypoint_coordinates(index=matches['destination_idx'], homogeneous=True) if edge.source.geodata.coordinate_transformation.this is not None: source_footprint_poly = edge.source.geodata.footprint destination_footprint_poly = edge.destination.geodata.footprint intersection_poly = destination_footprint_poly.Intersection(source_footprint_poly) intersection_geom = intersection_poly.GetGeometryRef(0) intersect_points = intersection_geom.GetPoints() intersection_points = [edge.source.geodata.latlon_to_pixel(lat, lon) for lat, lon in intersect_points] else: H, mask = cv2.findHomography(destination_keypoints_pd.values, source_keypoints_pd.values, cv2.RANSAC, 2.0) proj_corners = [] for c in destination_corners: x, y, h = utils.reproj_corner(H, c) x /= h y /= h h /= h proj_corners.append((x, y)) orig_poly = utils.array_to_poly(source_corners) proj_poly = utils.array_to_poly(proj_corners) intersection_poly = orig_poly.Intersection(proj_poly) intersection_geom = intersection_poly.GetGeometryRef(0) intersection_points = intersection_geom.GetPoints() centroid = intersection_poly.Centroid().GetPoint() voronoi_df = pd.DataFrame(data=source_keypoints_pd, columns=["x", "y", "vor_weights"]) voronoi_df["x"] = source_keypoints_pd['x'] voronoi_df["y"] = source_keypoints_pd['y'] keypoints = np.asarray(source_keypoints_pd) inters = np.empty((len(intersection_points), 2)) for g, (i, j) in enumerate(intersection_points): scaledx, scaledy = utils.scale_point((i, j), centroid, s) point = np.array([scaledx, scaledy]) inters[g] = point keypoints = np.vstack((keypoints[:, :2], inters)) vor = Voronoi(keypoints) poly_array = [] for i, region in enumerate(vor.regions): region_point = vor.points[np.argwhere(vor.point_region==i)] if -1 not in region: polygon_points = [vor.vertices[i] for i in region] if len(polygon_points) != 0: polygon = utils.array_to_poly(polygon_points) intersection = polygon.Intersection(intersection_poly) poly_array = np.append(poly_array, intersection) polygon_area = intersection.GetArea() voronoi_df.loc[(voronoi_df["x"] == region_point[0][0][0]) & (voronoi_df["y"] == region_point[0][0][1]), 'vor_weights'] = polygon_area return vor, voronoi_df
autocnet/examples/Apollo15/cube_adjacency.json 0 → 100644 +3 −0 Original line number Diff line number Diff line {"AS15-M-0297_crop.cub": ["AS15-M-0298_crop.cub", "AS15-M-0299_crop.cub"], "AS15-M-0298_crop.cub": ["AS15-M-0299_crop.cub", "AS15-M-0297_crop.cub"], "AS15-M-0299_crop.cub": ["AS15-M-0298_crop.cub", "AS15-M-0297_crop.cub"]}
autocnet/graph/edge.py +19 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ from collections import MutableMapping import numpy as np import pandas as pd from scipy.spatial.distance import cdist from autocnet.utils import utils Loading Loading @@ -775,3 +776,21 @@ class Edge(dict, MutableMapping): total_overlap_coverage = (convex_poly.GetArea()/intersection_area) return total_overlap_coverage def compute_weights(self, clean_keys, **kwargs): """ Computes a voronoi diagram for the overlap between two images then gets the area of each polygon resulting in a voronoi weight. These weights are then appended to the matches dataframe. Parameters ---------- clean_keys : list Of strings used to apply masks to omit correspondences """ if self.matches is None: 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 +19 −1 Original line number Diff line number Diff line Loading @@ -14,7 +14,7 @@ from plio.io.io_gdal import GeoDataset from autocnet.graph import markov_cluster from autocnet.graph.edge import Edge from autocnet.graph.node import Node from autocnet.vis.graph_view import plot_graph from autocnet.vis.graph_view import plot_graph, cluster_plot class CandidateGraph(nx.Graph): Loading Loading @@ -570,6 +570,24 @@ class CandidateGraph(nx.Graph): """ return plot_graph(self, ax=ax, **kwargs) def plot_cluster(self, ax=None, **kwargs): """ Plot the graph based on the clusters generated by the markov clustering algorithm Parameters ---------- ax : object A MatPlotLib axes object. Returns ------- ax : object A MatPlotLib axes object. """ return cluster_plot(self, ax, **kwargs) def create_edge_subgraph(self, edges): """ Create a subgraph using a list of edges. Loading
autocnet/graph/tests/test_edge.py +126 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ from plio.io import io_gdal from autocnet.examples import get_path from autocnet.graph.network import CandidateGraph from autocnet.utils.utils import array_to_poly from .. import edge from .. import node Loading Loading @@ -129,3 +130,128 @@ 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