Madiy commited on
Commit
673d852
·
verified ·
1 Parent(s): 443b4dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +81 -22
app.py CHANGED
@@ -11,6 +11,8 @@ import time
11
  import types
12
  import cv2
13
  import urllib.request
 
 
14
  import warnings
15
  import torch
16
  import numpy as np
@@ -95,28 +97,51 @@ print("Setting up GFPGAN & Real-ESRGAN...")
95
  weights_dir = "weights"
96
  os.makedirs(weights_dir, exist_ok=True)
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  # 1. Real-ESRGAN Weights Setup
99
  esrgan_path = f"{weights_dir}/RealESRGAN_x4plus.pth"
100
- if not os.path.exists(esrgan_path):
101
- url = "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth"
102
- urllib.request.urlretrieve(url, esrgan_path)
 
103
 
104
  esrgan_model = RRDBNet(
105
  num_in_ch=3, num_out_ch=3, num_feat=64,
106
  num_block=23, num_grow_ch=32, scale=4
107
  )
108
 
 
109
  enhancer = RealESRGANer(
110
  scale=4, model_path=esrgan_path, model=esrgan_model,
111
- tile=512, tile_pad=10, pre_pad=0, half=True,
112
- device=torch.device("cuda" if torch.cuda.is_available() else "cpu"),
 
113
  )
114
 
115
  # 2. GFPGAN Weights Setup
116
  gfpgan_path = f"{weights_dir}/GFPGANv1.3.pth"
117
- if not os.path.exists(gfpgan_path):
118
- url = "https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth"
119
- urllib.request.urlretrieve(url, gfpgan_path)
 
120
 
