prithivMLmods commited on
Commit
d3d34d3
·
verified ·
1 Parent(s): 5cde6d0

update app

Browse files
Files changed (1) hide show
  1. app.py +167 -238
app.py CHANGED
@@ -1,105 +1,41 @@
1
  import os
2
  import gc
3
  import gradio as gr
 
 
4
  import numpy as np
5
  import spaces
6
  import torch
7
  import random
 
 
 
8
  from PIL import Image
9
- from typing import Iterable
10
- from gradio.themes import Soft
11
- from gradio.themes.utils import colors, fonts, sizes
12
-
13
- colors.orange_red = colors.Color(
14
- name="orange_red",
15
- c50="#FFF0E5",
16
- c100="#FFE0CC",
17
- c200="#FFC299",
18
- c300="#FFA366",
19
- c400="#FF8533",
20
- c500="#FF4500",
21
- c600="#E63E00",
22
- c700="#CC3700",
23
- c800="#B33000",
24
- c900="#992900",
25
- c950="#802200",
26
- )
27
-
28
- class OrangeRedTheme(Soft):
29
- def __init__(
30
- self,
31
- *,
32
- primary_hue: colors.Color | str = colors.gray,
33
- secondary_hue: colors.Color | str = colors.orange_red,
34
- neutral_hue: colors.Color | str = colors.slate,
35
- text_size: sizes.Size | str = sizes.text_lg,
36
- font: fonts.Font | str | Iterable[fonts.Font | str] = (
37
- fonts.GoogleFont("Outfit"), "Arial", "sans-serif",
38
- ),
39
- font_mono: fonts.Font | str | Iterable[fonts.Font | str] = (
40
- fonts.GoogleFont("IBM Plex Mono"), "ui-monospace", "monospace",
41
- ),
42
- ):
43
- super().__init__(
44
- primary_hue=primary_hue,
45
- secondary_hue=secondary_hue,
46
- neutral_hue=neutral_hue,
47
- text_size=text_size,
48
- font=font,
49
- font_mono=font_mono,
50
- )
51
- super().set(
52
- background_fill_primary="*primary_50",
53
- background_fill_primary_dark="*primary_900",
54
- body_background_fill="linear-gradient(135deg, *primary_200, *primary_100)",
55
- body_background_fill_dark="linear-gradient(135deg, *primary_900, *primary_800)",
56
- button_primary_text_color="white",
57
- button_primary_text_color_hover="white",
58
- button_primary_background_fill="linear-gradient(90deg, *secondary_500, *secondary_600)",
59
- button_primary_background_fill_hover="linear-gradient(90deg, *secondary_600, *secondary_700)",
60
- button_primary_background_fill_dark="linear-gradient(90deg, *secondary_600, *secondary_700)",
61
- button_primary_background_fill_hover_dark="linear-gradient(90deg, *secondary_500, *secondary_600)",
62
- button_secondary_text_color="black",
63
- button_secondary_text_color_hover="white",
64
- button_secondary_background_fill="linear-gradient(90deg, *primary_300, *primary_300)",
65
- button_secondary_background_fill_hover="linear-gradient(90deg, *primary_400, *primary_400)",
66
- button_secondary_background_fill_dark="linear-gradient(90deg, *primary_500, *primary_600)",
67
- button_secondary_background_fill_hover_dark="linear-gradient(90deg, *primary_500, *primary_500)",
68
- slider_color="*secondary_500",
69
- slider_color_dark="*secondary_600",
70
- block_title_text_weight="600",
71
- block_border_width="3px",
72
- block_shadow="*shadow_drop_lg",
73
- button_primary_shadow="*shadow_drop_lg",
74
- button_large_padding="11px",
75
- color_accent_soft="*primary_100",
76
- block_label_background_fill="*primary_200",
77
- )
78
-
79
- orange_red_theme = OrangeRedTheme()
80
-
81
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
82
-
83
- print("CUDA_VISIBLE_DEVICES=", os.environ.get("CUDA_VISIBLE_DEVICES"))
84
- print("torch.__version__ =", torch.__version__)
85
- print("Using device:", device)
86
 
87
  from diffusers import FlowMatchEulerDiscreteScheduler
88
  from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
89
  from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
90
  from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3
91
 
 
 
 
 
