File size: 8,250 Bytes
058c4cc
b4f4090
058c4cc
2edceaf
40ed941
 
2edceaf
4bccd09
6511472
2818fe4
40ed941
 
2818fe4
f36cdfd
f9a2f23
d1a752e
344145d
058c4cc
09b0e51
 
 
3cb83a6
471ab6b
1a47adc
471ab6b
 
e4fc0d4
1a47adc
7cd35e1
 
 
471ab6b
 
 
 
fa6ee34
2edceaf
 
19a6e63
 
 
12d4d12
19a6e63
2edceaf
09b0e51
2edceaf
 
40ed941
dc7ddb0
5b90323
 
 
 
 
 
b4f4090
5b90323
 
b4f4090
5b90323
 
 
 
 
 
0b8a0be
5b90323
0b8a0be
5b90323
 
0b8a0be
5b90323
 
dc7ddb0
f8c8c82
5596da4
b8d30c6
 
 
 
 
6e2f77a
b8d30c6
058c4cc
40ed941
 
 
d1afbe1
b8d30c6
40ed941
 
 
1a47adc
2edceaf
 
40ed941
 
76371a5
6e2f77a
40ed941
d1afbe1
1a47adc
 
a77f580
b4f4090
dc7ddb0
 
6e2f77a
 
 
 
 
dc7ddb0
 
b4f4090
 
 
c0f4b3e
dc7ddb0
a77f580
dc7ddb0
b87f9cc
 
 
 
 
 
 
 
 
b4f4090
b87f9cc
 
 
b4f4090
1a47adc
b4f4090
 
 
2edceaf
dc7ddb0
b4f4090
dc7ddb0
b4f4090
 
dc7ddb0
b4f4090
 
df03056
 
 
 
dc7ddb0
b4f4090
 
 
1a47adc
dc7ddb0
b4f4090
5596da4
b4f4090
 
 
 
 
 
 
76371a5
b4f4090
 
a77f580
dc7ddb0
 
 
5596da4
9a3470e
2818fe4
b71a2cf
2edceaf
b71a2cf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2818fe4
09b0e51
b71a2cf
 
 
 
edec65b
4d13ffa
 
 
 
 
 
 
dc7ddb0
 
4d13ffa
 
 
2818fe4
 
b71a2cf
 
 
 
 
 
 
 
 
 
 
 
 
 
2818fe4
09b0e51
b71a2cf
 
 
 
 
 
 
 
 
 
 
 
09b0e51
1a47adc
edec65b
1a47adc
b4f4090
 
 
 
 
 
76371a5
edec65b
 
 
ba697f4
 
 
 
 
2149241
1a47adc
 
2edceaf
6e2f77a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
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)