Create Numpy array of images - python

I have some (950) 150x150x3 .jpg image files that I want to read into an Numpy array.
Following is my code:
X_data = []
files = glob.glob ("*.jpg")
for myFile in files:
image = cv2.imread (myFile)
X_data.append (image)
print('X_data shape:', np.array(X_data).shape)
The output is (950, 150). Please let me know why the list is not getting converted to np.array correctly and whether there is a better way to create the array of images.
Of what I have read, appending to numpy arrays is easier done through python lists and then converting them to arrays.
EDIT: Some more information (if it helps), image.shape returns (150,150,3) correctly.

I tested your code. It works fine for me with output
('X_data shape:', (4, 617, 1021, 3))
however, all images were exactly the same dimension.
When I add another image with different extents I have this output:
('X_data shape:', (5,))
So I'd recommend checking the sizes and the same number of channels (as in are really all images coloured images)? Also you should check if either all images (or none) have alpha channels (see #Gughan Ravikumar's comment)
If only the number of channels vary (i.e. some images are grey), then force loading all into the color format with:
image = cv2.imread (myFile, cv2.IMREAD_COLOR)
EDIT:
I used the very code from the question, only replaced with a directory of mine (and "*.PNG"):
import cv2
import glob
import numpy as np
X_data = []
files = glob.glob ("C:/Users/xxx/Desktop/asdf/*.PNG")
for myFile in files:
print(myFile)
image = cv2.imread (myFile)
X_data.append (image)
print('X_data shape:', np.array(X_data).shape)

Appending images in a list and then converting it into a numpy array, is not working for me. I have a large dataset and RAM gets crashed every time I attempt it. Rather I append the numpy array, but this has its own cons. Appending into list and then converting into np array is space complex, but appending a numpy array is time complex. If you are patient enough, this will take care of RAM crasing problems.
def imagetensor(imagedir):
for i, im in tqdm(enumerate(os.listdir(imagedir))):
image= Image.open(im)
image= image.convert('HSV')
if i == 0:
images= np.expand_dims(np.array(image, dtype= float)/255, axis= 0)
else:
image= np.expand_dims(np.array(image, dtype= float)/255, axis= 0)
images= np.append(images, image, axis= 0)
return images
I am looking for better implementations that can take care of both space and time. Please comment if someone has a better idea.

Here is a solution for images that have certain special Unicode characters, or if we are working with PNGs with a transparency layer, which are two cases that I had to handle with my dataset. In addition, if there are any images that aren't of the desired resolution, they will not be added to the Numpy array. This uses the Pillow package instead of cv2.
resolution = 150
import glob
import numpy as np
from PIL import Image
X_data = []
files = glob.glob(r"D:\Pictures\*.png")
for my_file in files:
print(my_file)
image = Image.open(my_file).convert('RGB')
image = np.array(image)
if image is None or image.shape != (resolution, resolution, 3):
print(f'This image is bad: {myFile} {image.shape if image is not None else "None"}')
else:
X_data.append(image)
print('X_data shape:', np.array(X_data).shape)
# If you have 950 150x150 images, this would print 'X_data shape: (950, 150, 150, 3)'
If you aren't using Python 3.6+, you can replace the r-string with a regular string (except with \\ instead of \, if you're using Windows), and the f-string with regular string interpolation.

Your definition for the .JPG frame that will be put into a matrix of the same size should should be x, y, R, G, B, A. "A" is not used, but it does take up 8 bits at the end of each pixel.

Related

Using patchify to create patches from images with different shapes

