Loading autocnet/graph/edge.py +26 −11 Original line number Diff line number Diff line Loading @@ -115,14 +115,6 @@ class Edge(dict, MutableMapping): """ pass def match_overlap(self, k=2, **kwargs): """ Given two sets of descriptors, apply the matcher with the source and destination overlaps. """ overlaps = [self['source_mbr'], self['destin_mbr']] self.match(k=k, overlap=overlaps, **kwargs) def decompose(self): """ Apply coupled decomposition to the images and Loading Loading @@ -153,6 +145,26 @@ class Edge(dict, MutableMapping): 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"]): warnings.warn( "Cannot use overlap constraint, minimum bounding rectangles" " have not been computed for one or more Nodes") return # Get overlapping keypts s_idx = self.get_keypoints(self.source, overlap=True).index d_idx = self.get_keypoints(self.destination, overlap=True).index # Create a mask from matches whose rows have both source idx & # dest idx in the overlapping keypts mask = pd.Series(False, index=self.matches.index) mask.loc[(self.matches["source_idx"].isin(s_idx)) & (self.matches["destination_idx"].isin(d_idx))] = True self.masks['overlap'] = mask def symmetry_check(self): self.masks['symmetry'] = od.mirroring_test(self.matches) Loading Loading @@ -207,9 +219,12 @@ class Edge(dict, MutableMapping): keypts = node.get_keypoint_coordinates(index=index, homogeneous=homogeneous) # If we only want keypoints in the overlap if overlap: # Compute overlap if we don't have it if not self["source_mbr"] or self["destin_mbr"]: self.compute_overlap() # Can't use overlap if we haven't computed MBRs if not (self["source_mbr"] and self["destin_mbr"]): warnings.warn( "Cannot use overlap constraint, minimum bounding rectangles" " have not been computed for one or more Nodes") return keypts # Create overlap's bounding polygon in pixel space bounds_poly = node.reproject_geom(self.overlap_latlon_coords) # Mask for node keypts based on bounding poly Loading autocnet/graph/network.py +12 −0 Original line number Diff line number Diff line Loading @@ -442,6 +442,18 @@ class CandidateGraph(nx.Graph): ''' self.apply_func_to_edges('ratio_check', *args, **kwargs) def compute_overlaps(self, *args, **kwargs): ''' Computes overlap MBRs for all edges ''' self.apply_func_to_edges('compute_overlap', *args, **kwargs) def overlap_checks(self, *args, **kwargs): ''' Apply overlap check to all edges in the graph ''' self.apply_func_to_edges('overlap_check', *args, **kwargs) def compute_homographies(self, *args, **kwargs): ''' Compute homographies for all edges using identical parameters Loading autocnet/graph/tests/test_edge.py +90 −1 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import ogr import numpy as np import pandas as pd from plio.io import io_gdal from shapely.geometry import Polygon as Poly from autocnet.matcher import cpu_outlier_detector as od from autocnet.examples import get_path Loading Loading @@ -135,7 +136,6 @@ class TestEdge(unittest.TestCase): e.source = source_node e.destination = destination_node e.clean = MagicMock(return_value=(matches_df, None)) e.matches = matches_df Loading Loading @@ -167,6 +167,41 @@ class TestEdge(unittest.TestCase): self.assertTrue(out_df[0].iloc[row_idx][column] == out_df[2].iloc[row_idx][column]) # Test when overlap=True # edge["source_mbr"] and edge["destin_mbr"] haven't been calculated # yet, so return val should be unmasked df of node's keypts s_no_overlap = e.get_keypoints(e.source, overlap=True) d_no_overlap = e.get_keypoints(e.destination, overlap=True) self.assertTrue(s_no_overlap.equals(src_keypoint_df)) self.assertTrue(d_no_overlap.equals(dst_keypoint_df)) # Define the MBRs e.overlap_latlon_coords = 0, 0 source_node.reproject_geom = MagicMock(return_value=Poly([(1, 6), (1, 8), (3, 6), (3, 8)])) source_node.keypoints = src_keypoint_df destination_node.reproject_geom = MagicMock(return_value=Poly([(31, 26), (31, 28), (33, 26), (33, 28)])) destination_node.keypoints = dst_keypoint_df # Only keep keypt vals w/I these bounding rects e["source_mbr"] = (0, 2, 8, 5) e["destin_mbr"] = (31, 33, 28, 26) # Grab the keypoints on our MBR overlaps s_overlap = e.get_keypoints(e.source, overlap=True) d_overlap = e.get_keypoints(e.destination, overlap=True) # Assert masked src keypts coords are equal s_expected = pd.DataFrame({'x': (1, 2, 3), 'y': (6, 7, 8)}) self.assertTrue(np.array_equal(s_expected['x'], s_overlap['x'].values)) self.assertTrue(np.array_equal(s_expected['y'], s_overlap['y'].values)) # Assert masked dst keypt coords are equal d_expected = pd.DataFrame({'x': (33, 32, 31), 'y': (28, 27, 26)}) self.assertTrue(np.array_equal(d_expected['x'], d_overlap['x'].values)) self.assertTrue(np.array_equal(d_expected['y'], d_overlap['y'].values)) # Assert type-checking in method throws proper errors with self.assertRaises(TypeError): e.get_keypoints("source", index = 456) Loading Loading @@ -241,3 +276,57 @@ class TestEdge(unittest.TestCase): expected = list(od.distance_ratio(matches_df)) e.ratio_check() self.assertEqual(expected, list(e.masks["ratio"])) def test_overlap_check(self): s = node.Node() d = node.Node() e = edge.Edge() e.source = s e.destination = d src_keypoint_df = pd.DataFrame({'x': (0, 1, 2, 3, 4), 'y': (5, 6, 7, 8, 9)}) dst_keypoint_df = pd.DataFrame({'x': (34, 33, 32, 31, 30), 'y': (29, 28, 27, 26, 25)}) # Create keypt 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']) s.keypoints = src_keypoint_df d.keypoints = dst_keypoint_df e.matches = matches_df s_overlap_keypts = pd.DataFrame({'x': (0, 1), 'y': (5, 6)}) d_overlap_keypts = pd.DataFrame({'x': (31, 30), 'y': (26, 25)}, index=[3, 4]) expected_mask = pd.Series(data=[True, True, False, False, False]) # Mockup of the Edge.get_keypoints() method when overlap=True def mock_get_keypts(node, overlap=False): if node == s and overlap: return s_overlap_keypts elif node == d and overlap: return d_overlap_keypts else: return None e.get_keypoints = MagicMock(side_effect=mock_get_keypts) # Should fail if no src & dst mbrs on edge; Warns user & mask isn't # populated e.overlap_check() self.assertTrue("overlap" not in e.masks) # Should work after MBRs are set e["source_mbr"] = (1, 1, 1, 1) e["destin_mbr"] = (1, 1, 1, 1) e.overlap_check() overlap_matches, overlap_mask = e.clean(clean_keys=['overlap']) self.assertTrue(expected_mask.equals(overlap_mask)) self.assertTrue(overlap_matches.equals(e.matches[overlap_mask])) autocnet/matcher/cpu_matcher.py +1 −10 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ FLANN_INDEX_KDTREE = 1 # Algorithm to set centers, DEFAULT_FLANN_PARAMETERS = dict(algorithm=FLANN_INDEX_KDTREE, trees=3) def match(self, k=2, overlap=[], **kwargs): def match(self, k=2, **kwargs): """ Given two sets of descriptors, utilize a FLANN (Approximate Nearest Neighbor KDTree) matcher to find the k nearest matches. Nearness is Loading Loading @@ -83,21 +83,12 @@ def match(self, k=2, overlap=[], **kwargs): if 'aidx' in kwargs.keys(): aidx = kwargs['aidx'] kwargs.pop('aidx') elif overlap: # Query the source keypoints for those in the MBR source_mbr = overlap[0] query_result = self.source.keypoints.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(*source_mbr)) aidx = query_result.index else: aidx = None if 'bidx' in kwargs.keys(): bidx = kwargs['bidx'] kwargs.pop('bidx') elif overlap: destin_mbr = overlap[1] query_result = self.destination.keypoints.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(*destin_mbr)) bidx = query_result.index else: bidx = None Loading autocnet/matcher/cuda_matcher.py +1 −19 Original line number Diff line number Diff line Loading @@ -4,7 +4,7 @@ import cudasift as cs import numpy as np import pandas as pd def match(self, ratio=0.8, overlap=[], **kwargs): def match(self, ratio=0.8, **kwargs): """ Apply a composite CUDA matcher and ratio check. If this method is used, Loading @@ -14,19 +14,6 @@ def match(self, ratio=0.8, overlap=[], **kwargs): without significant gain in accuracy when using this implementation. """ if overlap: source_overlap = overlap[0] source_kps = self.source.keypoints.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(*source_overlap)) idx = source_kps.index sremap = {k:v for k, v in enumerate(idx)} source_des = self.source.descriptors[idx] destin_overlap = overlap[1] destin_kps = self.destination.keypoints.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(*destin_overlap)) idx = destin_kps.index dremap = {k:v for k, v in enumerate(idx)} destin_des = self.destination.descriptors[idx] else: source_kps = self.source.get_keypoints() source_des = self.source.descriptors Loading @@ -49,11 +36,6 @@ def match(self, ratio=0.8, overlap=[], **kwargs): df.columns = ['source_image', 'source_idx', 'destination_image', 'destination_idx', 'score', 'ambiguity'] if overlap: df['source_idx'].replace(sremap, inplace=True) df['destination_idx'].replace(dremap, inplace=True) # Set the matches and set the 'ratio' (ambiguity) mask self.matches = df self.masks['ratio'] = df['ambiguity'] <= ratio Loading
autocnet/graph/edge.py +26 −11 Original line number Diff line number Diff line Loading @@ -115,14 +115,6 @@ class Edge(dict, MutableMapping): """ pass def match_overlap(self, k=2, **kwargs): """ Given two sets of descriptors, apply the matcher with the source and destination overlaps. """ overlaps = [self['source_mbr'], self['destin_mbr']] self.match(k=k, overlap=overlaps, **kwargs) def decompose(self): """ Apply coupled decomposition to the images and Loading Loading @@ -153,6 +145,26 @@ class Edge(dict, MutableMapping): 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"]): warnings.warn( "Cannot use overlap constraint, minimum bounding rectangles" " have not been computed for one or more Nodes") return # Get overlapping keypts s_idx = self.get_keypoints(self.source, overlap=True).index d_idx = self.get_keypoints(self.destination, overlap=True).index # Create a mask from matches whose rows have both source idx & # dest idx in the overlapping keypts mask = pd.Series(False, index=self.matches.index) mask.loc[(self.matches["source_idx"].isin(s_idx)) & (self.matches["destination_idx"].isin(d_idx))] = True self.masks['overlap'] = mask def symmetry_check(self): self.masks['symmetry'] = od.mirroring_test(self.matches) Loading Loading @@ -207,9 +219,12 @@ class Edge(dict, MutableMapping): keypts = node.get_keypoint_coordinates(index=index, homogeneous=homogeneous) # If we only want keypoints in the overlap if overlap: # Compute overlap if we don't have it if not self["source_mbr"] or self["destin_mbr"]: self.compute_overlap() # Can't use overlap if we haven't computed MBRs if not (self["source_mbr"] and self["destin_mbr"]): warnings.warn( "Cannot use overlap constraint, minimum bounding rectangles" " have not been computed for one or more Nodes") return keypts # Create overlap's bounding polygon in pixel space bounds_poly = node.reproject_geom(self.overlap_latlon_coords) # Mask for node keypts based on bounding poly Loading
autocnet/graph/network.py +12 −0 Original line number Diff line number Diff line Loading @@ -442,6 +442,18 @@ class CandidateGraph(nx.Graph): ''' self.apply_func_to_edges('ratio_check', *args, **kwargs) def compute_overlaps(self, *args, **kwargs): ''' Computes overlap MBRs for all edges ''' self.apply_func_to_edges('compute_overlap', *args, **kwargs) def overlap_checks(self, *args, **kwargs): ''' Apply overlap check to all edges in the graph ''' self.apply_func_to_edges('overlap_check', *args, **kwargs) def compute_homographies(self, *args, **kwargs): ''' Compute homographies for all edges using identical parameters Loading
autocnet/graph/tests/test_edge.py +90 −1 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import ogr import numpy as np import pandas as pd from plio.io import io_gdal from shapely.geometry import Polygon as Poly from autocnet.matcher import cpu_outlier_detector as od from autocnet.examples import get_path Loading Loading @@ -135,7 +136,6 @@ class TestEdge(unittest.TestCase): e.source = source_node e.destination = destination_node e.clean = MagicMock(return_value=(matches_df, None)) e.matches = matches_df Loading Loading @@ -167,6 +167,41 @@ class TestEdge(unittest.TestCase): self.assertTrue(out_df[0].iloc[row_idx][column] == out_df[2].iloc[row_idx][column]) # Test when overlap=True # edge["source_mbr"] and edge["destin_mbr"] haven't been calculated # yet, so return val should be unmasked df of node's keypts s_no_overlap = e.get_keypoints(e.source, overlap=True) d_no_overlap = e.get_keypoints(e.destination, overlap=True) self.assertTrue(s_no_overlap.equals(src_keypoint_df)) self.assertTrue(d_no_overlap.equals(dst_keypoint_df)) # Define the MBRs e.overlap_latlon_coords = 0, 0 source_node.reproject_geom = MagicMock(return_value=Poly([(1, 6), (1, 8), (3, 6), (3, 8)])) source_node.keypoints = src_keypoint_df destination_node.reproject_geom = MagicMock(return_value=Poly([(31, 26), (31, 28), (33, 26), (33, 28)])) destination_node.keypoints = dst_keypoint_df # Only keep keypt vals w/I these bounding rects e["source_mbr"] = (0, 2, 8, 5) e["destin_mbr"] = (31, 33, 28, 26) # Grab the keypoints on our MBR overlaps s_overlap = e.get_keypoints(e.source, overlap=True) d_overlap = e.get_keypoints(e.destination, overlap=True) # Assert masked src keypts coords are equal s_expected = pd.DataFrame({'x': (1, 2, 3), 'y': (6, 7, 8)}) self.assertTrue(np.array_equal(s_expected['x'], s_overlap['x'].values)) self.assertTrue(np.array_equal(s_expected['y'], s_overlap['y'].values)) # Assert masked dst keypt coords are equal d_expected = pd.DataFrame({'x': (33, 32, 31), 'y': (28, 27, 26)}) self.assertTrue(np.array_equal(d_expected['x'], d_overlap['x'].values)) self.assertTrue(np.array_equal(d_expected['y'], d_overlap['y'].values)) # Assert type-checking in method throws proper errors with self.assertRaises(TypeError): e.get_keypoints("source", index = 456) Loading Loading @@ -241,3 +276,57 @@ class TestEdge(unittest.TestCase): expected = list(od.distance_ratio(matches_df)) e.ratio_check() self.assertEqual(expected, list(e.masks["ratio"])) def test_overlap_check(self): s = node.Node() d = node.Node() e = edge.Edge() e.source = s e.destination = d src_keypoint_df = pd.DataFrame({'x': (0, 1, 2, 3, 4), 'y': (5, 6, 7, 8, 9)}) dst_keypoint_df = pd.DataFrame({'x': (34, 33, 32, 31, 30), 'y': (29, 28, 27, 26, 25)}) # Create keypt 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']) s.keypoints = src_keypoint_df d.keypoints = dst_keypoint_df e.matches = matches_df s_overlap_keypts = pd.DataFrame({'x': (0, 1), 'y': (5, 6)}) d_overlap_keypts = pd.DataFrame({'x': (31, 30), 'y': (26, 25)}, index=[3, 4]) expected_mask = pd.Series(data=[True, True, False, False, False]) # Mockup of the Edge.get_keypoints() method when overlap=True def mock_get_keypts(node, overlap=False): if node == s and overlap: return s_overlap_keypts elif node == d and overlap: return d_overlap_keypts else: return None e.get_keypoints = MagicMock(side_effect=mock_get_keypts) # Should fail if no src & dst mbrs on edge; Warns user & mask isn't # populated e.overlap_check() self.assertTrue("overlap" not in e.masks) # Should work after MBRs are set e["source_mbr"] = (1, 1, 1, 1) e["destin_mbr"] = (1, 1, 1, 1) e.overlap_check() overlap_matches, overlap_mask = e.clean(clean_keys=['overlap']) self.assertTrue(expected_mask.equals(overlap_mask)) self.assertTrue(overlap_matches.equals(e.matches[overlap_mask]))
autocnet/matcher/cpu_matcher.py +1 −10 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ FLANN_INDEX_KDTREE = 1 # Algorithm to set centers, DEFAULT_FLANN_PARAMETERS = dict(algorithm=FLANN_INDEX_KDTREE, trees=3) def match(self, k=2, overlap=[], **kwargs): def match(self, k=2, **kwargs): """ Given two sets of descriptors, utilize a FLANN (Approximate Nearest Neighbor KDTree) matcher to find the k nearest matches. Nearness is Loading Loading @@ -83,21 +83,12 @@ def match(self, k=2, overlap=[], **kwargs): if 'aidx' in kwargs.keys(): aidx = kwargs['aidx'] kwargs.pop('aidx') elif overlap: # Query the source keypoints for those in the MBR source_mbr = overlap[0] query_result = self.source.keypoints.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(*source_mbr)) aidx = query_result.index else: aidx = None if 'bidx' in kwargs.keys(): bidx = kwargs['bidx'] kwargs.pop('bidx') elif overlap: destin_mbr = overlap[1] query_result = self.destination.keypoints.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(*destin_mbr)) bidx = query_result.index else: bidx = None Loading
autocnet/matcher/cuda_matcher.py +1 −19 Original line number Diff line number Diff line Loading @@ -4,7 +4,7 @@ import cudasift as cs import numpy as np import pandas as pd def match(self, ratio=0.8, overlap=[], **kwargs): def match(self, ratio=0.8, **kwargs): """ Apply a composite CUDA matcher and ratio check. If this method is used, Loading @@ -14,19 +14,6 @@ def match(self, ratio=0.8, overlap=[], **kwargs): without significant gain in accuracy when using this implementation. """ if overlap: source_overlap = overlap[0] source_kps = self.source.keypoints.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(*source_overlap)) idx = source_kps.index sremap = {k:v for k, v in enumerate(idx)} source_des = self.source.descriptors[idx] destin_overlap = overlap[1] destin_kps = self.destination.keypoints.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(*destin_overlap)) idx = destin_kps.index dremap = {k:v for k, v in enumerate(idx)} destin_des = self.destination.descriptors[idx] else: source_kps = self.source.get_keypoints() source_des = self.source.descriptors Loading @@ -49,11 +36,6 @@ def match(self, ratio=0.8, overlap=[], **kwargs): df.columns = ['source_image', 'source_idx', 'destination_image', 'destination_idx', 'score', 'ambiguity'] if overlap: df['source_idx'].replace(sremap, inplace=True) df['destination_idx'].replace(dremap, inplace=True) # Set the matches and set the 'ratio' (ambiguity) mask self.matches = df self.masks['ratio'] = df['ambiguity'] <= ratio