Fabrice-TIERCELIN commited on
Commit
8ae6749
·
verified ·
1 Parent(s): b617d2a
Files changed (1) hide show
  1. app.py +316 -278
app.py CHANGED
@@ -1,279 +1,317 @@
1
- # PyTorch 2.8 (temporary hack)
2
- import os
3
- os.system('pip install --upgrade --pre --extra-index-url https://download.pytorch.org/whl/nightly/cu126 "torch<2.9" spaces')
4
-
5
- # Actual demo code
6
- try:
7
- import spaces
8
- except:
9
- class spaces():
10
- def GPU(*args, **kwargs):
11
- def decorator(function):
12
- return lambda *dummy_args, **dummy_kwargs: function(*dummy_args, **dummy_kwargs)
13
- return decorator
14
-
15
- import gradio as gr
16
- import numpy as np
17
- import torch
18
- import random
19
- from datetime import datetime
20
-
21
- from PIL import Image
22
- import tempfile
23
- import shutil
24
- from pathlib import Path
25
-
26
- from diffusers import FluxKontextPipeline
27
- from diffusers.utils import load_image
28
-
29
- from optimization import optimize_pipeline_
30
-
31
- MAX_SEED = np.iinfo(np.int32).max
32
-
33
- pipe = FluxKontextPipeline.from_pretrained("yuvraj108c/FLUX.1-Kontext-dev", torch_dtype=torch.bfloat16).to("cuda")
34
- optimize_pipeline_(pipe, image=Image.new("RGB", (512, 512)), prompt='prompt')
35
-
36
- input_image_debug_value = [None]
37
- prompt_debug_value = [None]
38
- number_debug_value = [None]
39
- def save_on_path(img: Image, filename: str, format_: str = None) -> Path:
40
- """
41
- Save `img` in a unique temporary folder under the given `filename`
42
- and return its absolute path.
43
- """
44
- # 1) unique temporary folder
45
- tmp_dir = Path(tempfile.mkdtemp(prefix="pil_tmp_"))
46
-
47
- # 2) full path of the future file
48
- file_path = tmp_dir / filename
49
-
50
- # 3) save
51
- img.save(file_path, format=format_ or img.format)
52
-
53
- return file_path
54
-
55
- @spaces.GPU(duration=40)
56
- def infer(
57
- input_image,
58
- prompt,
59
- seed = 42,
60
- randomize_seed = False,
61
- guidance_scale = 2.5,
62
- steps = 28,
63
- width = -1,
64
- height = -1,
65
- progress=gr.Progress(track_tqdm=True)
66
- ):
67
- """
68
- Perform image editing using the FLUX.1 Kontext pipeline.
69
-
70
- This function takes an input image and a text prompt to generate a modified version
71
- of the image based on the provided instructions. It uses the FLUX.1 Kontext model
72
- for contextual image editing tasks.
73
-
74
- Args:
75
- input_image (PIL.Image.Image): The input image to be edited. Will be converted
76
- to RGB format if not already in that format.
77
- prompt (str): Text description of the desired edit to apply to the image.
78
- Examples: "Remove glasses", "Add a hat", "Change background to beach".
79
- seed (int, optional): Random seed for reproducible generation. Defaults to 42.
80
- Must be between 0 and MAX_SEED (2^31 - 1).
81
- randomize_seed (bool, optional): If True, generates a random seed instead of
82
- using the provided seed value. Defaults to False.
83
- guidance_scale (float, optional): Controls how closely the model follows the
84
- prompt. Higher values mean stronger adherence to the prompt but may reduce
85
- image quality. Range: 1.0-10.0. Defaults to 2.5.
86
- steps (int, optional): Controls how many steps to run the diffusion model for.
87
- Range: 1-30. Defaults to 28.
88
- progress (gr.Progress, optional): Gradio progress tracker for monitoring
89
- generation progress. Defaults to gr.Progress(track_tqdm=True).
90
-
91
- Returns:
92
- tuple: A 3-tuple containing:
93
- - PIL.Image.Image: The generated/edited image
94
- - int: The seed value used for generation (useful when randomize_seed=True)
95
- - gr.update: Gradio update object to make the reuse button visible
96
-
97
- Example:
98
- >>> edited_image, used_seed, button_update = infer(
99
- ... input_image=my_image,
100
- ... prompt="Add sunglasses",
101
- ... seed=123,
102
- ... randomize_seed=False,
103
- ... guidance_scale=2.5
104
- ... )
105
- """
106
- if randomize_seed:
107
- seed = random.randint(0, MAX_SEED)
108
-
109
- if input_image:
110
- input_image = input_image.convert("RGB")
111
- image = pipe(
112
- image=input_image,
113
- prompt=prompt,
114
- guidance_scale=guidance_scale,
115
- width = input_image.size[0] if width == -1 else width,
116
- height = input_image.size[1] if height == -1 else height,
117
- num_inference_steps=steps,
118
- generator=torch.Generator().manual_seed(seed),
119
- ).images[0]
120
- else:
121
- image = pipe(
122
- prompt=prompt,
123
- guidance_scale=guidance_scale,
124
- num_inference_steps=steps,
125
- generator=torch.Generator().manual_seed(seed),
126
- ).images[0]
127
-
128
- image_filename = datetime.now().strftime("%Y-%m-%d_%H-%M-%S.%f") + '.webp'
129
- path = save_on_path(image, image_filename, format_="WEBP")
130
- return path, gr.update(value=path, visible=True), seed, gr.update(visible=True)
131
-
132
- def infer_example(input_image, prompt):
133
- number=1
134
- if input_image_debug_value[0] is not None or prompt_debug_value[0] is not None or number_debug_value[0] is not None:
135
- input_image=input_image_debug_value[0]
136
- prompt=prompt_debug_value[0]
137
- number=number_debug_value[0]
138
- #input_image_debug_value[0]=prompt_debug_value[0]=prompt_debug_value[0]=None
139
- gallery = []
140
- try:
141
- for i in range(number):
142
- print("Generating #" + str(i + 1) + " image...")
143
- seed = random.randint(0, MAX_SEED)
144
- image, download_button, seed, _ = infer(input_image, prompt, seed, True)
145
- gallery.append(image)
146
- except:
147
- print("Error")
148
- return gallery, seed
149
-
150
- css="""
151
- #col-container {
152
- margin: 0 auto;
153
- max-width: 960px;
154
- }
155
- """
156
-
157
- with gr.Blocks(css=css) as demo:
158
-
159
- with gr.Column(elem_id="col-container"):
160
- gr.Markdown(f"""# FLUX.1 Kontext [dev]
161
- Image editing and manipulation model guidance-distilled from FLUX.1 Kontext [pro], [[blog]](https://bfl.ai/announcements/flux-1-kontext-dev) [[model]](https://huggingface.co/black-forest-labs/FLUX.1-Kontext-dev)
162
- """)
163
- with gr.Row():
164
- with gr.Column():
165
- input_image = gr.Image(label="Upload the image for editing", type="pil")
166
- with gr.Row():
167
- prompt = gr.Text(
168
- label="Prompt",
169
- show_label=False,
170
- max_lines=1,
171
- placeholder="Enter your prompt for editing (e.g., 'Remove glasses', 'Add a hat')",
172
- container=False,
173
- )
174
- run_button = gr.Button(value="🚀 Edit", variant = "primary", scale=0)
175
- with gr.Accordion("Advanced Settings", open=False):
176
-
177
- seed = gr.Slider(
178
- label="Seed",
179
- minimum=0,
180
- maximum=MAX_SEED,
181
- step=1,
182
- value=0,
183
- )
184
-
185
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
186
-
187
- guidance_scale = gr.Slider(
188
- label="Guidance Scale",
189
- minimum=1,
190
- maximum=10,
191
- step=0.1,
192
- value=2.5,
193
- )
194
-
195
- steps = gr.Slider(
196
- label="Steps",
197
- minimum=1,
198
- maximum=30,
199
- value=30,
200
- step=1
201
- )
202
-
203
- width = gr.Slider(
204
- label="Output width",
205
- info="-1 = original width",
206
- minimum=-1,
207
- maximum=1024,
208
- value=-1,
209
- step=1
210
- )
211
-
212
- height = gr.Slider(
213
- label="Output height",
214
- info="-1 = original height",
215
- minimum=-1,
216
- maximum=1024,
217
- value=-1,
218
- step=1
219
- )
220
-
221
- with gr.Column():
222
- result = gr.Image(label="Result", show_label=False, interactive=False)
223
- download_button = gr.DownloadButton(elem_id="download_btn", visible=False)
224
- reuse_button = gr.Button("Reuse this image", visible=False)
225
-
226
- with gr.Row(visible=False):
227
- result_gallery = gr.Gallery(label = 'Downloadable results', show_label = True, interactive = False, elem_id = "gallery1")
228
- gr.Examples(
229
- examples=[
230
- ["monster.png", "Make this monster ride a skateboard on the beach"]
231
- ],
232
- inputs=[input_image, prompt],
233
- outputs=[result_gallery, seed],
234
- fn=infer_example,
235
- run_on_click=True,
236
- cache_examples=True,
237
- cache_mode='lazy'
238
- )
239
- prompt_debug=gr.Textbox(label="Prompt Debug")
240
- input_image_debug=gr.Image(type="pil", label="Image Debug")
241
- number_debug=gr.Slider(label="Number Debug", minimum=1, maximum=50, step=1, value=50)
242
-
243
- gr.Examples(
244
- label = "Examples from demo",
245
- examples=[
246
- ["flowers.png", "turn the flowers into sunflowers"],
247
- ["monster.png", "make this monster ride a skateboard on the beach"],
248
- ["cat.png", "make this cat happy"]
249
- ],
250
- inputs=[input_image, prompt],
251
- outputs=[result, download_button, seed],
252
- fn=infer
253
- )
254
-
255
- def handle_field_debug_change(input_image_debug_data, prompt_debug_data, number_debug_data):
256
- prompt_debug_value[0] = prompt_debug_data
257
- input_image_debug_value[0] = input_image_debug_data
258
- number_debug_value[0] = number_debug_data
259
- return []
260
-
261
- inputs_debug=[input_image_debug, prompt_debug, number_debug]
262
-
263
- input_image_debug.upload(fn=handle_field_debug_change, inputs=inputs_debug, outputs=[])
264
- prompt_debug.change(fn=handle_field_debug_change, inputs=inputs_debug, outputs=[])
265
- number_debug.change(fn=handle_field_debug_change, inputs=inputs_debug, outputs=[])
266
-
267
- gr.on(
268
- triggers=[run_button.click, prompt.submit],
269
- fn = infer,
270
- inputs = [input_image, prompt, seed, randomize_seed, guidance_scale, steps, width, height],
271
- outputs = [result, download_button, seed, reuse_button]
272
- )
273
- reuse_button.click(
274
- fn = lambda image: image,
275
- inputs = [result],
276
- outputs = [input_image]
277
- )
278
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  demo.launch(mcp_server=True)
 
