Loading autocnet/fileio/io_utils.py +9 −0 Original line number Diff line number Diff line Loading @@ -22,3 +22,12 @@ def delete_dir(dir): Remove a directory """ shutil.rmtree(dir) def file_to_list(file): with open(file, 'r') as f: file_list = f.readlines() file_list = map(str.rstrip, file_list) file_list = filter(None, file_list) return file_list autocnet/graph/network.py +31 −34 Original line number Diff line number Diff line import itertools import math import os import warnings import dill as pickle import networkx as nx import numpy as np import pandas as pd from autocnet.fileio.io_gdal import GeoDataset from autocnet.fileio import io_hdf from autocnet.control.control import C from autocnet.fileio import io_hdf from autocnet.fileio import io_json from autocnet.matcher.matcher import FlannMatcher import autocnet.matcher.suppression_funcs as spf from autocnet.fileio import io_utils from autocnet.fileio.io_gdal import GeoDataset from autocnet.graph import markov_cluster from autocnet.graph.edge import Edge from autocnet.graph.node import Node from autocnet.graph import markov_cluster from autocnet.matcher.matcher import FlannMatcher from autocnet.vis.graph_view import plot_graph Loading Loading @@ -44,7 +46,6 @@ class CandidateGraph(nx.Graph): self.node_counter = 0 node_labels = {} self.node_name_map = {} self.graph_masks = pd.DataFrame() for node_name in self.nodes(): image_name = os.path.basename(node_name) Loading Loading @@ -107,11 +108,8 @@ class CandidateGraph(nx.Graph): : object A Network graph object """ if not isinstance(filelist, list): with open(filelist, 'r') as f: filelist = f.readlines() filelist = map(str.rstrip, filelist) filelist = filter(None, filelist) if isinstance(filelist, str): filelist = io_utils.file_to_list(filelist) # TODO: Reject unsupported file formats + work with more file formats if basepath: Loading @@ -121,23 +119,28 @@ class CandidateGraph(nx.Graph): # This is brute force for now, could swap to an RTree at some point. adjacency_dict = {} valid_datasets = [] for i, j in itertools.permutations(datasets,2): if not i.file_name in adjacency_dict.keys(): for i in datasets: adjacency_dict[i.file_name] = [] if not j.file_name in adjacency_dict.keys(): adjacency_dict[j.file_name] = [] fp = i.footprint if fp and fp.IsValid(): valid_datasets.append(i) else: warnings.warn('Missing or invalid geospatial data for {}'.format(i.base_name)) # Grab the footprints and test for intersection for i, j in itertools.permutations(valid_datasets, 2): i_fp = i.footprint j_fp = j.footprint try: if j_fp and i_fp and i_fp.Intersects(j_fp): if i_fp.Intersects(j_fp): adjacency_dict[i.file_name].append(j.file_name) adjacency_dict[j.file_name].append(i.file_name) except: warnings.warn('No or incorrect geospatial information for {} and/or {}'.format(i, j)) warnings.warn('Failed to calculated intersection between {} and {}'.format(i, j)) return cls(adjacency_dict) Loading Loading @@ -300,6 +303,11 @@ class CandidateGraph(nx.Graph): descriptors = node.descriptors # Load the neighbors of the current node into the FLANN matcher neighbors = self.neighbors(i) # if node has no neighbors, skip if not neighbors: continue for n in neighbors: neighbor_descriptors = self.node[n].descriptors self._fl.add(neighbor_descriptors, n) Loading Loading @@ -365,7 +373,7 @@ class CandidateGraph(nx.Graph): """ _, self.clusters = func(self, *args, **kwargs) def apply_func_to_edges(self, function, *args, graph_mask_keys=[], **kwargs): def apply_func_to_edges(self, function, *args, **kwargs): """ Iterates over edges using an optional mask and and applies the given function. If func is not an attribute of Edge, raises AttributeError Loading @@ -376,20 +384,12 @@ class CandidateGraph(nx.Graph): graph_mask_keys : list of keys in graph_masks """ if graph_mask_keys: merged_graph_mask = self.graph_masks[graph_mask_keys].all(axis=1) edges_to_iter = merged_graph_mask[merged_graph_mask].index else: edges_to_iter = self.edges() if not isinstance(function, str): function = function.__name__ for s, d in edges_to_iter: curr_edge = self.get_edge_data(s, d) for s, d, edge in self.edges_iter(data=True): try: func = getattr(curr_edge, function) func = getattr(edge, function) except: raise AttributeError(function, ' is not an attribute of Edge') else: Loading @@ -406,11 +406,8 @@ class CandidateGraph(nx.Graph): boolean mask for edges in the minimum spanning tree """ graph_mask = pd.Series(False, index=self.edges()) self.graph_masks['mst'] = graph_mask mst = nx.minimum_spanning_tree(self) self.graph_masks['mst'][mst.edges()] = True return self.create_edge_subgraph(mst.edges()) def to_filelist(self): """ Loading autocnet/graph/tests/test_network.py +47 −29 Original line number Diff line number Diff line Loading @@ -4,6 +4,10 @@ sys.path.insert(0, os.path.abspath('..')) import unittest from unittest.mock import patch from unittest.mock import PropertyMock from osgeo import ogr import numpy as np from autocnet.examples import get_path Loading Loading @@ -42,20 +46,24 @@ class TestCandidateGraph(unittest.TestCase): def test_apply_func_to_edges(self): graph = self.graph.copy() graph.minimum_spanning_tree() mst_graph = graph.minimum_spanning_tree() try: graph.apply_func_to_edges('incorrect_func') except AttributeError: pass graph.extract_features(extractor_parameters={'nfeatures': 500}) graph.match_features() graph.apply_func_to_edges("symmetry_check", graph_mask_keys=['mst']) mst_graph.extract_features(extractor_parameters={'nfeatures': 500}) mst_graph.match_features() mst_graph.apply_func_to_edges("symmetry_check") self.assertFalse(graph[0][2].masks['symmetry'].all()) self.assertFalse(graph[0][1].masks['symmetry'].all()) try: self.assertTrue(graph[1][2].masks['symmetry'].all()) except: pass def test_connected_subgraphs(self): subgraph_list = self.disconnected_graph.connected_subgraphs() Loading Loading @@ -100,6 +108,21 @@ class TestCandidateGraph(unittest.TestCase): def test_fromlist(self): mock_list = ['AS15-M-0295_SML.png', 'AS15-M-0296_SML.png', 'AS15-M-0297_SML.png', 'AS15-M-0298_SML.png', 'AS15-M-0299_SML.png', 'AS15-M-0300_SML.png'] good_poly = ogr.CreateGeometryFromWkt('POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))') bad_poly = ogr.CreateGeometryFromWkt('POLYGON ((9999 10, 40 40, 20 40, 10 20, 30 10))') with patch('autocnet.fileio.io_gdal.GeoDataset.footprint', new_callable=PropertyMock) as patch_fp: patch_fp.return_value = good_poly n = network.CandidateGraph.from_filelist(mock_list, get_path('Apollo15')) self.assertEqual(n.number_of_nodes(), 6) self.assertEqual(n.number_of_edges(), 15) patch_fp.return_value = bad_poly n = network.CandidateGraph.from_filelist(mock_list, get_path('Apollo15')) self.assertEqual(n.number_of_nodes(), 6) self.assertEqual(n.number_of_edges(), 0) n = network.CandidateGraph.from_filelist(mock_list, get_path('Apollo15')) self.assertEqual(len(n.nodes()), 6) Loading Loading @@ -137,14 +160,8 @@ class TestCandidateGraph(unittest.TestCase): self.assertEqual(test_sub_graph.edges(), sub_graph_from_matches.edges()) def tearDown(self): pass class TestGraphMasks(unittest.TestCase): @classmethod def setUpClass(cls): cls.test_dict = {"0": ["4", "2", "1", "3"], def test_minimum_spanning_tree(self): test_dict = {"0": ["4", "2", "1", "3"], "1": ["0", "3", "2", "6", "5"], "2": ["1", "0", "3", "4", "7"], "3": ["2", "0", "1", "5"], Loading @@ -153,13 +170,14 @@ class TestGraphMasks(unittest.TestCase): "6": ["1"], "7": ["2"]} cls.graph = network.CandidateGraph.from_adjacency(cls.test_dict) cls.graph.minimum_spanning_tree() removed_edges = cls.graph.graph_masks['mst'][cls.graph.graph_masks['mst'] == False].index graph = network.CandidateGraph.from_adjacency(test_dict) mst_graph = graph.minimum_spanning_tree() print(len(mst_graph.edges())) self.assertEqual(sorted(mst_graph.nodes()), sorted(graph.nodes())) self.assertEqual(len(mst_graph.edges()), len(graph.edges())-5) cls.mst_graph = cls.graph.copy() cls.mst_graph.remove_edges_from(removed_edges) def test_mst_output(self): self.assertEqual(sorted(self.mst_graph.nodes()), sorted(self.graph.nodes())) self.assertEqual(self.mst_graph.number_of_edges(), self.graph.number_of_edges()-5) No newline at end of file def tearDown(self): pass notebooks/Tutorial with Visualization.ipynb +121 −57 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
autocnet/fileio/io_utils.py +9 −0 Original line number Diff line number Diff line Loading @@ -22,3 +22,12 @@ def delete_dir(dir): Remove a directory """ shutil.rmtree(dir) def file_to_list(file): with open(file, 'r') as f: file_list = f.readlines() file_list = map(str.rstrip, file_list) file_list = filter(None, file_list) return file_list
autocnet/graph/network.py +31 −34 Original line number Diff line number Diff line import itertools import math import os import warnings import dill as pickle import networkx as nx import numpy as np import pandas as pd from autocnet.fileio.io_gdal import GeoDataset from autocnet.fileio import io_hdf from autocnet.control.control import C from autocnet.fileio import io_hdf from autocnet.fileio import io_json from autocnet.matcher.matcher import FlannMatcher import autocnet.matcher.suppression_funcs as spf from autocnet.fileio import io_utils from autocnet.fileio.io_gdal import GeoDataset from autocnet.graph import markov_cluster from autocnet.graph.edge import Edge from autocnet.graph.node import Node from autocnet.graph import markov_cluster from autocnet.matcher.matcher import FlannMatcher from autocnet.vis.graph_view import plot_graph Loading Loading @@ -44,7 +46,6 @@ class CandidateGraph(nx.Graph): self.node_counter = 0 node_labels = {} self.node_name_map = {} self.graph_masks = pd.DataFrame() for node_name in self.nodes(): image_name = os.path.basename(node_name) Loading Loading @@ -107,11 +108,8 @@ class CandidateGraph(nx.Graph): : object A Network graph object """ if not isinstance(filelist, list): with open(filelist, 'r') as f: filelist = f.readlines() filelist = map(str.rstrip, filelist) filelist = filter(None, filelist) if isinstance(filelist, str): filelist = io_utils.file_to_list(filelist) # TODO: Reject unsupported file formats + work with more file formats if basepath: Loading @@ -121,23 +119,28 @@ class CandidateGraph(nx.Graph): # This is brute force for now, could swap to an RTree at some point. adjacency_dict = {} valid_datasets = [] for i, j in itertools.permutations(datasets,2): if not i.file_name in adjacency_dict.keys(): for i in datasets: adjacency_dict[i.file_name] = [] if not j.file_name in adjacency_dict.keys(): adjacency_dict[j.file_name] = [] fp = i.footprint if fp and fp.IsValid(): valid_datasets.append(i) else: warnings.warn('Missing or invalid geospatial data for {}'.format(i.base_name)) # Grab the footprints and test for intersection for i, j in itertools.permutations(valid_datasets, 2): i_fp = i.footprint j_fp = j.footprint try: if j_fp and i_fp and i_fp.Intersects(j_fp): if i_fp.Intersects(j_fp): adjacency_dict[i.file_name].append(j.file_name) adjacency_dict[j.file_name].append(i.file_name) except: warnings.warn('No or incorrect geospatial information for {} and/or {}'.format(i, j)) warnings.warn('Failed to calculated intersection between {} and {}'.format(i, j)) return cls(adjacency_dict) Loading Loading @@ -300,6 +303,11 @@ class CandidateGraph(nx.Graph): descriptors = node.descriptors # Load the neighbors of the current node into the FLANN matcher neighbors = self.neighbors(i) # if node has no neighbors, skip if not neighbors: continue for n in neighbors: neighbor_descriptors = self.node[n].descriptors self._fl.add(neighbor_descriptors, n) Loading Loading @@ -365,7 +373,7 @@ class CandidateGraph(nx.Graph): """ _, self.clusters = func(self, *args, **kwargs) def apply_func_to_edges(self, function, *args, graph_mask_keys=[], **kwargs): def apply_func_to_edges(self, function, *args, **kwargs): """ Iterates over edges using an optional mask and and applies the given function. If func is not an attribute of Edge, raises AttributeError Loading @@ -376,20 +384,12 @@ class CandidateGraph(nx.Graph): graph_mask_keys : list of keys in graph_masks """ if graph_mask_keys: merged_graph_mask = self.graph_masks[graph_mask_keys].all(axis=1) edges_to_iter = merged_graph_mask[merged_graph_mask].index else: edges_to_iter = self.edges() if not isinstance(function, str): function = function.__name__ for s, d in edges_to_iter: curr_edge = self.get_edge_data(s, d) for s, d, edge in self.edges_iter(data=True): try: func = getattr(curr_edge, function) func = getattr(edge, function) except: raise AttributeError(function, ' is not an attribute of Edge') else: Loading @@ -406,11 +406,8 @@ class CandidateGraph(nx.Graph): boolean mask for edges in the minimum spanning tree """ graph_mask = pd.Series(False, index=self.edges()) self.graph_masks['mst'] = graph_mask mst = nx.minimum_spanning_tree(self) self.graph_masks['mst'][mst.edges()] = True return self.create_edge_subgraph(mst.edges()) def to_filelist(self): """ Loading
autocnet/graph/tests/test_network.py +47 −29 Original line number Diff line number Diff line Loading @@ -4,6 +4,10 @@ sys.path.insert(0, os.path.abspath('..')) import unittest from unittest.mock import patch from unittest.mock import PropertyMock from osgeo import ogr import numpy as np from autocnet.examples import get_path Loading Loading @@ -42,20 +46,24 @@ class TestCandidateGraph(unittest.TestCase): def test_apply_func_to_edges(self): graph = self.graph.copy() graph.minimum_spanning_tree() mst_graph = graph.minimum_spanning_tree() try: graph.apply_func_to_edges('incorrect_func') except AttributeError: pass graph.extract_features(extractor_parameters={'nfeatures': 500}) graph.match_features() graph.apply_func_to_edges("symmetry_check", graph_mask_keys=['mst']) mst_graph.extract_features(extractor_parameters={'nfeatures': 500}) mst_graph.match_features() mst_graph.apply_func_to_edges("symmetry_check") self.assertFalse(graph[0][2].masks['symmetry'].all()) self.assertFalse(graph[0][1].masks['symmetry'].all()) try: self.assertTrue(graph[1][2].masks['symmetry'].all()) except: pass def test_connected_subgraphs(self): subgraph_list = self.disconnected_graph.connected_subgraphs() Loading Loading @@ -100,6 +108,21 @@ class TestCandidateGraph(unittest.TestCase): def test_fromlist(self): mock_list = ['AS15-M-0295_SML.png', 'AS15-M-0296_SML.png', 'AS15-M-0297_SML.png', 'AS15-M-0298_SML.png', 'AS15-M-0299_SML.png', 'AS15-M-0300_SML.png'] good_poly = ogr.CreateGeometryFromWkt('POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))') bad_poly = ogr.CreateGeometryFromWkt('POLYGON ((9999 10, 40 40, 20 40, 10 20, 30 10))') with patch('autocnet.fileio.io_gdal.GeoDataset.footprint', new_callable=PropertyMock) as patch_fp: patch_fp.return_value = good_poly n = network.CandidateGraph.from_filelist(mock_list, get_path('Apollo15')) self.assertEqual(n.number_of_nodes(), 6) self.assertEqual(n.number_of_edges(), 15) patch_fp.return_value = bad_poly n = network.CandidateGraph.from_filelist(mock_list, get_path('Apollo15')) self.assertEqual(n.number_of_nodes(), 6) self.assertEqual(n.number_of_edges(), 0) n = network.CandidateGraph.from_filelist(mock_list, get_path('Apollo15')) self.assertEqual(len(n.nodes()), 6) Loading Loading @@ -137,14 +160,8 @@ class TestCandidateGraph(unittest.TestCase): self.assertEqual(test_sub_graph.edges(), sub_graph_from_matches.edges()) def tearDown(self): pass class TestGraphMasks(unittest.TestCase): @classmethod def setUpClass(cls): cls.test_dict = {"0": ["4", "2", "1", "3"], def test_minimum_spanning_tree(self): test_dict = {"0": ["4", "2", "1", "3"], "1": ["0", "3", "2", "6", "5"], "2": ["1", "0", "3", "4", "7"], "3": ["2", "0", "1", "5"], Loading @@ -153,13 +170,14 @@ class TestGraphMasks(unittest.TestCase): "6": ["1"], "7": ["2"]} cls.graph = network.CandidateGraph.from_adjacency(cls.test_dict) cls.graph.minimum_spanning_tree() removed_edges = cls.graph.graph_masks['mst'][cls.graph.graph_masks['mst'] == False].index graph = network.CandidateGraph.from_adjacency(test_dict) mst_graph = graph.minimum_spanning_tree() print(len(mst_graph.edges())) self.assertEqual(sorted(mst_graph.nodes()), sorted(graph.nodes())) self.assertEqual(len(mst_graph.edges()), len(graph.edges())-5) cls.mst_graph = cls.graph.copy() cls.mst_graph.remove_edges_from(removed_edges) def test_mst_output(self): self.assertEqual(sorted(self.mst_graph.nodes()), sorted(self.graph.nodes())) self.assertEqual(self.mst_graph.number_of_edges(), self.graph.number_of_edges()-5) No newline at end of file def tearDown(self): pass
notebooks/Tutorial with Visualization.ipynb +121 −57 File changed.Preview size limit exceeded, changes collapsed. Show changes