Convert Numpy Array to Monotone Graph (networkx) - python

I have a simple array of 1s and 0s, and I want to convert this array to a graph using NetworkX with the following conditions:
monotone
Directional
Weighted graph (go/no go areas)
Starts in the lower left hand corner and works right
There is a built in function called from_numpy_matrix
See this
The goal is to take this graph and show that I can get from the lower left hand corner of the matrix (think raster dataset) to the upper right hand corner without moving backwards or down.
Example array:
array = [[0,0,1,0,0],
[1,0,0,1,0],
[1,0,1,1,0],
[0,0,1,1,0]]
myarray = np.array(array)
0 means go area, 1 means blocked.

That was fun.
from_numpy_matrix doesn't help as there is no simple transformation from your maze to an adjacency matrix. Instead it is much easier to iterate over allowed positions (i.e. "not wall") and check if there is an allowed position in the allowed directions (up, right, diagonal up-right).
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
def maze_to_graph(is_wall, allowed_steps):
"""
Arguments:
----------
is_wall -- 2D boolean array marking the position of walls in the maze
allowed_steps -- list of allowed steps; e.g. [(0, 1), (1, 1)] signifies that
from coming from tile (i, j) only tiles (i, j+1) and (i+1, j+1)
are reachable (iff there is no wall)
Returns:
--------
g -- networkx.DiGraph() instance
pos2idx -- dict mapping (i, j) position to node idx (for testing if path exists)
idx2pos -- dict mapping node idx to (i, j) position (for plotting)
"""
# map array indices to node indices and vice versa
node_idx = range(np.sum(~is_wall))
node_pos = zip(*np.where(~is_wall))
pos2idx = dict(zip(node_pos, node_idx))
# create graph
g = nx.DiGraph()
for (i, j) in node_pos:
for (delta_i, delta_j) in allowed_steps: # try to step in all allowed directions
if (i+delta_i, j+delta_j) in pos2idx: # i.e. target node also exists
g.add_edge(pos2idx[(i,j)], pos2idx[(i+delta_i, j+delta_j)])
idx2pos = dict(zip(node_idx, node_pos))
return g, idx2pos, pos2idx
def test():
arr = np.array([[0,0,1,0,0],
[1,0,0,1,0],
[1,0,1,1,0],
[0,0,1,1,0]]).astype(np.bool)
steps = [(0, 1), # right
(-1, 0), # up
(-1, 1)] # diagonal up-right
g, idx2pos, pos2idx = maze_to_graph(arr, steps)
nx.draw(g, pos=idx2pos, node_size=1200, node_color='w', labels=idx2pos)
start = (3, 0)
stop = (0, 4)
print "Has path: ", nx.has_path(g, pos2idx[start], pos2idx[stop])
return

Related

How to find branch and intersection points of a binary skeletonized image?

I have the following code which looks for feature points in a binary skeletonized image. I need to find ending points, branch points and intersection points separately and display their coordinates as (x, y, point type). For example, (147, 45, 3), where 3 is the number of adjacent pixels (branch point).
import cv2 as cv
import numpy as np
def extraction(img):
# Find row and column locations that are non-zero
(rows, cols) = np.nonzero(img)
# Initialize empty list of co-ordinates
skel_coords = []
# For each non-zero pixel
for (r, c) in zip(rows, cols):
# Extract an 8-connected neighbourhood
(col_neigh, row_neigh) = np.meshgrid(np.array([c - 1, c, c + 1]), np.array([r - 1, r, r + 1]))
# Cast to int to index into image
col_neigh = col_neigh.astype('int')
row_neigh = row_neigh.astype('int')
# Convert into a single 1D array and check for non-zero locations
pix_neighbourhood = img[row_neigh, col_neigh].ravel() != 0
# If the number of non-zero locations, add this to our list of co-ordinates
if np.sum(pix_neighbourhood) == 2:
skel_coords.append((c, r, 1))
elif np.sum(pix_neighbourhood) == 4:
skel_coords.append((c, r, 3))
elif np.sum(pix_neighbourhood) == 5:
skel_coords.append((c, r, 4))
return skel_coords
img = cv.imread('abc.png', 0)
coord = extraction(img)
for element in coord:
print(element)
The code correctly finds the number of neighboring pixels, but they are not branching and crossing points. You can see it in the picture below (the found point is marked in gray):
An enlarged image of a 3x3 pixel matrix (below, two white pixels are in a row):
I need to find points of the following kind for branch points (so that neighboring pixels alternate):
Does anyone have any ideas how to implement this? I would be very grateful for your help!

