how to change the particuler elements of an array - python

I have an array for an example:
import numpy as np
data=np.array([[4,4,4,0,1,1,1,1,1,1,0,0,0,0,1],
[3,0,1,0,1,1,1,1,1,1,1,1,1,1,0],
[6,0,0,0,1,1,1,1,1,1,1,1,1,1,0],
[2,0,0,0,1,1,1,0,1,0,1,1,1,0,0],
[2,0,1,0,1,1,1,0,1,0,1,0,1,0,0]])
Requirement :
In the data array, if element 1's are consecutive as the square size
of ((3,3)) and more than square size no changes. Otherwise, replace
element value 1 with zero except the square size.
Expected output :
[[4 4 4 0 1 1 1 1 1 1 0 0 0 0 0]
[3 0 0 0 1 1 1 1 1 1 1 1 1 0 0]
[6 0 0 0 1 1 1 1 1 1 1 1 1 0 0]
[2 0 0 0 1 1 1 0 0 0 1 1 1 0 0]
[2 0 0 0 1 1 1 0 0 0 0 0 0 0 0]]
current code:
k = np.ones((3,3))
print(k)
jk=binary_dilation(binary_erosion(data==1, k), k)
print(jk)
current output:
[[False False False False True True True True True True False
False False False False]
[False False False False True True True True True True True
True True False False]
[False False False False True True True True True True True
True True False False]
[False False False False True True True False False False True
True True False False]
[False False False False True True True False False False False
False False False False]]

You can use a 2D convolution on the 1s with a 3x3 kernel of 1s to identify the centers of the 3x3 squares, then dilate them and restore the non 1 numbers
from scipy.signal import convolve2d
from scipy.ndimage import binary_dilation
# get 1s (as boolean)
m = data==1
kernel = np.ones((3, 3))
# get centers
conv = convolve2d(m, kernel, mode='same')
# dilate and restore the other numbers
out = np.where(m, binary_dilation(conv == 9, kernel).astype(int), data)
print(out)
Alternative, erosion and dilation, similarly to my previous answer:
from scipy.ndimage import binary_dilation, binary_erosion
m = data==1
kernel = np.ones((3, 3))
out = np.where(m, binary_dilation(binary_erosion(m, kernel), kernel), data)
Output:
[[4 4 4 0 1 1 1 1 1 1 0 0 0 0 0]
[3 0 0 0 1 1 1 1 1 1 1 1 1 0 0]
[6 0 0 0 1 1 1 1 1 1 1 1 1 0 0]
[2 0 0 0 1 1 1 0 0 0 1 1 1 0 0]
[2 0 0 0 1 1 1 0 0 0 0 0 0 0 0]]

