Replacing ones and zeros in a 2D numpy array with another array? - python

I have a simple problem that I am trying to solve using numpy in an efficient manner. The jist of it is that I have a simple 2D array containing ones and zeros representing an image mask.
What I want to do is convert these ones and zeros into their RGB equivalent where one is a white pixel [255, 255, 255] and zero is a black pixel [0, 0, 0].
How would I go about doing this using NumPy?
mask = [[0, 0, 1],
[1, 0, 0]]
# something
result = [
[[0, 0, 0], [0, 0, 0], [255, 255, 255]],
[[255, 255, 255], [0, 0, 0], [0, 0, 0]]
]
The intent is to take the result and feed it into PIL to save into a PNG.
I've tried using numpy.where but can't seem to coax it into broadcasting another array out.

A possible solution:
np.stack([255 * mask, 255 * mask, 255 * mask], axis=2)
Output:
array([[[ 0, 0, 0],
[ 0, 0, 0],
[255, 255, 255]],
[[255, 255, 255],
[ 0, 0, 0],
[ 0, 0, 0]]])

As your image contains only two colours, I would suggest you consider saving it as a palette image, a.k.a. an indexed image.
Rather than needlessly inflating your image by a factor of 3 to enable it to store 16.7 million colours, you can just store one byte per pixel which will still enable you to have 256 colours which seems plenty when you only have 2 "colours", namely black and white.
That looks like this:
import numpy as np
from PIL import Image
# Make Numpy array "na" from your list
na = np.array(mask, dtype=np.uint8)
# Make PIL Image from Numpy array - this image will be 'L' mode
im = Image.fromarray(na)
# Now push a palette into the image that says:
# index 0 => black, i.e. [0,0,0]
# index 1 => white, i.e. [255,255,255]
#  all other 254 indices are black
# Afterwards the image will be 'P' mode
im.putpalette([0,0,0, 255,255,255] + [0,0,0]*254)
# Save
im.save('result.png')

Since you need to repeat each item three times, np.repeat in conjunction with reshape could be used:
mask = np.array([[0, 0, 1], [1, 0, 0]])
255 * np.repeat(mask, 3, axis=1).reshape(*mask.shape, -1)
>>> array([[[ 0, 0, 0],
[ 0, 0, 0],
[255, 255, 255]],
[[255, 255, 255],
[ 0, 0, 0],
[ 0, 0, 0]]])

Related

How get unique pixels from 2d numpy array?

I have 2d array with rgb pixel data (2 row with 3 pixel in a row).
[[[255, 255, 255],[3, 0, 2],[255, 255, 255]],[[255, 255, 255],[3, 0, 2],[255, 255, 255]]]
How can I get unique pixel? I want to get
[[255, 255, 255], [3, 0, 2]]
I am trying to use np.unique and np.transpose with np.reshape but I wasn't able to get the desired result.
Reshape the array to 2D and then use np.unique with axis=0
arr = np.array([[[255, 255, 255],[3, 0, 2],[255, 255, 255]],[[255, 255, 255],[3, 0, 2],[255, 255, 255]]])
shape = arr.shape
arr = arr.reshape((shape[0] * shape[1], shape[2]))
print(np.unique(arr, axis=0))
Output
[[ 3 0 2]
[255 255 255]]
How about this?
import itertools
np.unique(np.array(list(itertools.chain(*arr))), axis=0)
array([[ 3, 0, 2],
[255, 255, 255]])

How to use numpy.where to change all pixels of an image?

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)

Converting a PNG image to 2D array

I have a PNG file which when I convert the image to a numpy array, it is of the format that is 184 x 184 x 4. The image is 184 by 184 and each pixel is in RGBA format and hence the 3D array.
This a B&W image and the pixels are either [255, 255, 255, 255] or [0, 0, 0, 255].
I want to convert this to a 184 x 184 2D array where the pixels are now either 1 or 0, depending upon if it is [255, 255, 255, 255] or [0, 0, 0, 255].
Any ideas how to do a straightforward conversion of this.
There would be several ways to do the comparison to give us a boolean array and then, we just need to convert to int array with type conversion. So, for the comparison, one simple way would be to compare against 255 and check for ALL matches along the last axis. This would correspond to checking for [255, 255, 255, 255]. Thus, one approach would be like so -
((arr == 255).all(-1)).astype(int)
Sample run -
In [301]: arr
Out[301]:
array([[[255, 255, 255, 255],
[ 0, 0, 0, 255],
[ 0, 0, 0, 255]],
[[ 0, 0, 0, 255],
[255, 255, 255, 255],
[255, 255, 255, 255]]])
In [302]: ((arr == 255).all(-1)).astype(int)
Out[302]:
array([[1, 0, 0],
[0, 1, 1]])
If there are really only two values in the array as you say, simply scale and return one of the dimensions:
(arr[:,:,0] / 255).astype(int)

