Levaser commited on
Commit
04521d7
·
verified ·
1 Parent(s): e94ebf6

Switch Space to FLUX.2-klein BSOD image editing

Browse files
Files changed (2) hide show
  1. app.py +241 -105
  2. requirements.txt +9 -6
app.py CHANGED
@@ -1,94 +1,243 @@
1
- import gradio as gr
2
- import numpy as np
3
  import random
 
4
 
5
- # import spaces #[uncomment to use ZeroGPU]
6
- from diffusers import DiffusionPipeline
7
  import torch
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- device = "cuda" if torch.cuda.is_available() else "cpu"
10
- model_repo_id = "stabilityai/sdxl-turbo" # Replace to the model you would like to use
11
 
12
- if torch.cuda.is_available():
13
- torch_dtype = torch.float16
14
- else:
15
- torch_dtype = torch.float32
 
 
 
16
 
17
- pipe = DiffusionPipeline.from_pretrained(model_repo_id, torch_dtype=torch_dtype)
18
- pipe = pipe.to(device)
19
 
20
- MAX_SEED = np.iinfo(np.int32).max
21
- MAX_IMAGE_SIZE = 1024
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
 
24
- # @spaces.GPU #[uncomment to use ZeroGPU]
25
  def infer(
26
- prompt,
27
- negative_prompt,
28
- seed,
29
- randomize_seed,
30
- width,
31
- height,
32
- guidance_scale,
33
- num_inference_steps,
34
  progress=gr.Progress(track_tqdm=True),
35
  ):
 
 
 
 
 
 
 
36
  if randomize_seed:
37
  seed = random.randint(0, MAX_SEED)
38
 
39
- generator = torch.Generator().manual_seed(seed)
 
 
 
 
 
 
40
 
41
- image = pipe(
42
  prompt=prompt,
43
- negative_prompt=negative_prompt,
44
- guidance_scale=guidance_scale,
45
- num_inference_steps=num_inference_steps,
46
  width=width,
47
  height=height,
 
 
48
  generator=generator,
49
  ).images[0]
50
 
51
- return image, seed
 
52
 
53
 
54
- examples = [
55
- "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k",
56
- "An astronaut riding a green horse",
57
- "A delicious ceviche cheesecake slice",
58
- ]
59
-
60
- css = """
61
- #col-container {
62
- margin: 0 auto;
63
- max-width: 640px;
64
- }
65
- """
66
-
67
- with gr.Blocks(css=css) as demo:
68
- with gr.Column(elem_id="col-container"):
69
- gr.Markdown(" # Text-to-Image Gradio Template")
70
 
71
  with gr.Row():
72
- prompt = gr.Text(
73
- label="Prompt",
74
- show_label=False,
75
- max_lines=1,
76
- placeholder="Enter your prompt",
77
- container=False,
 
 
78
  )
79
 
80
- run_button = gr.Button("Run", scale=0, variant="primary")
81
-
82
- result = gr.Image(label="Result", show_label=False)
83
-
84
- with gr.Accordion("Advanced Settings", open=False):
85
- negative_prompt = gr.Text(
86
- label="Negative prompt",
87
- max_lines=1,
88
- placeholder="Enter a negative prompt",
89
- visible=False,
90
  )
91
 
 
92
  seed = gr.Slider(
93
  label="Seed",
94
  minimum=0,
@@ -96,59 +245,46 @@ with gr.Blocks(css=css) as demo:
96
  step=1,
97
  value=0,
98
  )
99
-
100
  randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
