Convert an images pixel value from rgb to grayscale manually python PIL? - python

Im trying to use a specific gamma corrected grayscale implementation - Gleam to convert an images pixels to grayscale. How can i do this manually with PIL python?
def tau_gamma_correct(pixel_channel):
pixel_channel = pixel_channel**(1/2.2)
return pixel_channel
##param: rgb
##result: returns grayscale value
def gleam(rgb):
#convert rgb tuple to list
rgblist = list(rgb)
#gamma correct each rgb channel
rgblist[0] = tau_gamma_correct(rgblist[0])
rgblist[1] = tau_gamma_correct(rgblist[1])
rgblist[2] = tau_gamma_correct(rgblist[2])
grayscale = 1/3*(rgblist[0] + rgblist[1] + rgblist[2])
return grayscale
# get a glob list of jpg filenames
files = glob.glob('*.jpg')
for file in files:
file = open(file)
filename = file.name
image = Image.open(file)
pix = image.load()
width, height = image.size
#print(width,height)
for x in range(0, width):
for y in range(0, height):
rgb = pix[x,y]
#print(rgb)
# calc new pixel value and set to pixel
image.mode = 'L'
pix[x,y] = gleam(rgb)
image.save(filename + 'gray.gleam'+'.jpg')
file.close()
SystemError: new style getargs format but argument is not a tuple
It is still expecting the rgb tuple i think.

I found that i could just build another image:
import sys
import os
import glob
import numpy
from PIL import Image
def tau_gamma_correct(pixel_channel):
pixel_channel = pixel_channel**(1/2.2)
return pixel_channel
##param: rgb
##result: returns grayscale value
def gleam(rgb):
#convert rgb tuple to list
rgblist = list(rgb)
#gamma correct each rgb channel
rgblist[0] = tau_gamma_correct(rgblist[0])
print('gleamed red ' + str(rgblist[0]))
rgblist[1] = tau_gamma_correct(rgblist[1])
print('gleamed green ' + str(rgblist[1]))
rgblist[2] = tau_gamma_correct(rgblist[2])
print('gleamed blue ' + str(rgblist[0]))
grayscale = (rgblist[0] + rgblist[1] + rgblist[2])/3
print('grayscale '+ str(grayscale))
return grayscale
# get a glob list of jpg filenames
files = glob.glob('*.jpg')
for file in files:
file = open(file)
filename = file.name
image = Image.open(file)
pix = image.load()
width, height = image.size
new_image = Image.new('L', image.size)
#pixelmatrix = [width][height]
pixelmatrix = numpy.zeros((width, height))
#print(width,height)
for x in range(0, width):
for y in range(0, height):
rgb = pix[x,y]
print('current pixel value: '+str(rgb))
# calc new pixel value and set to pixel
#print(gleam(rgb))
gray = gleam(rgb)
print('changing to pixel value: '+str(gray))
pixelmatrix[x,y] = gray
new_image.save(filename + 'gray.gleam'+'.jpg')
new_image.putdata(pixelmatrix)
file.close()

The problem is that image.mode = 'L' doesn't actually change the type of the image, it just changes the attribute so it's no longer accurate. To change the mode of the image you need to make a new copy with image.convert('L').
Once you have an image in grayscale mode, it won't require a tuple for a pixel value anymore.

Seeing the SystemError: new style getargs format but argument is not a tuple error it seems that you need to return a tuple, which is represented as :
sample_tuple = (1, 2, 3, 4)
So we edit the gleam() function as:
def gleam(rgb):
#convert rgb tuple to list
rgblist = list(rgb)
#gamma correct each rgb channel
rgblist[0] = tau_gamma_correct(rgblist[0])
rgblist[1] = tau_gamma_correct(rgblist[1])
rgblist[2] = tau_gamma_correct(rgblist[2])
grayscale = 1/3*(rgblist[0] + rgblist[1] + rgblist[2])
return (grayscale, )
Keep in mind that while returning a single element tuple you need to represent as :
sample_tuple = (1, )
This is due to the fact that (4) == 4 but (4, ) != 4

Related

Combine all tiff images into one single image

