HAL1993 commited on
Commit
4e60d69
·
verified ·
1 Parent(s): 980ce16

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -189
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # --------------------------------------------------------------
2
- # Qwen‑Image‑Edit‑Plus (Optimized) – Minimal UI with full style
3
  # --------------------------------------------------------------
4
 
5
  import os
@@ -28,10 +28,7 @@ logger = logging.getLogger(__name__)
28
  # 1️⃣ Prompt translation (Albanian → English)
29
  # ------------------------------------------------------------------
30
  def translate_prompt(text: str) -> str:
31
- """
32
- Calls the external HF Space that translates Albanian → English.
33
- Returns the translated string (or raises a Gradio error).
34
- """
35
  if not text.strip():
36
  raise gr.Error("Please enter a prompt.")
37
  url = (
@@ -87,243 +84,214 @@ pipeline.load_lora_weights(
87
  )
88
  pipeline.fuse_lora()
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  # ------------------------------------------------------------------
91
- # 3️⃣ Core inference – runs on a GPU worker (Spaces)
92
  # ------------------------------------------------------------------
93
  @spaces.GPU(duration=60)
94
  def edit_images(image1, image2, prompt):
95
  """
96
- Takes two input images + a user prompt, translates the prompt to English,
97
  then runs the Qwen‑Image‑Edit‑Plus pipeline.
98
- Returns the first generated image.
99
  """
100
- # ------------------------------------------------------------------
101
- # Validate inputs
102
- # ------------------------------------------------------------------
103
  if image1 is None or image2 is None:
104
  raise gr.Error("Please upload **both** images.")
105
 
106
- # Gradio may give us a PIL.Image already – otherwise convert
107
  if not isinstance(image1, Image.Image):
108
  image1 = Image.fromarray(image1)
109
  if not isinstance(image2, Image.Image):
110
  image2 = Image.fromarray(image2)
111
 
112
- # ------------------------------------------------------------------
113
- # Prompt translation + small quality suffix (kept from the original UI)
114
- # ------------------------------------------------------------------
115
  translated = translate_prompt(prompt.strip())
116
- prompt_enhanced = translated + ", high quality, detailed, vibrant, professional lighting"
117
 
118
- # ------------------------------------------------------------------
119
- # Random seed (kept deterministic per request)
120
- # ------------------------------------------------------------------
121
- seed = random.randint(0, np.iinfo(np.int32).max)
122
  generator = torch.Generator(device="cuda").manual_seed(seed)
123
 
124
- # ------------------------------------------------------------------
125
- # Run the pipeline (no extra CFG or negative prompt – defaults)
126
- # ------------------------------------------------------------------
127
  with torch.inference_mode():
128
  out = pipeline(
129
  image=[image1, image2],
130
  prompt=prompt_enhanced,
131
  generator=generator,
132
- num_inference_steps=4, # Lightning 4‑step inference
133
- true_cfg_scale=1.0, # no classifier‑free guidance
 
 
 
134
  )
135
  return out.images[0] if out.images else None
136
 
137
 
138
  # ------------------------------------------------------------------
139
- # 4️⃣ UI – full visual theme, only the required components
140
  # ------------------------------------------------------------------
141
  def create_demo():
142
  with gr.Blocks(css="", title="Qwen Image Edit Plus (Optimized)") as demo:
143
  # --------------------------------------------------------------
144
- # 4️⃣‑a️⃣ Full CSS / JS from the first UI you posted
145
  # --------------------------------------------------------------
146
  gr.HTML(
147
  """
148
  <style>
149
  @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;700&display=swap');
150
- @keyframes glow {
151
- 0% { box-shadow: 0 0 14px rgba(0, 255, 128, 0.5); }
152
- 50% { box-shadow: 0 0 14px rgba(0, 255, 128, 0.7); }
153
- 100% { box-shadow: 0 0 14px rgba(0, 255, 128, 0.5); }
154
- }
155
- @keyframes glow-hover {
156
- 0% { box-shadow: 0 0 20px rgba(0, 255, 128, 0.7); }
157
- 50% { box-shadow: 0 0 20px rgba(0, 255, 128, 0.9); }
158
- 100% { box-shadow: 0 0 20px rgba(0, 255, 128, 0.7); }
 
 
 
 
 
 
 
 
159
  }
160
- @keyframes slide {
161
- 0% { background-position: 0% 50%; }
162
- 50% { background-position: 100% 50%; }
163
- 100% { background-position: 0% 50%; }
 
 
164
  }
165
- body {
166
- background: #000000 !important;
167
- color: #FFFFFF !important;
168
- font-family: 'Orbitron', sans-serif;
169
- min-height: 100vh;
170
- margin: 0 !important;
171
- padding: 0 !important;
172
- width: 100% !important;
173
- max-width: 100vw !important;
174
- overflow-x: hidden !important;
175
- display: flex !important;
176
- justify-content: center;
177
- align-items: center;
178
- flex-direction: column;
179
  }
180
- .gr-blocks, .container {
181
- width: 100% !important;
182
- max-width: 100vw !important;
183
- background: #000000 !important;
184
- color: #FFFFFF !important;
185
- margin: 0 !important;
186
- padding: 0 !important;
187
- box-sizing: border-box !important;
188
- overflow-x: hidden !important;
 
189
  }
190
- #general_items {
191
- width: 100% !important;
192
- max-width: 100vw !important;
193
- margin: 2rem 0 !important;
194
- display: flex !important;
195
- flex-direction: column;
196
- align-items: center;
197
- justify-content: center;
198
- background: #000000 !important;
199
- color: #FFFFFF !important;
200
  }
201
- #input_column {
202
- background: #000000 !important;
203
- border: none !important;
204
- border-radius: 8px;
205
- padding: 1rem !important;
206
- box-shadow: 0 0 10px rgba(255, 255, 255, 0.3) !important;
207
- width: 100% !important;
208
- max-width: 100vw !important;
209
- box-sizing: border-box !important;
210
- color: #FFFFFF !important;
211
  }
212
- h1 {
213
- font-size: 5rem;
214
- font-weight: 700;
215
- text-align: center;
216
- color: #FFFFFF !important;
217
- text-shadow: 0 0 8px rgba(255, 255, 255, 0.3) !important;
218
- margin: 0 auto 0.5rem auto;
219
- display: block;
 
 
 
 
 
220
  }
221
- #subtitle {
222
- font-size: 1rem;
223
- text-align: center;
224
- color: #FFFFFF !important;
225
- opacity: 0.8;
226
- margin-bottom: 1rem;
227
- display: block;
 
 
 
 
228
  }
229
- .gradio-component {
230
- background: #000000 !important;
231
- border: none;
232
- margin: 0.75rem 0;
233
- width: 100% !important;
234
- max-width: 100vw !important;
235
- color: #FFFFFF !important;
 
 
 
 
 
 
 
 
 
236
  }
237
- .image-container {
238
- aspect-ratio: 1/1;
239
- width: 100% !important;
240
- max-width: 100vw !important;
241
- min-height: 500px;
242
- height: auto;
243
- border: 0.5px solid #FFFFFF !important;
244
- border-radius: 4px;
245
- box-sizing: border-box !important;
246
- background: #000000 !important;
247
- box-shadow: 0 0 10px rgba(255, 255, 255, 0.3) !important;
248
- position: relative;
249
- color: #FFFFFF !important;
250
  }
251
- .image-container img {
252
- width: 100% !important;
253
- height: auto;
254
- display: block !important;
 
 
 
 
 
 
 
 
255
  }
256
- input, textarea, select {
257
- background: #000000 !important;
258
- color: #FFFFFF !important;
259
- border: 1px solid #FFFFFF !important;
260
- border-radius: 4px;
261
- padding: 0.5rem;
262
- width: 100% !important;
263
- max-width: 100vw !important;
264
- box-sizing: border-box !important;
265
- font-family: 'Orbitron', sans-serif;
266
- }
267
- .gr-button-primary {
268
- background: linear-gradient(90deg, rgba(0,255,128,0.3), rgba(0,200,100,0.3), rgba(0,255,128,0.3)) !important;
269
- background-size: 200% 100%;
270
- animation: slide 4s ease-in-out infinite, glow 3s ease-in-out infinite;
271
- color: #FFFFFF !important;
272
- border: 1px solid #FFFFFF !important;
273
- border-radius: 6px;
274
- padding: 0.75rem 1.5rem;
275
- font-size: 1.1rem;
276
- font-weight: 600;
277
- box-shadow: 0 0 14px rgba(0,255,128,0.7) !important;
278
- transition: box-shadow 0.3s, transform 0.3s;
279
- width: 100% !important;
280
- max-width: 100vw !important;
281
- min-height: 48px;
282
- cursor: pointer;
283
- }
284
- .gr-button-primary:hover {
285
- box-shadow: 0 0 20px rgba(0,255,128,0.9) !important;
286
- animation: slide 4s ease-in-out infinite, glow-hover 3s ease-in-out infinite;
287
- transform: scale(1.05);
288
- }
289
- button[aria-label="Fullscreen"], button[aria-label="Share"] { display: none !important; }
290
- button[aria-label="Download"] {
291
- transform: scale(3);
292
- transform-origin: top right;
293
- background: #000000 !important;
294
- color: #FFFFFF !important;
295
- border: 1px solid #FFFFFF !important;
296
- border-radius: 4px;
297
- padding: 0.4rem !important;
298
- margin: 0.5rem !important;
299
- box-shadow: 0 0 8px rgba(255,255,255,0.3) !important;
300
- transition: box-shadow 0.3s;
301
- }
302
- button[aria-label="Download"]:hover {
303
- box-shadow: 0 0 12px rgba(255,255,255,0.5) !important;
304
- }
305
- .progress-text, .gr-progress, .gr-prose, .gr-log,
306
- footer, .gr-button-secondary, .gr-accordion, .gr-examples,
307
- .gr-group { display: none !important; }
308
- @media (max-width: 768px) {
309
- h1 { font-size: 4rem; }
310
- #subtitle { font-size: 0.9rem; }
311
- .gr-button-primary {
312
- padding: 0.6rem 1rem;
313
- font-size: 1rem;
314
- box-shadow: 0 0 10px rgba(0,255,128,0.7) !important;
315
- }
316
- .image-container { min-height: 300px; }
317
  }
318
  </style>
319
  <script>
320
  // Debug helper – logs dimensions and forces hidden UI bits
321
  document.addEventListener('DOMContentLoaded', () => {
322
  const containers = document.querySelectorAll('#general_items, #input_column, .image-container');
323
- containers.forEach(c => {
324
- console.log(`Container ${c.id||c.className}: ${c.offsetWidth}px`);
325
- });
326
- // Hide any stray toolbar elements (just in case)
327
  const toolbar = document.querySelectorAll('.gr-file-upload, .gr-image-toolbar, .gr-button');
328
  toolbar.forEach(el => el.style.display='none');
329
  });
@@ -332,7 +300,7 @@ def create_demo():
332
  )
333
 
334
  # --------------------------------------------------------------
335
- # 4️⃣‑b️⃣ Main layout – exactly the pieces you asked for
336
  # --------------------------------------------------------------
337
  with gr.Row(elem_id="general_items"):
338
  # ----- Left column: inputs -------------------------------------------------
@@ -371,7 +339,7 @@ def create_demo():
371
  )
372
 
373
  # --------------------------------------------------------------
374
- # 4️⃣‑c️⃣ Wire the button to the inference function
375
  # --------------------------------------------------------------
376
  generate_btn.click(
377
  fn=edit_images,
@@ -383,6 +351,9 @@ def create_demo():
383
  return demo
384
 
385
 
 
 
 
386
  if __name__ == "__main__":
387
  logger.info(f"Gradio version: {gr.__version__}")
388
  demo = create_demo()
 
1
  # --------------------------------------------------------------
2
+ # Qwen‑Image‑Edit‑Plus (Optimized) – Minimal UI + hidden dev params
3
  # --------------------------------------------------------------
4
 
5
  import os
 
28
  # 1️⃣ Prompt translation (Albanian → English)
29
  # ------------------------------------------------------------------
30
  def translate_prompt(text: str) -> str:
31
+ """Calls the external HF Space that translates Albanian → English."""
 
 
 
32
  if not text.strip():
33
  raise gr.Error("Please enter a prompt.")
34
  url = (
 
84
  )
85
  pipeline.fuse_lora()
86
 
87
+
88
+ # ------------------------------------------------------------------
89
+ # 3️⃣ **Hidden developer‑only inference parameters**
90
+ # ------------------------------------------------------------------
91
+ # You can edit any of these constants in the source file – they are NOT
92
+ # exposed to the end‑user UI.
93
+ DEFAULT_SEED = 0 # 0 → deterministic; set to random if you like
94
+ DEFAULT_NUM_STEPS = 4 # Lightning version uses 4 steps
95
+ DEFAULT_TRUE_CFG_SCALE = 1.0 # Keep at 1.0 for the Lightning LoRA
96
+ DEFAULT_GUIDANCE_SCALE = 1.0 # Same as true_cfg_scale for this model
97
+ DEFAULT_NEGATIVE_PROMPT = "" # Empty by default; change if you want a negative prompt
98
+ QUALITY_SUFFIX = ", high quality, detailed, vibrant, professional lighting"
99
+
100
+
101
  # ------------------------------------------------------------------
102
+ # 4️⃣ Core inference – runs on a GPU worker (Spaces)
103
  # ------------------------------------------------------------------
104
  @spaces.GPU(duration=60)
105
  def edit_images(image1, image2, prompt):
106
  """
107
+ Takes two input images + a prompt, translates the prompt,
108
  then runs the Qwen‑Image‑Edit‑Plus pipeline.
109
+ The hyper‑parameters used are the hidden constants above.
110
  """
 
 
 
111
  if image1 is None or image2 is None:
112
  raise gr.Error("Please upload **both** images.")
113
 
114
+ # Convert possible ndarray PIL.Image
115
  if not isinstance(image1, Image.Image):
116
  image1 = Image.fromarray(image1)
117
  if not isinstance(image2, Image.Image):
118
  image2 = Image.fromarray(image2)
119
 
120
+ # 1️⃣ Translate prompt + add quality suffix
 
 
121
  translated = translate_prompt(prompt.strip())
122
+ prompt_enhanced = translated + QUALITY_SUFFIX
123
 
124
+ # 2️⃣ Seed & generator (editable via DEFAULT_SEED)
125
+ seed = DEFAULT_SEED if DEFAULT_SEED != 0 else random.randint(0, np.iinfo(np.int32).max)
 
 
126
  generator = torch.Generator(device="cuda").manual_seed(seed)
127
 
128
+ # 3️⃣ Run the pipeline (Lightning defaults)
 
 
129
  with torch.inference_mode():
130
  out = pipeline(
131
  image=[image1, image2],
132
  prompt=prompt_enhanced,
133
  generator=generator,
134
+ num_inference_steps=DEFAULT_NUM_STEPS,
135
+ true_cfg_scale=DEFAULT_TRUE_CFG_SCALE,
136
+ # `guidance_scale` is ignored by the Lightning LoRA but kept for completeness
137
+ # (the pipeline internally uses `true_cfg_scale` only)
138
+ negative_prompt=DEFAULT_NEGATIVE_PROMPT,
139
  )
140
  return out.images[0] if out.images else None
141
 
142
 
143
  # ------------------------------------------------------------------
144
+ # 5️⃣ UI – full visual theme, only the four components you want
145
  # ------------------------------------------------------------------
146
  def create_demo():
147
  with gr.Blocks(css="", title="Qwen Image Edit Plus (Optimized)") as demo:
148
  # --------------------------------------------------------------
149
+ # 5️⃣‑a️⃣ Full CSS / JS (exactly the reference style, includes 600 px top gap)
150
  # --------------------------------------------------------------
151
  gr.HTML(
152
  """
153
  <style>
154
  @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;700&display=swap');
155
+ @keyframes glow { 0%{box-shadow:0 0 14px rgba(0,255,128,0.5);} 50%{box-shadow:0 0 14px rgba(0,255,128,0.7);} 100%{box-shadow:0 0 14px rgba(0,255,128,0.5);} }
156
+ @keyframes glow-hover { 0%{box-shadow:0 0 20px rgba(0,255,128,0.7);} 50%{box-shadow:0 0 20px rgba(0,255,128,0.9);} 100%{box-shadow:0 0 20px rgba(0,255,128,0.7);} }
157
+ @keyframes slide { 0%{background-position:0% 50%;} 50%{background-position:100% 50%;} 100%{background-position:0% 50%;} }
158
+ body{
159
+ background:#000000 !important;
160
+ color:#FFFFFF !important;
161
+ font-family:'Orbitron',sans-serif;
162
+ min-height:100vh;
163
+ margin:0 !important;
164
+ padding:0 !important;
165
+ width:100% !important;
166
+ max-width:100vw !important;
167
+ overflow-x:hidden !important;
168
+ display:flex !important;
169
+ justify-content:center;
170
+ align-items:center;
171
+ flex-direction:column;
172
  }
173
+ /* 600 px empty strip at the top – exactly as you asked */
174
+ body::before{
175
+ content:"";
176
+ display:block;
177
+ height:600px;
178
+ background:#000000 !important;
179
  }
180
+ .gr-blocks,.container{
181
+ width:100% !important;
182
+ max-width:100vw !important;
183
+ background:#000000 !important;
184
+ color:#FFFFFF !important;
185
+ margin:0 !important;
186
+ padding:0 !important;
187
+ box-sizing:border-box !important;
188
+ overflow-x:hidden !important;
 
 
 
 
 
189
  }
190
+ #general_items{
191
+ width:100% !important;
192
+ max-width:100vw !important;
193
+ margin:2rem 0 !important;
194
+ display:flex !important;
195
+ flex-direction:column;
196
+ align-items:center;
197
+ justify-content:center;
198
+ background:#000000 !important;
199
+ color:#FFFFFF !important;
200
  }
201
+ #input_column{
202
+ background:#000000 !important;
203
+ border:none !important;
204
+ border-radius:8px;
205
+ padding:1rem !important;
206
+ box-shadow:0 0 10px rgba(255,255,255,0.3) !important;
207
+ width:100% !important;
208
+ max-width:100vw !important;
209
+ box-sizing:border-box !important;
210
+ color:#FFFFFF !important;
211
  }
