Commit 728affd7 authored by Adam Paquette's avatar Adam Paquette
Browse files

Updated voronoi branch to be inline with dev.

parents f0e1d6bc 1187045c
Loading
Loading
Loading
Loading
+2 −4
Changes for .travis.yml: 2 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -44,8 +44,8 @@ install:
  - conda config --add channels jlaura
  - conda config --set ssl_verify false
  - conda install python=$PYTHON_VERSION
  - conda install -c conda-forge numpy
  - conda install -c jlaura plio opencv3=3.0.0
  - conda install -c conda-forge numpy opencv
  - conda install -c jlaura plio
  - conda install -c conda-forge vlfeat
  - conda install -c menpo cyvlfeat
  - pip install pillow pysal
@@ -66,8 +66,6 @@ install:

script:
  - pytest --cov=autocnet
  # clean up any remaining processes...
  - if [ $TRAVIS_OS_NAME == "linux" ]; then killall5; fi

after_success:
  # Upload to anaconda and push to coveralls
+2 −2
Changes for README.rst: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ AutoCNet

Automated sparse control network generation to support photogrammetric control of planetary image data.

* Documentation: https://autocnet.readthedocs.org.
* Documentation: https://usgs-astrogeology.github.io/autocnet/

Installation Instructions
-------------------------
@@ -37,6 +37,6 @@ We suggest using Anaconda Python to install Autocnet within a virtual environmen
  * ``conda create -n <your_environment_name> python=3 && source activate <your_environment_name>``
1. Bring up a command line and add three channels to your conda config (``~/condarc``):
  * ``conda config --add channels conda-forge``
  * ``conda condig --add channels jlaura``
  * ``conda config --add channels jlaura``
  * ``conda config --add channels menpo``
1. Finally, install autocnet: ``conda install -c jlaura autocnet-dev``
+20 −0
Changes for autocnet/control/tests/test_control.py: 20 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -75,3 +75,23 @@ class TestC(unittest.TestCase):
    def test_to_dataframe(self):
        self.C.to_dataframe()

    def test_point_repr(self):
        expected = 0
        p = control.Point(expected)
        self.assertEqual(str(expected), p.__repr__())

    def test_correspondence_repr(self):
        expected = 0
        c = control.Correspondence(expected, 1, 1)
        self.assertEqual(str(expected), c.__repr__())

    def test_correspondence_eq(self):
        expected = 0
        c = control.Correspondence(expected, 1, 1)
        self.assertTrue(c == expected)

    def test_correspondence_hash(self):
        expected = 200
        c = control.Correspondence(expected, 1, 1)
        self.assertEqual(hash(expected), hash(c))
+69 −40
Changes for autocnet/graph/edge.py: 69 added lines, 40 removed lines.
Original line number Diff line number Diff line
@@ -75,22 +75,11 @@ class Edge(dict, MutableMapping):
    def masks(self):
        mask_lookup = {'fundamental': 'fundamental_matrix'}
        if not hasattr(self, '_masks'):
            if self.matches is not None:
            if isinstance(self.matches, pd.DataFrame):
                self._masks = pd.DataFrame(True, columns=['symmetry'],
                                           index=self.matches.index)
            else:
                self._masks = pd.DataFrame()
        # 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:
                try:
                    truncated_mask = getattr(self, mask_lookup[c]).mask
                    self._masks[c] = False
                    self._masks[c].iloc[truncated_mask.index] = truncated_mask
                except Exception:
                    #TODO: Get rid of state
                    pass
        return self._masks

    @masks.setter
@@ -119,14 +108,14 @@ class Edge(dict, MutableMapping):
        pass

    def symmetry_check(self):
        if hasattr(self, 'matches'):
        if isinstance(self.matches, pd.DataFrame):
            mask = od.mirroring_test(self.matches)
            self.masks = ('symmetry', mask)
        else:
            raise AttributeError('No matches have been computed for this edge.')

    def ratio_check(self, clean_keys=[], **kwargs):
        if hasattr(self, 'matches'):
        if isinstance(self.matches, pd.DataFrame):
            matches, mask = self.clean(clean_keys)
            distance_mask = od.distance_ratio(matches, **kwargs)
            self.masks = ('ratio', distance_mask)