I use patchify to generate patches from images. My folder, from which I take the data base, contains images which are of different shape (1536x2048 and 2048x1536).
If I use only one shape (no matter if 1536x2048 or 2048x1536) I get the reasonable number of patches.
But if I combine both shapes, I get some additional images, which are just duplicates of patches.
Why does my code not work when I use two different shapes, even though they should both produce even numbers in the number of resulting patches for both axes?
The core of my code comes from the following question (before this code, I just create lists with the corresponding information about the images I'm using):
Problem when using patchify library to create patches
import numpy as np
import cv2
from PIL import Image
import os
from patchify import patchify
List = []
destinationFile = "C:/.../Output/Images/"
for root, Lists, files in os.walk("C:/.../Input/Images/"):
for name in files:
if name.endswith(".png"):
List.append(os.path.join(root, name))
for filename in List:
img_no_ndarray = Image.open(filename)
img = np.array(img_no_ndarray)
patches_img = patchify(img, (512, 512, 3), step=512)
for i in range(patches_img.shape[0]):
for j in range(patches_img.shape[1]):
single_patch_img = patches_img[i, j, 0, :, :, :]
if not cv2.imwrite(destinationFile + str(i) + "_" + str(j) + "_" + name, single_patch_img):
raise Exception("Could not write the image")
Thanks
Two thoughts, if it works when all input has the same dimensions then have you tried adding a reshaping step to match all input shapes before running through patchify?
And, you might find that this line is better for avoiding duplications/overwriting your output patches:
cv2.imwrite(r'C:/destinationfilepath/image_{}{}.png'.format(str(i).zfill(4),str(j).zfill(4)), single_patch_img)

numpy.resize throws valueError despite image matrix product equaling the total image size

I am trying to resize a grayscale image into a numpy array like so:
return np.array(image.getdata()).reshape((im_height, im_width, 3)).astype(np.uint8)
and getting this error:
ValueError: cannot reshape array of size 1909760 into shape
(1024,1865,3)
I've read that the product of an images columns and rows (1024 x 1865) is supposed to equal the size of the array being reshaped - (1909760) which it does. I've also tried the same code on images with three channels and it works.
If you're using the PIL module for your image, you could try converting it to an RGB before getting the data. Something like this should work:
image = image.convert("RGB")
return np.array(image.getdata()).reshape((im_height, im_width, 3)).astype(np.uint8)
This works because when you convert from a grayscale to an RGB, PIL automatically sets each pixel to have three values, an R, G, and B.
Do not use .getdata(). That's pointless and a waste of effort. What'll happen is that a python list of integers is constructed as an intermediate. Directly converting to a numpy array is much more efficient.
Just use this:
# image = Image.open(...)
image_array = np.array(image)
Secondly you need to handle the conversion from grayscale to RGB, which you seem to want. Your PIL image appears to be grayscale, yet you want a numpy array with three channels (third dimension sized 3). You can either use PIL to convert, or you can use OpenCV.
PIL: image = image.convert("RGB") before converting to numpy (thanks Timmy Diehl, I don't use PIL that often)
OpenCV: image_array = cv.cvtColor(image_array, cv.COLOR_GRAY2BGR) after converting to numpy
Also note the order of color channels. PIL prefers RGB. OpenCV prefers BGR. What you need depends on what you'll do with the numpy array.

How to handle image with extension .nii.gz? Is it possible to convert them in grayscale format like (240,240)?

I have always work with images with extensions .png, .jpg, .jpeg Now, I have seen medical images with extension .nii.gz
I'm using python and I have read it with the following code:
path = "./Task01_BrainTumour/imagesTr"
path_list = glob.glob(path+'/*.gz') #list with all paths of image.nii.gz
img = nib.load(path_list[0]).get_data() #load a single image
Now the image is an array of float32 and it has the following shape (240, 240, 155, 4). I have read online that (240, 240, 155, 4) indicates that the image has size (240,240), 155 indicates the depth of the image object, namely there are 155 layers in every image object. However, this information related to the layer/depth is not clear to me, what does it mean that an image has some layers? Finally, 4 indicates the channel of the image.
I would like to convert these images in the classical format (240,240,3) for rgb or (240,240) in grayscale. I don't know if it is possible to do that.
You're halfway there.
It looks like you're using the Brain Tumours data from the Medical Segmentation Decathlon, and NiBabel to read the images. You can install e.g. scikit-image to save the JPGs.
from pathlib import Path
import numpy as np
import nibabel as nib
from skimage import io
def to_uint8(data):
data -= data.min()
data /= data.max()
data *= 255
return data.astype(np.uint8)
def nii_to_jpgs(input_path, output_dir, rgb=False):
output_dir = Path(output_dir)
data = nib.load(input_path).get_fdata()
*_, num_slices, num_channels = data.shape
for channel in range(num_channels):
volume = data[..., channel]
volume = to_uint8(volume)
channel_dir = output_dir / f'channel_{channel}'
channel_dir.mkdir(exist_ok=True, parents=True)
for slice in range(num_slices):
slice_data = volume[..., slice]
if rgb:
slice_data = np.stack(3 * [slice_data], axis=2)
output_path = channel_dir / f'channel_{channel}_slice_{slice}.jpg'
io.imsave(output_path, slice_data)

How to convert a RGB image (3 channel) to grayscale (1 channel) and save it?

Working with a deep learning project and I have a lot of images, that don't need to have colors. I saved them doing:
import matplotlib.pyplot as plt
plt.imsave('image.png', image, format='png', cmap='gray')
However later when I checked the shape of the image the result is:
import cv2
img_rgb = cv2.imread('image.png')
print(img_rgb.shape)
(196,256,3)
So even though the image I view is in grayscale, I still have 3 color channels. I realized I had to do some algebric operations in order to convert those 3 channels into 1 single channel.
I have tried the methods described on the thread "How can I convert an RGB image into grayscale in Python?" but I'm confused.
For example, when to do the conversion using:
from skimage import color
from skimage import io
img_gray = color.rgb2gray(io.imread('image.png'))
plt.imsave('image_gray.png', img_gray, format='png')
However when I load the new image and check its shape:
img_gr = cv2.imread('image_gray.png')
print(img_gr.shape)
(196,256,3)
I tried the other methods on that thread but the results are the same. My goal is to have images with a (196,256,1) shape, given how much less computationally intensive it will be for a Convolutional Neural Network.
Any help would be appreciated.
Your first code block:
import matplotlib.pyplot as plt
plt.imsave('image.png', image, format='png', cmap='gray')
This is saving the image as RGB, because cmap='gray' is ignored when supplying RGB data to imsave (see pyplot docs).
You can convert your data into grayscale by taking the average of the three bands, either using color.rgb2gray as you have, or I tend to use numpy:
import numpy as np
from matplotlib import pyplot as plt
import cv2
img_rgb = np.random.rand(196,256,3)
print('RGB image shape:', img_rgb.shape)
img_gray = np.mean(img_rgb, axis=2)
print('Grayscale image shape:', img_gray.shape)
Output:
RGB image shape: (196, 256, 3)
Grayscale image shape: (196, 256)
img_gray is now the correct shape, however if you save it using plt.imsave, it will still write three bands, with R == G == B for each pixel. This is because, I believe, a PNG file requires three (or four) bands. Warning: I am not sure about this: I expect to be corrected.
plt.imsave('image_gray.png', img_gray, format='png')
new_img = cv2.imread('image_gray.png')
print('Loaded image shape:', new_img.shape)
Output:
Loaded image shape: (196, 256, 3)
One way to avoid this is to save the images as numpy files, or indeed to save a batch of images as numpy files:
np.save('np_image.npy', img_gray)
new_np = np.load('np_image.npy')
print('new_np shape:', new_np.shape)
Output:
new_np shape: (196, 256)
The other thing you could do is save the grayscale png (using imsave) but then only read in the first band:
finalimg = cv2.imread('image_gray.png',0)
print('finalimg image shape:', finalimg.shape)
Output:
finalimg image shape: (196, 256)
As it turns out, Keras, the deep-learning library I'm using has its own method of converting images to a single color channel (grayscale) in its image pre-processing step.
When using the ImageDataGenerator class the flow_from_directory method takes the color_mode argument. Setting color_mode = "grayscale" will automatically convert the PNG into a single color channel!
https://keras.io/preprocessing/image/#imagedatagenerator-methods
Hope this helps someone in the future.
if you want to just add extra channels that have the same value as the graysacale , maybe to use a specific model that requires 3 channel input_shape .
lets say your pictures are 28 X 28 and so you have a shape of (28 , 28 , 1)
def add_extra_channels_to_pic(pic):
if pic.shape == (28 , 28 , 1):
pic = pic.reshape(28,28)
pic = np.array([pic , pic , pic])
# to make the channel axis in the end
pic = np.moveaxis(pic , 0 , -1)
return pic
Try this method
import imageio
new_data = imageio.imread("file_path", as_gray =True)
imageio.imsave("file_path", new_data)
The optional argument "as_gray = True" in line 2 of the code does the actual conversion.

Averaging multiple images in python

I'm trying to average 300 .tif images with this code :
import os, numpy, PIL
from PIL import Image
# Access all PNG files in directory
allfiles=os.listdir(os.getcwd())
imlist=[filename for filename in allfiles if filename[-4:] in[".tif",".TIF"]]
# Assuming all images are the same size, get dimensions of first image
w,h = Image.open(imlist[0]).size
N = len(imlist)
# Create a numpy array of floats to store the average (assume RGB images)
arr = numpy.zeros((h,w,3),numpy.float)
# Build up average pixel intensities, casting each image as an array of floats
for im in imlist:
imarr = numpy.array(Image.open(im),dtype=numpy.float)
arr = arr+imarr/N
# Round values in array and cast as 16-bit integer
arr = numpy.array(numpy.round(arr),dtype=numpy.uint16)
# Generate, save and preview final image
out = Image.fromarray(arr,mode="RGB")
out.save("Average.tif")
And it gives me a TypeError like that :
imarr = numpy.array(Image.open(im),dtype=numpy.float)
TypeError: float() argument must be a string or a number, not 'TiffImageFile'
I understand that it doesn't really like to put a TIF image in the numpy array (it also doesn't work with PNG images). What should I do ? Splitting each image into R, G and B arrays to average and then merge everything seems too memory consuming.
It should work as is, checked right now with PIL (pillow 2.9.0) and numpy 1.9.2.

Categories

Resources