[Python] I made an image viewer with a simple sorting function.

background

I have to classify a large number of images at work. I feel that free software can be used for efficient sorting, but unfortunately we prohibit free software in principle. Then, in the spirit of making it, I made ** an image viewer with a simple sorting function **.

Finished image

Example: A dog image that has been confused in a cat image is extracted with a label "1". 仕分けツールデモ_.gif

  1. After pressing the "Load" button, specify the folder. Then, all the images under the directory including the subdirectory are read.
  2. Send the image with "←" and "→", and press the number keys to label the pressed key number on the displayed image. (Labeling = the entered key (number) is displayed under the file name. By the way, press 0 to cancel labeling)
  3. Execute folder division based on the contents labeled with the "Assort execution" button.
  4. After that, you can open the folder of the loaded image file by pressing the "Open Folder" button.

Full code

Click here for the main body of the sorting tool ↓

** Sorting tool ** (click to view code)

Sorting tool.py


from file_walker import folder_walker  #Self-made function
from folder_selecter import file_selecter  #Self-made function

import os
import shutil
import subprocess
import tkinter as tk
from tkinter import Label, Tk, StringVar

from PIL import Image, ImageTk  #External library


#File reading- - - - - - - - - - - - - - - - - - - - - - - -
def load_file(event):

    global img_num, item, dir_name

    #Read the file
    tex_var.set("Reading a file...")
    dir_name = file_selecter(dir_select=True)
    if not dir_name == None:
        file_list = folder_walker(dir_name)

    #List the images that can be read from the file
    for f in file_list:
        try:
            img_lst.append(Image.open(f))
            filename_lst.append(f)
        except:
            pass

    #Redefine canvas size to fit window size
    window_resize()

    #Image conversion
    for f in img_lst:

        #Resized to fit inside the canvas
        resized_img = img_resize_for_canvas(f, image_canvas)

        #Image conversion so that it can be displayed with tkinter
        tk_img_lst.append(ImageTk.PhotoImage(
            image=resized_img, master=image_canvas))

    #Get the center of the canvas
    c_width_half = round(int(image_canvas["width"]) / 2)
    c_height_half = round(int(image_canvas["height"]) / 2)

    #Display on canvas
    img_num = 0
    item = image_canvas.create_image(
        c_width_half, c_height_half,  image=tk_img_lst[0], anchor=tk.CENTER)
    #Label rewriting
    tex_var.set(filename_lst[img_num])

    #Hide the load button
    load_btn.pack_forget()
    #Arrangement of sorting execution buttons
    assort_btn.pack()

#To the next image- - - - - - - - - - - - - - - - - - - - - - - -
def next_img(event):
    global img_num

    #Get the number of images being loaded
    img_count = len(tk_img_lst)

    #Determine if the image is not the last
    if img_num >= img_count - 1:
        pass
    else:
        #Image No. being displayed.Update and display
        img_num += 1
        image_canvas.itemconfig(item, image=tk_img_lst[img_num])
        #Label rewriting
        tex_var.set(filename_lst[img_num])
        #Show labeling
        if filename_lst[img_num] in assort_dict:
            assort_t_var.set(assort_dict[filename_lst[img_num]])
        else:
            assort_t_var.set("")


#To the previous image- - - - - - - - - - - - - - - - - - - - - - - -
def prev_img(event):
    global img_num

    #Determine if the image is not the first
    if img_num <= 0:
        pass
    else:
        #Image No. being displayed.Update and display
        img_num -= 1
        image_canvas.itemconfig(item, image=tk_img_lst[img_num])
        #Label rewriting
        tex_var.set(filename_lst[img_num])
        #Show labeling
        if filename_lst[img_num] in assort_dict:
            assort_t_var.set(assort_dict[filename_lst[img_num]])
        else:
            assort_t_var.set("")


#Redefine canvas size from window size- - - - - - - - - - - - - - - - -
def window_resize():

    image_canvas["width"] = image_canvas.winfo_width()
    image_canvas["height"] = image_canvas.winfo_height()


#Shrink the image to fit the canvas size- - - - - - - - - - - - - - - - - - - -
def img_resize_for_canvas(img, canvas, expand=False):

    size_retio_w = int(canvas["width"]) / img.width
    size_retio_h = int(canvas["height"]) / img.height

    if expand == True:
        size_retio = min(size_retio_w, size_retio_h)
    else:
        size_retio = min(size_retio_w, size_retio_h, 1)

    resized_img = img.resize((round(img.width * size_retio),
                              round(img.height * size_retio)))
    return resized_img

#Image display- - - - - - - - - - - - - - - - - - - - - - - -
def image_show(event):
    img_lst[img_num].show()