@@ -153,7 +142,7 @@ class Edge(dict, MutableMapping):
        autocnet.transformation.transformations.FundamentalMatrix

        """
        if not hasattr(self, 'matches'):
        if not isinstance(self.matches, pd.DataFrame):
            raise AttributeError('Matches have not been computed for this edge')
            return
        matches, mask = self.clean(clean_keys)
@@ -198,7 +187,7 @@ class Edge(dict, MutableMapping):
               Boolean array of the outliers
        """

        if hasattr(self, 'matches'):
        if isinstance(self.matches, pd.DataFrame):
            matches = self.matches
        else:
            raise AttributeError('Matches have not been computed for this edge')
@@ -322,7 +311,7 @@ class Edge(dict, MutableMapping):
                     of mask keys to be used to reduce the total size
                     of the matches dataframe.
        """
        if not hasattr(self, 'matches'):
        if not isinstance(self.matches, pd.DataFrame):
            raise AttributeError('This edge does not yet have any matches computed.')

        matches, mask = self.clean(clean_keys)
@@ -419,7 +408,7 @@ class Edge(dict, MutableMapping):
                                   returns the overlap area
                                   covered by the keypoints
        """
        if self.matches is None:
        if not isinstance(self.matches, pd.DataFrame):
            raise AttributeError('Edge needs to have features extracted and matched')
            return
        matches, mask = self.clean(clean_keys)
@@ -443,28 +432,6 @@ class Edge(dict, MutableMapping):

        return total_overlap_coverage

    def get_keypoints(self, node, clean_keys, **kwargs):

        matches, _ = self.clean(clean_keys=clean_keys)

        if type(node) is str:
            node = node.lower()

        elif type(node) is Node:
            node = node['node_id']

        else:
            AssertionError('Node parameter is not a string or node object.')

        if node == "source" or node == "s" or node == self.source['node_id']:
            return self.source.get_keypoint_coordinates(index=matches['source_idx'], **kwargs)

        elif node == "destination" or node == "d" or node == self.destination['node_id']:
            return self.destination.get_keypoint_coordinates(index=matches['destination_idx'], **kwargs)

        else:
            AssertionError('Could not obtain the correct keypoints based on the given parameters.')

    def decompose(self, maxiterations=3):
        """
        Apply coupled decomposition to the images and
@@ -486,3 +453,65 @@ class Edge(dict, MutableMapping):

        """
        pass

    def get_keypoints(self, node, clean_keys):
        """

        Returns a list of keypoint coordinates that match the specified
        paramaters

        Parameters
        ----------
        node :      str or Node
                    Can be "source" or "destination" based on which node we're
                    pulling keypoint data for; Also can pass Node obj itself

        clean_keys :    list
                        List of clean key strings

        Return
        ------
        masked_keypts : Dataframe
                        Dataframe of keypoints that match the specified masks
                        on the specified node
        """

        # Assert parameter types are correct
        try:
            assert (isinstance(node, str) or isinstance(node, Node))
        except AssertionError:
            raise TypeError('Parameter "node" must be of type str or type Node')
        try:
            assert isinstance(clean_keys, list)
        except AssertionError:
            raise TypeError('Parameter "clean_keys" must be of type list')

        # If node param is a string, make sure it's one of the right strings
        if isinstance(node, str):
            try:
                assert (node in ["source", "destination"])
                # Define the node if str is passed as param
                if node == "source":
                    node = self.source
                elif node == "destination":
                    node = self.destination
            except AssertionError:
                raise KeyError('node" parameter must be "source"' +
                               'or "destination"')

        # Get cleaned, combined src & dst keypt df for this edge ("matches")
        matches, mask = self.clean(clean_keys)

        # Grab the keypt indices filtered by clean_keys as ints, pandas
        # complains when you use them as indicies if they're not ints
        if node == self.source:
            keypt_indices = matches["source_idx"].astype(int)
        elif node == self.destination:
            keypt_indices = matches["destination_idx"].astype(int)

        # Get all keypts for the specified node
        all_keypts = node.get_keypoints()
        # Return keypts @ masked indecies for the node
        masked_keypts = all_keypts.iloc[keypt_indices].sort_index()

        return masked_keypts
