Related
I need to list all coordinates of a 2D plane, with x and y axis ranging from 0 to 1066. Most answers I found recommends creating a list beforehand of all value of x and y axis, but I don't think that is the most efficient, since there will be 1067^2 elements as the result. The list should look something like this:
list = [(0,0), (0,1),...(0,1066), (1,0), (1,1),...,(1,1066),...,(1066,0),...,(1066,1066)].
I was thinking of using permutations since order matters, but I am still figuring out the best method.
You can create a generator that you can use to iterate over every single pair of coordinates, without creating a rather large list of values:
As a generator expression:
sizex, sizey = 3, 3 # replace with your own values
g = ((x, y) for x in range(sizex) for y in range(sizey))
print(type(g))
for coord in g:
print(coord)
output:
<class 'generator'>
(0, 0)
(0, 1)
(0, 2)
(1, 0)
(1, 1)
(1, 2)
(2, 0)
(2, 1)
(2, 2)
A generator as a lambda function:
sizex, sizey = 3, 3 # replace with your own values
g = lambda sizex, sizey: ((x, y) for x in range(sizex) for y in range(sizey))
print(type(g))
for coord in g(sizex, sizey):
print(coord)
out:
<class 'function'>
(0, 0)
(0, 1)
(0, 2)
(1, 0)
(1, 1)
(1, 2)
(2, 0)
(2, 1)
(2, 2)
As a regular function:
def g(sizex, sizey):
for x in range(sizex):
for y in range(sizey):
yield(x, y)
sizex, sizey = 3, 3 # replace with your own values
print(type(g))
for coord in g(sizex, sizey):
print(coord)
Use Cartesian product to create lazy loading
impot itertools
mx, my = 1066, 1066
for x, y in itertools.product(range(mx), range(my)):
print(x, y)
0 0
0 1
0 2
.
.
.
1065 1063
1065 1064
1065 1065
Suppose I have a matrix like this:
m = [0, 1, 1, 0,
1, 1, 0, 0,
0, 0, 0, 1]
And I need to get the coordinates of the same neighbouring values (but not diagonally):
So the result would be a list of lists of coordinates in the "matrix" list, starting with [0,0], like this:
r = [[[0,0]],
[[0,1], [0,2], [1,0], [1,1]],
[[0,3], [1,2], [1,3], [2,0], [2,1], [2,2]]
[[2,3]]]
There must be a way to do that, but I'm really stuck.
tl;dr: We take an array of zeros and ones and use scipy.ndimage.label to convert it to an array of zeros and [1,2,3,...]. We then use np.where to find the coordinates of each element with value > 0. Elements that have the same value end up in the same list.
scipy.ndimage.label interprets non-zero elements of a matrix as features and labels them. Each unique feature in the input gets assigned a unique label. Features are e.g. groups of adjacent elements (or pixels) with the same value.
import numpy as np
from scipy.ndimage import label
# make dummy data
arr = np.array([[0,1,1,0], [1,1,0,0], [0,0,0,1]])
#initialise list of features
r = []
Since OP wanted all features, that is groups of zero and non-zero pixels, we use label twice: First on the original array, and second on 1 - original array. (For an array of zeros and ones, 1 - array just flips the values).
Now, label returns a tuple, containing the labelled array (which we are interested in) and the number of features that it found in that array (which we could use, but when I coded this, I chose to ignore it. So, we are interested in the first element of the tuple returned by label, which we access with [0]:
a = label(arr)[0]
b = label(1-arr)[0]
Now we check which unique pixel values label has assigned. So we want the set of a and b, repectively. In order for set() to work, we need to linearise both arrays, which we do with .ravel(). We have to subtract {0} in both cases, because for both a and b we are interested in only the non-zero values.
So, having found the unique labels, we loop through these values, and use np.where to find where on the array a given value is located. np.where returns a tuple of arrays. The first element of this tuple are all the row-coordinates for which the condition was met, and the second element are the column-coordinates.
So, we can use zip(* to unpack the two containers of length n to n containers of length 2. This means that we go from list of all row-coords + list of all column-coords to list of all row-column-coordinate pairs for which the condition is met. Finally in python 3, zip is a generator, which we can evaluate by calling list() on it. The resulting list is then appended to our list of coordinates, r.
for x in set(a.ravel())-{0}:
r.append(list(zip(*np.where(a==x))))
for x in set(b.ravel())-{0}:
r.append(list(zip(*np.where(b==x))))
print(r)
[[(0, 1), (0, 2), (1, 0), (1, 1)],
[(2, 3)],
[(0, 0)],
[(0, 3), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2)]]
That said, we can speed up this code slightly by making use of the fact that label returns the number of features it assigned. This allows us to avoid the set command, which can take time on large arrays:
a, num_a = label(arr)
for x in range(1, num_a+1): # range from 1 to the highest label
r.append(list(zip(*np.where(a==x))))
A solution with only standard libraries:
from pprint import pprint
m = [0, 1, 1, 0,
1, 1, 0, 0,
0, 0, 0, 1]
def is_neighbour(x1, y1, x2, y2):
return (x1 in (x2-1, x2+1) and y1 == y2) or \
(x1 == x2 and y1 in (y2+1, y2-1))
def is_value_touching_group(val, groups, x, y):
for d in groups:
if d['color'] == val and any(is_neighbour(x, y, *cell) for cell in d['cells']):
return d
def check(m, w, h):
groups = []
for i in range(h):
for j in range(w):
val = m[i*w + j]
touching_group = is_value_touching_group(val, groups, i, j)
if touching_group:
touching_group['cells'].append( (i, j) )
else:
groups.append({'color':val, 'cells':[(i, j)]})
final_groups = []
while groups:
current_group = groups.pop()
for c in current_group['cells']:
touching_group = is_value_touching_group(current_group['color'], groups, *c)
if touching_group:
touching_group['cells'].extend(current_group['cells'])
break
else:
final_groups.append(current_group['cells'])
return final_groups
pprint( check(m, 4, 3) )
Prints:
[[(2, 3)],
[(0, 3), (1, 3), (1, 2), (2, 2), (2, 0), (2, 1)],
[(0, 1), (0, 2), (1, 1), (1, 0)],
[(0, 0)]]
Returns as a list of groups under value key.
import numpy as np
import math
def get_keys(old_dict):
new_dict = {}
for key, value in old_dict.items():
if value not in new_dict.keys():
new_dict[value] = []
new_dict[value].append(key)
else:
new_dict[value].append(key)
return new_dict
def is_neighbor(a,b):
if a==b:
return True
else:
distance = abs(a[0]-b[0]), abs(a[1]-b[1])
return distance == (0,1) or distance == (1,0)
def collate(arr):
arr2 = arr.copy()
ret = []
for a in arr:
for i, b in enumerate(arr2):
if set(a).intersection(set(b)):
a = list(set(a+b))
ret.append(a)
for clist in ret:
clist.sort()
return [list(y) for y in set([tuple(x) for x in ret])]
def get_groups(d):
for k,v in d.items():
ret = []
for point in v:
matches = [a for a in v if is_neighbor(point, a)]
ret.append(matches)
d[k] = collate(ret)
return d
a = np.array([[0,1,1,0],
[1,1,0,0],
[0,0,1,1]])
d = dict(np.ndenumerate(a))
d = get_keys(d)
d = get_groups(d)
print(d)
Result:
{
0: [[(0, 3), (1, 2), (1, 3)], [(0, 0)], [(2, 0), (2, 1)]],
1: [[(2, 2), (2, 3)], [(0, 1), (0, 2), (1, 0), (1, 1)]]
}
I have a 2D array, arr, where each cell in it has a value 1, 2 or 3, for example, arr[0][0] = 3, arr[2][1] = 2, and arr[0][4] = 1.
I want to know the shortest path from a given certain cell, for example, arr[5][5] to the closest cell which has value 2 where the path shouldn't contain any cells that have the value 1. How can I do this?
Below is a script for the BFS, but how can I make it accept a 2D array as a graph and starting point as a certain cell location in the array and then go to the nearest two from this cell avoiding cells with 1s, so that it looks like bfs(2darray, starting location, 2)?
def bfs(graph, start, end):
# Maintain a queue of paths
queue = []
# Push the first path into the queue
queue.append([start])
while queue:
# Get the first path from the queue
path = queue.pop(0)
# Get the last node from the path
node = path[-1]
# Path found
if node == end:
return path
# Enumerate all adjacent nodes, construct a new path and push it into the queue
for adjacent in graph.get(node, []):
new_path = list(path)
new_path.append(adjacent)
queue.append(new_path)
print bfs(graph, '1', '11')
You can use a simple breadth first search for this. Basically, each cell in your grid corresponds to a node in the graph, with edges between adjacent cells. Start at the starting position, and keep expanding passable cells until you find a goal cell.
def bfs(grid, start):
queue = collections.deque([[start]])
seen = set([start])
while queue:
path = queue.popleft()
x, y = path[-1]
if grid[y][x] == goal:
return path
for x2, y2 in ((x+1,y), (x-1,y), (x,y+1), (x,y-1)):
if 0 <= x2 < width and 0 <= y2 < height and grid[y2][x2] != wall and (x2, y2) not in seen:
queue.append(path + [(x2, y2)])
seen.add((x2, y2))
Grid setup and results: (Note that I'm using symbols instead of numbers, simply for the reason that it's easier to visually parse the grid this way and to verify the solution.)
wall, clear, goal = "#", ".", "*"
width, height = 10, 5
grid = ["..........",
"..*#...##.",
"..##...#*.",
".....###..",
"......*..."]
path = bfs(grid, (5, 2))
# [(5, 2), (4, 2), (4, 3), (4, 4), (5, 4), (6, 4)]
If the list is not too big, the easiest solution I find is using the where function of the NumPy library to find the cells which have the value you are looking for. So, you will need to convert your list into a NumPy array.
The code below might be simplified to make it shorter and more efficient, but in this way it will be clearer. By the way, you can compute two kind of distances: the typical Euclidean and the Manhattan.
If there is more than one target cell at the same distance of the origin cell, min_coords corresponds to the first cell found (first by rows, then by columns).
import numpy as np
# The list needs to be transformed into an array in order to use the np.where method
# arr = np.random.randint(5, size=(6, 6))
arr = np.array([[0, 0, 0, 1, 1, 3],
[0, 0, 2, 1, 1, 0],
[0, 0, 1, 1, 1, 1],
[3, 0, 3, 1, 1, 1], ])
# Origin cell to make the search
x0, y0 = (1, 1)
targetValue = 3
# This is the keypoint of the problem: find the positions of the cells containing the searched value
positions = np.where(arr == targetValue)
x, y = positions
dx = abs(x0 - x) # Horizontal distance
dy = abs(y0 - y) # Vertical distance
# There are different criteria to compute distances
euclidean_distance = np.sqrt(dx ** 2 + dy ** 2)
manhattan_distance = abs(dx + dy)
my_distance = euclidean_distance # Criterion choice
min_dist = min(my_distance)
print(min_dist)
min_pos = np.argmin(my_distance) # This method will only return the first occurrence (!)
min_coords = x[min_pos], y[min_pos]
print(min_coords)
I have grid points in 3d and I would like to sort them based (x,y,z) using python (if possible avoiding loops)..
For example if the input is,
(1,2,1), (0,8,1), (1,0,0) ..
then output should be
(0,8,1), (1,0,0), (1,2,1)..
Sorry for this side track but I am actually doing is reading from a file which has data in following way:
x y z f(x) f(y) f(z)..
what I was doing was following:
def fill_array(output_array,source_array,nx,ny,nz,position):
for i in range(nx):
for j in range(ny):
for k in range(nz):
output_array[i][j][k] = source_array[i][j][k][position]
nx = 8
ny = 8
nz = 8
ndim = 6
x = np.zeros((nx,ny,nz))
y = np.zeros((nx,ny,nz))
z = np.zeros((nx,ny,nz))
bx = np.zeros((nx,ny,nz))
by = np.zeros((nx,ny,nz))
bz = np.zeros((nx,ny,nz))
data_file = np.loadtxt('datafile')
f = np.reshape(data_file, (nx,ny,nz,ndim))
fill_array(x,f,nx,ny,nz,0))
fill_array(y,f,nx,ny,nz,1)
fill_array(z,f,nx,ny,nz,2)
fill_array(fx,f,nx,ny,nz,3)
fill_array(fy,f,nx,ny,nz,4)
fill_array(fz,f,nx,ny,nz,5)
This was working fine when data was arranged (as explained previously) but with file written not in order it is creating problems with plot later on. Is there are better way to do this ? Of course I only want to arrange x,y,z and then associate functional value f(x),f(y),f(z) to its right position (x,y,z)
two updates
1) i am getting following error when I use sorted with either x,y,z,fx,fy,fz or f.
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
2) i need it in that specific way because I am using mayavi then for contour3d
The built-in function sorted does what you want:
>>> a = [(1, 2, 1), (0, 8, 1), (1, 0, 0)]
>>> sorted(a)
[(0, 8, 1), (1, 0, 0), (1, 2, 1)]
Use [sorted][1].
In [71]: sorted(a)
Out[71]: [(0, 8, 1), (1, 0, 0), (1, 2, 1)]
more precisely
In [70]: sorted(a, key=lambda x: (x[0], x[1], x[2]))
Out[70]: [(0, 8, 1), (1, 0, 0), (1, 2, 1)]
key=lambda x: (x[0], x[1], x[2])
at this step we are sorting list at 0th 1st and 2nd element of tuple
I have a python list of points (x/y coordinates):
[(200, 245), (344, 248), (125, 34), ...]
It represents a contour on a 2d plane. I would like to use some numpy/scipy algorithms for smoothing, interpolation etc. They normally require numpy array as input. For example scipy.ndimage.interpolation.zoom.
What is the simplest way to get the right numpy array from my list of points?
EDIT: I added the word "image" to my question, hope it is clear now, I am really sorry, if it was somehow misleading. Example of what I meant (points to binary image array).
Input:
[(0, 0), (2, 0), (2, 1)]
Output:
[[0, 0, 1],
[1, 0, 1]]
Rounding the accepted answer here is the working sample:
import numpy as np
coordinates = [(0, 0), (2, 0), (2, 1)]
x, y = [i[0] for i in coordinates], [i[1] for i in coordinates]
max_x, max_y = max(x), max(y)
image = np.zeros((max_y + 1, max_x + 1))
for i in range(len(coordinates)):
image[max_y - y[i], x[i]] = 1
Ah, better now, so you do have all the points you want to fill... then its very simple:
image = np.zeros((max_x, max_y))
image[coordinates] = 1
You could create an array first, but its not necessary.
numpy.array(your_list)
numpy has very extensive documentation that you should try reading. You can find it online or by typing help(obj_you_want_help_with) (eg. help(numpy)) on the REPL.
Building on what Jon Clements and Dunes said, after doing
new_array = numpy.array([(200, 245), (344, 248), (125, 34), ...])
you will get a two-dimensional array where the first column contains the x coordinates and the second column contains the y coordinates. The array can be further split into separate x and y arrays like this:
x_coords = new_array[:,0]
y_coords = new_array[:,1]