fastSD / frontend /cli_interactive.py
thejagstudio's picture
Upload 61 files
510ee71 verified
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.controlnet import controlnet_settings_from_dict
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,
LCMDiffusionSetting,
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:
# Load a new LoRA
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,
)
# output_format = user_value(int, f"Output format (PNG)", 1)
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 i in range(0, _batch_count):
context.generate_text_to_image(
settings=config,
device=DEVICE,
)
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 i in range(0, _batch_count):
context.generate_text_to_image(
settings=config,
device=DEVICE,
)
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"] = args.file
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