prithivMLmods commited on
Commit
bfaa96a
·
verified ·
1 Parent(s): 3974298

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +100 -118
app.py CHANGED
@@ -1,13 +1,12 @@
1
- import os
2
  import gc
3
  import random
4
  import numpy as np
5
- from typing import List
6
-
7
  import torch
 
 
 
8
  import spaces
9
  import gradio as gr
10
- from PIL import Image
11
  from diffusers import Flux2KleinPipeline, AutoencoderKLFlux2
12
 
13
  # --- App Configuration ---
@@ -17,6 +16,10 @@ MAX_IMAGE_SIZE = 1024
17
  dtype = torch.bfloat16
18
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
19
 
 
 
 
 
20
  # --- Model Loading ---
21
  print("Loading Small Decoder VAE...")
22
  vae_small = AutoencoderKLFlux2.from_pretrained(
@@ -30,12 +33,11 @@ pipe = Flux2KleinPipeline.from_pretrained(
30
  vae=vae_small,
31
  torch_dtype=dtype,
32
  ).to(device)
33
- # Optional memory optimization
34
- # pipe.enable_model_cpu_offload()
35
 
36
  # --- Utility Functions ---
37
- def calc_dimensions(pil_img: Image.Image):
38
- """Calculate target dimensions preserving aspect ratio, capped at 1024."""
39
  iw, ih = pil_img.size
40
  aspect = iw / ih
41
 
@@ -46,62 +48,66 @@ def calc_dimensions(pil_img: Image.Image):
46
  new_height = 1024
47
  new_width = int(round(1024 * aspect))
48
 
49
- # Ensure dimensions are multiples of 8 and within bounds
50
- new_width = max(256, min(MAX_IMAGE_SIZE, round(new_width / 8) * 8))
51
- new_height = max(256, min(MAX_IMAGE_SIZE, round(new_height / 8) * 8))
52
  return new_width, new_height
53
 
54
- def parse_and_resize_images(image_paths: List[str], width: int, height: int):
55
- """Load and resize all provided images."""
56
- if not image_paths:
57
  return None
58
 
59
  resized = []
60
- for path in image_paths:
61
  try:
62
- img = Image.open(path).convert("RGB")
63
- resized.append(img.resize((width, height), Image.LANCZOS))
 
 
64
  except Exception as e:
65
- print(f"Skipping invalid image {path}: {e}")
66
 
67
  return resized if resized else None
68
 
69
  # --- Inference Function ---
70
- @spaces.GPU(duration=60)
71
  def generate_image(
 
72
  prompt: str,
73
- image_files: List[str],
74
  seed: int,
75
  randomize_seed: bool,
76
  width: int,
77
  height: int,
78
- num_inference_steps: int,
79
- guidance_scale: float,
80
- progress=gr.Progress(track_tqdm=True),
81
  ):
 
 
 
82
  gc.collect()
83
  if torch.cuda.is_available():
84
  torch.cuda.empty_cache()
85
 
86
- if not prompt or not prompt.strip():
87
- raise gr.Error("Please enter a prompt.")
88
-
89
  current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
90
-
91
  image_list = None
92
- if image_files and len(image_files) > 0:
93
  try:
94
- # Calculate dynamic dimensions based on the first input image
95
- first_pil = Image.open(image_files[0]).convert("RGB")
 
 
 
96
  calc_w, calc_h = calc_dimensions(first_pil)
97
- image_list = parse_and_resize_images(image_files, calc_w, calc_h)
98
 
99
- # Override manual width/height with the image's aspect-ratio-locked dimensions
100
  width, height = calc_w, calc_h
101
  except Exception as e:
102
- print(f"Error processing uploads: {e}")
103
 
104
- # Ensure constraints if running without an image upload
105
  final_width = max(256, min(MAX_IMAGE_SIZE, round(int(width) / 8) * 8))
106
  final_height = max(256, min(MAX_IMAGE_SIZE, round(int(height) / 8) * 8))
107
 
@@ -109,131 +115,107 @@ def generate_image(
109
  prompt=prompt,
110
  height=final_height,
111
  width=final_width,
112
- num_inference_steps=int(num_inference_steps),
113
- guidance_scale=float(guidance_scale),
114
  )
115
  if image_list is not None:
116
  kwargs["image"] = image_list
117
 
118
- # Generate Image
119
- gen = torch.Generator(device="cpu").manual_seed(current_seed)
120
- result = pipe(**kwargs, generator=gen).images[0]
 
 
 
121
 
122
  return result, current_seed
123
 
124
- css = '''
125
- .output-card {
126
- border: 1px solid var(--border-color-primary);
127
- border-radius: 12px;
128
- padding: 16px;
129
- margin-bottom: 12px;
130
- background: var(--background-fill-secondary);
131
- }
132
- '''
133
-
134
- with gr.Blocks() as demo:
135
- gr.Markdown("# **Flux.2 Klein — Small Decoder**")
136
- gr.Markdown(
137
- "Upload an image (optional) and enter a prompt to generate or edit using the **FLUX.2-klein-4B** distilled model paired with the **Small Decoder VAE**."
138
- )
139
 
140
  with gr.Row():
141
- # -- Left Column: Settings --
142
  with gr.Column(scale=1):
143
- image_files_input = gr.Gallery(
144
- file_types=["image"],
145
- label="Input Images (Optional)",
146
- elem_classes="output-card"
 
 
147
  )
148
-
149
  prompt_input = gr.Textbox(
150
- label="Prompt",
151
- placeholder="Describe the edit or generation...",
152
  lines=3
153
  )
154
 
155
  with gr.Accordion("Advanced Settings", open=False):
156
- seed_input = gr.Slider(
157
- label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=42, interactive=True
158
- )
159
- randomize_seed_checkbox = gr.Checkbox(
160
- label="Randomize seed", value=True, interactive=True
161
- )
162
- steps_slider = gr.Slider(
163
- label="Inference Steps", minimum=1, maximum=30, step=1, value=4
164
- )
165
- width_input = gr.Slider(
166
- label="Width", minimum=256, maximum=MAX_IMAGE_SIZE, step=8, value=1024
167
- )
168
- height_input = gr.Slider(
169
- label="Height", minimum=256, maximum=MAX_IMAGE_SIZE, step=8, value=1024
170
- )
171
- guidance_input = gr.Slider(
172
- label="Guidance Scale", minimum=0.0, maximum=10.0, step=0.1, value=1.0
173
- )
174
 
175
  generate_button = gr.Button("Generate Image", variant="primary")
176
 
177
- # -- Right Column: Outputs --
178
  with gr.Column(scale=1):
179
- with gr.Group(elem_classes="output-card"):
180
- output_image = gr.Image(
181
- label="Generated Output", type="pil", interactive=False, format="png",
182
- )
183
- used_seed_output = gr.Textbox(
184
- label="Used Seed", interactive=False
185
- )
186
-
187
- # -- Event Listener --
188
  generate_button.click(
189
  fn=generate_image,
190
  inputs=[
191
- prompt_input,
192
- image_files_input,
193
- seed_input,
194
- randomize_seed_checkbox,
195
- width_input,
196
- height_input,
197
- steps_slider,
198
- guidance_input
199
  ],
200
- outputs=[output_image, used_seed_output]
201
  )
202
 
203
- # -- Examples --
204
  gr.Examples(
205
  examples=[
206
  [
207
- ["examples/I1.jpg", "examples/I2.jpg"],
208
- "Make her wear these glasses in Image 2.",
209
- 4
 
 
 
210
  ],
211
  [
212
- ["examples/1.jpg"],
213
- "Change the weather to stormy.",
214
- 4
215
  ],
216
  [
217
- ["examples/2.jpg"],
218
- "Transform the scene into a snowy winter day while preserving the original subject identity, framing, and composition.",
219
- 4
220
  ],
221
  [
222
- ["examples/3.jpg"],
223
- "Relight the image with soft golden sunset lighting while keeping all structures and subject details consistent.",
224
- 4
225
  ],
226
  [
227
- ["examples/4.jpg"],
228
- "Make the texture high-resolution.",
229
- 4
230
  ]
231
  ],
232
- inputs=[image_files_input, prompt_input, steps_slider],
233
- outputs=[output_image, used_seed_output],
234
  fn=generate_image,
235
  cache_examples=False,
236
  )
237
 
238
  if __name__ == "__main__":
239
- demo.queue().launch(theme=gr.themes.Soft(), css=css, show_error=True, mcp_server=True)
 
 
1
  import gc
2
  import random
3
  import numpy as np
 
 
4
  import torch
5
+ from PIL import Image
6
+ from typing import List, Tuple
7
+
8
  import spaces
9
  import gradio as gr
 
10
  from diffusers import Flux2KleinPipeline, AutoencoderKLFlux2
11
 
12
  # --- App Configuration ---
 
16
  dtype = torch.bfloat16
17
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
18
 
19
+ if torch.cuda.is_available():
20
+ print("current device:", torch.cuda.current_device())
21
+ print("device name:", torch.cuda.get_device_name(torch.cuda.current_device()))
22
+
23
  # --- Model Loading ---
24
  print("Loading Small Decoder VAE...")
25
  vae_small = AutoencoderKLFlux2.from_pretrained(
 
33
  vae=vae_small,
34
  torch_dtype=dtype,
35
  ).to(device)
36
+ pipe.enable_model_cpu_offload()
 
37
 
38
  # --- Utility Functions ---
39
+ def calc_dimensions(pil_img: Image.Image) -> Tuple[int, int]:
40
+ """Calculates dimensions preserving aspect ratio, snapped to multiples of 8."""
41
  iw, ih = pil_img.size
42
  aspect = iw / ih
43
 
 
48
  new_height = 1024
49
  new_width = int(round(1024 * aspect))
50
 
51
+ new_width = max(256, min(1024, round(new_width / 8) * 8))
52
+ new_height = max(256, min(1024, round(new_height / 8) * 8))
 
53
  return new_width, new_height
54
 
55
+ def parse_and_resize_images(gallery_items: List, target_width: int, target_height: int) -> List[Image.Image]:
56
+ """Extracts images from Gradio Gallery and resizes them."""
57
+ if not gallery_items:
58
  return None
59
 
60
  resized = []
61
+ for item in gallery_items:
62
  try:
63
+ # Gradio Gallery returns a list of tuples: (filepath, label)
64
+ filepath = item[0] if isinstance(item, (tuple, list)) else item
65
+ img = Image.open(filepath).convert("RGB")
66
+ resized.append(img.resize((target_width, target_height), Image.LANCZOS))
67
  except Exception as e:
68
+ print(f"Skipping invalid image: {e}")
69
 
70
  return resized if resized else None
71
 
72
  # --- Inference Function ---
73
+ @spaces.GPU(size="xlarge")
74
  def generate_image(
75
+ gallery_inputs,
76
  prompt: str,
 
77
  seed: int,
78
  randomize_seed: bool,
79
  width: int,
80
  height: int,
81
+ steps: int,
82
+ guidance: float,
83
+ progress=gr.Progress(track_tqdm=True)
84
  ):
85
+ if not prompt or not prompt.strip():
86
+ raise gr.Error("Please enter a prompt.")
87
+
88
  gc.collect()
89
  if torch.cuda.is_available():
90
  torch.cuda.empty_cache()
91
 
 
 
 
92
  current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
93
+
94
  image_list = None
95
+ if gallery_inputs and len(gallery_inputs) > 0:
96
  try:
97
+ # Get first image to calculate reference dimensions
98
+ first_item = gallery_inputs[0]
99
+ first_filepath = first_item[0] if isinstance(first_item, (tuple, list)) else first_item
100
+ first_pil = Image.open(first_filepath).convert("RGB")
101
+
102
  calc_w, calc_h = calc_dimensions(first_pil)
103
+ image_list = parse_and_resize_images(gallery_inputs, calc_w, calc_h)
104
 
105
+ # Override manual width/height if images are provided to match input aspect ratio
106
  width, height = calc_w, calc_h
107
  except Exception as e:
108
+ print(f"Error processing gallery uploads: {e}")
109
 
110
+ # Ensure dimensions are multiples of 8
111
  final_width = max(256, min(MAX_IMAGE_SIZE, round(int(width) / 8) * 8))
112
  final_height = max(256, min(MAX_IMAGE_SIZE, round(int(height) / 8) * 8))
113
 
 
115
  prompt=prompt,
116
  height=final_height,
117
  width=final_width,
118
+ num_inference_steps=int(steps),
119
+ guidance_scale=float(guidance),
120
  )
