Loading .travis.yml +1 −1 Original line number Diff line number Diff line Loading @@ -65,7 +65,7 @@ install: - python condaci.py setup script: - pytest --cov=autocnet - pytest autocnet functional_tests after_success: # Upload to anaconda and push to coveralls Loading autocnet/control/tests/test_control.py +31 −103 Original line number Diff line number Diff line import os import sys import itertools from time import gmtime, strftime import pytest from unittest.mock import Mock, MagicMock import numpy as np from unittest.mock import MagicMock import geopandas as gpd import pandas as pd from shapely.geometry import Polygon sys.path.insert(0, os.path.abspath('..')) from autocnet.control import control @pytest.fixture def controlnetwork_data(): df = pd.DataFrame([[0, 0.0, 0.0, (0.0, 1.0), 0, 0.0, 0.0], [0, 1.0, 0.0, (0.0, 1.0), 0, 0.0, 0.0], [1, 0.0, 1.0, (0.0, 1.0), 1, 0.0, 0.0], [1, 1.0, 1.0, (0.0, 1.0), 1, 0.0, 0.0], [2, 0.0, 2.0, (0.0, 1.0), 2, 0.0, 0.0], [2, 1.0, 2.0, (0.0, 1.0), 2, 0.0, 0.0], [3, 0.0, 3.0, (0.0, 1.0), 3, 0.0, 0.0], [3, 1.0, 3.0, (0.0, 1.0), 3, 0.0, 0.0], [4, 0.0, 4.0, (0.0, 1.0), 4, 0.0, 0.0], [4, 1.0, 4.0, (0.0, 1.0), 4, 0.0, 0.0], [5, 0.0, 5.0, (0.0, 1.0), 5, 0.0, 0.0], [5, 1.0, 5.0, (0.0, 1.0), 5, 0.0, 0.0], [6, 0.0, 6.0, (0.0, 1.0), 6, 0.0, 0.0], [6, 1.0, 6.0, (0.0, 1.0), 6, 0.0, 0.0], [7, 0.0, 7.0, (0.0, 1.0), 7, 0.0, 0.0], [7, 1.0, 7.0, (0.0, 1.0), 7, 0.0, 0.0], [0, 2.0, 0.0, (0.0, 2.0), 0, 0.0, 0.0], [1, 2.0, 1.0, (0.0, 2.0), 1, 0.0, 0.0], [2, 2.0, 2.0, (0.0, 2.0), 2, 0.0, 0.0], [3, 2.0, 3.0, (0.0, 2.0), 3, 0.0, 0.0], [4, 2.0, 4.0, (0.0, 2.0), 4, 0.0, 0.0], [5, 2.0, 5.0, (0.0, 2.0), 5, 0.0, 0.0], [8, 0.0, 8.0, (0.0, 2.0), 6, 0.0, 0.0], [8, 2.0, 8.0, (0.0, 2.0), 6, 0.0, 0.0], [9, 0.0, 9.0, (0.0, 2.0), 7, 0.0, 0.0], [9, 2.0, 9.0, (0.0, 2.0), 7, 0.0, 0.0], [10, 1.0, 8.0, (1.0, 2.0), 6, 0.0, 0.0], [10, 2.0, 6.0, (1.0, 2.0), 6, 0.0, 0.0], [11, 1.0, 9.0, (1.0, 2.0), 7, 0.0, 0.0], [11, 2.0, 7.0, (1.0, 2.0), 7, 0.0, 0.0]], columns=['point_id', 'image_index', 'keypoint_index', 'edge', 'match_idx', 'x', 'y']) df.index.name = 'measure_id' #Fix types df['point_id'] = df['point_id'].astype(object) df['match_idx'] = df['match_idx'].astype(object) return df @pytest.fixture def candidategraph(): edges = [(0,1), (0,2), (1,2)] match_indices = [([0,1,2,3,4,5,6,7], [0,1,2,3,4,5,6,7]), ([0,1,2,3,4,5,8,9], [0,1,2,3,4,5,8,9]), ([0,1,2,3,4,5,8,9], [0,1,2,3,4,5,6,7])] matches = [] for i, e in enumerate(edges): c = match_indices[i] source_image = np.repeat(e[0], 8) destin_image = np.repeat(e[1], 8) coords = np.zeros(8) data = np.vstack((source_image, c[0], destin_image, c[1], coords, coords, coords, coords)).T matches_df = pd.DataFrame(data, columns=['source_image', 'source_idx', 'destination_image', 'destination_idx', 'source_x', 'source_y', 'destination_x', 'destination_y']) matches.append(matches_df) # Mock in the candidate graph cg = MagicMock() cg.get_matches = MagicMock(return_value=matches) return cg @pytest.fixture() def controlnetwork(controlnetwork_data): cn = control.ControlNetwork() cn.data = controlnetwork_data # Patching data this way does NOT update the internal _measure_id and _point_id attributes return cn @pytest.fixture() def bad_controlnetwork(controlnetwork_data): cn = control.ControlNetwork() cn.data = controlnetwork_data # Since the data is being patched in, fix the measure counter cn._measure_id = len(cn.data) + 1 # Add a duplicate measure in image 0 to point 0 cn.add_measure((0,11), (0,1), 2, [1,1], point_id=0) return cn import os import sys sys.path.insert(0, '..') from .. import control def test_fromcandidategraph(candidategraph, controlnetwork_data):#, controlnetwork): matches = candidategraph.get_matches() Loading Loading @@ -136,5 +42,27 @@ def test_bad_validate_points(bad_controlnetwork): assert bad_controlnetwork.validate_points().iloc[0] == False assert bad_controlnetwork.validate_points().iloc[1:].all() def test_potential_overlap(controlnetwork): pass def test_identify_potential_overlaps(controlnetwork, candidategraph): res = control.identify_potential_overlaps(candidategraph, controlnetwork, overlap=False) assert res.equals(pd.Series([(2,), (2,), (1,), (1,), (0,), (0,)], index=[6,7,8,9,10,11])) def test_potential_overlap(controlnetwork, candidategraph): # Patch in an intersection check so that all points intersect all geoms candidategraph.create_node_subgraph = MagicMock(return_value=candidategraph) coords = [(-1., -1.), (-1., 1.), (1., 1.), (1., -1.), (-1., -1.)] poly = gpd.GeoSeries(Polygon(coords)) candidategraph.compute_intersection = MagicMock(return_value=(poly, 0)) res = control.identify_potential_overlaps(candidategraph, controlnetwork, overlap=True) assert res.equals(pd.Series([(2,), (2,), (1,), (1,), (0,), (0,)], index=[6,7,8,9,10,11])) autocnet/graph/edge.py +2 −39 Original line number Diff line number Diff line Loading @@ -67,33 +67,17 @@ class Edge(dict, MutableMapping): if not k in o.keys(): eq = False return eq if isinstance(v, pd.DataFrame): if not v.equals(o[k]): eq = False print(k) elif isinstance(v, np.ndarray): if not v.all() == o[k].all(): eq = False print(k) return eq """@property def masks(self): mask_lookup = {'fundamental': 'fundamental_matrix'} if not hasattr(self, '_masks'): if isinstance(self.matches, pd.DataFrame): self._masks = pd.DataFrame(True, columns=['symmetry'], index=self.matches.index) else: self._masks = pd.DataFrame() return self._masks @masks.setter def masks(self, v): column_name = v[0] boolean_mask = v[1] self.masks[column_name] = boolean_mask""" def match(self, k=2, **kwargs): """ Loading Loading @@ -136,27 +120,6 @@ class Edge(dict, MutableMapping): def decompose_and_match(*args, **kwargs): pass """ def extract_subset(self, *args, **kwargs): self.compute_overlap() # Extract the source minx, maxx, miny, maxy = self['source_mbr'] xystart = (minx, miny) pixels=[minx, miny, maxx-minx, maxy-miny] node = self.source arr = node.geodata.read_array(pixels=pixels) node.extract_features(arr, xystart=xystart, *args, **kwargs) # Extract the destination minx, maxx, miny, maxy = self['destin_mbr'] xystart = (minx, miny) pixels=[minx, miny, maxx-minx, maxy-miny] node = self.destination arr = node.geodata.read_array(pixels=pixels) node.extract_features(arr, xystart=xystart, *args, **kwargs) """ def overlap_check(self): """Creates a mask for matches on the overlap""" if not (self["source_mbr"] and self["destin_mbr"]): Loading autocnet/graph/network.py +1 −3 Original line number Diff line number Diff line Loading @@ -860,8 +860,6 @@ class CandidateGraph(nx.Graph): # except: # return s.edges_iter([self.node[node]['image_path'] for node in nbunch], data=data) def subgraph_from_matches(self): """ Returns a sub-graph where all edges have matches. Loading Loading @@ -1103,7 +1101,7 @@ class CandidateGraph(nx.Graph): def identify_potential_overlaps(self, **kwargs): cc = control.identify_potential_overlaps(self, self.controlnetwork, **kwargs) print(cc) return cc def to_isis(self, outname, *args, **kwargs): serials = self.serials() Loading autocnet/graph/node.py +2 −144 Original line number Diff line number Diff line Loading @@ -66,8 +66,6 @@ class Node(dict, MutableMapping): self['node_id'] = node_id self['hash'] = image_name self._mask_arrays = {} self.point_to_correspondence = defaultdict(set) self.point_to_correspondence_df = None self.descriptors = None self.keypoints = pd.DataFrame() self.masks = pd.DataFrame() Loading Loading @@ -116,26 +114,13 @@ class Node(dict, MutableMapping): for k, v in d.items(): if isinstance(v, pd.DataFrame): if not v.equals(o[k]): print('NODE', k) eq = False elif isinstance(v, np.ndarray): if not v.all() == o[k].all(): print('NODE', k) eq = False return eq """ def __getitem__(self, item): attribute_dict = {'image_name': self['image_name'], 'image_path': self['image_path'], 'geodata': self.geodata, 'keypoints': self.keypoints, 'nkeypoints': self.nkeypoints, 'descriptors': self.descriptors, 'masks': self.masks, 'isis_serial': self.isis_serial} if item in attribute_dict.keys(): return attribute_dict[item] else: return super(Node, self).__getitem__(item) """ @property def geodata(self): Loading @@ -147,31 +132,6 @@ class Node(dict, MutableMapping): else: return None """ @property def masks(self): mask_lookup = {'suppression': 'suppression'} if self.keypoints is None: warnings.warn('Keypoints have not been extracted') return if not hasattr(self, '_masks'): self._masks = pd.DataFrame(index=self.keypoints.index) # If the mask is coming form another object that tracks # state, dynamically draw the mask from the object. for c in self._masks.columns: if c in mask_lookup: self._masks[c] = getattr(self, mask_lookup[c]).mask return self._masks @masks.setter def masks(self, v): column_name = v[0] boolean_mask = v[1] self.masks[column_name] = boolean_mask """ @property def footprint(self): if not getattr(self, '_footprint', None): Loading Loading @@ -461,108 +421,6 @@ class Node(dict, MutableMapping): io_keypoints.to_npy(self.keypoints, self.descriptors, out_path) def group_correspondences(self, cg, *args, deepen=False, **kwargs): """ Parameters ---------- cg : object The graph object this node is a member of deepen : bool If True, attempt to punch matches through to all incident edges. Default: False """ node = self['node_id'] # Get the edges incident to the current node incident_edges = set(cg.edges(node)).intersection(set(cg.edges())) # If this node is free floating, ignore it. if not incident_edges: # TODO: Add dangling correspondences to control network anyway. Subgraphs handle this segmentation if req. return try: clean_keys = kwargs['clean_keys'] except: clean_keys = [] # Grab all the incident edge matches and concatenate into a group match set. # All share the same source node edge_matches = [] for e in incident_edges: edge = cg[e[0]][e[1]] matches, mask = edge.clean(clean_keys=clean_keys) # Add a depth mask that initially mirrors the fundamental mask edge_matches.append(matches) d = pd.concat(edge_matches) # Counter for point identifiers pid = 0 # Iterate through all of the correspondences and attempt to add additional correspondences using # the epipolar constraint for idx, g in d.groupby('source_idx'): # Pull the source index to be used as the search source_idx = g['source_idx'].values[0] # Add the point object onto the node point = Point(pid) #print(g[['source_image', 'destination_image']]) covered_edges = list(map(tuple, g[['source_image', 'destination_image']].values)) s = g['source_image'].iat[0] d = g['destination_image'].iat[0] # The reference edge that we are deepening with ab = cg.edge[covered_edges[0][0]][covered_edges[0][1]] # Get the coordinates of the search correspondence ab_keypoints = ab.source.get_keypoint_coordinates(index=g['source_idx']) ab_x = None for j, (r_idx, r) in enumerate(g.iterrows()): kp = ab_keypoints.iloc[j].values # Homogenize the coord used for epipolar projection if ab_x is None: ab_x = np.array([kp[0], kp[1], 1.]) kpd = ab.destination.get_keypoint_coordinates(index=g['destination_idx']).values[0] # Add the existing source and destination correspondences self.point_to_correspondence[point].add((r['source_image'], Correspondence(r['source_idx'], kp[0], kp[1], serial=self.isis_serial))) self.point_to_correspondence[point].add((r['destination_image'], Correspondence(r['destination_idx'], kpd[0], kpd[1], serial=cg.node[r['destination_image']].isis_serial))) # If the user wants to punch correspondences through if deepen: search_edges = incident_edges.difference(set(covered_edges)) for search_edge in search_edges: bc = cg.edge[search_edge[0]][search_edge[1]] coords, idx = deepen_correspondences(ab_x, bc, source_idx) if coords is not None: cg.node[node].point_to_correspondence[point].add((search_edge[1], Correspondence(idx, coords[0], coords[1], serial=cg.node[search_edge[1]].isis_serial))) pid += 1 # Convert the dict to a dataframe data = [] for k, measures in self.point_to_correspondence.items(): for image_id, m in measures: data.append((k.point_id, k.point_type, m.serial, m.measure_type, m.x, m.y, image_id)) columns = ['point_id', 'point_type', 'serialnumber', 'measure_type', 'x', 'y', 'node_id'] self.point_to_correspondence_df = pd.DataFrame(data, columns=columns) def coverage_ratio(self, clean_keys=[]): """ Compute the ratio $area_{convexhull} / area_{total}$ Loading Loading
.travis.yml +1 −1 Original line number Diff line number Diff line Loading @@ -65,7 +65,7 @@ install: - python condaci.py setup script: - pytest --cov=autocnet - pytest autocnet functional_tests after_success: # Upload to anaconda and push to coveralls Loading
autocnet/control/tests/test_control.py +31 −103 Original line number Diff line number Diff line import os import sys import itertools from time import gmtime, strftime import pytest from unittest.mock import Mock, MagicMock import numpy as np from unittest.mock import MagicMock import geopandas as gpd import pandas as pd from shapely.geometry import Polygon sys.path.insert(0, os.path.abspath('..')) from autocnet.control import control @pytest.fixture def controlnetwork_data(): df = pd.DataFrame([[0, 0.0, 0.0, (0.0, 1.0), 0, 0.0, 0.0], [0, 1.0, 0.0, (0.0, 1.0), 0, 0.0, 0.0], [1, 0.0, 1.0, (0.0, 1.0), 1, 0.0, 0.0], [1, 1.0, 1.0, (0.0, 1.0), 1, 0.0, 0.0], [2, 0.0, 2.0, (0.0, 1.0), 2, 0.0, 0.0], [2, 1.0, 2.0, (0.0, 1.0), 2, 0.0, 0.0], [3, 0.0, 3.0, (0.0, 1.0), 3, 0.0, 0.0], [3, 1.0, 3.0, (0.0, 1.0), 3, 0.0, 0.0], [4, 0.0, 4.0, (0.0, 1.0), 4, 0.0, 0.0], [4, 1.0, 4.0, (0.0, 1.0), 4, 0.0, 0.0], [5, 0.0, 5.0, (0.0, 1.0), 5, 0.0, 0.0], [5, 1.0, 5.0, (0.0, 1.0), 5, 0.0, 0.0], [6, 0.0, 6.0, (0.0, 1.0), 6, 0.0, 0.0], [6, 1.0, 6.0, (0.0, 1.0), 6, 0.0, 0.0], [7, 0.0, 7.0, (0.0, 1.0), 7, 0.0, 0.0], [7, 1.0, 7.0, (0.0, 1.0), 7, 0.0, 0.0], [0, 2.0, 0.0, (0.0, 2.0), 0, 0.0, 0.0], [1, 2.0, 1.0, (0.0, 2.0), 1, 0.0, 0.0], [2, 2.0, 2.0, (0.0, 2.0), 2, 0.0, 0.0], [3, 2.0, 3.0, (0.0, 2.0), 3, 0.0, 0.0], [4, 2.0, 4.0, (0.0, 2.0), 4, 0.0, 0.0], [5, 2.0, 5.0, (0.0, 2.0), 5, 0.0, 0.0], [8, 0.0, 8.0, (0.0, 2.0), 6, 0.0, 0.0], [8, 2.0, 8.0, (0.0, 2.0), 6, 0.0, 0.0], [9, 0.0, 9.0, (0.0, 2.0), 7, 0.0, 0.0], [9, 2.0, 9.0, (0.0, 2.0), 7, 0.0, 0.0], [10, 1.0, 8.0, (1.0, 2.0), 6, 0.0, 0.0], [10, 2.0, 6.0, (1.0, 2.0), 6, 0.0, 0.0], [11, 1.0, 9.0, (1.0, 2.0), 7, 0.0, 0.0], [11, 2.0, 7.0, (1.0, 2.0), 7, 0.0, 0.0]], columns=['point_id', 'image_index', 'keypoint_index', 'edge', 'match_idx', 'x', 'y']) df.index.name = 'measure_id' #Fix types df['point_id'] = df['point_id'].astype(object) df['match_idx'] = df['match_idx'].astype(object) return df @pytest.fixture def candidategraph(): edges = [(0,1), (0,2), (1,2)] match_indices = [([0,1,2,3,4,5,6,7], [0,1,2,3,4,5,6,7]), ([0,1,2,3,4,5,8,9], [0,1,2,3,4,5,8,9]), ([0,1,2,3,4,5,8,9], [0,1,2,3,4,5,6,7])] matches = [] for i, e in enumerate(edges): c = match_indices[i] source_image = np.repeat(e[0], 8) destin_image = np.repeat(e[1], 8) coords = np.zeros(8) data = np.vstack((source_image, c[0], destin_image, c[1], coords, coords, coords, coords)).T matches_df = pd.DataFrame(data, columns=['source_image', 'source_idx', 'destination_image', 'destination_idx', 'source_x', 'source_y', 'destination_x', 'destination_y']) matches.append(matches_df) # Mock in the candidate graph cg = MagicMock() cg.get_matches = MagicMock(return_value=matches) return cg @pytest.fixture() def controlnetwork(controlnetwork_data): cn = control.ControlNetwork() cn.data = controlnetwork_data # Patching data this way does NOT update the internal _measure_id and _point_id attributes return cn @pytest.fixture() def bad_controlnetwork(controlnetwork_data): cn = control.ControlNetwork() cn.data = controlnetwork_data # Since the data is being patched in, fix the measure counter cn._measure_id = len(cn.data) + 1 # Add a duplicate measure in image 0 to point 0 cn.add_measure((0,11), (0,1), 2, [1,1], point_id=0) return cn import os import sys sys.path.insert(0, '..') from .. import control def test_fromcandidategraph(candidategraph, controlnetwork_data):#, controlnetwork): matches = candidategraph.get_matches() Loading Loading @@ -136,5 +42,27 @@ def test_bad_validate_points(bad_controlnetwork): assert bad_controlnetwork.validate_points().iloc[0] == False assert bad_controlnetwork.validate_points().iloc[1:].all() def test_potential_overlap(controlnetwork): pass def test_identify_potential_overlaps(controlnetwork, candidategraph): res = control.identify_potential_overlaps(candidategraph, controlnetwork, overlap=False) assert res.equals(pd.Series([(2,), (2,), (1,), (1,), (0,), (0,)], index=[6,7,8,9,10,11])) def test_potential_overlap(controlnetwork, candidategraph): # Patch in an intersection check so that all points intersect all geoms candidategraph.create_node_subgraph = MagicMock(return_value=candidategraph) coords = [(-1., -1.), (-1., 1.), (1., 1.), (1., -1.), (-1., -1.)] poly = gpd.GeoSeries(Polygon(coords)) candidategraph.compute_intersection = MagicMock(return_value=(poly, 0)) res = control.identify_potential_overlaps(candidategraph, controlnetwork, overlap=True) assert res.equals(pd.Series([(2,), (2,), (1,), (1,), (0,), (0,)], index=[6,7,8,9,10,11]))
autocnet/graph/edge.py +2 −39 Original line number Diff line number Diff line Loading @@ -67,33 +67,17 @@ class Edge(dict, MutableMapping): if not k in o.keys(): eq = False return eq if isinstance(v, pd.DataFrame): if not v.equals(o[k]): eq = False print(k) elif isinstance(v, np.ndarray): if not v.all() == o[k].all(): eq = False print(k) return eq """@property def masks(self): mask_lookup = {'fundamental': 'fundamental_matrix'} if not hasattr(self, '_masks'): if isinstance(self.matches, pd.DataFrame): self._masks = pd.DataFrame(True, columns=['symmetry'], index=self.matches.index) else: self._masks = pd.DataFrame() return self._masks @masks.setter def masks(self, v): column_name = v[0] boolean_mask = v[1] self.masks[column_name] = boolean_mask""" def match(self, k=2, **kwargs): """ Loading Loading @@ -136,27 +120,6 @@ class Edge(dict, MutableMapping): def decompose_and_match(*args, **kwargs): pass """ def extract_subset(self, *args, **kwargs): self.compute_overlap() # Extract the source minx, maxx, miny, maxy = self['source_mbr'] xystart = (minx, miny) pixels=[minx, miny, maxx-minx, maxy-miny] node = self.source arr = node.geodata.read_array(pixels=pixels) node.extract_features(arr, xystart=xystart, *args, **kwargs) # Extract the destination minx, maxx, miny, maxy = self['destin_mbr'] xystart = (minx, miny) pixels=[minx, miny, maxx-minx, maxy-miny] node = self.destination arr = node.geodata.read_array(pixels=pixels) node.extract_features(arr, xystart=xystart, *args, **kwargs) """ def overlap_check(self): """Creates a mask for matches on the overlap""" if not (self["source_mbr"] and self["destin_mbr"]): Loading
autocnet/graph/network.py +1 −3 Original line number Diff line number Diff line Loading @@ -860,8 +860,6 @@ class CandidateGraph(nx.Graph): # except: # return s.edges_iter([self.node[node]['image_path'] for node in nbunch], data=data) def subgraph_from_matches(self): """ Returns a sub-graph where all edges have matches. Loading Loading @@ -1103,7 +1101,7 @@ class CandidateGraph(nx.Graph): def identify_potential_overlaps(self, **kwargs): cc = control.identify_potential_overlaps(self, self.controlnetwork, **kwargs) print(cc) return cc def to_isis(self, outname, *args, **kwargs): serials = self.serials() Loading
autocnet/graph/node.py +2 −144 Original line number Diff line number Diff line Loading @@ -66,8 +66,6 @@ class Node(dict, MutableMapping): self['node_id'] = node_id self['hash'] = image_name self._mask_arrays = {} self.point_to_correspondence = defaultdict(set) self.point_to_correspondence_df = None self.descriptors = None self.keypoints = pd.DataFrame() self.masks = pd.DataFrame() Loading Loading @@ -116,26 +114,13 @@ class Node(dict, MutableMapping): for k, v in d.items(): if isinstance(v, pd.DataFrame): if not v.equals(o[k]): print('NODE', k) eq = False elif isinstance(v, np.ndarray): if not v.all() == o[k].all(): print('NODE', k) eq = False return eq """ def __getitem__(self, item): attribute_dict = {'image_name': self['image_name'], 'image_path': self['image_path'], 'geodata': self.geodata, 'keypoints': self.keypoints, 'nkeypoints': self.nkeypoints, 'descriptors': self.descriptors, 'masks': self.masks, 'isis_serial': self.isis_serial} if item in attribute_dict.keys(): return attribute_dict[item] else: return super(Node, self).__getitem__(item) """ @property def geodata(self): Loading @@ -147,31 +132,6 @@ class Node(dict, MutableMapping): else: return None """ @property def masks(self): mask_lookup = {'suppression': 'suppression'} if self.keypoints is None: warnings.warn('Keypoints have not been extracted') return if not hasattr(self, '_masks'): self._masks = pd.DataFrame(index=self.keypoints.index) # If the mask is coming form another object that tracks # state, dynamically draw the mask from the object. for c in self._masks.columns: if c in mask_lookup: self._masks[c] = getattr(self, mask_lookup[c]).mask return self._masks @masks.setter def masks(self, v): column_name = v[0] boolean_mask = v[1] self.masks[column_name] = boolean_mask """ @property def footprint(self): if not getattr(self, '_footprint', None): Loading Loading @@ -461,108 +421,6 @@ class Node(dict, MutableMapping): io_keypoints.to_npy(self.keypoints, self.descriptors, out_path) def group_correspondences(self, cg, *args, deepen=False, **kwargs): """ Parameters ---------- cg : object The graph object this node is a member of deepen : bool If True, attempt to punch matches through to all incident edges. Default: False """ node = self['node_id'] # Get the edges incident to the current node incident_edges = set(cg.edges(node)).intersection(set(cg.edges())) # If this node is free floating, ignore it. if not incident_edges: # TODO: Add dangling correspondences to control network anyway. Subgraphs handle this segmentation if req. return try: clean_keys = kwargs['clean_keys'] except: clean_keys = [] # Grab all the incident edge matches and concatenate into a group match set. # All share the same source node edge_matches = [] for e in incident_edges: edge = cg[e[0]][e[1]] matches, mask = edge.clean(clean_keys=clean_keys) # Add a depth mask that initially mirrors the fundamental mask edge_matches.append(matches) d = pd.concat(edge_matches) # Counter for point identifiers pid = 0 # Iterate through all of the correspondences and attempt to add additional correspondences using # the epipolar constraint for idx, g in d.groupby('source_idx'): # Pull the source index to be used as the search source_idx = g['source_idx'].values[0] # Add the point object onto the node point = Point(pid) #print(g[['source_image', 'destination_image']]) covered_edges = list(map(tuple, g[['source_image', 'destination_image']].values)) s = g['source_image'].iat[0] d = g['destination_image'].iat[0] # The reference edge that we are deepening with ab = cg.edge[covered_edges[0][0]][covered_edges[0][1]] # Get the coordinates of the search correspondence ab_keypoints = ab.source.get_keypoint_coordinates(index=g['source_idx']) ab_x = None for j, (r_idx, r) in enumerate(g.iterrows()): kp = ab_keypoints.iloc[j].values # Homogenize the coord used for epipolar projection if ab_x is None: ab_x = np.array([kp[0], kp[1], 1.]) kpd = ab.destination.get_keypoint_coordinates(index=g['destination_idx']).values[0] # Add the existing source and destination correspondences self.point_to_correspondence[point].add((r['source_image'], Correspondence(r['source_idx'], kp[0], kp[1], serial=self.isis_serial))) self.point_to_correspondence[point].add((r['destination_image'], Correspondence(r['destination_idx'], kpd[0], kpd[1], serial=cg.node[r['destination_image']].isis_serial))) # If the user wants to punch correspondences through if deepen: search_edges = incident_edges.difference(set(covered_edges)) for search_edge in search_edges: bc = cg.edge[search_edge[0]][search_edge[1]] coords, idx = deepen_correspondences(ab_x, bc, source_idx) if coords is not None: cg.node[node].point_to_correspondence[point].add((search_edge[1], Correspondence(idx, coords[0], coords[1], serial=cg.node[search_edge[1]].isis_serial))) pid += 1 # Convert the dict to a dataframe data = [] for k, measures in self.point_to_correspondence.items(): for image_id, m in measures: data.append((k.point_id, k.point_type, m.serial, m.measure_type, m.x, m.y, image_id)) columns = ['point_id', 'point_type', 'serialnumber', 'measure_type', 'x', 'y', 'node_id'] self.point_to_correspondence_df = pd.DataFrame(data, columns=columns) def coverage_ratio(self, clean_keys=[]): """ Compute the ratio $area_{convexhull} / area_{total}$ Loading