92
  dtype = torch.bfloat16
93
 
 
 
 
 
 
94
  pipe = QwenImageEditPlusPipeline.from_pretrained(
95
  "Qwen/Qwen-Image-Edit-2509",
96
  transformer=QwenImageTransformer2DModel.from_pretrained(
97
  "prithivMLmods/Qwen-Image-Edit-Rapid-AIO-V19",
98
- #subfolder='transformer',
99
  torch_dtype=dtype,
100
- device_map='cuda'
101
  ),
102
- torch_dtype=dtype
103
  ).to(device)
104
 
105
  try:
@@ -108,8 +44,7 @@ try:
108
  except Exception as e:
109
  print(f"Warning: Could not set FA3 processor: {e}")
110
 
111
- MAX_SEED = np.iinfo(np.int32).max
112
-
113
  ADAPTER_SPECS = {
114
  "Qwen-Image-Edit-2511-Object-Adder": {
115
  "repo": "prithivMLmods/Qwen-Image-Edit-2511-Object-Adder",
@@ -143,86 +78,144 @@ ADAPTER_SPECS = {
143
  },
144
  }
145
 
146
- LOADED_ADAPTERS = set()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
  def update_dimensions_on_upload(image):
149
  if image is None:
150
  return 1024, 1024
151
-
152
- original_width, original_height = image.size
153
-
154
- if original_width > original_height:
155
- new_width = 1024
156
- aspect_ratio = original_height / original_width
157
- new_height = int(new_width * aspect_ratio)
158
  else:
159
- new_height = 1024
160
- aspect_ratio = original_width / original_height
161
- new_width = int(new_height * aspect_ratio)
162
-
163
- new_width = (new_width // 8) * 8
164
- new_height = (new_height // 8) * 8
165
-
166
- return new_width, new_height
167
 
 
 
168
  @spaces.GPU(size="xlarge")
169
  def infer(
170
- images,
171
- prompt,
172
- lora_adapter,
173
- seed,
174
- randomize_seed,
175
- guidance_scale,
176
- steps,
177
- progress=gr.Progress(track_tqdm=True)
178
- ):
179
  gc.collect()
180
  torch.cuda.empty_cache()
181
 
182
- if not images:
183
- raise gr.Error("Please upload at least one image to edit.")
184
-
185
- pil_images = []
186
- if images is not None:
187
- for item in images:
188
- try:
189
- if isinstance(item, tuple) or isinstance(item, list):
190
- path_or_img = item[0]
191
- else:
192
- path_or_img = item
193
-
194
- if isinstance(path_or_img, str):
195
- pil_images.append(Image.open(path_or_img).convert("RGB"))
196
- elif isinstance(path_or_img, Image.Image):
197
- pil_images.append(path_or_img.convert("RGB"))
198
- else:
199
- pil_images.append(Image.open(path_or_img.name).convert("RGB"))
200
- except Exception as e:
201
- print(f"Skipping invalid image item: {e}")
202
- continue
203
-
204
  if not pil_images:
205
- raise gr.Error("Could not process uploaded images.")
 
 
206
 
207
  spec = ADAPTER_SPECS.get(lora_adapter)
208
  if not spec:
209
  raise gr.Error(f"Configuration not found for: {lora_adapter}")
210
 
211
  adapter_name = spec["adapter_name"]
212
-
213
  if adapter_name not in LOADED_ADAPTERS:
214
  print(f"--- Downloading and Loading Adapter: {lora_adapter} ---")
215
  try:
216
- pipe.load_lora_weights(
217
- spec["repo"],
218
- weight_name=spec["weights"],
219
- adapter_name=adapter_name
220
- )
221
  LOADED_ADAPTERS.add(adapter_name)
222
  except Exception as e:
223
  raise gr.Error(f"Failed to load adapter {lora_adapter}: {e}")
224
  else:
225
- print(f"--- Adapter {lora_adapter} is already loaded. ---")
226
 
227
  pipe.set_adapters([adapter_name], adapter_weights=[1.0])
228
 
@@ -230,8 +223,10 @@ def infer(
230
  seed = random.randint(0, MAX_SEED)
231
 
232
  generator = torch.Generator(device=device).manual_seed(seed)
233
- negative_prompt = "worst quality, low quality, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry"
234
-
 
 
235
  width, height = update_dimensions_on_upload(pil_images[0])
236
 
237
  try:
@@ -245,111 +240,45 @@ def infer(
245
  generator=generator,
246
  true_cfg_scale=guidance_scale,
247
  ).images[0]
248
-
249
- return result_image, seed
250
-
251
  except Exception as e:
252
  raise e
253
  finally:
254
  gc.collect()
255
  torch.cuda.empty_cache()
256
 
257
- @spaces.GPU(size="xlarge")
258
- def infer_example(images, prompt, lora_adapter):
259
- if not images:
260
- return None, 0
261
-
262
- if isinstance(images, str):
263
- images_list = [images]
264
- else:
265
- images_list = images
266
-
267
- result, seed = infer(
268
- images=images_list,
269
- prompt=prompt,
270
- lora_adapter=lora_adapter,
271
- seed=0,
272
- randomize_seed=True,
273
- guidance_scale=1.0,
274
- steps=4
275
- )
276
- return result, seed
277
 
278
- css="""
279
- #col-container {
280
- margin: 0 auto;
281
- max-width: 1000px;
282
- }
283
- #main-title h1 {font-size: 2.3em !important;}
284
- """
285
-
286
- with gr.Blocks() as demo:
287
- with gr.Column(elem_id="col-container"):
288
- gr.Markdown("# **Qwen-Image-Edit-Object-Manipulator**", elem_id="main-title")
289
- gr.Markdown("Perform diverse image edits using specialized [LoRA](https://huggingface.co/models?other=base_model:adapter:Qwen/Qwen-Image-Edit-2511) adapters. Upload one or more images.")
290
-
291
- with gr.Row(equal_height=True):
292
- with gr.Column():
293
- images = gr.Gallery(
294
- label="Upload Images",
295
- type="filepath",
296
- columns=2,
297
- rows=1,
298
- height=300,
299
- allow_preview=True
300
- )
301
-
302
- prompt = gr.Text(
303
- label="Edit Prompt",
304
- show_label=True,
305
- placeholder="e.g., transform into anime..",
306
- )
307
-
308
- run_button = gr.Button("Edit Image", variant="primary")
309
-
310
- with gr.Column():
311
- output_image = gr.Image(label="Output Image", interactive=False, format="png", height=363)
312
-
313
- with gr.Row():
314
- lora_adapter = gr.Dropdown(
315
- label="Choose Manipulator",
316
- choices=list(ADAPTER_SPECS.keys()),
317
- value="Qwen-Image-Edit-2511-Object-Adder"
318
- )
319
-
320
- with gr.Accordion("Advanced Settings", open=False, visible=False):
321
- seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0)
322
- randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)
323
- guidance_scale = gr.Slider(label="Guidance Scale", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
324
- steps = gr.Slider(label="Inference Steps", minimum=1, maximum=50, step=1, value=4)
325
-
326
- gr.Examples(
327
- examples=[
328
- [["examples/D.jpg"], "Add the batman logo to the image while preserving the background lighting and surrounding elements maintaining realism and original details.", "Qwen-Image-Edit-2511-Object-Adder"],
329
- [["examples/A.jpg"], "Add the slim rectangular transparent frame sunglasses to the image while preserving the background lighting and surrounding elements maintaining realism and original details.", "Qwen-Image-Edit-2511-Object-Adder"],
330
- [["examples/B.jpeg"], "Remove the necklace and goggles from the image while preserving the background and remaining elements, maintaining realism and original details.", "Qwen-Image-Edit-2511-Object-Remover"],
331
- [["examples/DL2.jpg"], "add the nike tick design inside the red marked area.", "Outfit-Design-Layout"],
332
- [["examples/DL1.jpg"], "add the akatsuki cloud design inside the red marked area.", "Outfit-Design-Layout"],
333
- [["examples/C.png"], "Add the leather cowboy cap to the image while preserving the background lighting and surrounding elements maintaining realism and original details.", "Qwen-Image-Edit-2511-Object-Adder"],
334
- [["examples/ZM.jpg"], "Zoom into the red highlighted area.", "Zoom-Master"],
335
- [["examples/OBJ1.jpg"], "Remove the red highlighted object from the scene.", "QIE-2511-Object-Remover-v2"],
336
- [["examples/OBJ2.jpg"], "Remove the red highlighted object from the scene.", "QIE-2511-Object-Remover-v2"],
337
- [["examples/OE.jpg"], "Extract the clothing and create a flat mockup.", "Extract-Outfit"],
338
- ],
339
- inputs=[images, prompt, lora_adapter],
340
- outputs=[output_image, seed],
341
- fn=infer_example,
342
- cache_examples=False,
343
- label="Examples"
344
- )
345
-
346
- gr.Markdown("[*](https://huggingface.co/spaces/prithivMLmods/Qwen-Image-Edit-2511-LoRAs-Fast)This is still an experimental Space for Qwen-Image-Edit-2511.")
347
-
348
- run_button.click(
349
- fn=infer,
350
- inputs=[images, prompt, lora_adapter, seed, randomize_seed, guidance_scale, steps],
351
- outputs=[output_image, seed]
352
- )
353
 
354
  if __name__ == "__main__":
355
- demo.queue(max_size=30).launch(css=css, theme=orange_red_theme, mcp_server=True, ssr_mode=False, show_error=True)
 
1
  import os
2
  import gc
3
  import gradio as gr
4
+ from gradio import Server
5
+ from fastapi.responses import HTMLResponse
6
  import numpy as np
7
  import spaces
8
  import torch
9
  import random
10
+ import base64
11
+ import json
12
+ from io import BytesIO
13
  from PIL import Image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  from diffusers import FlowMatchEulerDiscreteScheduler
16
  from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
17
  from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
18
  from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3
19
 
20
+ MAX_SEED = np.iinfo(np.int32).max
21
+ LANCZOS = getattr(Image, "Resampling", Image).LANCZOS
22
+
23
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
24
  dtype = torch.bfloat16
25
 
26
+ print("CUDA_VISIBLE_DEVICES=", os.environ.get("CUDA_VISIBLE_DEVICES"))
27
+ print("torch.__version__ =", torch.__version__)
28
+ print("Using device:", device)
29
+
30
+ print("Loading FLUX.2 Klein 9B model base...")
31
  pipe = QwenImageEditPlusPipeline.from_pretrained(
32
  "Qwen/Qwen-Image-Edit-2509",
33
  transformer=QwenImageTransformer2DModel.from_pretrained(
34
  "prithivMLmods/Qwen-Image-Edit-Rapid-AIO-V19",
 
35
  torch_dtype=dtype,
36
+ device_map="cuda",
37
  ),
38
+ torch_dtype=dtype,
39
  ).to(device)
40
 
41
  try:
 
44
  except Exception as e:
45
  print(f"Warning: Could not set FA3 processor: {e}")
46
 
47
+ # ── LoRA adapter registry ──────────────────────────────────────────────────────
 
48
  ADAPTER_SPECS = {
49
  "Qwen-Image-Edit-2511-Object-Adder": {
50
  "repo": "prithivMLmods/Qwen-Image-Edit-2511-Object-Adder",
 
78
  },
79
  }
80
 
81
+ LOADED_ADAPTERS: set = set()
82
+ ADAPTER_NAMES = list(ADAPTER_SPECS.keys())
83
+
84
+ EXAMPLES_CONFIG = [
85
+ {"images": ["examples/D.jpg"], "prompt": "Add the batman logo to the image while preserving the background lighting and surrounding elements maintaining realism and original details.", "lora": "Qwen-Image-Edit-2511-Object-Adder"},
86
+ {"images": ["examples/A.jpg"], "prompt": "Add the slim rectangular transparent frame sunglasses to the image while preserving the background lighting and surrounding elements maintaining realism and original details.", "lora": "Qwen-Image-Edit-2511-Object-Adder"},
87
+ {"images": ["examples/B.jpeg"], "prompt": "Remove the necklace and goggles from the image while preserving the background and remaining elements, maintaining realism and original details.", "lora": "Qwen-Image-Edit-2511-Object-Remover"},
88
+ {"images": ["examples/DL2.jpg"], "prompt": "add the nike tick design inside the red marked area.", "lora": "Outfit-Design-Layout"},
89
+ {"images": ["examples/DL1.jpg"], "prompt": "add the akatsuki cloud design inside the red marked area.", "lora": "Outfit-Design-Layout"},
90
+ {"images": ["examples/C.png"], "prompt": "Add the leather cowboy cap to the image while preserving the background lighting and surrounding elements maintaining realism and original details.", "lora": "Qwen-Image-Edit-2511-Object-Adder"},
91
+ {"images": ["examples/ZM.jpg"], "prompt": "Zoom into the red highlighted area.", "lora": "Zoom-Master"},
92
+ {"images": ["examples/OBJ1.jpg"], "prompt": "Remove the red highlighted object from the scene.", "lora": "QIE-2511-Object-Remover-v2"},
93
+ {"images": ["examples/OBJ2.jpg"], "prompt": "Remove the red highlighted object from the scene.", "lora": "QIE-2511-Object-Remover-v2"},
94
+ {"images": ["examples/OE.jpg"], "prompt": "Extract the clothing and create a flat mockup.", "lora": "Extract-Outfit"},
95
+ ]
96
+
97
+ def make_thumb_b64(path, max_dim=220):
98
+ if not os.path.exists(path):
99
+ return ""
100
+ try:
101
+ img = Image.open(path).convert("RGB")
102
+ img.thumbnail((max_dim, max_dim), LANCZOS)
103
+ buf = BytesIO()
104
+ img.save(buf, format="JPEG", quality=65)
105
+ return f"data:image/jpeg;base64,{base64.b64encode(buf.getvalue()).decode()}"
106
+ except Exception as e:
107
+ return ""
108
+
109
+ def encode_full_image(path):
110
+ if not os.path.exists(path):
111
+ return ""
112
+ try:
113
+ with open(path, "rb") as f:
114
+ data = f.read()
115
+ ext = path.rsplit(".", 1)[-1].lower()
116
+ mime = {"jpg": "image/jpeg", "jpeg": "image/jpeg", "png": "image/png", "webp": "image/webp"}.get(ext, "image/jpeg")
117
+ return f"data:{mime};base64,{base64.b64encode(data).decode()}"
118
+ except Exception as e:
119
+ return ""
120
+
121
+ def build_client_config():
122
+ examples = []
123
+ for i, ex in enumerate(EXAMPLES_CONFIG):
124
+ examples.append({
125
+ "idx": i,
126
+ "thumbs": [make_thumb_b64(p) for p in ex["images"]],
127
+ "n_images": len(ex["images"]),
128
+ "lora": ex["lora"],
129
+ "prompt": ex["prompt"],
130
+ })
131
+ return {
132
+ "loras": ADAPTER_NAMES,
133
+ "default_lora": "Qwen-Image-Edit-2511-Object-Adder",
134
+ "examples": examples,
135
+ }
136
+
137
+ print("Building client config (example thumbnails)…")
138
+ CLIENT_CONFIG = build_client_config()
139
+ print(f"Built config with {len(EXAMPLES_CONFIG)} examples and {len(ADAPTER_NAMES)} LoRAs.")
140
+
141
+ def b64_to_pil_list(b64_json_str):
142
+ if not b64_json_str or b64_json_str.strip() in ("", "[]"):
143
+ return []
144
+ try:
145
+ b64_list = json.loads(b64_json_str)
146
+ except Exception:
147
+ return []
148
+ pil_images = []
149
+ for b64_str in b64_list:
150
+ if not b64_str or not isinstance(b64_str, str):
151
+ continue
152
+ try:
153
+ if b64_str.startswith("data:image"):
154
+ _, data = b64_str.split(",", 1)
155
+ else:
156
+ data = b64_str
157
+ image_data = base64.b64decode(data)
158
+ pil_images.append(Image.open(BytesIO(image_data)).convert("RGB"))
159
+ except Exception as e:
160
+ print(f"Error decoding image: {e}")
161
+ return pil_images
162
+
163
+ def pil_to_b64_png(image: Image.Image) -> str:
164
+ buf = BytesIO()
165
+ image.save(buf, format="PNG")
166
+ return f"data:image/png;base64,{base64.b64encode(buf.getvalue()).decode()}"
167
 
168
  def update_dimensions_on_upload(image):
169
  if image is None:
170
  return 1024, 1024
171
+ w, h = image.size
172
+ if w > h:
173
+ nw = 1024
174
+ nh = int(nw * h / w)
 
 
 
175
  else:
176
+ nh = 1024
177
+ nw = int(nh * w / h)
178
+ return (nw // 8) * 8, (nh // 8) * 8
179
+
180
+ # ── Gradio Server (Server mode): FastAPI + Gradio queue/API engine ────────────
181
+ app = Server(title="Qwen-Image-Edit-Object-Manipulator")
 
 
182
 
183
+ @app.mcp.tool(name="edit_image")
184
+ @app.api(name="edit_image")
185
  @spaces.GPU(size="xlarge")
186
  def infer(
187
+ images_b64_json: str,
188
+ prompt: str,
189
+ lora_adapter: str,
190
+ seed: int,
191
+ randomize_seed: bool,
192
+ guidance_scale: float,
193
+ steps: int,
194
+ ) -> dict:
195
+ """Edit one or more images with Qwen-Image-Edit + a lazily-loaded LoRA."""
196
  gc.collect()
197
  torch.cuda.empty_cache()
198
 
199
+ pil_images = b64_to_pil_list(images_b64_json)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  if not pil_images:
201
+ raise gr.Error("Please upload at least one image to edit.")
202
+ if not prompt or prompt.strip() == "":
203
+ raise gr.Error("Please enter an edit prompt.")
204
 
205
  spec = ADAPTER_SPECS.get(lora_adapter)
206
  if not spec:
207
  raise gr.Error(f"Configuration not found for: {lora_adapter}")
208
 
209
  adapter_name = spec["adapter_name"]
 
210
  if adapter_name not in LOADED_ADAPTERS:
211
  print(f"--- Downloading and Loading Adapter: {lora_adapter} ---")
212
  try:
213
+ pipe.load_lora_weights(spec["repo"], weight_name=spec["weights"], adapter_name=adapter_name)
 
 
 
 
214
  LOADED_ADAPTERS.add(adapter_name)
215
  except Exception as e:
216
  raise gr.Error(f"Failed to load adapter {lora_adapter}: {e}")
217
  else:
218
+ print(f"--- Adapter {lora_adapter} already loaded. ---")
219
 
220
  pipe.set_adapters([adapter_name], adapter_weights=[1.0])
221
 
 
223
  seed = random.randint(0, MAX_SEED)
224
 
225
  generator = torch.Generator(device=device).manual_seed(seed)
226
+ negative_prompt = (
227
+ "worst quality, low quality, bad anatomy, bad hands, text, error, missing fingers, "
228
+ "extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry"
229
+ )
230
  width, height = update_dimensions_on_upload(pil_images[0])
231
 
232
  try:
 
240
  generator=generator,
241
  true_cfg_scale=guidance_scale,
242
  ).images[0]
243
+ return {"image": pil_to_b64_png(result_image), "seed": seed}
 
 
244
  except Exception as e:
245
  raise e
246
  finally:
247
  gc.collect()
248
  torch.cuda.empty_cache()
249
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
+ @app.api(name="load_example", queue=False)
252
+ def load_example(idx: float) -> dict:
253
+ """Return base64-encoded example images + prompt + LoRA for a given example index."""
254
+ try:
255
+ i = int(idx)
256
+ except (ValueError, TypeError):
257
+ i = -1
258
+ if i < 0 or i >= len(EXAMPLES_CONFIG):
259
+ return {"images": [], "prompt": "", "lora": "", "names": [], "status": "error"}
260
+ ex = EXAMPLES_CONFIG[i]
261
+ b64_list, names = [], []
262
+ for path in ex["images"]:
263
+ b64 = encode_full_image(path)
264
+ if b64:
265
+ b64_list.append(b64)
266
+ names.append(os.path.basename(path))
267
+ return {"images": b64_list, "prompt": ex["prompt"], "lora": ex["lora"], "names": names, "status": "ok"}
268
+
269
+
270
+ @app.get("/api/config")
271
+ def client_config():
272
+ """Plain FastAPI route: LoRA choices + example card data for the frontend."""
273
+ return CLIENT_CONFIG
274
+
275
+
276
+ @app.get("/", response_class=HTMLResponse)
277
+ async def homepage():
278
+ html_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "index.html")
279
+ with open(html_path, "r", encoding="utf-8") as f:
280
+ return f.read()
281
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
 
283
  if __name__ == "__main__":
284
+ app.launch(show_error=True, mcp_server=True)