212
+ .gradio-component{
213
+ background:#000000 !important;
214
+ border:none;
215
+ margin:0.75rem 0;
216
+ width:100% !important;
217
+ max-width:100vw !important;
218
+ color:#FFFFFF !important;
 
 
 
219
  }
220
+ .image-container{
221
+ aspect-ratio:1/1;
222
+ width:100% !important;
223
+ max-width:100vw !important;
224
+ min-height:500px;
225
+ height:auto;
226
+ border:0.5px solid #FFFFFF !important;
227
+ border-radius:4px;
228
+ box-sizing:border-box !important;
229
+ background:#000000 !important;
230
+ box-shadow:0 0 10px rgba(255,255,255,0.3) !important;
231
+ position:relative;
232
+ color:#FFFFFF !important;
233
  }
234
+ .image-container img{width:100% !important;height:auto;display:block !important;}
235
+ input,textarea,select{
236
+ background:#000000 !important;
237
+ color:#FFFFFF !important;
238
+ border:1px solid #FFFFFF !important;
239
+ border-radius:4px;
240
+ padding:0.5rem;
241
+ width:100% !important;
242
+ max-width:100vw !important;
243
+ box-sizing:border-box !important;
244
+ font-family:'Orbitron',sans-serif;
245
  }
246
+ .gr-button-primary{
247
+ background:linear-gradient(90deg,rgba(0,255,128,0.3),rgba(0,200,100,0.3),rgba(0,255,128,0.3)) !important;
248
+ background-size:200% 100%;
249
+ animation:slide 4s ease-in-out infinite,glow 3s ease-in-out infinite;
250
+ color:#FFFFFF !important;
251
+ border:1px solid #FFFFFF !important;
252
+ border-radius:6px;
253
+ padding:0.75rem 1.5rem;
254
+ font-size:1.1rem;
255
+ font-weight:600;
256
+ box-shadow:0 0 14px rgba(0,255,128,0.7) !important;
257
+ transition:box-shadow 0.3s,transform 0.3s;
258
+ width:100% !important;
259
+ max-width:100vw !important;
260
+ min-height:48px;
261
+ cursor:pointer;
262
  }