I have 15 tiles or tiff files a folder and I would like combine it as a single file with all the images as one tiff image. All the tiles should be stitched as a single tiff image. How do I do that?
What I tried so far?
import imageio
import os
path = "path/to/dir"
image_path_list = os.listdir(path)
with imageio.get_writer("new_image.tif") as new_image:
for image_path in image_path_list:
image = imageio.imread(path+image_path)
new_image.append_data(image)
This saves as a separate image in a tiff file. I would like to stitch all the images together and save it like the following:
1,2,3...,15 represent the tiles. Needs to be stitched as a single image.
It seems from your comments that you are prepared to consider a non-Python solution, so I used ImageMagick in the Terminal to montage 15 images as follows:
magick montage -tile 3x -geometry +0+0 09*tif result.tif
To demonstrate how you can lay out 5 images across instead of 3, add a different background and affect the horizontal and vertical spacing differently, here is a variation:
magick montage -background magenta -tile 5x -geometry +5+15 09*tif result.tif
Just FYI, I made the 15 randomly coloured blocks like this:
for x in {a..o} ; do magick xc: +noise random -scale 80x50\! 09$x.tif ; done
given one directory with 15 images of same size
using PIL (pillow), I ended up with:
from PIL import Image
import os
path_to_file ='tiff-files'
images = []
for i in os.listdir(path_to_file):
with Image.open(path_to_file+'/'+i) as im:
images.append(im.copy())
new_image = Image.new(images[0].mode, (images[0].size[0]*3,images[0].size[1]*5))
new_image.paste(images[0])
new_image.paste(images[1],(images[0].size[0]*1,0))
new_image.paste(images[2],(images[0].size[0]*2,0))
new_image.paste(images[3],(0,images[0].size[1]*1))
new_image.paste(images[4],(images[0].size[0]*1,images[0].size[1]*1))
new_image.paste(images[5],(images[0].size[0]*2,images[0].size[1]*1))
new_image.paste(images[6],(0,images[0].size[1]*2))
new_image.paste(images[7],(images[0].size[0]*1,images[0].size[1]*2))
new_image.paste(images[8],(images[0].size[0]*2,images[0].size[1]*2))
new_image.paste(images[9],(0,images[0].size[1]*3))
new_image.paste(images[10],(images[0].size[0]*1,images[0].size[1]*3))
new_image.paste(images[11],(images[0].size[0]*2,images[0].size[1]*3))
new_image.paste(images[12],(0,images[0].size[1]*4))
new_image.paste(images[13],(images[0].size[0]*1,images[0].size[1]*4))
new_image.paste(images[14],(images[0].size[0]*2,images[0].size[1]*4))
new_image.show()
let me know if it works.....
After Mark Setchell suggestion here a new version, hope it is better
from PIL import Image
import os
path_to_file ='tiff-files'
def stich_tile(path_to_file, xx , yy):
images = []
for i in os.listdir(path_to_file):
images.append(i)
if len(images) >= xx*yy:
pass
else:
raise ValueError('not enough images in path_to_file !!!!!!!!!!!')
sq_x = xx
sq_y = yy
img_x = (Image.open(path_to_file+'/'+images[0]).size[0])
img_y = (Image.open(path_to_file+'/'+images[0]).size[1])
img_mode = (Image.open(path_to_file+'/'+images[0]).mode)
new_image = Image.new(img_mode, (img_x*sq_x, img_y*sq_y))
x = 0
y = 0
cnt = 0
for i in images:
with Image.open(path_to_file+'/'+i) as img:
new_image.paste(img, (x,y))
cnt += 1
x += img_x
if cnt == sq_x:
x = 0
y += img_y
cnt = 0
else:
pass
return new_image
stich_tile(path_to_file, 3, 5).show()
And thinking more along the lines of https://stackoverflow.com/a/68468658/2836621
import numpy as np
from PIL import Image
import os
# path_to_file ='tiff-files'
path_to_file ='tiff-files2'
# path_to_file ='tiff-files3'
image = []
for i in os.listdir(path_to_file):
with Image.open(path_to_file+'/'+i) as im:
image.append(im.copy())
w, h = image[0].size
new_image = np.zeros((4 * h, 3 * w)).astype('uint8')
col = 0
row = -1
for i, img in enumerate(image):
if not i % 3 :
row += 1
col = 0
img = np.array(img)
new_image[row * h: (row + 1) * h, col * w: (col + 1) * w] = img
col += 1
image_pillow = Image.fromarray(new_image, mode = 'L')
image_pillow.save('prova.tif', mode = 'L')
image_pillow.show()
tested with .tif images grayscale 8-bit
modify adding 3 channel for RGB et similia:
new_image = np.zeros((3 * h, 3 * w,3)).astype('uint8')
new_image[row * h: (row + 1) * h,col * w: (col + 1) * w,:] = img
once more the last example as function for 8 bit grayscale images:
import numpy as np
from PIL import Image
import os
path_to_file ='tiff-files'
# path_to_file ='tiff-files2'
# path_to_file ='tiff-files3'
# path_to_file ='tiff-files5'
def stich_img(path_to_file, x , y):
image = []
for i in os.listdir(path_to_file):
image.append(path_to_file+'/'+i)
print(image)
if len(image) >= x*y:
pass
else:
# raise ValueError('not enough images in path_to_file !!!!!!!!!!!')
raise ValueError('EXCEPTION not enough images in path_to_file !!!!!!!!!!!', x*y ,'images needed : ', len(image),'images present !!!')
image = image[:x*y] #-----> riduce lista immagini al numero richiesto
with Image.open(image[0]) as img0:
w, h = img0.size
# new_image = np.zeros((4 * h, 3 * w)).astype('uint8')
new_image = np.zeros((y * h, x * w)).astype('uint8')
col = 0
row = -1
for i, imgs in enumerate(image):
with Image.open(imgs) as img:
if not i % x :
row += 1
col = 0
img = np.array(img)
new_image[row * h: (row + 1) * h, col * w: (col + 1) * w] = img
col += 1
image_pillow = Image.fromarray(new_image, mode = 'L')
return image_pillow
img_stiched = stich_img(path_to_file, 3,5)
# img_stiched.save('prova.tif', mode = 'L')
img_stiched.show()
Read all images in a list. Iterate over this list using two nested for loops. One in range of 3 and one in range of 5. Use numpy.hstack() and numpy.vstack() to make a final 3x5 image assuming that the size of each tile image is same.
Using numpy:
This script accepts generator of images (to work faster with large images). It does not check their size in advance. If image height does not fit row height or if rows have not the same width, it will fail.
#!/usr/bin/env python3
import numpy as np
from imageio import imread, imwrite
from pathlib import Path
def tile_images(images, cols):
"""Tile images of same size to grid with given number of columns.
Args:
images (collection of ndarrays)
cols (int): number of colums
Returns:
ndarray: stitched image
"""
images = iter(images)
first = True
rows = []
i = 0
while True:
try:
im = next(images)
print(f"add image, shape: {im.shape}, type: {im.dtype}")
except StopIteration:
if first:
break
else:
im = np.zeros_like(im) # black background
if first:
row = im # start next row
first = False
else:
row = np.concatenate((row, im), axis=1) # append to row
i += 1
if not i % cols:
print(f"row done, shape: {row.shape}")
rows.append(row) # finished row
first = True
tiled = np.concatenate(rows) # stitch rows
return tiled
def main():
images = (imread(f) for f in Path().glob("*.*") if f.suffix in (".jpg", ".png") if f.name != "new.png")
new = tile_images(images, cols=3)
imwrite("new.png", new)
def test():
im1 = np.arange(65536).reshape(256,256)
im2 = np.arange(65536/2).reshape(128,256)
images = [im1,im1,im1,im2,im2,im2]
# works
new = tile_images(images, 3)
imwrite("new.png", new)
# failes
new = tile_images(images, 2)
imwrite("new2.png", new)
if __name__ == "__main__":
main()
# test()
The following elaborates on #saad_saeed answer.
Note, the following will break:
if your list_of_images doesn't have enough images to build the num_mosaic_rows x num_mosaic_cols mosaic. I've left it to the user to add the handling of this (e.g. adding an if/else).
if each img in your list_of_images doesn't have the same shape
def build_mosaic(list_of_images, num_mosaic_rows, num_mosaic_cols):
list_of_mosaic_rows = []
for row_number in range(num_mosaic_rows):
list_of_mosaic_rows = list_of_images[row_number*num_mosaic_cols,(row_number+1)*num_mosaic_cols]
mosaic = np.vstack(list_of_mosaic_rows)
return mosaic