- with gr.Row():
103
- width = gr.Slider(
104
- label="Width",
105
- minimum=256,
106
- maximum=MAX_IMAGE_SIZE,
107
- step=32,
108
- value=1024, # Replace with defaults that work for your model
109
- )
110
-
111
- height = gr.Slider(
112
- label="Height",
113
- minimum=256,
114
- maximum=MAX_IMAGE_SIZE,
115
- step=32,
116
- value=1024, # Replace with defaults that work for your model
117
- )
118
-
119
- with gr.Row():
120
- guidance_scale = gr.Slider(
121
- label="Guidance scale",
122
- minimum=0.0,
123
- maximum=10.0,
124
- step=0.1,
125
- value=0.0, # Replace with defaults that work for your model
126
- )
127
-
128
- num_inference_steps = gr.Slider(
129
- label="Number of inference steps",
130
- minimum=1,
131
- maximum=50,
132
- step=1,
133
- value=2, # Replace with defaults that work for your model
134
- )
135
-
136
- gr.Examples(examples=examples, inputs=[prompt])
137
- gr.on(
138
- triggers=[run_button.click, prompt.submit],
139
  fn=infer,
140
  inputs=[
141
- prompt,
142
- negative_prompt,
143
  seed,
144
  randomize_seed,
145
- width,
146
- height,
147
- guidance_scale,
148
  num_inference_steps,
 
149
  ],
150
- outputs=[result, seed],
151
  )
152
 
 
153
  if __name__ == "__main__":
154
  demo.launch()
 
 
 
1
  import random
2
+ import threading
3
 
4
+ import gradio as gr
 
5
  import torch
6
+ from diffusers import Flux2KleinPipeline, Flux2Transformer2DModel, GGUFQuantizationConfig
7
+ from PIL import Image, ImageDraw, ImageFont, ImageOps
8
+
9
+
10
+ MODEL_ID = "black-forest-labs/FLUX.2-klein-4B"
11
+ GGUF_URL = (
12
+ "https://huggingface.co/unsloth/FLUX.2-klein-4B-GGUF/resolve/main/"
13
+ "flux-2-klein-4b-Q4_K_M.gguf"
14
+ )
15
+ MAX_SEED = 2_147_483_647
16
+ MAX_GENERATION_EDGE = 1024
17
+ MIN_GENERATION_EDGE = 256
18
+ SIZE_STEP = 32
19
+
20
+ PIPELINE = None
21
+ PIPELINE_LOCK = threading.Lock()
22
+
23
+ BSOD_PROMPT = (
24
+ "Transform the reference photo into a BSOD-inspired scene. "
25
+ "Keep the main subject recognizable and preserve the overall composition. "
26
+ "Use blue-screen-of-death aesthetics, computer hardware, machines, robots, "
27
+ "cybernetic details, metallic structures, monitor glow, motherboard patterns, "
28
+ "industrial sci-fi atmosphere, neon blue diagnostics, clean high detail."
29
+ )
30
+
31
+ CSS = """
32
+ .app-shell {
33
+ max-width: 1080px;
34
+ margin: 0 auto;
35
+ }
36
+ .hero {
37
+ padding: 8px 0 20px;
38
+ }
39
+ .hero h1 {
40
+ margin-bottom: 8px;
41
+ }
42
+ """
43
+
44
+
45
+ def _device() -> str:
46
+ return "cuda" if torch.cuda.is_available() else "cpu"
47
+
48
+
49
+ def _dtype() -> torch.dtype:
50
+ return torch.bfloat16 if torch.cuda.is_available() else torch.float32
51
+
52
+
53
+ def get_pipeline() -> Flux2KleinPipeline:
54
+ global PIPELINE
55
+
56
+ if PIPELINE is not None:
57
+ return PIPELINE
58
+
59
+ with PIPELINE_LOCK:
60
+ if PIPELINE is not None:
61
+ return PIPELINE
62
+
63
+ quantization_config = GGUFQuantizationConfig(compute_dtype=_dtype())
64
+ transformer = Flux2Transformer2DModel.from_single_file(
65
+ GGUF_URL,
66
+ config=MODEL_ID,
67
+ subfolder="transformer",
68
+ quantization_config=quantization_config,
69
+ torch_dtype=_dtype(),
70
+ )
71
+
72
+ pipe = Flux2KleinPipeline.from_pretrained(
73
+ MODEL_ID,
74
+ transformer=transformer,
75
+ torch_dtype=_dtype(),
76
+ )
77
+ pipe.vae.enable_slicing()
78
+
79
+ if torch.cuda.is_available():
80
+ pipe.enable_model_cpu_offload()
81
+ else:
82
+ pipe.to("cpu")
83
+
84
+ pipe.set_progress_bar_config(disable=True)
85
+ PIPELINE = pipe
86
+ return PIPELINE
87
+
88
+
89
+ def _round_to_step(value: int, step: int = SIZE_STEP) -> int:
90
+ return max(step, int(round(value / step) * step))
91
+
92
+
93
+ def _generation_size(image: Image.Image) -> tuple[int, int]:
94
+ width, height = image.size
95
+ longest_edge = max(width, height)
96
+ scale = min(1.0, MAX_GENERATION_EDGE / longest_edge) if longest_edge else 1.0
97
+
98
+ resized_width = max(MIN_GENERATION_EDGE, int(width * scale))
99
+ resized_height = max(MIN_GENERATION_EDGE, int(height * scale))
100
+
101
+ gen_width = _round_to_step(resized_width)
102
+ gen_height = _round_to_step(resized_height)
103
+
104
+ gen_width = max(MIN_GENERATION_EDGE, min(MAX_GENERATION_EDGE, gen_width))
105
+ gen_height = max(MIN_GENERATION_EDGE, min(MAX_GENERATION_EDGE, gen_height))
106
+ return gen_width, gen_height
107
+
108
+
109
+ def _resize_for_model(image: Image.Image, width: int, height: int) -> Image.Image:
110
+ return image.resize((width, height), Image.Resampling.LANCZOS)
111
 
 
 
