ovi054 commited on
Commit
44ce963
ยท
verified ยท
1 Parent(s): 8a79fb3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +35 -47
app.py CHANGED
@@ -1,4 +1,5 @@
1
  import gradio as gr
 
2
  import math
3
  import numpy as np
4
  import random
@@ -7,7 +8,7 @@ import spaces
7
 
8
  from PIL import Image
9
  from diffusers import QwenImageEditPlusPipeline
10
- from typing import Optional, Tuple
11
 
12
  MAX_SEED = np.iinfo(np.int32).max
13
 
@@ -42,39 +43,16 @@ _VAE_IMAGE_SIZE = 1024 * 1024
42
 
43
 
44
  def calculate_vae_gen_size(image: Image.Image) -> tuple:
45
- """
46
- Return (gen_w, gen_h) that exactly matches the pipeline's internal VAE
47
- conditioning scale for this image.
48
-
49
- The pipeline always resizes every input image to VAE_IMAGE_SIZE (~1MP) before
50
- VAE-encoding it into image_latents, using:
51
- vae_width, vae_height = calculate_dimensions(VAE_IMAGE_SIZE, w / h)
52
-
53
- img_shapes (used for 2-D RoPE) is built from BOTH the output size (height/width)
54
- AND the conditioning sizes (vae_width, vae_height). When they differ, the RoPE
55
- coordinate systems are misaligned โ†’ huge pixel shift.
56
-
57
- Passing gen_h/gen_w = the same 1MP-equivalent makes the output tokens and Image 1
58
- conditioning tokens share an identical coordinate system โ†’ no shift.
59
- This is exactly what ComfyUIโ€™s ImageScaleToTotalPixels (megapixels=1.0) achieves.
60
- """
61
  W, H = image.size
62
  ratio = W / H
63
  gen_w = math.sqrt(_VAE_IMAGE_SIZE * ratio)
64
  gen_h = gen_w / ratio
65
- # pipeline rounds to multiples of 32 (also satisfies the รท16 divisibility requirement)
66
  gen_w = round(gen_w / 32) * 32
67
  gen_h = round(gen_h / 32) * 32
68
  return int(gen_w), int(gen_h)
69
 
70
 
71
-
72
  def update_dimensions_on_upload(image: Optional[Image.Image]) -> Image.Image:
73
- """
74
- Cap longest side to 1328px, snap to multiples of 16.
75
- Pipeline requires divisibility by vae_scale_factor * 2 = 8 * 2 = 16.
76
- Never upscales.
77
- """
78
  if image is None:
79
  return image
80
 
@@ -83,7 +61,6 @@ def update_dimensions_on_upload(image: Optional[Image.Image]) -> Image.Image:
83
  original_width, original_height = image.size
84
  scale = min(MAX_SIDE / original_width, MAX_SIDE / original_height, 1.0)
85
 
