Loading autocnet/graph/edge.py +39 −12 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 @@ -201,18 +213,33 @@ class Edge(dict, MutableMapping): self.masks[maskname] = mask @utils.methodispatch def get_keypoints(self, node, index=None, homogeneous=False): def get_keypoints(self, node, index=None, homogeneous=False, overlap=False): if not hasattr(index, '__iter__') and index is not None: raise TypeError return node.get_keypoint_coordinates(index=index, homogeneous=homogeneous) keypts = node.get_keypoint_coordinates(index=index, homogeneous=homogeneous) # If we only want keypoints in the overlap if 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 overlap_mask = cg.geom_mask(node.keypoints, bounds_poly) # Return masked keypts return keypts[overlap_mask] return keypts @get_keypoints.register(str) def _(self, node, index=None, homogeneous=False): def _(self, node, index=None, homogeneous=False, overlap=False): if not hasattr(index, '__iter__') and index is not None: raise TypeError node = node.lower() node = getattr(self, node) return node.get_keypoint_coordinates(index=index, homogeneous=homogeneous) return self.get_keypoints(node, index=index, homogeneous=homogeneous, overlap=overlap) def compute_fundamental_error(self, clean_keys=[]): """ 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_decompose.py +3 −2 Original line number Diff line number Diff line Loading @@ -67,8 +67,9 @@ def decompose_and_match(self, k=2, maxiteration=3, size=18, buf_dist=3,**kwargs) dsize = ddata.shape # Grab all the available candidate keypoints skp = self.source.get_keypoints() dkp = self.destination.get_keypoints() overlap = kwargs.get("overlap", False) skp = self.get_keypoints(self.source, overlap=overlap) dkp = self.get_keypoints(self.destination, overlap=overlap) # Set up the membership arrays self.smembership = np.zeros(sdata.shape, dtype=np.int16) Loading 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 Loading
autocnet/graph/edge.py +39 −12 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 @@ -201,18 +213,33 @@ class Edge(dict, MutableMapping): self.masks[maskname] = mask @utils.methodispatch def get_keypoints(self, node, index=None, homogeneous=False): def get_keypoints(self, node, index=None, homogeneous=False, overlap=False): if not hasattr(index, '__iter__') and index is not None: raise TypeError return node.get_keypoint_coordinates(index=index, homogeneous=homogeneous) keypts = node.get_keypoint_coordinates(index=index, homogeneous=homogeneous) # If we only want keypoints in the overlap if 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 overlap_mask = cg.geom_mask(node.keypoints, bounds_poly) # Return masked keypts return keypts[overlap_mask] return keypts @get_keypoints.register(str) def _(self, node, index=None, homogeneous=False): def _(self, node, index=None, homogeneous=False, overlap=False): if not hasattr(index, '__iter__') and index is not None: raise TypeError node = node.lower() node = getattr(self, node) return node.get_keypoint_coordinates(index=index, homogeneous=homogeneous) return self.get_keypoints(node, index=index, homogeneous=homogeneous, overlap=overlap) def compute_fundamental_error(self, clean_keys=[]): """ 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_decompose.py +3 −2 Original line number Diff line number Diff line Loading @@ -67,8 +67,9 @@ def decompose_and_match(self, k=2, maxiteration=3, size=18, buf_dist=3,**kwargs) dsize = ddata.shape # Grab all the available candidate keypoints skp = self.source.get_keypoints() dkp = self.destination.get_keypoints() overlap = kwargs.get("overlap", False) skp = self.get_keypoints(self.source, overlap=overlap) dkp = self.get_keypoints(self.destination, overlap=overlap) # Set up the membership arrays self.smembership = np.zeros(sdata.shape, dtype=np.int16) Loading
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