112
 
113
+ def _label_font() -> ImageFont.ImageFont | ImageFont.FreeTypeFont:
114
+ for font_name in ("DejaVuSans-Bold.ttf", "Arial.ttf"):
115
+ try:
116
+ return ImageFont.truetype(font_name, 36)
117
+ except OSError:
118
+ continue
119
+ return ImageFont.load_default()
120
 
 
 
121
 
122
+ def _compose_comparison(original: Image.Image, bsod: Image.Image) -> Image.Image:
123
+ pad = 28
124
+ gap = 24
125
+ header_height = 74
126
+ bg_color = (10, 16, 30)
127
+ panel_color = (18, 30, 54)
128
+ text_color = (223, 236, 255)
129
+
130
+ left_w, left_h = original.size
131
+ right_w, right_h = bsod.size
132
+ panel_height = max(left_h, right_h)
133
+
134
+ total_width = pad * 2 + left_w + right_w + gap
135
+ total_height = pad * 2 + header_height + panel_height
136
+ canvas = Image.new("RGB", (total_width, total_height), bg_color)
137
+ draw = ImageDraw.Draw(canvas)
138
+ font = _label_font()
139
+
140
+ left_panel = (pad, pad + header_height, pad + left_w, pad + header_height + panel_height)
141
+ right_panel = (
142
+ pad + left_w + gap,
143
+ pad + header_height,
144
+ pad + left_w + gap + right_w,
145
+ pad + header_height + panel_height,
146
+ )
147
+
148
+ draw.rounded_rectangle(left_panel, radius=20, fill=panel_color)
149
+ draw.rounded_rectangle(right_panel, radius=20, fill=panel_color)
150
+
151
+ left_text_x = pad + 16
152
+ right_text_x = pad + left_w + gap + 16
153
+ text_y = pad + 18
154
+ draw.text((left_text_x, text_y), "original", fill=text_color, font=font)
155
+ draw.text((right_text_x, text_y), "bsod", fill=text_color, font=font)
156
+
157
+ left_y = pad + header_height + (panel_height - left_h) // 2
158
+ right_y = pad + header_height + (panel_height - right_h) // 2
159
+
160
+ canvas.paste(original, (pad, left_y))
161
+ canvas.paste(bsod, (pad + left_w + gap, right_y))
162
+ return canvas
163
 
164
 
 
165
  def infer(
166
+ input_image: Image.Image,
167
+ extra_prompt: str,
168
+ seed: int,
169
+ randomize_seed: bool,
170
+ num_inference_steps: int,
171
+ guidance_scale: float,
 
 
172
  progress=gr.Progress(track_tqdm=True),
173
  ):
174
+ if input_image is None:
175
+ raise gr.Error("Upload a source image first.")
176
+
177
+ original = ImageOps.exif_transpose(input_image).convert("RGB")
178
+ width, height = _generation_size(original)
179
+ conditioning = _resize_for_model(original, width, height)
180
+
181
  if randomize_seed:
182
  seed = random.randint(0, MAX_SEED)
183
 
184
+ prompt = BSOD_PROMPT
185
+ if extra_prompt and extra_prompt.strip():
186
+ prompt = f"{prompt} Extra instructions: {extra_prompt.strip()}"
187
+
188
+ pipe = get_pipeline()
189
+ generator_device = "cuda" if torch.cuda.is_available() else "cpu"
190
+ generator = torch.Generator(device=generator_device).manual_seed(int(seed))
191
 
192
+ result = pipe(
193
  prompt=prompt,
194
+ image=conditioning,
 
 
195
  width=width,
196
  height=height,
197
+ guidance_scale=guidance_scale,
198
+ num_inference_steps=int(num_inference_steps),
199
  generator=generator,
200
  ).images[0]
201
 
202
+ comparison = _compose_comparison(original, result)
203
+ return comparison, result, seed
204
 
205
 
206
+ with gr.Blocks(css=CSS) as demo:
207
+ with gr.Column(elem_classes=["app-shell"]):
208
+ with gr.Column(elem_classes=["hero"]):
209
+ gr.Markdown(
210
+ """
211
+ # Make It BSOD
212
+ Upload a normal photo and get a side-by-side comparison:
213
+ the left panel stays untouched, the right panel is regenerated
214
+ in a BSOD, computers, robots, and industrial sci-fi style.
215
+ """
216
+ )
 
 
 
 
 
217
 
218
  with gr.Row():
219
+ input_image = gr.Image(
220
+ label="Original photo",
221
+ type="pil",
222
+ image_mode="RGB",
223
+ )
224
+ comparison_image = gr.Image(
225
+ label="Comparison",
226
+ type="pil",
227
  )
228
 
229
+ with gr.Row():
230
+ extra_prompt = gr.Textbox(
231
+ label="Extra style instructions",
232
+ placeholder="Optional: chrome limbs, server room, broken CRTs, robot swarm...",
233
+ lines=2,
234
+ )
235
+ stylized_image = gr.Image(
236
+ label="BSOD only",
237
+ type="pil",
 
238
  )
239
 
240
+ with gr.Accordion("Generation settings", open=False):
241
  seed = gr.Slider(
242
  label="Seed",
243
  minimum=0,
 
245
  step=1,
246
  value=0,
247
  )
 
248
  randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
249
+ num_inference_steps = gr.Slider(
250
+ label="Steps",
251
+ minimum=1,
252
+ maximum=50,
253
+ step=1,
254
+ value=12,
255
+ )
256
+ guidance_scale = gr.Slider(
257
+ label="Guidance scale",
258
+ minimum=1.0,
259
+ maximum=10.0,
260
+ step=0.1,
261
+ value=4.0,
262
+ )
263
+
264
+ run_button = gr.Button("Make it BSOD", variant="primary")
265
 
266
+ gr.Examples(
267
+ examples=[
268
+ ["cold blue datacenter, mechanical arms, diagnostic overlays"],
269
+ ["retro windows crash screen, motherboard textures, chrome robot face"],
270
+ ["factory machines, server racks, terminal glow, cybernetic details"],
271
+ ],
272
+ inputs=[extra_prompt],
273
+ )
274
+
275
+ run_button.click(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  fn=infer,
277
  inputs=[
278
+ input_image,
279
+ extra_prompt,
280
  seed,
281
  randomize_seed,
 
 
 
282
  num_inference_steps,
283
+ guidance_scale,
284
  ],
285
+ outputs=[comparison_image, stylized_image, seed],
286
  )
287
 
288
+
289
  if __name__ == "__main__":
290
  demo.launch()
requirements.txt CHANGED
@@ -1,6 +1,9 @@
1
- accelerate
2
- diffusers
3
- invisible_watermark
4
- torch
5
- transformers
6
- xformers
 
 
 
 
1
+ accelerate>=1.10.0
2
+ diffusers>=0.37.1
3
+ gguf>=0.17.1
4
+ gradio>=6.5.1
5
+ huggingface_hub>=0.34.0
6
+ Pillow>=11.3.0
7
+ sentencepiece>=0.2.0
8
+ torch>=2.6.0
9
+ transformers>=4.57.0