How to Create New Image From Array List Which Contains Binary Pixel Data

Here I use the PIL Library to read and manipulate images. I am confused, how to create a new image from the list of arrays containing binary pixel data, after being converted to binary images.
I have tried it, but the resulting image is of type RGB, not a binary image. The following is the code that I wrote:
from PIL import Image
import numpy as np
img = Image.open('data_train/ga.jpeg')
pixels = img.load()
width, height = img.size
all_pixels = []
for x in range(width):
for y in range(height):
hpixel = pixels[x, y]
img_gray = (0.2989 * hpixel[0]) + (0.5870 * hpixel[1]) + (0.1140 * hpixel[2])
if img_gray >= 110:
all_pixels.append('1')
else:
all_pixels.append('0')
data_isi = {'0': 0,
'1': 255}
data = [data_isi[letter] for letter in all_pixels]
img_new = Image.fromarray(data)
img_new.save('data_train/gabiner.jpeg')
Updated Answer
As you are required to use a for loop, you could go with something more like this:
#!/usr/bin/env python3
from PIL import Image
# Load image and get dimensions
img = Image.open('start.jpg').convert('RGB')
width, height = img.size
# Actually load input pixels, else PIL is too lazy
imi = img.load()
# List of result pixels
imo = []
for y in range(height):
for x in range(width):
R, G, B = imi[x, y]
gray = (0.2989 * R) + (0.5870 * G) + (0.1140 * B)
if gray >= 110:
imo.append(255)
else:
imo.append(0)
# Make output image and put output pixels into it
result = Image.new('L', (width,height))
result.putdata(imo)
# Save result
result.save('result.png')
Which turns this start image:
Into this result:
Original Answer
You appear to be converting the image to greyscale and thresholding at 110, which can be done much more simply, and faster, like this:
#!/usr/local/bin/python3
from PIL import Image
# Load image and make greyscale
im = Image.open('image.png').convert('L')
# Threshold to make black and white
thr = im.point(lambda p: p > 110 and 255)
# Save result
thr.save('result.png')

