|
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 |
|
|