Create a numpy image with constant color - python

It's easy to create a color image with a constant scalar across all channels:
height, width = 3, 4
shape = (height, width)
num_channels = 3
scalar_value = 0.5
image = np.full((*shape, num_channels), scalar_value)
Is there an easy way to create an image with a constant color vector?
vector_value = (0.3, 0.4, 0.5) # e.g. (red, green, blue)
image = create_new(shape, vector_value)
This could be done using a custom function:
def create_new(shape, vector_value):
image = np.empty((*shape, len(vector_value)))
image[...] = vector_value
return image
but I am wondering if this can be done using a simple numpy expression.

The documentation for np.full is a little misleading (read incorrect). It accepts any broadcastable value as fill_value, not just a scalar. That means you can just do
np.full((*shape, len(vector_value)), vector_value)
I tested this with numpy version 1.17.3, and I'm not sure when that changed. My guess is that if you go back far enough, the documentation held true, and fill_value could only be a scalar.
There is currently an issue open to update the documentation: https://github.com/numpy/numpy/issues/14837 .

This seems to work, although it creates a view onto the vector rather than a separate image array:
image = np.broadcast_to(vector_value, (*shape, num_channels))
Perhaps this one, although it is a bit long:
image = np.broadcast_to(vector_value, (*shape, num_channels)).copy()

Related

How to replace pixel value as a tensorflow operation?

I need to replace a pixel value in an image as an operation in the graph. Doing this beforehand is unfortunately not an option as it is part of an optimization process.
As a fix until I come up with a solution, I am simply using tf.py_func() but since this operation has to be executed a lot it's very slow and inefficient.
# numpy function to perturb a single pixel in an image
def perturb_image(pixel, img):
# At each pixel's x,y position, assign its rgb value
x_pos, y_pos, r, g, b = pixel
rgb = [r,g,b]
img[x_pos, y_pos] = rgb
return img
# pixel is a 1D tensor like [x-dim,y-dim,R,G,B]
# image is tensor with shape (x-dim,y-dim,3)
img_perturbed = tf.py_func(perturb_image,[pixel, image], tf.uint8)
One way I thought of is using tf.add(perturbation, image) where both have the same dimension and perturbation is all zeros except at the pixel location which needs its RGB-values changed to the same value as defined in pixel from the above code snippet. Unfortunately, I would need to rewrite a lot of code surrounding this operation which I am trying to avoid.
Can you think of a solution to replace py_func with another tensorflow operation using the same inputs?
Any help is much appreciated.

Tiling an array in place with numpy

I have an image stored in RGBA format as a 3d numpy array in python, i.e.
image = np.zeros((500, 500, 4), dtype=np.int16)
would be a transparent, black 500x500 square.
I would like to be able to quickly fill the image with a uniform color. For instance fill_img(some_instance_with_img, (255, 0, 0, 255)) would fill the image stored in some_instance_with_img with opaque red. The following code does the trick, assuming self is an instance that contains an image stored as image:
def fill_img(self, color):
color = np.array(color)
shape = self.image.shape
self.image = np.tile(color, (shape[0] * shape[1])).reshape(shape)
However, it creates a brand new array and simply reassigns self.image to this new array. What I would like to do is avoid this intermediate array. If np.tile had an out argument, it would look like:
def fill_img(self, color):
color = np.array(color)
shape = self.image.shape
np.tile(color, (shape[0] * shape[1]), out=self.image)
self.image.reshape(shape)
but np.tile does not support an out parameter. It feels like I am just missing something, although it is possible that this behavior doesn't exist. Any help would be appreciated. Thanks.

How to reshape a 3D numpy array?

I have a list of numpy arrays which are actually input images to my CNN. However size of each of my image is not cosistent, and my CNN takes only images which are of dimension 224X224. How do I reshape each of my image into the given dimension?
print(train_images[key].reshape(224, 224,3))
gives me an output
ValueError: total size of new array must be unchanged
I would be very grateful if anybody could help me with this.
New array should have the same amount of values when you are reshaping. What you need is cropping the picture (if it is bigger than 224x224) and padding (if it is smaller than 224x224) or resizing on both occasions.
Cropping is simply slicing with correct indexes:
def crop(np_img, size):
v_start = round((np_img.shape[0] - size[0]) / 2)
h_start = round((np_img.shape[1] - size[1]) / 2)
return np_img[v_start:v_start+size[1], h_start:h_start+size[0],:]
Padding is slightly more complex, this will create a zeros array in desired shape and plug in the values of image inside:
def pad_image(np_img, size):
v_start = round((size[0] - np_img.shape[0]) / 2)
h_start = round((size[1] - np_img.shape[1]) / 2)
result = np.zeros(size)
result[v_start:v_start+np_img.shape[1], h_start:h_start+np_img.shape[0], :] = np_img
return result
You can also use np.pad function for it:
def pad_image(np_img, size):
v_dif = size[0] - np_img.shape[0]
h_dif = size[1] - np_img.shape[1]
return np.lib.pad(np_img, ((v_dif, 0), (h_dif, 0), (0, 0)), 'constant', constant_values=(0))
You may realize padding is a bit different in two functions, I didn't want to over complicate the problem and just padded top and left on the second function. Did the both sides in first one since it was easier to calculate.
And finally for resizing, you better use another library. You can use scipy.misc.imresize, its pretty straightforward. This should do it:
imresize(np_img, size)
Here are a few ways I know to achieve this:
Since you're using python, you can use cv2.resize(), to resize the image to 224x224. The problem here is going to be distortions.
Scale the image to adjust to one of the required sizes (W=224 or H=224) and trim off whatever is extra. There is a loss of information here.
If you have the larger image, and a bounding box, use some delta to bounding box to maintain the aspect ratio and then resize down to the required size.
When you reshape a numpy array, the produce of the dimensions must match. If not, it'll throw a ValueError as you've got. There's no solution using reshape to solve your problem, AFAIK.
The standard way is to resize the image such that the smaller side is equal to 224 and then crop the image to 224x224. Resizing the image to 224x224 may distort the image and can lead to erroneous training. For example, a circle might become an ellipse if the image is not a square. It is important to maintain the original aspect ratio.

