Commit c5ff01ef authored by Jay's avatar Jay
Browse files

Merge remote-tracking branch 'upstream/dev'

parents c292e65f 0cd9f1f5
Loading
Loading
Loading
Loading
+117 −0
Changes for autocnet/cg/cg.py: 117 added lines, 0 removed lines.
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):
@@ -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
+3 −0
Changes for autocnet/examples/Apollo15/cube_adjacency.json: 3 added lines, 0 removed lines.
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"]}
+34 −6
Changes for autocnet/graph/edge.py: 34 added lines, 6 removed lines.
Original line number Diff line number Diff line
@@ -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
@@ -49,6 +50,7 @@ class Edge(dict, MutableMapping):
        self.matches = None
        self._subpixel_offsets = None

        self.provenance = {}
        self.weight = {}

        self._observers = set()
@@ -63,6 +65,17 @@ class Edge(dict, MutableMapping):
        Available Masks: {}
        """.format(self.source, self.destination, self.masks)

    def __getitem__(self, item):
        attribute_dict = {'source': self.source,
                          'destination': self.destination,
                          'masks': self.masks,
                          'provenance': self.provenance,
                          'weight': self.weight}
        if item in attribute_dict.keys():
            return attribute_dict[item]
        else:
            return super(Edge, self).__getitem__(item)

    @property
    def masks(self):
        mask_lookup = {'fundamental': 'fundamental_matrix',
@@ -688,18 +701,15 @@ class Edge(dict, MutableMapping):
    def plot_decomposition(self, *args, **kwargs): #pragma: no cover
        return plot_edge_decomposition(self, *args, **kwargs)

    def clean(self, clean_keys, pid=None):
    def clean(self, clean_keys):
        """
        Given a list of clean keys and a provenance id compute the
        mask of valid matches
        Given a list of clean keys compute the mask of valid
        matches

        Parameters
        ----------
        clean_keys : list
                     of columns names (clean keys)
        pid : int
              The provenance id of the parameter set to be cleaned.
              Defaults to the last run.

        Returns
        -------
@@ -768,3 +778,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)
+19 −1
Changes for autocnet/graph/network.py: 19 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -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):
@@ -569,6 +569,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.
+36 −4
Changes for autocnet/graph/node.py: 36 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -76,11 +76,29 @@ class Node(dict, MutableMapping):
        """.format(self.node_id, self.image_name, self.image_path,
                   self.nkeypoints, self.masks, self.__class__)

    def __getitem__(self, item):
        attribute_dict = {'image_name': self.image_name,
                          'image_path': self.image_path,
                          'geodata': self.geodata,
                          'keypoints': self.keypoints,
                          'nkeypoints': self.nkeypoints,
                          'descriptors': self.descriptors,
                          'masks': self.masks,
                          'isis_serial': self.isis_serial}
        if item in attribute_dict.keys():
            return attribute_dict[item]
        else:
            return super(Node, self).__getitem__(item)

    @property
    def geodata(self):
        if not getattr(self, '_geodata', None):
        if not getattr(self, '_geodata', None) and self.image_path is not None:
            self._geodata = GeoDataset(self.image_path)
            return self._geodata
        if hasattr(self, '_geodata'):
            return self._geodata
        else:
            return None

    @property
    def masks(self):
@@ -127,6 +145,20 @@ class Node(dict, MutableMapping):
        else:
            return 0

    @property
    def keypoints(self):
        if hasattr(self, '_keypoints'):
            return self._keypoints.copy()
        else:
            return None

    @property
    def descriptors(self):
        if hasattr(self, '_descriptors'):
            return np.copy(self._descriptors)
        else:
            return None

    def coverage(self):
        """
        Determines the area of keypoint coverage
@@ -239,7 +271,7 @@ class Node(dict, MutableMapping):
                 kwargs passed to autocnet.feature_extractor.extract_features

        """
        self._keypoints, self.descriptors = fe.extract_features(array, **kwargs)
        self._keypoints, self._descriptors = fe.extract_features(array, **kwargs)

    def load_features(self, in_path):
        """
@@ -256,7 +288,7 @@ class Node(dict, MutableMapping):
        else:
            hdf = in_path

        self.descriptors = hdf['{}/descriptors'.format(self.image_name)][:]
        self._descriptors = hdf['{}/descriptors'.format(self.image_name)][:]
        raw_kps = hdf['{}/keypoints'.format(self.image_name)][:]
        index = raw_kps['index']
        clean_kps = utils.remove_field_name(raw_kps, 'index')
@@ -298,7 +330,7 @@ class Node(dict, MutableMapping):

        try:
            hdf.create_dataset('{}/descriptors'.format(self.image_name),
                               data=self.descriptors,
                               data=self._descriptors,
                               compression=io_hdf.DEFAULT_COMPRESSION,
                               compression_opts=io_hdf.DEFAULT_COMPRESSION_VALUE)
            hdf.create_dataset('{}/keypoints'.format(self.image_name),
Loading