How to compare against and modify values of NumPy array - python

I am trying to convert a numpy array to a .vox file. .vox files have a limit where they can only store 255 unique colors. My numpy array is being somewhat randomly generated, so it's length and values are not always the same. However, it's shape is always (N, 3) and the color values are usually similar. For instance, if there is a "red" part of the array, most of the reds are close enough to be visually the same. I've created another numpy array with a set of 19 sample colors equally spaced between 13 points in the RGB color space, which produces a shape of (247, 3).
eg. ([13, 0, 0], [26, 0, 0], [39, 0, 0], [52, 0, 0], [65, 0, 0], [78, 0, 0], [91, 0, 0],
[104, 0, 0], [117, 0, 0], [130, 0, 0], [143, 0, 0], [156, 0, 0], [169, 0, 0], [182, 0, 0],
[195, 0, 0], [208, 0, 0], [221, 0, 0], [234, 0, 0], [247, 0, 0]) x 13 other sets
How can I compare every color in my original numpy array to my array of sample colors and change its value to the closest match? It is ok if the length of the array is greater than 255 so long as there are only 255 or less unique colors.

The most usual way to compare everything to everything (and, generally speaking to do in numpy the equivalent of nested for loops) is to use broadcasting.
Let's consider a smaller example
colorTable = np.array([[0,0,0], [120,0,0], [0,120,0], [0,0,120], [255,255,255]])
randomColors = np.array([[10,10,10], [255,0,0], [140,140,140], [0,0,130], [20,200,80]])
So, the idea is to compare all colors from randomColors to all from colorTable.
Numpy broadcasting consist in assigning one different axis to each dimension you want to iterated in nested implicit for loop.
For example, before applying to our case
a=np.array([1,2,3])
b=np.array([4,5,6,7])
a[:,None]*b[None, :]
# array([[ 4, 5, 6, 7],
# [ 8, 10, 12, 14],
# [12, 15, 18, 21]])
See that we places ourselves in 2D, making a a column of 3 numbers, and b a row of 4 numbers, and letting numpy broadcasting peform the 12 matching multiplications.
So, in our case,
colorTable[:,None,:]-randomColors[None,:,:]
computes the difference between each color (in axis 0) of colorTable, and each color of randomColor (in axis 1). Note that axis 2 are the 3 r,g,b. Since this axis is present in both operands, no broadcasting here.
array([[[ -10, -10, -10],
[-255, 0, 0],
[-140, -140, -140],
[ 0, 0, -130],
[ -20, -200, -80]],
[[ 110, -10, -10],
[-135, 0, 0],
[ -20, -140, -140],
[ 120, 0, -130],
[ 100, -200, -80]],
[[ -10, 110, -10],
[-255, 120, 0],
[-140, -20, -140],
[ 0, 120, -130],
[ -20, -80, -80]],
[[ -10, -10, 110],
[-255, 0, 120],
[-140, -140, -20],
[ 0, 0, -10],
[ -20, -200, 40]],
[[ 245, 245, 245],
[ 0, 255, 255],
[ 115, 115, 115],
[ 255, 255, 125],
[ 235, 55, 175]]])
As you can see, this is a 3D array, that you can see as a 2D array of rgb triplets (1 color of color table in each row, 1 color of randomColors in each column)
((colorTable[:,None,:]-randomColors[None,:,:])**2).sum(axis=2)
sum the square of this difference along axis 2. So what we have here is, for each pair (r,g,b), (r',g',b') of color from both array, is (r-r')²+(g-g')²+(b-b')².
array([[ 300, 65025, 58800, 16900, 46800],
[ 12300, 18225, 39600, 31300, 56400],
[ 12300, 79425, 39600, 31300, 13200],
[ 12300, 79425, 39600, 100, 42000],
[180075, 130050, 39675, 145675, 88875]])
This is a 2D array of square of euclidean distance between each color of colorTable (on each row) and each color of randomColors (on each column).
If we want to find the index in colorTable of the closest color to randomColors[3], all we have to do is to compute argmin of column 3 of this table.
((colorTable[:,None,:]-randomColors[None,:,:])**2).sum(axis=2)[:,3].argmin()
Result is, correctly, 3.
Or, even better, we can do that for all columns, by telling argmin to compute minimum only along axis 0, that is along rows, that is along all color of colorTable
((colorTable[:,None,:]-randomColors[None,:,:])**2).sum(axis=2).argmin(axis=0)
# array([0, 1, 1, 3, 2])
You can see that the result is, correctly, for each column, that is each color of randomColors, the index of the color of colorTable that is closest (for euclidean distance) to id. That is, the index of the smallest number in each column of the previous table
So, all that remains here, is to extract the color of colorTable matching this index
colorTable[((colorTable[:,None,:]-randomColors[None,:,:])**2).sum(axis=2).argmin(axis=0)]
Giving a table of the same shape as randomColors (that is having as many rows as the previous result have indexes), made of colors from colorTable (the one closest to the each rows)
array([[ 0, 0, 0],
[120, 0, 0],
[120, 0, 0],
[ 0, 0, 120],
[ 0, 120, 0]])
Note that the result is not always intuitive. (140,140,140) is closest to (120,0,0) than it is to (255,255,255)
But that is a matter of defining the distance.