nx.draw() plots edges as bi-directional even tho they are uni-directional

So consider a Lattice where every edge is bi-directional. Now I have some code that removes some edges in order to reduce the percentage of edges that are bi-directional and increase the percentage of uni-directional edges:
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
from random import shuffle
G = nx.grid_2d_graph(20,20, periodic=True)
G = nx.relabel.convert_node_labels_to_integers(G)
G2 = nx.DiGraph(G)
nx.set_edge_attributes(G2, values = 1, name = 'weight')
edge_list = list(G2.edges())
shuffle(edge_list)
remove_list = []
seen = []
for (u,v) in edge_list:
seen.append((u,v))
if (v,u) in edge_list:
if (v,u) not in seen:
sample = rd.sample([(u,v), (v,u)],1)[0]
if len(remove_list)< 0.8*(len(edge_list)/2):
remove_list.append(sample)
else:
break
G2.remove_edges_from(remove_list)
Now I saved the edges that are uni-directional in a list:
H = [(u,v) for (u,v) in G2.edges if (v,u) not in G2.edges]
H[:10]
[(0, 20),
(0, 1),
(0, 380),
(1, 2),
(2, 22),
(2, 382),
(3, 4),
(5, 4),
(5, 25),
(5, 6)]
What I want to do now is plot this Lattice:
edge_color = ['red' if (u,v) in H else 'black' for (u,v) in G2.edges] ## uni-directional edges will be red and bi-directional ones will be black
pos = {(x,y):(y,-x) for x,y in product(range(size), range(size))}
pos1 = {i: value for i, value in zip(range(size**2), pos.values())}## assign the nodes to their positions
plt.figure(figsize=(40, 40))
nx.draw(G2,node_size=3000,node_color='lightgreen', with_labels=True, pos=pos1, width=5, edge_color = edge_color)
The problem with this is that in the plot I have red edges that are bi-directional which is wrong
Let's compare the output of H[:10] with the plot:
As you can see the edge (0,1) is painted correctly but has two arrows indicating its a bi-directional one even tho it isn't. Another example is the edge (1,21) which is a bi-directional one and is painted red. Can someone help me fix this?
G = nx.grid_2d_graph(20,20, periodic=True)
From the nx.grid_2d_graph docs:
periodic (bool or iterable) – If periodic is True, both dimensions are periodic. If False, none are periodic. If periodic is iterable, it should yield 2 bool values indicating whether the 1st and 2nd axes, respectively, are periodic.
I didn't know what a periodic grid lattice was, so I did a little research(Google), and found this:
From Matarazzo, U., Tsur, D., & Ziv-Ukelson, M. (2014). Efficient all path score computations on grid graphs. Theoretical Computer Science, 525, 138-149.:
A periodic grid graph is an infinite graph obtained by concatenating horizontally an infinite number of a (finite) grid graph.
Conclusion
This is still pretty mysterious to me, but you can see in the results that you obtained, that edges seem somehow overlapping(like there was more than one graph being drawn...).
So, it seems like you are obtaining some unexpected results due to this "periodicity" feature.
Removing periodic=True and returning this value back to default,False, solves the issue:
G = nx.grid_2d_graph(20,20)
One problem is that your are using G2.edges as a list which is not. So saying (u,v) in G2.edges is not checking for item membership making your H list wrong.
So you can just do:
H = [(u,v) for (u,v) in G2.edges if (v,u) not in list(G2.edges)]
I am not sure if this completely solves your problem, but at least solves that one issue.

Lots of edges on a graph plot in python

