Numpy, counting unique neighbours in 2d array - python

I am trying to count the number of neighbours for each element in a 2d numpy array that differ from the element itself (4-neighbourhood in this case, but 8-neighbourhood is also interesting).
Something like this:
input labels:
[[1 1 1 2 2 2 2]
[1 1 1 2 2 2 2]
[1 1 1 2 2 2 2]
[1 1 3 3 3 5 5]
[4 4 4 3 3 5 5]
[4 4 4 3 3 5 5]] (6, 7)
count of unique neighbour labels:
[[0 0 1 1 0 0 0]
[0 0 1 1 0 0 0]
[0 0 2 2 1 1 1]
[1 2 2 1 2 2 1]
[1 1 1 1 1 1 0]
[0 0 1 1 1 1 0]] (6, 7)
I have the code below, and out of curiosity I am wondering if there is a better way to achieve this, perhaps without the for loops?
import numpy as np
import cv2
labels_image = np.array([
[1,1,1,2,2,2,2],
[1,1,1,2,2,2,2],
[1,1,1,2,2,2,2],
[1,1,3,3,3,5,5],
[4,4,4,3,3,5,5],
[4,4,4,3,3,5,5]])
print('input labels:\n', labels_image, labels_image.shape)
# Make a border, otherwise neighbours are counted as wrapped values from the other side
labels_image = cv2.copyMakeBorder(labels_image, 1, 1, 1, 1, cv2.BORDER_REPLICATE)
offsets = [(-1, 0), (0, -1), (0, 1), (1, 0)] # 4 neighbourhood
# Stack labels_image with one shifted per offset so we get a 3d array
# where each z-value corresponds to one of the neighbours
stacked = np.dstack(np.roll(np.roll(labels_image, i, axis=0), j, axis=1) for i, j in offsets)
# count number of unique neighbours, also take the border away again
labels_image = np.array([[(len(np.unique(stacked[i,j])) - 1)
for j in range(1, labels_image.shape[1] - 1)]
for i in range(1, labels_image.shape[0] - 1)])
print('count of unique neighbour labels:\n', labels_image, labels_image.shape)
I tried using np.unique with the return_counts and axis arguments, but could not get it to work.

Here's one approach -
import itertools
def count_nunique_neighbors(ar):
a = np.pad(ar, (1,1), mode='reflect')
c = a[1:-1,1:-1]
top = a[:-2,1:-1]
bottom = a[2:,1:-1]
left = a[1:-1,:-2]
right = a[1:-1,2:]
ineq = [top!= c,bottom!= c, left!= c, right!= c]
count = ineq[0].astype(int) + ineq[1] + ineq[2] + ineq[3]
blck = [top, bottom, left, right]
for i,j in list(itertools.combinations(range(4), r=2)):
count -= ((blck[i] == blck[j]) & ineq[j])
return count
Sample run -
In [22]: a
Out[22]:
array([[1, 1, 1, 2, 2, 2, 2],
[1, 1, 1, 2, 2, 2, 2],
[1, 1, 1, 2, 2, 2, 2],
[1, 1, 3, 3, 3, 5, 5],
[4, 4, 4, 3, 3, 5, 5],
[4, 4, 4, 3, 3, 5, 5]])
In [23]: count_nunique_neighbors(a)
Out[23]:
array([[0, 0, 1, 1, 0, 0, 0],
[0, 0, 1, 1, 0, 0, 0],
[0, 0, 2, 2, 1, 1, 1],
[1, 2, 2, 1, 2, 2, 1],
[1, 1, 1, 1, 1, 1, 0],
[0, 0, 1, 1, 1, 1, 0]])

Related

How to create circularly shift matrix using python

