-2

I have a bunch of jpeg images that I want to join in a specific position following its filename. Examples of filenames are:

7_1.jpeg --> This image should be pasted in the 7th row, 1st column

15_5.jpeg --> This image should be pasted in the 15th row, 5th column

etc

In this case, the filenames range from 1_1.jpeg to 40_20.jpeg but I would like to join any number of images.

The final image would have empty cells because not all positions have an image.

The closest post for my purposes that I found is this one: https://stackoverflow.com/a/42853439/8252488

UPDATE: I tried different option and now I can paste different images in specific positions using:

import cv2
import numpy
import glob
import os

dir = "." # current directory
ext = ".jpg" # whatever extension you want

pathname = os.path.join(dir, "*" + ext)
images = [cv2.imread(img) for img in glob.glob(pathname)]

height = sum(image.shape[0] for image in images)
width = max(image.shape[1] for image in images)
output = numpy.zeros((height,width,3))

y = 0
for image in images:
    h,w,d = image.shape
    output[y:y+h,0:w] = image
    y += h

cv2.imwrite("test.jpg", output)

from here

Now I am trying to specify the positions using filenames, as follows:

pathname = os.path.join(dir, "*" + ext)
images = [cv2.imread(img) for img in glob.glob(pathname)]
names = [img for img in glob.glob(pathname)]
#Get filenames from paths
files = [re.sub("/.*/", "", x) for x in names]
#Get positions
position = [re.sub(".jpeg", "", x) for x in files]
#Get hight position and tranform to int
height = [re.sub("_.*", "", x) for x in position]
height2 = [int(i) for i in height]
#Get width position and tranform to int
width = [re.sub(".*_", "", x) for x in position]
width2 = [int(i) for i in width]
#Generate background image
height_tot = 280*max(height2) #All images are 280*280
width_tot = 280*max(width2)
output = numpy.zeros((height_tot,width_tot,3))
#Locate images
for name, image in zip(names,images):
        files = re.sub("/.*/", "", name)
        position = re.sub(".jpeg", "", files)
        height = re.sub("_.*", "", position)
        height2 = int(height)*280
        width = re.sub(".*_", "", position)
        width2 = int(width)*280
        output[height2:height2+280,width2:width2+280] = image
cv2.imwrite("test.jpg", output)

I know its very far from clean but at this point I only want the thing working. However, I got the following error:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-70-529c37a03571> in <module>()
      6     width = re.sub(".*_", "", position)
      7     width2 = int(width)*280
----> 8     output[height2:height2+280,width2:width2+280] = image
      9 #    print(height2)
     10 cv2.imwrite("test.jpg", output)

ValueError: could not broadcast input array from shape (280,280,3) into shape (0,280,3)

Any idea what is missing? Thanks!

UPDATE2 If I change the 280 to 262 then I do not have the error but the image is not well arranged. Actually I found that the patches are actually pasted more than one time. The code:

for name, image in zip(names,images):
    h,w,d = image.shape
    files = re.sub("/.*/", "", name)
    position = re.sub(".jpeg", "", files)
    height = re.sub("_.*", "", position)
    height2 = int(height)*262
    width = re.sub(".*_", "", position)
    width2 = int(width)*262
    output[height2:height2+h,width2:width2+w,:] = image

cv2.imwrite("test.jpg", output)

Badly arranged image

Tato14
  • 425
  • 1
  • 4
  • 9
  • Did you get started with any code yet? Do you know whether the images are all the same size? – Mark Setchell May 09 '19 at 12:35
  • Sincerely, I do not have any clue how to specify the position using PIL. I thought that I could kind of build a matrix of filenames using the filename as position and then use it as list like what is done here(https://stackoverflow.com/a/30228789/8252488). Yes, all images are same size. – Tato14 May 09 '19 at 12:42
  • I guess the first step would be to work out the largest row and largest column number, and the size in pixels of the first image in order to create a large background image onto which to paste the tiles... – Mark Setchell May 09 '19 at 13:50

2 Answers2

0

After some struggling, I finally got what I was looking for. I just gonna put the code that worked for me in case anybody needs something similar. I know the code is not clean but it works :-P

pathname = os.path.join(dir, "*" + ext)
images = [cv2.imread(img) for img in glob.glob(pathname)]
names = [img for img in glob.glob(pathname)]
#Get filenames from paths
files = [re.sub("/.*/", "", x) for x in names]
#Get positions
position = [re.sub(".jpeg", "", x) for x in files]
#Get hight position and tranform to int
height = [re.sub("_.*", "", x) for x in position]
height2 = [int(i) for i in height]
#Get width position and tranform to int
width = [re.sub(".*_", "", x) for x in position]
width2 = [int(i) for i in width]
#Generate background image
height_tot = 280*max(height2) #All images are 280*280
width_tot = 280*max(width2)
output = numpy.zeros((height_tot+280,width_tot+280,3))
#Locate images
for name, image in zip(names,images):
    h,w,d = image.shape
    files = re.sub("/.*/", "", name)
    position = re.sub(".jpeg", "", files)
    height = re.sub("_.*", "", position)
    height2 = int(height)*280
    width = re.sub(".*_", "", position)
    width2 = int(width)*280
    output[width2:width2+280,height2:height2+280] = image

cv2.imwrite("test_processed.jpg", output)

You will see that the only difference with the previous code is that I added 280 in each axis because the outer patches didn't fit. I also swapped the axis for better display.

Tato14
  • 425
  • 1
  • 4
  • 9
0

libvips can do this at the command line, or from Python with pyvips.

At the command-line, try:

vips arrayjoin "$(ls *.jpeg | sort -t_ -k2g -k1g)" x.jpg --across 20

How this works:

  • The ls lists all the jpg files, one per line.
  • But they'll be in alphabetical order! If they are named as x_y.jpeg, then 10_0.jpeg will come before 1_0.jpeg, for example.
  • The sort command splits each line into fields on the _ character, then sorts numerically by the second number, then the first.
  • This puts files in order from left to right, then from top to bottom.
  • arrayjoin joins a list of tiles into a single square image.
  • You just need to give it the number of tiles across (the across param).

From Python you can do the same with:

import pyvips

tiles_across = 37
tiles_down = 37

tiles = [pyvips.Image.new_from_file(f"{x}_{y}.jpeg", access="sequential"))
         for y in range(tiles_down) for x in range(tiles_across)]
im = pyvips.Image.arrayjoin(tiles, across=tiles_across)

im.write_to_file("x.jpg")

Here's a more complete explanation, with examples and tips:

how to convert dzi files to multi-tile pyramidal tiff format

jcupitt
  • 10,213
  • 2
  • 23
  • 39