I have following script:
import pandas as pd
from igraph import *
df_p_c = pd.read_csv('data/edges.csv')
...
edges = list_edges
vertices = list(dict_case_to_number.keys())
g = Graph(edges=edges, directed=True)
plot(g, bbox=(6000, 6000))
I have 2300 edges with rare connection. This is my plot of it:
And here are zooms of a few parts of it:
This plot is not readable because the distance between edges is too small. How can I have a bigger distance between edges? Only edges from the same 'family' have small distance.
Is there any other way to improve plots with a lot of edges?
I'm looking for any way to visualize parent-child correlation, it could be another python packet.
You seem to have a lot of small, disconnected components. If you want an informative graph, I think you should sort and group the connected components by size. Furthermore, the underlying assumption of many network layout algorithms is that there is a single giant component. Hence if you want sensible coordinates, you will often need to compute the layout for each component separately and then arrange the components with respect to each other. I would re-plot your graph in this way:
I have written the code for this graph using networkx as that is my module of choice. However, it would be very easy to substitute the networkx functions with igraph functions. The two functions that you need to replace are networkx.connected_component_subgraphs and whatever you want to use for the component_layout_func.
#!/usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
import networkx
def layout_many_components(graph,
component_layout_func=networkx.layout.spring_layout,
pad_x=1., pad_y=1.):
"""
Arguments:
----------
graph: networkx.Graph object
The graph to plot.
component_layout_func: function (default networkx.layout.spring_layout)
Function used to layout individual components.
You can parameterize the layout function by partially evaluating the
function first. For example:
from functools import partial
my_layout_func = partial(networkx.layout.spring_layout, k=10.)
pos = layout_many_components(graph, my_layout_func)
pad_x, pad_y: float
Padding between subgraphs in the x and y dimension.
Returns:
--------
pos : dict node : (float x, float y)
The layout of the graph.
"""
components = _get_components_sorted_by_size(graph)
component_sizes = [len(component) for component in components]
bboxes = _get_component_bboxes(component_sizes, pad_x, pad_y)
pos = dict()
for component, bbox in zip(components, bboxes):
component_pos = _layout_component(component, bbox, component_layout_func)
pos.update(component_pos)
return pos
def _get_components_sorted_by_size(g):
subgraphs = list(networkx.connected_component_subgraphs(g))
return sorted(subgraphs, key=len)
def _get_component_bboxes(component_sizes, pad_x=1., pad_y=1.):
bboxes = []
x, y = (0, 0)
current_n = 1
for n in component_sizes:
width, height = _get_bbox_dimensions(n, power=0.8)
if not n == current_n: # create a "new line"
x = 0 # reset x
y += height + pad_y # shift y up
current_n = n
bbox = x, y, width, height
bboxes.append(bbox)
x += width + pad_x # shift x down the line
return bboxes
def _get_bbox_dimensions(n, power=0.5):
# return (np.sqrt(n), np.sqrt(n))
return (n**power, n**power)
def _layout_component(component, bbox, component_layout_func):
pos = component_layout_func(component)
rescaled_pos = _rescale_layout(pos, bbox)
return rescaled_pos
def _rescale_layout(pos, bbox):
min_x, min_y = np.min([v for v in pos.values()], axis=0)
max_x, max_y = np.max([v for v in pos.values()], axis=0)
if not min_x == max_x:
delta_x = max_x - min_x
else: # graph probably only has a single node
delta_x = 1.
if not min_y == max_y:
delta_y = max_y - min_y
else: # graph probably only has a single node
delta_y = 1.
new_min_x, new_min_y, new_delta_x, new_delta_y = bbox
new_pos = dict()
for node, (x, y) in pos.items():
new_x = (x - min_x) / delta_x * new_delta_x + new_min_x
new_y = (y - min_y) / delta_y * new_delta_y + new_min_y
new_pos[node] = (new_x, new_y)
return new_pos
def test():
from itertools import combinations
g = networkx.Graph()
# add 100 unconnected nodes
g.add_nodes_from(range(100))
# add 50 2-node components
g.add_edges_from([(ii, ii+1) for ii in range(100, 200, 2)])
# add 33 3-node components
for ii in range(200, 300, 3):
g.add_edges_from([(ii, ii+1), (ii, ii+2), (ii+1, ii+2)])
# add a couple of larger components
n = 300
for ii in np.random.randint(4, 30, size=10):
g.add_edges_from(combinations(range(n, n+ii), 2))
n += ii
pos = layout_many_components(g, component_layout_func=networkx.layout.circular_layout)
networkx.draw(g, pos, node_size=100)
plt.show()
if __name__ == '__main__':
test()
EDIT
If you want the subgraphs tightly arranged, you need to install rectangle-packer (pip install rectangle-packer), and substitute _get_component_bboxes with this version:
import rpack
def _get_component_bboxes(component_sizes, pad_x=1., pad_y=1.):
dimensions = [_get_bbox_dimensions(n, power=0.8) for n in component_sizes]
# rpack only works on integers; sizes should be in descending order
dimensions = [(int(width + pad_x), int(height + pad_y)) for (width, height) in dimensions[::-1]]
origins = rpack.pack(dimensions)
bboxes = [(x, y, width-pad_x, height-pad_y) for (x,y), (width, height) in zip(origins, dimensions)]
return bboxes[::-1]
Edit #2
I have written a library for visualising networks, which is called netgraph. It automatically handles networks with multiple components in the way outlined above. It is fully compatible with networkx and igraph Graph objects, so it should be easy and fast to make great looking graphs of graphs (at least that is the idea).
import itertools
import matplotlib.pyplot as plt
import networkx as nx
# installation easiest via pip:
# pip install netgraph
from netgraph import Graph
# construct the graph as before:
g = nx.Graph()
# add 30 unconnected nodes
g.add_nodes_from(range(30))
# add 15 2-node components
g.add_edges_from([(ii, ii+1) for ii in range(30, 60, 2)])
# add 10 3-node components
for ii in range(60, 90, 3):
g.add_edges_from([(ii, ii+1), (ii, ii+2), (ii+1, ii+2)])
# add a couple of larger components
n = 90
for ii in [10, 20, 40]:
g.add_edges_from(itertools.combinations(range(n, n+ii), 2))
n += ii
# if there are any disconnected components, netgraph automatically handles them separately
Graph(g, node_layout='circular', node_size=1, node_edge_width=0.1, edge_width=0.1, edge_color='black', edge_alpha=1.)
plt.show()
You could checkout networkx, which is a pretty nice graph library. Networkx has direct plotting support for matplotlib.
It supports various layout types, for example spring layout, random layout, and a few more
You should especially look at spring layout, which has a few interesting parameters for your use-case:
k (float (default=None)) – Optimal distance between nodes. If None the
distance is set to 1/sqrt(n) where n is the number of nodes. Increase
this value to move nodes farther apart.
Or both of these in combination with a custom layout:
pos (dict or None optional (default=None)) – Initial positions for
nodes as a dictionary with node as keys and values as a coordinate
list or tuple. If None, then use random initial positions.
fixed (list or None optional (default=None)) – Nodes to keep fixed at initial
position.
The edge weight might also be something you can tune in order to get results you like:
weight (string or None optional (default=’weight’)) – The edge
attribute that holds the numerical value used for the edge weight. If
None, then all edge weights are 1.
I would recommend combining networkx with bokeh, which is a new plotting library that creates web-based html/js plots. It has direct support for networkx, and has some nice features like easy integration of node hover tools. If your graph isn't too big, the performance is pretty good. (I've plotted graphs with about 20000 nodes and a few thousand edges).
With both libraries combined, all you need is the following bit of code for a simple example (from the documentation) that tries to build an optimized layout:
import networkx as nx
from bokeh.io import show, output_file
from bokeh.plotting import figure
from bokeh.models.graphs import from_networkx
G=nx.karate_club_graph() # Replace with your own graph
plot = figure(title="Networkx Integration Demonstration", x_range=(-1.1,1.1), y_range=(-1.1,1.1),
tools="", toolbar_location=None)
graph = from_networkx(G, nx.spring_layout, scale=2, center=(0,0))
plot.renderers.append(graph)
output_file("networkx_graph.html")
show(plot)
Do you know what meaning you are looking for? Or are you exploring? Or is this a specific question about zooming issues?
So far, you have done a good job of seeing the overall structure. Some ideas you might consider making new vocabulary with a few routines to support it. For example, if you make a small cluster be the set of points and edges that are together, then you can plot histograms, visualizations of clusters overlayed on each other, compare clusters with and without long nodes, and so one.

