Commit d3d8991b authored by jay's avatar jay
Browse files

upstream merge & updates to tests

parent 1bec1bfe
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -65,7 +65,7 @@ install:
  - python condaci.py setup

script:
  - pytest --cov=autocnet
  - pytest autocnet functional_tests

after_success:
  # Upload to anaconda and push to coveralls
+31 −103
Original line number Diff line number Diff line
import os
import sys
import itertools

from time import gmtime, strftime

import pytest

from unittest.mock import Mock, MagicMock


import numpy as np
from unittest.mock import MagicMock
import geopandas as gpd
import pandas as pd
from shapely.geometry import Polygon

sys.path.insert(0, os.path.abspath('..'))

from autocnet.control import control


@pytest.fixture
def controlnetwork_data():
    df = pd.DataFrame([[0, 0.0, 0.0, (0.0, 1.0), 0, 0.0, 0.0],
                             [0, 1.0, 0.0, (0.0, 1.0), 0, 0.0, 0.0],
                             [1, 0.0, 1.0, (0.0, 1.0), 1, 0.0, 0.0],
                             [1, 1.0, 1.0, (0.0, 1.0), 1, 0.0, 0.0],
                             [2, 0.0, 2.0, (0.0, 1.0), 2, 0.0, 0.0],
                             [2, 1.0, 2.0, (0.0, 1.0), 2, 0.0, 0.0],
                             [3, 0.0, 3.0, (0.0, 1.0), 3, 0.0, 0.0],
                             [3, 1.0, 3.0, (0.0, 1.0), 3, 0.0, 0.0],
                             [4, 0.0, 4.0, (0.0, 1.0), 4, 0.0, 0.0],
                             [4, 1.0, 4.0, (0.0, 1.0), 4, 0.0, 0.0],
                             [5, 0.0, 5.0, (0.0, 1.0), 5, 0.0, 0.0],
                             [5, 1.0, 5.0, (0.0, 1.0), 5, 0.0, 0.0],
                             [6, 0.0, 6.0, (0.0, 1.0), 6, 0.0, 0.0],
                             [6, 1.0, 6.0, (0.0, 1.0), 6, 0.0, 0.0],
                             [7, 0.0, 7.0, (0.0, 1.0), 7, 0.0, 0.0],
                             [7, 1.0, 7.0, (0.0, 1.0), 7, 0.0, 0.0],
                             [0, 2.0, 0.0, (0.0, 2.0), 0, 0.0, 0.0],
                             [1, 2.0, 1.0, (0.0, 2.0), 1, 0.0, 0.0],
                             [2, 2.0, 2.0, (0.0, 2.0), 2, 0.0, 0.0],
                             [3, 2.0, 3.0, (0.0, 2.0), 3, 0.0, 0.0],
                             [4, 2.0, 4.0, (0.0, 2.0), 4, 0.0, 0.0],
                             [5, 2.0, 5.0, (0.0, 2.0), 5, 0.0, 0.0],
                             [8, 0.0, 8.0, (0.0, 2.0), 6, 0.0, 0.0],
                             [8, 2.0, 8.0, (0.0, 2.0), 6, 0.0, 0.0],
                             [9, 0.0, 9.0, (0.0, 2.0), 7, 0.0, 0.0],
                             [9, 2.0, 9.0, (0.0, 2.0), 7, 0.0, 0.0],
                             [10, 1.0, 8.0, (1.0, 2.0), 6, 0.0, 0.0],
                             [10, 2.0, 6.0, (1.0, 2.0), 6, 0.0, 0.0],
                             [11, 1.0, 9.0, (1.0, 2.0), 7, 0.0, 0.0],
                             [11, 2.0, 7.0, (1.0, 2.0), 7, 0.0, 0.0]],
                            columns=['point_id', 'image_index', 'keypoint_index',
                                     'edge', 'match_idx', 'x', 'y'])

    df.index.name = 'measure_id'

    #Fix types
    df['point_id'] = df['point_id'].astype(object)
    df['match_idx'] = df['match_idx'].astype(object)

    return df

@pytest.fixture
def candidategraph():
    edges = [(0,1), (0,2), (1,2)]


    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 the candidate graph
    cg = MagicMock()
    cg.get_matches = MagicMock(return_value=matches)

    return cg

@pytest.fixture()
def controlnetwork(controlnetwork_data):
    cn = control.ControlNetwork()
    cn.data = controlnetwork_data
    # Patching data this way does NOT update the internal _measure_id and _point_id attributes
    return cn

@pytest.fixture()
def bad_controlnetwork(controlnetwork_data):
    cn = control.ControlNetwork()
    cn.data = controlnetwork_data
    # Since the data is being patched in, fix the measure counter
    cn._measure_id = len(cn.data) + 1
    # Add a duplicate measure in image 0 to point 0
    cn.add_measure((0,11), (0,1), 2, [1,1], point_id=0)
    return cn
