Commit 74f92095 authored by Kristin Berry's avatar Kristin Berry
Browse files

Merge pull request #66 from jlaura/graphabstraction

Graph Abstraction
parents d9ae503c 95e2f071
Loading
Loading
Loading
Loading
+47.1 KiB

File added.

No diff preview for this file type.

+107 −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.  That is, the flow between a node and itself.

    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):
        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.

+48 −47
Original line number Diff line number Diff line
from functools import singledispatch
import itertools
import os
import dill as pickle
@@ -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


@@ -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
@@ -106,17 +111,17 @@ class CandidateGraph(nx.Graph):
        adjacency_dict = {}

        for i, j in itertools.permutations(datasets,2):
            if not i.base_name in adjacency_dict.keys():
                adjacency_dict[i.base_name] = []
            if not j.base_name in adjacency_dict.keys():
                adjacency_dict[j.base_name] = []
            if not i.file_name in adjacency_dict.keys():
                adjacency_dict[i.file_name] = []
            if not j.file_name in adjacency_dict.keys():
                adjacency_dict[j.file_name] = []

            # Grab the footprints and test for intersection
            i_fp = i.footprint
            j_fp = j.footprint
            if i_fp.Intersects(j_fp):
                adjacency_dict[i.base_name].append(j.base_name)
                adjacency_dict[j.base_name].append(i.base_name)
                adjacency_dict[i.file_name].append(j.file_name)
                adjacency_dict[j.file_name].append(i.file_name)

        return cls(adjacency_dict)

@@ -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.
@@ -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={}):
        """
@@ -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
@@ -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
+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)

Loading