Getting each column in a 3d numpy array - python

I converted an image from RBG to CieLab, now I need to use the value of the cielab to calculate some equations.
I have been trying to get the value of each column in the array. For example if I have:
List =
[[[ 65 234 169]
[203 191 245]
[ 36 58 196]
[207 208 143]
[251 208 187]]
[[ 79 69 237]
[ 13 124 42]
[104 165 82]
[170 178 178]
[ 66 42 210]]
[[ 40 163 219]
[142 37 140]
[ 75 205 143]
[246 30 221]
[ 16 98 102]]]
How can I get it to give me the values of each columns like:
1st_column =
65
203
36
207
251
79
13
104
170
66
40
142
75
246
16
Thank you.

Try:
>>> m[:, :, 0]
array([[ 65, 203, 36, 207, 251],
[ 79, 13, 104, 170, 66],
[ 40, 142, 75, 246, 16]])
As suggested by #mozway, you can use the ellipsis syntax: m[..., 0].
To know more, read How do you use the ellipsis slicing syntax in Python?
You can also flatten your array:
>>> m[:, :, 0].flatten()
array([ 65, 203, 36, 207, 251, 79, 13, 104, 170, 66, 40, 142, 75, 246, 16])

Related

How to replace the list in numpy list?

I'm currently working on one project where I need to quantize the image. First, I'm reading the image using skimage, and the shape of it is (825, 1100, 3). Image array looks like this:
[[[ 43 78 48]
[ 43 78 48]
[ 43 78 48]
...
[ 5 24 18]
[ 5 24 18]
[ 4 23 17]]
[[ 43 78 48]
[ 43 78 48]
[ 43 78 48]
...
[ 5 24 18]
[ 5 24 18]
[ 4 23 17]]
[[ 43 78 48]
[ 43 78 48]
[ 43 78 48]
...
[ 5 24 18]
[ 4 23 17]
[ 4 23 17]]
...
[[ 99 143 45]
[ 99 143 45]
[ 98 142 44]
...
[102 145 38]
[100 146 38]
[100 146 38]]
[[ 99 143 45]
[ 99 143 45]
[ 99 143 45]
...
[103 146 39]
[100 146 38]
[ 99 145 37]]
[[ 97 142 41]
[ 98 143 42]
[ 99 144 43]
...
[100 146 38]
[ 99 145 37]
[ 99 145 37]]]
Then I apply K-means to quantize the image and decrease the colors in it, and I call that arrary less_colors which also has the same shape of (825, 1100, 3). The output is:
[[[ 29 48 30]
[ 29 48 30]
[ 29 48 30]
...
[ 29 48 30]
[ 29 48 30]
[ 29 48 30]]
[[ 29 48 30]
[ 29 48 30]
[ 29 48 30]
...
[ 29 48 30]
[ 29 48 30]
[ 29 48 30]]
[[ 29 48 30]
[ 29 48 30]
[ 29 48 30]
...
[ 29 48 30]
[ 29 48 30]
[ 29 48 30]]
...
[[111 137 58]
[111 137 58]
[111 137 58]
...
[111 137 58]
[111 137 58]
[111 137 58]]
[[111 137 58]
[111 137 58]
[111 137 58]
...
[111 137 58]
[111 137 58]
[111 137 58]]
[[111 137 58]
[111 137 58]
[111 137 58]
...
[111 137 58]
[111 137 58]
[111 137 58]]]
I have another variable called first which is a list that is [30, 48, 29].
I would like to change the row of less_colors array into a different array (let's say [0, 0, 0]) if it contains the array called first.
I have tried NumPy, but my code does not work.
less_colors[np.where((less_colors == first).all(axis=2))] = [0,0,0]
The complete code:
import cv2
img = io.imread('dog.jpg')
less_colors[(less_colors[:, :] == first).all(axis=2)] = [0, 0, 0]
io.imshow(less_colors)
plt.show()
Short answer:
This was already answered in comments, however, here goes the complete answer:
less_color[(less_color==first).all(axis=2)] = 0
What's goning on?
less_color==first returns a boolean mask which is True only for the indexes where the condition is met. This is a matrix with the same shape as the image.
Next, the .all(axis=2) operation make sure that the condition is met for all the channels (the second axis): you want to overwrite iff three channels contain same value. This also returns a boolean mask, but now with only two dimensions, telling if each coordinate [i,j] accomplish the criteria at the three channels.
Then, we are using this mask to select only those pixels in the less_colors array: less_color[(less_color==first).all(axis=2)]
Finally, we assign those pixels with the desired value, overriding them with 0; note that this is equivalent to [0, 0, 0] due to numpy's broadcasting mechanism.
Small working example
import numpy as np
# create a small image with ones
less_color = np.ones((5,5,3))
# change one pixel with a different value
less_color[1,1] = 30, 40, 29
# This other should kep as is, since only 2 out of three match the required value
less_color[2,2] = 30, 40, 290
print(less_color)
print('='*10)
# the following line actually solves the question
less_color[(less_color==[30, 40, 29]).all(axis=2)] = 0
# check it out:
print(less_color)
Common error:
less_color[less_color==first] = 0 is not enough since it will also replace pixels with partial-matching, for instance, pixels with values like [10, 10, 29] will end up as [10, 10, 0] while they must not be changed.
Thanks #Aaron for your original and quickly answer.
So you want to map a new value to an old value. For your very case it is:
arr[np.all(arr == old_value, axis=-1)] = new_value
But you can create a general function to apply any mapping to any ndarray as follows:
def ndarray_apply_mapping(
arr, mapping, mask_function=lambda arr, target: arr == target
):
res = arr.copy()
for old_value, new_value in mapping.items():
mask = mask_function(arr, old_value)
res[mask] = new_value
return res
It will work on simpler cases:
import numpy as np
arr = np.array([0, 1, 2, 3, 4, 5])
mapping = {1: 10, 3: 30, 5: 50}
res = ndarray_apply_mapping(arr, mapping)
assert np.all(res == [0, 10, 2, 30, 4, 50])
But also on more complicated cases as yours.
Let's say you have an array with a limited set of RGB values (or cluster labels resulting from k-means, or whatever):
import numpy as np
H, W, C = 8, 16, 3
vmin, vmax = 0, 255
num_values = 10
values = np.random.randint(vmin, vmax, size=(num_values, C))
values_rnd_idxs = np.random.randint(0, num_values, size=(H, W))
arr = values[values_rnd_idxs]
assert arr.shape == (H, W, C)
And you have a mapping from some of those values to new values:
new_values = np.random.randint(vmin, vmax, size=(num_values // 3, C))
mapping = {tuple(old): tuple(new) for old, new in zip(values, new_values)}
You can use this mapping as follows:
res = ndarray_apply_mapping(
arr,
mapping,
mask_function=lambda arr, target: np.all(arr == target, axis=-1),
)
Plotting to see the result:
import matplotlib.pyplot as plt
fig, (ax_old, ax_new, ax_same) = plt.subplots(ncols=3)
ax_old.imshow(arr)
ax_new.imshow(res)
ax_same.imshow((res == arr).all(axis=-1), vmin=0, vmax=1, cmap="gray")
ax_old.set_title("Old")
ax_new.set_title("New")
ax_same.set_title("Matches")
plt.show()
I should have caught it earlier just from your example data, but [30, 48, 29] does not exist in your example data:
[[ 29 48 30]
[ 29 48 30]
[ 29 48 30]
...
[ 29 48 30]
[ 29 48 30]
[ 29 48 30]]
...
[[111 137 58]
[111 137 58]
[111 137 58]
Somewhere along the line you inverted the color channels (RGB to BGR), and tried to compare a BGR color against RGB data. The match and replace line I suggested in the comments only needs a small modification if you want to keep the first variable in reverse order:
less_colors[(less_colors[:,:] == first[::-1]).all(axis=2)] = [0,0,0]

Sum of positive arrays yields negative results

I try to sum together three positive arrays, however, the result yields an array that has negative values. How is this possible?
#Example of an image
img=np.array(([[[246, 240, 243],[240, 239, 239],
[243, 242, 244]],[[ 241, 240, 240],
[241, 243, 246],[ 239, 239, 239]],
[[249, 249, 250],[ 33, 33, 34],
[249, 249, 249]],[[ 33, 33, 33],
[250, 250, 249],[ 34, 34, 34]]]), dtype=np.uint8)
#Creating three positive arrays from image
#Image type converted to np.int16 as otherwise values remain between 0-255
R=abs((img[:,:,0].astype(np.int16)-255)**2)
G=abs((img[:,:,1].astype(np.int16)-255)**2)
B=abs((img[:,:,2].astype(np.int16)-255)**2)
print(R, G, B)
[[ 81 225 144]
[ 196 196 256]
[ 36 16252 36]
[16252 25 16695]] [[ 225 256 169]
[ 225 144 256]
[ 36 16252 36]
[16252 25 16695]] [[ 144 256 121]
[ 225 81 256]
[ 25 16695 36]
[16252 36 16695]]
#Adding three positive arrays together
R+G+B
array([[ 450, 737, 434],
[ 646, 421, 768],
[ 97, -16337, 108],
[-16780, 86, -15451]], dtype=int16)
I thought it had something to do with the abs() function I am applying, however, the results separately clearly show they are referenced correctly and positive?

get coordinate of each pixel in an image

i have an image read as a numpy array A shape(n,m,3)
A =
array([[[ 21, 38, 32],
[ 29, 46, 38],
[ 35, 52, 42],
...,
and i would to transform it in order to get the index/coordinate of each element in a new axis
B =
array([[[ 21, 38, 32, 0, 0],
[ 29, 46, 38, 0, 1],
[ 35, 52, 42, 0, 2],
...,
# in the form
B =
array([[[ R, G, B, px, py],
where
px= row index of the pixel
py= column index of the pixel
I coded this
B=np.zeros((n,m,5))
for x in range(n):
for y in range(m):
row=list(A[x,y,:])+[x,y]
B[x,y]=row
but it's taking to much time to iterate
have you a better way?
best regards
If you want an answer without imports:
array = np.array(img)
print(array.shape)
# (1080, 1920, 3)
zeros = np.zeros(array.shape[:2])
x_and_y = (np.dstack([(zeros.T + np.arange(0, array.shape[0])).T,
zeros + np.arange(0, array.shape[1])])
.astype('uint32'))
print(np.dstack([array, x_and_y]))
Outputting:
[[[39 86 101 0 0]
[39 86 101 0 1]
[39 86 101 0 2]
...
[11 114 123 0 1917]
[13 121 128 0 1918]
[13 121 128 0 1919]]
[[39 86 101 1 0]
[39 86 101 1 1]
[39 86 101 1 2]
...
[7 110 119 1 1917]
[19 127 134 1 1918]
[17 125 132 1 1919]]
...
[[46 136 154 1078 0]
[49 139 157 1078 1]
[46 143 159 1078 2]
...
[30 105 119 1078 1917]
[30 105 119 1078 1918]
[30 105 119 1078 1919]]
[[46 136 154 1079 0]
[49 139 157 1079 1]
[46 143 159 1079 2]
...
[30 105 119 1079 1917]
[30 105 119 1079 1918]
[30 105 119 1079 1919]]]
What I would do is to create the coordinate arrays and concatenate:
# random A
np.random.seed(1)
A = np.random.randint(0,256, (3,2,3))
from itertools import product
coords = np.array(list(product(np.arange(A.shape[0]),
np.arange(A.shape[1])))
).reshape(A.shape[:2]+(-1,))
B = np.concatenate((A,coords), axis=-1)
Output:
array([[[ 37, 235, 140, 0, 0]],
[[ 72, 255, 137, 1, 0]],
[[203, 133, 79, 2, 0]]])

Python: Randomly insert multiple rows into 3D numpy array

I have a 3D array arr of size (2, 5, 5). I also have another array rows_to_ins of size (3, 5).
I would like to randomly insert rows_to_insert into each page of arr. However, rows_to_insert must not be inserted as a block. In addition, the position to insert should be random for ever page of arr.
However, I am struggling with efficiently inserting rows_to_ins. My current solution incorporates a for-loop.
import numpy as np
arr = np.arange(100, 125).reshape(5, 5)
arr = np.repeat(arr[None, :, :], 2, axis=0)
rows_to_ins = np.random.randint(0, 99, (3,5))
row_nums_3D = np.random.randint(0, arr.shape[1], (2, 1, 3))
arr_ins = list()
for i in range(row_nums_3D.shape[0]):
arr_ins.append(np.insert(arr[i, :, :], np.squeeze(row_nums_3D[i, :, :]), rows_to_ins, axis=0))
arr_ins = np.asarray(arr_ins)
I am wondering, if I can avoid the for-loop. What would a vectorize solution look like?
Maybe a more concrete example will help to understand my problem.
# arr - shape (2, 5, 5)
[[[100 101 102 103 104]
[105 106 107 108 109]
[110 111 112 113 114]
[115 116 117 118 119]
[120 121 122 123 124]]
[[100 101 102 103 104]
[105 106 107 108 109]
[110 111 112 113 114]
[115 116 117 118 119]
[120 121 122 123 124]]]
# rows_to_insert - shape(3, 5)
[[37 31 28 34 10]
[ 2 97 89 36 11]
[66 14 70 37 45]]
I am looking for a potential result such like this:
# 3D array with insertet rows - shape (2, 8, 5)
[[[100 101 102 103 104]
[ 2 97 89 36 11]
[66 14 70 37 45]
[105 106 107 108 109]
[110 111 112 113 114]
[115 116 117 118 119]
[120 121 122 123 124]
[37 31 28 34 10]]
[[66 14 70 37 45]
[100 101 102 103 104]
[105 106 107 108 109]
[ 2 97 89 36 11]
[110 111 112 113 114]
[37 31 28 34 10]
[115 116 117 118 119]
[120 121 122 123 124]]]
Here's a vectorized way -
def insert_random_places(arr, rows_to_ins):
m,n,r = arr.shape
N = len(rows_to_ins) + n
idx = np.random.rand(m,N).argsort(1)
out = np.zeros((m,N,r),dtype=np.result_type(arr, rows_to_ins))
np.put_along_axis(out,np.sort(idx[:,:n,None],axis=1),arr,axis=1)
np.put_along_axis(out,idx[:,n:,None],rows_to_ins,axis=1)
return out
Sample run -
In [58]: arr
Out[58]:
array([[[100, 101, 102, 103, 104],
[105, 106, 107, 108, 109],
[110, 111, 112, 113, 114],
[115, 116, 117, 118, 119]],
[[100, 101, 102, 103, 104],
[105, 106, 107, 108, 109],
[110, 111, 112, 113, 114],
[115, 116, 117, 118, 119]]])
In [59]: rows_to_ins
Out[59]:
array([[77, 72, 9, 20, 80],
[69, 79, 47, 64, 82]])
In [60]: np.random.seed(0)
In [61]: insert_random_places(arr, rows_to_ins)
Out[61]:
array([[[100, 101, 102, 103, 104],
[ 69, 79, 47, 64, 82],
[105, 106, 107, 108, 109],
[110, 111, 112, 113, 114],
[115, 116, 117, 118, 119],
[ 77, 72, 9, 20, 80]],
[[100, 101, 102, 103, 104],
[ 77, 72, 9, 20, 80],
[ 69, 79, 47, 64, 82],
[105, 106, 107, 108, 109],
[110, 111, 112, 113, 114],
[115, 116, 117, 118, 119]]])
Another one based on masking -
def insert_random_places_v2(arr, rows_to_ins):
m,n,r = arr.shape
L = len(rows_to_ins)
N = L + n
insert_idx = np.random.rand(m,N).argpartition(kth=-L,axis=1)[:,-L:]
mask = np.zeros((m,N),dtype=bool)
np.put_along_axis(mask,insert_idx,1,axis=1)
out = np.zeros((m,N,r),dtype=np.result_type(arr, rows_to_ins))
rows_to_ins_3D = rows_to_ins[np.random.rand(m,L).argsort(1)]
out[mask] = rows_to_ins_3D.reshape(-1,r)
out[~mask] = arr.reshape(-1,r)
return out

Read contents of a text file into a dictionary

The contents of my text file are as follows:
a 0 45 124 234 53 12 34
a 90 294 32 545 190 87
a 180 89 63 84 73 63 83
How can I read the contents into a dictionary such that a0 becomes the key and the rest of them as values. I would want my dictionary to look like this:
{a0: [45, 124, 234, 53, 12, 34], a90: [294, 32, 545, 190, 87], a180: [89, 63, 84, 73, 63, 83]}
I have tried the conventional approach where I remove the delimiter and then
store it in the dictionary as shown below
newt = {}
newt = {t[0]: t[1:] for t in data}
But here I get only a as the key
This may help you out (it's about Christmas time after all)
d = {}
with open("dd.txt") as f:
for line in f:
els = line.split()
k = ''.join(els[:2])
d[k] = list(map(int,els[2:]))
print(d)
with an input file of
a 0 45 124 234 53 12 34
a 90 294 32 545 190 87
a 180 89 63 84 73 63 83
it produces
{'a90': [294, 32, 545, 190, 87],
'a180': [89, 63, 84, 73, 63, 83],
'a0': [45, 124, 234, 53, 12, 34]}
It essentially reads each line from the file, it then splits it into chunks ignoring whitespace.
It then uses the first two elements to compose the key and the rest to compose a list, converting each element into an integer.
I have assumed you want the numbers as integers. If you want them as strings you can ignore the conversion to int
d[k] = els[2:]
If you like one-liners (kind-of):
with open('my_file.txt') as f:
res = {''.join(r.split(None, 2)[:2]): [int(x) for x in r.split()[2:]] for r in f}
>>> res
{'a0': [45, 124, 234, 53, 12, 34],
'a180': [89, 63, 84, 73, 63, 83],
'a90': [294, 32, 545, 190, 87]}

Categories