Related

Alternatives for numpy.random generation with choice values and specific frequency of values

I am working in generating an (1109, 8) array with random values generated from a fixed set of numbers [18, 24, 36, 0], I need to ensure each row contains 5 zeros at all times, but it wasn't happening even after adjusting the weightings for probabilities.
My workaround code is below but wanted to know if there is an easier way with another function? or perhaps by adjusting some of the parameters of the generator?
https://numpy.org/doc/stable/reference/random/generator.html
#Random output using new method
from numpy.random import default_rng
rng = default_rng(1)
#generate an array with random values of test duration,
test_duration = rng.choice([18, 24, 36, 0], size = arr.shape, p=[0.075, 0.1, 0.2, 0.625])
# ensure number of tests equals n_tests
n_tests = 3
non_tested = arr.shape[1] - n_tests
for row in range(len(test_duration)):
while np.count_nonzero(test_duration[row, :]) != n_tests:
new_test = rng.choice([18, 24, 36, 0], size = arr.shape[1], p=[0.075, 0.1, 0.2, 0.625])
test_duration[row, :] = np.array(new_test)
else:
pass
print('There are no days exceeding n_tests')
#print(test_durations)
print(test_duration[:10, :])
If you need 5 zeros in every row, you can just randomly select 3 values from [18, 24, 36], pad the rest with zeros and then do a per-row random shuffle. The numpy shuffle happens in-place, so you don't need to reassign.
import numpy as np
c = [18,24,26]
p = np.array([0.075, 0.1, 0.2])
p = p / p.sum() # normalize the probs
a = np.random.choice(c, size=(1109, 3), replace=True, p=(p/p.sum()))
a = np.hstack([a, np.zeros((1109, 5), dtype=np.int32)])
list(map(np.random.shuffle, a))
a
# returns:
array([[ 0, 0, 0, 0, 36, 0, 36, 36],
[ 0, 36, 0, 24, 24, 0, 0, 0],
[ 0, 0, 0, 0, 36, 36, 36, 0]])
...
[ 0, 0, 0, 24, 24, 36, 0, 0],
[ 0, 24, 0, 0, 0, 36, 0, 18],
[ 0, 0, 0, 36, 36, 24, 0, 0]])
You could simply create a random choice for the 5 positions of the zeros in the array, this way you would enforce that there are indeed 5 zeros, and after you sample the [18, 24, 36] with their normalized probabilities.
But by doing this you are not respecting the probability density that you specified in the first place, I don't know in which application you're using this for but this is a point to consider.

How to generate all possible combinations of a 2d array in Python, if a value in this array can be between 0 and 255

So i have an empty 4*4 array, values in it can be between 0 and 255, i want to generate all possible states of this array, for example, one of the states is:
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
and the other is:
[245, 241, 124, 53]
[124, 11, 45, 31]
[44, 0, 124, 3]
[1, 30, 123, 31]
Is it possible to do with itertools? And if it is, how do i do it
The number of combinations that you are requesting here is 255^16=3.1962658*10^38 which is a very big number.
Given that 1TB = 10^12 bytes and the clock spead of CPU is 4GHz, it will take as number of molecules in a sugar cube number of storage devices and 10 billion times the age of the universe to acquire and list all of them!
In short: you can't.
You can do something like
import numpy as np
i=0
while i < 10000:
currentstate=256*np.floor(np.rand(4,4))
/* ... do something with current state */
and randomly sample the states.

Efficiently zero out all but largest n elements for each image pixel