86
- # Must be multiples of 16 (vae_scale_factor * 2)
87
  new_width = (int(original_width * scale) // 16) * 16
88
  new_height = (int(original_height * scale) // 16) * 16
89
 
@@ -102,10 +79,7 @@ def infer(
102
  true_guidance_scale: float = 1.0,
103
  num_inference_steps: int = 4,
104
  progress=gr.Progress(track_tqdm=True)
105
- ) -> Tuple[Image.Image, int]:
106
- """
107
- Transfer color grading from a reference image onto a source image.
108
- """
109
  if source_image is None:
110
  raise gr.Error("Please upload a source image (Image 1).")
111
  if reference_image is None:
@@ -118,16 +92,8 @@ def infer(
118
  src_img = source_image.convert("RGB")
119
  ref_img = reference_image.convert("RGB")
120
 
121
- # Original size โ€” used to resize the output back at the end
122
  out_w, out_h = src_img.size
123
 
124
- # Generate at the 1MP-equivalent of Image 1โ€™s aspect ratio.
125
- # The pipeline internally scales ALL input images to VAE_IMAGE_SIZE (~1MP) before
126
- # VAE-encoding them as conditioning latents. img_shapes (for 2-D RoPE) combines
127
- # the output size (height/width) with those conditioning sizes. If they differ,
128
- # the RoPE coordinate systems are misaligned โ†’ huge pixel shift.
129
- # Using the same 1MP formula as the pipeline eliminates the mismatch.
130
- # (ComfyUI achieves this via ImageScaleToTotalPixels at megapixels=1.0.)
131
  gen_w, gen_h = calculate_vae_gen_size(src_img)
132
 
133
  result = pipe(
@@ -141,11 +107,22 @@ def infer(
141
  num_images_per_prompt=1,
142
  ).images[0]
143
 
144
- # Resize output back to the original image dimensions
145
- # if result.size != (out_w, out_h):
146
- # result = result.resize((out_w, out_h), Image.LANCZOS)
 
 
 
 
147
 
148
- return result, seed
 
 
 
 
 
 
 
149
 
150
 
151
  # --- UI ---
@@ -208,7 +185,12 @@ with gr.Blocks() as demo:
208
  )
209
 
210
  with gr.Column():
211
- result = gr.Image(label="Color Graded Output", interactive=False)
 
 
 
 
 
212
 
213
  gr.Examples(
214
  examples=[
@@ -216,22 +198,28 @@ with gr.Blocks() as demo:
216
  ["images/image2.jpeg","images/image1.jpg"],
217
  ],
218
  inputs=[source_image, reference_image],
219
- outputs=[result, seed],
220
  fn=infer,
221
- cache_examples=True,
222
- cache_mode="lazy",
223
  elem_id="examples"
224
  )
225
 
 
226
  inputs = [
227
  source_image, reference_image,
228
  seed, randomize_seed, true_guidance_scale,
229
  num_inference_steps,
230
  ]
231
- outputs = [result, seed]
232
-
233
  run_btn.click(fn=infer, inputs=inputs, outputs=outputs)
234
 
 
 
 
 
 
 
 
235
  source_image.upload(
236
  fn=update_dimensions_on_upload,
237
  inputs=[source_image],
 
1
  import gradio as gr
2
+ from gradio_imageslider import ImageSlider
3
  import math
4
  import numpy as np
5
  import random
 
8
 
9
  from PIL import Image
10
  from diffusers import QwenImageEditPlusPipeline
11
+ from typing import Optional, Tuple, Any
12
 
13
  MAX_SEED = np.iinfo(np.int32).max
14
 
 
43
 
44
 
45
  def calculate_vae_gen_size(image: Image.Image) -> tuple:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  W, H = image.size
47
  ratio = W / H
48
  gen_w = math.sqrt(_VAE_IMAGE_SIZE * ratio)
49
  gen_h = gen_w / ratio
 
50
  gen_w = round(gen_w / 32) * 32
51
  gen_h = round(gen_h / 32) * 32
52
  return int(gen_w), int(gen_h)
53
 
54
 
 
55
  def update_dimensions_on_upload(image: Optional[Image.Image]) -> Image.Image:
 
 
 
 
 
56
  if image is None:
57
  return image
58
 
 
61
  original_width, original_height = image.size
62
  scale = min(MAX_SIDE / original_width, MAX_SIDE / original_height, 1.0)
63
 
 
64
  new_width = (int(original_width * scale) // 16) * 16
65
  new_height = (int(original_height * scale) // 16) * 16
66
 
 
79
  true_guidance_scale: float = 1.0,
80
  num_inference_steps: int = 4,
81
  progress=gr.Progress(track_tqdm=True)
82
+ ):
 
 
 
83
  if source_image is None:
84
  raise gr.Error("Please upload a source image (Image 1).")
85
  if reference_image is None:
 
92
  src_img = source_image.convert("RGB")
93
  ref_img = reference_image.convert("RGB")
94
 
 
95
  out_w, out_h = src_img.size
96
 
 
 
 
 
 
 
 
97
  gen_w, gen_h = calculate_vae_gen_size(src_img)
98
 
99
  result = pipe(
 
107
  num_images_per_prompt=1,
108
  ).images[0]
109
 
110
+ # Return updates to make sure the standard image shows up first, and the toggle button appears
111
+ return (
112
+ gr.update(value=result, visible=True), # Show single image
113
+ gr.update(value=(src_img, result), visible=False), # Hide slider
114
+ seed,
115
+ gr.update(visible=True, value="๐Ÿ” Compare Before & After") # Show toggle button
116
+ )
117
 
118
+ def toggle_compare_view(btn_text):
119
+ """Toggles visibility between the single image and the comparison slider."""
120
+ if "Compare" in btn_text:
121
+ # Hide image, Show Slider, Update Button text
122
+ return gr.update(visible=False), gr.update(visible=True), gr.update(value="๐Ÿ–ผ๏ธ Show Only Result")
123
+ else:
124
+ # Show image, Hide Slider, Update Button text
125
+ return gr.update(visible=True), gr.update(visible=False), gr.update(value="๐Ÿ” Compare Before & After")
126
 
127
 
128
  # --- UI ---
 
185
  )
186
 
187
  with gr.Column():
188
+ # Both components exist, but only one is visible at a time
189
+ result_image = gr.Image(label="Final Color-Graded Output", interactive=False)
190
+ compare_slider = ImageSlider(label="Before & After Comparison", interactive=False, visible=False)
191
+
192
+ # Hidden button that only appears AFTER the image is generated
193
+ compare_btn = gr.Button("๐Ÿ” Compare Before & After", visible=False, variant="secondary")
194
 
195
  gr.Examples(
196
  examples=[
 
198
  ["images/image2.jpeg","images/image1.jpg"],
199
  ],
200
  inputs=[source_image, reference_image],
201
+ outputs=[result_image, compare_slider, seed, compare_btn],
202
  fn=infer,
203
+ cache_examples=False, # Disabled caching to ensure gr.update() functions flawlessly
 
204
  elem_id="examples"
205
  )
206
 
207
+ # Trigger Generation
208
  inputs = [
209
  source_image, reference_image,
210
  seed, randomize_seed, true_guidance_scale,
211
  num_inference_steps,
212
  ]
213
+ outputs = [result_image, compare_slider, seed, compare_btn]
 
214
  run_btn.click(fn=infer, inputs=inputs, outputs=outputs)
215
 
216
+ # Trigger Toggle View
217
+ compare_btn.click(
218
+ fn=toggle_compare_view,
219
+ inputs=[compare_btn],
220
+ outputs=[result_image, compare_slider, compare_btn]
221
+ )
222
+
223
  source_image.upload(
224
  fn=update_dimensions_on_upload,
225
  inputs=[source_image],