Saving numpy 32int into multi layer tiff

I am working with scientific images, where something is called "Line" have a sequence of (n) grayscale images.
What I do is I color the images of each line with a specific color, and then I merge the corresponding image of each line together (i.e. I merge image_0 of each line into one image, and then image_1 of each line into one and so on).
The result is a colored merged sequence of (n) images.
My goal now is to save this sequence into one tiff file, which I couldn't so far.
def load_image(infilename):
img = Image.open(infilename)
img.load()
data = np.asarray(img, dtype="int32")
return data
def merge_lines_images(lines, colors):
rotations_count = len(lines[0])
layers = []
for rotation in range(0, rotations_count):
images_to_merge = []
for line in lines:
img = load_image(line[rotation])
images_to_merge.append(img)
colored_images = []
for img_index, img in enumerate(images_to_merge):
color = colors[img_index]
r = img * color[0] / 255
g = img * color[1] / 255
b = img * color[2] / 255
rgb = np.dstack((r,g,b))
colored_images.append(rgb)
merged_image = np.zeros(colored_images[0].shape)
for img in colored_images:
merged_image += img
clipped = np.clip(merged_image, 0, 255, out=merged_image)
layers.append(clipped)
How to save the "layers" array into one multi-layer tiff file?
Thank you very much

Python Pillow Creating an Image with List of Pixels