#Labeling for images- - - - - - - - - - - - - - - - - - - - - - - -
def file_assort(event):

    if str(event.keysym) in ["1", "2", "3", "4", "5", "6", "7", "8", "9"]:
        assort_dict[filename_lst[img_num]] = str(event.keysym)
    elif str(event.keysym) == "0":
        del assort_dict[filename_lst[img_num]]

    #Show labeling
    if filename_lst[img_num] in assort_dict:
        assort_t_var.set(assort_dict[filename_lst[img_num]])
    else:
        assort_t_var.set("")

    print(assort_dict[filename_lst[img_num]])


#Folder division execution- - - - - - - - - - - - - - - - - - - - - - - -
def assort_go(event):

    global f_dir

    for f in assort_dict:
        #Get file names and folder names before and after sorting
        f_dir = os.path.dirname(f)
        f_basename = os.path.basename(f)
        new_dir = os.path.join(f_dir, assort_dict[f])
        new_path = os.path.join(new_dir, f_basename)

        #Directory existence check
        if not os.path.exists(new_dir):
            os.mkdir(new_dir)
        #File move execution
        shutil.move(f, new_path)
        #Show / hide various buttons
        assort_btn.pack_forget()
        open_folder_btn.pack()

        print(new_path)

#Open folder- - - - - - - - - - - - - - - - - - - - - - - -
def folder_open(event):
    #Convert path to open in Explorer
    open_dir_name = f_dir.replace("/", "\\")
    #Open in explorer
    subprocess.Popen(['explorer', open_dir_name])
    #Close tkinter window
    root.destroy()
 
    print(open_dir_name)


#Main processing-------------------------------------------------------
if __name__ == "__main__":

    #Global variables
    img_lst, tk_img_lst = [], []
    filename_lst = []
    assort_file_list = []
    assort_dict = {}

    img_num = 0
    f_basename = ""

    #tkinter drawing settings
    root = tk.Tk()

    root.title(u"Display / sorting tool")
    root.option_add("*font", ("Meiryo UI", 11))

    #Load button drawing settings
    load_btn = tk.Button(root, text="Read")
    load_btn.bind("<Button-1>", load_file)
    load_btn.pack()

    #Canvas drawing settings
    image_canvas = tk.Canvas(root,
                             width=640,
                             height=480)

    image_canvas.pack(expand=True, fill="both")

    #Sorting result display
    assort_t_var = tk.StringVar()
    assort_label = tk.Label(
        root, textvariable=assort_t_var, font=("Meiryo UI", 14))
    assort_label.pack()

    #File name label drawing settings
    tex_var = tk.StringVar()
    tex_var.set("file name")

    lbl = tk.Label(root, textvariable=tex_var, font=("Meiryo UI", 8))
    lbl["foreground"] = "gray"
    lbl.pack()

    #Operation setting to feed images with the right and left keys
    root.bind("<Key-Right>", next_img)
    root.bind("<Key-Left>", prev_img)
    # 「Ctrl」+Image display with "P"
    root.bind("<Control-Key-p>", image_show)

    #Sorting target setting with number keys
    root.bind("<Key>", file_assort)

    #Sort execution button
    assort_btn = tk.Button(root, text="Sorting execution")
    assort_btn.bind("<Button-1>", assort_go)

    #Button to open folder
    open_folder_btn = tk.Button(root,text="Open folder")
    open_folder_btn.bind("<Button-1>", folder_open)

    root.mainloop()

The created self-made function was used for the operation of specifying the folder and the operation of acquiring the file list from the specified folder. Store the following .py file in the same folder as the above sorting tool .py.

** file_selector.py ** (click to view code)

file_selector.py


import os
import sys

import tkinter as f_tk
from tkinter import filedialog

def file_selecter(ini_folder_path = str(os.path.dirname(sys.argv[0])), 
                                    multiple= False, dir_select = False):
    """
Open a dialog and select a file or folder.
If you do not specify an initial folder, the folder of the file itself is opened.
You can select folder selection and file selection (multiple / single) as options.

    Parameters
    ----------
    ini_folder_path : str
The folder to open initially. The default value is the folder path of the executable file
    multiple : bool
Whether to make multiple files selectable. The default value is False, which is a single selection.
    dir_select : bool
Folder selection mode. The default value is False, which is in file selection mode.
    """

    root_fileselect=f_tk.Tk()
    root_fileselect.withdraw()  #Hide window

    if  os.path.isfile(ini_folder_path):
        ini_folder_path = os.path.dirname(ini_folder_path) #If the file name is included in the initial folder specification, the file folder is returned.

    if dir_select:
        select_item = f_tk.filedialog.askdirectory(initialdir=ini_folder_path)  #Directory selection mode

    elif multiple:
        select_item = f_tk.filedialog.askopenfilenames(initialdir=ini_folder_path)  #File (multiple) selection mode
    else:
        select_item = f_tk.filedialog.askopenfilename(initialdir=ini_folder_path)  #File (single) selection mode

    root_fileselect.destroy()

    if not select_item =="":
        return select_item

