X-HighVoltage-X's picture
Update app.py
b87f9cc verified
import random
from typing import List, Tuple
import gradio as gr
import numpy as np
import spaces
import torch
from diffusers import FluxFillPipeline
from loras import LoRA, loras
MAX_SEED = np.iinfo(np.int32).max
pipe = FluxFillPipeline.from_pretrained(
"black-forest-labs/FLUX.1-Fill-dev",
torch_dtype=torch.bfloat16
)
flux_keywords_available = [
"IMG_1025.HEIC",
"Selfie",
]
def activate_loras(pipe: FluxFillPipeline, loras_with_weights: list[tuple[LoRA, float]]):
adapter_names = []
adapter_weights = []
for lora, weight in loras_with_weights:
print(f"Loading LoRA: {lora.name} with weight {weight}")
pipe.load_lora_weights(lora.id, weight=weight, adapter_name=lora.name)
adapter_names.append(lora.name)
adapter_weights.append(weight)
print(f"Activating adapters: {adapter_names} with weights {adapter_weights}")
pipe.set_adapters(adapter_names, adapter_weights=adapter_weights)
return pipe
def get_loras() -> list[dict]:
return loras
def deactivate_loras(pipe):
print("Unloading all LoRAs...")
pipe.unload_lora_weights()
return pipe
def calculate_optimal_dimensions(image):
original_width, original_height = image.size
MIN_ASPECT_RATIO = 9 / 16
MAX_ASPECT_RATIO = 16 / 9
FIXED_DIMENSION = 1024
original_aspect_ratio = original_width / original_height
if original_aspect_ratio > 1:
width = FIXED_DIMENSION
height = round(FIXED_DIMENSION / original_aspect_ratio)
else:
height = FIXED_DIMENSION
width = round(FIXED_DIMENSION * original_aspect_ratio)
width = (width // 8) * 8
height = (height // 8) * 8
calculated_aspect_ratio = width / height
if calculated_aspect_ratio > MAX_ASPECT_RATIO:
width = int((height * MAX_ASPECT_RATIO // 8) * 8)
elif calculated_aspect_ratio < MIN_ASPECT_RATIO:
height = int((width / MIN_ASPECT_RATIO // 8) * 8)
width = max(width, 576) if width == FIXED_DIMENSION else width
height = max(height, 576) if height == FIXED_DIMENSION else height
return width, height
@spaces.GPU(duration=45)
def inpaint(
image,
mask,
prompt: str = "",
seed: int = 0,
num_inference_steps: int = 28,
guidance_scale: int = 50,
strength: float = 1.0
):
image = image.convert("RGB")
mask = mask.convert("L")
width, height = calculate_optimal_dimensions(image)
pipe.to("cuda")
result = pipe(
image=image,
mask_image=mask,
prompt=prompt,
width=width,
height=height,
num_inference_steps=num_inference_steps,
guidance_scale=guidance_scale,
strength=strength,
generator=torch.Generator().manual_seed(seed)
).images[0]
return result.convert("RGBA"), prompt, seed
def inpaint_api(
image,
mask,
prompt: str,
seed: int,
num_inference_steps: int,
guidance_scale: int,
strength: float,
flux_keywords: List[str] = None,
loras_selected: List[Tuple[str, float]] = None
):
flux_keywords = flux_keywords or []
loras_selected = loras_selected or []
# Convertir nombres a objetos LoRA
selected_loras_with_weights = []
for name, weight_value in loras_selected:
try:
# Convierte explícitamente el peso (que viene como string) a float
weight = float(weight_value)
except (ValueError, TypeError):
# Ignora si el valor no es un número válido (ej: None o string vacío)
print(f"Valor de peso inválido '{weight_value}' para LoRA '{name}', omitiendo.")
continue # Pasa al siguiente LoRA
lora_obj = next((l for l in loras if l.display_name == name), None)
# Ahora la comparación 'weight != 0.0' es segura (float con float)
if lora_obj and weight != 0.0:
selected_loras_with_weights.append((lora_obj, weight))
deactivate_loras(pipe)
if selected_loras_with_weights:
activate_loras(pipe, selected_loras_with_weights)
# Construir prompt final
final_prompt = ""
if flux_keywords:
final_prompt += ", ".join(flux_keywords) + ", "
for lora, _ in selected_loras_with_weights:
if lora.keyword:
if isinstance(lora.keyword, str):
final_prompt += lora.keyword + ", "
else:
final_prompt += ", ".join(lora.keyword) + ", "
if final_prompt:
final_prompt += "\n\n"
final_prompt += prompt
if not isinstance(seed, int) or seed < 0:
seed = random.randint(0, MAX_SEED)
return inpaint(
image=image,
mask=mask,
prompt=final_prompt,
seed=seed,
num_inference_steps=num_inference_steps,
guidance_scale=guidance_scale,
strength=strength
)
# ========================
# UI DIRECTA A inpaint_api
# ========================
with gr.Blocks(title="Flux.1 Fill dev Inpainting with LoRAs", theme=gr.themes.Soft()) as demo:
gr.api(get_loras, api_name="get_loras")
with gr.Row():
with gr.Column(scale=2):
prompt_input = gr.Text(
label="Prompt",
lines=4,
value="a 25 years old woman"
)
seed_slider = gr.Slider(
label="Seed",
minimum=-1,
maximum=MAX_SEED,
step=1,
value=-1,
info="(-1 = Random)",
interactive=True
)
num_inference_steps_input = gr.Number(
label="Inference steps",
value=40, interactive=True
)
guidance_scale_input = gr.Number(
label="Guidance scale",
value=28,
interactive=True
)
strength_input = gr.Number(
label="Strength",
value=1.0,
interactive=True,
maximum=1.0
)
gr.Markdown("### Flux Keywords")
flux_keywords_input = gr.CheckboxGroup(
choices=flux_keywords_available,
label="Flux Keywords"
)
if loras:
gr.Markdown("### Available LoRAs")
lora_names = [l.display_name for l in loras]
loras_selected_input = gr.Dataframe(
type="array",
headers=["LoRA", "Weight"],
value=[[name, 0.0] for name in lora_names],
datatype=["str", "number"], # Primera columna string, segunda número
interactive=[False, True], # Solo la segunda columna editable
static_columns=[0],
label="LoRA selection (Weight 0 = disable)"
)
with gr.Column(scale=3):
image_input = gr.Image(
label="Image",
type="pil"
)
mask_input = gr.Image(
label="Mask",
type="pil"
)
run_btn = gr.Button(
"Run",
variant="primary"
)
with gr.Column(scale=3):
result_image = gr.Image(
label="Result"
)
used_prompt_box = gr.Text(
label="Used prompt",
lines=4
)
used_seed_box = gr.Number(
label="Used seed"
)
run_btn.click(
fn=inpaint_api,
inputs=[
image_input,
mask_input,
prompt_input,
seed_slider,
num_inference_steps_input,
guidance_scale_input,
strength_input,
flux_keywords_input,
loras_selected_input
],
outputs=[
result_image,
used_prompt_box,
used_seed_box
],
api_name="inpaint"
)
if __name__ == "__main__":
demo.launch(share=False, show_error=True)