The below proposed solution uses skimage.util.view_as_windows in order to get all of the possible 3x3 views onto the larger array and then looping using them with help of Python loops to set the found hits back to ones after zeroing all the ones in the first step.
It seems that OpenCV, scimage, numpy don't provide a method able to label areas in which a 3x3 square can move around having all the values beneath set to 1's, but I am not sure here. So if you read this and know much about dilations, convolutions, etc. please leave a note pointing me in the right direction allowing to label the area of image by a methond implemented in C-code for better speed on huge arrays.
# https://stackoverflow.com/questions/73649612/how-to-change-the-particuler-elements-of-an-array
# Example array:
import numpy as np
D_in =np.array([[4,4,4,0,1,1,1,1,1,1,0,0,0,0,1],
[3,0,1,0,1,1,1,1,1,1,1,1,1,1,0],
[6,0,0,0,1,1,1,1,1,1,1,1,1,1,0],
[2,0,0,0,1,1,1,0,1,0,1,1,1,0,0],
[2,0,1,0,1,1,1,0,1,0,1,0,1,0,0]])
# Requirements: If in the D_in array at least one 3x3 square with 1's
# is found, replace in D_in all 1's with 0's except the square size and more than square size no changes.
# Otherwise.
# Expected output :
D_tgt=np.array([[4,4,4,0,1,1,1,1,1,1,0,0,0,0,0],
[3,0,0,0,1,1,1,1,1,1,1,1,1,0,0],
[6,0,0,0,1,1,1,1,1,1,1,1,1,0,0],
[2,0,0,0,1,1,1,0,0,0,1,1,1,0,0],
[2,0,0,0,1,1,1,0,0,0,0,0,0,0,0]])
# ----------------------------------------------------------------------
import numpy as np
D_out = np.copy(D_in) # D_out for restoring hit 3x3 ONEs
D_out[D_out==1] = 0 # set all ONEs to zero
needle = np.ones((3,3)) # not necessary here, except documentation
# Create all possible 3x3 needle views on larger D_in heystack:
from skimage.util import view_as_windows as winview
# vvvvvvvvvvvvvvvvvvvvvvvvvvv
all3x3 = winview(D_in,(3,3)) # the CORE of the algorithm
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^
all3x3_rows, all3x3_cols, needle_rows, needle_cols = all3x3.shape
print(f'{all3x3_rows=}, {all3x3_cols=}, {needle_rows=}, {needle_cols=}')
noOfHits = 0 # used also to decide about the output if not found Hits
for row in range(all3x3_rows):
for col in range(all3x3_cols):
if np.all(all3x3[row,col,:,:]):
noOfHits += 1
# print(f'3x3 Ones at: {row=}, {col=}')
D_out[row:row+3, col:col+3] = 1
print('--------------------------')
print(D_in)
print('--------------------------')
if noOfHits > 0:
print(D_out)
assert D_out.all() == D_tgt.all() # make sure the result is correct
else:
print(D_in)
gives on output:
all3x3_rows=3, all3x3_cols=13, needle_rows=3, needle_cols=3
--------------------------
[[4 4 4 0 1 1 1 1 1 1 0 0 0 0 1]
[3 0 1 0 1 1 1 1 1 1 1 1 1 1 0]
[6 0 0 0 1 1 1 1 1 1 1 1 1 1 0]
[2 0 0 0 1 1 1 0 1 0 1 1 1 0 0]
[2 0 1 0 1 1 1 0 1 0 1 0 1 0 0]]
--------------------------
[[4 4 4 0 1 1 1 1 1 1 0 0 0 0 0]
[3 0 0 0 1 1 1 1 1 1 1 1 1 0 0]
[6 0 0 0 1 1 1 1 1 1 1 1 1 0 0]
[2 0 0 0 1 1 1 0 0 0 1 1 1 0 0]
[2 0 0 0 1 1 1 0 0 0 0 0 0 0 0]]
The same output can be achieved with the one-liner from the answer by mozway. Here some preliminary code:
import numpy as np
data =np.array([[4,4,4,0,1,1,1,1,1,1,0,0,0,0,1],
[3,0,1,0,1,1,1,1,1,1,1,1,1,1,0],
[6,0,0,0,1,1,1,1,1,1,1,1,1,1,0],
[2,0,0,0,1,1,1,0,1,0,1,1,1,0,0],
[2,0,1,0,1,1,1,0,1,0,1,0,1,0,0]])
print(data)
print(" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^")
from scipy.signal import convolve2d
from scipy.ndimage import binary_dilation
b = binary_dilation
c = convolve2d
d = data
k = (3,3)
m = data==1 # ; print( m.astype(int) )
n = np.ones
s = 'same'
w = np.where
than
out=w(m,b(c(d==1,n(k),mode=s)==9,n(k)),d)
and finally
print(out)

Related

Create a matrix that contains 1 if there is a 1 in the bottom-right corner

