Loading autocnet/graph/network.py +56 −93 Original line number Diff line number Diff line Loading @@ -233,9 +233,8 @@ class CandidateGraph(nx.Graph): 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 adjacency : string or Node list The list of adjacent Nodes or image files for this image basepath : str The directory path for the image Loading Loading @@ -282,52 +281,6 @@ class CandidateGraph(nx.Graph): # 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)) Loading @@ -343,42 +296,22 @@ class CandidateGraph(nx.Graph): # Create new node within graph new_node = add_node(image_path) # If adjacency supplied, build list of adjacent nodes # If adjacency supplied make sure it's the right type 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: adjacency = os.path.join(basepath, adjacency) try: assert os.path.exists(adjacency) assert type(adjacency) is list 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 " raise TypeError("Named parameter 'adjacency' must be a list of" "adjacent Node objects or list of adjacent " "images; Could not add {} 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: # Create empty adjacency list adjacency = list() # 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 \ Loading @@ -389,12 +322,36 @@ class CandidateGraph(nx.Graph): 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()]) target_nodes = [self.node[idx] for idx in self.nodes()] valid_datasets = list() datasets = [node.geodata for node in target_nodes] # Build new edge(s) for a_img in adjacent_nodes: # Must be string (image name) # 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 == new_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): adjacency.append(ds_file_name) except: warnings.warn('Failed to calculate intersection between {} ' 'and {}'.format(image_name, ds_file_name)) # Build new edge(s) from adjacency for a_img in adjacency: # If 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(): Loading @@ -408,10 +365,23 @@ class CandidateGraph(nx.Graph): # Set the nodes for the new graph s = new_node d = add_node(os.path.join(basepath, a_img)) # If Node elif isinstance(a_img, Node): # If it's already in the graph, it'll be the source node, # since its idx is lower than our new node if a_img['image_name'] in [self.node[idx]['image_name'] for idx in self.nodes()]: s = a_img d = new_node # Otherwise, can't create edge else: warnings.warn("{0} is not in the graph; No Edge between" "{0} and {1} can be " "created".format(a_img['image_name'], image_name)) continue else: raise TypeError("Adjacency dict must have image names as keys " "and lists of adjacent image names as values; " "Could not add {} to " raise TypeError("Adjacency list contains Node objects or image " "names; Could not add {} to " "CandidateGraph".format(image_name)) # Create the new edge Loading @@ -427,27 +397,20 @@ class CandidateGraph(nx.Graph): # 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) [func(new_edge) for func in apply_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() Loading autocnet/graph/tests/test_network.py +27 −29 Original line number Diff line number Diff line Loading @@ -57,7 +57,7 @@ def test_size(graph): def test_add_image(graph): # apply_funcs # apply_func def extract_and_match(edge): for n in [edge.source, edge.destination]: n.extract_features(n.get_array(band=1), Loading @@ -72,8 +72,10 @@ def test_add_image(graph): # Test with all optional args cub_img = "AS15-M-0299_crop.cub" png_img = "AS15-M-0299_SML.png" cub_adj = {cub_img: ["AS15-M-0298_crop.cub", "AS15-M-0297_crop.cub"]} png_adj = {png_img: ["AS15-M-0298_crop.cub", "AS15-M-0297_crop.cub"]} cub_adj = ["AS15-M-0298_crop.cub", "AS15-M-0297_crop.cub"] png_adj = ["AS15-M-0298_crop.cub", "AS15-M-0297_crop.cub"] cang.add_image(cub_img, adjacency=cub_adj, basepath=basepath, apply_func=extract_and_match) Loading @@ -87,6 +89,11 @@ def test_add_image(graph): assert cang[0][2].destination['image_name'] == cang.edge[0][2].destination['image_name'] == cub_img assert cang[1][2].destination['image_name'] == cang.edge[1][2].destination['image_name'] == cub_img # Test when img is already in graph cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image(cub_adj[0], basepath=basepath) # Test for file not found with pytest.raises(FileNotFoundError): cang = network.CandidateGraph.from_adjacency(cube_adjacency, Loading @@ -105,35 +112,33 @@ def test_add_image(graph): cang.add_image(cub_img, basepath=basepath) # Autodetect # Test auto-detect when new node does not intersect # TODO: Need a non-intersecting cube file; network.py, lines 324-325 # Need a non-intersecting cube file # Test when img is already in graph # Test when adjacency is list of nodes cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image("AS15-M-0297_crop.cub", basepath=basepath) cub_adj2 = [cang.node[0], cang.node[1]] cang.add_image(cub_img, adjacency=cub_adj2, basepath=basepath) # Test when adjacency is of wrong type with pytest.raises(TypeError): # Test when an adjacency node is not in graph cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image(png_img, adjacency=1, basepath=basepath) # Invalid cub_adj2 = [cang.node[0], cang.node[1]] not_there = node.Node("_" + cub_img, os.path.join(basepath, cub_img), 15) adj = [not_there] edges_bf = cang.edges() cang.add_image(cub_img, adjacency=adj, basepath=basepath) assert cang.edges() == edges_bf # Should be no change in edges # Test when loading adjacency from json cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image(cub_img, adjacency='cube_adjacency.json', basepath=basepath) with pytest.raises(FileNotFoundError): # Test when adjacency is of wrong type with pytest.raises(TypeError): cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image(cub_img, adjacency='null.json', basepath=basepath) # Test when adjacency doesn't contain image as key with pytest.raises(KeyError): cang.add_image(png_img, adjacency=1, basepath=basepath) # Invalid with pytest.raises(TypeError): cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image(cub_img, adjacency='two_image_adjacency.json', basepath=basepath) cang.add_image(png_img, adjacency=[1], basepath=basepath) # Invalid # Test when no adjacency supplied, but image doesn't have footprint; # This results in a disconnected node added to the graph Loading @@ -145,14 +150,7 @@ def test_add_image(graph): # Test when adjacency includes a node not already in the graph adj = cub_adj adj[cub_img].append("AS15-M-0300_crop.cub") cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image(cub_img, adjacency=adj, basepath=basepath) # Test when adjacency includes a list of something other than image names adj[cub_img].append(1) with pytest.raises(TypeError): adj.append("AS15-M-0300_crop.cub") cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image(cub_img, adjacency=adj, basepath=basepath) Loading Loading
autocnet/graph/network.py +56 −93 Original line number Diff line number Diff line Loading @@ -233,9 +233,8 @@ class CandidateGraph(nx.Graph): 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 adjacency : string or Node list The list of adjacent Nodes or image files for this image basepath : str The directory path for the image Loading Loading @@ -282,52 +281,6 @@ class CandidateGraph(nx.Graph): # 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)) Loading @@ -343,42 +296,22 @@ class CandidateGraph(nx.Graph): # Create new node within graph new_node = add_node(image_path) # If adjacency supplied, build list of adjacent nodes # If adjacency supplied make sure it's the right type 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: adjacency = os.path.join(basepath, adjacency) try: assert os.path.exists(adjacency) assert type(adjacency) is list 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 " raise TypeError("Named parameter 'adjacency' must be a list of" "adjacent Node objects or list of adjacent " "images; Could not add {} 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: # Create empty adjacency list adjacency = list() # 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 \ Loading @@ -389,12 +322,36 @@ class CandidateGraph(nx.Graph): 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()]) target_nodes = [self.node[idx] for idx in self.nodes()] valid_datasets = list() datasets = [node.geodata for node in target_nodes] # Build new edge(s) for a_img in adjacent_nodes: # Must be string (image name) # 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 == new_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): adjacency.append(ds_file_name) except: warnings.warn('Failed to calculate intersection between {} ' 'and {}'.format(image_name, ds_file_name)) # Build new edge(s) from adjacency for a_img in adjacency: # If 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(): Loading @@ -408,10 +365,23 @@ class CandidateGraph(nx.Graph): # Set the nodes for the new graph s = new_node d = add_node(os.path.join(basepath, a_img)) # If Node elif isinstance(a_img, Node): # If it's already in the graph, it'll be the source node, # since its idx is lower than our new node if a_img['image_name'] in [self.node[idx]['image_name'] for idx in self.nodes()]: s = a_img d = new_node # Otherwise, can't create edge else: warnings.warn("{0} is not in the graph; No Edge between" "{0} and {1} can be " "created".format(a_img['image_name'], image_name)) continue else: raise TypeError("Adjacency dict must have image names as keys " "and lists of adjacent image names as values; " "Could not add {} to " raise TypeError("Adjacency list contains Node objects or image " "names; Could not add {} to " "CandidateGraph".format(image_name)) # Create the new edge Loading @@ -427,27 +397,20 @@ class CandidateGraph(nx.Graph): # 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) [func(new_edge) for func in apply_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() Loading
autocnet/graph/tests/test_network.py +27 −29 Original line number Diff line number Diff line Loading @@ -57,7 +57,7 @@ def test_size(graph): def test_add_image(graph): # apply_funcs # apply_func def extract_and_match(edge): for n in [edge.source, edge.destination]: n.extract_features(n.get_array(band=1), Loading @@ -72,8 +72,10 @@ def test_add_image(graph): # Test with all optional args cub_img = "AS15-M-0299_crop.cub" png_img = "AS15-M-0299_SML.png" cub_adj = {cub_img: ["AS15-M-0298_crop.cub", "AS15-M-0297_crop.cub"]} png_adj = {png_img: ["AS15-M-0298_crop.cub", "AS15-M-0297_crop.cub"]} cub_adj = ["AS15-M-0298_crop.cub", "AS15-M-0297_crop.cub"] png_adj = ["AS15-M-0298_crop.cub", "AS15-M-0297_crop.cub"] cang.add_image(cub_img, adjacency=cub_adj, basepath=basepath, apply_func=extract_and_match) Loading @@ -87,6 +89,11 @@ def test_add_image(graph): assert cang[0][2].destination['image_name'] == cang.edge[0][2].destination['image_name'] == cub_img assert cang[1][2].destination['image_name'] == cang.edge[1][2].destination['image_name'] == cub_img # Test when img is already in graph cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image(cub_adj[0], basepath=basepath) # Test for file not found with pytest.raises(FileNotFoundError): cang = network.CandidateGraph.from_adjacency(cube_adjacency, Loading @@ -105,35 +112,33 @@ def test_add_image(graph): cang.add_image(cub_img, basepath=basepath) # Autodetect # Test auto-detect when new node does not intersect # TODO: Need a non-intersecting cube file; network.py, lines 324-325 # Need a non-intersecting cube file # Test when img is already in graph # Test when adjacency is list of nodes cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image("AS15-M-0297_crop.cub", basepath=basepath) cub_adj2 = [cang.node[0], cang.node[1]] cang.add_image(cub_img, adjacency=cub_adj2, basepath=basepath) # Test when adjacency is of wrong type with pytest.raises(TypeError): # Test when an adjacency node is not in graph cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image(png_img, adjacency=1, basepath=basepath) # Invalid cub_adj2 = [cang.node[0], cang.node[1]] not_there = node.Node("_" + cub_img, os.path.join(basepath, cub_img), 15) adj = [not_there] edges_bf = cang.edges() cang.add_image(cub_img, adjacency=adj, basepath=basepath) assert cang.edges() == edges_bf # Should be no change in edges # Test when loading adjacency from json cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image(cub_img, adjacency='cube_adjacency.json', basepath=basepath) with pytest.raises(FileNotFoundError): # Test when adjacency is of wrong type with pytest.raises(TypeError): cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image(cub_img, adjacency='null.json', basepath=basepath) # Test when adjacency doesn't contain image as key with pytest.raises(KeyError): cang.add_image(png_img, adjacency=1, basepath=basepath) # Invalid with pytest.raises(TypeError): cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image(cub_img, adjacency='two_image_adjacency.json', basepath=basepath) cang.add_image(png_img, adjacency=[1], basepath=basepath) # Invalid # Test when no adjacency supplied, but image doesn't have footprint; # This results in a disconnected node added to the graph Loading @@ -145,14 +150,7 @@ def test_add_image(graph): # Test when adjacency includes a node not already in the graph adj = cub_adj adj[cub_img].append("AS15-M-0300_crop.cub") cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image(cub_img, adjacency=adj, basepath=basepath) # Test when adjacency includes a list of something other than image names adj[cub_img].append(1) with pytest.raises(TypeError): adj.append("AS15-M-0300_crop.cub") cang = network.CandidateGraph.from_adjacency(cube_adjacency, basepath=basepath) cang.add_image(cub_img, adjacency=adj, basepath=basepath) Loading