I am writing a code for circular convolution and now I am stuck at position where I need to create circular shift matrix can anyone help me to do this using python or numpy
I want to shift this matrix circularly
[1, -1, 2, 0]
I want matrix like,
[ 1, -1, 2, 0]
[ 0, 1, -1, 2]
[-2, 0, 1, -1]
[-1, -2, 0, 1]
code :-
https://drive.google.com/file/d/16XNJ7Q5Iwdlg6Ouz8HU8PgW17xCd1gTp/view?usp=sharing
when you shift by n you take last n elements and put then in the front side of the list and that is l[len(l)-n:]. And remaining 0 to len(l)-n-1 elements you put at the end and that is l[0:len(l)-n]
def shift(l,n):
return l[len(l)-n:] + l[0:len(l)-n]
output = []
m = [1, -1, 2, 0]
for i in range(4):
output.append(shift(m, i))
print(output)
# [[1, -1, 2, 0],
# [0, 1, -1, 2],
# [2, 0, 1, -1],
# [-1, 2, 0, 1]]
As suggested in a duplicate, collections.deque.rotate (builtin library) or numpy.roll (more efficient 3rd-party library) is almost-certainly what you're looking for!
>>> from collections import deque as Deque
>>> d = Deque([1, -1, 2, 0])
>>> d
deque([1, -1, 2, 0])
>>> d.rotate(1)
>>> d
deque([0, 1, -1, 2])
>>> import numpy as np
>>> arr = np.array([1, -1, 2, 0])
>>> np.roll(arr, 1)
array([ 0, 1, -1, 2])
>>> np.roll(arr, 2)
array([ 2, 0, 1, -1])
NOTE that the deque mutates the original collection, while numpy.roll returns a rotated copy
You can create a single DataFrame by assembling each possible roll for the length of the array, though you may find it's more efficient to calculate the rolls when you need them
>>> arr = np.array([1, 2])
>>> pd.DataFrame([np.roll(arr, roll_index) for roll_index in range(len(arr))])
0 1
0 1 2
1 2 1
>>> arr = np.array([1, -1, 2, 0, 9])
>>> pd.DataFrame([np.roll(arr, roll_index) for roll_index in range(len(arr))])
0 1 2 3 4
0 1 -1 2 0 9
1 9 1 -1 2 0
2 0 9 1 -1 2
3 2 0 9 1 -1
4 -1 2 0 9 1

Python: How do I change items between lists?

I'm trying to code the 2048 game, and I'm stuck at the move up/down part.
So ex, if I've a list like this:
2 0 2 4
0 8 2 4
4 4 2 0
0 4 0 2
I want to move my numbers up, so I've something like this:
2 8 2 4
4 4 2 4
0 4 2 2
0 0 0 0
And I don't know how to even begin, someone can give me tips?
I tried this but it only works for a identity matrix:
new_matrix = []
for line in matrix:
for pos, element in enumerate(line):
if element != 0:
new_matrix.append(element)
line[pos] = 0
list.clear(matrix[0])
matrix[0] = new_matrix
return (matrix)
You can iterate through the list, check which index contains 0, let's say (n) then assign the value of the next elements nth index, and make next elements nth index 0:
A = [[1,0,2,1],[0,1,3,4],[4,5,1,0],[0,3,0,1]]
length = len(A)
for i, elem in enumerate(A):
for j, item in enumerate(elem):
if item == 0 and i + 1 < length:
A[i][j] = A[i+1][j]
A[i+1][j] = 0
print(A)
Prints:
[[1, 1, 2, 1], [4, 5, 3, 4], [0, 3, 1, 1], [0, 0, 0, 0]]
For the current example:
>>> A
[[2, 0, 2, 4],
[0, 8, 2, 4],
[4, 4, 2, 0],
[0, 4, 0, 2]]
>>> for i, elem in enumerate(A):
for j, item in enumerate(elem):
if item == 0 and i + 1 < length:
A[i][j] = A[i+1][j]
A[i+1][j] = 0
>>> A
[[2, 8, 2, 4],
[4, 4, 2, 4],
[0, 4, 2, 2],
[0, 0, 0, 0]]

creating matrix, which reflex properties of a given one with numpy