Numpy: vectorized operations to create a 3D array

I am learning Python and would like to find an efficient way to solve this problem using Numpy.
I currently have a 4x8 array containing random integers:
import numpy as np
n = 3
k = np.random.randint(n, size = (4,8))
Each number represents a color defined by its RGB value in a nx3 array:
colors = np.array([[0 , 0 , 0 ],
[0 , 100, 255],
[255, 100, 0 ]])
I would like to use these numbers to create a new 4x8x3 array where the first two dimensions represent pixels locations, and the third dimension the color of each pixel. This could be thought of as number painting. For example, if k[3,4] = 2, then myArray[3,4,:] = [255 100 0].
I am getting familiar with Numpy tools, but I am unsure of what I should be looking for exactly. Since the array k will eventually be much larger (I'm thinking ~640x480) and contain more than n = 3 non-random colors, I would like to use vectorized operations in order to speed up the process (and learn a bit more about them). Is this the most efficient way to do it?
IIUC, all you need to do is index into colors with k:
>>> k = np.random.randint(n, size = (2,4))
>>> out = colors[k]
>>> out
array([[[ 0, 100, 255],
[255, 100, 0],
[255, 100, 0],
[255, 100, 0]],
[[ 0, 100, 255],
[ 0, 100, 255],
[255, 100, 0],
[255, 100, 0]]])
>>> out.shape
(2, 4, 3)
>>> all((out[i]==colors[c]).all() for i,c in np.ndenumerate(k))
True

numpy-->PIL int type issue

So I've got the x and y values of a curve that I want to plot held as float values in numpy arrays. Now, I want to round them to the nearest int, and plot them as pixel values in an empty PIL image.
Leaving out how I actually fill my x and y vectors, here is what we're working with:
# create blank image
new_img = Image.new('L', (500,500))
pix = new_img.load()
# round to int and convert to int
xx = np.rint(x).astype(int)
yy = np.rint(y).astype(int)
ordered_pairs = set(zip(xx, yy))
for i in ordered_pairs:
pix[i[0], i[1]] = 255
This gives me an error message:
File "makeCurves.py", line 105, in makeCurve
pix[i[0], i[1]] = 255
TypeError: an integer is required
However, this makes no sense to me since the .astype(int) should have cast these puppies to an integer. If I use pix[int(i[0]], int(i[1])] it works, but that's gross.
Why isn't my .astype(int) being recognized as int by PIL?
I think the problem is that your numpy arrays have type numpy.int64 or something similar, which PIL does not understand as an int that it can use to index into the image.
Try this, which converts all the numpy.int64s to Python ints:
# round to int and convert to int
xx = map(int, np.rint(x).astype(int))
yy = map(int, np.rint(y).astype(int))
In case you're wondering how I figured this out, I used the type function on a value from a numpy array:
>>> a = np.array([[1.3, 403.2], [1.0, 0.3]])
>>> b = np.rint(a).astype(int)
>>> b.dtype
dtype('int64')
>>> type(b[0, 0])
numpy.int64
>>> type(int(b[0, 0]))
int
Not sure what you're up to in the first part of your code, but why don't you replace pix = new_img.load() using this instead:
# create blank image
new_img = Image.new('L', (500,500))
pix = array(new_img) # create an array with 500 rows and 500 columns
And then you can follow your original code:
# round to int and convert to int
xx = np.rint(x).astype(int)
yy = np.rint(y).astype(int)
ordered_pairs = set(zip(xx, yy))
for i in ordered_pairs:
pix[i[0], i[1]] = 255
Out[23]:
array([[ 0, 0, 0, ..., 0, 0, 0],
[ 0, 255, 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]], dtype=uint8)

Categories

Resources