import os
import sys
sys.path.insert(0, '..')
from .. import control

def test_fromcandidategraph(candidategraph, controlnetwork_data):#, controlnetwork):
    matches = candidategraph.get_matches()
@@ -136,5 +42,27 @@ def test_bad_validate_points(bad_controlnetwork):
    assert bad_controlnetwork.validate_points().iloc[0] == False
    assert bad_controlnetwork.validate_points().iloc[1:].all()

def test_potential_overlap(controlnetwork):
    pass
def test_identify_potential_overlaps(controlnetwork, candidategraph):
    res = control.identify_potential_overlaps(candidategraph,
                                              controlnetwork,
                                              overlap=False)

    assert res.equals(pd.Series([(2,), (2,),
                                 (1,), (1,),
                                 (0,), (0,)],
                                 index=[6,7,8,9,10,11]))

def test_potential_overlap(controlnetwork, candidategraph):
    # Patch in an intersection check so that all points intersect all geoms
    candidategraph.create_node_subgraph = MagicMock(return_value=candidategraph)
    coords = [(-1., -1.), (-1., 1.), (1., 1.), (1., -1.), (-1., -1.)]
    poly = gpd.GeoSeries(Polygon(coords))
    candidategraph.compute_intersection = MagicMock(return_value=(poly, 0))
    res = control.identify_potential_overlaps(candidategraph,
                                              controlnetwork,
                                              overlap=True)

    assert res.equals(pd.Series([(2,), (2,),
                                 (1,), (1,),
                                 (0,), (0,)],
                                 index=[6,7,8,9,10,11]))
+2 −39
Original line number Diff line number Diff line
@@ -67,33 +67,17 @@ class Edge(dict, MutableMapping):
            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

    """@property
    def masks(self):
        mask_lookup = {'fundamental': 'fundamental_matrix'}
        if not hasattr(self, '_masks'):
            if isinstance(self.matches, pd.DataFrame):
                self._masks = pd.DataFrame(True, columns=['symmetry'],
                                           index=self.matches.index)
            else:
                self._masks = pd.DataFrame()
        return self._masks

    @masks.setter
    def masks(self, v):
        column_name = v[0]
        boolean_mask = v[1]
        self.masks[column_name] = boolean_mask"""

    def match(self, k=2, **kwargs):

        """
@@ -136,27 +120,6 @@ class Edge(dict, MutableMapping):
    def decompose_and_match(*args, **kwargs):
        pass

    """
    def extract_subset(self, *args, **kwargs):
        self.compute_overlap()

        # Extract the source
        minx, maxx, miny, maxy = self['source_mbr']
        xystart = (minx, miny)
        pixels=[minx, miny, maxx-minx, maxy-miny]
        node = self.source
        arr = node.geodata.read_array(pixels=pixels)
        node.extract_features(arr, xystart=xystart, *args, **kwargs)

        # Extract the destination
        minx, maxx, miny, maxy = self['destin_mbr']
        xystart = (minx, miny)
        pixels=[minx, miny, maxx-minx, maxy-miny]
        node = self.destination
        arr = node.geodata.read_array(pixels=pixels)
        node.extract_features(arr, xystart=xystart, *args, **kwargs)
    """

    def overlap_check(self):
        """Creates a mask for matches on the overlap"""
        if not (self["source_mbr"] and self["destin_mbr"]):
+1 −3
Original line number Diff line number Diff line
@@ -860,8 +860,6 @@ class CandidateGraph(nx.Graph):
    #     except:
    #         return s.edges_iter([self.node[node]['image_path'] for node in nbunch], data=data)



    def subgraph_from_matches(self):
        """
        Returns a sub-graph where all edges have matches.
@@ -1103,7 +1101,7 @@ class CandidateGraph(nx.Graph):

    def identify_potential_overlaps(self, **kwargs):
        cc = control.identify_potential_overlaps(self, self.controlnetwork, **kwargs)
        print(cc)
        return cc

    def to_isis(self, outname, *args, **kwargs):
        serials = self.serials()
+2 −144
Original line number Diff line number Diff line
@@ -66,8 +66,6 @@ class Node(dict, MutableMapping):
        self['node_id'] = node_id
        self['hash'] = image_name
        self._mask_arrays = {}
        self.point_to_correspondence = defaultdict(set)
        self.point_to_correspondence_df = None
        self.descriptors = None
        self.keypoints = pd.DataFrame()
        self.masks = pd.DataFrame()