** file_walker.py ** (click to view code)

file_walker.py


import os
import pathlib

def folder_walker(folder_path, recursive = True, file_ext = ".*"):
    """
Get the file list of the specified folder.
It can be obtained recursively or non-recursively by specifying an argument.

    Parameters
    ----------
    folder_path : str
Target folder path
    recursive : bool
Whether to get recursively. The default value is True, which is recursively acquired.
    file_ext : str
Specify the extension of the file to be read. Example:".jpg "Need a period like. The default value is".*"Not specified in
    """

    p = pathlib.Path(folder_path)

    if recursive:
        return list(p.glob("**/*" + file_ext))  # **/*Recursively get files with
    else:
        return list(p.glob("*" + file_ext))  #Do not recursively retrieve files

What I studied

Parent-child relationship in Tkinter window

I also use Tkinter's filedialog to select the folder, but at first I tried to draw the root of the main window after selecting the folder. Then, I had a hard time because the main root was not displayed well or became inactive, probably because of inheritance. After all, if I called filedialog after drawing the main root, I could draw the root without any problem.

Garbage collection concept

At first, I was impatient because the contents of the canvas and the label with tk.StringVar () embedded were not displayed at all. As a result of various investigations, python has a concept of garbage collection, and the contents of variables that are judged to be unnecessary (= inaccessible) are automatically deleted. As a result, when the canvas tried to refer to the object, it could not be drawn because its contents were lost. To avoid this, you can either load it as a global variable in advance or have an instance of the class load the object. See below for details. Reference page: [Python] What to do when objects such as images cannot be drawn with Tkinter https://daeudaeu.com/create_image_problem/

Refer to the parameters of the tkinter widget

I had a hard time finding a way to reference the parameters of the widget. After all, all I had to do was get it with ** widget name ["parameter name"] ** as shown below.


canvas_w = int(image_canvas["width"])

(In this case, since the window size was made variable, it was necessary to obtain the canvas size so that the center of the canvas could be determined.)

Impressions / future

I thought it would be easy to make because it was a simple operation, but I had a hard time unexpectedly due to lack of knowledge. In the future, I would like to make various modifications based on the one created this time to make it a more user-friendly tool. In particular,

etc.

Recommended Posts

[Python] I made an image viewer with a simple sorting function.
I made a simple blackjack with Python
I made a fortune with Python.
I made a daemon with Python
I made a simple typing game with tkinter in Python
I made a simple book application with python + Flask ~ Introduction ~
I made a character counter with Python
I made a Hex map with Python
I made a roguelike game with Python
I made a configuration file with Python
I made a neuron simulator with Python
I made a simple circuit with Python (AND, OR, NOR, etc.)
I tried to make an image similarity function with Python + OpenCV
I made a competitive programming glossary with Python
I made a weather forecast bot-like with Python.
I made a GUI application with Python + PyQt5
I made a Twitter fujoshi blocker with Python ①
[Python] I made a Youtube Downloader with Tkinter.
I made a simple Bitcoin wallet with pycoin
I made a bin picking game with Python
I made a Mattermost bot with Python (+ Flask)
I made a QR code image with CuteR
I made a Twitter BOT with GAE (python) (with a reference)
I made a Christmas tree lighting game with Python
I made blackjack with python!
I made a net news notification app with Python
Sorting image files with Python (2)
I made a familiar function that can be used in statistics with Python
Sorting image files with Python
I made a LINE BOT with Python and Heroku
I made a python text
I made blackjack with Python.
I made wordcloud with Python.
I made a package to filter time series with python
Try adding a simple image baking function as an add-on
I made a puzzle game (like) with Tkinter in Python
Cut out an image with python
I made a Line-bot using Python!
I sent an SMS with Python
[Python] I made a function that decrypts AES encryption just by throwing it with pycrypto.
I made a function to crop the image of python openCV, so please use it.
I made a library to easily read config files with Python
I made a package that can compare morphological analyzers with Python
Try to extract a character string from an image with Python3
I made a Nyanko tweet form with Python, Flask and Heroku
I made a lot of files for RDP connection with Python
I made a shuffle that can be reset (reverted) with Python
I made a poker game server chat-holdem using websocket with python
I made a segment tree with python, so I will introduce it
I made a Python wrapper library for docomo image recognition API.
Create a Python function decorator with Class
Creating a simple PowerPoint file with Python
A simple RSS reader made with Django
Create an image processing viewer with PySimpleGUI
I made a payroll program in Python!
Simple Slack API client made with Python
Create a dummy image with Python + PIL.
I drew a heatmap with seaborn [Python]
I tried a functional language with Python
What I did with a Python array
I made a life game with Numpy