+300 −0
Changes for autocnet/graph/tests/test_edge.py: 300 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -3,9 +3,11 @@ from unittest.mock import Mock
from unittest.mock import MagicMock

import ogr
import numpy as np
import pandas as pd
from plio.io import io_gdal

from autocnet.matcher import outlier_detector as od
from autocnet.examples import get_path
from autocnet.graph.network import CandidateGraph
from autocnet.utils.utils import array_to_poly
@@ -44,6 +46,33 @@ class TestEdge(unittest.TestCase):

    def test_masks(self):
        self.assertIsInstance(self.edge.masks, pd.DataFrame)
        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=matches,
                                  columns=['source_image', 'source_idx',
                                           'destination_image',
                                           'destination_idx'])
        e = edge.Edge()
        e.matches = matches_df

        # Test empty masks df on an edge with computed matches
        expected = pd.DataFrame(True, columns=['symmetry'],
                                index=matches_df.index)
        self.assertTrue(expected.equals(e.masks))

        # Test the masks setter, changing a given row
        new_symmetry_rows = [True, False, True, False, True]
        e.masks = "symmetry", new_symmetry_rows

        self.assertEqual(new_symmetry_rows, list(e.masks.loc[:, "symmetry"]))

        # Test the masks setter, inserting a new row
        e.masks = "fundamental", new_symmetry_rows
        self.assertEqual(new_symmetry_rows, list(e.masks.loc[:, "fundamental"]))



    def test_compute_fundamental_matrix(self):