So I have an image I of size (H x W x C), where C is some number of channels. The challenge is to obtain a new image J, again of size (H x W x C), in which J[i, j] contains only the maximum n entries in I[i, j].
Equivalently, think about iterating through each image pixel in I and zero-ing out all but the highest n entries.
What I've tried:
# NOTE: bone_weight_matrix is a matrix of size (256 x 256 x 43)
argsort_four = np.argsort(bone_weight_matrix, axis=2)[:, :, -4:]
# For each pixel, retain only the top four influencing bone weights
proc_matrix = np.zeros(bone_weight_matrix.shape)
for i in range(bone_weight_matrix.shape[0]):
for j in range(bone_weight_matrix.shape[1]):
proc_matrix[i, j, argsort_four[i, j]] = bone_weight_matrix[i, j, argsort_four[i, j]]
return proc_matrix
Problem is this method seems to be super slow and doesn't feel very pythonic. Any advice would be great.
Cheers.
Generic case : Keeping largest or smallest n elements along an axis
Basically two steps would be involved :
Get those n indices to be kept along the specified axis with np.argparition.
Initialize a zeros array and use those earlier obtained indices with advanced-indexing to select from the input array as well as assign into the zeros array.
Let's try to solve for a generic problem that works to select n elements along the specified axis and also be able to keep largest n as well as smallest n elements.
The implementation would look like this -
def keep(ar, n, axis=-1, order='largest'):
axis = np.core.multiarray.normalize_axis_index(axis, ar.ndim)
slice_l = [slice(None, None, None)]*ar.ndim
if order=='largest':
slice_l[axis] = slice(-n,None,None)
idx = np.argpartition(ar, kth=-n, axis=axis)[slice_l]
elif order=='smallest':
slice_l[axis] = slice(None,n,None)
idx = np.argpartition(ar, kth=n, axis=axis)[slice_l]
else:
raise Exception('Invalid order value')
grid = np.ogrid[tuple(map(slice, ar.shape))]
grid[axis] = idx
out = np.zeros_like(ar)
out[grid] = ar[grid]
return out
Sample runs
Input array :
In [208]: np.random.seed(0)
...: I = np.random.randint(11,99,(2,2,6))
In [209]: I
Out[209]:
array([[[55, 58, 75, 78, 78, 20],
[94, 32, 47, 98, 81, 23]],
[[69, 76, 50, 98, 57, 92],
[48, 36, 88, 83, 20, 31]]])
Keep largest 2 elements along last axis :
In [210]: keep(I, n=2, axis=-1, order='largest')
Out[210]:
array([[[ 0, 0, 0, 78, 78, 0],
[94, 0, 0, 98, 0, 0]],
[[ 0, 0, 0, 98, 0, 92],
[ 0, 0, 88, 83, 0, 0]]])
Keep largest 1 element along first axis :
In [211]: keep(I, n=1, axis=1, order='largest')
Out[211]:
array([[[ 0, 58, 75, 0, 0, 0],
[94, 0, 0, 98, 81, 23]],
[[69, 76, 0, 98, 57, 92],
[ 0, 0, 88, 0, 0, 0]]])
Keep smallest 2 elements along last axis :
In [212]: keep(I, n=2, axis=-1, order='smallest')
Out[212]:
array([[[55, 0, 0, 0, 0, 20],
[ 0, 32, 0, 0, 0, 23]],
[[ 0, 0, 50, 0, 57, 0],
[ 0, 0, 0, 0, 20, 31]]])

Printing one color using imshow [closed]

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.

Multiply NumPy ndarray with every element in another binary ndarray of different size

I have two ndarrays :
a = [[30,40],
[60,90]]
b = [[0,0,1],
[1,0,1],
[1,1,1]]
please notice that a shape might be larger but always square array (50,50) , (100,100)
The wanted result is :
Result = [[a*0,a*0,a*1],
[[a*1,a*0,a*1],
[[a*1,a*1,a*1]]
I managed to get the right answer with this code but I think there would be a built in function in numpy that accomplish this task in fast manners
totalrows=[]
for row in range(b.shape[0]):
cells=[]
for column in range(b.shape[1]):
print row,column
cells.append(b[row,column]*a)
totalrows.append(np.concatenate(cells,axis=1))
return np.concatenate(totalrows,axis=0)
Indeed there's a NumPy built-in np.kron for such block-based elementwise multiplication problems. To solve your case, it could be used like so -
np.kron(b,a)
Sample run -
In [50]: a
Out[50]:
array([[30, 40],
[60, 90]])
In [51]: b
Out[51]:
array([[0, 0, 1],
[1, 0, 1],
[1, 1, 1]])
In [52]: np.kron(b,a)
Out[52]:
array([[ 0, 0, 0, 0, 30, 40],
[ 0, 0, 0, 0, 60, 90],
[30, 40, 0, 0, 30, 40],
[60, 90, 0, 0, 60, 90],
[30, 40, 30, 40, 30, 40],
[60, 90, 60, 90, 60, 90]])
3D array case
Now, let's say we are working with a as a 3D array (m,n,p) and b as (q,r) and assuming you are looking to perform such a block-wise multiplication iteratively along the last axis of a. Thus, the shapes are to be multiplied along the first two axes on the two inputs to get the output array. To achieve such an output, we need to extend the dimension of b by introducing a singleton dimension as the last axis. The final output would be of shape (m*q,n*r,p*1). The implementation would be simply -
np.kron(b[...,None],a)
Shape check -
In [161]: a = np.random.randint(0,99,(4,5,2))
...: b = np.random.randint(0,99,(6,7))
...:
In [162]: np.kron(b[...,None],a).shape
Out[162]: (24, 35, 2)

Categories

Resources