when given a N x K matrix with elements from 0 to K, is there an efficient an elegant way to create the following matrix:
#0 | #2 | ... | #K
-------------------------
Row 1 x | y | ... | z
-------------------------
Row 2 a | b | ... | d
-------------------------
...
-------------------------
Row N g | h | ... | j
Where in cell 1,2 should be the amount of 2s in the first row for example.
I know, that a rather inefficient way would be to do this with two for loops, but I was wondering if that is also possible to solve with some matrix / NumPy operations.
Cheers
EDIT:
some code might look like this:
x_mod = np.zeros((N,I))
for n in range(N):
for i in range(I):
x_mod[n][int(X[n][i])] += 1
where X is the original matrix and x_mod the new one.
So for
X = 2 3 4 4 0
0 1 3 3 2
1 1 4 2 2
the desired result would look like:
1 0 1 1 2
1 1 1 2 0
0 2 2 0 1
I think you are looking for np.bincount. It's fast but unfortunately only works 1D. So one loop is required:
import numpy as np
rng = np.random.default_rng()
N,K = 5,6
in_ = rng.integers(0,K,(N,K))
in_
# array([[2, 4, 3, 5, 4, 1],
# [4, 5, 3, 5, 4, 5],
# [2, 2, 0, 3, 3, 3],
# [5, 5, 0, 4, 4, 5],
# [1, 1, 0, 2, 3, 2]])
out = np.array([np.bincount(i,None,K) for i in in_])
out
# array([[0, 1, 1, 1, 2, 1],
# [0, 0, 0, 1, 2, 3],
# [1, 0, 2, 3, 0, 0],
# [1, 0, 0, 0, 2, 3],
# [1, 2, 2, 1, 0, 0]])

Make everything zeros in a numpy 2d-array outside of bounding box