Python NetworkX: How to access edges with a specific data value

For a bond percolation model I want to build a square lattice with NetworkX using grid_2d_graph(l,l). This gives me a square lattice of size lxl with every edge open.
The idea is that I want to pick an edge of the graph randomly, then check if the edge has already been assigned (1 to leave the edge as it is, 0 to add it to the list of edges to remove from the graph) and if it hasn't been assigned yet (edge has 'state' = -1), I want to randomly choose with a specific probability p, if the edge is open (leave it as it is), or if it is closed (put it on the list of the edges to remove).
Therefor, I saved all edges with data attribute 'state' = -1 as a list and then tried to randomly access an entry of this list to then change the attribute 'state' to some value. But it seems that this operation is not allowed. When I try to edit the states, I receive the following error:
File "bond-percolation.py", line 39, in <module>
ed[10][2] = 1
TypeError: 'tuple' object does not support item assignment
So my question is, how can I randomly pick an edge and change the value of 'state' efficiently?
Here is my code:
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
import random
#Width of the quadratic lattice
l = 30
#Create grid
G = nx.grid_2d_graph(l,l)
#Total number of edges in the lattice
n = 2 * l * (l-1)
m = 0
#Set probability if an edge is open
p = 0.17
#Create empty list to add closed edges later
ed = []
ld = []
for e in G.edges(data = 'state', default = -1):
ed.append(e)
#Creating the lattice
while (m != n):
i = np.random.randint(n-1)
a = random.random()
if (ed[i][2] == -1):
if (a > p):
ld.append(ed[i])
else:
ed[i][2] = 1
m = m + 1
#We need this so that the lattice is drawn vertically to the horizon
pos = dict( (l,l) for l in G.nodes() )
#Draw the lattice
nx.draw_networkx(G, pos = pos, with_labels = False, node_size = 0)
#Plot it on the screen
plt.axis('off')
plt.show()
I believe you can simply search for it using the edge selector.
There's no built-in selector (afaik) but you can create a helper function that loops through the edges and returns your list.
def filter_edges(value):
edge_list = []
for u,v,s in G.edges(data='state'):
if s == value:
edge_list.append((u,v))
return edge_list
Re-reading your error, I don't think your error is related to randomly picking edges. Instead, you are incorrectly trying to assign the state values.
ed[10][2] returns the whole edge (presumably a dict). It'd be helpful to include the output when you just run ed[10][2].
You can't assign an int to that. You likely want to do ed[10][2]['state'] = 1