I have a .txt file that contains coordinates and color codes. I want to create an image from this
My example txt file:
1,1#f8f3ed
1,2#fff9f1
1,3#faf2e7
1,4#fbf2e1
1,5#f6eed9
1,6#e1d6c0
1,7#e2d6be
1,8#ebdfc5
1,9#d0c4ac
1,10#cdc2ac
1,11#e3dbc6
1,12#ded7c5
.
.
187,249#1b2019
How can I create this image?
Edited code as below:
from PIL import Image
from PIL import ImageColor
path_to_file = 'data.txt'
img_data = []
height = 0
width = 0
# The first step is to read the file
with open(path_to_file) as f:
lines = f.readlines()
# Strip off new lines
lines = [x.strip('\n') for x in lines]
for line in lines:
x,y = line[:-7].split(',')
# I'm assuming from the data that the x,y vals start at 1,1. Subtract 1 from each value so they are zero indexed.
x = int(x) - 1
y = int(y) - 1
color = line[-7:]
# Use PIL's ImageColor.getrgb() to convert hex string to a rgb tuple
color = ImageColor.getrgb(color)
img_data.append((x,y,color))
# Keep track of the max x,y vals for to get the width and height of the image
height = max(height, x+1)
width = max(width, y+1)
# Create a new image
background = (0, 0, 0, 255)
img = Image.new('RGB', (width, height), background)
pixels = img.load()
# Set the pixel values from our data
for d in img_data:
pixels[d[0], d[1]] = d[2]
img.save("image.png")
Now it raises:
ValueError: too many values to unpack
How to solve this error?
This worked for me:
from PIL import Image
from PIL import ImageColor
path_to_file = 'test.txt'
img_data = []
height = 0
width = 0
# The first step is to read the file
with open(path_to_file) as f:
lines = f.readlines()
# Strip off new lines
lines = [x.strip('\n') for x in lines]
for line in lines:
x,y = line[:-7].split(',')
# I'm assuming from the data that the x,y vals start at 1,1. Subtract 1 from each value so they are zero indexed.
x = int(x) - 1
y = int(y) - 1
color = line[-7:]
# Use PIL's ImageColor.getrgb() to convert hex string to a rgb tuple
color = ImageColor.getrgb(color)
img_data.append((x,y,color))
# Keep track of the max x,y vals for to get the width and height of the image
height = max(height, y)
width = max(width, x)
# Create a new image
background = (0, 0, 0, 255)
img = Image.new('RGB', (width + 1, height + 1), background)
pixels = img.load()
# Set the pixel values from our data
for d in img_data:
pixels[d[0], d[1]] = d[2]
img.save("image.png")

Python - gray scale formula with PIL

I want to make greyscale image by my own code.
from PIL import Image
path = "people.jpg"
img = Image.open(path)
img = img.convert("LA")
img.save("new_image.png")
So I change img.convert() for my own formula. But it does not work.
from PIL import Image
path = "people.jpg"
img = Image.open(path)
rgb = img.convert("RGB")
width,height = rgb.size
for x in range(width):
for y in range(height):
red,green,blue = rgb.getpixel((x,y))
value = red * 299/1000 + green * 587/1000 + blue * 114/1000
value = int(value)
rgb.putpixel((x,y),value)
rgb.save("new.png")
Any idea what is wrong? The new image is with red backgroud.
As martineau mentioned, rgb is initialized as an RGB (tri-band) image, so it is expecting RGB values. You want grayscale, which only requires a single band. You have two options here:
Follow martineau's advice and simply swap out rgb.putpixel((x,y),value) for rgb.putpixel((x, y), (value, value, value)). This will give it the proper three color values.
Create a new grayscale image using Image.new('L',...) before putting the pixels into it.
Here's how you might implement option 2 (note that I'm using Python 2.7):
from PIL import Image
path = "people.jpg"
img = Image.open(path)
width, height = rgb.size
gray = Image.new('L', (width, height))
for x in xrange(width):
for y in xrange(height):
r, g, b = img.getpixel((x, y))
value = r * 299.0/1000 + g * 587.0/1000 + b * 114.0/1000
value = int(value)
gray.putpixel((x, y), value)
gray.save("new.png")
Also, if speed is a concern, consider using putdata() instead of putpixel(). (That's beside the point, so I won't belabor it.)
Greyscale image will be having a tuple of two values for each pixel - (luminosity value, 255). Creating a new greyscale image and putting values to each pixel will work.
greyim=Image.new("LA",(width,height))
for x in range(width):
for y in range(height):
red,green,blue = rgb.getpixel((x,y))
value = red * 299/1000 + green * 587/1000 + blue * 114/1000
greyim.putpixel((x,y),(value,255))

Categories

Resources