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]]])
Related
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]
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])
I am currently working on a computer vision project with python and openCV.
I have a 2D numpy array like this:
[100 38 18]
[134 332 16]
[136 200 16]
[136 288 15]
[138 160 17]
[138 246 15]
[140 76 12]
[140 116 12]
[142 34 14]
The 2D array is already sorted by the first column. This works fine. Now I need to sort pakets of 3 rows by the second column.
This is the result I need to achieve:
[100 38 18]
[136 200 16]
[134 332 16]
[138 160 17]
[138 246 15]
[136 288 15]
[142 34 14]
[140 76 12]
[140 116 12]
How can I achieve this?
Consider reshaping your data into 3d, then use for loop to sort each array and cast back into an np.array
np.array([sorted(i, key = lambda x: x[1]) for i in ar.reshape(3, -1, ar.shape[1])]).reshape(ar.shape)
array([[100, 38, 18],
[136, 200, 16],
[134, 332, 16],
[138, 160, 17],
[138, 246, 15],
[136, 288, 15],
[142, 34, 14],
[140, 76, 12],
[140, 116, 12]])
Just by NumPy, not looping:
sort_ = np.argsort(np.split(a[:, 1], a.shape[0] // 3))
# [[0 2 1]
# [1 2 0]
# [2 0 1]]
sort_ += np.linspace(0, a.shape[0] - 3, a.shape[0] // 3, dtype=np.int64)[:, None]
# [[0 2 1]
# [4 5 3]
# [8 6 7]]
a = a[sort_.ravel()]
I see no other way than to use a loop
let A be your array
output = np.zeros((0, 3))
for i in range(int(A.shape[0]/3)):
output = np.vstack((output, A[3*i + np.argsort(A[3*i:3*(i+1), 1])]))
Note: I'm assuming that your array has a number of lines which is a multiple of 3
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
We know how to do it when N = 1
import numpy as np
m = np.arange(15).reshape(3, 5)
m[xrange(len(m)), m.argmax(axis=1)] # array([ 4, 9, 14])
What is the best way to get the top N, when N > 1? (say, 5)
Doing a partial sort using np.partition can be much cheaper than a full sort:
gen = np.random.RandomState(0)
x = gen.permutation(100)
# full sort
print(np.sort(x)[-10:])
# [90 91 92 93 94 95 96 97 98 99]
# partial sort such that the largest 10 items are in the last 10 indices
print(np.partition(x, -10)[-10:])
# [90 91 93 92 94 96 98 95 97 99]
If you need the largest N items to be sorted, you can call np.sort on the last N elements in your partially sorted array:
print(np.sort(np.partition(x, -10)[-10:]))
# [90 91 92 93 94 95 96 97 98 99]
This can still be much faster than a full sort on the whole array, provided your array is sufficiently large.
To sort across each row of a two-dimensional array you can use the axis= arguments to np.partition and/or np.sort:
y = np.repeat(np.arange(100)[None, :], 5, 0)
gen.shuffle(y.T)
# partial sort, followed by a full sort of the last 10 elements in each row
print(np.sort(np.partition(y, -10, axis=1)[:, -10:], axis=1))
# [[90 91 92 93 94 95 96 97 98 99]
# [90 91 92 93 94 95 96 97 98 99]
# [90 91 92 93 94 95 96 97 98 99]
# [90 91 92 93 94 95 96 97 98 99]
# [90 91 92 93 94 95 96 97 98 99]]
Benchmarks:
In [1]: %%timeit x = np.random.permutation(10000000)
...: np.sort(x)[-10:]
...:
1 loop, best of 3: 958 ms per loop
In [2]: %%timeit x = np.random.permutation(10000000)
np.partition(x, -10)[-10:]
....:
10 loops, best of 3: 41.3 ms per loop
In [3]: %%timeit x = np.random.permutation(10000000)
np.sort(np.partition(x, -10)[-10:])
....:
10 loops, best of 3: 78.8 ms per loop
Why not do something like:
np.sort(m)[:,-N:]
partition, sort, argsort etc take an axis parameter
Let's shuffle some values
In [161]: A=np.arange(24)
In [162]: np.random.shuffle(A)
In [163]: A=A.reshape(4,6)
In [164]: A
Out[164]:
array([[ 1, 2, 4, 19, 12, 11],
[20, 5, 13, 21, 22, 3],
[10, 6, 16, 18, 17, 8],
[23, 9, 7, 0, 14, 15]])
Partition:
In [165]: A.partition(4,axis=1)
In [166]: A
Out[166]:
array([[ 2, 1, 4, 11, 12, 19],
[ 5, 3, 13, 20, 21, 22],
[ 6, 8, 10, 16, 17, 18],
[14, 7, 9, 0, 15, 23]])
the 4 smallest values of each row are first, the 2 largest last; slice to get an array of the 2 largest:
In [167]: A[:,-2:]
Out[167]:
array([[12, 19],
[21, 22],
[17, 18],
[15, 23]])
Sort is probably slower, but on a small array like this probably doesn't matter much. Plus it lets you pick any N.
In [169]: A.sort(axis=1)
In [170]: A
Out[170]:
array([[ 1, 2, 4, 11, 12, 19],
[ 3, 5, 13, 20, 21, 22],
[ 6, 8, 10, 16, 17, 18],
[ 0, 7, 9, 14, 15, 23]])