121
  if image_list is not None:
122
  kwargs["image"] = image_list
123
 
124
+ generator = torch.Generator(device="cpu").manual_seed(current_seed)
125
+ result = pipe(**kwargs, generator=generator).images[0]
126
+
127
+ gc.collect()
128
+ if torch.cuda.is_available():
129
+ torch.cuda.empty_cache()
130
 
131
  return result, current_seed
132
 
133
+ # --- Gradio UI ---
134
+ with gr.Blocks(title="Flux.2 Klein - Small Decoder") as demo:
135
+ gr.Markdown("# **Flux.2 Klein — Small Decoder VAE**")
136
+ gr.Markdown("Upload images (optional) and enter a prompt to generate or edit with the 4B distilled model.")
 
 
 
 
 
 
 
 
 
 
 
137
 
138
  with gr.Row():
 
139
  with gr.Column(scale=1):
140
+ gallery_input = gr.Gallery(
141
+ label="Input Images (Optional)",
142
+ type="filepath",
143
+ height=300,
144
+ allow_preview=True,
145
+ elem_id="gallery_input"
146
  )
 
147
  prompt_input = gr.Textbox(
148
+ label="Prompt",
149
+ placeholder="Describe the edit or generation...",
150
  lines=3
151
  )
152
 
153
  with gr.Accordion("Advanced Settings", open=False):
