I'm trying to edit all images within a specified directory (and subdirs) using PIL, and then save the edited versions in a different location but under the same subdirectory structure.

ie. Input:

Root directory
|
|---------Subdir 1
|           |---Image_1
|           |---Image_2
|           |---Image_3
|
|---------Subdir 2
|           |---Image_1
|           |---Image_2

And Output:
{Specified directory}
|
|---------Subdir 1
|           |---Edited_Image_1
|           |---Edited_Image_2
|           |---Edited_Image_3
|
|---------Subdir 2
|           |---Edited_Image_1
|           |---Edited_Image_2

Where the subdirs share same names, and are created when os.walk is run.

I've constructed this as a function so I can later run it in a Linux terminal with options changed upon calling the function.

from PIL import Image, ImageDraw, ImageOps
import os
from os.path import isfile, join

def editimage(image_directory="image_directory",
               version="B", #Specify which version of ImageDraw edit to do
               resize=False, #choose whether or not to resize image
               new_size=(800,428), #choose new size
               output_directory="output_directory"):

    for directory, subdirs, files in os.walk(image_directory):
        for dirname in subdirs:
            if not os.path.exists(output_directory + dirname):
                os.mkdir(output_directory + image_directory + dirname) #This does exactly what I want it to do

    for directory, subdirs, files in os.walk(image_directory):
        for f in files:
            if f.endswith('.JPG'):
                img = Image.open(directory + f)
                draw= ImageDraw.Draw(img)
                if version=="A":
                    draw.ellipse((1823, 1472, 2033, 1536), fill = (0,0,0), outline=None) 
                    draw.rectangle((0, 0, 2048, 32), fill = (0,0,0), outline=None) 
                    draw.rectangle((0, 1504, 2048, 1536), fill = (0,0,0), outline=None) 
                    img=ImageOps.pad(img, size=(2731,1536), color="grey")
                elif version=="B":
                    draw.rectangle((0, 1231, 2304, 1296), fill = (0,0,0), outline=None)
                if resize==True:
                    img = img.resize(new_size)
                else:
                    pass
                img.save(output_directory + "/" + f + ".png")

This would let the user enter:

editimage("Path/to/input/", version="A", resize= False, output_directory="Path/to/output/")

I've tried using img.save(output_directory + subdirs + ".png" or creating a variable for that, but Python returns: TypeError: can only concatenate list (not "str") to list, or otherwise wants the input directory within the output directory. It doesn't seem to walk every subdirectory.

I previously used

    imagelist = [join(directory, f)
                 for directory, subdirs, files in os.walk(image_directory)
                 for f in files
                 if isfile
                 if f.endswith('.JPG')]

    for f in imagelist:
        img = Image.open(f) #etc..

to construct read and edit the images, but then img.save(f) keeps the entire path and cannot save the image to a new location.

The rest of the function works perfectly, and will overwrite images within the original directory (and subdirs) if I remove the output_directory argument in the img.save line. I had no issues until realising that the output directory wasn't being used.

Because the version A set of images are not named uniquely between folders, I need to be able to reconstruct the directory paths.

How can I fix this? Thanks!

1

There are 1 best solutions below

2
JohnM On

The function below will create a new directory for annotated images with the same tree structure as the input directory. I used pathlib from the core Python library even though you asked how to use os.walk(). I like its object-oriented methods and find that the code is cleaner. PEP428 provides more detail on the rationale behind pathlib.

from pathlib import Path

from PIL import Image, ImageDraw


def editimage(
    image_directory="image_directory",
    version="B",  # Specify which version of ImageDraw edit to do
    resize=False,  # choose whether or not to resize image
    new_size=(800, 428),  # choose new size
    output_directory="output_directory",
):
    for jpg_file in image_directory.rglob("*.jpg"):
        # Translate the input file path to the corresponding output file_path
        relative_path = jpg_file.relative_to(image_directory)
        output_file_path = output_directory / relative_path

        # Create the output directory if it doesn't exist
        output_file_path.parent.mkdir(parents=True, exist_ok=True)

        # Annotate the image and save it (simplified example)
        with Image.open(jpg_file) as img:
            draw = ImageDraw.Draw(img)
            draw.ellipse((0, 0, 300, 300), fill=(255, 0, 0), outline=None)
            img.save(output_file_path)


editimage(
    image_directory=Path("Path/to/input/"),
    version="A",
    resize=False,
    output_directory=Path("Path/to/output/"),
)