Using python, how can I display a matrix of RGB values in a window?

I have calculated a matrix of RGB triples for an image and I would like to know the most straightforward technique to display them in an interactive window. I suspect that pygame will be involved. Techniques that minimize the use of pip will be given preference.
result = numpy.zeros([height,width, 3], dtype=numpy.uint8)
pyopencl.enqueue_copy(queue, result, result_g).wait()
surface = pygame.display.set_mode((width, height), pygame.DOUBLEBUF)
# now what?
The solution I was able to get working was this:
result = numpy.zeros([height,width, 3], dtype=numpy.uint8)
pyopencl.enqueue_copy(queue, result, result_g).wait()
surface = pygame.display.set_mode((width, height), pygame.DOUBLEBUF)
rgb2 = numpy.transpose(rgb, (1,0,2))
pygame.pixelcopy.array_to_surface(surface, rgb2)
pygame.display.flip()
The transpose is only necessary because my opencl kernel computed an image arranged as result[y,x,:] = (r,g,b) whereas array_to_surface expects result[x,y,:] (which is backwards from how most framebuffers work). I could alter my opencl kernel to store things in column-major order if I wanted to avoid the transpose.
This solution only works because my surface is the exact same dimensions as my image. I will upvote any other good solutions that work when the surface and the pixel matrix are different dimensions (because someone might find this article when searching for that).
It is difficult to answer exactly without knowing what the code you have shown does, but something like this:
for c in range(width):
for d in range(height):
color = result(d,c)
pygame.draw.line(surface, color, (c,d),(c+1,d))

matplotlib imshow plots different if using colormap or RGB array

I am having the following problem: I am saving 16-bit tiff images with a microscope and I need to analyze them. I want to do that with numpy and matplotlib, but when I want to do something as simple as plotting the image in green (I will later need to superpose other images), it fails.
Here is an example when I try to plot the image either as a RGB array, or with the default jet colormap.
import numpy as np
import matplotlib.pyplot as plt
import cv2
imageName = 'image.tif'
# image as luminance
img1 = cv2.imread(imageName,-1)
# image as RGB array
shape = (img1.shape[0], img1.shape[1], 3)
img2 = np.zeros(shape,dtype='uint16')
img2[...,1] += img1
fig = plt.figure(figsize=(20,8))
ax1 = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2)
im1 = ax1.imshow(img1,interpolation='none')
im2 = ax2.imshow(img2,interpolation='none')
fig.show()
Which to me yields the following figure:
I am sorry if the question is too basic, but I have no idea why the right plot is showing this artifacts. I would like to get with the green scale, something like how the figure looks (imageJ also yields somthing similar to the left plot).
Thank you very much for your collaboration.
I find the right plot much more artistic...
matplotlib is rather complicated when it comes to interpreting images. It goes roughly as follows:
if the image is a NxM array of any type, it is interpreted through the colormap (autoscale, if not indicated otherwise). (In principle, if the array is a float array scaled to 0..1, it should be interpreted as a grayscale image. This is what the documentation says, but in practice this does not happen.)
if the image is a NxMx3 float array, the RGB components are interpreted as RGB components between 0..1. If the values are outside of this range, they are taken with positive modulo 1, i.e. 1.2 -> 0.2, -1.7 -> 0.3, etc.
if the image is a NxMx3 uint8 array, it is interpreted as a standard image (0..255 components)
if the image is NxMx4, the interpretation is as above, but the fourth component is the opacity (alpha)
So, if you give matplotlib a NxMx3 array of integers other than uint8 or float, the results are not defined. However, by looking at the source code, the odd behavour can be understood:
if A.dtype != np.uint8:
A = (255*A).astype(np.uint8)
where A is the image array. So, if you give it uint16 values 0, 1, 2, 3, 4..., you get 0, 255, 254, 253, ... Yes, it will look very odd. (IMHO, the interpretation could be a bit more intuitive, but this is how it is done.)
In this case the easiest solution is to divide the array by 65535., and then the image should be as expected. Also, if your original image is truly linear, then you'll need to make the reverse gamma correction:
img1_corr = (img1 / 65535.)**(1/2.2)
Otherwise your middle tones will be too dark.
I approached this by normalising the image by the maximum value of the given datatype, which said by DrV, for uint16 is 65535. The helper function would look something like:
def normalise_bits(img):
bits = 1.0 # catch all
try:
# Test integer value, e.g. np.uint16
bits = np.iinfo(img.dtype).max
except ValueError:
# Try float maximum, e.g. np.float32
bits = np.finfo(img.dtype).max
return (img / bits).astype(float)
Then the image can be handled by matplotlib as a float [0.0, 1.0]

Categories