Given a matrix M n*n (containing only 0 and 1), I want to build the matrix that contains a 1 in position (i, j) if and only if there is at least a 1 in the bottom-right submatrix M[i:n, j:n]
Please note that I know there are optimal algorithm to compute this, but for performance reasons, I'm looking for a solution using numpy (so the algorithm is fully compiled)
Example:
Given this matrix:
0 0 0 0 1
0 0 1 0 0
0 0 0 0 1
1 0 1 0 0
I'm looking for a way to compute this matrix:
0 0 0 0 1
0 0 1 1 1
0 0 1 1 1
1 1 1 1 1
Thanks
Using numpy, you can accumulate the maximum value over each axis:
import numpy as np
M = np.array([[0,0,0,0,1],
[0,0,1,0,0],
[0,0,0,0,1],
[1,0,1,0,0]])
M = np.maximum.accumulate(M)
M = np.maximum.accumulate(M,axis=1)
print(M)
[[0 0 0 0 1]
[0 0 1 1 1]
[0 0 1 1 1]
[1 1 1 1 1]]
Note: This matches your example result (presence of 1 in top-left quadrant). Your explanations of the logic would produce a different result however
If we go with M[i:n,j:n] (bottom-right):
M = np.array([[0,0,0,0,1],
[0,0,1,0,0],
[0,0,0,0,1],
[1,0,1,0,0]])
M = np.maximum.accumulate(M[::-1,:])[::-1,:]
M = np.maximum.accumulate(M[:,::-1],axis=1)[:,::-1]
print(M)
[[1 1 1 1 1]
[1 1 1 1 1]
[1 1 1 1 1]
[1 1 1 0 0]]
It is essentially the same approach except with reversed accumulation on the axes

Counting repeated sequences in transition table

I'm using the following function to generate a transition table:
import numpy as np
import pandas as pd
def make_table(allSeq):
n = max([ max(s) for s in allSeq ]) + 1
arr = np.zeros((n,n), dtype=int)
for seq in allSeq:
ind = (seq[1:], seq[:-1])
arr[ind] += 1
return pd.DataFrame(arr).rename_axis(index='Next', columns='Current')
However, my result is incorrect:
list1 = [1,2,3,4,5,4,5,4,5]
list2 = [4,5,4,5]
make_table([list1, list2])
Current 0 1 2 3 4 5
Next
0 0 0 0 0 0 0
1 0 0 0 0 0 0
2 0 1 0 0 0 0
3 0 0 1 0 0 0
4 0 0 0 1 0 2
5 0 0 0 0 2 0
For example, the transition 4->5 should be counted 5 times, but it's only counted once per sequence (2). I know the issue is the arr[ind] += 1 line, but I just can't figure it out! Do I nest another loop, or is there a slick way to add the total number of instances at once? Thanks!
Figured it out! Switched to the following:
def make_table(allSeq):
n = max([ max(s) for s in allSeq ]) + 1
arr = np.zeros((n,n), dtype=int)
for seq in allSeq:
for i,j in zip(seq[1:],seq[:-1]):
ind = (i,j)
arr[ind] += 1
return pd.DataFrame(arr).rename_axis(index='Next', columns='Current')
Another loop seems like the easiest solution, with a bit of a twist of using zip:
import numpy as np
import pandas as pd
def make_table(allSeq):
n = max([ max(s) for s in allSeq ]) + 1
arr = np.zeros((n,n), dtype=int)
for seq in allSeq:
ind = zip(seq[1:], seq[:-1])
for i in ind:
arr[i] += 1
return pd.DataFrame(arr).rename_axis(index='Next', columns='Current')
list1 = [1,2,3,4,5,4,5,4,5]
list2 = [4,5,4,5]
make_table([list1, list2])
returns
Next 0 1 2 3 4 5
------ --- --- --- --- --- ---
0 0 0 0 0 0 0
1 0 0 0 0 0 0
2 0 1 0 0 0 0
3 0 0 1 0 0 0
4 0 0 0 1 0 3
5 0 0 0 0 5 0

Finding the nearest element in a 2D Numpy array