@@ -128,3 +157,274 @@ 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

    def test_get_keypoints(self):
        src_keypoint_df = pd.DataFrame({'x': (0, 1, 2, 3, 4), 'y': (5, 6, 7, 8, 9),
                                        'response': (10, 11, 12, 13, 14), 'size': (15, 16, 17, 18, 19),
                                        'angle': (20, 21, 22, 23, 24), 'octave': (25, 26, 27, 28, 29),
                                        'layer': (30, 31, 32, 33, 34)})

        dst_keypoint_df = pd.DataFrame({'x': (34, 33, 32, 31, 30), 'y': (29, 28, 27, 26, 25),
                                        'response': (24, 23, 22, 21, 20), 'size': (19, 18, 17, 16, 15),
                                        'angle': (14, 13, 12, 11, 10), 'octave': (9, 8, 7, 6, 5),
                                        'layer': (4, 3, 2, 1, 0)})

        keypoint_matches = [[0, 0, 1, 4],
                            [0, 1, 1, 3],
                            [0, 2, 1, 2],
                            [0, 3, 1, 1],
                            [0, 4, 1, 0]]

        matches_df = pd.DataFrame(data=keypoint_matches, columns=['source_image', 'source_idx',
                                                                  'destination_image', 'destination_idx'])

        e = edge.Edge()
        source_node = MagicMock(spec=node.Node())
        destination_node = MagicMock(spec=node.Node())

        source_node.get_keypoints = MagicMock(return_value=src_keypoint_df)
        destination_node.get_keypoints = MagicMock(return_value=dst_keypoint_df)

        e.source = source_node
        e.destination = destination_node

        e.clean = MagicMock(return_value=(matches_df, None))
        e.matches = matches_df

        clean_keys = ["fundamental", "ratio", "symmetry"]

        # Test all uses for edge.get_keypoints()
        src_matched_keypts = e.get_keypoints("source", clean_keys)
        src_matched_keypts2 = e.get_keypoints(e.source, clean_keys)
        dst_matched_keypts = e.get_keypoints("destination", clean_keys)
        dst_matched_keypts2 = e.get_keypoints(e.destination, clean_keys)

        # [output df to test] [name of node] [df to test against]
        to_test = [[src_matched_keypts, "source", src_keypoint_df],
                   [src_matched_keypts2, "source", src_keypoint_df],
                   [dst_matched_keypts, "destination", dst_keypoint_df],
                   [dst_matched_keypts2, "destination", dst_keypoint_df]]

        for out_df in to_test:
            # For each row index in the appropriate column of the matches_df,
            # assert that row index exists in the function's returned df
            [self.assertIn(row_idx, out_df[0].index.values)
             for row_idx in matches_df[out_df[1] + '_idx']]
            # For each row index in the returned df
            for row_idx in out_df[0].index.values:
                # Assert that row_idx exists in the matches_df's appropriate
                # column
                self.assertIn(row_idx, matches_df[out_df[1] + '_idx'])
                # Assert that all row_idx[column] vals returned by function
                # match their counterpart in orig df
                for column in out_df[0].columns:
                    self.assertTrue(out_df[0].iloc[row_idx][column] ==
                                    out_df[2].iloc[row_idx][column])

        # Assert type-checking in method throws proper errors
        with self.assertRaises(TypeError):
            e.get_keypoints("source", 1)
        with self.assertRaises(TypeError):
            e.get_keypoints(1, clean_keys)
        # Check key error thrown when string arg != "source" or "destination"
        with self.assertRaises(KeyError):
            e.get_keypoints("string", clean_keys)

    def test_eq(self):
        edge1 = edge.Edge()
        edge2 = edge.Edge()
        edge3 = edge.Edge()

        # Test edges w/ different keys are not equal, ones with same keys are
        edge1.__dict__["key"] = 1
        edge2.__dict__["key"] = 1
        edge3.__dict__["not_key"] = 1

        self.assertTrue(edge1 == edge2)
        self.assertFalse(edge1 == edge3)

        # Test edges with same keys, but diff df values
        edge1.__dict__["key"] = pd.DataFrame({'x': (0, 1, 2, 3, 4)})
        edge2.__dict__["key"] = pd.DataFrame({'x': (0, 1, 2, 3, 4)})
        edge3.__dict__["key"] = pd.DataFrame({'x': (0, 1, 2, 3, 5)})

        self.assertTrue(edge1 == edge2)
        self.assertFalse(edge1 == edge3)

        # Test edges with same keys, but diff np array vals
        # edge.__eq__ calls ndarray.all(), which checks that
        # all values in an array eval to true
        edge1.__dict__["key"] = np.array([True, True, True], dtype=np.bool)
        edge2.__dict__["key"] = np.array([True, True, True], dtype=np.bool)
        edge3.__dict__["key"] = np.array([True, True, False], dtype=np.bool)

        self.assertTrue(edge1 == edge2)
        self.assertFalse(edge1 == edge3)

    def test_repr(self):
        src = node.Node()
        dst = node.Node()
        masks = pd.DataFrame()

        e = edge.Edge()
        e.source = src
        e.destination = dst

        expected = """
        Source Image Index: {}
        Destination Image Index: {}
        Available Masks: {}
        """.format(src, dst, masks)

        self.assertEqual(expected, e.__repr__())

    def test_symmetry_check(self):
        # Matches is init to None
        e = edge.Edge()
        e.source = node.Node()
        e.destination = node.Node()
        # If there are no matches, should raise attrib err
        with (self.assertRaises(AttributeError)):
            e.symmetry_check()

    def test_ratio_check(self):
        # Matches is init to None
        e = edge.Edge()
        # If there are no matches, should raise attrib err
        with (self.assertRaises(AttributeError)):
            e.ratio_check()

        # If there are matches...
        keypoint_matches = [[0, 0, 1, 4, 5],
                            [0, 1, 1, 3, 5],
                            [0, 2, 1, 2, 5],
                            [0, 3, 1, 1, 5],
                            [0, 4, 1, 0, 5]]

        matches_df = pd.DataFrame(data=keypoint_matches, columns=['source_image', 'source_idx',
                                                                  'destination_image', 'destination_idx', 'distance'])
        e.matches = matches_df
        expected = list(od.distance_ratio(matches_df))
        e.ratio_check()
        self.assertEqual(expected, list(e.masks["ratio"]))
Loading