Wahrscheinlich weil deutlich mehr über Python dokumentiert ist als über viele andere Programmiersprachen können wohl KI-Systeme Python deutlich fehlerfreier schreiben als alles andere.
Da ich eine gewissermaßen große Sammlung Bilder habe brauchte ich ein Triage Tool um zu entscheiden ob Bilder gut sind oder nicht. Auch weil ich damit Datensets für KI-Systeme schneller basteln kann.
Hier ist der Code:
import os
import shutil
import tkinter as tk
from tkinter import filedialog, ttk, messagebox
from PIL import Image, ImageTk
Image.MAX_IMAGE_PIXELS = None # Remove decompression bomb protection
class ImageReviewTool:
def __init__(self, master):
self.master = master
master.title("Image Triage Tool")
master.geometry("800x600") # Set initial window size
master.minsize(400, 300) # Set minimum window size
# Create the main frame
self.frame = tk.Frame(master)
self.frame.pack(fill=tk.BOTH, expand=True)
# Create the image display area
self.image_label = tk.Label(self.frame)
self.image_label.pack(fill=tk.BOTH, expand=True)
# Create frame for file handling options
options_frame = tk.Frame(self.frame)
options_frame.pack(side=tk.BOTTOM, fill=tk.X)
# Create dropdown for "Good" images handling
good_options_frame = tk.Frame(options_frame)
good_options_frame.pack(side=tk.LEFT, padx=10, pady=5)
tk.Label(good_options_frame, text="Good images:").pack(side=tk.LEFT)
self.good_handling = ttk.Combobox(good_options_frame,
values=["Copy", "Move"],
state="readonly",
width=10)
self.good_handling.set("Copy")
self.good_handling.pack(side=tk.LEFT, padx=5)
# Create dropdown for "Bad" images handling
bad_options_frame = tk.Frame(options_frame)
bad_options_frame.pack(side=tk.LEFT, padx=10, pady=5)
tk.Label(bad_options_frame, text="Bad images:").pack(side=tk.LEFT)
self.bad_handling = ttk.Combobox(bad_options_frame,
values=["Copy", "Move"],
state="readonly",
width=10)
self.bad_handling.set("Move")
self.bad_handling.pack(side=tk.LEFT, padx=5)
# Create the "Good", "Bad", and "Skip" buttons
button_frame = tk.Frame(self.frame)
button_frame.pack(side=tk.BOTTOM, fill=tk.X)
self.good_button = tk.Button(button_frame, text="Good (1)", command=self.handle_good)
self.good_button.pack(side=tk.LEFT, padx=10, pady=10)
self.bad_button = tk.Button(button_frame, text="Bad (0)", command=self.handle_bad)
self.bad_button.pack(side=tk.LEFT, padx=10, pady=10)
self.skip_button = tk.Button(button_frame, text="Skip (#)", command=self.handle_skip)
self.skip_button.pack(side=tk.LEFT, padx=10, pady=10)
# Create the directory selection buttons
dir_button_frame = tk.Frame(self.frame)
dir_button_frame.pack(side=tk.BOTTOM, fill=tk.X)
self.select_directory_button = tk.Button(dir_button_frame, text="Select Directory", command=self.select_directory)
self.select_directory_button.pack(side=tk.LEFT, padx=10, pady=10)
self.select_good_directory_button = tk.Button(dir_button_frame, text="Select Good Directory", command=self.select_good_directory)
self.select_good_directory_button.pack(side=tk.LEFT, padx=10, pady=10)
self.select_bad_directory_button = tk.Button(dir_button_frame, text="Select Bad Directory", command=self.select_bad_directory)
self.select_bad_directory_button.pack(side=tk.LEFT, padx=10, pady=10)
# Add current file label
self.file_label = tk.Label(self.frame, text="")
self.file_label.pack(side=tk.BOTTOM, pady=5)
self.current_directory = None
self.good_folder = None
self.bad_folder = None
self.current_image_index = 0
self.image_files = []
self.reviewed_images = []
self.log_file = "image_review_log.txt"
self.current_image = None
# Bind the window resize event to the resize_image method
self.master.bind("<Configure>", self.resize_image)
# Bind keyboard shortcuts
self.master.bind("1", self.handle_good)
self.master.bind("0", self.handle_bad)
self.master.bind("#", self.handle_skip)
def select_directory(self):
self.current_directory = filedialog.askdirectory()
if self.current_directory:
self.image_files = [f for f in os.listdir(self.current_directory) if f.lower().endswith((".jpg", ".jpeg", ".png", ".gif"))]
self.current_image_index = 0
self.reviewed_images = self.load_reviewed_images()
self.display_image()
def select_good_directory(self):
self.good_folder = filedialog.askdirectory()
def select_bad_directory(self):
self.bad_folder = filedialog.askdirectory()
def display_image(self):
if self.current_directory and self.image_files:
if self.current_image_index >= len(self.image_files):
self.image_label.configure(image='')
self.file_label.config(text="")
messagebox.showinfo("Complete", "All images have been reviewed!")
return
try:
image_path = os.path.join(self.current_directory, self.image_files[self.current_image_index])
self.current_image = Image.open(image_path)
# Update file info label
img_size = os.path.getsize(image_path) / (1024 * 1024) # Convert to MB
self.file_label.config(
text=f"File: {self.image_files[self.current_image_index]} | "
f"Size: {img_size:.1f}MB | "
f"Dimensions: {self.current_image.width}x{self.current_image.height}"
)
self.resize_image(None)
except Exception as e:
messagebox.showerror("Error", f"Failed to load image: {str(e)}")
self.current_image_index += 1
self.display_image()
def resize_image(self, event):
if self.current_image:
try:
# Get the current window size
window_width = self.master.winfo_width()
window_height = self.master.winfo_height() - 150 # Adjust for buttons and options
# Scale the image to fit the window while maintaining aspect ratio
image_ratio = self.current_image.width / self.current_image.height
if window_width / window_height > image_ratio:
new_height = window_height
new_width = int(new_height * image_ratio)
else:
new_width = window_width
new_height = int(new_width / image_ratio)
# Use thumbnail for memory-efficient resizing of large images
img_copy = self.current_image.copy()
img_copy.thumbnail((new_width, new_height), Image.Resampling.LANCZOS)
self.photo = ImageTk.PhotoImage(img_copy)
self.image_label.configure(image=self.photo)
except Exception as e:
messagebox.showerror("Error", f"Failed to resize image: {str(e)}")
def handle_good(self, event=None):
if self.current_directory and self.good_folder and self.image_files:
src_path = os.path.join(self.current_directory, self.image_files[self.current_image_index])
dst_path = os.path.join(self.good_folder, self.image_files[self.current_image_index])
try:
if self.good_handling.get() == "Copy":
shutil.copy2(src_path, dst_path)
else: # Move
shutil.move(src_path, dst_path)
self.reviewed_images.append(self.image_files[self.current_image_index])
self.save_reviewed_images()
self.current_image_index += 1
self.display_image()
except Exception as e:
messagebox.showerror("Error", f"Failed to process image: {str(e)}")
def handle_bad(self, event=None):
if self.current_directory and self.bad_folder and self.image_files:
src_path = os.path.join(self.current_directory, self.image_files[self.current_image_index])
dst_path = os.path.join(self.bad_folder, self.image_files[self.current_image_index])
try:
if self.bad_handling.get() == "Copy":
shutil.copy2(src_path, dst_path)
else: # Move
shutil.move(src_path, dst_path)
self.reviewed_images.append(self.image_files[self.current_image_index])
self.save_reviewed_images()
self.current_image_index += 1
self.display_image()
except Exception as e:
messagebox.showerror("Error", f"Failed to process image: {str(e)}")
def handle_skip(self, event=None):
if self.current_directory and self.image_files:
self.current_image_index += 1
self.display_image()
def load_reviewed_images(self):
reviewed_images = []
if os.path.exists(self.log_file):
with open(self.log_file, "r") as f:
reviewed_images = [line.strip() for line in f.readlines()]
return reviewed_images
def save_reviewed_images(self):
with open(self.log_file, "w") as f:
for image in self.reviewed_images:
f.write(image + "\n")
if __name__ == "__main__":
root = tk.Tk()
app = ImageReviewTool(root)
root.mainloop()
Kopiert das Ding in eine .py Datei und führt das Ding dann mit python.exe aus… Natürlich solltet ihr verstehen (oder euch halt von der KI erklären lassen) was das Ding hier tut.
Erst die Good und Bad Ordner wählen, dann den Ordner mit Bildern die ihr sortieren müsst.
Keyboard-Bedienung geht auch 1 ist für gutes, 0 ist für schlechtes und # ist fürs Überspringen.
Sicher noch nicht das Beste, aber brauchbar. Der ganze Code kommt nahezu vollständig aus Claude.ai.
Viel Spaß damit, vielleicht könnt ihr es ja brauchen.