Loading .travis.yml +2 −4 Changes for .travis.yml: 2 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading README.rst +2 −2 Changes for README.rst: 2 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -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 ------------------------- Loading @@ -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`` autocnet/control/tests/test_control.py +20 −0 Changes for autocnet/control/tests/test_control.py: 20 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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)) autocnet/graph/edge.py +69 −40 Changes for autocnet/graph/edge.py: 69 added lines, 40 removed lines. Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) Loading Loading @@ -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') Loading Loading @@ -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) Loading Loading @@ -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) Loading @@ -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 Loading @@ -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 autocnet/graph/tests/test_edge.py +300 −0 Changes for autocnet/graph/tests/test_edge.py: 300 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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): Loading Loading @@ -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
.travis.yml +2 −4 Changes for .travis.yml: 2 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading
README.rst +2 −2 Changes for README.rst: 2 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -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 ------------------------- Loading @@ -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``
autocnet/control/tests/test_control.py +20 −0 Changes for autocnet/control/tests/test_control.py: 20 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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))
autocnet/graph/edge.py +69 −40 Changes for autocnet/graph/edge.py: 69 added lines, 40 removed lines. Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) Loading Loading @@ -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') Loading Loading @@ -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) Loading Loading @@ -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) Loading @@ -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 Loading @@ -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
autocnet/graph/tests/test_edge.py +300 −0 Changes for autocnet/graph/tests/test_edge.py: 300 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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): Loading Loading @@ -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"]))