263
+ .gr-button-primary:hover{
264
+ box-shadow:0 0 20px rgba(0,255,128,0.9) !important;
265
+ animation:slide 4s ease-in-out infinite,glow-hover 3s ease-in-out infinite;
266
+ transform:scale(1.05);
 
 
 
 
 
 
 
 
 
267
  }
268
+ button[aria-label="Fullscreen"],button[aria-label="Share"]{display:none !important;}
269
+ button[aria-label="Download"]{
270
+ transform:scale(3);
271
+ transform-origin:top right;
272
+ background:#000000 !important;
273
+ color:#FFFFFF !important;
274
+ border:1px solid #FFFFFF !important;
275
+ border-radius:4px;
276
+ padding:0.4rem !important;
277
+ margin:0.5rem !important;
278
+ box-shadow:0 0 8px rgba(255,255,255,0.3) !important;
279
+ transition:box-shadow 0.3s;
280
  }
281
+ button[aria-label="Download"]:hover{box-shadow:0 0 12px rgba(255,255,255,0.5) !important;}
282
+ .progress-text,.gr-progress,.gr-prose,.gr-log,
283
+ footer,.gr-button-secondary,.gr-accordion,.gr-examples,
284
+ .gr-group{display:none !important;}
285
+ @media (max-width:768px){
286
+ .gr-button-primary{padding:0.6rem 1rem;font-size:1rem;box-shadow:0 0 10px rgba(0,255,128,0.7) !important;}
287
+ .image-container{min-height:300px;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  }
289
  </style>
290
  <script>
291
  // Debug helper – logs dimensions and forces hidden UI bits
292
  document.addEventListener('DOMContentLoaded', () => {
293
  const containers = document.querySelectorAll('#general_items, #input_column, .image-container');
294
+ containers.forEach(c => console.log(`Container ${c.id||c.className}: ${c.offsetWidth}px`));
 
 
 
295
  const toolbar = document.querySelectorAll('.gr-file-upload, .gr-image-toolbar, .gr-button');
296
  toolbar.forEach(el => el.style.display='none');
297
  });
 
300
  )
301
 
302
  # --------------------------------------------------------------
303
+ # 5️⃣‑b️⃣ Main layout – only the four widgets you asked for
304
  # --------------------------------------------------------------
305
  with gr.Row(elem_id="general_items"):
306
  # ----- Left column: inputs -------------------------------------------------
 
339
  )
340
 
341
  # --------------------------------------------------------------
342
+ # 5️⃣‑c️⃣ Wire the button to the inference function
343
  # --------------------------------------------------------------
344
  generate_btn.click(
345
  fn=edit_images,
 
351
  return demo
352
 
353
 
354
+ # ------------------------------------------------------------------
355
+ # 6️⃣ Launch
356
+ # ------------------------------------------------------------------
357
  if __name__ == "__main__":
358
  logger.info(f"Gradio version: {gr.__version__}")
359
  demo = create_demo()