How to get all connections between pixels in an image

Developing the A* algorithm for path planning, I am trying to get a list called edges in which all connections from one pixel to its neighbour pixels that occur in a non-occupied space (where the pixel value is 1).
The image from which I compute this connections is a 351x335 pixels image.
Pixels P2,4,6,8 are at a distance=1 from the center, while pixels P1,3,5,7 are at a approximate distance=1.4 from the center (Pythagoras theorem); see image:
The code written for the edges loop is never ending. Is this taking too much computational time due to the loops? The vertices loop ends in a second or so.
Note: I am initializing the lists as two very big arrays and cutting them at the end to not use dynamic allocation.
EDIT: The image (imOut) is the following one:
Link to image used as map
EDIT: The full code is the following:
'''
IMPORTS
'''
import cv2 as cv # Import OpenCV
import numpy as np # Import Numpy
from skimage.color import rgb2gray
import math
from datetime import datetime
import matplotlib.pyplot as plt
from scipy import arange
'''
CODE SETTINGS
'''
# Allowing to print full array without truncation
np.set_printoptions(threshold=np.nan)
'''
MAIN PROGRAM
'''
im = rgb2gray(cv.imread('map1.png'))
imOut = im # Making a copy of the image to output
# plt.imshow(imOut)
# plt.show()
vertices = np.zeros((imOut.shape[0]*imOut.shape[1], 3)) # 1st col to x, 2nd col to y, 3rd col to heuristic (euclidean distance to QGoal)
edges = np.zeros((100*imOut.shape[0]*imOut.shape[1], 3)) # 1st col 1st vertex, 2nd col 2nd vertex, 3rd col edge length
# Initialization of vertices with start pos.
# CREATE VERTICES AND EDGES LISTS FROM THE MAP PROVIDED
'''
Vertices List -> Add all obstacle-free configurations to the vertices list.
Edges List -> Go pixel by pixel in the map and, if they are obstacle-free configurations,
add, out of the 8 neighbouring pixels, the ones that are obstacle-free as feasible edges.
'''
# Vertices list creation
indexVertices = 0
for i in range(0, imOut.shape[0]):
for j in range(0, imOut.shape[1]):
if imOut[i,j] == 1: # If it's in free space
# Compute heuristic to goal node (euclidean distance).
heuristic = math.sqrt(pow(i-QGoal[0],2)
+ pow(j-QGoal[1],2))
vertices[indexVertices,:] = [i, j, heuristic]
indexVertices = indexVertices + 1
vertices = vertices[0:indexVertices,:]
# Edges list creation
# I loop over the same vertices array, as it only contains the free pixels.
indexEdges = 0
for i in range(0, vertices.shape[0]):
for k in range(0, vertices.shape[0]):
# If it is not the same pixel that we are checking
if i != k:
# Check if it is a neighbouring pixel and, if so,
#add it to the list of edges with its distance(path cost).
pathCost = (math.sqrt(pow(vertices[i,0]
- vertices[k,0], 2)
+ pow(vertices[i,1]
- vertices[k,1], 2))
if pathCost == 1 or round(pathCost,1) == 1.4:
edges[indexEdges,:] = [i, k, pathCost]
indexEdges = indexEdges + 1
edges = edges[0:indexEdges,:]

Categories

Resources