Python code to process and summarize images
New here? Learn about Bountify and follow @bountify to get notified of new bounties! x

We did a quick POC earlier in javascript and it worked amazingly well.

https://bountify.co/process-an-image-with-javascript-and-output-image-and-data

Now we want to implement this in python and have it process an image or folder of images.

https://jsfiddle.net/jo1u5r6p/2/

Output should be as follows. In the same path as the image/s we will make a folder for each unique image

/image name/

Then inside of it we will copy the original image there.

/image name/image.jpg-or-png

We will have a folder with all of the tiles that we generated

/image name/tiles/

And inside of that we put all of the image tiles

Finally we need the output json in the root like this

/image name/results.json

I think the lib for this in python is pillow but let me know if you have another high performance method for this.

Machine running the python will be windows

awarded to kerncy
Tags
python3

Crowdsource coding tasks.

2 Solutions


Hey Qdev, I'm a little late, but posting my solution anyway.

It's using the suggested Pillow library.

Usage:

python py-quantize-image.py INPUT --size TILE_SIZE E.g.:
python py-quantize-image.py demo.jpg --size 5x5

Arguments:

INPUT: Image file or a folder containing image files. REQUIRED

-s/--size: Count of the tiles separated by x. E.g.: 4x4. Default: 5x5


The output tile filenames are in the format of ROWxCOLUMN.extension. E.g. 1x1.jpg. Results JSON file is generated to match the one in the other bounty.

Source:

#!/usr/bin/env python
import os
import json
import shutil
from argparse import ArgumentParser

from PIL import Image

parser = ArgumentParser()
parser.add_argument(dest='input', help='Image file or a folder containing image files', metavar='INPUT')
parser.add_argument('-s', '--size', dest='size', help='Number of tiles e.g.: 5x5', default="5x5", type=str)
args = parser.parse_args()

def process_image(image_path, xTiles, yTiles):
    basename = os.path.basename(image_path)
    filename, extension = os.path.splitext(basename)
    directory, _ = os.path.split(os.path.abspath(image_path))
    resultsPath = os.path.join(directory, filename)
    tilesPath = os.path.join(directory, filename, "tiles")

    if not os.path.exists(resultsPath):
        os.makedirs(resultsPath)

    if not os.path.exists(tilesPath):
        os.makedirs(tilesPath)

    copyPath = os.path.join(resultsPath, filename + extension)
    shutil.copy2(image_path, copyPath)

    with Image.open(image_path) as img:
        width, height = img.size
        tileWidth = width / xTiles
        tileHeight = height / yTiles

        results = []

        for y in range(yTiles):
            for x in range(xTiles):
                cropLeft = tileWidth * x
                cropTop = tileHeight * y
                cropRight = cropLeft + tileWidth
                cropBottom = cropTop + tileHeight
               cropRect = (cropLeft, cropTop, cropRight, cropBottom)
                tileImg = img.crop(cropRect)
                tileImgPath = os.path.join(tilesPath, "%dx%d%s" % (y + 1, x + 1, extension))
                tileImg.save(tileImgPath)

                tileR, tileG, tileB = tileImg.resize((1, 1), Image.NEAREST).getpixel((0, 0))

                results.append({ "col": x + 1, "row": y + 1, "red": tileR, "green": tileG, "blue": tileB })

        resultsJsonPath = os.path.join(resultsPath, "results.json")
        with open(resultsJsonPath, "w") as f:
            json.dump(results, f)

if __name__ == '__main__':
    xTiles = 5
    yTiles = 5
    tileSizes = args.size.split("x")
    if len(tileSizes) != 2:
        print("Incorrect tile size passed, defaulting to '5x5'")
    else:
        xTiles = int(tileSizes[0])
        yTiles = int(tileSizes[1])

    input_uri = args.input

        if os.path.isfile(input_uri):
        process_image(input_uri, xTiles, yTiles)
    elif os.path.isdir(input_uri):
        for f in os.listdir(input_uri):
            if f.endswith((".jpg", ".jpeg", ".png")):
                filePath = os.path.join(input_uri, f)
                process_image(filePath, xTiles, yTiles)
    else:
        print("The provided input doesn't appear to be a file or a folder")