154
+ with gr.Row():
155
+ width_slider = gr.Slider(minimum=256, maximum=1024, step=8, value=1024, label="Width")
156
+ height_slider = gr.Slider(minimum=256, maximum=1024, step=8, value=1024, label="Height")
157
+
158
+ with gr.Row():
159
+ steps_slider = gr.Slider(minimum=1, maximum=30, step=1, value=4, label="Inference Steps")
160
+ guidance_slider = gr.Slider(minimum=0.0, maximum=10.0, step=0.1, value=1.0, label="Guidance Scale")
161
+
162
+ seed_input = gr.Slider(minimum=0, maximum=MAX_SEED, step=1, value=42, label="Seed")
163
+ randomize_seed_checkbox = gr.Checkbox(label="Randomize Seed", value=True)
 
 
 
 
 
 
 
 
164
 
165
  generate_button = gr.Button("Generate Image", variant="primary")
166
 
 
167
  with gr.Column(scale=1):
168
+ output_image = gr.Image(label="Generated Output", type="pil", interactive=False)
169
+
170
+ # Wire up the button
 
 
 
 
 
 
171
  generate_button.click(
172
  fn=generate_image,
173
  inputs=[
174
+ gallery_input,
175
+ prompt_input,
176
+ seed_input,
177
+ randomize_seed_checkbox,
178
+ width_slider,
179
+ height_slider,
180
+ steps_slider,
181
+ guidance_slider
182
  ],
183
+ outputs=[output_image, seed_input]
184
  )
