Loading autocnet/graph/markov_cluster.py 0 → 100644 +108 −0 Original line number Diff line number Diff line import numpy as np import networkx as nx def mcl(g, expand_factor=2, inflate_factor=2, max_loop=10, mult_factor=1): """ Markov Cluster Algorithm Implementation modified from: https://github.com/koteth/python_mcl Originally released under the MIT license (https://opensource.org/licenses/MIT) Parameters ---------- g : object or ndarray NetworkX graph object or adjacency matrix inflate_factor : float Parameter to strengthen and weaken flow between nodes. The larger the value the more granular the resultant clusters are. expand_factor : int Parameter to manage flow connection between different regions of the graph. mult_factor : int Value to set for self loops. max_loop : int Number of iterations to perform before terminating (or convergence). Returns ------- arr : ndarray arr normalized flow matrix computed after convergence or max_loop is exceeded. clusters : dict of clusters where the key is an arbitrary cluster identifier and the value is a list of node identifiers. References ---------- [Stijn2000]_ [Stijn2000a]_ """ def _normalize(arr): """ Column normalize an array Parameters ---------- arr : ndarray array to be normalized Returns ------- new_matrix : ndarray normalized array """ column_sums = arr.sum(axis=0) new_matrix = arr / column_sums[np.newaxis, :] return new_matrix def _inflate(arr, inflate_factor): return _normalize(np.power(arr, inflate_factor)) def _expand(arr, expand_factor): return np.linalg.matrix_power(arr, expand_factor) def _add_diag(arr, mult_factor): return arr + mult_factor * np.identity(arr.shape[0]) def _stop(arr, i): if i%5==4: m = np.max(arr ** 2 - arr) - np.min(arr ** 2 - arr) if m==0: return True return False def _get_clusters(arr): clusters = {} cid = 0 for j in arr: row_positive = np.nonzero(j)[0].tolist() if row_positive and not row_positive in clusters.values(): clusters[cid] = row_positive cid += 1 return clusters # Create a dense adjacency matrix if isinstance(g, nx.Graph): arr = np.array(nx.adjacency_matrix(g).todense()) elif isinstance(g, np.ndarray): arr = g arr = _add_diag(arr, mult_factor) arr = _normalize(arr) for i in range(max_loop): #logging.info("loop", i) arr = _inflate(arr, inflate_factor) arr = _expand(arr, expand_factor) # Check for convergence if _stop(arr, i): break clusters = _get_clusters(arr) return arr, clusters autocnet/graph/mcl.py 0 → 100644 +0 −0 Empty file added. autocnet/graph/network.py +42 −41 Original line number Diff line number Diff line from functools import singledispatch import itertools import os import dill as pickle Loading @@ -13,6 +14,7 @@ from autocnet.matcher.matcher import FlannMatcher import autocnet.matcher.suppression_funcs as spf from autocnet.graph.edge import Edge from autocnet.graph.node import Node from autocnet.graph import markov_cluster from autocnet.vis.graph_view import plot_graph Loading @@ -28,8 +30,11 @@ class CandidateGraph(nx.Graph): The number of nodes in the graph. node_name_map : dict The mapping of image labels (i.e. file base names) to their corresponding node indices. corresponding node indices clusters : dict of clusters with key as the cluster id and value as a list of node indices ---------- """ edge_attr_dict_factory = Edge Loading Loading @@ -169,44 +174,6 @@ class CandidateGraph(nx.Graph): """ return self.node[node_index].image_name def get_node(self, node_name): """ Get the node with the given name. Parameters ---------- node_name : str The name of the node. Returns ------- : object The node with the given image name. """ return self.node[self.node_name_map[node_name]] def get_keypoints(self, nodekey): """ Get the list of keypoints for the given node. Parameters ---------- nodeIndex : int or string The key for the node, by index or name. Returns ------- : list The list of keypoints for the given node. """ try: return self.get_node(nodekey).keypoints except: return self.node[nodekey].keypoints def add_image(self, *args, **kwargs): """ Adds an image node to the graph. Loading @@ -217,8 +184,6 @@ class CandidateGraph(nx.Graph): """ raise NotImplementedError self.add_node(self.node_counter, *args, **kwargs) self.node_counter += 1 def extract_features(self, method='orb', extractor_parameters={}): """ Loading Loading @@ -307,6 +272,25 @@ class CandidateGraph(nx.Graph): else: edge.matches = dest_group def compute_clusters(self, func=markov_cluster.mcl, *args, **kwargs): """ Apply some graph clustering algorithm to compute a subset of the global graph. Parameters ---------- func : object The clustering function to be applied. Defaults to Markov Clustering Algorithm args : list of arguments to be passed through to the func kwargs : dict of keyword arguments to be passed through to the func """ _, self.clusters = func(self, *args, **kwargs) def symmetry_checks(self): """ Perform a symmetry check on all edges in the graph Loading Loading @@ -583,3 +567,20 @@ class CandidateGraph(nx.Graph): A MatPlotLib axes object """ return plot_graph(self, ax=ax, **kwargs) @singledispatch def create_subgraph(self, arg, key=None): if not hasattr(self, 'clusters'): raise AttributeError('No clusters have been computed for this graph.') nodes_in_cluster = self.clusters[arg] subgraph = self.subgraph(nodes_in_cluster) return subgraph @create_subgraph.register(pd.DataFrame) def _(self, arg, g, key=None): if key == None: return col = arg[key] nodes = col[col == True].index subgraph = g.subgraph(nodes) return subgraph No newline at end of file autocnet/graph/tests/test_markov_cluster.py 0 → 100644 +28 −0 Original line number Diff line number Diff line import unittest import numpy as np import networkx as nx from .. import markov_cluster from autocnet.examples import get_path from autocnet.graph.network import CandidateGraph class TestMarkovCluster(unittest.TestCase): def setUp(self): self.g = CandidateGraph.from_graph(get_path('sixty_four_apollo.graph')) def test_mcl_from_network(self): self.g.compute_clusters(inflate_factor=15) self.assertIsInstance(self.g.clusters, dict) self.assertEqual(len(self.g.clusters), 14) def test_mcl_from_adj_matrix(self): arr = np.array(nx.adjacency_matrix(self.g).todense()) flow, clusters = markov_cluster.mcl(arr) self.assertIsInstance(clusters, dict) self.assertEqual(len(clusters), 3) autocnet/graph/tests/test_network.py +0 −1 Original line number Diff line number Diff line Loading @@ -49,7 +49,6 @@ class TestCandidateGraph(unittest.TestCase): subgraph_list = self.graph.connected_subgraphs() self.assertEqual(len(subgraph_list), 1) def test_save_load(self): self.graph.save('test_save.cg') loaded = self.graph.from_graph('test_save.cg') Loading Loading
autocnet/graph/markov_cluster.py 0 → 100644 +108 −0 Original line number Diff line number Diff line import numpy as np import networkx as nx def mcl(g, expand_factor=2, inflate_factor=2, max_loop=10, mult_factor=1): """ Markov Cluster Algorithm Implementation modified from: https://github.com/koteth/python_mcl Originally released under the MIT license (https://opensource.org/licenses/MIT) Parameters ---------- g : object or ndarray NetworkX graph object or adjacency matrix inflate_factor : float Parameter to strengthen and weaken flow between nodes. The larger the value the more granular the resultant clusters are. expand_factor : int Parameter to manage flow connection between different regions of the graph. mult_factor : int Value to set for self loops. max_loop : int Number of iterations to perform before terminating (or convergence). Returns ------- arr : ndarray arr normalized flow matrix computed after convergence or max_loop is exceeded. clusters : dict of clusters where the key is an arbitrary cluster identifier and the value is a list of node identifiers. References ---------- [Stijn2000]_ [Stijn2000a]_ """ def _normalize(arr): """ Column normalize an array Parameters ---------- arr : ndarray array to be normalized Returns ------- new_matrix : ndarray normalized array """ column_sums = arr.sum(axis=0) new_matrix = arr / column_sums[np.newaxis, :] return new_matrix def _inflate(arr, inflate_factor): return _normalize(np.power(arr, inflate_factor)) def _expand(arr, expand_factor): return np.linalg.matrix_power(arr, expand_factor) def _add_diag(arr, mult_factor): return arr + mult_factor * np.identity(arr.shape[0]) def _stop(arr, i): if i%5==4: m = np.max(arr ** 2 - arr) - np.min(arr ** 2 - arr) if m==0: return True return False def _get_clusters(arr): clusters = {} cid = 0 for j in arr: row_positive = np.nonzero(j)[0].tolist() if row_positive and not row_positive in clusters.values(): clusters[cid] = row_positive cid += 1 return clusters # Create a dense adjacency matrix if isinstance(g, nx.Graph): arr = np.array(nx.adjacency_matrix(g).todense()) elif isinstance(g, np.ndarray): arr = g arr = _add_diag(arr, mult_factor) arr = _normalize(arr) for i in range(max_loop): #logging.info("loop", i) arr = _inflate(arr, inflate_factor) arr = _expand(arr, expand_factor) # Check for convergence if _stop(arr, i): break clusters = _get_clusters(arr) return arr, clusters
autocnet/graph/network.py +42 −41 Original line number Diff line number Diff line from functools import singledispatch import itertools import os import dill as pickle Loading @@ -13,6 +14,7 @@ from autocnet.matcher.matcher import FlannMatcher import autocnet.matcher.suppression_funcs as spf from autocnet.graph.edge import Edge from autocnet.graph.node import Node from autocnet.graph import markov_cluster from autocnet.vis.graph_view import plot_graph Loading @@ -28,8 +30,11 @@ class CandidateGraph(nx.Graph): The number of nodes in the graph. node_name_map : dict The mapping of image labels (i.e. file base names) to their corresponding node indices. corresponding node indices clusters : dict of clusters with key as the cluster id and value as a list of node indices ---------- """ edge_attr_dict_factory = Edge Loading Loading @@ -169,44 +174,6 @@ class CandidateGraph(nx.Graph): """ return self.node[node_index].image_name def get_node(self, node_name): """ Get the node with the given name. Parameters ---------- node_name : str The name of the node. Returns ------- : object The node with the given image name. """ return self.node[self.node_name_map[node_name]] def get_keypoints(self, nodekey): """ Get the list of keypoints for the given node. Parameters ---------- nodeIndex : int or string The key for the node, by index or name. Returns ------- : list The list of keypoints for the given node. """ try: return self.get_node(nodekey).keypoints except: return self.node[nodekey].keypoints def add_image(self, *args, **kwargs): """ Adds an image node to the graph. Loading @@ -217,8 +184,6 @@ class CandidateGraph(nx.Graph): """ raise NotImplementedError self.add_node(self.node_counter, *args, **kwargs) self.node_counter += 1 def extract_features(self, method='orb', extractor_parameters={}): """ Loading Loading @@ -307,6 +272,25 @@ class CandidateGraph(nx.Graph): else: edge.matches = dest_group def compute_clusters(self, func=markov_cluster.mcl, *args, **kwargs): """ Apply some graph clustering algorithm to compute a subset of the global graph. Parameters ---------- func : object The clustering function to be applied. Defaults to Markov Clustering Algorithm args : list of arguments to be passed through to the func kwargs : dict of keyword arguments to be passed through to the func """ _, self.clusters = func(self, *args, **kwargs) def symmetry_checks(self): """ Perform a symmetry check on all edges in the graph Loading Loading @@ -583,3 +567,20 @@ class CandidateGraph(nx.Graph): A MatPlotLib axes object """ return plot_graph(self, ax=ax, **kwargs) @singledispatch def create_subgraph(self, arg, key=None): if not hasattr(self, 'clusters'): raise AttributeError('No clusters have been computed for this graph.') nodes_in_cluster = self.clusters[arg] subgraph = self.subgraph(nodes_in_cluster) return subgraph @create_subgraph.register(pd.DataFrame) def _(self, arg, g, key=None): if key == None: return col = arg[key] nodes = col[col == True].index subgraph = g.subgraph(nodes) return subgraph No newline at end of file
autocnet/graph/tests/test_markov_cluster.py 0 → 100644 +28 −0 Original line number Diff line number Diff line import unittest import numpy as np import networkx as nx from .. import markov_cluster from autocnet.examples import get_path from autocnet.graph.network import CandidateGraph class TestMarkovCluster(unittest.TestCase): def setUp(self): self.g = CandidateGraph.from_graph(get_path('sixty_four_apollo.graph')) def test_mcl_from_network(self): self.g.compute_clusters(inflate_factor=15) self.assertIsInstance(self.g.clusters, dict) self.assertEqual(len(self.g.clusters), 14) def test_mcl_from_adj_matrix(self): arr = np.array(nx.adjacency_matrix(self.g).todense()) flow, clusters = markov_cluster.mcl(arr) self.assertIsInstance(clusters, dict) self.assertEqual(len(clusters), 3)
autocnet/graph/tests/test_network.py +0 −1 Original line number Diff line number Diff line Loading @@ -49,7 +49,6 @@ class TestCandidateGraph(unittest.TestCase): subgraph_list = self.graph.connected_subgraphs() self.assertEqual(len(subgraph_list), 1) def test_save_load(self): self.graph.save('test_save.cg') loaded = self.graph.from_graph('test_save.cg') Loading