I have a two-dimensional numpy array like:
[[0 0 0 0 0 0 0 0 1 1]
[0 0 0 1 0 1 0 0 0 1]
[1 0 1 0 0 0 1 0 0 1]
[1 0 0 0 0 0 0 0 1 0]
[0 1 0 0 0 1 0 1 1 0]
[0 0 0 1 1 0 0 0 0 0]
[0 1 1 1 1 1 0 0 0 0]
[1 0 0 0 1 0 1 0 0 0]
[0 0 0 0 0 0 0 1 0 0]
[0 1 0 0 0 0 0 0 0 0]]
We can think of it as a map that is viewed from above.
I'll pick a random cell, let's say line 3 column 4 (start counting at 0). If the cell contains a 1, there is no problem. If the cell is a 0, I need to find the index of the nearest 1.
Here, line 3 column 4 is a 0, I want a way to find the nearest 1 which is line 4 column 5.
If two cells containing 1 are at the same distance, I don't care which one I get.
Borders are not inter-connected, i.e. the nearest 1 for the cell line 7 column 9 is not the 1 line 7 column 0
Of course it is a simplified example of my problem, my actual np arrays do not contain zeros and ones but rather Nones and floats
This is a simple "path-finding" problem. Prepare an empty queue of coordinates and push a starting position to the queue. Then, pop the first element from the queue and check location and if it's 1 return the coordinates, otherwise push all neighbours to the queue and repeat.
ADJACENT = [(0, 1), (1, 0), (0, -1), (-1, 0)]
def find(data: np.array, start: tuple):
queue = deque()
deque.append(start)
while queue:
pos = queue.popleft()
if data[pos[0], pos[1]]:
return position
else:
for dxy in ADJACENT:
(x, y) = (pos[0] + dxy[0], pos[1], dxy[1])
if x >= 0 and x < data.size[0] and y >= and y < data.size[1]:
queue.append((x,y))
return None

Plotting multidimensional binary data as horizontal bars

I'm trying to nicely plot a multidimensional array (see below) as parallel horizontal bars, in a way that it's filled when True and white when False.
Here's my data:
bar_1 bar_2 bar_3
0 True False False
1 True False False
2 True False True
3 False True False
4 False True False
5 False True False
6 False False False
7 False False False
8 False False False
9 False True False
10 False True False
11 False True False
12 False True False
13 False True False
14 False True False
15 False True False
16 True False False
17 True False False
18 True False True
19 False True False
20 False True False
21 False True False
22 True False True
23 False True False
24 False True False
25 False True False
Here's how I'd like to display it:
I was looking through the matplotlib docs for something similar, but no luck. Perhaps I'm missing the keywords for this type of plotting. What'd be the name of this type of plot? Is it possible to generate this with matplotlib?
Thanks to ImportanceOfBeingErnest I came up with a solution. I am going to use broken_barh which was not exactly designed for this purpose, but with a little bit of tweaking, it can be used for this.
For the sake of simplicity, I'll only display a function, and mark the places red where it's above 0.5.
index = np.linspace(start=-1, stop=5, num=100)
sin = np.sin(index**2)
df = pd.DataFrame({'sin': sin, 'filtered': sin > .5}, index=index)
Produces
Note I'm not going to use the same data as on the plots because that'd take up too much space
In the next step, I compute the flag points where my function crosses the threshold. Just to visualize it:
0 0 0 1 1 1 0 0 1 1 0 0 1 0 0
I compute the shifted values:
0 0 1 1 1 0 0 1 1 0 0 1 0 0 0
0 0 0 1 1 1 0 0 1 1 0 0 1 0 0 // The original values
0 0 0 0 1 1 1 0 0 1 1 0 0 1 0
And I keep only the values where the original array is True and the other two arrays xor (either one is True or the other but not both):
0 0 0 1 0 1 0 0 1 1 0 0 0 0 0
Note that a single spike that doesn't cross just touches the threshold will be 0.
This can be easily achieved with
flags = df.filtered & (df.filtered.shift().fillna(False) ^ df.filtered.shift(-1).fillna(False))
Now I multiply the flag points with the index (not necessarily an integer index).
flag_values = flags * flags.index
0 0 0 3 0 5 0 0 8 9 0 0 0 0 0
And drop the 0 values:
flag_values = flag_values[flag_values != 0]
[3, 5, 8, 9]
I still need to reshape it:
value_pairs = flag_values.values.reshape((-1, 2))
[[3, 5],
[8, 9]]
And now I need to subtract the first column from the second one:
value_pairs[:, 1] = value_pairs[:, 1] - value_pairs[:, 0]
And I can plot it as follows:
ax = df.sin.plot()
// The second parameter is the height of the bar, and its thickness.
ax.broken_barh(value_pairs, (0.49, 0.02,), facecolors='red')
Here's the result

