Loading autocnet/graph/network.py +222 −6 Original line number Diff line number Diff line Loading @@ -165,7 +165,7 @@ class CandidateGraph(nx.Graph): adjacency_dict[i.file_name].append(j.file_name) adjacency_dict[j.file_name].append(i.file_name) except: warnings.warn('Failed to calculated intersection between {} and {}'.format(i, j)) warnings.warn('Failed to calculate intersection between {} and {}'.format(i, j)) return cls(adjacency_dict) Loading Loading @@ -223,16 +223,234 @@ class CandidateGraph(nx.Graph): """ return self.node[node_index]['image_name'] def add_image(self, *args, **kwargs): def add_image(self, image_name, adjacency=None, basepath=None, apply_func=None): """ Adds an image node to the graph. Parameters ---------- image_name : str The file name of or path to the image to add adjacency : dict or path to json The an adjacency dictionary or json file containing the adjacency dictionary for the image basepath : str The directory path for the image apply_func : function A static function that takes an Edge as its parameter Function will be applied to all Edges generated when adding the image """ def add_node(img_pth): """ Adds a new, disconnected Node to the graph and returns a reference to it img_pth : The absolute path to the image Returns ------- Node : A reference to the added Node """ raise NotImplementedError self._order_adjacency() # Check that image path exists try: assert os.path.exists(img_pth) except AssertionError: raise FileNotFoundError("Could not add {} to CandidateGraph; " "File does not exist".format(img_pth)) # Grab the image name img_nm = os.path.basename(img_pth) # Get the node id & map [id -> Node] in the graph id = self.graph['node_counter'] node = Node(img_nm, img_pth, id) self.node[id] = node # Map [image name -> id] in the graph and increment node counter self.graph['node_name_map'][img_nm] = id self.graph['node_counter'] += 1 # Return the Node reference return node def detect_adjacency(src_node, target_nodes): """ Returns the target nodes, if any, that intersect with the source node src_node : Node The Node we are testing for adjacency target_nodes : Node List The list of Nodes that may intersect with the source Node Returns ------- Node List : The list of Nodes that intersect with the source Node """ # Get datasets from target nodes adjacent_nodes = list() valid_datasets = list() datasets = [node.geodata for node in target_nodes] # Make sure target nodes have valid footprints for ds in datasets: # Skip the source node if it's in the list of target nodes if ds.file_name == src_node['image_path']: continue # Grab footprints from nodes that have them fp = ds.footprint if fp and fp.IsValid(): valid_datasets.append(ds) else: warnings.warn('Missing or invalid geospatial data for ' '{}'.format(os.path.basename(ds.file_name))) # Grab the footprints and test for intersection for ds in valid_datasets: ds_file_name = os.path.basename(ds.file_name) try: if new_node.geodata.footprint.Intersects(ds.footprint): adjacent_nodes.append(ds_file_name) except: warnings.warn('Failed to calculate intersection between {} ' 'and {}'.format(image_name, ds_file_name)) # Return the adjacent Nodes for the source Node return adjacent_nodes # Check if image is already in the graph if image_name in self.graph['node_name_map']: warnings.warn("{} is already in the graph".format(image_name)) return # Basepath resolution if basepath: image_path = os.path.join(basepath, image_name) else: image_path = image_name image_name = os.path.basename(image_path) # Create new node within graph new_node = add_node(image_path) # If adjacency supplied, build list of adjacent nodes if adjacency: # Type check try: assert type(adjacency) is dict or type(adjacency) is str except AssertionError: raise TypeError("Named parameter 'adjacency' must be a dict or " "path to a json file containing the adjacency " "dict; Could not add {} to " "CandidateGraph".format(image_name)) # If a json is supplied, load as dict if type(adjacency) is not dict: try: assert os.path.exists(adjacency) except AssertionError: raise FileNotFoundError( "Could not load adjacency {}; " "File does not exist; Could not add {} to " "CandidateGraph".format(adjacency, image_name)) adjacency = io_json.read_json(adjacency) # Make sure added image is in adjacency dict try: assert image_name in adjacency.keys() except AssertionError: raise KeyError("Adjacency dict contains no key for" "{0}; Could not add {0} to " "CandidateGraph".format(image_name)) # Build list of adjacent images from dict adjacent_nodes = adjacency[image_name] # If adjacency not supplied, figure it out from footprints else: # Make sure new node has valid footprint; If not, it will be a # disconnected node on the graph if not new_node.geodata.footprint or not \ new_node.geodata.footprint.IsValid(): warnings.warn('Missing or invalid geospatial data for ' '{0}; {0} will be added to the CandidateGraph' 'as a disconnected Node'.format(image_name)) return # Detect adjacency between our new node and the CG's nodes adjacent_nodes = detect_adjacency(new_node, [self.node[idx] for idx in self.nodes()]) # Build new edge(s) for a_img in adjacent_nodes: # Must be string (image name) if isinstance(a_img, str): # If adjacent img is already in the graph if a_img in self.graph['node_name_map'].keys(): # Set the nodes for the new edge a_node_idx = self.graph['node_name_map'][a_img] s = self.node[a_node_idx] d = new_node # If adjacent img isnt already in graph, add it else: # Set the nodes for the new graph s = new_node d = add_node(os.path.join(basepath, a_img)) else: raise TypeError("Adjacency dict must have image names as keys " "and lists of adjacent image names as values; " "Could not add {} to " "CandidateGraph".format(image_name)) # Create the new edge new_edge = Edge(s, d) # If there's a clean func for the new edge, apply it if apply_func: ERR = "Named parameter 'apply_func' must be a static " \ "function or list of static functions; These " \ "function(s) are applied to all new edges generated " \ "when adding {0}; Could not add {0} to " \ "CandidateGraph".format(image_name) # Type Check try: assert callable(apply_func) or type(apply_func) is list except AssertionError: raise TypeError(ERR) # If it's a function, apply it if callable(apply_func): apply_func(new_edge) # If it's a list of functions, apply all of them else: for func in apply_func: try: assert callable(func) except AssertionError: raise TypeError(ERR) func(new_edge) # Grab node ids s_id = s['node_id'] d_id = d['node_id'] # Make sure source node is a key in the edge lookup # TODO: Is there a better way to do this? if s_id not in self.edge.keys(): self.edge[s_id] = dict() # Add the new edge to the graph self.edge[s_id][d_id] = new_edge def extract_features(self, band=1, *args, **kwargs): # pragma: no cover """ Loading Loading @@ -425,8 +643,6 @@ class CandidateGraph(nx.Graph): of keys in graph_masks """ return_lis = [] if callable(function): function = function.__name__ for s, d, edge in self.edges_iter(data=True): try: Loading Loading
autocnet/graph/network.py +222 −6 Original line number Diff line number Diff line Loading @@ -165,7 +165,7 @@ class CandidateGraph(nx.Graph): adjacency_dict[i.file_name].append(j.file_name) adjacency_dict[j.file_name].append(i.file_name) except: warnings.warn('Failed to calculated intersection between {} and {}'.format(i, j)) warnings.warn('Failed to calculate intersection between {} and {}'.format(i, j)) return cls(adjacency_dict) Loading Loading @@ -223,16 +223,234 @@ class CandidateGraph(nx.Graph): """ return self.node[node_index]['image_name'] def add_image(self, *args, **kwargs): def add_image(self, image_name, adjacency=None, basepath=None, apply_func=None): """ Adds an image node to the graph. Parameters ---------- image_name : str The file name of or path to the image to add adjacency : dict or path to json The an adjacency dictionary or json file containing the adjacency dictionary for the image basepath : str The directory path for the image apply_func : function A static function that takes an Edge as its parameter Function will be applied to all Edges generated when adding the image """ def add_node(img_pth): """ Adds a new, disconnected Node to the graph and returns a reference to it img_pth : The absolute path to the image Returns ------- Node : A reference to the added Node """ raise NotImplementedError self._order_adjacency() # Check that image path exists try: assert os.path.exists(img_pth) except AssertionError: raise FileNotFoundError("Could not add {} to CandidateGraph; " "File does not exist".format(img_pth)) # Grab the image name img_nm = os.path.basename(img_pth) # Get the node id & map [id -> Node] in the graph id = self.graph['node_counter'] node = Node(img_nm, img_pth, id) self.node[id] = node # Map [image name -> id] in the graph and increment node counter self.graph['node_name_map'][img_nm] = id self.graph['node_counter'] += 1 # Return the Node reference return node def detect_adjacency(src_node, target_nodes): """ Returns the target nodes, if any, that intersect with the source node src_node : Node The Node we are testing for adjacency target_nodes : Node List The list of Nodes that may intersect with the source Node Returns ------- Node List : The list of Nodes that intersect with the source Node """ # Get datasets from target nodes adjacent_nodes = list() valid_datasets = list() datasets = [node.geodata for node in target_nodes] # Make sure target nodes have valid footprints for ds in datasets: # Skip the source node if it's in the list of target nodes if ds.file_name == src_node['image_path']: continue # Grab footprints from nodes that have them fp = ds.footprint if fp and fp.IsValid(): valid_datasets.append(ds) else: warnings.warn('Missing or invalid geospatial data for ' '{}'.format(os.path.basename(ds.file_name))) # Grab the footprints and test for intersection for ds in valid_datasets: ds_file_name = os.path.basename(ds.file_name) try: if new_node.geodata.footprint.Intersects(ds.footprint): adjacent_nodes.append(ds_file_name) except: warnings.warn('Failed to calculate intersection between {} ' 'and {}'.format(image_name, ds_file_name)) # Return the adjacent Nodes for the source Node return adjacent_nodes # Check if image is already in the graph if image_name in self.graph['node_name_map']: warnings.warn("{} is already in the graph".format(image_name)) return # Basepath resolution if basepath: image_path = os.path.join(basepath, image_name) else: image_path = image_name image_name = os.path.basename(image_path) # Create new node within graph new_node = add_node(image_path) # If adjacency supplied, build list of adjacent nodes if adjacency: # Type check try: assert type(adjacency) is dict or type(adjacency) is str except AssertionError: raise TypeError("Named parameter 'adjacency' must be a dict or " "path to a json file containing the adjacency " "dict; Could not add {} to " "CandidateGraph".format(image_name)) # If a json is supplied, load as dict if type(adjacency) is not dict: try: assert os.path.exists(adjacency) except AssertionError: raise FileNotFoundError( "Could not load adjacency {}; " "File does not exist; Could not add {} to " "CandidateGraph".format(adjacency, image_name)) adjacency = io_json.read_json(adjacency) # Make sure added image is in adjacency dict try: assert image_name in adjacency.keys() except AssertionError: raise KeyError("Adjacency dict contains no key for" "{0}; Could not add {0} to " "CandidateGraph".format(image_name)) # Build list of adjacent images from dict adjacent_nodes = adjacency[image_name] # If adjacency not supplied, figure it out from footprints else: # Make sure new node has valid footprint; If not, it will be a # disconnected node on the graph if not new_node.geodata.footprint or not \ new_node.geodata.footprint.IsValid(): warnings.warn('Missing or invalid geospatial data for ' '{0}; {0} will be added to the CandidateGraph' 'as a disconnected Node'.format(image_name)) return # Detect adjacency between our new node and the CG's nodes adjacent_nodes = detect_adjacency(new_node, [self.node[idx] for idx in self.nodes()]) # Build new edge(s) for a_img in adjacent_nodes: # Must be string (image name) if isinstance(a_img, str): # If adjacent img is already in the graph if a_img in self.graph['node_name_map'].keys(): # Set the nodes for the new edge a_node_idx = self.graph['node_name_map'][a_img] s = self.node[a_node_idx] d = new_node # If adjacent img isnt already in graph, add it else: # Set the nodes for the new graph s = new_node d = add_node(os.path.join(basepath, a_img)) else: raise TypeError("Adjacency dict must have image names as keys " "and lists of adjacent image names as values; " "Could not add {} to " "CandidateGraph".format(image_name)) # Create the new edge new_edge = Edge(s, d) # If there's a clean func for the new edge, apply it if apply_func: ERR = "Named parameter 'apply_func' must be a static " \ "function or list of static functions; These " \ "function(s) are applied to all new edges generated " \ "when adding {0}; Could not add {0} to " \ "CandidateGraph".format(image_name) # Type Check try: assert callable(apply_func) or type(apply_func) is list except AssertionError: raise TypeError(ERR) # If it's a function, apply it if callable(apply_func): apply_func(new_edge) # If it's a list of functions, apply all of them else: for func in apply_func: try: assert callable(func) except AssertionError: raise TypeError(ERR) func(new_edge) # Grab node ids s_id = s['node_id'] d_id = d['node_id'] # Make sure source node is a key in the edge lookup # TODO: Is there a better way to do this? if s_id not in self.edge.keys(): self.edge[s_id] = dict() # Add the new edge to the graph self.edge[s_id][d_id] = new_edge def extract_features(self, band=1, *args, **kwargs): # pragma: no cover """ Loading Loading @@ -425,8 +643,6 @@ class CandidateGraph(nx.Graph): of keys in graph_masks """ return_lis = [] if callable(function): function = function.__name__ for s, d, edge in self.edges_iter(data=True): try: Loading