@@ -116,26 +114,13 @@ class Node(dict, MutableMapping):
        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
    """
    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):
@@ -147,31 +132,6 @@ class Node(dict, MutableMapping):
        else:
            return None

    """    @property
    def masks(self):
        mask_lookup = {'suppression': 'suppression'}

        if self.keypoints is None:
            warnings.warn('Keypoints have not been extracted')
            return

        if not hasattr(self, '_masks'):
            self._masks = pd.DataFrame(index=self.keypoints.index)

        # If the mask is coming form another object that tracks
        # state, dynamically draw the mask from the object.
        for c in self._masks.columns:
            if c in mask_lookup:
                self._masks[c] = getattr(self, mask_lookup[c]).mask
        return self._masks

    @masks.setter
    def masks(self, v):
        column_name = v[0]
        boolean_mask = v[1]
        self.masks[column_name] = boolean_mask
    """

    @property
    def footprint(self):
        if not getattr(self, '_footprint', None):
@@ -461,108 +421,6 @@ class Node(dict, MutableMapping):
        io_keypoints.to_npy(self.keypoints, self.descriptors,
                            out_path)

    def group_correspondences(self, cg, *args, deepen=False, **kwargs):
        """

        Parameters
        ----------
        cg : object
             The graph object this node is a member of

        deepen : bool
                 If True, attempt to punch matches through to all incident edges.  Default: False
        """
        node = self['node_id']
        # Get the edges incident to the current node
        incident_edges = set(cg.edges(node)).intersection(set(cg.edges()))

        # If this node is free floating, ignore it.
        if not incident_edges:
             # TODO: Add dangling correspondences to control network anyway.  Subgraphs handle this segmentation if req.
            return

        try:
            clean_keys = kwargs['clean_keys']
        except:
            clean_keys = []

        # Grab all the incident edge matches and concatenate into a group match set.
        # All share the same source node
        edge_matches = []
        for e in incident_edges:
            edge = cg[e[0]][e[1]]
            matches, mask = edge.clean(clean_keys=clean_keys)
            # Add a depth mask that initially mirrors the fundamental mask
            edge_matches.append(matches)
        d = pd.concat(edge_matches)

        # Counter for point identifiers
        pid = 0

        # Iterate through all of the correspondences and attempt to add additional correspondences using
        # the epipolar constraint
        for idx, g in d.groupby('source_idx'):
            # Pull the source index to be used as the search
            source_idx = g['source_idx'].values[0]

            # Add the point object onto the node
            point = Point(pid)
            #print(g[['source_image', 'destination_image']])
            covered_edges = list(map(tuple, g[['source_image', 'destination_image']].values))
            s = g['source_image'].iat[0]
            d = g['destination_image'].iat[0]
            # The reference edge that we are deepening with
            ab = cg.edge[covered_edges[0][0]][covered_edges[0][1]]

            # Get the coordinates of the search correspondence
            ab_keypoints = ab.source.get_keypoint_coordinates(index=g['source_idx'])
            ab_x = None

            for j, (r_idx, r) in enumerate(g.iterrows()):
                kp = ab_keypoints.iloc[j].values

                # Homogenize the coord used for epipolar projection
                if ab_x is None:
                    ab_x = np.array([kp[0], kp[1], 1.])

                kpd = ab.destination.get_keypoint_coordinates(index=g['destination_idx']).values[0]
                # Add the existing source and destination correspondences
                self.point_to_correspondence[point].add((r['source_image'],
                                                                  Correspondence(r['source_idx'],
                                                                                 kp[0],
                                                                                 kp[1],
                                                                                 serial=self.isis_serial)))
                self.point_to_correspondence[point].add((r['destination_image'],
                                                                  Correspondence(r['destination_idx'],
                                                                                 kpd[0],
                                                                                 kpd[1],
                                                                                 serial=cg.node[r['destination_image']].isis_serial)))

            # If the user wants to punch correspondences through
            if deepen:
                search_edges = incident_edges.difference(set(covered_edges))
                for search_edge in search_edges:
                    bc = cg.edge[search_edge[0]][search_edge[1]]
                    coords, idx = deepen_correspondences(ab_x, bc, source_idx)

                    if coords is not None:
                        cg.node[node].point_to_correspondence[point].add((search_edge[1],
                                                                          Correspondence(idx,
                                                                                         coords[0],
                                                                                         coords[1],
                                                                                         serial=cg.node[search_edge[1]].isis_serial)))

            pid += 1

        # Convert the dict to a dataframe
        data = []
        for k, measures in self.point_to_correspondence.items():
            for image_id, m in measures:
                data.append((k.point_id, k.point_type, m.serial, m.measure_type, m.x, m.y, image_id))

        columns = ['point_id', 'point_type', 'serialnumber', 'measure_type', 'x', 'y', 'node_id']
        self.point_to_correspondence_df = pd.DataFrame(data, columns=columns)

    def coverage_ratio(self, clean_keys=[]):
        """
        Compute the ratio $area_{convexhull} / area_{total}$
Loading