Reputation: 341
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.
Upvotes: 1
Views: 5327
Reputation: 5095
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
Upvotes: 0
Reputation: 3096
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()
Upvotes: 1
Reputation: 305
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()
Upvotes: 1
Reputation: 207678
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
Upvotes: 4
Reputation: 203
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.
Upvotes: 1