121
  gfpgan_restorer = GFPGANer(
122
  model_path=gfpgan_path,
@@ -124,7 +149,7 @@ gfpgan_restorer = GFPGANer(
124
  arch="clean",
125
  channel_multiplier=2,
126
  bg_upsampler=enhancer,
127
- device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
128
  )
129
 
130
  print("GFPGAN & Real-ESRGAN ready.")
@@ -158,10 +183,21 @@ print("All models loaded successfully.")
158
  # 6. HELPER FUNCTIONS
159
  # ================================================================
160
  def is_greyscale_or_sepia(image):
161
- rgb = np.array(image.convert("RGB"))
162
  hsv = cv2.cvtColor(rgb, cv2.COLOR_RGB2HSV)
163
- sat = np.mean(hsv[:, :, 1])
164
- return (sat < 25), False
 
 
 
 
 
 
 
 
 
 
 
165
 
166
 
167
  def calculate_padding(ow, oh, direction, pct):
@@ -174,7 +210,9 @@ def calculate_padding(ow, oh, direction, pct):
174
  elif direction == "Bottom": bottom = v_ext
175
  elif direction == "Horizontal": left = right = h_ext
176
  elif direction == "Vertical": top = bottom = v_ext
177
- elif direction in ["Both", "All Sides"]: left = right = top = bottom = h_ext
 
 
178
  return left, top, right, bottom
179
 
180
 
@@ -340,6 +378,9 @@ def fast_single_pass_outpaint(
340
  raise gr.Error("Please upload an image first.")
341
 
342
  start_time = time.time()
 
 
 
343
 
344
  image = cap_image(image, 1024)
345
  ow, oh = image.size
@@ -370,6 +411,20 @@ def fast_single_pass_outpaint(
370
  target_w = int(16 * math.ceil(target_w / 16))
371
  target_h = int(16 * math.ceil(target_h / 16))
372
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  canvas = Image.new("RGB", (target_w, target_h), (127, 127, 127))
374
  canvas.paste(image, (left, top))
375
 
@@ -429,15 +484,19 @@ def fast_single_pass_outpaint(
429
  bs = brisque(t, data_range=1.0).item()
430
  final_info += f"\n📉 BRISQUE: {bs:.2f} (lower better)"
431
 
432
- clip_model.to("cuda")
433
- with torch.no_grad():
434
- inputs = clip_processor(
435
- text=[prompt[:77]], images=current_img,
436
- return_tensors="pt", padding=True, truncation=True
437
- ).to("cuda")
438
- clip_score = clip_model(**inputs).logits_per_image.item()
439
- clip_model.to("cpu")
440
- final_info += f"\n🤖 CLIP: {clip_score:.4f} (higher better)"
 
 
 
 
441
  except Exception as e:
442
  final_info += f"\n⚠️ Metrics error: {e}"
443
 
 
11
  import types
12
  import cv2
13
  import urllib.request
14
+ import shutil
15
+ import tempfile
16
  import warnings
17
  import torch
18
  import numpy as np
 
97
  weights_dir = "weights"
98
  os.makedirs(weights_dir, exist_ok=True)
99
 
100
+ # Download into a temporary file and atomically publish only a complete weight file.
101
+ def ensure_weight_file(path, url, min_bytes=1_000_000):
102
+ if os.path.isfile(path) and os.path.getsize(path) >= min_bytes:
103
+ return
104
+
105
+ os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
106
+ fd, temp_path = tempfile.mkstemp(prefix=".download-", dir=os.path.dirname(path) or ".")
107
+ os.close(fd)
108
+ try:
109
+ with urllib.request.urlopen(url, timeout=60) as response, open(temp_path, "wb") as out:
110
+ shutil.copyfileobj(response, out)
111
+ if os.path.getsize(temp_path) < min_bytes:
112
+ raise RuntimeError(f"Downloaded weight file is unexpectedly small: {path}")
113
+ os.replace(temp_path, path)
114
+ finally:
115
+ if os.path.exists(temp_path):
116
+ os.remove(temp_path)
117
+
118
+
119
  # 1. Real-ESRGAN Weights Setup
120
  esrgan_path = f"{weights_dir}/RealESRGAN_x4plus.pth"
121
+ ensure_weight_file(
122
+ esrgan_path,
123
+ "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth",
124
+ )
125
 
126
  esrgan_model = RRDBNet(
127
  num_in_ch=3, num_out_ch=3, num_feat=64,
128
  num_block=23, num_grow_ch=32, scale=4
129
  )
130
 
131
+ restoration_device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
132
  enhancer = RealESRGANer(
133
  scale=4, model_path=esrgan_path, model=esrgan_model,
134
+ tile=512, tile_pad=10, pre_pad=0,
135
+ half=(restoration_device.type == "cuda"),
136
+ device=restoration_device,
137
  )
138
 
139
  # 2. GFPGAN Weights Setup
140
  gfpgan_path = f"{weights_dir}/GFPGANv1.3.pth"
141
+ ensure_weight_file(
142
+ gfpgan_path,
143
+ "https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth",
144
+ )
145
 
146
  gfpgan_restorer = GFPGANer(
147
  model_path=gfpgan_path,
 
149
  arch="clean",
150
  channel_multiplier=2,
151
  bg_upsampler=enhancer,
152
+ device=restoration_device
153
  )
154
 
155
  print("GFPGAN & Real-ESRGAN ready.")
 
183
  # 6. HELPER FUNCTIONS
184
  # ================================================================
185
  def is_greyscale_or_sepia(image):
186
+ rgb = np.asarray(image.convert("RGB"), dtype=np.uint8)
187
  hsv = cv2.cvtColor(rgb, cv2.COLOR_RGB2HSV)
188
+ mean_sat = float(np.mean(hsv[:, :, 1]))
189
+ is_greyscale = mean_sat < 25
190
+
191
+ # A simple warm-channel heuristic; intentionally avoid classifying grayscale
192
+ # images as sepia. This is a prompt hint, not a strict image classifier.
193
+ channel_means = rgb.mean(axis=(0, 1))
194
+ red_mean, green_mean, blue_mean = channel_means
195
+ is_sepia = (
196
+ not is_greyscale
197
+ and red_mean > green_mean + 8
198
+ and green_mean > blue_mean + 5
199
+ )
200
+ return is_greyscale, bool(is_sepia)
201
 
202
 
203
  def calculate_padding(ow, oh, direction, pct):
 
210
  elif direction == "Bottom": bottom = v_ext
211
  elif direction == "Horizontal": left = right = h_ext
212
  elif direction == "Vertical": top = bottom = v_ext
213
+ elif direction in ["Both", "All Sides"]:
214
+ left = right = h_ext
215
+ top = bottom = v_ext
216
  return left, top, right, bottom
217
 
218
 
 
378
  raise gr.Error("Please upload an image first.")
379
 
380
  start_time = time.time()
381
+ steps = int(steps)
382
+ if not 10 <= steps <= 50:
383
+ raise gr.Error("Inference steps must be between 10 and 50.")
384
 
385
  image = cap_image(image, 1024)
386
  ow, oh = image.size
 
411
  target_w = int(16 * math.ceil(target_w / 16))
412
  target_h = int(16 * math.ceil(target_h / 16))
413
 
414
+ # Bound worst-case FLUX memory use. Raise this only after validating the
415
+ # selected ZeroGPU allocation can handle larger canvases reliably.
416
+ max_outpaint_side = 2048
417
+ max_outpaint_pixels = 4_194_304
418
+ if (
419
+ target_w > max_outpaint_side
420
+ or target_h > max_outpaint_side
421
+ or target_w * target_h > max_outpaint_pixels
422
+ ):
423
+ raise gr.Error(
424
+ f"Requested canvas is too large ({target_w}x{target_h}). "
425
+ f"Reduce the extension percentage or use a smaller input image."
426
+ )
427
+
428
  canvas = Image.new("RGB", (target_w, target_h), (127, 127, 127))
429
  canvas.paste(image, (left, top))
430
 
 
484
  bs = brisque(t, data_range=1.0).item()
485
  final_info += f"\n📉 BRISQUE: {bs:.2f} (lower better)"
486
 
487
+ try:
488
+ clip_model.to("cuda")
489
+ with torch.no_grad():
490
+ inputs = clip_processor(
491
+ text=[prompt[:77]], images=current_img,
492
+ return_tensors="pt", padding=True, truncation=True
493
+ ).to("cuda")
494
+ clip_score = clip_model(**inputs).logits_per_image.item()
495
+ final_info += f"\n🤖 CLIP: {clip_score:.4f} (higher better)"
496
+ finally:
497
+ # Keep this large optional metrics model off the GPU even
498
+ # when preprocessing or scoring fails.
499
+ clip_model.to("cpu")
500
  except Exception as e:
501
  final_info += f"\n⚠️ Metrics error: {e}"
502