rotate an nxnxn matrix in python

I have a binary array of size 64x64x64, where a volume of 40x40x40 is set to "1" and rest is "0". I have been trying to rotate this cube about its center around z-axis using skimage.transform.rotate and also Opencv as:
def rotateImage(image, angle):
row, col = image.shape
center = tuple(np.array([row, col]) / 2)
rot_mat = cv2.getRotationMatrix2D(center, angle, 1.0)
new_image = cv2.warpAffine(image, rot_mat, (col, row))
return new_image
In the case of openCV, I tried, 2D rotation of each idividual slices in a cube (Cube[:,:,n=1,2,3...p]).
After rotating, total sum of the values in the array changes. This may be caused by interpolation during rotation. How can I rotate 3D array of this kind without adding anything to the array?
Ok so I understand now what you are asking. The closest I can come up with is scipy.ndimage. But there is a way interface with imagej from python if which might be easier. But here is what I did with scipy.ndimage:
from scipy.ndimage import interpolation
angle = 25 #angle should be in degrees
Rotatedim = interpolation.rotate(yourimage, angle, reshape = False,output = np.int32, order = 5,prefilter = False)
This worked for some angles to preserve the some and not others, perhaps by playing around more with the parameters you might be able to get your desired outcome.
One option is to convert into sparse, and transform the coordinates using a matrix rotation. Then transform back into dense. In 2 dimensions, this looks like:
import numpy as np
import scipy.sparse
import math
N = 10
space = np.zeros((N, N), dtype=np.int8)
space[3:7, 3:7].fill(1)
print(space)
print(np.sum(space))
space_coo = scipy.sparse.coo_matrix(space)
Coords = np.array(space_coo.nonzero()) - 3
theta = 30 * 3.1416 / 180
R = np.array([[math.cos(theta), math.sin(theta)], [-math.sin(theta), math.cos(theta)]])
space2_coords = R.dot(Coords)
space2_coords = np.round(space2_coords)
space2_coords += 3
space2_sparse = scipy.sparse.coo_matrix(([1] * space2_coords.shape[1], (space2_coords[0], space2_coords[1])), shape=(N, N))
space2 = space2_sparse.todense()
print(space2)
print(np.sum(space2))
Output:
[[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 0 0 0]
[0 0 0 1 1 1 1 0 0 0]
[0 0 0 1 1 1 1 0 0 0]
[0 0 0 1 1 1 1 0 0 0]
[0 0 0 1 1 1 1 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 0 0 0 0 0 0]]
16
[[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 0 0 0]
[0 0 0 1 0 0 0 0 0 0]
[0 0 1 1 1 1 0 0 0 0]
[0 0 1 1 1 1 1 0 0 0]
[0 1 1 0 1 1 0 0 0 0]
[0 0 0 1 1 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]]
16
The advantage is that you'll get exactly as many 1 values before and after the transform. The downsides is that you might get 'holes', as above, and/or duplicate coordinates, giving values of '2' in the final dense matrix.

Categories

Resources