From c8e67b67320f5d090b758303e675c1e5586575a5 Mon Sep 17 00:00:00 2001 From: Artem Kotov Date: Mon, 29 May 2023 20:39:24 +0400 Subject: [PATCH 1/5] improve filename matching for mask we should not rely that mask filename will be of the same extension as the image filename so better pattern matching is added --- modules/img2img.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/img2img.py b/modules/img2img.py index d704bf900..bc79ea1f9 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -1,4 +1,5 @@ import os +from pathlib import Path import numpy as np from PIL import Image, ImageOps, ImageFilter, ImageEnhance, ImageChops, UnidentifiedImageError @@ -53,7 +54,11 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args): if is_inpaint_batch: # try to find corresponding mask for an image using simple filename matching - mask_image_path = os.path.join(inpaint_mask_dir, os.path.basename(image)) + path = Path(os.path.join(inpaint_mask_dir, os.path.basename(image))) + mask_image_path = list(path.parent.glob(f"**/{path.stem}*")) + if len(mask_image_path) > 0: + mask_image_path = str(mask_image_path[0]) + # if not found use first one ("same mask for all images" use-case) if mask_image_path not in inpaint_masks: mask_image_path = inpaint_masks[0] From 6c610a8a951ea8141024dbb659c2528bf24225ec Mon Sep 17 00:00:00 2001 From: Artem Kotov Date: Mon, 29 May 2023 20:47:20 +0400 Subject: [PATCH 2/5] add scale_by to batch processing --- modules/img2img.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/img2img.py b/modules/img2img.py index bc79ea1f9..5d6ad5200 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -14,7 +14,7 @@ from modules.ui import plaintext_to_html import modules.scripts -def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args): +def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=False, scale_by=1.0): processing.fix_seed(p) images = shared.listfiles(input_dir) @@ -50,6 +50,11 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args): continue # Use the EXIF orientation of photos taken by smartphones. img = ImageOps.exif_transpose(img) + + if to_scale: + p.width = int(img.width * scale_by) + p.height = int(img.height * scale_by) + p.init_images = [img] * p.batch_size if is_inpaint_batch: @@ -119,7 +124,7 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s if image is not None: image = ImageOps.exif_transpose(image) - if selected_scale_tab == 1: + if selected_scale_tab == 1 and not is_batch: assert image, "Can't scale by because no image is selected" width = int(image.width * scale_by) @@ -174,7 +179,7 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s if is_batch: assert not shared.cmd_opts.hide_ui_dir_config, "Launched with --hide-ui-dir-config, batch img2img disabled" - process_batch(p, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, args) + process_batch(p, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, args, to_scale=selected_scale_tab == 1, scale_by=scale_by) processed = Processed(p, [], p.seed, "") else: From 23314a6e27a24fc8bf98717ddc180aec06674abd Mon Sep 17 00:00:00 2001 From: Artem Kotov Date: Mon, 29 May 2023 21:38:49 +0400 Subject: [PATCH 3/5] ruffed --- modules/img2img.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/img2img.py b/modules/img2img.py index 5d6ad5200..a29add3a0 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -50,11 +50,11 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal continue # Use the EXIF orientation of photos taken by smartphones. img = ImageOps.exif_transpose(img) - + if to_scale: p.width = int(img.width * scale_by) p.height = int(img.height * scale_by) - + p.init_images = [img] * p.batch_size if is_inpaint_batch: @@ -63,7 +63,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal mask_image_path = list(path.parent.glob(f"**/{path.stem}*")) if len(mask_image_path) > 0: mask_image_path = str(mask_image_path[0]) - + # if not found use first one ("same mask for all images" use-case) if mask_image_path not in inpaint_masks: mask_image_path = inpaint_masks[0] From 49f4b4be67fa97306f99a461dc76de901d7499d2 Mon Sep 17 00:00:00 2001 From: Artem Kotov Date: Thu, 1 Jun 2023 11:29:56 +0400 Subject: [PATCH 4/5] add subdir support for images, masks and output; search mask only in subdir --- modules/img2img.py | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/modules/img2img.py b/modules/img2img.py index a29add3a0..abf7960f1 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -17,11 +17,12 @@ import modules.scripts def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=False, scale_by=1.0): processing.fix_seed(p) - images = shared.listfiles(input_dir) + allowed_extensions = [ext for ext, f in Image.registered_extensions().items() if f in Image.OPEN] + images = list(shared.walk_files(input_dir, allowed_extensions=allowed_extensions)) is_inpaint_batch = False if inpaint_mask_dir: - inpaint_masks = shared.listfiles(inpaint_mask_dir) + inpaint_masks = list(shared.walk_files(inpaint_mask_dir, allowed_extensions=allowed_extensions)) is_inpaint_batch = len(inpaint_masks) > 0 if is_inpaint_batch: print(f"\nInpaint batch is enabled. {len(inpaint_masks)} masks found.") @@ -57,16 +58,28 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal p.init_images = [img] * p.batch_size + image_path = Path(image) + if image_path.parent == Path(input_dir): + image_subpath = "" + else: + image_subpath = str(image_path.parent).replace(f"{input_dir}/", "") + if is_inpaint_batch: # try to find corresponding mask for an image using simple filename matching - path = Path(os.path.join(inpaint_mask_dir, os.path.basename(image))) - mask_image_path = list(path.parent.glob(f"**/{path.stem}*")) - if len(mask_image_path) > 0: - mask_image_path = str(mask_image_path[0]) - - # if not found use first one ("same mask for all images" use-case) - if mask_image_path not in inpaint_masks: + if len(inpaint_masks) == 1: mask_image_path = inpaint_masks[0] + else: + # try to find corresponding mask for an image using simple filename matching + mask_image_dir = Path(inpaint_mask_dir).joinpath(image_subpath) + masks_found = list(mask_image_dir.glob(f"{image_path.stem}.*")) + + if len(masks_found) == 0: + raise ValueError(f"Mask is not found for {image_path} in {mask_image_dir}") + + # it should contain only 1 matching mask + # otherwise user has many masks with the same name but different extensions + mask_image_path = masks_found[0] + mask_image = Image.open(mask_image_path) p.image_mask = mask_image @@ -75,17 +88,17 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal proc = process_images(p) for n, processed_image in enumerate(proc.images): - filename = os.path.basename(image) + filename = image_path.name if n > 0: left, right = os.path.splitext(filename) filename = f"{left}-{n}{right}" if not save_normally: - os.makedirs(output_dir, exist_ok=True) + os.makedirs(os.path.join(output_dir, image_subpath), exist_ok=True) if processed_image.mode == 'RGBA': processed_image = processed_image.convert("RGB") - processed_image.save(os.path.join(output_dir, filename)) + processed_image.save(os.path.join(output_dir, image_subpath, filename)) def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_index: int, mask_blur: int, mask_alpha: float, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, selected_scale_tab: int, height: int, width: int, scale_by: float, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, img2img_batch_inpaint_mask_dir: str, override_settings_texts, *args): From ba110bf09302ec6aed10a69016d96912e39c7c05 Mon Sep 17 00:00:00 2001 From: Artem Kotov Date: Thu, 1 Jun 2023 15:44:55 +0400 Subject: [PATCH 5/5] fallback to original file retrieving; skip img if mask not found usage of `shared.walk_files` breaks controlnet extension images are processed in different order which leads to unmatched img file used for img2img and img file used for controlnet (if no folder is specified for control net or the same as img2img input dir used for it) --- modules/img2img.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/modules/img2img.py b/modules/img2img.py index abf7960f1..3af6dac8b 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -17,12 +17,11 @@ import modules.scripts def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=False, scale_by=1.0): processing.fix_seed(p) - allowed_extensions = [ext for ext, f in Image.registered_extensions().items() if f in Image.OPEN] - images = list(shared.walk_files(input_dir, allowed_extensions=allowed_extensions)) + images = shared.listfiles(input_dir) is_inpaint_batch = False if inpaint_mask_dir: - inpaint_masks = list(shared.walk_files(inpaint_mask_dir, allowed_extensions=allowed_extensions)) + inpaint_masks = shared.listfiles(inpaint_mask_dir) is_inpaint_batch = len(inpaint_masks) > 0 if is_inpaint_batch: print(f"\nInpaint batch is enabled. {len(inpaint_masks)} masks found.") @@ -59,22 +58,18 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal p.init_images = [img] * p.batch_size image_path = Path(image) - if image_path.parent == Path(input_dir): - image_subpath = "" - else: - image_subpath = str(image_path.parent).replace(f"{input_dir}/", "") - if is_inpaint_batch: # try to find corresponding mask for an image using simple filename matching if len(inpaint_masks) == 1: mask_image_path = inpaint_masks[0] else: # try to find corresponding mask for an image using simple filename matching - mask_image_dir = Path(inpaint_mask_dir).joinpath(image_subpath) + mask_image_dir = Path(inpaint_mask_dir) masks_found = list(mask_image_dir.glob(f"{image_path.stem}.*")) if len(masks_found) == 0: - raise ValueError(f"Mask is not found for {image_path} in {mask_image_dir}") + print(f"Warning: mask is not found for {image_path} in {mask_image_dir}. Skipping it.") + continue # it should contain only 1 matching mask # otherwise user has many masks with the same name but different extensions @@ -95,10 +90,10 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal filename = f"{left}-{n}{right}" if not save_normally: - os.makedirs(os.path.join(output_dir, image_subpath), exist_ok=True) + os.makedirs(output_dir, exist_ok=True) if processed_image.mode == 'RGBA': processed_image = processed_image.convert("RGB") - processed_image.save(os.path.join(output_dir, image_subpath, filename)) + processed_image.save(os.path.join(output_dir, filename)) def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_index: int, mask_blur: int, mask_alpha: float, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, selected_scale_tab: int, height: int, width: int, scale_by: float, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, img2img_batch_inpaint_mask_dir: str, override_settings_texts, *args):