diff --git a/webui.py b/webui.py index b356784..fac126d 100644 --- a/webui.py +++ b/webui.py @@ -59,6 +59,7 @@ parser.add_argument("--gfpgan-dir", type=str, help="GFPGAN directory", default=( parser.add_argument("--no-verify-input", action='store_true', help="do not verify input to check if it's too long") parser.add_argument("--no-half", action='store_true', help="do not switch the model to 16-bit floats") parser.add_argument("--no-progressbar-hiding", action='store_true', help="do not hide progressbar in gradio UI (we hide it because it slows down ML if you have hardware accleration in browser)") +parser.add_argument("--defaults", type=str, help="path to configuration file providing UI defaults, uses same format as cli parameter", default='configs/webui/webui.yaml') parser.add_argument("--cli", type=str, help="don't launch web server, take Python function kwargs from this file.", default=None) opt = parser.parse_args() @@ -684,7 +685,7 @@ class Flagging(gr.FlaggingCallback): print("Logged:", filenames[0]) -def img2img(prompt: str, image_editor_mode: str, cropped_image, image_with_mask, mask_mode: str, ddim_steps: int, sampler_name: str, +def img2img(prompt: str, image_editor_mode: str, cropped_image, image_with_mask, mask_mode: int, ddim_steps: int, sampler_name: str, toggles: List[int], n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, seed: int, height: int, width: int, resize_mode: int, fp): outpath = opt.outdir_img2img or opt.outdir or "outputs/img2img-samples" @@ -723,7 +724,7 @@ def img2img(prompt: str, image_editor_mode: str, cropped_image, image_with_mask, init_mask = image_with_mask["mask"] init_mask = init_mask.convert("RGB") init_mask = resize_image(resize_mode, init_mask, width, height) - keep_mask = mask_mode == "Keep masked area" + keep_mask = mask_mode == 0 init_mask = init_mask if keep_mask else ImageOps.invert(init_mask) else: init_img = cropped_image @@ -919,8 +920,16 @@ def run_GFPGAN(image, strength): css = "" if opt.no_progressbar_hiding else css_hide_progressbar css = css + '[data-testid="image"] {min-height: 512px !important}' -sample_img2img = "assets/stable-samples/img2img/sketch-mountains-input.jpg" -sample_img2img = sample_img2img if os.path.exists(sample_img2img) else None +if opt.defaults is not None and os.path.isfile(opt.defaults): + try: + with open(opt.defaults, "r", encoding="utf8") as f: + user_defaults = yaml.safe_load(f) + except (OSError, yaml.YAMLError) as e: + print(f"Error loading defaults file {opt.defaults}:", e) + print("Falling back to program defaults.") + user_defaults = {} +else: + user_defaults = {} # make sure these indicies line up at the top of txt2img() txt2img_toggles = [ @@ -932,11 +941,28 @@ txt2img_toggles = [ if GFPGAN is not None: txt2img_toggles.append('Fix faces using GFPGAN') -txt2img_toggle_defaults = [ - 'Normalize Prompt Weights (ensure sum of weights add up to 1.0)', - 'Save individual images', - 'Save grid' -] +txt2img_defaults = { + 'prompt': '', + 'ddim_steps': 50, + 'toggles': [1, 2, 3], + 'sampler_name': 'k_lms', + 'ddim_eta': 0.0, + 'n_iter': 1, + 'batch_size': 1, + 'cfg_scale': 7.5, + 'seed': '', + 'height': 512, + 'width': 512, + 'fp': None, +} + +if 'txt2img' in user_defaults: + txt2img_defaults.update(user_defaults['txt2img']) + +txt2img_toggle_defaults = [txt2img_toggles[i] for i in txt2img_defaults['toggles']] + +sample_img2img = "assets/stable-samples/img2img/sketch-mountains-input.jpg" +sample_img2img = sample_img2img if os.path.exists(sample_img2img) else None # make sure these indicies line up at the top of img2img() img2img_toggles = [ @@ -950,11 +976,39 @@ img2img_toggles = [ if GFPGAN is not None: img2img_toggles.append('Fix faces using GFPGAN') -img2img_toggle_defaults = [ - 'Normalize Prompt Weights (ensure sum of weights add up to 1.0)', - 'Save individual images', - 'Save grid', +img2img_mask_modes = [ + "Keep masked area", + "Regenerate only masked area", ] + +img2img_resize_modes = [ + "Just resize", + "Crop and resize", + "Resize and fill", +] + +img2img_defaults = { + 'prompt': '', + 'ddim_steps': 50, + 'toggles': [1, 4, 5], + 'sampler_name': 'k_lms', + 'ddim_eta': 0.0, + 'n_iter': 1, + 'batch_size': 1, + 'cfg_scale': 5.0, + 'denoising_strength': 0.75, + 'mask_mode': 0, + 'resize_mode': 0, + 'seed': '', + 'height': 512, + 'width': 512, + 'fp': None, +} + +if 'img2img' in user_defaults: + img2img_defaults.update(user_defaults['img2img']) + +img2img_toggle_defaults = [img2img_toggles[i] for i in img2img_defaults['toggles']] img2img_image_mode = 'sketch' def change_image_editor_mode(choice, cropped_image, resize_mode, width, height): @@ -981,18 +1035,18 @@ with gr.Blocks(css=css) as demo: with gr.Row().style(equal_height=False): with gr.Column(): gr.Markdown("Generate images from text with Stable Diffusion") - txt2img_prompt = gr.Textbox(label="Prompt", placeholder="A corgi wearing a top hat as an oil painting.", lines=1) - txt2img_steps = gr.Slider(minimum=1, maximum=250, step=1, label="Sampling Steps", value=50) - txt2img_sampling = gr.Radio(label='Sampling method (k_lms is default k-diffusion sampler)', choices=["DDIM", "PLMS", 'k_dpm_2_a', 'k_dpm_2', 'k_euler_a', 'k_euler', 'k_heun', 'k_lms'], value="k_lms") + txt2img_prompt = gr.Textbox(label="Prompt", placeholder="A corgi wearing a top hat as an oil painting.", lines=1, value=txt2img_defaults['prompt']) + txt2img_steps = gr.Slider(minimum=1, maximum=250, step=1, label="Sampling Steps", value=txt2img_defaults['ddim_steps']) + txt2img_sampling = gr.Radio(label='Sampling method (k_lms is default k-diffusion sampler)', choices=["DDIM", "PLMS", 'k_dpm_2_a', 'k_dpm_2', 'k_euler_a', 'k_euler', 'k_heun', 'k_lms'], value=txt2img_defaults['sampler_name']) txt2img_toggles = gr.CheckboxGroup(label='', choices=txt2img_toggles, value=txt2img_toggle_defaults, type="index") - txt2img_ddim_eta = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label="DDIM ETA", value=0.0, visible=False) - txt2img_batch_count = gr.Slider(minimum=1, maximum=250, step=1, label='Batch count (how many batches of images to generate)', value=1) - txt2img_batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size (how many images are in a batch; memory-hungry)', value=1) - txt2img_cfg = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='Classifier Free Guidance Scale (how strongly the image should follow the prompt)', value=7.5) - txt2img_seed = gr.Textbox(label="Seed (blank to randomize)", lines=1, value="") - txt2img_height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512) - txt2img_width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512) - txt2img_embeddings = gr.File(label = "Embeddings file for textual inversion", visible=hasattr(model, "embedding_manager")) + txt2img_ddim_eta = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label="DDIM ETA", value=txt2img_defaults['ddim_eta'], visible=False) + txt2img_batch_count = gr.Slider(minimum=1, maximum=250, step=1, label='Batch count (how many batches of images to generate)', value=txt2img_defaults['n_iter']) + txt2img_batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size (how many images are in a batch; memory-hungry)', value=txt2img_defaults['batch_size']) + txt2img_cfg = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='Classifier Free Guidance Scale (how strongly the image should follow the prompt)', value=txt2img_defaults['cfg_scale']) + txt2img_seed = gr.Textbox(label="Seed (blank to randomize)", lines=1, value=txt2img_defaults['seed']) + txt2img_height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=txt2img_defaults['height']) + txt2img_width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=txt2img_defaults['width']) + txt2img_embeddings = gr.File(label = "Embeddings file for textual inversion", visible=hasattr(model, "embedding_manager"), value=txt2img_defaults['fp']) txt2img_btn = gr.Button("Generate") with gr.Column(): output_txt2img_gallery = gr.Gallery(label="Images") @@ -1012,24 +1066,24 @@ with gr.Blocks(css=css) as demo: with gr.Row().style(equal_height=False): with gr.Column(): gr.Markdown("Generate images from images with Stable Diffusion") - img2img_prompt = gr.Textbox(label="Prompt", placeholder="A fantasy landscape, trending on artstation.", lines=1) + img2img_prompt = gr.Textbox(label="Prompt", placeholder="A fantasy landscape, trending on artstation.", lines=1, value=img2img_defaults['prompt']) img2img_image_editor_mode = gr.Radio(choices=["Mask", "Crop"], label="Image Editor Mode", value="Crop") gr.Markdown("The masking/cropping is very temperamental. It may take some time for the image to show when switching from Crop to Mask. If it doesn't work try switching modes again, switch tabs, clear the image or reload.") img2img_image_editor = gr.Image(value=sample_img2img, source="upload", interactive=True, type="pil", tool="select") img2img_image_mask = gr.Image(value=sample_img2img, source="upload", interactive=True, type="pil", tool="sketch", visible=False) - img2img_mask = gr.Radio(choices=["Keep masked area", "Regenerate only masked area"], label="Mask Mode", value="Keep masked area") - img2img_steps = gr.Slider(minimum=1, maximum=250, step=1, label="Sampling Steps", value=50) - img2img_sampling = gr.Radio(label='Sampling method (k_lms is default k-diffusion sampler)', choices=["DDIM", 'k_dpm_2_a', 'k_dpm_2', 'k_euler_a', 'k_euler', 'k_heun', 'k_lms'], value="k_lms") + img2img_mask = gr.Radio(choices=["Keep masked area", "Regenerate only masked area"], label="Mask Mode", type="index", value=img2img_mask_modes[img2img_defaults['mask_mode']]) + img2img_steps = gr.Slider(minimum=1, maximum=250, step=1, label="Sampling Steps", value=img2img_defaults['ddim_steps']) + img2img_sampling = gr.Radio(label='Sampling method (k_lms is default k-diffusion sampler)', choices=["DDIM", 'k_dpm_2_a', 'k_dpm_2', 'k_euler_a', 'k_euler', 'k_heun', 'k_lms'], value=img2img_defaults['sampler_name']) img2img_toggles = gr.CheckboxGroup(label='', choices=img2img_toggles, value=img2img_toggle_defaults, type="index") - img2img_batch_count = gr.Slider(minimum=1, maximum=250, step=1, label='Batch count (how many batches of images to generate)', value=1) - img2img_batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size (how many images are in a batch; memory-hungry)', value=1) - img2img_cfg = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='Classifier Free Guidance Scale (how strongly the image should follow the prompt)', value=5.0) - img2img_denoising = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising Strength', value=0.75) - img2img_seed = gr.Textbox(label="Seed (blank to randomize)", lines=1, value="") - img2img_height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512) - img2img_width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512) - img2img_resize = gr.Radio(label="Resize mode", choices=["Just resize", "Crop and resize", "Resize and fill"], type="index", value="Just resize") - img2img_embeddings = gr.File(label = "Embeddings file for textual inversion", visible=hasattr(model, "embedding_manager")) + img2img_batch_count = gr.Slider(minimum=1, maximum=250, step=1, label='Batch count (how many batches of images to generate)', value=img2img_defaults['n_iter']) + img2img_batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size (how many images are in a batch; memory-hungry)', value=img2img_defaults['batch_size']) + img2img_cfg = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='Classifier Free Guidance Scale (how strongly the image should follow the prompt)', value=img2img_defaults['cfg_scale']) + img2img_denoising = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising Strength', value=img2img_defaults['denoising_strength']) + img2img_seed = gr.Textbox(label="Seed (blank to randomize)", lines=1, value=img2img_defaults['seed']) + img2img_height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=img2img_defaults['height']) + img2img_width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=img2img_defaults['width']) + img2img_resize = gr.Radio(label="Resize mode", choices=["Just resize", "Crop and resize", "Resize and fill"], type="index", value=img2img_resize_modes[img2img_defaults['resize_mode']]) + img2img_embeddings = gr.File(label = "Embeddings file for textual inversion", visible=hasattr(model, "embedding_manager"), value=img2img_defaults['fp']) img2img_btn = gr.Button("Generate") with gr.Column(): output_img2img_gallery = gr.Gallery(label="Images") @@ -1064,12 +1118,19 @@ with gr.Blocks(css=css) as demo: [output_img2img_gallery, output_img2img_seed, output_img2img_params, output_img2img_stats] ) if GFPGAN is not None: + gfpgan_defaults = { + 'strength': 100, + } + + if 'gfpgan' in user_defaults: + gfpgan_defaults.update(user_defaults['gfpgan']) + with gr.TabItem("GFPGAN"): gr.Markdown("Fix faces on images") with gr.Row(): with gr.Column(): gfpgan_source = gr.Image(label="Source", source="upload", interactive=True, type="pil") - gfpgan_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Effect strength", value=100) + gfpgan_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Effect strength", value=gfpgan_defaults['strength']) gfpgan_btn = gr.Button("Generate") with gr.Column(): gfpgan_output = gr.Image(label="Output") diff --git a/webui.yaml b/webui.yaml new file mode 100644 index 0000000..be83128 --- /dev/null +++ b/webui.yaml @@ -0,0 +1,58 @@ +# UI defaults configuration file. Is read automatically if located at configs/webui/webui.yaml, or specify path via --defaults. + +txt2img: + prompt: + ddim_steps: 50 + # Adding an int to toggles enables the corresponding feature. + # 0: Create prompt matrix (separate multiple prompts using |, and get all combinations of them) + # 1: Normalize Prompt Weights (ensure sum of weights add up to 1.0) + # 2: Save individual images + # 3: Save grid + # 4: Fix faces using GFPGAN + toggles: [1, 2, 3] + sampler_name: k_lms + ddim_eta: 0.0 + n_iter: 1 + batch_size: 1 + cfg_scale: 7.5 + # Leave blank for random seed + seed: + height: 512 + width: 512 + # Textual inversion embeddings file path + fp: + +img2img: + prompt: + ddim_steps: 50 + # Adding an int to toggles enables the corresponding feature. + # 0: Create prompt matrix (separate multiple prompts using |, and get all combinations of them) + # 1: Normalize Prompt Weights (ensure sum of weights add up to 1.0) + # 2: Loopback (use images from previous batch when creating next batch) + # 3: Random loopback seed + # 4: Save individual images + # 5: Save grid + # 6: Fix faces using GFPGAN + toggles: [1, 4, 5] + sampler_name: k_lms + ddim_eta: 0.0 + n_iter: 1 + batch_size: 1 + cfg_scale: 5.0 + denoising_strength: 0.75 + # 0: Keep masked area + # 1: Regenerate only masked area + mask_mode: 0 + # 0: Just resize + # 1: Crop and resize + # 2: Resize and fill + resize_mode: 0 + # Leave blank for random seed + seed: + height: 512 + width: 512 + # Textual inversion embeddings file path + fp: + +gfpgan: + strength: 100