185
 
186
+ # Examples
187
  gr.Examples(
188
  examples=[
189
  [
190
+ ["examples/I1.jpg", "examples/I2.jpg"],
191
+ "Make her wear these glasses in Image 2."
192
+ ],
193
+ [
194
+ ["examples/1.jpg"],
195
+ "Change the weather to stormy."
196
  ],
197
  [
198
+ ["examples/2.jpg"],
199
+ "Transform the scene into a snowy winter day while preserving the original subject identity, framing, and composition."
 
200
  ],
201
  [
202
+ ["examples/3.jpg"],
203
+ "Relight the image with soft golden sunset lighting while keeping all structures and subject details consistent."
 
204
  ],
205
  [
206
+ ["examples/4.jpg"],
207
+ "Make the texture high-resolution."
 
208
  ],
209
  [
210
+ None,
211
+ "A futuristic cyberpunk cityscape at night, neon lights reflecting in puddles, flying cars in the background."
 
212
  ]
213
  ],
214
+ inputs=[gallery_input, prompt_input],
215
+ outputs=[output_image, seed_input],
216
  fn=generate_image,
217
  cache_examples=False,
218
  )
219
 
220
  if __name__ == "__main__":
221
+ demo.queue().launch(theme=gr.themes.Citrus(), show_error=True)