|
1 | 1 | import gradio as gr |
2 | 2 | from easygui import boolbox |
| 3 | +from PIL import Image |
3 | 4 | from .common_gui import get_folder_path, scriptdir, list_dirs |
4 | 5 | from math import ceil |
5 | 6 | import os |
@@ -248,6 +249,25 @@ def error_message(msg): |
248 | 249 | "No shared images found between the target and control directories." |
249 | 250 | ) |
250 | 251 |
|
| 252 | + mismatched_files = [] |
| 253 | + for image_file in shared_files: |
| 254 | + target_image_path = os.path.join(target_images_dir, image_file) |
| 255 | + control_image_path = os.path.join(control_images_dir, image_file) |
| 256 | + |
| 257 | + try: |
| 258 | + with Image.open(target_image_path) as target_img, Image.open(control_image_path) as control_img: |
| 259 | + target_aspect_ratio = target_img.width / target_img.height |
| 260 | + control_aspect_ratio = control_img.width / control_img.height |
| 261 | + |
| 262 | + if abs(target_aspect_ratio - control_aspect_ratio) > 1e-2: |
| 263 | + mismatched_files.append(image_file) |
| 264 | + log.warning(f"Aspect ratio mismatch for {image_file}: Target AR is {target_aspect_ratio:.4f}, Control AR is {control_aspect_ratio:.4f}") |
| 265 | + except Exception as e: |
| 266 | + log.error(f"Could not load or process image {image_file}. Error: {e}") |
| 267 | + |
| 268 | + if mismatched_files: |
| 269 | + gr.Warning(f"Found {len(mismatched_files)} images with aspect ratio mismatches. Use the 'Apply Correction' button to fix them.") |
| 270 | + |
251 | 271 | total_images = len(shared_files) |
252 | 272 | max_pages = ceil(total_images / IMAGES_TO_SHOW) |
253 | 273 |
|
@@ -275,6 +295,132 @@ def error_message(msg): |
275 | 295 | ] |
276 | 296 |
|
277 | 297 |
|
| 298 | +def crop_image(image, target_aspect_ratio): |
| 299 | + width, height = image.size |
| 300 | + current_aspect_ratio = width / height |
| 301 | + |
| 302 | + if current_aspect_ratio > target_aspect_ratio: |
| 303 | + # Crop width |
| 304 | + new_width = int(target_aspect_ratio * height) |
| 305 | + left = (width - new_width) / 2 |
| 306 | + top = 0 |
| 307 | + right = left + new_width |
| 308 | + bottom = height |
| 309 | + else: |
| 310 | + # Crop height |
| 311 | + new_height = int(width / target_aspect_ratio) |
| 312 | + left = 0 |
| 313 | + top = (height - new_height) / 2 |
| 314 | + right = width |
| 315 | + bottom = top + new_height |
| 316 | + |
| 317 | + return image.crop((left, top, right, bottom)) |
| 318 | + |
| 319 | + |
| 320 | +def pad_image(image, target_aspect_ratio, color="white"): |
| 321 | + width, height = image.size |
| 322 | + current_aspect_ratio = width / height |
| 323 | + |
| 324 | + if current_aspect_ratio > target_aspect_ratio: |
| 325 | + # Pad height |
| 326 | + new_height = int(width / target_aspect_ratio) |
| 327 | + padded_image = Image.new(image.mode, (width, new_height), color) |
| 328 | + padded_image.paste(image, (0, int((new_height - height) / 2))) |
| 329 | + else: |
| 330 | + # Pad width |
| 331 | + new_width = int(height * target_aspect_ratio) |
| 332 | + padded_image = Image.new(image.mode, (new_width, height), color) |
| 333 | + padded_image.paste(image, (int((new_width - width) / 2), 0)) |
| 334 | + |
| 335 | + return padded_image |
| 336 | + |
| 337 | + |
| 338 | +def save_image_with_backup(image_path, image_to_save): |
| 339 | + if not os.path.exists(image_path): |
| 340 | + log.error(f"Image path does not exist: {image_path}") |
| 341 | + return |
| 342 | + |
| 343 | + try: |
| 344 | + directory = os.path.dirname(image_path) |
| 345 | + original_dir = os.path.join(directory, "original") |
| 346 | + |
| 347 | + if not os.path.exists(original_dir): |
| 348 | + os.makedirs(original_dir) |
| 349 | + |
| 350 | + base_filename = os.path.basename(image_path) |
| 351 | + backup_path = os.path.join(original_dir, base_filename) |
| 352 | + |
| 353 | + if not os.path.exists(backup_path): |
| 354 | + os.rename(image_path, backup_path) |
| 355 | + log.info(f"Backed up original image to {backup_path}") |
| 356 | + else: |
| 357 | + log.info(f"Backup already exists for {base_filename}, skipping backup.") |
| 358 | + |
| 359 | + image_to_save.save(image_path) |
| 360 | + log.info(f"Saved modified image to {image_path}") |
| 361 | + |
| 362 | + except Exception as e: |
| 363 | + log.error(f"Error saving image with backup: {e}") |
| 364 | + |
| 365 | + |
| 366 | +def apply_correction( |
| 367 | + image_files, |
| 368 | + target_images_dir, |
| 369 | + control_images_dir, |
| 370 | + correction_method, |
| 371 | + save_padded, |
| 372 | +): |
| 373 | + if not image_files: |
| 374 | + gr.Warning("No images loaded.") |
| 375 | + return gr.update() |
| 376 | + |
| 377 | + if correction_method == "None": |
| 378 | + gr.Info("No correction method selected.") |
| 379 | + return gr.update() |
| 380 | + |
| 381 | + corrected_files = 0 |
| 382 | + for image_file in image_files: |
| 383 | + target_image_path = os.path.join(target_images_dir, image_file) |
| 384 | + control_image_path = os.path.join(control_images_dir, image_file) |
| 385 | + |
| 386 | + try: |
| 387 | + with Image.open(target_image_path) as target_img, Image.open( |
| 388 | + control_image_path |
| 389 | + ) as control_img: |
| 390 | + target_aspect_ratio = target_img.width / target_img.height |
| 391 | + control_aspect_ratio = control_img.width / control_img.height |
| 392 | + |
| 393 | + if abs(target_aspect_ratio - control_aspect_ratio) > 1e-2: |
| 394 | + if target_aspect_ratio > control_aspect_ratio: |
| 395 | + # Target is wider, so we correct it |
| 396 | + image_to_correct = target_img |
| 397 | + path_to_save = target_image_path |
| 398 | + correct_aspect_ratio = control_aspect_ratio |
| 399 | + else: |
| 400 | + # Control is wider, so we correct it |
| 401 | + image_to_correct = control_img |
| 402 | + path_to_save = control_image_path |
| 403 | + correct_aspect_ratio = target_aspect_ratio |
| 404 | + |
| 405 | + if correction_method == "Crop": |
| 406 | + modified_image = crop_image(image_to_correct, correct_aspect_ratio) |
| 407 | + elif correction_method == "Pad": |
| 408 | + modified_image = pad_image(image_to_correct, correct_aspect_ratio) |
| 409 | + else: |
| 410 | + continue # Should not happen |
| 411 | + |
| 412 | + if save_padded: |
| 413 | + save_image_with_backup(path_to_save, modified_image) |
| 414 | + |
| 415 | + corrected_files += 1 |
| 416 | + |
| 417 | + except Exception as e: |
| 418 | + log.error(f"Could not process or save image {image_file}. Error: {e}") |
| 419 | + |
| 420 | + gr.Info(f"Corrected {corrected_files} images with aspect ratio mismatches.") |
| 421 | + return gr.update() |
| 422 | + |
| 423 | + |
278 | 424 | def update_images( |
279 | 425 | image_files, # REFACTOR: Receive the list of files from gr.State |
280 | 426 | target_images_dir, |
@@ -382,6 +528,24 @@ def render_pagination_with_logic(page, max_page): |
382 | 528 |
|
383 | 529 | load_images_button = gr.Button("Load Images", variant="primary") |
384 | 530 |
|
| 531 | + with gr.Row(): |
| 532 | + aspect_ratio_correction = gr.Dropdown(["None", "Crop", "Pad"], label="Aspect Ratio Correction", value="None") |
| 533 | + save_padded_images = gr.Checkbox(label="Save Padded Images", value=False) |
| 534 | + |
| 535 | + apply_correction_button = gr.Button("Apply Correction") |
| 536 | + |
| 537 | + apply_correction_button.click( |
| 538 | + apply_correction, |
| 539 | + inputs=[ |
| 540 | + image_files_state, |
| 541 | + loaded_images_dir, |
| 542 | + loaded_control_images_dir, |
| 543 | + aspect_ratio_correction, |
| 544 | + save_padded_images, |
| 545 | + ], |
| 546 | + outputs=info_box, |
| 547 | + ) |
| 548 | + |
385 | 549 | target_images_dir.change(update_dir_list, inputs=target_images_dir, outputs=target_images_dir, show_progress=False) |
386 | 550 | control_images_dir.change( |
387 | 551 | lambda path, current_target: ( |
|
0 commit comments