Say I have a numpy 2d-array like:
>>> ar
array([[1, 2, 3, 1, 2, 3, 1, 7, 2, 3],
[4, 3, 2, 4, 5, 5, 6, 5, 2, 1],
[5, 4, 2, 4, 6, 2, 4, 2, 1, 4],
[1, 5, 6, 1, 4, 2, 2, 4, 1, 4],
[7, 4, 5, 6, 2, 5, 3, 5, 6, 7]])
I define a bounding box like follows, and I want that all the items in ar which are not in the bounding box all become zeros:
>>> my_bbox = ((2, 7), (1, 3))
>>> make_zeros(ar, bounding_box)
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 2, 4, 5, 5, 6, 5, 0, 0],
[0, 0, 2, 4, 6, 2, 4, 2, 0, 0],
[0, 0, 6, 1, 4, 2, 2, 4, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
Beyond the "obvious" (and also ugly and inefficient) looping methods, is there a slice/vector-type operation to achieve that? I believe that array masks might work, but not sure how specifically.
You might like to create a copy of array and then write needed submatrix on top of it:
ar_zeroed = np.zeros(ar.shape).astype(int)
slice1, slice2 = slice(my_bbox[0][0], my_bbox[0][1]+1),
slice(my_bbox[1][0], my_bbox[1][1]+1)
ar_zeroed[slice2, slice1] = ar[slice2, slice1]
Note that dimensions are swapped in your bounding box
How about something like this?
import numpy as np
arr = np.array([[1, 2, 3, 1, 2, 3, 1, 7, 2, 3],
[4, 3, 2, 4, 5, 5, 6, 5, 2, 1],
[5, 4, 2, 4, 6, 2, 4, 2, 1, 4],
[1, 5, 6, 1, 4, 2, 2, 4, 1, 4],
[7, 4, 5, 6, 2, 5, 3, 5, 6, 7]])
print(arr, end='\n\n')
first_idx = (1, 3)
second_idx = (2, 7)
first_slice = slice(first_idx[0], first_idx[1] + 1)
second_slice = slice(second_idx[0], second_idx[1] + 1)
res = np.zeros_like(arr)
res[first_slice, second_slice] = arr[first_slice, second_slice]
print(res)
Output:
[[1 2 3 1 2 3 1 7 2 3]
[4 3 2 4 5 5 6 5 2 1]
[5 4 2 4 6 2 4 2 1 4]
[1 5 6 1 4 2 2 4 1 4]
[7 4 5 6 2 5 3 5 6 7]]
[[0 0 0 0 0 0 0 0 0 0]
[0 0 2 4 5 5 6 5 0 0]
[0 0 2 4 6 2 4 2 0 0]
[0 0 6 1 4 2 2 4 0 0]
[0 0 0 0 0 0 0 0 0 0]]
I'm almost certain it can still be improved, though.
You are trying to do "something" to the slices ar[1:3+1, 2:7+1]. The expression in square brackets is turned into a tuple of slice objects by the interpreter. You can programatically create the same sequence of slices and pass them in as the index to an array. This can be used in a number of approaches.
The "simplest" is to create a new array of zeros and assign the required swath:
index = tuple(slice(start, stop + 1) for start, stop in bounding_box)
result = np.zeros_like(ar)
result[index] = ar[index]
Another would be to make a mask, invert it, and set the elements it corresponds to to zero:
mask = np.ones(ar.shape, dtype=np.bool)
mask[index] = zero
ar[mask] = 0
Finally, you could just set the appropriate swaths to zeros directly. This is probably less optimal than the other approaches, unless you code it very carefully to not have overlaps in the corners:
index = [slice(None) for _ in range(ar.ndim)]
for dim, (start, stop) in enumerate(bounding_box):
# Blank out initial portion
index[dim] = slice(None, start)
ar[tuple(index)] = 0
# Blank out trailing portion
index[dim] = slice(stop + 1, None)
ar[tuple(index)] = 0
# Reinstate ":"
index[dim] = slice(None)
I was just thinking of this solution
a = np.random.uniform(1, 10, [7, 8]).astype(int)
print(a)
b = np.zeros([7, 8]).astype(int)
b[1:4, 2:5] = 1
print(a * b)
Outputs
[[8 3 8 5 8 3 5 7]
[8 8 2 8 5 4 3 5]
[5 2 2 2 5 1 4 7]
[5 8 9 3 5 6 4 1]
[3 6 1 1 4 6 4 8]
[9 5 2 7 8 2 1 1]
[1 4 5 4 2 6 2 4]]
[[0 0 0 0 0 0 0 0]
[0 0 2 8 5 0 0 0]
[0 0 2 2 5 0 0 0]
[0 0 9 3 5 0 0 0]
[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]]
Not totally satisfied though because it works only if we want 0 out of the bounding box. If we want any other value, then it doesn't work as is.
Guess it was already answered, but I will still give a try:
import numpy as np
bbox = ((2, 7), (1, 3))
arr = np.array([[1, 2, 3, 1, 2, 3, 1, 7, 2, 3],
[4, 3, 2, 4, 5, 5, 6, 5, 2, 1],
[5, 4, 2, 4, 6, 2, 4, 2, 1, 4],
[1, 5, 6, 1, 4, 2, 2, 4, 1, 4],
[7, 4, 5, 6, 2, 5, 3, 5, 6, 7]])
zeros = np.zeros(arr.shape)
zeros[bbox[1][0]:bbox[1][1]+1,
bbox[0][0]:bbox[0][1]+1] = arr[bbox[1][0]:bbox[1][1]+1,
bbox[0][0]:bbox[0][1]+1]
print(zeros)
Out:
[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 2. 4. 5. 5. 6. 5. 0. 0.]
[0. 0. 2. 4. 6. 2. 4. 2. 0. 0.]
[0. 0. 6. 1. 4. 2. 2. 4. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]

numpy replace 2d bool array with sum of consecutive elements across an axis efficiently

I have a bool array (bool_arr) that I want to replace the consecutive non-zero numbers along the columns with their count (consecutive_count) (which is also the max/last number of the consecutive group)
bool_arr = consecutive_count =
[[1 1 1 1 0 1] [[3 6 1 6 0 1]
[1 1 0 1 1 0] [3 6 0 6 5 0]
[1 1 1 1 1 1] [3 6 3 6 5 2]
[0 1 1 1 1 1] [0 6 3 6 5 2]
[1 1 1 1 1 0] [2 6 3 6 5 0]
[1 1 0 1 1 1]] [2 6 0 6 5 1]]
I've created my own function that gets the cumulative sum of consecutive non-zero elements along the columns
consecutive_cumsum =
[[1 1 1 1 0 1]
[2 2 0 2 1 0]
[3 3 1 3 2 1]
[0 4 2 4 3 2]
[1 5 3 5 4 0]
[2 6 0 6 5 1]]
I currently use the following to get consecutive_count:
bool_arr = np.array([[1,1,1,1,0,1],
[1,1,0,1,1,0],
[1,1,1,1,1,1],
[0,1,1,1,1,1],
[1,1,1,1,1,0],
[1,1,0,1,1,1]])
consecutive_cumsum = np.array([[1,1,1,1,0,1],
[2,2,0,2,1,0],
[3,3,1,3,2,1],
[0,4,2,4,3,2],
[1,5,3,5,4,0],
[2,6,0,6,5,1]])
consecutive_count = consecutive_cumsum.copy()
for x in range(consecutive_count.shape[1]):
maximum = 0
for y in range(consecutive_count.shape[0]-1, -1, -1):
if consecutive_cumsum[y,x] > 0:
if consecutive_cumsum[y,x] < maximum: consecutive_count[y,x] = maximum
else: maximum = consecutive_cumsum[y,x]
else: maximum = 0
print(consecutive_count)
It works great but I am iterating over every element to replace with the max, between zeros.
Is there a way to use numpy to vectorize this instead of looping over all elements. And as a bonus, specify which axis (row vs column) it will perform it on
The new (v1.15.0 I believe) append and prepend keywords of np.diff make this easy:
bnd = np.diff(bool_arr, axis=0, prepend=0, append=0)
x, y = np.where(bnd.T)
bnd.T[x, y] *= (y[1::2]-y[::2]).repeat(2)
bnd[:-1].cumsum(axis=0)
# array([[3, 6, 1, 6, 0, 1],
# [3, 6, 0, 6, 5, 0],
# [3, 6, 3, 6, 5, 2],
# [0, 6, 3, 6, 5, 2],
# [2, 6, 3, 6, 5, 0],
# [2, 6, 0, 6, 5, 1]])
With selectable axis:
def count_ones(a, axis=-1):
a = a.swapaxes(-1, axis)
bnd = np.diff(a, axis=-1, prepend=0, append=0)
*idx, last = np.where(bnd)
bnd[(*idx, last)] *= (last[1::2]-last[::2]).repeat(2)
return bnd[..., :-1].cumsum(axis=-1).swapaxes(-1, axis)
UPDATE: and a version that works with general (not just 0/1) entries:
def sum_stretches(a, axis=-1):
a = a.swapaxes(-1, axis)
dtype = np.result_type(a, 'i1')
bnd = np.diff((a!=0).astype(dtype), axis=-1, prepend=0, append=0)
*idx, last = np.where(bnd)
A = np.concatenate([np.zeros((*a.shape[:-1], 1), a.dtype), a.cumsum(axis=-1)], -1)[(*idx, last)]
bnd[(*idx, last)] *= (A[1::2]-A[::2]).repeat(2)
return bnd[..., :-1].cumsum(axis=-1).swapaxes(-1, axis)
Using itertools.groupby:
import itertools
for i in range(b.shape[1]):
counts = []
for k,v in itertools.groupby(b[:,i]):
g = list(v)
counts.extend([sum(g)] * len(g))
b[:,i] = counts
Output:
array([[3, 6, 1, 6, 0, 1],
[3, 6, 0, 6, 5, 0],
[3, 6, 3, 6, 5, 2],
[0, 6, 3, 6, 5, 2],
[2, 6, 3, 6, 5, 0],
[2, 6, 0, 6, 5, 1]])
building on paulpanzer's answer for poor souls (like me) who dont have numpy v1.15+
def sum_stretches(a, axis=-1):
a = a.swapaxes(-1, axis)
padding = [[0,0].copy()]*a.ndim
padding[-1] = [1,1]
padded = np.pad((a!=0), padding, 'constant', constant_values=0).astype('int32')
bnd = np.diff(padded, axis=-1)
*idx, last = np.where(bnd)
A = np.concatenate([np.zeros((*a.shape[:-1], 1), 'int32'), a.cumsum(axis=-1)], -1)[(*idx, last)]
bnd[(*idx, last)] *= (A[1::2]-A[::2]).repeat(2)
return bnd[..., :-1].cumsum(axis=-1).swapaxes(-1, axis)

Categories