Calculating neighbor-distances for all grid points in NumPy - python

Imagine a 3-dimensional grid in space, where each grid point has a binary value.
The values of the grid points are represented by a 3d numpy array.
For each grid point that has a value of 1, we want to know where the nearest 0-valued point is located, in 14 different directions (±x, ±y, ±z, and 8 diagonals).
That means, with a numpy array of shape (nx, ny, nz) as input, the output should be an array of shape (nx, ny, nz, 14), where each value in the last dimension of the output corresponds to distance of that point to the nearest 0-valued neighbor in one direction (but we don't need to calculate this for 0-valued grid points, so their values can be set to zero).
What is the most efficient way to calculate this in numpy?
My current approach is looping over the grid points (three nested for-loops), and for each point, first checking whether it's a 1, and if so, slicing the array 14 times to get the points in one of the directions starting from the current point, and taking the index of first 0 element as distance to nearest 0-valued neighbor in that direction.

Here's a way that should work for all the non-diagonal cases. The example is just in 1d, but should work along each axis in 3d. This just computes the distance to the nearest 0 to the left:
a = np.array([0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1])
n = len(a)
zero_index = np.arange(0, n) * (1 - a)
one_index = np.arange(0, n) * a
(one_index - np.maximum.accumulate(zero_index)) * a
# -> returns array([0, 1, 2, 3, 4, 0, 0, 0, 1, 2, 0, 1, 2])
The idea is that zero_index has the integer index wherever a is 0, and one_index has the integer index wherever a is 1. np.maximum.accumulate does a cumulative maximum, which fills the zero values in zero_index with the values from the left. The final * a just sets the final distance to 0 wherever these was a 0.
This doesn't handle the case when a[0] is 1. We can fix that by offsetting the zero_index by 1 (so it starts with 1 if a[0] is 0):
a = np.array([1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1])
n = len(a)
zero_index = np.arange(1, n+1) * (1 - a)
one_index = np.arange(0, n) * a
(one_index - np.maximum.accumulate(zero_index) + 1) * a
# -> returns array([1, 2, 3, 4, 5, 0, 0, 0, 1, 2, 0, 1, 2])
You can handle the opposite direction with reverse indexing.
Unfortunately I can't propose a vectorized way of doing the diagonals. Perhaps there's a way (some kind of shape or roll) that skews the diagonal to make it aligned with an axis.

Related

grid with circular distances python

I have a 2d grid of dim q(rows) p(columns).
The element of the grid are the indices of the element itself.
So the element a[i,j]=(i,j).
Now I need to create pair-wise distances between these points with circularity constraint, meaning that the element distance(a[i,p-1],a[i,0])=1 (I am using 0-index based matrix as in Python). Analogously distance(a[q-1,j],a[0,j])=1
Observe that distance(a[q-2,j],a[0,j]) is the shorter vertical path going from 0 to q-2 and the path from q2 to 0 (leveraging the circularity of the grid). Same thing happens horizontally.
My question is: is there a NumPy function that enables to quickly calculate such a pairwise distances matrix?
I don't know of a function do do this, but it's pretty easy to compute manually:
import numpy as np
q = 6 # rows
p = 5 # columns
# Create array of indices
a = np.mgrid[0:q, 0:p].transpose(1, 2, 0)
# The array `a` now has shape (q, p, 2) :
a[1, 2] # array([1, 2])
a[3, 0] # array([3, 0])
# Create a new array measuring the i,j difference between all pairs of
# locations in the array. `diff` will have shape (q, p, q, p, 2) :
diff = a[None, None, :, :, :] - a[:, :, None, None, :]
# Modify diff to obey circularity constraint
di = diff[..., 0]
dj = diff[..., 1]
di[di > q//2] -= q
dj[dj > p//2] -= p
# compute distance from diff
dist = (diff[..., 0]**2 + diff[..., 1]**2)**0.5
# test:
dist[1, 0, 1, 0] # 0.0
dist[1, 0, 1, 1] # 1.0
dist[1, 0, 1, 2] # 2.0
dist[1, 0, 1, 3] # 2.0
dist[1, 0, 1, 4] # 1.0

Scanning for groups of the same value in numpy array

I have a numpy array where 0 denotes empty space and 1 denotes that a location is filled. I am trying to find a quick method of scanning the numpy array for where there are multiple values of zero adjacent to each other and return the location of the central zero.
For Example if I had the following array
[0 1 0 1]
[0 0 0 1]
[0 1 0 1]
[1 1 1 1]
I want to return the locations for which there is an adjacent zero on either side of a central zero
e.g
[1,1]
as this is the central of 3 zeros, i.e there is a zero either side of the zero at this location
Im aware that this can be calculated using if statements, but wondered if there was a more pythonic way of doing this.
Any help is greatly appreciated
The desired output here for arbitrary inputs is not exhaustively specified in the question, but here is a possible approach that might be useful for this kind of problem, and adapted to the details of the desired output. It uses np.cumsum, np.bincount, np.where, and np.median to find the middle index for groups of consecutive zeros along rows of a 2D array:
import numpy as np
def find_groups(x, min_size=3, value=0):
# Compute a sequential label for groups in each row.
xc = (x != value).cumsum(1)
# Count the number of occurances per group in each row.
counts = np.apply_along_axis(
lambda x: np.bincount(x, minlength=1 + xc.max()),
axis=1, arr=xc)
# Filter by minimum number of occurances.
i, j = np.where(counts >= min_size)
# Compute the median index of each group.
return [
(ii, int(np.ceil(np.median(np.where(xc[ii] == jj)[0]))))
for ii, jj in zip(i, j)
]
x = np.array([[0, 1, 0, 1],
[0, 0, 0, 1],
[0, 1, 0, 1],
[1, 1, 1, 1]])
print(find_groups(x))
# [(1, 1)]
It should work properly even for multiple rows with groups of varying sizes, and even multiple groups per row:
x2 = np.array([[0, 1, 0, 1, 1, 1, 1],
[0, 0, 0, 1, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0]])
print(find_groups(x2))
# [(1, 1), (1, 5), (2, 3), (3, 3)]

Element-wise Cross Product of 2D arrays of Coordinates

I'm working with a dataset that stores an array of unit-vectors as arrays of the vectors' components.
How would I use vectorised code / broadcasting to write clean and compact code to give the cross product of the vectors element-wise?
For example, here's a brute force method for looping through the length of the arrays, picking out the coordinates, re-composing the two vectors, then calculating the cross product.
x = [0,0,1,1]
y = [0,1,0,1]
z = [1,0,0,1]
v1 = np.array([x,y,z])
x = [1,1,0,1]
y = [1,0,1,1]
z = [0,1,1,1]
v2 = np.array([x,y,z])
result = []
for i in range(0, len(x)):
a = [v1[0][i], v1[1][i], v1[2][i]]
b = [v2[0][i], v2[1][i], v2[2][i]]
result.append(np.cross(a,b))
result
>>>
[
array([-1, 1, 0]),
array([ 1, 0, -1]),
array([ 0, -1, 1]),
array([ 0, 0, 0])
]
I've tried to understand this question and answer to generalise it, but failed:
- Element wise cross product of vectors contained in 2 arrays with Python
np.cross can work with 2D arrays too, you just need to specify the right axes:
np.cross(v1,v2, axisa=0, axisb=0)
array([[-1, 1, 0],
[ 1, 0, -1],
[ 0, -1, 1],
[ 0, 0, 0]])

What is the most pythonic way to find all coordinate pairs in a numpy array that match a specific condition?

So given a 2d numpy array consisting of ones and zeros, I want to find every index where it is a value of one and where either to its top, left, right, or bottom consists of a zero. For example in this array
0 0 0 0 0
0 0 1 0 0
0 1 1 1 0
0 0 1 0 0
0 0 0 0 0
I only want coordinates for (1,2), (2,1), (2,3) and (3,2) but not for (2,2).
I have created code that works and creates two lists of coordinates, similar to the numpy nonzero method, however I don't think its very "pythonic" and I was hoping there was a better and more efficient way to solve this problem. (*Note this only works on arrays padded by zeros)
from numpy import nonzero
...
array= ... # A numpy array consistent of zeros and ones
non_zeros_pairs=nonzero(array)
coordinate_pairs=[[],[]]
for x, y in zip(temp[0],temp[1]):
if array[x][y+1]==0 or array[x][y-1]==0 or array[x+1][y]==0 or array[x-1][y]==0:
coordinate_pairs[0].append(x)
coordinate_pairs[1].append(y)
...
If there exist methods in numpy that can handle this for me, that would be awesome. If this question has already been asked/answered on stackoverflow before, I will gladly remove this, I just struggled to find anything. Thank You.
Setup
import scipy.signal
import numpy as np
a = np.array([[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]])
Create a window which matches the four directions from each value, and convolve. Then, you can check if elements are 1, and if their convolution is less than 4, since a value ==4 means that the value was surrounded by 1s
window = np.array([[0, 1, 0],
[1, 0, 1],
[0, 1, 0]])
m = scipy.signal.convolve2d(a, window, mode='same', fillvalue=1)
v = np.where(a & (m < 4))
list(zip(*v))
[(1, 2), (2, 1), (2, 3), (3, 2)]

How to find where in numpy array a zero element is preceded by at least N-1 consecutive zeros?

Given a numpy array (let it be a bit array for simplicity), how can I construct a new array of the same shape where 1 stands exactly at the positions where in the original array there was a zero, preceded by at least N-1 consecutive zeros?
For example, what is the best way to implement function nzeros having two arguments, a numpy array and the minimal required number of consecutive zeros:
import numpy as np
a = np.array([0, 0, 0, 0, 1, 0, 0, 0, 1, 1])
b = nzeros(a, 3)
Function nzeros(a, 3) should return
array([0, 0, 1, 1, 0, 0, 0, 1, 0, 0])
Approach #1
We can use 1D convolution -
def nzeros(a, n):
# Define kernel for 1D convolution
k = np.ones(n,dtype=int)
# Get sliding summations for zero matches with that kernel
s = np.convolve(a==0,k)
# Look for summations that are equal to n value, which will occur for
# n consecutive 0s. Remember that we are using a "full" version of
# convolution, so there's one-off offsetting because of the way kernel
# slides across input data. Also, we need to create 1s at places where
# n consective 0s end, so we would need to slice out ending elements.
# Thus, we would end up with the following after int dtype conversion
return (s==n).astype(int)[:-n+1]
Sample run -
In [46]: a
Out[46]: array([0, 0, 0, 0, 1, 0, 0, 0, 1, 1])
In [47]: nzeros(a,3)
Out[47]: array([0, 0, 1, 1, 0, 0, 0, 1, 0, 0])
In [48]: nzeros(a,2)
Out[48]: array([0, 1, 1, 1, 0, 0, 1, 1, 0, 0])
Approach #2
Another way to solve and this could be considered as a variant of the 1D convolution approach, would be to use erosion, because if you look at the outputs, we can simply erode the mask of 0s from the starts until n-1 places. So, we can use scipy.ndimage.morphology's binary_erosion that also allow us to specify the portion of kernel center with its origin arg, hence we will avoid any slicing. The implementation would look something like this -
from scipy.ndimage.morphology import binary_erosion
out = binary_erosion(a==0,np.ones(n),origin=(n-1)//2).astype(int)
Using for loop:
def nzeros(a, n):
#Create a numpy array of zeros of length equal to n
b = np.zeros(n)
#Create a numpy array of zeros of same length as array a
c = np.zeros(len(a), dtype=int)
for i in range(0,len(a) - n):
if (b == a[i : i+n]).all(): #Check if array b is equal to slice in a
c[i+n-1] = 1
return c
Sample Output:
print(nzeros(a, 3))
[0 0 1 1 0 0 0 1 0 0]

Categories