Winning solution

Hi

Here is a solution, you will need to install PIL (Python Image Library). You can do this following those steps :
https://wp.stolaf.edu/it/installing-pil-pillow-cimage-on-windows-and-mac/

and here is your code : (for information, I have only tested it on a linux environement)

from PIL import Image

import glob
import sys
import os
import json

if len(sys.argv) != 5:
    print("Usage python splitter.py input_path output_path nbCol nbRow")
    exit(0)

def split(input_image, output_path, nbCol, nbRow, format):
    print("=> processing %s" % input_image)
    #get filename
    filename = os.path.basename(input_image)

    #prepare output
    outputFolder = os.path.splitext(filename)[0]    


    #load image
    im = Image.open(input_image)
    #get image dimension    
    imgwidth, imgheight = im.size

    #compute tile size
    width = int(imgwidth / nbCol)
    height = int(imgheight / nbRow)

    os.mkdir(os.path.join(output_path, outputFolder))
    os.mkdir(os.path.join(output_path, outputFolder, "tiles"))
    #copy original image to destination folder
    try:
        im.save(os.path.join(output_path, outputFolder, "%s.%s" % (filename, format)))
        print("== Image saved to : %s" % (os.path.join(output_path, outputFolder, "%s.%s" % (filename, format))))
    except:
        pass

    #generate tiles
    outputJson = []
    indexI = 0
    indexJ = 0
    for i in range(0,nbCol):
        indexJ = 0
        for j in range(0,nbRow):
            box = (i * width, j * height, (i+1)*width, (j+1)*height)
            try:
                #crop image
                a = im.crop(box)
                #resize to a 1x1 pixel is equivalent to compute average
                mean = a.resize((1,1))
                color = mean.getpixel((0,0))
                #fill tile with mean color
                a.paste(color, [0,0,width, height])
                #save image
                a.save(os.path.join(output_path,outputFolder,"tiles", "tile-%04d-%04d.%s" % (indexI, indexJ, format)))
                #generate json
                tile = {}
                tile["red"] = color[0]
                tile["green"] = color[1]
                tile["blue"] = color[2]
                tile["col"] = indexI
                tile["row"] = indexJ
                tile["filename"] = str(os.path.join(output_path,outputFolder,"tiles", "tile-%04d-%04d.%s" % (indexI, indexJ, format)))
                outputJson.append(tile)

            except:
                pass

            indexJ = indexJ + 1
        indexI = indexI + 1

    #write json
    with open(os.path.join(output_path, outputFolder, "results.json"), "w+") as f:
        json.dump(outputJson, f)

    print("<= all tiles generated in %s" % (os.path.join(output_path, outputFolder)))


for filename in glob.glob(sys.argv[1] + '/*.jpg'): #assuming jpg
    split(filename, sys.argv[2], int(sys.argv[3]), int(sys.argv[4]), "jpg")

for filename in glob.glob(sys.argv[1] + '/*.png'): #assuming png
    split(filename, sys.argv[2], int(sys.argv[3]), int(sys.argv[4]), "png")

for execution, just call your program passing 4 arguments :

  • input_folder
  • output_folder
  • colCount
  • rowCoung

output_folder must exist

Do not hesitate if you have any question.

EDIT : I have to fix a small bug for windows that was creating a unexpected row on some particular cases, but now it's tested on windows too. Should be ok now

Thanks for this, we will review it today
Qdev 15 days ago
Have to edit the code for a small bug on windows, should be ok now.
kerncy 15 days ago
Is your review OK ? do you need any code update or fix ?
kerncy 9 days ago
View Timeline