#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Graph analysis module
---------------------
Module provides basic Graph classes.
"""
__author__ = 'Christoph Kirst <christoph.kirst.ck@gmail.com>'
__license__ = 'GPLv3 - GNU General Pulic License v3 (see LICENSE)'
__copyright__ = 'Copyright © 2020 by Christoph Kirst'
__webpage__ = 'http://idisco.info'
__download__ = 'http://www.github.com/ChristophKirst/ClearMap2'
import numpy as np
###############################################################################
### Base graph class
###############################################################################
[docs]
class Graph(object):
"""Abstract base Graph class.
All graph interfaces should inherit from the class.
"""
def __init__(self, name = None, n_vertices = None, edges = None, directed = None):
if name is not None:
self.name = name;
if directed is not None:
self.directed = directed;
else:
self.directed = False;
if n_vertices is not None:
self.add_vertex(n_vertices=n_vertices);
if edges is not None:
self.add_edge(edges);
@property
def name(self):
if hasattr(self, '_name'):
return self._name;
else:
return type(self).__name__;
@name.setter
def name(self, value):
self._name = str(value);
@property
def directed(self):
return self._directed;
@directed.setter
def directed(self, value):
self._directed = bool(value);
### Vertices
@property
def n_vertices(self):
"""Number of vertices in the graph."""
return None;
@n_vertices.setter
def n_vertices(self, value):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def vertex(self, index):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
@property
def vertices(self):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def add_vertex(self, n_vertices = None, index = None, vertex = None):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def remove_vertex(self, index = None, vertex = None):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def vertex_property(self, name, index = None):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def vertex_properties(self):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def add_vertex_property(self, name, source, dtype = None):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def set_vertex_property(self, name, source):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def remove_vertex_property(self, name):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def vertex_degrees(self):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def vertex_degree(self, index):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def vertex_out_degrees(self):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def vertex_out_degree(self, index):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def vertex_in_degrees(self):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def vertex_in_degree(self, index):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
### Edges
@property
def n_edges(self):
"""Number of edges in the graph."""
return None;
@n_edges.setter
def n_edges(self, value):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def edge(self, edge):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
@property
def edges(self):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def edge_connectivity(self):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def add_edge(self, edge):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def remove_edge(self, edge):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def edge_property(self, name):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def edge_properties(self):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def add_edge_property(self, name, source, dtype = None):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def set_edge_property(self, name, source):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def remove_edge_property(self, name):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
### IO
[docs]
def save(self, filename):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def load(self, filename):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
def __str__(self):
try:
name = self.name;
name = '%s' % name if name is not None else '';
except:
name ='';
try:
vertices = self.n_vertices;
vertices = '[%d·]' % vertices if vertices is not None else '';
except:
vertices = '';
try:
edges = self.n_edges;
edges = '[%d-]' % edges if edges is not None else '';
except:
edges = '';
return name + vertices + edges;
def __repr__(self):
return self.__str__();
###############################################################################
### Graphs with spatial geometry
###############################################################################
[docs]
class GeometricGraph(Graph):
"""Base class for graphs whose vertices are embedded in an Eucledian space."""
def __init__(self, name = None, n_vertices = None, edges = None, directed = False,
vertex_coordinates = None, vertex_radii = None,
edge_coordinates = None, edge_radii = None, edge_geometries = None, shape = None):
super(GeometricGraph, self).__init__(name=name, n_vertices=n_vertices, edges=edges, directed=directed);
if vertex_coordinates is not None:
self.vertex_coordinates = vertex_coordinates;
if vertex_radii is not None:
self.vertex_radii = vertex_radii;
if edge_coordinates is not None:
self.edge_coordinates = edge_coordinates;
if edge_radii is not None:
self.edge_radii = edge_radii;
if edge_geometries is not None:
self.edge_geometries = edge_geometries;
self.shape = shape;
@property
def shape(self):
"""The shape of the underlying space in which the graph is embedded."""
return self._shape;
@shape.setter
def shape(self, value):
self._shape = value;
@property
def ndim(self):
return len(self.shape);
### Vertices
[docs]
def vertex_coordinates(self, axis = None, vertex = None):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def set_vertex_coordinates(self, coordinates, axis = None, vertex = None):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def vertex_radii(self, vertex = None):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def set_vertex_radii(self, radius, vertex = None):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
### Edges
[docs]
def edge_coordinates(self, edge = None):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def set_edge_coordinates(self, coordinates, axis = None, edge = None):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def edge_radii(self, edge = None):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def set_edge_radii(self, edge = None):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def edge_coordinates_from_vertex_coordinates(self):
coords = self.vertex_coordinates();
i,j = self.edge_connectivity().T;
return (0.5 * (coords[i] + coords[j]));
[docs]
def edge_radii_from_vertex_radii(self):
r = self.vertex_radii();
i,j = self.edge_connectivity().T;
return (0.5 * (r[i] + r[j]));
# def set_edge_radii_from_vertex_radii(self):
# r = self.vertex_radii();
# i,j = self.edge_connectivity().T;
# self.set_edge_radii(0.5 * (r[i] + r[j]));
[docs]
def edge_vectors(self, normalize = False):
xyz = self.vertex_coordinates();
i,j = self.edge_connectiivty().T;
v = xyz[i] - xyz[j];
if normalize:
v = (v.T / np.linalg.norm(v, axis = 1)).T;
return v;
# Edge geometries
@property
def has_edge_geometry(self):
return False;
@property
def edge_geometry_type(self):
return None;
[docs]
def edge_geometry(self, *args, **kwargs):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def set_edge_geometry(self, *args, **kwargs):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def reduce_edge_geometry(self):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def expand_edge_geometry(self):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
### Functionality
#def from_skeleton(self, skeleton):
# raise NotImplementedError('Not implemented in graph class %s!' % self.name)
#def skeleton(self, sink = None, dtype = bool):
# raise NotImplementedError('Not implemented in graph class %s!' % self.name);
### Functionality
def __str__(self):
s = super(GeometricGraph, self).__str__();
try:
shape = self.shape;
shape = '%r' % shape if shape is not None else '';
except:
shape = '';
try:
edge_geometry_type = self.edge_geometry_type;
if edge_geometry_type is None:
edge_geometry_type = ''
elif edge_geometry_type == 'graph':
edge_geometry_type = '|G|'
else:
edge_geometry_type = '|E|'
except:
edge_geometry_type = '';
return s + shape + edge_geometry_type;
[docs]
class AnnotatedGraph(GeometricGraph):
"""Base class for graphs whose vertices are embedded in an Eucledian space and have an annotation."""
def __init__(self, name = None, n_vertices = None, edges = None, directed = False,
vertex_coordinates = None, vertex_radii = None,
edge_coordinates = None, edge_radii = None, edge_geometries = None, shape = None,
vertex_labels = None, edge_labels = None, annotation = None):
super(AnnotatedGraph, self).__init__(name=name, n_vertices=n_vertices, edges=edges, directed=directed,
vertex_coordinates=vertex_coordinates, vertex_radii=vertex_radii,
edge_coordinates=edge_coordinates, edge_radii=edge_radii, edge_geometries=edge_geometries, shape=shape);
if vertex_labels is not None:
self.vertex_labels = vertex_labels;
if edge_labels is not None:
self.edge_labels = edge_labels;
self.annotation = annotation;
@property
def annotation(self):
return self._annotation;
@annotation.setter
def annotation(self, value):
self._annotation = value;
### Vertices
[docs]
def vertex_annotation(self):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def set_vertex_annotation(self, value):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
### Edges
[docs]
def edge_annotation(self):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
[docs]
def set_edge_annotation(self, value):
raise NotImplementedError('Not implemented in graph class %s!' % self.name)
def __str__(self):
s = super(AnnotatedGraph, self).__str__();
try:
annotation = self.annotation;
annotation = '{{%r}}' % annotation if annotation is not None else '';
except:
annotation = '';
return s + annotation;
[docs]
def load(filename):
return Graph().load(filename);
[docs]
def save(filename, graph):
graph.save(filename);
###############################################################################
### Tests
###############################################################################
def _test():
import ClearMap.Analysis.Graphs.Graph as gr
reload(gr)
g = gr.Graph();
print(g)
g = gr.GeometricGraph();
print(g)
g = gr.AnnotatedGraph(annotation='test.npy')
print(g)