I'm trying to take a few png images add them an enumerated grid and save them each image by itself as a single tiff file
the output I get is, first image with grid and numbers as required,
the first image is shorter then the others if that matters
other images are only numbered but without a grid
this is my code
from PIL import Image, ImageDraw, ImageOps, ImageFont
import os
import glob
path = r'D:\in'
out = r"d:\1.tif"
font = ImageFont.truetype(r"D:\python1\New folder\keyer_layout\films.EPISODE1.ttf",32)
def add_grid(path):
im = Image.open(path)
im = ImageOps.expand(im, border=50, fill = 'rgb(255,255,255)') #add margin to the image
draw = ImageDraw.Draw(im)
y_start = 0
y_end = im.height
step_size = int(im.width / 10)
li = 0
for x in range(0, im.width, step_size):
line = ((x, y_start), (x, y_end))
draw.line(line, fill=200)
draw.text((x, y_start),str(li),'rgb(0,0,0)',font=font)
li+=1
x_start = 0
x_end = im.width
li = 0
for y in range(0, im.height, step_size):
line = ((x_start, y), (x_end, y))
draw.line(line, fill=128)
draw.text((x_start, y),str(li),'rgb(0,0,0)',font=font)
li+=1
del draw
return im
pics_path = os.path.join(path,"*.png")
pics = glob.glob(pics_path)
pics_arr = []
for i in pics:
pics_arr.append(add_grid(i))
pics_arr[0].save(r"d:\test.tif", append_images = pics_arr[1:],save_all = True)
I tried to add im.show() inside the function the images looked the same first one with grid others without
when I tried to skip the first image, non of the images had a grid on it
Thanks
Firstly, instead of the last line there should be something like this:
for i, pict in enumerate(pics_arr):
pict.save(r"d:\test" + str(i) + ".tif")
This way you will get all your images with the grid on them as a bunch of tif files.
After that you need to combine them into one big file. It can be done this way: Combine several images horizontally with Python
The final script could look like this:
from PIL import Image, ImageDraw, ImageOps, ImageFont
import os
import glob
path = r'd:\in'
out = r'd:\out'
# font = ImageFont.truetype() <-- sorry, I have no your font at hand
def add_grid(path):
im = Image.open(path)
im = ImageOps.expand(im, border=50, fill = 'rgb(255,255,255)') #add margin to the image
draw = ImageDraw.Draw(im)
y_start = 0
y_end = im.height
step_size = int(im.width / 10)
li = 0
for x in range(0, im.width, step_size):
line = ((x, y_start), (x, y_end))
draw.line(line, fill=(200,200,200)) # <----- light gray RGB color
draw.text((x, y_start),str(li),'rgb(0,0,0)') #,font=font)
li+=1
x_start = 0
x_end = im.width
li = 0
for y in range(0, im.height, step_size):
line = ((x_start, y), (x_end, y))
draw.line(line, fill=(128,128,128)) # <----- gray RGB color
draw.text((x_start, y),str(li),'rgb(0,0,0)') #,font=font)
li+=1
del draw
return im
pics_path = os.path.join(path,"*.png")
pics = glob.glob(pics_path)
pics_arr = []
for i in pics:
pics_arr.append(add_grid(i))
# save gridded images
for i, pict in enumerate(pics_arr):
pict.save(out +"\test" + str(i) + ".png")
# combine the gridded images into the one tif
pics_path = os.path.join(out, "*.png")
pics = glob.glob(pics_path)
images = [Image.open(x) for x in pics]
widths, heights = zip(*(i.size for i in images))
total_width = sum(widths)
max_height = max(heights)
new_im = Image.new('RGB', (total_width, max_height))
x_offset = 0
for im in images:
new_im.paste(im, (x_offset,0))
x_offset += im.size[0]
new_im.save(out + '/test.tif')
Input:
Step 1:
Output:
I'm working on a project where I need to concatenate a lot of images (80282). Each image is 256 x 256 pixels, and some of the files are empty (no image), so I need to create a blank image to replace the file. I have the data in this format: data-D0H0-X52773-Y14041
X and Y correspond to the coordinates that I need to concatenate in order. The order is from the top left X52773-Y14314 to the bottom right X52964-Y14041. It is 294 iterations on X and 274 on Y. Here is the code I have written which is not working correctly, I could use any help if you have an idea, currently, my images are not well aligned on Y. For example, the image X10-Y10 is not under the image X10-Y11 as it should. I think I have some problem using correctly the try: and except:
Thanks for you help !
from PIL import Image
width = 75264
height = 70144
new_im = Image.new('RGBA', (75264, 70144))
x_offset = 0
y_offset = 0
coordinate = {}
coordinate['x']=52672
coordinate['y']=14314
#top image line should be from: X52,672-Y14,314 to X52,965-Y14,314
#bottom image line should be from: X52,672-Y14,041 to X52,965-Y14,041
for irow in range(0, 274):
for icol in range(0, 294):
try:
if (x_offset == width):
coordinate['y'] = coordinate['y'] - 1
coordinate['x'] = 52672
img = Image.open("data-D0H0-X"+str(coordinate['x'])+"-Y"+str(coordinate['y'])+".png")
except:
coordinate['x'] = coordinate['x'] + 1
blank = Image.new('RGBA', (256,256))
new_im.paste(blank, (x_offset, y_offset))
x_offset += 256
if (x_offset == width):
x_offset = 0
y_offset += 256
break
new_im.paste(img, (x_offset, y_offset))
x_offset += 256
if (x_offset == width):
x_offset = 0
y_offset += 256
coordinate['x'] = coordinate['x'] + 1
new_im.show()
new_im.save('full_image.png')
EDIT:
Here is the new code I've modified according to your answer. However, I'm still getting an error:
struct.error: 'I' format requires 0 <= number <= 4294967295
Not sure if my coordinate calcul is right now.
CODE:
from PIL import Image
import glob
import imghdr
width = 75264
height = 70144
new_im = Image.new('RGBA', (width, height))
for filename in glob.glob('data-D0H0-X*.png'):
tmp_arr = filename.split('-')
x_coord = int(tmp_arr[2][1:6])
y_coord = int(tmp_arr[3][1:6])
info = imghdr.what(filename)
if (info == "png"):
new_img = Image.open(filename)
else:
new_img = Image.new('RGBA', (256,256))
x_coord = (x_coord-52672)*256
y_coord = (14314-y_coord)*256
print x_coord, y_coord
new_im.paste(new_img, (x_coord, y_coord))
new_im.show()
new_im.save('full_image.png')
Your coordinate arithmetic seems a bit off. Since your images are 256x256 you should never have to inc/dec x and y by 1 as you do in your code.
The code below hasn't been tested but should provide a general outline.
from PIL import Image
import glob
width = 75264
height = 70144
new_im = Image.new('RGBA', (width, height))
for filename in glob.glob('data-D0H0-X*.png'):
tmp_arr = filename.split('-')
x_coord = int(tmp_arr[2][1:])
y_coord = int(tmp_arr[3][1:])
small_img = Image.open(filename)
new_im.paste(small_img, (x_coord, y_coord))
new_im.show()
new_im.save('full_image.png')
I am trying to horizontally combine some JPEG images in Python.
Problem
I have 3 images - each is 148 x 95 - see attached. I just made 3 copies of the same image - that is why they are the same.
My attempt
I am trying to horizontally join them using the following code:
import sys
from PIL import Image
list_im = ['Test1.jpg','Test2.jpg','Test3.jpg']
# creates a new empty image, RGB mode, and size 444 by 95
new_im = Image.new('RGB', (444,95))
for elem in list_im:
for i in xrange(0,444,95):
im=Image.open(elem)
new_im.paste(im, (i,0))
new_im.save('test.jpg')
However, this is producing the output attached as test.jpg.
Question
Is there a way to horizontally concatenate these images such that the sub-images in test.jpg do not have an extra partial image showing?
Additional Information
I am looking for a way to horizontally concatenate n images. I would like to use this code generally so I would prefer to:
not to hard-code image dimensions, if possible
specify dimensions in one line so that they can be easily changed
You can do something like this:
import sys
from PIL import Image
images = [Image.open(x) for x in ['Test1.jpg', 'Test2.jpg', 'Test3.jpg']]
widths, heights = zip(*(i.size for i in images))
total_width = sum(widths)
max_height = max(heights)
new_im = Image.new('RGB', (total_width, max_height))
x_offset = 0
for im in images:
new_im.paste(im, (x_offset,0))
x_offset += im.size[0]
new_im.save('test.jpg')
Test1.jpg
Test2.jpg
Test3.jpg
test.jpg
The nested for for i in xrange(0,444,95): is pasting each image 5 times, staggered 95 pixels apart. Each outer loop iteration pasting over the previous.
for elem in list_im:
for i in xrange(0,444,95):
im=Image.open(elem)
new_im.paste(im, (i,0))
new_im.save('new_' + elem + '.jpg')
I would try this:
import numpy as np
import PIL
from PIL import Image
list_im = ['Test1.jpg', 'Test2.jpg', 'Test3.jpg']
imgs = [ Image.open(i) for i in list_im ]
# pick the image which is the smallest, and resize the others to match it (can be arbitrary image shape here)
min_shape = sorted( [(np.sum(i.size), i.size ) for i in imgs])[0][1]
imgs_comb = np.hstack([i.resize(min_shape) for i in imgs])
# save that beautiful picture
imgs_comb = Image.fromarray( imgs_comb)
imgs_comb.save( 'Trifecta.jpg' )
# for a vertical stacking it is simple: use vstack
imgs_comb = np.vstack([i.resize(min_shape) for i in imgs])
imgs_comb = Image.fromarray( imgs_comb)
imgs_comb.save( 'Trifecta_vertical.jpg' )
It should work as long as all images are of the same variety (all RGB, all RGBA, or all grayscale). It shouldn't be difficult to ensure this is the case with a few more lines of code. Here are my example images, and the result:
Test1.jpg
Test2.jpg
Test3.jpg
Trifecta.jpg:
Trifecta_vertical.jpg
Edit: DTing's answer is more applicable to your question since it uses PIL, but I'll leave this up in case you want to know how to do it in numpy.
Here is a numpy/matplotlib solution that should work for N images (only color images) of any size/shape.
import numpy as np
import matplotlib.pyplot as plt
def concat_images(imga, imgb):
"""
Combines two color image ndarrays side-by-side.
"""
ha,wa = imga.shape[:2]
hb,wb = imgb.shape[:2]
max_height = np.max([ha, hb])
total_width = wa+wb
new_img = np.zeros(shape=(max_height, total_width, 3))
new_img[:ha,:wa]=imga
new_img[:hb,wa:wa+wb]=imgb
return new_img
def concat_n_images(image_path_list):
"""
Combines N color images from a list of image paths.
"""
output = None
for i, img_path in enumerate(image_path_list):
img = plt.imread(img_path)[:,:,:3]
if i==0:
output = img
else:
output = concat_images(output, img)
return output
Here is example use:
>>> images = ["ronda.jpeg", "rhod.jpeg", "ronda.jpeg", "rhod.jpeg"]
>>> output = concat_n_images(images)
>>> import matplotlib.pyplot as plt
>>> plt.imshow(output)
>>> plt.show()
Here is a function generalizing previous approaches, creating a grid of images in PIL:
from PIL import Image
import numpy as np
def pil_grid(images, max_horiz=np.iinfo(int).max):
n_images = len(images)
n_horiz = min(n_images, max_horiz)
h_sizes, v_sizes = [0] * n_horiz, [0] * (n_images // n_horiz)
for i, im in enumerate(images):
h, v = i % n_horiz, i // n_horiz
h_sizes[h] = max(h_sizes[h], im.size[0])
v_sizes[v] = max(v_sizes[v], im.size[1])
h_sizes, v_sizes = np.cumsum([0] + h_sizes), np.cumsum([0] + v_sizes)
im_grid = Image.new('RGB', (h_sizes[-1], v_sizes[-1]), color='white')
for i, im in enumerate(images):
im_grid.paste(im, (h_sizes[i % n_horiz], v_sizes[i // n_horiz]))
return im_grid
It will shrink each row and columns of the grid to the minimum. You can have only a row by using pil_grid(images), or only a column by using pil_grid(images, 1).
One benefit of using PIL over numpy-array based solutions is that you can deal with images structured differently (like grayscale or palette-based images).
Example outputs
def dummy(w, h):
"Produces a dummy PIL image of given dimensions"
from PIL import ImageDraw
im = Image.new('RGB', (w, h), color=tuple((np.random.rand(3) * 255).astype(np.uint8)))
draw = ImageDraw.Draw(im)
points = [(i, j) for i in (0, im.size[0]) for j in (0, im.size[1])]
for i in range(len(points) - 1):
for j in range(i+1, len(points)):
draw.line(points[i] + points[j], fill='black', width=2)
return im
dummy_images = [dummy(20 + np.random.randint(30), 20 + np.random.randint(30)) for _ in range(10)]
pil_grid(dummy_images):
pil_grid(dummy_images, 3):
pil_grid(dummy_images, 1):
Based on DTing's answer I created a function that is easier to use:
from PIL import Image
def append_images(images, direction='horizontal',
bg_color=(255,255,255), aligment='center'):
"""
Appends images in horizontal/vertical direction.
Args:
images: List of PIL images
direction: direction of concatenation, 'horizontal' or 'vertical'
bg_color: Background color (default: white)
aligment: alignment mode if images need padding;
'left', 'right', 'top', 'bottom', or 'center'
Returns:
Concatenated image as a new PIL image object.
"""
widths, heights = zip(*(i.size for i in images))
if direction=='horizontal':
new_width = sum(widths)
new_height = max(heights)
else:
new_width = max(widths)
new_height = sum(heights)
new_im = Image.new('RGB', (new_width, new_height), color=bg_color)
offset = 0
for im in images:
if direction=='horizontal':
y = 0
if aligment == 'center':
y = int((new_height - im.size[1])/2)
elif aligment == 'bottom':
y = new_height - im.size[1]
new_im.paste(im, (offset, y))
offset += im.size[0]
else:
x = 0
if aligment == 'center':
x = int((new_width - im.size[0])/2)
elif aligment == 'right':
x = new_width - im.size[0]
new_im.paste(im, (x, offset))
offset += im.size[1]
return new_im
It allows choosing a background color and image alignment. It's also easy to do recursion:
images = map(Image.open, ['hummingbird.jpg', 'tiger.jpg', 'monarch.png'])
combo_1 = append_images(images, direction='horizontal')
combo_2 = append_images(images, direction='horizontal', aligment='top',
bg_color=(220, 140, 60))
combo_3 = append_images([combo_1, combo_2], direction='vertical')
combo_3.save('combo_3.png')
If all image's heights are same,
import numpy as np
imgs = ['a.jpg', 'b.jp', 'c.jpg']
concatenated = Image.fromarray(
np.concatenate(
[np.array(Image.open(x)) for x in imgs],
axis=1
)
)
Maybe you can resize images before the concatenation like this,
import numpy as np
imgs = ['a.jpg', 'b.jpg', 'c.jpg']
concatenated = Image.fromarray(
np.concatenate(
[np.array(Image.open(x).resize((640,480)) for x in imgs],
axis=1
)
)
Here's my solution:
from PIL import Image
def join_images(*rows, bg_color=(0, 0, 0, 0), alignment=(0.5, 0.5)):
rows = [
[image.convert('RGBA') for image in row]
for row
in rows
]
heights = [
max(image.height for image in row)
for row
in rows
]
widths = [
max(image.width for image in column)
for column
in zip(*rows)
]
tmp = Image.new(
'RGBA',
size=(sum(widths), sum(heights)),
color=bg_color
)
for i, row in enumerate(rows):
for j, image in enumerate(row):
y = sum(heights[:i]) + int((heights[i] - image.height) * alignment[1])
x = sum(widths[:j]) + int((widths[j] - image.width) * alignment[0])
tmp.paste(image, (x, y))
return tmp
def join_images_horizontally(*row, bg_color=(0, 0, 0), alignment=(0.5, 0.5)):
return join_images(
row,
bg_color=bg_color,
alignment=alignment
)
def join_images_vertically(*column, bg_color=(0, 0, 0), alignment=(0.5, 0.5)):
return join_images(
*[[image] for image in column],
bg_color=bg_color,
alignment=alignment
)
For these images:
images = [
[Image.open('banana.png'), Image.open('apple.png')],
[Image.open('lime.png'), Image.open('lemon.png')],
]
Results will look like:
join_images(
*images,
bg_color='green',
alignment=(0.5, 0.5)
).show()
join_images(
*images,
bg_color='green',
alignment=(0, 0)
).show()
join_images(
*images,
bg_color='green',
alignment=(1, 1)
).show()
There is also skimage.util.montage to create a montage of images of the same shape:
import numpy as np
import PIL
from PIL import Image
from skimage.util import montage
list_im = ['Test1.jpg', 'Test2.jpg', 'Test3.jpg']
imgs = [ np.array(Image.open(i)) for i in list_im ]
montage(imgs)
"""
merge_image takes three parameters first two parameters specify
the two images to be merged and third parameter i.e. vertically
is a boolean type which if True merges images vertically
and finally saves and returns the file_name
"""
def merge_image(img1, img2, vertically):
images = list(map(Image.open, [img1, img2]))
widths, heights = zip(*(i.size for i in images))
if vertically:
max_width = max(widths)
total_height = sum(heights)
new_im = Image.new('RGB', (max_width, total_height))
y_offset = 0
for im in images:
new_im.paste(im, (0, y_offset))
y_offset += im.size[1]
else:
total_width = sum(widths)
max_height = max(heights)
new_im = Image.new('RGB', (total_width, max_height))
x_offset = 0
for im in images:
new_im.paste(im, (x_offset, 0))
x_offset += im.size[0]
new_im.save('test.jpg')
return 'test.jpg'
from __future__ import print_function
import os
from pil import Image
files = [
'1.png',
'2.png',
'3.png',
'4.png']
result = Image.new("RGB", (800, 800))
for index, file in enumerate(files):
path = os.path.expanduser(file)
img = Image.open(path)
img.thumbnail((400, 400), Image.ANTIALIAS)
x = index // 2 * 400
y = index % 2 * 400
w, h = img.size
result.paste(img, (x, y, x + w, y + h))
result.save(os.path.expanduser('output.jpg'))
Output
Just adding to the solutions already suggested. Assumes same height, no resizing.
import sys
import glob
from PIL import Image
Image.MAX_IMAGE_PIXELS = 100000000 # For PIL Image error when handling very large images
imgs = [ Image.open(i) for i in list_im ]
widths, heights = zip(*(i.size for i in imgs))
total_width = sum(widths)
max_height = max(heights)
new_im = Image.new('RGB', (total_width, max_height))
# Place first image
new_im.paste(imgs[0],(0,0))
# Iteratively append images in list horizontally
hoffset=0
for i in range(1,len(imgs),1):
**hoffset=imgs[i-1].size[0]+hoffset # update offset**
new_im.paste(imgs[i],**(hoffset,0)**)
new_im.save('output_horizontal_montage.jpg')
my solution would be :
import sys
import os
from PIL import Image, ImageFilter
from PIL import ImageFont
from PIL import ImageDraw
os.chdir('C:/Users/Sidik/Desktop/setup')
print(os.getcwd())
image_list= ['IMG_7292.jpg','IMG_7293.jpg','IMG_7294.jpg', 'IMG_7295.jpg' ]
image = [Image.open(x) for x in image_list] # list
im_1 = image[0].rotate(270)
im_2 = image[1].rotate(270)
im_3 = image[2].rotate(270)
#im_4 = image[3].rotate(270)
height = image[0].size[0]
width = image[0].size[1]
# Create an empty white image frame
new_im = Image.new('RGB',(height*2,width*2),(255,255,255))
new_im.paste(im_1,(0,0))
new_im.paste(im_2,(height,0))
new_im.paste(im_3,(0,width))
new_im.paste(im_4,(height,width))
draw = ImageDraw.Draw(new_im)
font = ImageFont.truetype('arial',200)
draw.text((0, 0), '(a)', fill='white', font=font)
draw.text((height, 0), '(b)', fill='white', font=font)
draw.text((0, width), '(c)', fill='white', font=font)
#draw.text((height, width), '(d)', fill='white', font=font)
new_im.show()
new_im.save('BS1319.pdf')
[![Laser spots on the edge][1]][1]
#**How to merge cropped images back to original image**
images = [Image.open(x) for x in images_list]
print("Length:: ", len(images))
widths, heights = zip(*(i.size for i in images))
print(widths, heights)
total_width = sum(widths)
max_height = sum(heights)
print(total_width,max_height)
new_im = Image.new('RGB', (5*384, 5*216))
x_offset = 0
y_offset = 0
img_size = [384,216]
def grouped(iterable, n):
return zip(*[iter(iterable)]*n)
for x,y,a,b,c in grouped(images, 5):
temp = []
temp.append([x,y,a,b,c])
print(temp[0])
print(len(temp[0]))
for lsingle_img in temp[0]:
# print(lsingle_img)
print("x_y_offset: ", (x_offset, y_offset))
new_im.paste(lsingle_img, (x_offset, y_offset))
x_offset += img_size[0]
temp = []
x_offset = 0
y_offset += img_size[1]
new_im.save('test.jpg')