Loading autocnet/__init__.py +1 −0 Changes for autocnet/__init__.py: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -52,4 +52,5 @@ def cuda(enable=False, gpu=0): from autocnet.matcher.cpu_decompose import decompose_and_match Edge.decompose_and_match = decompose_and_match cuda() autocnet/graph/edge.py +33 −268 Changes for autocnet/graph/edge.py: 33 added lines, 268 removed lines. Original line number Diff line number Diff line Loading @@ -12,7 +12,8 @@ from autocnet.matcher import health from autocnet.matcher import outlier_detector as od from autocnet.matcher import suppression_funcs as spf from autocnet.matcher import subpixel as sp from autocnet.transformation.transformations import FundamentalMatrix, Homography from autocnet.transformation import fundamental_matrix as fm from autocnet.transformation import homography as hm from autocnet.vis.graph_view import plot_edge, plot_node, plot_edge_decomposition from autocnet.cg import cg Loading @@ -38,8 +39,8 @@ class Edge(dict, MutableMapping): def __init__(self, source=None, destination=None): self.source = source self.destination = destination self.homography = None self.fundamental_matrix = None self['homography'] = None self['fundamental_matrix'] = None self.matches = None self['weight'] = {} Loading @@ -50,15 +51,20 @@ class Edge(dict, MutableMapping): Available Masks: {} """.format(self.source, self.destination, self.masks) def __getitem__(self, item): attribute_dict = {'source': self.source, 'destination': self.destination, 'masks': self.masks, 'weight': self['weight']} if item in attribute_dict.keys(): return attribute_dict[item] else: return super(Edge, self).__getitem__(item) def __eq__(self, other): eq = True d = self.__dict__ o = other.__dict__ for k, v in d.items(): if isinstance(v, pd.DataFrame): if not v.equals(o[k]): print('E', k, self.source['node_id'], self.destination['node_id']) eq = False elif isinstance(v, np.ndarray): if not v.all() == o[k].all(): eq = False print('E2', k) return eq @property def masks(self): Loading Loading @@ -88,247 +94,6 @@ class Edge(dict, MutableMapping): boolean_mask = v[1] self.masks[column_name] = boolean_mask def decompose_and_match(self, k=2, maxiteration=3, size=18, buf_dist=3,**kwargs): """ Similar to match, this method first decomposed the image into $4^{maxiteration}$ subimages and applys matching between each sub-image. This method is potential slower than the standard match due to the overhead in matching, but can be significantly more accurate. The increase in accuracy is a function of the total image size. Suggested values for maxiteration are provided below. Parameters ---------- k : int The number of neighbors to find method : {'coupled', 'whole'} whether to utilize coupled decomposition or match the whole image maxiteration : int When using coupled decomposition, the number of recursive divisions to apply. The total number of resultant sub-images will be 4 ** maxiteration. Approximate values: | Number of megapixels | maxiteration | |----------------------|--------------| | m < 10 |1-2| | 10 < m < 30 | 3 | | 30 < m < 100 | 4 | | 100 < m < 1000 | 5 | | m > 1000 | 6 | size : int When using coupled decomposition, the total number of points to check in each sub-image to try and find a match. Selection of this number is a balance between seeking a representative mid-point and computational cost. buf_dist : int When using coupled decomposition, the distance from the edge of the (sub)image a point must be in order to be used as a partioning point. The smaller the distance, the more likely percision errors can results in erroneous partitions. """ def mono_matches(a, b, aidx=None, bidx=None): """ Apply the FLANN match_features Parameters ---------- a : object A node object b : object A node object aidx : iterable An index for the descriptors to subset bidx : iterable An index for the descriptors to subset """ # Subset if requested if aidx is not None: ad = a.descriptors[aidx] else: ad = a.descriptors if bidx is not None: bd = b.descriptors[bidx] else: bd = b.descriptors # Load, train, and match fl.add(ad, a['node_id'], index=aidx) fl.train() matches = fl.query(bd, b['node_id'], k, index=bidx) self._add_matches(matches) fl.clear() def func(group): ratio = 0.8 res = [False] * len(group) if len(res) == 1: return [single] if group.iloc[0] < group.iloc[1] * ratio: res[0] = True return res # Grab the original image arrays sdata = self.source.get_array() ddata = self.destination.get_array() ssize = sdata.shape dsize = ddata.shape # Grab all the available candidate keypoints skp = self.source.get_keypoints() dkp = self.destination.get_keypoints() # Set up the membership arrays self.smembership = np.zeros(sdata.shape, dtype=np.int16) self.dmembership = np.zeros(ddata.shape, dtype=np.int16) self.smembership[:] = -1 self.dmembership[:] = -1 pcounter = 0 # FLANN Matcher fl= FlannMatcher() for k in range(maxiteration): partitions = np.unique(self.smembership) for p in partitions: sy_part, sx_part = np.where(self.smembership == p) dy_part, dx_part = np.where(self.dmembership == p) # Get the source extent minsy = np.min(sy_part) maxsy = np.max(sy_part) + 1 minsx = np.min(sx_part) maxsx = np.max(sx_part) + 1 # Get the destination extent mindy = np.min(dy_part) maxdy = np.max(dy_part) + 1 mindx = np.min(dx_part) maxdx = np.max(dx_part) + 1 # Clip the sub image from the full images asub = sdata[minsy:maxsy, minsx:maxsx] bsub = ddata[mindy:maxdy, mindx:maxdx] # Utilize the FLANN matcher to find a match to approximate a center fl.add(self.destination.descriptors, self.destination['node_id']) fl.train() scounter = 0 decompose = False while True: sub_skp = skp.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(minsx, maxsx, minsy, maxsy)) # Check the size to ensure a valid return if len(sub_skp) == 0: break # No valid keypoints in this (sub)image if size > len(sub_skp): size = len(sub_skp) candidate_idx = np.random.choice(sub_skp.index, size=size, replace=False) candidates = self.source.descriptors[candidate_idx] matches = fl.query(candidates, self.source['node_id'], k=3, index=candidate_idx) # Apply Lowe's ratio test to try to find a 'good' starting point mask = matches.groupby('source_idx')['distance'].transform(func).astype('bool') candidate_matches = matches[mask] match_idx = candidate_matches['source_idx'] # Extract those matches that pass the ratio check sub_skp = skp.iloc[match_idx] # Check that valid points remain if len(sub_skp) == 0: break # Locate the candidate closest to the middle of all of the matches smx, smy = sub_skp[['x', 'y']].mean() mid = np.array([[smx, smy]]) dists = cdist(mid, sub_skp[['x', 'y']]) closest = sub_skp.iloc[np.argmin(dists)] closest_idx = closest.name soriginx, soriginy = closest[['x', 'y']] # Grab the corresponding point in the destination q = candidate_matches.query('source_idx == {}'.format(closest.name)) dest_idx = q['destination_idx'].iat[0] doriginx = dkp.at[dest_idx, 'x'] doriginy = dkp.at[dest_idx, 'y'] if mindy + buf_dist <= doriginy <= maxdy - buf_dist\ and mindx + 3 <= doriginx <= maxdx - 3: # Point is good to split on decompose = True break else: scounter += 1 if scounter >= maxiteration: break # Clear the Flann matcher for reuse fl.clear() # Check that the identified match falls within the (sub)image # This catches most bad matches that have passed the ratio check if not (buf_dist <= doriginx - mindx <= bsub.shape[1] - buf_dist) or not\ (buf_dist <= doriginy - mindy <= bsub.shape[0] - buf_dist): decompose = False if decompose: # Apply coupled decomposition, shifting the origin to the sub-image s_submembership, d_submembership = coupled_decomposition(asub, bsub, sorigin=(soriginx - minsx, soriginy - minsy), dorigin=(doriginx - mindx, doriginy - mindy), **kwargs) # Shift the returned membership counters to a set of unique numbers s_submembership += pcounter d_submembership += pcounter # And assign membership self.smembership[minsy:maxsy, minsx:maxsx] = s_submembership self.dmembership[mindy:maxdy, mindx:maxdx] = d_submembership pcounter += 4 # Now match the decomposed segments to one another for p in np.unique(self.smembership): sy_part, sx_part = np.where(self.smembership == p) dy_part, dx_part = np.where(self.dmembership == p) # Get the source extent minsy = np.min(sy_part) maxsy = np.max(sy_part) + 1 minsx = np.min(sx_part) maxsx = np.max(sx_part) + 1 # Get the destination extent mindy = np.min(dy_part) maxdy = np.max(dy_part) + 1 mindx = np.min(dx_part) maxdx = np.max(dx_part) + 1 # Get the indices of the candidate keypoints within those regions / variables are pulled before decomp. sidx = skp.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(minsx, maxsx, minsy, maxsy)).index didx = dkp.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(mindx, maxdx, mindy, maxdy)).index # If the candidates < k, OpenCV throws an error if len(sidx) >= k and len(didx) >=k: mono_matches(self.source, self.destination, sidx, didx) mono_matches(self.destination, self.source, didx, sidx) ======= @property def health(self): return self._health.health def decompose_and_match(*args, **kwargs): pass Loading Loading @@ -399,19 +164,15 @@ class Edge(dict, MutableMapping): s_keypoints.index = matches.index d_keypoints.index = matches.index self.fundamental_matrix = FundamentalMatrix(np.zeros((3,3)), index=matches.index) self.fundamental_matrix.compute(s_keypoints, d_keypoints, **kwargs) self['fundamental_matrix'], fmask = fm.compute_fundamental_matrix(s_keypoints, d_keypoints, **kwargs) # Convert the truncated RANSAC mask back into a full length mask mask[mask] = self.fundamental_matrix.mask # Subscribe the health watcher to the fundamental matrix observable self.fundamental_matrix._notify_subscribers(self.fundamental_matrix) mask[mask] = fmask # Set the initial state of the fundamental mask in the masks self.masks = ('fundamental', mask) def refine_fundamental_matrix_matches(self, **kwargs): # pragma: no cover def refine_fundamental_matrix_matches(self, clean_keys=[], **kwargs): # pragma: no cover """ Given an estimated fundamental matrix, refine the correspondences based on the reprojective error. Loading @@ -423,8 +184,17 @@ class Edge(dict, MutableMapping): if not hasattr(self, 'fundamental_matrix'): raise AttributeError('No fundamental matrix exists for this edge.') return # TODO: Homogeneous is horribly inefficient here, use Numpy array notation s_keypoints = self.source.get_keypoint_coordinates(index=matches['source_idx'], homogeneous=True) d_keypoints = self.destination.get_keypoint_coordinates(index=matches['destination_idx'], homogeneous=True) self.fundamental_matrix.refine_matches(**kwargs) mask = update_fundamental_mask(self['fundamental_matrix'], s_keypoints, d_keypoints, index=self.matches.index, **kwargs) self.masks = ('fundamental', mask) def compute_homography(self, method='ransac', clean_keys=[], pid=None, **kwargs): """ Loading Loading @@ -456,17 +226,12 @@ class Edge(dict, MutableMapping): s_keypoints = self.source.get_keypoint_coordinates(index=matches['source_idx']) d_keypoints = self.destination.get_keypoint_coordinates(index=matches['destination_idx']) self.homography = Homography(np.zeros((3,3)), index=self.masks.index) self.homography.compute(s_keypoints.values, d_keypoints.values) self['homography'], hmask = hm.compute_homography(s_keypoints.values, d_keypoints.values) # Convert the truncated RANSAC mask back into a full length mask mask[mask] = self.homography.mask mask[mask] = hmask self.masks = ('ransac', mask) # Finalize the array to get custom attrs to propagate self.homography.__array_finalize__(self.homography) def subpixel_register(self, clean_keys=[], threshold=0.8, template_size=19, search_size=53, max_x_shift=1.0, max_y_shift=1.0, tiled=False, **kwargs): Loading autocnet/graph/mcl.pydeleted 100644 → 0 +0 −0 Empty file deleted. autocnet/graph/network.py +14 −8 Changes for autocnet/graph/network.py: 14 added lines, 8 removed lines. Original line number Diff line number Diff line Loading @@ -7,8 +7,7 @@ import dill as pickle import networkx as nx import pandas as pd from plio.io import io_hdf from plio.io import io_json from plio.io import io_hdf, io_json, io_autocnetgraph from plio.utils import utils as io_utils from plio.io.io_gdal import GeoDataset from autocnet.graph import markov_cluster Loading Loading @@ -70,6 +69,17 @@ class CandidateGraph(nx.Graph): self.graph['creationdate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) self.graph['modifieddate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) def __eq__(self, other): eq = True # Check the nodes for n in self.nodes_iter(): if not self.node[n] == other.node[n]: eq = False for s, d in self.edges_iter(): if not self.edge[s][d] == other.edge[s][d]: eq = False return eq @classmethod def from_graph(cls, graph): """ Loading Loading @@ -545,12 +555,7 @@ class CandidateGraph(nx.Graph): filename : str The relative or absolute PATH where the network is saved """ for i, node in self.nodes_iter(data=True): # Close the file handle because pickle doesn't handle SwigPyObjects node._handle = None with open(filename, 'wb') as f: pickle.dump(self, f, protocol=pickle.HIGHEST_PROTOCOL) io_autocnetgraph.save(self, filename) def plot(self, ax=None, **kwargs): # pragma: no cover """ Loading Loading @@ -670,6 +675,7 @@ class CandidateGraph(nx.Graph): bunch = set(self.nbunch_iter(nodes)) # create new graph and copy subgraph into it H = self.__class__() # copy node and attribute dictionaries for n in bunch: H.node[n] = self.node[n] Loading autocnet/graph/node.py +26 −10 Changes for autocnet/graph/node.py: 26 added lines, 10 removed lines. Original line number Diff line number Diff line Loading @@ -61,10 +61,11 @@ class Node(dict, MutableMapping): self['image_name'] = image_name self['image_path'] = image_path self['node_id'] = node_id self['hash'] = self['image_name'] #TODO: Repalce with farmhash self['hash'] = image_name self._mask_arrays = {} self.point_to_correspondence = defaultdict(set) self.point_to_correspondence_df = None self.descriptors = None def __repr__(self): return """ Loading @@ -77,10 +78,24 @@ class Node(dict, MutableMapping): """.format(self['node_id'], self['image_name'], self['image_path'], self.nkeypoints, self.masks, self.__class__) def __eq__(self, other): eq = True d = self.__dict__ o = other.__dict__ for k, v in d.items(): if isinstance(v, pd.DataFrame): if not v.equals(o[k]): eq = False print('N', k) elif isinstance(v, np.ndarray): if not v.all() == o[k].all(): eq = False print('N2', k) return eq """ def __getitem__(self, item): attribute_dict = {'image_name': self.image_name, 'image_path': self.image_path, attribute_dict = {'image_name': self['image_name'], 'image_path': self['image_path'], 'geodata': self.geodata, 'keypoints': self.keypoints, 'nkeypoints': self.nkeypoints, Loading @@ -92,6 +107,7 @@ class Node(dict, MutableMapping): else: return super(Node, self).__getitem__(item) """ @property def geodata(self): if not getattr(self, '_geodata', None) and self['image_path'] is not None: Loading Loading @@ -147,7 +163,7 @@ class Node(dict, MutableMapping): else: return 0 @property """ @property def keypoints(self): if hasattr(self, '_keypoints'): return self._keypoints.copy() Loading @@ -159,7 +175,7 @@ class Node(dict, MutableMapping): if hasattr(self, '_descriptors'): return np.copy(self._descriptors) else: return None return None""" def coverage(self): """ Loading Loading @@ -294,7 +310,7 @@ class Node(dict, MutableMapping): else: hdf = in_path self._descriptors = hdf['{}/descriptors'.format(self['image_name'])][:] self.descriptors = hdf['{}/descriptors'.format(self['image_name'])][:] raw_kps = hdf['{}/keypoints'.format(self['image_name'])][:] index = raw_kps['index'] clean_kps = utils.remove_field_name(raw_kps, 'index') Loading Loading @@ -334,17 +350,17 @@ class Node(dict, MutableMapping): else: hdf = out_path try: #try: hdf.create_dataset('{}/descriptors'.format(self['image_name']), data=self._descriptors, data=self.descriptors, compression=io_hdf.DEFAULT_COMPRESSION, compression_opts=io_hdf.DEFAULT_COMPRESSION_VALUE) hdf.create_dataset('{}/keypoints'.format(self['image_name']), data=hdf.df_to_sarray(self._keypoints.reset_index()), compression=io_hdf.DEFAULT_COMPRESSION, compression_opts=io_hdf.DEFAULT_COMPRESSION_VALUE) except: warnings.warn('Descriptors for the node {} are already stored'.format(self['image_name'])) #except: #warnings.warn('Descriptors for the node {} are already stored'.format(self['image_name'])) # If the out_path is a string, assume this method is being called as a singleton # and close the hdf file gracefully. If an object, let the instantiator of the Loading Loading
autocnet/__init__.py +1 −0 Changes for autocnet/__init__.py: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -52,4 +52,5 @@ def cuda(enable=False, gpu=0): from autocnet.matcher.cpu_decompose import decompose_and_match Edge.decompose_and_match = decompose_and_match cuda()
autocnet/graph/edge.py +33 −268 Changes for autocnet/graph/edge.py: 33 added lines, 268 removed lines. Original line number Diff line number Diff line Loading @@ -12,7 +12,8 @@ from autocnet.matcher import health from autocnet.matcher import outlier_detector as od from autocnet.matcher import suppression_funcs as spf from autocnet.matcher import subpixel as sp from autocnet.transformation.transformations import FundamentalMatrix, Homography from autocnet.transformation import fundamental_matrix as fm from autocnet.transformation import homography as hm from autocnet.vis.graph_view import plot_edge, plot_node, plot_edge_decomposition from autocnet.cg import cg Loading @@ -38,8 +39,8 @@ class Edge(dict, MutableMapping): def __init__(self, source=None, destination=None): self.source = source self.destination = destination self.homography = None self.fundamental_matrix = None self['homography'] = None self['fundamental_matrix'] = None self.matches = None self['weight'] = {} Loading @@ -50,15 +51,20 @@ class Edge(dict, MutableMapping): Available Masks: {} """.format(self.source, self.destination, self.masks) def __getitem__(self, item): attribute_dict = {'source': self.source, 'destination': self.destination, 'masks': self.masks, 'weight': self['weight']} if item in attribute_dict.keys(): return attribute_dict[item] else: return super(Edge, self).__getitem__(item) def __eq__(self, other): eq = True d = self.__dict__ o = other.__dict__ for k, v in d.items(): if isinstance(v, pd.DataFrame): if not v.equals(o[k]): print('E', k, self.source['node_id'], self.destination['node_id']) eq = False elif isinstance(v, np.ndarray): if not v.all() == o[k].all(): eq = False print('E2', k) return eq @property def masks(self): Loading Loading @@ -88,247 +94,6 @@ class Edge(dict, MutableMapping): boolean_mask = v[1] self.masks[column_name] = boolean_mask def decompose_and_match(self, k=2, maxiteration=3, size=18, buf_dist=3,**kwargs): """ Similar to match, this method first decomposed the image into $4^{maxiteration}$ subimages and applys matching between each sub-image. This method is potential slower than the standard match due to the overhead in matching, but can be significantly more accurate. The increase in accuracy is a function of the total image size. Suggested values for maxiteration are provided below. Parameters ---------- k : int The number of neighbors to find method : {'coupled', 'whole'} whether to utilize coupled decomposition or match the whole image maxiteration : int When using coupled decomposition, the number of recursive divisions to apply. The total number of resultant sub-images will be 4 ** maxiteration. Approximate values: | Number of megapixels | maxiteration | |----------------------|--------------| | m < 10 |1-2| | 10 < m < 30 | 3 | | 30 < m < 100 | 4 | | 100 < m < 1000 | 5 | | m > 1000 | 6 | size : int When using coupled decomposition, the total number of points to check in each sub-image to try and find a match. Selection of this number is a balance between seeking a representative mid-point and computational cost. buf_dist : int When using coupled decomposition, the distance from the edge of the (sub)image a point must be in order to be used as a partioning point. The smaller the distance, the more likely percision errors can results in erroneous partitions. """ def mono_matches(a, b, aidx=None, bidx=None): """ Apply the FLANN match_features Parameters ---------- a : object A node object b : object A node object aidx : iterable An index for the descriptors to subset bidx : iterable An index for the descriptors to subset """ # Subset if requested if aidx is not None: ad = a.descriptors[aidx] else: ad = a.descriptors if bidx is not None: bd = b.descriptors[bidx] else: bd = b.descriptors # Load, train, and match fl.add(ad, a['node_id'], index=aidx) fl.train() matches = fl.query(bd, b['node_id'], k, index=bidx) self._add_matches(matches) fl.clear() def func(group): ratio = 0.8 res = [False] * len(group) if len(res) == 1: return [single] if group.iloc[0] < group.iloc[1] * ratio: res[0] = True return res # Grab the original image arrays sdata = self.source.get_array() ddata = self.destination.get_array() ssize = sdata.shape dsize = ddata.shape # Grab all the available candidate keypoints skp = self.source.get_keypoints() dkp = self.destination.get_keypoints() # Set up the membership arrays self.smembership = np.zeros(sdata.shape, dtype=np.int16) self.dmembership = np.zeros(ddata.shape, dtype=np.int16) self.smembership[:] = -1 self.dmembership[:] = -1 pcounter = 0 # FLANN Matcher fl= FlannMatcher() for k in range(maxiteration): partitions = np.unique(self.smembership) for p in partitions: sy_part, sx_part = np.where(self.smembership == p) dy_part, dx_part = np.where(self.dmembership == p) # Get the source extent minsy = np.min(sy_part) maxsy = np.max(sy_part) + 1 minsx = np.min(sx_part) maxsx = np.max(sx_part) + 1 # Get the destination extent mindy = np.min(dy_part) maxdy = np.max(dy_part) + 1 mindx = np.min(dx_part) maxdx = np.max(dx_part) + 1 # Clip the sub image from the full images asub = sdata[minsy:maxsy, minsx:maxsx] bsub = ddata[mindy:maxdy, mindx:maxdx] # Utilize the FLANN matcher to find a match to approximate a center fl.add(self.destination.descriptors, self.destination['node_id']) fl.train() scounter = 0 decompose = False while True: sub_skp = skp.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(minsx, maxsx, minsy, maxsy)) # Check the size to ensure a valid return if len(sub_skp) == 0: break # No valid keypoints in this (sub)image if size > len(sub_skp): size = len(sub_skp) candidate_idx = np.random.choice(sub_skp.index, size=size, replace=False) candidates = self.source.descriptors[candidate_idx] matches = fl.query(candidates, self.source['node_id'], k=3, index=candidate_idx) # Apply Lowe's ratio test to try to find a 'good' starting point mask = matches.groupby('source_idx')['distance'].transform(func).astype('bool') candidate_matches = matches[mask] match_idx = candidate_matches['source_idx'] # Extract those matches that pass the ratio check sub_skp = skp.iloc[match_idx] # Check that valid points remain if len(sub_skp) == 0: break # Locate the candidate closest to the middle of all of the matches smx, smy = sub_skp[['x', 'y']].mean() mid = np.array([[smx, smy]]) dists = cdist(mid, sub_skp[['x', 'y']]) closest = sub_skp.iloc[np.argmin(dists)] closest_idx = closest.name soriginx, soriginy = closest[['x', 'y']] # Grab the corresponding point in the destination q = candidate_matches.query('source_idx == {}'.format(closest.name)) dest_idx = q['destination_idx'].iat[0] doriginx = dkp.at[dest_idx, 'x'] doriginy = dkp.at[dest_idx, 'y'] if mindy + buf_dist <= doriginy <= maxdy - buf_dist\ and mindx + 3 <= doriginx <= maxdx - 3: # Point is good to split on decompose = True break else: scounter += 1 if scounter >= maxiteration: break # Clear the Flann matcher for reuse fl.clear() # Check that the identified match falls within the (sub)image # This catches most bad matches that have passed the ratio check if not (buf_dist <= doriginx - mindx <= bsub.shape[1] - buf_dist) or not\ (buf_dist <= doriginy - mindy <= bsub.shape[0] - buf_dist): decompose = False if decompose: # Apply coupled decomposition, shifting the origin to the sub-image s_submembership, d_submembership = coupled_decomposition(asub, bsub, sorigin=(soriginx - minsx, soriginy - minsy), dorigin=(doriginx - mindx, doriginy - mindy), **kwargs) # Shift the returned membership counters to a set of unique numbers s_submembership += pcounter d_submembership += pcounter # And assign membership self.smembership[minsy:maxsy, minsx:maxsx] = s_submembership self.dmembership[mindy:maxdy, mindx:maxdx] = d_submembership pcounter += 4 # Now match the decomposed segments to one another for p in np.unique(self.smembership): sy_part, sx_part = np.where(self.smembership == p) dy_part, dx_part = np.where(self.dmembership == p) # Get the source extent minsy = np.min(sy_part) maxsy = np.max(sy_part) + 1 minsx = np.min(sx_part) maxsx = np.max(sx_part) + 1 # Get the destination extent mindy = np.min(dy_part) maxdy = np.max(dy_part) + 1 mindx = np.min(dx_part) maxdx = np.max(dx_part) + 1 # Get the indices of the candidate keypoints within those regions / variables are pulled before decomp. sidx = skp.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(minsx, maxsx, minsy, maxsy)).index didx = dkp.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(mindx, maxdx, mindy, maxdy)).index # If the candidates < k, OpenCV throws an error if len(sidx) >= k and len(didx) >=k: mono_matches(self.source, self.destination, sidx, didx) mono_matches(self.destination, self.source, didx, sidx) ======= @property def health(self): return self._health.health def decompose_and_match(*args, **kwargs): pass Loading Loading @@ -399,19 +164,15 @@ class Edge(dict, MutableMapping): s_keypoints.index = matches.index d_keypoints.index = matches.index self.fundamental_matrix = FundamentalMatrix(np.zeros((3,3)), index=matches.index) self.fundamental_matrix.compute(s_keypoints, d_keypoints, **kwargs) self['fundamental_matrix'], fmask = fm.compute_fundamental_matrix(s_keypoints, d_keypoints, **kwargs) # Convert the truncated RANSAC mask back into a full length mask mask[mask] = self.fundamental_matrix.mask # Subscribe the health watcher to the fundamental matrix observable self.fundamental_matrix._notify_subscribers(self.fundamental_matrix) mask[mask] = fmask # Set the initial state of the fundamental mask in the masks self.masks = ('fundamental', mask) def refine_fundamental_matrix_matches(self, **kwargs): # pragma: no cover def refine_fundamental_matrix_matches(self, clean_keys=[], **kwargs): # pragma: no cover """ Given an estimated fundamental matrix, refine the correspondences based on the reprojective error. Loading @@ -423,8 +184,17 @@ class Edge(dict, MutableMapping): if not hasattr(self, 'fundamental_matrix'): raise AttributeError('No fundamental matrix exists for this edge.') return # TODO: Homogeneous is horribly inefficient here, use Numpy array notation s_keypoints = self.source.get_keypoint_coordinates(index=matches['source_idx'], homogeneous=True) d_keypoints = self.destination.get_keypoint_coordinates(index=matches['destination_idx'], homogeneous=True) self.fundamental_matrix.refine_matches(**kwargs) mask = update_fundamental_mask(self['fundamental_matrix'], s_keypoints, d_keypoints, index=self.matches.index, **kwargs) self.masks = ('fundamental', mask) def compute_homography(self, method='ransac', clean_keys=[], pid=None, **kwargs): """ Loading Loading @@ -456,17 +226,12 @@ class Edge(dict, MutableMapping): s_keypoints = self.source.get_keypoint_coordinates(index=matches['source_idx']) d_keypoints = self.destination.get_keypoint_coordinates(index=matches['destination_idx']) self.homography = Homography(np.zeros((3,3)), index=self.masks.index) self.homography.compute(s_keypoints.values, d_keypoints.values) self['homography'], hmask = hm.compute_homography(s_keypoints.values, d_keypoints.values) # Convert the truncated RANSAC mask back into a full length mask mask[mask] = self.homography.mask mask[mask] = hmask self.masks = ('ransac', mask) # Finalize the array to get custom attrs to propagate self.homography.__array_finalize__(self.homography) def subpixel_register(self, clean_keys=[], threshold=0.8, template_size=19, search_size=53, max_x_shift=1.0, max_y_shift=1.0, tiled=False, **kwargs): Loading
autocnet/graph/network.py +14 −8 Changes for autocnet/graph/network.py: 14 added lines, 8 removed lines. Original line number Diff line number Diff line Loading @@ -7,8 +7,7 @@ import dill as pickle import networkx as nx import pandas as pd from plio.io import io_hdf from plio.io import io_json from plio.io import io_hdf, io_json, io_autocnetgraph from plio.utils import utils as io_utils from plio.io.io_gdal import GeoDataset from autocnet.graph import markov_cluster Loading Loading @@ -70,6 +69,17 @@ class CandidateGraph(nx.Graph): self.graph['creationdate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) self.graph['modifieddate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) def __eq__(self, other): eq = True # Check the nodes for n in self.nodes_iter(): if not self.node[n] == other.node[n]: eq = False for s, d in self.edges_iter(): if not self.edge[s][d] == other.edge[s][d]: eq = False return eq @classmethod def from_graph(cls, graph): """ Loading Loading @@ -545,12 +555,7 @@ class CandidateGraph(nx.Graph): filename : str The relative or absolute PATH where the network is saved """ for i, node in self.nodes_iter(data=True): # Close the file handle because pickle doesn't handle SwigPyObjects node._handle = None with open(filename, 'wb') as f: pickle.dump(self, f, protocol=pickle.HIGHEST_PROTOCOL) io_autocnetgraph.save(self, filename) def plot(self, ax=None, **kwargs): # pragma: no cover """ Loading Loading @@ -670,6 +675,7 @@ class CandidateGraph(nx.Graph): bunch = set(self.nbunch_iter(nodes)) # create new graph and copy subgraph into it H = self.__class__() # copy node and attribute dictionaries for n in bunch: H.node[n] = self.node[n] Loading
autocnet/graph/node.py +26 −10 Changes for autocnet/graph/node.py: 26 added lines, 10 removed lines. Original line number Diff line number Diff line Loading @@ -61,10 +61,11 @@ class Node(dict, MutableMapping): self['image_name'] = image_name self['image_path'] = image_path self['node_id'] = node_id self['hash'] = self['image_name'] #TODO: Repalce with farmhash self['hash'] = image_name self._mask_arrays = {} self.point_to_correspondence = defaultdict(set) self.point_to_correspondence_df = None self.descriptors = None def __repr__(self): return """ Loading @@ -77,10 +78,24 @@ class Node(dict, MutableMapping): """.format(self['node_id'], self['image_name'], self['image_path'], self.nkeypoints, self.masks, self.__class__) def __eq__(self, other): eq = True d = self.__dict__ o = other.__dict__ for k, v in d.items(): if isinstance(v, pd.DataFrame): if not v.equals(o[k]): eq = False print('N', k) elif isinstance(v, np.ndarray): if not v.all() == o[k].all(): eq = False print('N2', k) return eq """ def __getitem__(self, item): attribute_dict = {'image_name': self.image_name, 'image_path': self.image_path, attribute_dict = {'image_name': self['image_name'], 'image_path': self['image_path'], 'geodata': self.geodata, 'keypoints': self.keypoints, 'nkeypoints': self.nkeypoints, Loading @@ -92,6 +107,7 @@ class Node(dict, MutableMapping): else: return super(Node, self).__getitem__(item) """ @property def geodata(self): if not getattr(self, '_geodata', None) and self['image_path'] is not None: Loading Loading @@ -147,7 +163,7 @@ class Node(dict, MutableMapping): else: return 0 @property """ @property def keypoints(self): if hasattr(self, '_keypoints'): return self._keypoints.copy() Loading @@ -159,7 +175,7 @@ class Node(dict, MutableMapping): if hasattr(self, '_descriptors'): return np.copy(self._descriptors) else: return None return None""" def coverage(self): """ Loading Loading @@ -294,7 +310,7 @@ class Node(dict, MutableMapping): else: hdf = in_path self._descriptors = hdf['{}/descriptors'.format(self['image_name'])][:] self.descriptors = hdf['{}/descriptors'.format(self['image_name'])][:] raw_kps = hdf['{}/keypoints'.format(self['image_name'])][:] index = raw_kps['index'] clean_kps = utils.remove_field_name(raw_kps, 'index') Loading Loading @@ -334,17 +350,17 @@ class Node(dict, MutableMapping): else: hdf = out_path try: #try: hdf.create_dataset('{}/descriptors'.format(self['image_name']), data=self._descriptors, data=self.descriptors, compression=io_hdf.DEFAULT_COMPRESSION, compression_opts=io_hdf.DEFAULT_COMPRESSION_VALUE) hdf.create_dataset('{}/keypoints'.format(self['image_name']), data=hdf.df_to_sarray(self._keypoints.reset_index()), compression=io_hdf.DEFAULT_COMPRESSION, compression_opts=io_hdf.DEFAULT_COMPRESSION_VALUE) except: warnings.warn('Descriptors for the node {} are already stored'.format(self['image_name'])) #except: #warnings.warn('Descriptors for the node {} are already stored'.format(self['image_name'])) # If the out_path is a string, assume this method is being called as a singleton # and close the hdf file gracefully. If an object, let the instantiator of the Loading