Related
How can i set a list of pixels efficiently using python.
For example if i have:
import numpy as np
img = np.zeros((700, 200, 3), dtype=np.uint8)
l = [[300, 101], [200, 102], [150, 103], [300, 104], [88, 105], [66, 106], [666, 107]]
i just want to set pixels with l coordinates to black color.
I know i can do something like this:
for p in l:
img[p[0], p[1]] = (0, 0, 0)
I wonder if there is something more efficient.
Your example is confusing (at least for me :D ) but I think this might be more "efficient"
import cv2
import numpy as np
#an array:
img=255*np.ones((700,700,3),dtype=np.uint8)
list = [[444, 101], [200, 102], [150, 103], [300, 104], [88, 105], [66, 106], [666, 107]]
#with for loop
def set_pixels(img, list):
for i in range(len(list)):
x = list[i][0]
y = list[i][1]
img[x, y] = [0, 0, 0]
without for loop (Alex Alex mentioned)
def set_pixels_2(img, list):
list = np.array(list)
img[list[:,0], list[:,1]] = [0, 0, 0]
set_pixels(img, list)
set_pixels2(img, list)
cv2.imshow('image', img)
cv2.waitKey(0)
I have an image which is read as a uint8 array with the shape (512,512,3).
Now I would like to convert this array to a uint8 array of shape (512,512,1), where each pixel value in the third axis are converted from a color value [255,0,0] to a single class label value [3], based on the following color/class encoding:
1 : [0, 0, 0],
2 : [0, 0, 255],
3 : [255, 0, 0],
4 : [150, 30, 150],
5 : [255, 65, 255],
6 : [150, 80, 0],
7 : [170, 120, 65],
8 : [125, 125, 125],
9 : [255, 255, 0],
10 : [0, 255, 255],
11 : [255, 150, 0],
12 : [255, 225, 120],
13 : [255, 125, 125],
14 : [200, 100, 100],
15 : [0, 255, 0],
16 : [0, 150, 80],
17 : [215, 175, 125],
18 : [220, 180, 210],
19 : [125, 125, 255]
What is the most efficient way to do this? I thought of looping through all classes and using numpy.where, but this is obviously time-consuming.
You could use giant lookup table. Let cls be [[0,0,0], [0,0,255], ...] of dtype=np.uint8.
LUT = np.zeros(size=(256,256,256), dtype='u1')
LUT[cls[:,0],cls[:,1],cls[:,2]] = np.arange(cls.shape[1])+1
img_as_cls = LUT[img[...,0],img[...,1], img[...,2]]
This solution is O(1) per pixel. It is also quite cache efficient because a small part of entries in LUT are actually used. It takes circa 10ms to process 1000x1000 image on my machine.
The solution can be slightly improved by converting 3-color channels to 24-bit integers.
Here is the code
def scalarize(x):
# compute x[...,2]*65536+x[...,1]*256+x[...,0] in efficient way
y = x[...,2].astype('u4')
y <<= 8
y +=x[...,1]
y <<= 8
y += x[...,0]
return y
LUT = np.zeros(2**24, dtype='u1')
LUT[scalarize(cls)] = 1 + np.arange(cls.shape[0])
simg = scalarize(img)
img_to_cls = LUT[simg]
After optimization it takes about 5ms to process 1000x1000 image.
One way: separately create the boolean arrays with True values where the input's pixel value matches one of the palette values, and then use arithmetic to combine them. Thus:
palette = [
[0, 0, 0],
[0, 0, 255],
[255, 0, 0],
# etc.
]
def palettized(data, palette):
# Initialize result array
shape = list(data.shape)
shape[-1] = 1
result = np.zeros(shape)
# Loop and add each palette index component.
for value, colour in enumerate(palette, 1):
result += (data == colour).all(axis=2) * value
return result
Here's one based on views -
# https://stackoverflow.com/a/45313353/ #Divakar
def view1D(a, b): # a, b are arrays
# This function gets 1D view into 2D input arrays
a = np.ascontiguousarray(a)
b = np.ascontiguousarray(b)
void_dt = np.dtype((np.void, a.dtype.itemsize * a.shape[-1]))
return a.view(void_dt).ravel(), b.view(void_dt).ravel()
def img2label(a, maps):
# Get one-dimension reduced view into input image and map arrays.
# We need to reshape image to 2D, then feed it to view1D to get 1D
# outputs and then reshape 1D image to 2D
A,B = view1D(a.reshape(-1,a.shape[-1]),maps)
A = A.reshape(a.shape[:2])
# Trace back positions of A in B using searchsorted. This gives us
# original order, which is the final output.
sidx = B.argsort()
return sidx[np.searchsorted(B,A,sorter=sidx)]
Given that your labels start from 1, you might want to add 1 to the output.
Sample run -
In [100]: # Mapping array
...: maps = np.array([[0, 0, 0],[0, 0, 255],\
...: [255, 0, 0],[150, 30, 150]],dtype=np.uint8)
...:
...: # Setup random image array
...: idx = np.array([[0,2,1,3],[1,3,2,0]])
...: img = maps[idx]
In [101]: img2label(img, maps) # should retrieve back idx
Out[101]:
array([[0, 2, 1, 3],
[1, 3, 2, 0]])
I have an image of shape (300,300,3) consisting of these pixels [255, 194, 7],[224, 255, 8],[230, 230, 230],[11, 102, 255]. I want to change this pixel [230, 230, 230] to [255,255,255]. And rest other pixels to [0,0,0]. So I'm applying numpy where function to switch the pixels. Below is the code:
import numpy
im = numpy.array([[[255, 194, 7],[224, 255, 8],[230, 230, 230],[11, 102, 255]]])
im[np.where((im == [230, 230, 230]).all(axis = 2))] = [255,255,255]
im[np.where((im != [255,255,255]).all(axis = 2))] = [0,0,0]
The first code is working fine, but all the pixels that have 255 in it like [11, 102, 255] doesnot get flipped at all in the second line. and the image remains same. Can anyone tell me what I'm doing wrong ?
import numpy as np
im = np.array([[[255, 194, 7],[224, 255, 8],[230, 230, 230],[11, 102, 255]]])
Like this?
Make a mask and use it to change the values.
>>> mask = im == 230
>>> im[mask] = 255
>>> im[np.logical_not(mask)] = 0
>>> im
=> array([[[ 0, 0, 0],
[ 0, 0, 0],
[255, 255, 255],
[ 0, 0, 0]]])
Or using numpy.where
>>> np.where(im==230, 255, 0)
=> array([[[ 0, 0, 0],
[ 0, 0, 0],
[255, 255, 255],
[ 0, 0, 0]]])
try
np.array_equal(arr1, arr2)
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I want to print a color on the screen using RGB values and the output should be just a single color. For example if I give RGB values of red, I want the output to show me a red color. But when I try this code, it isn't working. What am I missing?
import matplotlib.pyplot as plt
plt.imshow([(255, 0, 0)])
plt.show()
The output is:
The issue is that you are trying to display a 2D color array with 1 row and 3 columns. The pixel values from left to right are 255, 0and 0. As #Ben K. correctly pointed out in the comments, by doing so the intensity values are scaled to the range 0..1 and displayed using the current colormap. That's why your code displays one yellow pixel and two violet pixels.
If you wish to specify the RGB values you should create a 3D array of m rows, n columns and 3 color channels (one chromatic channel for each RGB component).
Demo
The snippet below generates a random array of indices of a color palette and displays the result:
In [14]: import numpy as np
In [15]: import matplotlib.pyplot as plt
In [16]: from skimage import io
In [17]: palette = np.array([[255, 0, 0], # index 0: red
...: [ 0, 255, 0], # index 1: green
...: [ 0, 0, 255], # index 2: blue
...: [255, 255, 255], # index 3: white
...: [ 0, 0, 0], # index 4: black
...: [255, 255, 0], # index 5: yellow
...: ], dtype=np.uint8)
...:
In [18]: m, n = 4, 6
In [19]: indices = np.random.randint(0, len(palette), size=(4, 6))
In [20]: indices
Out[20]:
array([[2, 4, 0, 1, 4, 2],
[1, 1, 5, 5, 2, 0],
[4, 4, 3, 3, 0, 4],
[2, 5, 0, 5, 2, 3]])
In [21]: io.imshow(palette[indices])
Out[21]: <matplotlib.image.AxesImage at 0xdbb8ac8>
You could also generate a random color pattern rather than using a color palette:
In [24]: random_colors = np.uint8(np.random.randint(0, 255, size=(m, n, 3)))
In [24]: random_colors
Out[27]:
array([[[137, 40, 84],
[ 42, 142, 25],
[ 48, 240, 90],
[ 22, 27, 205],
[253, 130, 22],
[137, 33, 252]],
[[144, 67, 156],
[155, 208, 130],
[187, 243, 200],
[ 88, 171, 116],
[ 51, 15, 157],
[ 39, 64, 235]],
[[ 76, 56, 135],
[ 20, 38, 46],
[216, 4, 102],
[142, 60, 118],
[ 93, 222, 117],
[ 53, 138, 39]],
[[246, 88, 20],
[219, 114, 172],
[208, 76, 247],
[ 1, 163, 65],
[ 76, 83, 8],
[191, 46, 53]]], dtype=uint8)
In [26]: io.imshow(random_colors)
Out[26]: <matplotlib.image.AxesImage at 0xe6c6a90>
This is the output produced by
import matplotlib.pyplot as plt
plt.imshow([(3,0,0),(0,2,0),(0,0,1)])
plt.colorbar()
plt.show()
You see that the three tuples I provided to imshow are interpreted as rows of a matrix:
3 0 0
0 2 0
0 0 1
The numeric values are mappped to colors for the plot. The colorbar function shows the mapping between colors and numeric values.
To draw rectangles, refer to this SO question, but replace the value of the facecolor parameter with one of the following possibilities:
A color name, as a string.
A Hex color code, given as a string with a leading # sign. For example, facecolor='#FF0000' is bright red.
A triple with three values between 0 and 1, which specify the (Red, Green, Blue) parts of your color. (Not 0 to 255 like you assumed in your question!)
Use the edgecolor parameter in the same manner to determine the color of the rectangle border, or use 'None' to draw no border.
I have a numpy array of size (4, X, Y), where the first dimension stands for an (R,G,B,A) quadruplet.
My aim is to transpose each X*Y RGBA quadruplets to X*Y floating-point values, given a dictionary matching them.
My current code is as follows:
codeTable = {
(255, 255, 255, 127): 5.5,
(128, 128, 128, 255): 6.5,
(0 , 0 , 0 , 0 ): 7.5,
}
for i in range(0, rows):
for j in range(0, cols):
new_data[i,j] = codeTable.get(tuple(data[:,i,j]), -9999)
Where data is a numpy array of size (4, rows, cols), and new_data is of size (rows, cols).
The code is working fine, but takes quite a long time. How should I optimize that piece of code?
Here is a full example:
import numpy
codeTable = {
(253, 254, 255, 127): 5.5,
(128, 129, 130, 255): 6.5,
(0 , 0 , 0 , 0 ): 7.5,
}
# test data
rows = 2
cols = 2
data = numpy.array([
[[253, 0], [128, 0], [128, 0]],
[[254, 0], [129, 144], [129, 0]],
[[255, 0], [130, 243], [130, 5]],
[[127, 0], [255, 120], [255, 5]],
])
new_data = numpy.zeros((rows,cols), numpy.float32)
for i in range(0, rows):
for j in range(0, cols):
new_data[i,j] = codeTable.get(tuple(data[:,i,j]), -9999)
# expected result for `new_data`:
# array([[ 5.50000000e+00, 7.50000000e+00],
# [ 6.50000000e+00, -9.99900000e+03],
# [ 6.50000000e+00, -9.99900000e+03], dtype=float32)
Here's an approach that returns your expected result, but with such a small amount of data it's hard to know if this will be faster for you. Since I've avoided the double for loop, however, I imagine you'll see a pretty decent speedup.
import numpy
import pandas as pd
codeTable = {
(253, 254, 255, 127): 5.5,
(128, 129, 130, 255): 6.5,
(0 , 0 , 0 , 0 ): 7.5,
}
# test data
rows = 3
cols = 2
data = numpy.array([
[[253, 0], [128, 0], [128, 0]],
[[254, 0], [129, 144], [129, 0]],
[[255, 0], [130, 243], [130, 5]],
[[127, 0], [255, 120], [255, 5]],
])
new_data = numpy.zeros((rows,cols), numpy.float32)
for i in range(0, rows):
for j in range(0, cols):
new_data[i,j] = codeTable.get(tuple(data[:,i,j]), -9999)
def create_output(data):
# Reshape your two data sources to be a bit more sane
reshaped_data = data.reshape((4, -1))
df = pd.DataFrame(reshaped_data).T
reshaped_codeTable = []
for key in codeTable.keys():
reshaped = list(key) + [codeTable[key]]
reshaped_codeTable.append(reshaped)
ct = pd.DataFrame(reshaped_codeTable)
# Merge on the data, replace missing merges with -9999
result = df.merge(ct, how='left')
newest_data = result[4].fillna(-9999)
# Reshape
output = newest_data.reshape(rows, cols)
return output
output = create_output(data)
print(output)
# array([[ 5.50000000e+00, 7.50000000e+00],
# [ 6.50000000e+00, -9.99900000e+03],
# [ 6.50000000e+00, -9.99900000e+03])
print(numpy.array_equal(new_data, output))
# True
The numpy_indexed package (disclaimer: I am its author) contains a vectorized nd-array capable variant of list.index, which can be used to solve your problem efficiently and concisely:
import numpy_indexed as npi
map_keys = np.array(list(codeTable.keys()))
map_values = np.array(list(codeTable.values()))
indices = npi.indices(map_keys, data.reshape(4, -1).T, missing='mask')
remapped = np.where(indices.mask, -9999, map_values[indices.data]).reshape(data.shape[1:])