How to center the nonzero values within 2D numpy array? - python

I'd like to locate all the nonzero values within a 2D numpy array and move them so that the image is centered. I do not want to pad the array because I need to keep it the same shape. For example:
my_array = np.array([[1, 1, 0, 0], [0, 0, 2, 4], [0, 0, 0, 0], [0, 0, 0, 0]])
# center...
>>> [[0 0 0 0]
[0 1 1 0]
[0 2 4 0]
[0 0 0 0]]
But in reality the arrays I need to center are much larger (like 200x200, 403x403, etc, and they are all square). I think np.nonzero and np.roll might come in handy, but am not sure of the best way to use these for my large arrays.

The combination of nonzero and roll can be used for this purpose. For example, if k=0 in the loop shown below, then np.any will identify the rows that are not identically zero. The first and last such rows are noted, and the shift along the axis is computed so that after the shift, (first+last)/2 will move to the middle row of the array. Then the same is done for columns.
import numpy as np
my_array = np.array([[1, 1, 0, 0], [2, 4, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
print(my_array) # before
for k in range(2):
nonempty = np.nonzero(np.any(my_array, axis=1-k))[0]
first, last = nonempty.min(), nonempty.max()
shift = (my_array.shape[k] - first - last)//2
my_array = np.roll(my_array, shift, axis=k)
print(my_array) # after
Before:
[[1 1 0 0]
[2 4 0 0]
[0 0 0 0]
[0 0 0 0]]
After:
[[0 0 0 0]
[0 1 1 0]
[0 2 4 0]
[0 0 0 0]]
Alternative: np.count_nonzeros can be used in place of np.any, which allows to potentially set some threshold for the number of nonzero pixels that are deemed "enough" to qualify a row as a part of the image.

Related

Minimum variance of binary matrix [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
I have a matrix of 0 and 1, example:
X =
[[1, 1, 0, 0],
[1, 0, 1, 1],
[0, 0, 1, 1],
[1, 1, 1, 1],
In each row, choose only an '1' and leave orthers become 0, to get the same number of '1' in each colunm. Basically, minimum variance after get sum by columns.
Example: From X above, the answer is:
Y =
[[1, 0, 0, 0],
[0, 0, 0, 1],
[0, 0, 1, 0],
[0, 1, 0, 0],
Each column in Y have a '1'. Y choose 1st '1' from row 1 of X, choose 2nd '1' in index 4 from row 2,.... X can have any size.
How can i do that?
Sorry for my bad english.
EDIT: what follows is a brute force technique which finds every "solution". It does not work for non-square arrays because OP's problem is vague on how non-square arrays should be treated. The technique I have in mind for finding a random solution is just a simple backtracking algorithm over idxs, which I may get around to tomorrow if somebody else doesn't come up with something better.
Here's a brute force solution which takes the cartesian product of the column indices where there is a one for each row:
import itertools
import numpy as np
x = np.array([[1, 1, 0, 0],
[1, 0, 1, 1],
[0, 0, 1, 1],
[1, 1, 1, 1]])
n = x.shape[1]
rows, cols = np.argwhere(x).T
idxs = np.split(cols, np.unique(rows, return_index=True)[1][1:])
col_idxs = [t for t in itertools.product(*idxs) if len(set(t)) == n]
row_idxs = np.arange(n)
Showing all solutions for this particular X:
for t in col_idxs:
z = np.zeros_like(x)
z[row_idxs, t] = 1
print(f"{z}\n")
Output:
[[1 0 0 0]
[0 0 1 0]
[0 0 0 1]
[0 1 0 0]]
[[1 0 0 0]
[0 0 0 1]
[0 0 1 0]
[0 1 0 0]]
[[0 1 0 0]
[1 0 0 0]
[0 0 1 0]
[0 0 0 1]]
[[0 1 0 0]
[1 0 0 0]
[0 0 0 1]
[0 0 1 0]]
[[0 1 0 0]
[0 0 1 0]
[0 0 0 1]
[1 0 0 0]]
[[0 1 0 0]
[0 0 0 1]
[0 0 1 0]
[1 0 0 0]]

Sort a occurrence matrix

I'm trying to sort a co-occurrence matrix.
I have a sparse matrix like this:
[0 0 0 1]
[1 0 1 0]
[0 0 0 0]
[0 0 1 0]
There's a way in Python to sort rows and columns in order to get a matrix with blocks containing the ones?
I would apply a process like shown in occurrence matrix here: https://en.wikipedia.org/wiki/Latent_semantic_analysis
Thanks a lot.
I am not sure to understand what you are looking for.
Do you wish to obtain at the end :
[1, 1, 1, 1]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
Is that what you are looking for ?
Best Regards
Mathieu

How to translate / shift a numpy array?

I am not sure what key-word to search for so if it has been already asked please link the response and close this thread.
I am trying to shift the non-zero entries of a numpy array by a fixed direction, for instance, imagine I have a 2d array:
0 1 2 0
0 3 0 0
0 0 0 0
0 0 0 0
Shifting it by (1,1) would produce the following array:
0 0 0 0
0 0 1 2
0 0 3 0
0 0 0 0
Let's say if the non-zero entries goes out of bound they're simply dropped. How might I do this?
edit: aparently some duplicate from this? Shift elements in a numpy array
I don't really see why are they the same question at all because that one talks about looping the things out of bound around, so it's more of a "rolling" action rather than shifting. Also I liked the solution here, it is very simple and readable.
edit again: fixed some formats
Using roll method from numpy.
>>> import numpy as np
>>> m
array([[0, 1, 2, 0],
[0, 3, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]])
>>> m = np.roll(m, 1, axis=0) # shift 1 place in horizontal axis
>>> m = np.roll(m, 1, axis=1) # shift 1 place in vertical axis
>>> m
array([[0, 0, 0, 0],
[0, 0, 1, 2],
[0, 0, 3, 0],
[0, 0, 0, 0]])
https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.roll.html
To simply manage the edges, you can enlarge your array in a bigger one :
square=\
array([[0, 2, 2, 0],
[0, 2, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]], dtype=int64)
n,m=square.shape
bigsquare=np.zeros((3*n,3*m),square.dtype)
bigsquare[n:2*n,m:2*m]=square
Then shift is just a view :
def shift(dx,dy):
x=n-dx
y=m-dy
return bigsquare[x:x+n,y:y+m]
print(shift(1,1))
#[[0 0 0 0]
# [0 0 2 2]
# [0 0 2 0]
# [0 0 0 0]]

How to create a numpy array from itertools.combinations without looping

Is there a way to get this result without a loop? I've made a couple attempts at fancy indexing with W[range(W.shape[0]),... but have been so far unsuccessful.
import itertools
import numpy as np
n = 4
ct = 2
one_index_tuples = list(itertools.combinations(range(n), r=ct))
W = np.zeros((len(one_index_tuples), n), dtype='int')
for row_index, col_index in enumerate(one_index_tuples):
W[row_index, col_index] = 1
print(W)
Result:
[[1 1 0 0]
[1 0 1 0]
[1 0 0 1]
[0 1 1 0]
[0 1 0 1]
[0 0 1 1]]
You can use fancy indexing (advanced indexing) as follows:
# reshape the row index to 2d since your column index is also 2d so that the row index and
# column index will broadcast properly
W[np.arange(len(one_index_tuples))[:, None], one_index_tuples] = 1
W
#array([[1, 1, 0, 0],
# [1, 0, 1, 0],
# [1, 0, 0, 1],
# [0, 1, 1, 0],
# [0, 1, 0, 1],
# [0, 0, 1, 1]])
Try this:
[[ 1 if i in x else 0 for i in range(n) ] for x in itertools.combinations( range(n), ct )]

First row of numpy.ones is still populated after referencing another matrix

I have a matrix 'A' whose values are shown below. After creating a matrix 'B' of ones using numpy.ones and assigning the values from 'A' to 'B' by indexing 'i' rows and 'j' columns, the resulting 'B' matrix is retaining the first row of ones from the original 'B' matrix. I'm not sure why this is happening with the code provided below.
The resulting 'B' matrix from command line is shown below:
import numpy
import numpy as np
A = np.matrix([[8,8,8,7,7,6,8,2],
[8,8,7,7,7,6,6,7],
[1,8,8,7,7,6,6,6],
[1,1,8,7,7,6,7,7],
[1,1,1,1,8,7,7,6],
[1,1,2,1,8,7,7,6],
[2,2,2,1,1,8,7,7],
[2,1,2,1,1,8,8,7]])
B = np.ones((8,8),dtype=np.int)
for i in np.arange(1,9):
for j in np.arange(1,9):
B[i:j] = A[i:j]
C = np.zeros((6,6),dtype=np.int)
print C
D = np.matrix([[1,1,2,3,3,2,2,1],
[1,2,1,2,3,3,3,2],
[1,1,2,1,1,2,2,3],
[2,2,3,2,2,2,1,3],
[1,2,2,3,2,3,1,3],
[1,2,3,3,2,3,2,3],
[1,2,2,3,2,3,1,2],
[2,2,3,2,2,3,2,2]])
print D
for k in np.arange(2,8):
for l in np.arange(2,8):
B[k,l] # point in middle
b = B[(k-1),(l-1)]
if b == 8:
# Matrix C is smaller than Matrix B
C[(k-1),(l-1)] = C[(k-1),(l-1)] + 1*D[(k-1),(l-1)]
#Output for Matrix B
B=
[1,1,1,1,1,1,1,1],
[8,8,7,7,7,6,6,7],
[1,8,8,7,7,6,6,6],
[1,1,8,7,7,6,7,7],
[1,1,1,1,8,7,7,6],
[1,1,2,1,8,7,7,6],
[2,2,2,1,1,8,7,7],
[2,1,2,1,1,8,8,7]
Python starts counting at 0, so your code should work find if you replace np.arange(1,9) with np.arange(9)
In [11]: np.arange(1,9)
Out[11]: array([1, 2, 3, 4, 5, 6, 7, 8])
In [12]: np.arange(9)
Out[12]: array([0, 1, 2, 3, 4, 5, 6, 7, 8])
As stated above: python indices start at 0.
In order to iterate over some (say matrix) indices, you should use the builtin function 'range' and not 'numpy.arange'. The arange returns an ndarray, while range returns a generator in a recent python version.
The syntax 'B[i:j]' does not refer to the element at row i and column j in an array B. It rather means: all rows of B starting at row i and going up to (but not including) row j (if B has so many rows, otherwise it returns until includingly the last row). The element at position i, j is in fact 'B[i,j]'.
The indexing syntax of python / numpy is quite powerful and performant.
For one thing, as others have mentioned, NumPy uses 0-based indexing. But even once you fix that, this is not what you want to use:
for i in np.arange(9):
for j in np.arange(9):
B[i:j] = A[i:j]
The : indicates slicing, so i:j means "all items from the i-th, up to the j-th, excluding the last one." So your code is copying every row over several times, which is not a very efficient way of doing things.
You probable wanted to use ,:
for i in np.arange(8): # Notice the range only goes up to 8
for j in np.arange(8): # ditto
B[i, j] = A[i, j]
This will work, but is also pretty wasteful performancewise when using NumPy. A much faster approach is to simply ask for:
B[:] = A
Here first what I think you are trying to do, with minimal corrections, comments to your code:
import numpy as np
A = np.matrix([[8,8,8,7,7,6,8,2],
[8,8,7,7,7,6,6,7],
[1,8,8,7,7,6,6,6],
[1,1,8,7,7,6,7,7],
[1,1,1,1,8,7,7,6],
[1,1,2,1,8,7,7,6],
[2,2,2,1,1,8,7,7],
[2,1,2,1,1,8,8,7]])
B = np.ones((8,8),dtype=np.int)
for i in np.arange(1,9): # i= 1...8
for j in np.arange(1,9): # j= 1..8, but A[8,j] and A[j,8] do not exist,
# if you insist on 1-based indeces, numpy still expects 0... n-1,
# so you'll have to subtract 1 from each index to use them
B[i-1,j-1] = A[i-1,j-1]
C = np.zeros((6,6),dtype=np.int)
D = np.matrix([[1,1,2,3,3,2,2,1],
[1,2,1,2,3,3,3,2],
[1,1,2,1,1,2,2,3],
[2,2,3,2,2,2,1,3],
[1,2,2,3,2,3,1,3],
[1,2,3,3,2,3,2,3],
[1,2,2,3,2,3,1,2],
[2,2,3,2,2,3,2,2]])
for k in np.arange(2,8): # k = 2..7
for l in np.arange(2,8): # l = 2..7 ; matrix B has indeces 0..7, so if you want inner points, you'll need 1..6
b = B[k-1,l-1] # so this is correct, gives you the inner matrix
if b == 8: # here b is a value in the matrix , not the index, careful not to mix those
# Matrix C is smaller than Matrix B ; yes C has indeces from 0..5 for k and l
# so to address C you'll need to subtract 2 from the k,l that you defined in the for loop
C[k-2,l-2] = C[k-2,l-2] + 1*D[k-1,l-1]
print C
output:
[[2 0 0 0 0 0]
[1 2 0 0 0 0]
[0 3 0 0 0 0]
[0 0 0 2 0 0]
[0 0 0 2 0 0]
[0 0 0 0 3 0]]
But there are more elegant ways to do it. In particular look up slicing, ( numpy conditional array arithmetic, possibly scipy threshold.All of the below should be much faster than Python loops too (numpy loops are written in C).
B=np.copy(A) #if you need a copy of A, this is the way
# one quick way to make a matrix that's 1 whereever A==8, and is smaller
from scipy import stats
B1=stats.threshold(A, threshmin=8, threshmax=8, newval=0)/8 # make a matrix with ones where there is an 8
B1=B1[1:-1,1:-1]
print B1
#another quick way to make a matrix that's 1 whereever A==8
B2 = np.zeros((8,8),dtype=np.int)
B2[A==8]=1
B2=B2[1:-1,1:-1]
print B2
# the following would obviously work with either B1 or B2 (which are the same)
print np.multiply(B2,D[1:-1,1:-1])
Output:
[[1 0 0 0 0 0]
[1 1 0 0 0 0]
[0 1 0 0 0 0]
[0 0 0 1 0 0]
[0 0 0 1 0 0]
[0 0 0 0 1 0]]
[[1 0 0 0 0 0]
[1 1 0 0 0 0]
[0 1 0 0 0 0]
[0 0 0 1 0 0]
[0 0 0 1 0 0]
[0 0 0 0 1 0]]
[[2 0 0 0 0 0]
[1 2 0 0 0 0]
[0 3 0 0 0 0]
[0 0 0 2 0 0]
[0 0 0 2 0 0]
[0 0 0 0 3 0]]
A cleaner way, in my opinion, of writing the C loop is:
for k in range(1,7):
for l in range(1,7):
if B[k,l]==8:
C[k-1, l-1] += D[k,l]
That inner block of B (and D) can be selected with slices, B[1:7, 1:7] or B[1:-1, 1:-1].
A and D are defined as np.matrix. Since we aren't doing matrix multiplications here (no dot products), that can create problems. For example I was puzzled why
In [27]: (B[1:-1,1:-1]==8)*D[1:-1,1:-1]
Out[27]:
matrix([[2, 1, 2, 3, 3, 3],
[3, 3, 3, 4, 5, 5],
[1, 2, 1, 1, 2, 2],
[2, 2, 3, 2, 3, 1],
[2, 2, 3, 2, 3, 1],
[2, 3, 3, 2, 3, 2]])
What I expected (and matches the loop C) is:
In [28]: (B[1:-1,1:-1]==8)*D.A[1:-1,1:-1]
Out[28]:
array([[2, 0, 0, 0, 0, 0],
[1, 2, 0, 0, 0, 0],
[0, 3, 0, 0, 0, 0],
[0, 0, 0, 2, 0, 0],
[0, 0, 0, 2, 0, 0],
[0, 0, 0, 0, 3, 0]])
B = A.copy() still leaves B as matrix. B=A.A returns an np.ndarray. (as does np.copy(A))
D.A is the array equivalent of D. B[1:-1,1:-1]==8 is boolean, but when used in the multiplication context it is effectively 0s and 1s.
But if we want to stick with np.matrix then I'd suggest using the element by element multiply function:
In [46]: np.multiply((A[1:-1,1:-1]==8), D[1:-1,1:-1])
Out[46]:
matrix([[2, 0, 0, 0, 0, 0],
[1, 2, 0, 0, 0, 0],
[0, 3, 0, 0, 0, 0],
[0, 0, 0, 2, 0, 0],
[0, 0, 0, 2, 0, 0],
[0, 0, 0, 0, 3, 0]])
or just multiply the full matrixes, and select the inner block after:
In [47]: np.multiply((A==8), D)[1:-1, 1:-1]
Out[47]:
matrix([[2, 0, 0, 0, 0, 0],
[1, 2, 0, 0, 0, 0],
[0, 3, 0, 0, 0, 0],
[0, 0, 0, 2, 0, 0],
[0, 0, 0, 2, 0, 0],
[0, 0, 0, 0, 3, 0]])

Categories