| | from os import path |
| | from PIL import Image |
| | from typing import Any |
| |
|
| | from constants import DEVICE |
| | from paths import FastStableDiffusionPaths |
| | from backend.upscale.upscaler import upscale_image |
| | from backend.upscale.tiled_upscale import generate_upscaled_image |
| | from frontend.webui.image_variations_ui import generate_image_variations |
| | from backend.lora import ( |
| | get_active_lora_weights, |
| | update_lora_weights, |
| | load_lora_weight, |
| | ) |
| | from backend.models.lcmdiffusion_setting import ( |
| | DiffusionTask, |
| | ControlNetSetting, |
| | ) |
| |
|
| |
|
| | _batch_count = 1 |
| | _edit_lora_settings = False |
| |
|
| |
|
| | def user_value( |
| | value_type: type, |
| | message: str, |
| | default_value: Any, |
| | ) -> Any: |
| | try: |
| | value = value_type(input(message)) |
| | except: |
| | value = default_value |
| | return value |
| |
|
| |
|
| | def interactive_mode( |
| | config, |
| | context, |
| | ): |
| | print("=============================================") |
| | print("Welcome to FastSD CPU Interactive CLI") |
| | print("=============================================") |
| | while True: |
| | print("> 1. Text to Image") |
| | print("> 2. Image to Image") |
| | print("> 3. Image Variations") |
| | print("> 4. EDSR Upscale") |
| | print("> 5. SD Upscale") |
| | print("> 6. Edit default generation settings") |
| | print("> 7. Edit LoRA settings") |
| | print("> 8. Edit ControlNet settings") |
| | print("> 9. Edit negative prompt") |
| | print("> 10. Quit") |
| | option = user_value( |
| | int, |
| | "Enter a Diffusion Task number (1): ", |
| | 1, |
| | ) |
| | if option not in range(1, 11): |
| | print("Wrong Diffusion Task number!") |
| | exit() |
| |
|
| | if option == 1: |
| | interactive_txt2img( |
| | config, |
| | context, |
| | ) |
| | elif option == 2: |
| | interactive_img2img( |
| | config, |
| | context, |
| | ) |
| | elif option == 3: |
| | interactive_variations( |
| | config, |
| | context, |
| | ) |
| | elif option == 4: |
| | interactive_edsr( |
| | config, |
| | context, |
| | ) |
| | elif option == 5: |
| | interactive_sdupscale( |
| | config, |
| | context, |
| | ) |
| | elif option == 6: |
| | interactive_settings( |
| | config, |
| | context, |
| | ) |
| | elif option == 7: |
| | interactive_lora( |
| | config, |
| | context, |
| | True, |
| | ) |
| | elif option == 8: |
| | interactive_controlnet( |
| | config, |
| | context, |
| | True, |
| | ) |
| | elif option == 9: |
| | interactive_negative( |
| | config, |
| | context, |
| | ) |
| | elif option == 10: |
| | exit() |
| |
|
| |
|
| | def interactive_negative( |
| | config, |
| | context, |
| | ): |
| | settings = config.lcm_diffusion_setting |
| | print(f"Current negative prompt: '{settings.negative_prompt}'") |
| | user_input = input("Write a negative prompt (set guidance > 1.0): ") |
| | if user_input == "": |
| | return |
| | else: |
| | settings.negative_prompt = user_input |
| |
|
| |
|
| | def interactive_controlnet( |
| | config, |
| | context, |
| | menu_flag=False, |
| | ): |
| | """ |
| | @param menu_flag: Indicates whether this function was called from the main |
| | interactive CLI menu; _True_ if called from the main menu, _False_ otherwise |
| | """ |
| | settings = config.lcm_diffusion_setting |
| | if not settings.controlnet: |
| | settings.controlnet = ControlNetSetting() |
| |
|
| | current_enabled = settings.controlnet.enabled |
| | current_adapter_path = settings.controlnet.adapter_path |
| | current_conditioning_scale = settings.controlnet.conditioning_scale |
| | current_control_image = settings.controlnet._control_image |
| |
|
| | option = input("Enable ControlNet? (y/N): ") |
| | settings.controlnet.enabled = True if option.upper() == "Y" else False |
| | if settings.controlnet.enabled: |
| | option = input( |
| | f"Enter ControlNet adapter path ({settings.controlnet.adapter_path}): " |
| | ) |
| | if option != "": |
| | settings.controlnet.adapter_path = option |
| | settings.controlnet.conditioning_scale = user_value( |
| | float, |
| | f"Enter ControlNet conditioning scale ({settings.controlnet.conditioning_scale}): ", |
| | settings.controlnet.conditioning_scale, |
| | ) |
| | option = input( |
| | f"Enter ControlNet control image path (Leave empty to reuse current): " |
| | ) |
| | if option != "": |
| | try: |
| | new_image = Image.open(option) |
| | settings.controlnet._control_image = new_image |
| | except (AttributeError, FileNotFoundError) as e: |
| | settings.controlnet._control_image = None |
| | if ( |
| | not settings.controlnet.adapter_path |
| | or not path.exists(settings.controlnet.adapter_path) |
| | or not settings.controlnet._control_image |
| | ): |
| | print("Invalid ControlNet settings! Disabling ControlNet") |
| | settings.controlnet.enabled = False |
| |
|
| | if ( |
| | settings.controlnet.enabled != current_enabled |
| | or settings.controlnet.adapter_path != current_adapter_path |
| | ): |
| | settings.rebuild_pipeline = True |
| |
|
| |
|
| | def interactive_lora( |
| | config, |
| | context, |
| | menu_flag=False, |
| | ): |
| | """ |
| | @param menu_flag: Indicates whether this function was called from the main |
| | interactive CLI menu; _True_ if called from the main menu, _False_ otherwise |
| | """ |
| | if context == None or context.lcm_text_to_image.pipeline == None: |
| | print("Diffusion pipeline not initialized, please run a generation task first!") |
| | return |
| |
|
| | print("> 1. Change LoRA weights") |
| | print("> 2. Load new LoRA model") |
| | option = user_value( |
| | int, |
| | "Enter a LoRA option (1): ", |
| | 1, |
| | ) |
| | if option not in range(1, 3): |
| | print("Wrong LoRA option!") |
| | return |
| |
|
| | if option == 1: |
| | update_weights = [] |
| | active_weights = get_active_lora_weights() |
| | for lora in active_weights: |
| | weight = user_value( |
| | float, |
| | f"Enter a new LoRA weight for {lora[0]} ({lora[1]}): ", |
| | lora[1], |
| | ) |
| | update_weights.append( |
| | ( |
| | lora[0], |
| | weight, |
| | ) |
| | ) |
| | if len(update_weights) > 0: |
| | update_lora_weights( |
| | context.lcm_text_to_image.pipeline, |
| | config.lcm_diffusion_setting, |
| | update_weights, |
| | ) |
| | elif option == 2: |
| | |
| | settings = config.lcm_diffusion_setting |
| | settings.lora.fuse = False |
| | settings.lora.enabled = False |
| | settings.lora.path = input("Enter LoRA model path: ") |
| | settings.lora.weight = user_value( |
| | float, |
| | "Enter a LoRA weight (0.5): ", |
| | 0.5, |
| | ) |
| | if not path.exists(settings.lora.path): |
| | print("Invalid LoRA model path!") |
| | return |
| | settings.lora.enabled = True |
| | load_lora_weight(context.lcm_text_to_image.pipeline, settings) |
| |
|
| | if menu_flag: |
| | global _edit_lora_settings |
| | _edit_lora_settings = False |
| | option = input("Edit LoRA settings after every generation? (y/N): ") |
| | if option.upper() == "Y": |
| | _edit_lora_settings = True |
| |
|
| |
|
| | def interactive_settings( |
| | config, |
| | context, |
| | ): |
| | global _batch_count |
| | settings = config.lcm_diffusion_setting |
| | print("Enter generation settings (leave empty to use current value)") |
| | print("> 1. Use LCM") |
| | print("> 2. Use LCM-Lora") |
| | print("> 3. Use OpenVINO") |
| | option = user_value( |
| | int, |
| | "Select inference model option (1): ", |
| | 1, |
| | ) |
| | if option not in range(1, 4): |
| | print("Wrong inference model option! Falling back to defaults") |
| | return |
| |
|
| | settings.use_lcm_lora = False |
| | settings.use_openvino = False |
| | if option == 1: |
| | lcm_model_id = input(f"Enter LCM model ID ({settings.lcm_model_id}): ") |
| | if lcm_model_id != "": |
| | settings.lcm_model_id = lcm_model_id |
| | elif option == 2: |
| | settings.use_lcm_lora = True |
| | lcm_lora_id = input( |
| | f"Enter LCM-Lora model ID ({settings.lcm_lora.lcm_lora_id}): " |
| | ) |
| | if lcm_lora_id != "": |
| | settings.lcm_lora.lcm_lora_id = lcm_lora_id |
| | base_model_id = input( |
| | f"Enter Base model ID ({settings.lcm_lora.base_model_id}): " |
| | ) |
| | if base_model_id != "": |
| | settings.lcm_lora.base_model_id = base_model_id |
| | elif option == 3: |
| | settings.use_openvino = True |
| | openvino_lcm_model_id = input( |
| | f"Enter OpenVINO model ID ({settings.openvino_lcm_model_id}): " |
| | ) |
| | if openvino_lcm_model_id != "": |
| | settings.openvino_lcm_model_id = openvino_lcm_model_id |
| |
|
| | settings.use_offline_model = True |
| | settings.use_tiny_auto_encoder = True |
| | option = input("Work offline? (Y/n): ") |
| | if option.upper() == "N": |
| | settings.use_offline_model = False |
| | option = input("Use Tiny Auto Encoder? (Y/n): ") |
| | if option.upper() == "N": |
| | settings.use_tiny_auto_encoder = False |
| |
|
| | settings.image_width = user_value( |
| | int, |
| | f"Image width ({settings.image_width}): ", |
| | settings.image_width, |
| | ) |
| | settings.image_height = user_value( |
| | int, |
| | f"Image height ({settings.image_height}): ", |
| | settings.image_height, |
| | ) |
| | settings.inference_steps = user_value( |
| | int, |
| | f"Inference steps ({settings.inference_steps}): ", |
| | settings.inference_steps, |
| | ) |
| | settings.guidance_scale = user_value( |
| | float, |
| | f"Guidance scale ({settings.guidance_scale}): ", |
| | settings.guidance_scale, |
| | ) |
| | settings.number_of_images = user_value( |
| | int, |
| | f"Number of images per batch ({settings.number_of_images}): ", |
| | settings.number_of_images, |
| | ) |
| | _batch_count = user_value( |
| | int, |
| | f"Batch count ({_batch_count}): ", |
| | _batch_count, |
| | ) |
| | |
| | print(config.lcm_diffusion_setting) |
| |
|
| |
|
| | def interactive_txt2img( |
| | config, |
| | context, |
| | ): |
| | global _batch_count |
| | config.lcm_diffusion_setting.diffusion_task = DiffusionTask.text_to_image.value |
| | user_input = input("Write a prompt (write 'exit' to quit): ") |
| | while True: |
| | if user_input == "exit": |
| | return |
| | elif user_input == "": |
| | user_input = config.lcm_diffusion_setting.prompt |
| | config.lcm_diffusion_setting.prompt = user_input |
| | for _ in range(0, _batch_count): |
| | images = context.generate_text_to_image( |
| | settings=config, |
| | device=DEVICE, |
| | ) |
| | context.save_images( |
| | images, |
| | config, |
| | ) |
| | if _edit_lora_settings: |
| | interactive_lora( |
| | config, |
| | context, |
| | ) |
| | user_input = input("Write a prompt: ") |
| |
|
| |
|
| | def interactive_img2img( |
| | config, |
| | context, |
| | ): |
| | global _batch_count |
| | settings = config.lcm_diffusion_setting |
| | settings.diffusion_task = DiffusionTask.image_to_image.value |
| | steps = settings.inference_steps |
| | source_path = input("Image path: ") |
| | if source_path == "": |
| | print("Error : You need to provide a file in img2img mode") |
| | return |
| | settings.strength = user_value( |
| | float, |
| | f"img2img strength ({settings.strength}): ", |
| | settings.strength, |
| | ) |
| | settings.inference_steps = int(steps / settings.strength + 1) |
| | user_input = input("Write a prompt (write 'exit' to quit): ") |
| | while True: |
| | if user_input == "exit": |
| | settings.inference_steps = steps |
| | return |
| | settings.init_image = Image.open(source_path) |
| | settings.prompt = user_input |
| | for _ in range(0, _batch_count): |
| | images = context.generate_text_to_image( |
| | settings=config, |
| | device=DEVICE, |
| | ) |
| | context.save_images( |
| | images, |
| | config, |
| | ) |
| | new_path = input(f"Image path ({source_path}): ") |
| | if new_path != "": |
| | source_path = new_path |
| | settings.strength = user_value( |
| | float, |
| | f"img2img strength ({settings.strength}): ", |
| | settings.strength, |
| | ) |
| | if _edit_lora_settings: |
| | interactive_lora( |
| | config, |
| | context, |
| | ) |
| | settings.inference_steps = int(steps / settings.strength + 1) |
| | user_input = input("Write a prompt: ") |
| |
|
| |
|
| | def interactive_variations( |
| | config, |
| | context, |
| | ): |
| | global _batch_count |
| | settings = config.lcm_diffusion_setting |
| | settings.diffusion_task = DiffusionTask.image_to_image.value |
| | steps = settings.inference_steps |
| | source_path = input("Image path: ") |
| | if source_path == "": |
| | print("Error : You need to provide a file in Image variations mode") |
| | return |
| | settings.strength = user_value( |
| | float, |
| | f"Image variations strength ({settings.strength}): ", |
| | settings.strength, |
| | ) |
| | settings.inference_steps = int(steps / settings.strength + 1) |
| | while True: |
| | settings.init_image = Image.open(source_path) |
| | settings.prompt = "" |
| | for i in range(0, _batch_count): |
| | generate_image_variations( |
| | settings.init_image, |
| | settings.strength, |
| | ) |
| | if _edit_lora_settings: |
| | interactive_lora( |
| | config, |
| | context, |
| | ) |
| | user_input = input("Continue in Image variations mode? (Y/n): ") |
| | if user_input.upper() == "N": |
| | settings.inference_steps = steps |
| | return |
| | new_path = input(f"Image path ({source_path}): ") |
| | if new_path != "": |
| | source_path = new_path |
| | settings.strength = user_value( |
| | float, |
| | f"Image variations strength ({settings.strength}): ", |
| | settings.strength, |
| | ) |
| | settings.inference_steps = int(steps / settings.strength + 1) |
| |
|
| |
|
| | def interactive_edsr( |
| | config, |
| | context, |
| | ): |
| | source_path = input("Image path: ") |
| | if source_path == "": |
| | print("Error : You need to provide a file in EDSR mode") |
| | return |
| | while True: |
| | output_path = FastStableDiffusionPaths.get_upscale_filepath( |
| | source_path, |
| | 2, |
| | config.generated_images.format, |
| | ) |
| | result = upscale_image( |
| | context, |
| | source_path, |
| | output_path, |
| | 2, |
| | ) |
| | user_input = input("Continue in EDSR upscale mode? (Y/n): ") |
| | if user_input.upper() == "N": |
| | return |
| | new_path = input(f"Image path ({source_path}): ") |
| | if new_path != "": |
| | source_path = new_path |
| |
|
| |
|
| | def interactive_sdupscale_settings(config): |
| | steps = config.lcm_diffusion_setting.inference_steps |
| | custom_settings = {} |
| | print("> 1. Upscale whole image") |
| | print("> 2. Define custom tiles (advanced)") |
| | option = user_value( |
| | int, |
| | "Select an SD Upscale option (1): ", |
| | 1, |
| | ) |
| | if option not in range(1, 3): |
| | print("Wrong SD Upscale option!") |
| | return |
| |
|
| | |
| | custom_settings["source_file"] = "" |
| | new_path = input(f"Input image path ({custom_settings['source_file']}): ") |
| | if new_path != "": |
| | custom_settings["source_file"] = new_path |
| | if custom_settings["source_file"] == "": |
| | print("Error : You need to provide a file in SD Upscale mode") |
| | return |
| | custom_settings["target_file"] = None |
| | if option == 2: |
| | custom_settings["target_file"] = input("Image to patch: ") |
| | if custom_settings["target_file"] == "": |
| | print("No target file provided, upscaling whole input image instead!") |
| | custom_settings["target_file"] = None |
| | option = 1 |
| | custom_settings["output_format"] = config.generated_images.format |
| | custom_settings["strength"] = user_value( |
| | float, |
| | f"SD Upscale strength ({config.lcm_diffusion_setting.strength}): ", |
| | config.lcm_diffusion_setting.strength, |
| | ) |
| | config.lcm_diffusion_setting.inference_steps = int( |
| | steps / custom_settings["strength"] + 1 |
| | ) |
| | if option == 1: |
| | custom_settings["scale_factor"] = user_value( |
| | float, |
| | f"Scale factor (2.0): ", |
| | 2.0, |
| | ) |
| | custom_settings["tile_size"] = user_value( |
| | int, |
| | f"Split input image into tiles of the following size, in pixels (256): ", |
| | 256, |
| | ) |
| | custom_settings["tile_overlap"] = user_value( |
| | int, |
| | f"Tile overlap, in pixels (16): ", |
| | 16, |
| | ) |
| | elif option == 2: |
| | custom_settings["scale_factor"] = user_value( |
| | float, |
| | "Input image to Image-to-patch scale_factor (2.0): ", |
| | 2.0, |
| | ) |
| | custom_settings["tile_size"] = 256 |
| | custom_settings["tile_overlap"] = 16 |
| | custom_settings["prompt"] = input( |
| | "Write a prompt describing the input image (optional): " |
| | ) |
| | custom_settings["tiles"] = [] |
| | if option == 2: |
| | add_tile = True |
| | while add_tile: |
| | print("=== Define custom SD Upscale tile ===") |
| | tile_x = user_value( |
| | int, |
| | "Enter tile's X position: ", |
| | 0, |
| | ) |
| | tile_y = user_value( |
| | int, |
| | "Enter tile's Y position: ", |
| | 0, |
| | ) |
| | tile_w = user_value( |
| | int, |
| | "Enter tile's width (256): ", |
| | 256, |
| | ) |
| | tile_h = user_value( |
| | int, |
| | "Enter tile's height (256): ", |
| | 256, |
| | ) |
| | tile_scale = user_value( |
| | float, |
| | "Enter tile's scale factor (2.0): ", |
| | 2.0, |
| | ) |
| | tile_prompt = input("Enter tile's prompt (optional): ") |
| | custom_settings["tiles"].append( |
| | { |
| | "x": tile_x, |
| | "y": tile_y, |
| | "w": tile_w, |
| | "h": tile_h, |
| | "mask_box": None, |
| | "prompt": tile_prompt, |
| | "scale_factor": tile_scale, |
| | } |
| | ) |
| | tile_option = input("Do you want to define another tile? (y/N): ") |
| | if tile_option == "" or tile_option.upper() == "N": |
| | add_tile = False |
| |
|
| | return custom_settings |
| |
|
| |
|
| | def interactive_sdupscale( |
| | config, |
| | context, |
| | ): |
| | settings = config.lcm_diffusion_setting |
| | settings.diffusion_task = DiffusionTask.image_to_image.value |
| | settings.init_image = "" |
| | source_path = "" |
| | steps = settings.inference_steps |
| |
|
| | while True: |
| | custom_upscale_settings = None |
| | option = input("Edit custom SD Upscale settings? (y/N): ") |
| | if option.upper() == "Y": |
| | config.lcm_diffusion_setting.inference_steps = steps |
| | custom_upscale_settings = interactive_sdupscale_settings(config) |
| | if not custom_upscale_settings: |
| | return |
| | source_path = custom_upscale_settings["source_file"] |
| | else: |
| | new_path = input(f"Image path ({source_path}): ") |
| | if new_path != "": |
| | source_path = new_path |
| | if source_path == "": |
| | print("Error : You need to provide a file in SD Upscale mode") |
| | return |
| | settings.strength = user_value( |
| | float, |
| | f"SD Upscale strength ({settings.strength}): ", |
| | settings.strength, |
| | ) |
| | settings.inference_steps = int(steps / settings.strength + 1) |
| |
|
| | output_path = FastStableDiffusionPaths.get_upscale_filepath( |
| | source_path, |
| | 2, |
| | config.generated_images.format, |
| | ) |
| | generate_upscaled_image( |
| | config, |
| | source_path, |
| | settings.strength, |
| | upscale_settings=custom_upscale_settings, |
| | context=context, |
| | tile_overlap=32 if settings.use_openvino else 16, |
| | output_path=output_path, |
| | image_format=config.generated_images.format, |
| | ) |
| | user_input = input("Continue in SD Upscale mode? (Y/n): ") |
| | if user_input.upper() == "N": |
| | settings.inference_steps = steps |
| | return |
| |
|