1
+ # PyTorch 2.8 (temporary hack)
2
+ import os
3
+ os.system('pip install --upgrade --pre --extra-index-url https://download.pytorch.org/whl/nightly/cu126 "torch<2.9" spaces')
4
+
5
+ # Actual demo code
6
+ try:
7
+ import spaces
8
+ except:
9
+ class spaces():
10
+ def GPU(*args, **kwargs):
11
+ def decorator(function):
12
+ return lambda *dummy_args, **dummy_kwargs: function(*dummy_args, **dummy_kwargs)
13
+ return decorator
14
+
15
+ import gradio as gr
16
+ import numpy as np
17
+ import torch
18
+ import random
19
+ from datetime import datetime
20
+
21
+ from PIL import Image
22
+ import tempfile
23
+ import shutil
24
+ from pathlib import Path
25
+
26
+ from diffusers import FluxKontextPipeline
27
+ from diffusers.utils import load_image
28
+
29
+ from optimization import optimize_pipeline_
30
+
31
+ MAX_SEED = np.iinfo(np.int32).max
32
+
33
+ pipe = FluxKontextPipeline.from_pretrained("yuvraj108c/FLUX.1-Kontext-dev", torch_dtype=torch.bfloat16).to("cuda")
34
+ optimize_pipeline_(pipe, image=Image.new("RGB", (512, 512)), prompt='prompt')
35
+
36
+ input_image_debug_value = [None]
37
+ prompt_debug_value = [None]
38
+ number_debug_value = [None]
39
+ def save_on_path(img: Image, filename: str, format_: str = None) -> Path:
40
+ """
41
+ Save `img` in a unique temporary folder under the given `filename`
42
+ and return its absolute path.
43
+ """
44
+ # 1) unique temporary folder
45
+ tmp_dir = Path(tempfile.mkdtemp(prefix="pil_tmp_"))
46
+
47
+ # 2) full path of the future file
48
+ file_path = tmp_dir / filename
49
+
50
+ # 3) save
51
+ img.save(file_path, format=format_ or img.format)
52
+
53
+ return file_path
54
+
55
+ @spaces.GPU(duration=40)
56
+ def infer(
57
+ input_image,
58
+ prompt,
59
+ seed = 42,
60
+ randomize_seed = False,
61
+ guidance_scale = 2.5,
62
+ steps = 28,
63
+ width = -1,
64
+ height = -1,
65
+ progress=gr.Progress(track_tqdm=True)
66
+ ):
67
+ """
68
+ Perform image editing using the FLUX.1 Kontext pipeline.
69
+
70
+ This function takes an input image and a text prompt to generate a modified version
71
+ of the image based on the provided instructions. It uses the FLUX.1 Kontext model
72
+ for contextual image editing tasks.
73
+
74
+ Args:
75
+ input_image (PIL.Image.Image): The input image to be edited. Will be converted
76
+ to RGB format if not already in that format.
77
+ prompt (str): Text description of the desired edit to apply to the image.
78
+ Examples: "Remove glasses", "Add a hat", "Change background to beach".
79
+ seed (int, optional): Random seed for reproducible generation. Defaults to 42.
80
+ Must be between 0 and MAX_SEED (2^31 - 1).
81
+ randomize_seed (bool, optional): If True, generates a random seed instead of
82
+ using the provided seed value. Defaults to False.
83
+ guidance_scale (float, optional): Controls how closely the model follows the
84
+ prompt. Higher values mean stronger adherence to the prompt but may reduce
85
+ image quality. Range: 1.0-10.0. Defaults to 2.5.
86
+ steps (int, optional): Controls how many steps to run the diffusion model for.
87
+ Range: 1-30. Defaults to 28.
88
+ progress (gr.Progress, optional): Gradio progress tracker for monitoring
89
+ generation progress. Defaults to gr.Progress(track_tqdm=True).
90
+
91
+ Returns:
92
+ tuple: A 3-tuple containing:
93
+ - PIL.Image.Image: The generated/edited image
94
+ - int: The seed value used for generation (useful when randomize_seed=True)
95
+ - gr.update: Gradio update object to make the reuse button visible
96
+
97
+ Example:
98
+ >>> edited_image, used_seed, button_update = infer(
99
+ ... input_image=my_image,
100
+ ... prompt="Add sunglasses",
101
+ ... seed=123,
102
+ ... randomize_seed=False,
103
+ ... guidance_scale=2.5
104
+ ... )
105
+ """
106
+ if randomize_seed:
107
+ seed = random.randint(0, MAX_SEED)
108
+
109
+ if input_image:
110
+ input_image = input_image.convert("RGB")
111
+ image = pipe(
112
+ image=input_image,
113
+ prompt=prompt,
114
+ guidance_scale=guidance_scale,
115
+ width = input_image.size[0] if width == -1 else width,
116
+ height = input_image.size[1] if height == -1 else height,
117
+ num_inference_steps=steps,
118
+ generator=torch.Generator().manual_seed(seed),
119
+ ).images[0]
120
+ else:
121
+ image = pipe(
122
+ prompt=prompt,
123
+ guidance_scale=guidance_scale,
124
+ num_inference_steps=steps,
125
+ generator=torch.Generator().manual_seed(seed),
126
+ ).images[0]
127
+
128
+ image_filename = datetime.now().strftime("%Y-%m-%d_%H-%M-%S.%f") + '.webp'
129
+ path = save_on_path(image, image_filename, format_="WEBP")
130
+ return path, gr.update(value=path, visible=True), seed, gr.update(visible=True)
131
+
132
+ def infer_example(input_image, prompt):
133
+ number=1
134
+ if input_image_debug_value[0] is not None or prompt_debug_value[0] is not None or number_debug_value[0] is not None:
135
+ input_image=input_image_debug_value[0]
136
+ prompt=prompt_debug_value[0]
137
+ number=number_debug_value[0]
138
+ #input_image_debug_value[0]=prompt_debug_value[0]=prompt_debug_value[0]=None
139
+ gallery = []
140
+ try:
141
+ for i in range(number):
142
+ print("Generating #" + str(i + 1) + " image...")
143
+ seed = random.randint(0, MAX_SEED)
144
+ image, download_button, seed, _ = infer(input_image, prompt, seed, True)
145
+ gallery.append(image)
146
+ except:
147
+ print("Error")
148
+ return gallery, seedoutput_frames_list
149
+
150
+ def export_compiled_transformers_to_zip() -> str:
151
+ """
152
+ Bundle compiled_transformer_1 and compiled_transformer_2 into a zip file and return the file path.
153
+ """
154
+ ct1 = getattr(optimization, "COMPILED_TRANSFORMER_1", None)
155
+ ct2 = getattr(optimization, "COMPILED_TRANSFORMER_2", None)
156
+
157
+ if ct1 is None or ct2 is None:
158
+ raise gr.Error("Compiled transformers are not available yet (compilation may have failed).")
159
+
160
+ payload_1 = ct1.to_serializable_dict()
161
+ payload_2 = ct2.to_serializable_dict()
162
+
163
+ tmp_zip = tempfile.NamedTemporaryFile(suffix=".zip", delete=False)
164
+ tmp_zip.close()
165
+
166
+ with zipfile.ZipFile(tmp_zip.name, "w", compression=zipfile.ZIP_DEFLATED) as zf:
167
+ # store with torch.save so users can load easily with torch.load()
168
+ buf1 = tempfile.NamedTemporaryFile(suffix=".pt", delete=False)
169
+ buf1.close()
170
+ torch.save(payload_1, buf1.name)
171
+
172
+ buf2 = tempfile.NamedTemporaryFile(suffix=".pt", delete=False)
173
+ buf2.close()
174
+ torch.save(payload_2, buf2.name)
175
+
176
+ zf.write(buf1.name, arcname="compiled_transformer_1.pt")
177
+ zf.write(buf2.name, arcname="compiled_transformer_2.pt")
178
+
179
+ # cleanup intermediate .pt
180
+ try:
181
+ os.remove(buf1.name)
182
+ os.remove(buf2.name)
183
+ except:
184
+ pass
185
+
186
+ return tmp_zip.name
187
+
188
+ css="""
189
+ #col-container {
190
+ margin: 0 auto;
191
+ max-width: 960px;
192
+ }
193
+ """
194
+
195
+ with gr.Blocks(css=css) as demo:
196
+
197
+ with gr.Column(elem_id="col-container"):
198
+ gr.Markdown(f"""# FLUX.1 Kontext [dev]
199
+ Image editing and manipulation model guidance-distilled from FLUX.1 Kontext [pro], [[blog]](https://bfl.ai/announcements/flux-1-kontext-dev) [[model]](https://huggingface.co/black-forest-labs/FLUX.1-Kontext-dev)
200
+ """)
201
+ with gr.Row():
202
+ with gr.Column():
203
+ input_image = gr.Image(label="Upload the image for editing", type="pil")
204
+ with gr.Row():
205
+ prompt = gr.Text(
206
+ label="Prompt",
207
+ show_label=False,
208
+ max_lines=1,
209
+ placeholder="Enter your prompt for editing (e.g., 'Remove glasses', 'Add a hat')",
210
+ container=False,
211
+ )
212
+ run_button = gr.Button(value="🚀 Edit", variant = "primary", scale=0)
213
+ with gr.Accordion("Advanced Settings", open=False):
214
+
215
+ seed = gr.Slider(
216
+ label="Seed",
217
+ minimum=0,
218
+ maximum=MAX_SEED,
219
+ step=1,
220
+ value=0,
221
+ )
222
+
223
+ randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
224
+
225
+ guidance_scale = gr.Slider(
226
+ label="Guidance Scale",
227
+ minimum=1,
228
+ maximum=10,
229
+ step=0.1,
230
+ value=2.5,
231
+ )
232
+
233
+ steps = gr.Slider(
234
+ label="Steps",
235
+ minimum=1,
236
+ maximum=30,
237
+ value=30,
238
+ step=1
239
+ )
240
+
241
+ width = gr.Slider(
242
+ label="Output width",
243
+ info="-1 = original width",
244
+ minimum=-1,
245
+ maximum=1024,
246
+ value=-1,
247
+ step=1
248
+ )
249
+
250
+ height = gr.Slider(
251
+ label="Output height",
252
+ info="-1 = original height",
253
+ minimum=-1,
254
+ maximum=1024,
255
+ value=-1,
256
+ step=1
257
+ )
258
+
259
+ with gr.Column():
260
+ result = gr.Image(label="Result", show_label=False, interactive=False)
261
+ download_button = gr.DownloadButton(elem_id="download_btn", visible=False)
262
+ reuse_button = gr.Button("Reuse this image", visible=False)
263
+
264
+ with gr.Row(visible=False):
265
+ result_gallery = gr.Gallery(label = 'Downloadable results', show_label = True, interactive = False, elem_id = "gallery1")
266
+ gr.Examples(
267
+ examples=[
268
+ ["monster.png", "Make this monster ride a skateboard on the beach"]
269
+ ],
270
+ inputs=[input_image, prompt],
271
+ outputs=[result_gallery, seed],
272
+ fn=infer_example,
273
+ run_on_click=True,
274
+ cache_examples=True,
275
+ cache_mode='lazy'
276
+ )
277
+ prompt_debug=gr.Textbox(label="Prompt Debug")
278
+ input_image_debug=gr.Image(type="pil", label="Image Debug")
279
+ number_debug=gr.Slider(label="Number Debug", minimum=1, maximum=50, step=1, value=50)
280
+
281
+ gr.Examples(
282
+ label = "Examples from demo",
283
+ examples=[
284
+ ["flowers.png", "turn the flowers into sunflowers"],
285
+ ["monster.png", "make this monster ride a skateboard on the beach"],
286
+ ["cat.png", "make this cat happy"]
287
+ ],
288
+ inputs=[input_image, prompt],
289
+ outputs=[result, download_button, seed],
290
+ fn=infer
291
+ )
292
+
293
+ def handle_field_debug_change(input_image_debug_data, prompt_debug_data, number_debug_data):
294
+ prompt_debug_value[0] = prompt_debug_data
295
+ input_image_debug_value[0] = input_image_debug_data
296
+ number_debug_value[0] = number_debug_data
297
+ return []
298
+
299
+ inputs_debug=[input_image_debug, prompt_debug, number_debug]
300
+
301
+ input_image_debug.upload(fn=handle_field_debug_change, inputs=inputs_debug, outputs=[])
302
+ prompt_debug.change(fn=handle_field_debug_change, inputs=inputs_debug, outputs=[])
303
+ number_debug.change(fn=handle_field_debug_change, inputs=inputs_debug, outputs=[])
304
+
305
+ gr.on(
306
+ triggers=[run_button.click, prompt.submit],
307
+ fn = infer,
308
+ inputs = [input_image, prompt, seed, randomize_seed, guidance_scale, steps, width, height],
309
+ outputs = [result, download_button, seed, reuse_button]
310
+ )
311
+ reuse_button.click(
312
+ fn = lambda image: image,
313
+ inputs = [result],
314
+ outputs = [input_image]
315
+ )
316
+
317
  demo.launch(mcp_server=True)