Spaces:
Running
on
Zero
Running
on
Zero
import os | |
import time | |
import random | |
import logging | |
from gradio.blocks import postprocess_update_dict | |
import numpy as np | |
from typing import Any, Dict, List, Optional, Union | |
import torch | |
from PIL import Image | |
import gradio as gr | |
from tempfile import NamedTemporaryFile | |
from diffusers import ( | |
DiffusionPipeline, | |
AutoencoderTiny, | |
AutoencoderKL, | |
AutoPipelineForImage2Image, | |
FluxPipeline, | |
FlowMatchEulerDiscreteScheduler, | |
DPMSolverMultistepScheduler) | |
from huggingface_hub import ( | |
hf_hub_download, | |
HfFileSystem, | |
ModelCard, | |
snapshot_download) | |
from diffusers.utils import load_image | |
from modules.version_info import ( | |
versions_html, | |
#initialize_cuda, | |
#release_torch_resources, | |
#get_torch_info | |
) | |
from modules.image_utils import ( | |
change_color, | |
open_image, | |
build_prerendered_images_by_quality, | |
upscale_image, | |
# lerp_imagemath, | |
# shrink_and_paste_on_blank, | |
show_lut, | |
apply_lut_to_image_path, | |
multiply_and_blend_images, | |
alpha_composite_with_control, | |
resize_and_crop_image, | |
convert_to_rgba_png, | |
get_image_from_dict | |
) | |
from modules.constants import ( | |
LORA_DETAILS, LORAS as loras, MODELS, | |
default_lut_example_img, | |
lut_files, | |
MAX_SEED, | |
IS_SHARED_SPACE, | |
# lut_folder,cards, | |
# cards_alternating, | |
# card_colors, | |
# card_colors_alternating, | |
pre_rendered_maps_paths, | |
PROMPTS, | |
NEGATIVE_PROMPTS, | |
TARGET_SIZE, | |
temp_files, | |
load_env_vars, | |
dotenv_path | |
) | |
# from modules.excluded_colors import ( | |
# add_color, | |
# delete_color, | |
# build_dataframe, | |
# on_input, | |
# excluded_color_list, | |
# on_color_display_select | |
# ) | |
from modules.misc import ( | |
get_filename, | |
convert_ratio_to_dimensions, | |
update_dimensions_on_ratio, | |
wait5 | |
) | |
from modules.lora_details import ( | |
approximate_token_count, | |
split_prompt_precisely, | |
upd_prompt_notes_by_index, | |
get_trigger_words_by_index | |
) | |
import spaces | |
input_image_palette = [] | |
current_prerendered_image = gr.State("./images/Beeuty-1.png") | |
user_info = { | |
"username": "guest", | |
"session_hash": None, | |
"headers": None, | |
"client": None, | |
"query_params": None, | |
"path_params": None, | |
"level" : 0 | |
} | |
user_profile = gr.State(None) | |
def get_profile() -> gr.OAuthProfile | None: | |
global user_profile | |
return user_profile | |
# Define a function to handle the login button click and retrieve user information. | |
def handle_login(profile: gr.OAuthProfile | None, request: gr.Request): | |
# Extract user information from the request | |
if profile is None: | |
profile = gr.State(None) | |
profile = get_profile() | |
user_info = { | |
"username": request.username, | |
"session_hash": request.session_hash, | |
"headers": dict(request.headers), | |
"client": request.client, | |
"query_params": dict(request.query_params), | |
"path_params": dict(request.path_params), | |
"level" : (0 if request.username == "guest" else 2) | |
} | |
return user_info, gr.update(logout_value=f"Logout {user_info['username']} ({user_info['level']})", value=f"Login {user_info['username']} ({user_info['level']})"), profile | |
#---if workspace = local or colab--- | |
# Authenticate with Hugging Face | |
# from huggingface_hub import login | |
# Log in to Hugging Face using the provided token | |
# hf_token = 'hf-token-authentication' | |
# login(hf_token) | |
def calculate_shift( | |
image_seq_len, | |
base_seq_len: int = 256, | |
max_seq_len: int = 4096, | |
base_shift: float = 0.5, | |
max_shift: float = 1.16, | |
): | |
m = (max_shift - base_shift) / (max_seq_len - base_seq_len) | |
b = base_shift - m * base_seq_len | |
mu = image_seq_len * m + b | |
return mu | |
def retrieve_timesteps( | |
scheduler, | |
num_inference_steps: Optional[int] = None, | |
device: Optional[Union[str, torch.device]] = None, | |
timesteps: Optional[List[int]] = None, | |
sigmas: Optional[List[float]] = None, | |
**kwargs, | |
): | |
if timesteps is not None and sigmas is not None: | |
raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values") | |
if timesteps is not None: | |
scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs) | |
timesteps = scheduler.timesteps | |
num_inference_steps = len(timesteps) | |
elif sigmas is not None: | |
scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs) | |
timesteps = scheduler.timesteps | |
num_inference_steps = len(timesteps) | |
else: | |
scheduler.set_timesteps(num_inference_steps, device=device, **kwargs) | |
timesteps = scheduler.timesteps | |
return timesteps, num_inference_steps | |
def flux_pipe_call_that_returns_an_iterable_of_images( | |
self, | |
prompt: Union[str, List[str]] = None, | |
prompt_2: Optional[Union[str, List[str]]] = None, | |
negative_prompt: Optional[Union[str, List[str]]] = None, | |
height: Optional[int] = None, | |
width: Optional[int] = None, | |
num_inference_steps: int = 28, | |
timesteps: List[int] = None, | |
guidance_scale: float = 3.5, | |
num_images_per_prompt: Optional[int] = 1, | |
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, | |
latents: Optional[torch.FloatTensor] = None, | |
prompt_embeds: Optional[torch.FloatTensor] = None, | |
pooled_prompt_embeds: Optional[torch.FloatTensor] = None, | |
output_type: Optional[str] = "pil", | |
return_dict: bool = True, | |
joint_attention_kwargs: Optional[Dict[str, Any]] = None, | |
max_sequence_length: int = 512, | |
good_vae: Optional[Any] = None, | |
): | |
# Set default height and width | |
height = height or self.default_sample_size * self.vae_scale_factor | |
width = width or self.default_sample_size * self.vae_scale_factor | |
# Validate inputs | |
self.check_inputs( | |
prompt, | |
prompt_2, | |
height, | |
width, | |
prompt_embeds=prompt_embeds, | |
pooled_prompt_embeds=pooled_prompt_embeds, | |
max_sequence_length=max_sequence_length, | |
) | |
self._guidance_scale = guidance_scale | |
self._joint_attention_kwargs = joint_attention_kwargs | |
self._interrupt = False | |
batch_size = 1 if isinstance(prompt, str) else len(prompt) | |
device = self._execution_device | |
lora_scale = joint_attention_kwargs.get("scale", None) if joint_attention_kwargs is not None else None | |
# Encode the positive prompt | |
prompt_embeds_pos, pooled_prompt_embeds_pos, text_ids_pos = self.encode_prompt( | |
prompt=prompt, | |
prompt_2=prompt_2, | |
prompt_embeds=prompt_embeds, | |
pooled_prompt_embeds=pooled_prompt_embeds, | |
device=device, | |
num_images_per_prompt=num_images_per_prompt, | |
max_sequence_length=max_sequence_length, | |
lora_scale=lora_scale, | |
) | |
# Encode the negative prompt if provided | |
if negative_prompt is not None: | |
prompt_embeds_neg, pooled_prompt_embeds_neg, text_ids_neg = self.encode_prompt( | |
prompt=negative_prompt, | |
prompt_2=None, # Assuming no secondary prompt for negative | |
prompt_embeds=None, | |
pooled_prompt_embeds=None, | |
device=device, | |
num_images_per_prompt=num_images_per_prompt, | |
max_sequence_length=max_sequence_length, | |
lora_scale=lora_scale, | |
) | |
else: | |
# Fallback to positive embeddings if no negative prompt is provided | |
prompt_embeds_neg = prompt_embeds_pos | |
pooled_prompt_embeds_neg = pooled_prompt_embeds_pos | |
text_ids_neg = text_ids_pos | |
# Prepare latents | |
num_channels_latents = self.transformer.config.in_channels // 4 | |
latents, latent_image_ids = self.prepare_latents( | |
batch_size * num_images_per_prompt, | |
num_channels_latents, | |
height, | |
width, | |
prompt_embeds_pos.dtype, | |
device, | |
generator, | |
latents, | |
) | |
# Set up timesteps | |
sigmas = np.linspace(1.0, 1 / num_inference_steps, num_inference_steps) | |
image_seq_len = latents.shape[1] | |
mu = calculate_shift( | |
image_seq_len, | |
self.scheduler.config.base_image_seq_len, | |
self.scheduler.config.max_image_seq_len, | |
self.scheduler.config.base_shift, | |
self.scheduler.config.max_shift, | |
) | |
timesteps, num_inference_steps = retrieve_timesteps( | |
self.scheduler, | |
num_inference_steps, | |
device, | |
timesteps, | |
sigmas, | |
mu=mu, | |
) | |
self._num_timesteps = len(timesteps) | |
guidance = ( | |
torch.full([1], guidance_scale, device=device, dtype=torch.float32).expand(latents.shape[0]) | |
if self.transformer.config.guidance_embeds | |
else None | |
) | |
# Denoising loop | |
for i, t in enumerate(timesteps): | |
if self._interrupt: | |
continue | |
timestep = t.expand(latents.shape[0]).to(latents.dtype) | |
print(f"Step {i + 1}/{num_inference_steps} - Timestep: {timestep.item()}\n") | |
# Compute noise prediction for positive prompt | |
noise_pred_pos = self.transformer( | |
hidden_states=latents, | |
timestep=timestep / 1000, | |
guidance=guidance, | |
pooled_projections=pooled_prompt_embeds_pos, | |
encoder_hidden_states=prompt_embeds_pos, | |
txt_ids=text_ids_pos, | |
img_ids=latent_image_ids, | |
joint_attention_kwargs=self.joint_attention_kwargs, | |
return_dict=False, | |
)[0] | |
# Compute noise prediction for negative prompt | |
noise_pred_neg = self.transformer( | |
hidden_states=latents, | |
timestep=timestep / 1000, | |
guidance=guidance, | |
pooled_projections=pooled_prompt_embeds_neg, | |
encoder_hidden_states=prompt_embeds_neg, | |
txt_ids=text_ids_neg, | |
img_ids=latent_image_ids, | |
joint_attention_kwargs=self.joint_attention_kwargs, | |
return_dict=False, | |
)[0] | |
# Combine noise predictions using guidance scale | |
noise_pred = noise_pred_neg + guidance_scale * (noise_pred_pos - noise_pred_neg) | |
# Generate intermediate image | |
latents_for_image = self._unpack_latents(latents, height, width, self.vae_scale_factor) | |
latents_for_image = (latents_for_image / self.vae.config.scaling_factor) + self.vae.config.shift_factor | |
image = self.vae.decode(latents_for_image, return_dict=False)[0] | |
yield self.image_processor.postprocess(image, output_type=output_type)[0] | |
# Update latents with combined noise prediction | |
latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0] | |
torch.cuda.empty_cache() | |
# Final image generation | |
latents = self._unpack_latents(latents, height, width, self.vae_scale_factor) | |
latents = (latents / good_vae.config.scaling_factor) + good_vae.config.shift_factor | |
image = good_vae.decode(latents, return_dict=False)[0] | |
self.maybe_free_model_hooks() | |
torch.cuda.empty_cache() | |
yield self.image_processor.postprocess(image, output_type=output_type)[0] | |
#--------------------------------------------------Model Initialization-----------------------------------------------------------------------------------------# | |
dtype = torch.bfloat16 | |
device = "cuda" if torch.cuda.is_available() else "cpu" | |
base_model = "black-forest-labs/FLUX.1-dev" | |
#TAEF1 is very tiny autoencoder which uses the same "latent API" as FLUX.1's VAE. FLUX.1 is useful for real-time previewing of the FLUX.1 generation process.# | |
taef1 = AutoencoderTiny.from_pretrained("madebyollin/taef1", torch_dtype=dtype).to(device) | |
good_vae = AutoencoderKL.from_pretrained(base_model, subfolder="vae", torch_dtype=dtype).to(device) | |
pipe = DiffusionPipeline.from_pretrained(base_model, torch_dtype=dtype, vae=taef1).to(device) | |
pipe_i2i = AutoPipelineForImage2Image.from_pretrained(base_model, | |
vae=good_vae, | |
transformer=pipe.transformer, | |
text_encoder=pipe.text_encoder, | |
tokenizer=pipe.tokenizer, | |
text_encoder_2=pipe.text_encoder_2, | |
tokenizer_2=pipe.tokenizer_2, | |
torch_dtype=dtype | |
) | |
pipe.flux_pipe_call_that_returns_an_iterable_of_images = flux_pipe_call_that_returns_an_iterable_of_images.__get__(pipe) | |
class calculateDuration: | |
def __init__(self, activity_name=""): | |
self.activity_name = activity_name | |
def __enter__(self): | |
self.start_time = time.time() | |
return self | |
def __exit__(self, exc_type, exc_value, traceback): | |
self.end_time = time.time() | |
self.elapsed_time = self.end_time - self.start_time | |
if self.activity_name: | |
print(f"Elapsed time for {self.activity_name}: {self.elapsed_time:.6f} seconds") | |
else: | |
print(f"Elapsed time: {self.elapsed_time:.6f} seconds") | |
def update_selection(evt: gr.SelectData, width, height, aspect_ratio): | |
selected_lora = loras[evt.index] | |
new_placeholder = f"Type a prompt for {selected_lora['title']}" | |
new_aspect_ratio = aspect_ratio | |
lora_repo = selected_lora["repo"] | |
updated_text = f"### Selected: [{lora_repo}](https://huggingface.co/{lora_repo}) ✅" | |
# aspect will now use ratios if implemented, like 16:9, 4:3, 1:1, etc. | |
if "aspect" in selected_lora: | |
try: | |
new_aspect_ratio = selected_lora["aspect"] | |
width, height = update_dimensions_on_ratio(new_aspect_ratio, height) | |
except Exception as e: | |
print(f"\nError in update selection aspect ratios:{e}\nSkipping") | |
new_aspect_ratio = aspect_ratio | |
width = width | |
height = height | |
return ( | |
gr.update(placeholder=new_placeholder), | |
updated_text, | |
evt.index, | |
width, | |
height, | |
new_aspect_ratio, | |
upd_prompt_notes_by_index(evt.index) | |
) | |
def generate_image(prompt_mash, negative_prompt, steps, seed, cfg_scale, width, height, lora_scale, progress): | |
pipe.to("cuda") | |
generator = torch.Generator(device="cuda").manual_seed(seed) | |
flash_attention_enabled = torch.backends.cuda.flash_sdp_enabled() | |
if flash_attention_enabled: | |
pipe.attn_implementation="flash_attention_2" | |
if IS_SHARED_SPACE: | |
pipe.vae.enable_tiling() # For larger resolutions if needed | |
else: | |
# Compile UNet | |
#pipe.transformer = torch.compile(pipe.transformer, mode="reduce-overhead") | |
#pipe.enable_model_cpu_offload() #for smaller GPUs | |
pipe.vae.enable_slicing() | |
# Disable unnecessary features | |
pipe.safety_checker = None | |
print(f"\nGenerating image with prompt: {prompt_mash}\n") | |
approx_tokens= approximate_token_count(prompt_mash) | |
if approx_tokens > 76: | |
print(f"\nSplitting prompt due to length: {approx_tokens}\n") | |
prompt, prompt2 = split_prompt_precisely(prompt_mash) | |
else: | |
prompt = prompt_mash | |
prompt2 = None | |
with calculateDuration("Generating image"): | |
# Generate image | |
for img in pipe.flux_pipe_call_that_returns_an_iterable_of_images( | |
prompt=prompt, | |
prompt_2=prompt2, | |
num_inference_steps=steps, | |
guidance_scale=cfg_scale, | |
width=width, | |
height=height, | |
generator=generator, | |
joint_attention_kwargs={"scale": lora_scale}, | |
output_type="pil", | |
good_vae=good_vae, | |
): | |
yield img | |
def generate_image_to_image(prompt_mash, negative_prompt, image_input_path, image_strength, steps, cfg_scale, width, height, lora_scale, seed, progress): | |
generator = torch.Generator(device="cuda").manual_seed(seed) | |
pipe_i2i.to("cuda") | |
flash_attention_enabled = torch.backends.cuda.flash_sdp_enabled() | |
if flash_attention_enabled: | |
pipe_i2i.attn_implementation="flash_attention_2" | |
if IS_SHARED_SPACE: | |
pipe_i2i.vae.enable_tiling() # For larger resolutions if needed | |
else: | |
# Compile UNet | |
# pipe.transformer = torch.compile(pipe.transformer, mode="reduce-overhead") # uses the other pipe's transformer | |
# pipe_i2i.enable_model_cpu_offload() #for smaller GPUs | |
pipe_i2i.vae.enable_slicing() | |
# Disable unnecessary features | |
pipe_i2i.safety_checker = None | |
image_input = open_image(image_input_path) | |
print(f"\nGenerating image with prompt: {prompt_mash} and {image_input_path}\n") | |
approx_tokens= approximate_token_count(prompt_mash) | |
if approx_tokens > 76: | |
print(f"\nSplitting prompt due to length: {approx_tokens}\n") | |
prompt, prompt2 = split_prompt_precisely(prompt_mash) | |
else: | |
prompt = prompt_mash | |
prompt2 = None | |
with calculateDuration("Generating image"): | |
# Generate image | |
final_image = pipe_i2i( | |
prompt=prompt, | |
prompt_2=prompt2, | |
image=image_input, | |
strength=image_strength, | |
num_inference_steps=steps, | |
guidance_scale=cfg_scale, | |
width=width, | |
height=height, | |
generator=generator, | |
joint_attention_kwargs={"scale": lora_scale}, | |
output_type="pil", | |
).images[0] | |
return final_image | |
def run_lora(prompt, map_option, image_input, image_strength, cfg_scale, steps, selected_index, randomize_seed, seed, width, height, lora_scale, enlarge, use_conditioned_image=False, progress=gr.Progress(track_tqdm=True)): | |
if selected_index is None: | |
raise gr.Error("You must select a LoRA before proceeding.🧨") | |
print(f"input Image: {image_input}\n") | |
# handle selecting a conditioned image from the gallery | |
global current_prerendered_image | |
conditioned_image=None | |
formatted_map_option = map_option.lower().replace(' ', '_') | |
if use_conditioned_image: | |
print(f"Conditioned path: {current_prerendered_image.value}.. converting to RGB\n") | |
# ensure the conditioned image is an image and not a string, cannot use RGBA | |
if isinstance(current_prerendered_image.value, str): | |
conditioned_image = open_image(current_prerendered_image.value).convert("RGB") | |
image_input = resize_and_crop_image(conditioned_image, width, height) | |
print(f"Conditioned Image: {image_input.size}.. converted to RGB and resized\n") | |
if map_option != "Prompt": | |
prompt = PROMPTS[map_option] | |
negative_prompt = NEGATIVE_PROMPTS.get(map_option, "") | |
selected_lora = loras[selected_index] | |
lora_path = selected_lora["repo"] | |
trigger_word = selected_lora["trigger_word"] | |
if(trigger_word): | |
if "trigger_position" in selected_lora: | |
if selected_lora["trigger_position"] == "prepend": | |
prompt_mash = f"{trigger_word} {prompt}" | |
else: | |
prompt_mash = f"{prompt} {trigger_word}" | |
else: | |
prompt_mash = f"{trigger_word} {prompt}" | |
else: | |
prompt_mash = prompt | |
with calculateDuration("Unloading LoRA"): | |
pipe.unload_lora_weights() | |
pipe_i2i.unload_lora_weights() | |
#LoRA weights flow | |
with calculateDuration(f"Loading LoRA weights for {selected_lora['title']}"): | |
pipe_to_use = pipe_i2i if image_input is not None else pipe | |
weight_name = selected_lora.get("weights", None) | |
pipe_to_use.load_lora_weights( | |
lora_path, | |
weight_name=weight_name, | |
low_cpu_mem_usage=True | |
) | |
with calculateDuration("Randomizing seed"): | |
if randomize_seed: | |
seed = random.randint(0, MAX_SEED) | |
if(image_input is not None): | |
print(f"\nGenerating image to image with seed: {seed}\n") | |
generated_image = generate_image_to_image(prompt_mash, negative_prompt, image_input, image_strength, steps, cfg_scale, width, height, lora_scale, seed, progress) | |
if enlarge: | |
upscaled_image = upscale_image(generated_image, max(1.0,min((TARGET_SIZE[0]/width),(TARGET_SIZE[1]/height)))) | |
else: | |
upscaled_image = generated_image | |
# Save the upscaled image to a temporary file | |
with NamedTemporaryFile(delete=False, suffix=".png", prefix=f"{formatted_map_option}_") as tmp_upscaled: | |
upscaled_image.save(tmp_upscaled.name, format="PNG") | |
temp_files.append(tmp_upscaled.name) | |
print(f"Upscaled image saved to {tmp_upscaled.name}") | |
final_image = tmp_upscaled.name | |
yield final_image, seed, gr.update(visible=False) | |
else: | |
image_generator = generate_image(prompt_mash, negative_prompt, steps, seed, cfg_scale, width, height, lora_scale, progress) | |
final_image = None | |
step_counter = 0 | |
for image in image_generator: | |
step_counter+=1 | |
generated_image = image | |
progress_bar = f'<div class="progress-container"><div class="progress-bar" style="--current: {step_counter}; --total: {steps};"></div></div>' | |
yield image, seed, gr.update(value=progress_bar, visible=True) | |
if enlarge: | |
upscaled_image = upscale_image(generated_image, max(1.0,min((TARGET_SIZE[0]/width),(TARGET_SIZE[1]/height)))) | |
else: | |
upscaled_image = generated_image | |
# Save the upscaled image to a temporary file | |
with NamedTemporaryFile(delete=False, suffix=".png", prefix=f"{formatted_map_option}_") as tmp_upscaled: | |
upscaled_image.save(tmp_upscaled.name, format="PNG") | |
temp_files.append(tmp_upscaled.name) | |
print(f"Upscaled image saved to {tmp_upscaled.name}") | |
final_image = tmp_upscaled.name | |
yield final_image, seed, gr.update(value=progress_bar, visible=False) | |
def get_huggingface_safetensors(link): | |
split_link = link.split("/") | |
if(len(split_link) == 2): | |
model_card = ModelCard.load(link) | |
base_model = model_card.data.get("base_model") | |
print(base_model) | |
#Allows Both | |
if base_model not in MODELS: | |
#if((base_model != "black-forest-labs/FLUX.1-dev") and (base_model != "black-forest-labs/FLUX.1-schnell")): | |
raise Exception("Flux LoRA Not Found!") | |
# Only allow "black-forest-labs/FLUX.1-dev" | |
#if base_model != "black-forest-labs/FLUX.1-dev": | |
#raise Exception("Only FLUX.1-dev is supported, other LoRA models are not allowed!") | |
image_path = model_card.data.get("widget", [{}])[0].get("output", {}).get("url", None) | |
trigger_word = model_card.data.get("instance_prompt", "") | |
image_url = f"https://huggingface.co/{link}/resolve/main/{image_path}" if image_path else None | |
fs = HfFileSystem() | |
try: | |
list_of_files = fs.ls(link, detail=False) | |
for file in list_of_files: | |
if(file.endswith(".safetensors")): | |
safetensors_name = file.split("/")[-1] | |
if (not image_url and file.lower().endswith((".jpg", ".jpeg", ".png", ".webp"))): | |
image_elements = file.split("/") | |
image_url = f"https://huggingface.co/{link}/resolve/main/{image_elements[-1]}" | |
except Exception as e: | |
print(e) | |
gr.Warning(f"You didn't include a link neither a valid Hugging Face repository with a *.safetensors LoRA") | |
raise Exception(f"You didn't include a link neither a valid Hugging Face repository with a *.safetensors LoRA") | |
return split_link[1], link, safetensors_name, trigger_word, image_url | |
def check_custom_model(link): | |
if(link.startswith("https://")): | |
if(link.startswith("https://huggingface.co") or link.startswith("https://www.huggingface.co")): | |
link_split = link.split("huggingface.co/") | |
return get_huggingface_safetensors(link_split[1]) | |
else: | |
return get_huggingface_safetensors(link) | |
def add_custom_lora(custom_lora): | |
global loras | |
if(custom_lora): | |
try: | |
title, repo, path, trigger_word, image = check_custom_model(custom_lora) | |
print(f"Loaded custom LoRA: {repo}") | |
card = f''' | |
<div class="custom_lora_card"> | |
<span>Loaded custom LoRA:</span> | |
<div class="card_internal"> | |
<img src="{image}" /> | |
<div> | |
<h3>{title}</h3> | |
<small>{"Using: <code><b>"+trigger_word+"</code></b> as the trigger word" if trigger_word else "No trigger word found. If there's a trigger word, include it in your prompt"}<br></small> | |
</div> | |
</div> | |
</div> | |
''' | |
existing_item_index = next((index for (index, item) in enumerate(loras) if item['repo'] == repo), None) | |
if(not existing_item_index): | |
new_item = { | |
"image": image, | |
"title": title, | |
"repo": repo, | |
"weights": path, | |
"trigger_word": trigger_word | |
} | |
print(new_item) | |
existing_item_index = len(loras) | |
loras.append(new_item) | |
return gr.update(visible=True, value=card), gr.update(visible=True), gr.Gallery(selected_index=None), f"Custom: {path}", existing_item_index, trigger_word | |
except Exception as e: | |
gr.Warning(f"Invalid LoRA: either you entered an invalid link, or a non-FLUX LoRA") | |
return gr.update(visible=True, value=f"Invalid LoRA: either you entered an invalid link, a non-FLUX LoRA"), gr.update(visible=False), gr.update(), "", None, "" | |
else: | |
return gr.update(visible=False), gr.update(visible=False), gr.update(), "", None, "" | |
def remove_custom_lora(): | |
return gr.update(visible=False), gr.update(visible=False), gr.update(), "", None, "" | |
def on_prerendered_gallery_selection(event_data: gr.SelectData): | |
global current_prerendered_image | |
selected_index = event_data.index | |
selected_image = pre_rendered_maps_paths[selected_index] | |
print(f"Gallery Image Selected: {selected_image}\n") | |
current_prerendered_image.value = selected_image | |
return current_prerendered_image | |
def update_prompt_visibility(map_option): | |
is_visible = (map_option == "Prompt") | |
return ( | |
gr.update(visible=is_visible), | |
gr.update(visible=is_visible), | |
gr.update(visible=is_visible) | |
) | |
def composite_with_control_sync(input_image, sketch_image, slider_value): | |
# Load the images using open_image() if they are provided as file paths. | |
in_img = open_image(input_image) if isinstance(input_image, str) else input_image | |
sk_img_path, _ = get_image_from_dict(sketch_image) | |
sk_img = open_image(sk_img_path) | |
# Resize sketch image if dimensions don't match input image. | |
if in_img.size != sk_img.size: | |
sk_img = sk_img.resize(in_img.size, Image.LANCZOS) | |
# Now composite using the original alpha_composite_with_control function. | |
result_img = alpha_composite_with_control(in_img, sk_img, slider_value) | |
return result_img | |
def replace_input_with_sketch_image(sketch_image): | |
print(f"Sketch Image: {sketch_image}\n") | |
sketch, is_dict = get_image_from_dict(sketch_image) | |
return sketch | |
def on_input_image_change(image_path): | |
if image_path is None: | |
gr.Warning("Please upload an Input Image to get started.") | |
return None, gr.update() | |
img, img_path = convert_to_rgba_png(image_path) | |
with Image.open(img_path) as pil_img: | |
width, height = pil_img.size | |
return [img_path, gr.update(width=width, height=height)] | |
def update_sketch_dimensions(input_image, sketch_image): | |
# Load the images using open_image() if they are provided as file paths. | |
in_img = open_image(input_image) if isinstance(input_image, str) else input_image | |
sk_img_path, _ = get_image_from_dict(sketch_image) | |
sk_img = open_image(sk_img_path) | |
# Resize sketch image if dimensions don't match input image. | |
if (in_img) and (in_img.size != sk_img.size): | |
sk_img = sk_img.resize(in_img.size, Image.LANCZOS) | |
return [sk_img, gr.update(width=in_img.width, height=in_img.height)] | |
else: | |
return [sk_img, gr.update()] | |
def getVersions(): | |
return versions_html() | |
run_lora.zerogpu = True | |
gr.set_static_paths(paths=["images/","images/images","images/prerendered","LUT/","fonts/", "assets/"]) | |
title = "MapLore Magic (Development Features for HexaGrid)" | |
with gr.Blocks(css_paths="style_20250314.css", title=title, theme='Surn/beeuty@==0.5.12', delete_cache=(43200, 43200), head_paths="head.htm") as app: | |
with gr.Row(): | |
gr.Markdown(""" | |
# MapLore Magic (Development Features for HexaGrid) | |
## This project includes features that did not make it into the main project! ⬢""", elem_classes="intro") | |
with gr.Row(): | |
with gr.Accordion("Welcome to MapLore Magic, the ultimate tool for transforming your images into mesmerizing artworks. Whether you're a tabletop game enthusiast, a digital artist, or someone who loves unique patterns, LandLore Magic has something for you.", open=False, elem_classes="intro"): | |
gr.Markdown (""" | |
## Drop an image into the Input Image and get started! | |
## What is MapLore Magic? | |
MapLore Magic is a web-based application that allows you to transform your images into stunning artworks. You can customize various aspects of your images, apply filters, and use advanced AI models to generate unique visuals. | |
### What Can You Do? | |
- **AI-Powered Image Generation:** Use advanced AI models to generate images based on your prompts. | |
- **Sketch, Upload, or Select:** Guide your AI image generation with sketches, uploaded images, or pre-rendered templates. | |
- **Image Filter [Look-Up Table (LUT)] Application:** Apply filters (LUTs) to your images for color grading and enhancement. | |
- **Pre-rendered Maps:** Access a library of pre-rendered maps for quick and easy customization. | |
- **Composite Images:** Composite images with control over opacity and color. | |
- **Sketch Pad:** Draw or upload sketches to guide image generation. | |
### Why You'll Love It | |
- **Fun and Easy to Use:** With an intuitive interface and real-time previews, creating unique images has never been this fun! | |
- **Endless Creativity:** Unleash your creativity with endless customization options and see your images transform in unique ways. | |
- **Advanced AI Models:** Leverage advanced AI models and LoRA weights for high-quality image generation and customization. | |
### Get Started | |
1. **Upload or Generate an Image:** Start by uploading your own image or generate one using our AI-powered tool. | |
2. **Customize Your Image:** Play around with the settings to create the perfect image. | |
3. **Download and Share:** Once you're happy with your creation, download it and share it with the world! | |
### Advanced Features | |
- **Generative AI Integration:** Utilize models like `black-forest-labs/FLUX.1-dev` and various LoRA weights for generating unique images. | |
- **Pre-rendered Maps:** Access a library of pre-rendered maps for quick and easy customization. | |
- **Image Filter [Look-Up Table (LUT)] Application:** Apply filters (LUTs) to your images for color grading and enhancement. | |
- **Composite Images:** Composite images with control over opacity and color. | |
- **Sketch Pad:** Draw or upload sketches to guide image generation. | |
Join the creative journey with MapLore Magic today! | |
""", elem_classes="intro") | |
selected_index = gr.State(None) | |
with gr.Row(): | |
with gr.Column(scale=2): | |
progress_bar = gr.Markdown(elem_id="progress",visible=False) | |
input_image = gr.Image( | |
label="Input Image", | |
type="filepath", | |
interactive=True, | |
elem_classes="centered solid imgcontainer", | |
key="imgInput", | |
image_mode="RGB", | |
format="PNG" | |
) | |
with gr.Column(scale=1): | |
with gr.Accordion("Image Filters", open = False): | |
with gr.Row(): | |
with gr.Column(): | |
lut_filename = gr.Textbox( | |
value="", | |
label="Look Up Table (LUT) File Name", | |
elem_id="lutFileName") | |
with gr.Column(): | |
lut_file = gr.File( | |
value=None, | |
file_count="single", | |
file_types=[".cube"], | |
type="filepath", | |
label="LUT cube File", | |
height=120) | |
with gr.Row(): | |
lut_intensity = gr.Slider(label="Filter Intensity", minimum=-200, maximum=200, value=100, info="0 none, negative inverts the filter", interactive=True) | |
apply_lut_button = gr.Button("Apply Filter (LUT)", elem_classes="solid", elem_id="apply_lut_button") | |
with gr.Row(): | |
lut_example_image = gr.Image(type="pil", label="Filter (LUT) Example Image", value=default_lut_example_img, format="png") | |
with gr.Row(): | |
with gr.Column(): | |
gr.Markdown(""" | |
### Included Filters (LUTs) | |
Try on Example Image then APPLY FILTER to Input Image. | |
*-none.cube files are placebo controls | |
""", elem_id="lut_markdown") | |
with gr.Column(): | |
gr.Examples(elem_id="lut_examples", | |
examples=[[f] for f in lut_files], | |
inputs=[lut_filename], | |
outputs=[lut_filename], | |
label="Select a Filter (LUT) file to populate the LUT File Name field", | |
examples_per_page = 25, | |
) | |
lut_file.change(get_filename, inputs=[lut_file], outputs=[lut_filename]) | |
lut_filename.change(show_lut, inputs=[lut_filename, input_image, lut_intensity], outputs=[lut_example_image], scroll_to_output=True) | |
lut_intensity.change(show_lut, inputs=[lut_filename, input_image, lut_intensity], outputs=[lut_example_image]) | |
apply_lut_button.click( | |
lambda lut_filename, input_image, lut_intensity: gr.Warning("Please upload an Input Image to get started.") if input_image is None else apply_lut_to_image_path(lut_filename, input_image, lut_intensity)[1], | |
inputs=[lut_filename, input_image, lut_intensity], | |
outputs=[input_image], | |
scroll_to_output=True | |
) | |
with gr.Accordion("Color Composite", open = False): | |
with gr.Row(): | |
composite_color = gr.ColorPicker(label="Color", value="#ede9ac44") | |
composite_opacity = gr.Slider(label="Opacity %", minimum=0, maximum=100, value=50, interactive=True) | |
with gr.Row(): | |
composite_button = gr.Button("Composite", elem_classes="solid") | |
with gr.Accordion("Sketch Pad", open = False): | |
with gr.Row(): | |
sketch_image = gr.Sketchpad( | |
label="Sketch Image", | |
type="filepath", | |
#invert_colors=True, | |
#sources=['upload','canvas'], | |
#tool=['editor','select','color-sketch'], | |
placeholder="Draw a sketch or upload an image. Currently broken in gradio 5.17.1", | |
interactive=True, | |
elem_classes="centered solid imgcontainer", | |
key="imgSketch", | |
image_mode="RGBA", | |
format="PNG", | |
brush=gr.Brush(), | |
height=300 | |
) | |
with gr.Row(): | |
with gr.Column(scale=1): | |
sketch_replace_input_image_button = gr.Button( | |
"Replace Input Image with sketch", | |
elem_id="sketch_replace_input_image_button", | |
elem_classes="solid" | |
) | |
with gr.Column(scale=2): | |
alpha_composite_slider = gr.Slider(0,100,50,0.5, label="Alpha Composite Sketch to Input Image", elem_id="alpha_composite_slider") | |
with gr.Row(): | |
with gr.Accordion("Generative AI", open = True ): | |
with gr.Column(): | |
map_options = gr.Dropdown( | |
label="Map Options*", | |
choices=list(PROMPTS.keys()), | |
value="Alien Landscape", | |
elem_classes="solid", | |
scale=0 | |
) | |
prompt = gr.Textbox( | |
label="Prompt", | |
visible=False, | |
elem_classes="solid", | |
value="Planetary overhead view, directly from above, centered on the planet’s surface, (rectangular tabletop_map) alien planet map, Battletech_boardgame scifi world with forests, lakes, oceans, continents and snow at the top and bottom, (middle is dark, no_reflections, no_shadows), looking straight down.", | |
lines=4 | |
) | |
negative_prompt_textbox = gr.Textbox( | |
label="Negative Prompt", | |
visible=False, | |
elem_classes="solid", | |
value="Earth, low quality, bad anatomy, blurry, cropped, worst quality, shadows, people, humans, reflections, shadows, realistic map of the Earth, isometric, text" | |
) | |
prompt_notes_label = gr.Label( | |
"Choose a LoRa style or add an image. YOU MUST CLEAR THE IMAGE TO START OVER ", | |
elem_classes="solid centered small", | |
show_label=False, | |
visible=False | |
) | |
# Keep the change event to maintain functionality | |
map_options.change( | |
fn=update_prompt_visibility, | |
inputs=[map_options], | |
outputs=[prompt, negative_prompt_textbox, prompt_notes_label] | |
) | |
with gr.Row(): | |
with gr.Column(scale=1): | |
generate_button = gr.Button("Generate From Map Options, Input Image and LoRa Style", variant="primary", elem_id="gen_btn") | |
with gr.Accordion("Image Styles*", open=False): | |
selected_info = gr.Markdown("") | |
lora_gallery = gr.Gallery( | |
[(item["image"], item["title"]) for item in loras], | |
label="LoRA Styles", | |
allow_preview=False, | |
columns=3, | |
elem_id="lora_gallery", | |
show_share_button=False | |
) | |
with gr.Accordion("Custom LoRA", open=False): | |
with gr.Group(): | |
custom_lora = gr.Textbox(label="Enter Custom LoRA. **NOT TESTED**", placeholder="prithivMLmods/Canopus-LoRA-Flux-Anime") | |
gr.Markdown("[Check the list of FLUX LoRA's](https://huggingface.co/models?other=base_model:adapter:black-forest-labs/FLUX.1-dev)", elem_id="lora_list") | |
custom_lora_info = gr.HTML(visible=False) | |
custom_lora_button = gr.Button("Remove custom LoRA", visible=False) | |
with gr.Column(scale=2): | |
generate_input_image_from_gallery = gr.Button( | |
"Generate AI Image from Template Image", | |
elem_id="generate_input_image_from_gallery", | |
elem_classes="solid", | |
variant="primary" | |
) | |
with gr.Accordion("Template Images", open = False): | |
with gr.Row(): | |
with gr.Column(scale=1): | |
# Gallery from PRE_RENDERED_IMAGES GOES HERE | |
prerendered_image_gallery = gr.Gallery(label="Template Gallery", show_label=True, value=build_prerendered_images_by_quality(3,'thumbnail'), elem_id="gallery", elem_classes="solid", type="filepath", columns=[3], rows=[3], preview=False ,object_fit="contain", height="auto", format="png",allow_preview=False) | |
with gr.Column(scale=1): | |
# def handle_login(request: gr.Request): | |
# # Extract user information from the request | |
# user_info = { | |
# "username": request.username, | |
# "session_hash": request.session_hash, | |
# "headers": dict(request.headers), | |
# "client": request.client, | |
# "query_params": dict(request.query_params), | |
# "path_params": dict(request.path_params) | |
# } | |
# print(f"\n{user_info}\n") | |
# return user_info | |
replace_input_image_button = gr.Button( | |
"Replace Input Image", | |
elem_id="prerendered_replace_input_image_button", | |
elem_classes="solid" | |
) | |
# login_button = gr.LoginButton() | |
# user_info_output = gr.JSON(label="User Information") | |
# login_button.click(fn=handle_login, inputs=[], outputs=user_info_output) | |
with gr.Row(): | |
with gr.Accordion("Advanced Settings", open=False): | |
with gr.Row(): | |
image_strength = gr.Slider(label="Image Guidance Strength (prompt percentage)", info="Lower means more image influence", minimum=0.1, maximum=1.0, step=0.01, value=0.85) | |
with gr.Column(): | |
with gr.Row(): | |
cfg_scale = gr.Slider(label="CFG Scale", minimum=1, maximum=20, step=0.5, value=5.0) | |
steps = gr.Slider(label="Steps", minimum=1, maximum=50, step=1, value=30) | |
with gr.Row(): | |
negative_prompt_textbox = gr.Textbox( | |
label="Negative Prompt", | |
visible=False, | |
elem_classes="solid", | |
value="Earth, low quality, bad anatomy, blurry, cropped, worst quality, shadows, people, humans, reflections, shadows, realistic map of the Earth, isometric, text" | |
) | |
# Add Dropdown for sizing of Images, height and width based on selection. Options are 16x9, 16x10, 4x5, 1x1 | |
# The values of height and width are based on common resolutions for each aspect ratio | |
# Default to 16x9, 1024x576 | |
image_size_ratio = gr.Dropdown(label="Image Aspect Ratio", choices=["16:9", "16:10", "4:5", "4:3", "2:1","3:2","1:1", "9:16", "10:16", "5:4", "3:4","1:2", "2:3"], value="16:9", elem_classes="solid", type="value", scale=0, interactive=True) | |
width = gr.Slider(label="Width", minimum=256, maximum=2560, step=16, value=1024, interactive=False) | |
height = gr.Slider(label="Height", minimum=256, maximum=1536, step=64, value=512) | |
enlarge_to_default = gr.Checkbox(label="Auto Enlarge to Default Size", value=False) | |
image_size_ratio.change( | |
fn=update_dimensions_on_ratio, | |
inputs=[image_size_ratio, height], | |
outputs=[width, height] | |
) | |
height.change( | |
fn=lambda *args: update_dimensions_on_ratio(*args)[0], | |
inputs=[image_size_ratio, height], | |
outputs=[width] | |
) | |
with gr.Row(): | |
randomize_seed = gr.Checkbox(False, label="Randomize seed",elem_id="rnd_seed_chk") | |
seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0, randomize=True, elem_id="rnd_seed") | |
lora_scale = gr.Slider(label="LoRA Scale", minimum=0, maximum=3, step=0.01, value=1.01) | |
with gr.Row(): | |
login_button = gr.LoginButton(logout_value=f"Logout({user_info['username']} ({user_info['level']}))", size="md", elem_classes="solid centered", elem_id="hf_login_btn", icon="./assets/favicon.ico") | |
# Create a JSON component to display the user information | |
user_info_output = gr.JSON(label="User Information:") | |
# Set up the event listener for the login button click | |
login_button.click(fn=wait5, inputs=[gr.State("Login/logout"), gr.State(10)], outputs=[] | |
).then( | |
fn=handle_login, | |
inputs=[], | |
outputs=[user_info_output, login_button, user_profile] | |
) | |
with gr.Row(): | |
gr.HTML(value=getVersions(), visible=True, elem_id="versions") | |
# Event Handlers | |
composite_button.click( | |
fn=lambda input_image, composite_color, composite_opacity: gr.Warning("Please upload an Input Image to get started.") if input_image is None else change_color(input_image, composite_color, composite_opacity), | |
inputs=[input_image, composite_color, composite_opacity], | |
outputs=[input_image] | |
) | |
input_image.input( | |
fn=on_input_image_change, | |
inputs=[input_image], | |
outputs=[input_image,sketch_image], scroll_to_output=True, | |
) | |
#use conditioned_image as the input_image for generate_input_image_click | |
generate_input_image_from_gallery.click( | |
fn=run_lora, | |
inputs=[prompt, map_options, input_image, image_strength, cfg_scale, steps, selected_index, randomize_seed, seed, width, height, lora_scale, enlarge_to_default, gr.State(True)], | |
outputs=[input_image, seed, progress_bar], scroll_to_output=True | |
).then( | |
fn=update_sketch_dimensions, | |
inputs=[input_image, sketch_image], | |
outputs=[sketch_image, sketch_image] | |
) | |
prerendered_image_gallery.select( | |
fn=on_prerendered_gallery_selection, | |
inputs=None, | |
outputs=gr.State(current_prerendered_image), # Update the state with the selected image | |
show_api=False, scroll_to_output=True | |
) | |
alpha_composite_slider.change( | |
fn=composite_with_control_sync, | |
inputs=[input_image, sketch_image, alpha_composite_slider], | |
outputs=[input_image], | |
scroll_to_output=True | |
) | |
sketch_replace_input_image_button.click( | |
lambda sketch_image: replace_input_with_sketch_image(sketch_image), | |
inputs=[sketch_image], | |
outputs=[input_image], scroll_to_output=True | |
) | |
# replace input image with selected prerendered image gallery selection | |
replace_input_image_button.click( | |
lambda: current_prerendered_image.value, | |
inputs=None, | |
outputs=[input_image], scroll_to_output=True | |
).then( | |
fn=update_sketch_dimensions, | |
inputs=[input_image, sketch_image], | |
outputs=[sketch_image, sketch_image] | |
) | |
lora_gallery.select( | |
update_selection, | |
inputs=[width, height, image_size_ratio], | |
outputs=[prompt, selected_info, selected_index, width, height, image_size_ratio, prompt_notes_label] | |
) | |
custom_lora.input( | |
add_custom_lora, | |
inputs=[custom_lora], | |
outputs=[custom_lora_info, custom_lora_button, lora_gallery, selected_info, selected_index, prompt] | |
) | |
custom_lora_button.click( | |
remove_custom_lora, | |
outputs=[custom_lora_info, custom_lora_button, lora_gallery, selected_info, selected_index, custom_lora] | |
) | |
gr.on( | |
triggers=[generate_button.click, prompt.submit], | |
fn=run_lora, | |
inputs=[prompt, map_options, input_image, image_strength, cfg_scale, steps, selected_index, randomize_seed, seed, width, height, lora_scale, enlarge_to_default, gr.State(False)], | |
outputs=[input_image, seed, progress_bar] | |
).then( | |
fn=update_sketch_dimensions, | |
inputs=[input_image, sketch_image], | |
outputs=[sketch_image, sketch_image] | |
) | |
load_env_vars(dotenv_path) | |
logging.basicConfig( | |
format="[%(levelname)s] %(asctime)s %(message)s", level=logging.INFO | |
) | |
logging.info("Environment Variables: %s" % os.environ) | |
app.queue() | |
app.launch(allowed_paths=["assets","/","./assets","images","./images", "./images/prerendered"], favicon_path="./assets/favicon.ico", max_file_size="10mb") |