jgitsolutions commited on
Commit
a323027
·
verified ·
1 Parent(s): 946ca97

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +58 -147
  2. requirements.txt +1 -4
app.py CHANGED
@@ -1,7 +1,5 @@
1
  import gradio as gr
2
  import torch
3
- from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_upscale import StableDiffusionUpscalePipeline
4
- from transformers import AutoImageProcessor, Swin2SRForImageSuperResolution
5
  import gc
6
  from PIL import Image
7
  import numpy as np
@@ -22,8 +20,6 @@ class Config:
22
  MODEL_DIR = "weights"
23
  REALESRGAN_URL = "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth"
24
  REALESRGAN_FILENAME = "RealESRGAN_x2plus.pth"
25
- SWIN2SR_ID = "caidas/swin2SR-classical-sr-x2-64"
26
- SD_ID = "stabilityai/stable-diffusion-x4-upscaler"
27
 
28
  # SOTA Models (2025)
29
  SPAN_URL = "https://huggingface.co/Phips/2xNomosUni_span_multijpg/resolve/main/2xNomosUni_span_multijpg.safetensors"
@@ -31,7 +27,6 @@ class Config:
31
  HATS_URL = "https://huggingface.co/Phips/4xNomos8kSCHAT-S/resolve/main/4xNomos8kSCHAT-S.safetensors"
32
  HATS_FILENAME = "4xNomos8kSCHAT-S.safetensors"
33
 
34
- MAX_IMAGE_SIZE_SD = 512 # Max dimension for SD input to prevent OOM
35
  DEVICE = "cpu" # Force CPU for this demo, can be "cuda" if available
36
 
37
  @staticmethod
@@ -193,22 +188,22 @@ class RealESRGANStrategy(UpscalerStrategy):
193
  # 'reduce-overhead' uses CUDA graphs, so only use it on CUDA
194
  if Config.DEVICE == 'cuda':
195
  self.model = torch.compile(self.model, mode='reduce-overhead')
196
- logger.info(" torch.compile enabled (reduce-overhead mode)")
197
  elif os.name == 'nt' and Config.DEVICE == 'cpu':
198
  # Windows requires MSVC for Inductor (default cpu backend)
199
  # We skip it to avoid "Compiler: cl is not found" error unless user has it.
200
- logger.info(" Skipping torch.compile on Windows CPU to avoid MSVC requirement.")
201
  elif (psutil.cpu_count(logical=False) or 0) < 4 and Config.DEVICE == 'cpu':
202
  # Skip compilation on weak CPUs (e.g. HF Spaces Free Tier) to avoid long startup times
203
- logger.info(" Skipping torch.compile on low-core CPU to prevent timeout.")
204
  else:
205
  # On Linux/Mac CPU, use default mode or skip if problematic. Default is usually safe.
206
  self.model = torch.compile(self.model)
207
- logger.info(" torch.compile enabled (default mode)")
208
 
209
  self.compiled = True
210
  except Exception as e:
211
- logger.warning(f" torch.compile not available or failed: {e}")
212
  self.compiled = True # Mark as tried
213
 
214
  logger.info(f"{self.name} loaded successfully.")
@@ -270,102 +265,6 @@ class RealESRGANStrategy(UpscalerStrategy):
270
 
271
  return Image.fromarray(output_np)
272
 
273
- class Swin2SRStrategy(UpscalerStrategy):
274
- def __init__(self):
275
- super().__init__()
276
- self.name = "Swin2SR x2"
277
- self.processor = None
278
-
279
- def load(self) -> None:
280
- if self.model is None:
281
- logger.info(f"Loading {self.name}...")
282
- try:
283
- self.processor = AutoImageProcessor.from_pretrained(Config.SWIN2SR_ID)
284
- model = Swin2SRForImageSuperResolution.from_pretrained(Config.SWIN2SR_ID)
285
- self.model = model.to(Config.DEVICE) # type: ignore
286
- logger.info(f"{self.name} loaded successfully.")
287
- except Exception as e:
288
- logger.error(f"Failed to load Swin2SR: {e}")
289
- # Swin2SR loading failure is often due to transformers version mismatch or device issues
290
- # We re-raise to let the UI handle it, but log the specific error
291
- raise
292
-
293
- def upscale(self, image: Image.Image, **kwargs) -> Image.Image:
294
- if self.model is None or self.processor is None:
295
- self.load()
296
-
297
- logger.info(f"Starting inference with {self.name}...")
298
- start_time = time.time()
299
-
300
- if self.processor is None:
301
- raise ValueError("Processor not loaded")
302
-
303
- inputs = self.processor(images=image, return_tensors="pt").to(Config.DEVICE)
304
-
305
- # Swin2SR on CPU can be finicky with autocast/tracing.
306
- # Explicitly disable autocast for Swin2SR on CPU to avoid "PythonFallbackKernel" errors
307
- context = torch.no_grad()
308
-
309
- with context:
310
- outputs = self.model(**inputs)
311
-
312
- output = outputs.reconstruction.data.squeeze().float().cpu().clamp_(0, 1).numpy()
313
- output = np.moveaxis(output, source=0, destination=-1)
314
- output = (output * 255.0).round().astype(np.uint8)
315
-
316
- logger.info(f"Inference finished in {time.time() - start_time:.2f}s")
317
- return Image.fromarray(output)
318
-
319
- class StableDiffusionStrategy(UpscalerStrategy):
320
- def __init__(self):
321
- super().__init__()
322
- self.name = "Stable Diffusion x4"
323
-
324
- def load(self) -> None:
325
- if self.model is None:
326
- logger.info(f"Loading {self.name} (this may take time)...")
327
- try:
328
- self.model = StableDiffusionUpscalePipeline.from_pretrained(
329
- Config.SD_ID,
330
- torch_dtype=torch.float32,
331
- low_cpu_mem_usage=True
332
- )
333
- # Optimizations for CPU
334
- self.model.enable_attention_slicing("max")
335
- self.model.enable_vae_tiling()
336
- logger.info(f"{self.name} loaded successfully.")
337
- except Exception as e:
338
- logger.error(f"Failed to load Stable Diffusion: {e}")
339
- raise
340
-
341
- def upscale(self, image: Image.Image, **kwargs) -> Image.Image:
342
- if self.model is None:
343
- self.load()
344
-
345
- prompt = kwargs.get("prompt", "high quality, detailed")
346
-
347
- # Pre-check size
348
- if max(image.size) > Config.MAX_IMAGE_SIZE_SD:
349
- ratio = Config.MAX_IMAGE_SIZE_SD / max(image.size)
350
- new_size = (int(image.size[0] * ratio), int(image.size[1] * ratio))
351
- image = image.resize(new_size, Image.Resampling.LANCZOS)
352
- logger.warning(f"Resized input to {new_size} to prevent OOM on CPU.")
353
-
354
- logger.info(f"Starting inference with {self.name}...")
355
- start_time = time.time()
356
-
357
- generator = torch.manual_seed(42)
358
- output = self.model(
359
- prompt=prompt,
360
- image=image,
361
- num_inference_steps=20,
362
- guidance_scale=7.0,
363
- generator=generator
364
- ).images[0] # type: ignore
365
-
366
- logger.info(f"Inference finished in {time.time() - start_time:.2f}s")
367
- return output
368
-
369
  class SpanStrategy(UpscalerStrategy):
370
  def __init__(self):
371
  super().__init__()
@@ -401,14 +300,14 @@ class SpanStrategy(UpscalerStrategy):
401
  try:
402
  if Config.DEVICE == 'cuda':
403
  self.model = torch.compile(self.model, mode='reduce-overhead')
404
- logger.info(" torch.compile enabled (reduce-overhead mode)")
405
  elif os.name == 'nt' and Config.DEVICE == 'cpu':
406
- logger.info(" Skipping torch.compile on Windows CPU.")
407
  elif (psutil.cpu_count(logical=False) or 0) < 4 and Config.DEVICE == 'cpu':
408
- logger.info(" Skipping torch.compile on low-core CPU.")
409
  else:
410
  # SPAN architecture uses .data.clone() in forward pass which breaks torch.compile/inductor
411
- logger.info(" Skipping torch.compile for SPAN (incompatible architecture).")
412
  # self.model = torch.compile(self.model)
413
  self.compiled = True
414
  except Exception:
@@ -503,7 +402,7 @@ class HatsStrategy(UpscalerStrategy):
503
  pass
504
  else:
505
  # HAT architecture also triggers "UntypedStorage" weakref errors with inductor on CPU
506
- logger.info(" Skipping torch.compile for HAT-S (incompatible architecture).")
507
  # self.model = torch.compile(self.model)
508
  self.compiled = True
509
  except Exception:
@@ -560,9 +459,7 @@ class UpscalerManager:
560
  self.strategies: Dict[str, UpscalerStrategy] = {
561
  "SPAN (NomosUni) x2": SpanStrategy(),
562
  "RealESRGAN x2": RealESRGANStrategy(),
563
- "HAT-S x4": HatsStrategy(),
564
- "Swin2SR x2": Swin2SRStrategy(),
565
- "Stable Diffusion x4": StableDiffusionStrategy()
566
  }
567
  self.current_model_name: Optional[str] = None
568
 
@@ -590,26 +487,31 @@ class UpscalerManager:
590
  manager = UpscalerManager()
591
 
592
  # --- Gradio Interface Logic ---
593
- def process_image(input_img: Image.Image, model_name: str, prompt: str) -> Tuple[Optional[Image.Image], str, str]:
594
  if input_img is None:
595
  return None, get_logs(), get_system_usage()
596
 
597
  try:
598
  strategy = manager.get_strategy(model_name)
599
 
600
- # Optional: Unload others if memory is tight (simple logic here)
601
- # For now, we just rely on the user or OS, but in prod we might auto-unload.
602
 
603
- output = strategy.upscale(input_img, prompt=prompt)
 
 
 
 
 
 
 
604
 
605
  # Explicit GC after heavy operations
606
  gc.collect()
607
 
608
- return output, get_logs(), get_system_usage()
609
  except Exception as e:
610
  error_msg = f"Critical Error: {str(e)}\n{traceback.format_exc()}"
611
  logger.error(error_msg)
612
- # Return the error message in the logs output so the user sees it
613
  return None, get_logs() + "\n\n" + error_msg, get_system_usage()
614
 
615
  def unload_models():
@@ -618,49 +520,61 @@ def unload_models():
618
 
619
  # --- UI Construction ---
620
  desc = """
621
- ### 🚀 Enterprise-Grade Universal Upscaler (SOTA 2025)
622
- Select a specialized model to upscale your image.
623
- * **SPAN (NomosUni) x2**: **SOTA Speed**. Fastest CPU model. Best for general use.
624
- * **RealESRGAN x2**: 🛡️ **Robust**. Best for removing JPEG artifacts and noise.
625
- * **HAT-S x4**: 💎 **SOTA Quality**. Best texture detail (slower).
626
- * **Swin2SR x2**: 🎯 High fidelity, removes compression artifacts.
627
- * **Stable Diffusion x4**: 🎨 Generative upscaling. Adds missing details (slow, high RAM).
 
 
 
 
 
 
 
 
 
 
 
628
  """
629
 
630
  with gr.Blocks(title="Universal Upscaler Pro") as iface:
631
  gr.Markdown(desc)
632
 
633
  with gr.Row():
634
- with gr.Column(scale=1):
635
- input_image = gr.Image(type="pil", label="Input Image")
636
 
637
- with gr.Group():
638
  model_selector = gr.Dropdown(
639
  choices=list(manager.strategies.keys()),
640
  value="SPAN (NomosUni) x2",
641
- label="Select Model Architecture"
 
642
  )
643
- prompt_input = gr.Textbox(
644
- label="Prompt (Stable Diffusion Only)",
645
- value="highly detailed, 4k, sharp",
646
- placeholder="Describe the image content..."
 
647
  )
648
 
 
 
649
  with gr.Accordion("Advanced Settings", open=False):
650
- gr.Markdown("Memory Management")
651
  unload_btn = gr.Button("Unload All Models (Free RAM)", variant="secondary")
652
-
653
- submit_btn = gr.Button("✨ Upscale Image", variant="primary", size="lg")
654
- system_info = gr.Label(value=get_system_usage(), label="System Status")
655
 
656
- with gr.Column(scale=1):
657
- output_image = gr.Image(type="pil", label="Upscaled Result")
658
- logs_output = gr.TextArea(label="Execution Logs", interactive=False, lines=10)
659
 
660
  # Event Wiring
661
  submit_btn.click(
662
  fn=process_image,
663
- inputs=[input_image, model_selector, prompt_input],
664
  outputs=[output_image, logs_output, system_info]
665
  )
666
 
@@ -669,8 +583,5 @@ with gr.Blocks(title="Universal Upscaler Pro") as iface:
669
  inputs=[],
670
  outputs=[logs_output, system_info]
671
  )
672
-
673
- # Auto-refresh system info every 2 seconds (optional, can be heavy on UI)
674
- # iface.load(get_system_usage, None, system_info, every=2)
675
 
676
- iface.launch()
 
1
  import gradio as gr
2
  import torch
 
 
3
  import gc
4
  from PIL import Image
5
  import numpy as np
 
20
  MODEL_DIR = "weights"
21
  REALESRGAN_URL = "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth"
22
  REALESRGAN_FILENAME = "RealESRGAN_x2plus.pth"
 
 
23
 
24
  # SOTA Models (2025)
25
  SPAN_URL = "https://huggingface.co/Phips/2xNomosUni_span_multijpg/resolve/main/2xNomosUni_span_multijpg.safetensors"
 
27
  HATS_URL = "https://huggingface.co/Phips/4xNomos8kSCHAT-S/resolve/main/4xNomos8kSCHAT-S.safetensors"
28
  HATS_FILENAME = "4xNomos8kSCHAT-S.safetensors"
29
 
 
30
  DEVICE = "cpu" # Force CPU for this demo, can be "cuda" if available
31
 
32
  @staticmethod
 
188
  # 'reduce-overhead' uses CUDA graphs, so only use it on CUDA
189
  if Config.DEVICE == 'cuda':
190
  self.model = torch.compile(self.model, mode='reduce-overhead')
191
+ logger.info("[INFO] torch.compile enabled (reduce-overhead mode)")
192
  elif os.name == 'nt' and Config.DEVICE == 'cpu':
193
  # Windows requires MSVC for Inductor (default cpu backend)
194
  # We skip it to avoid "Compiler: cl is not found" error unless user has it.
195
+ logger.info("[INFO] Skipping torch.compile on Windows CPU to avoid MSVC requirement.")
196
  elif (psutil.cpu_count(logical=False) or 0) < 4 and Config.DEVICE == 'cpu':
197
  # Skip compilation on weak CPUs (e.g. HF Spaces Free Tier) to avoid long startup times
198
+ logger.info("[INFO] Skipping torch.compile on low-core CPU to prevent timeout.")
199
  else:
200
  # On Linux/Mac CPU, use default mode or skip if problematic. Default is usually safe.
201
  self.model = torch.compile(self.model)
202
+ logger.info("[SUCCESS] torch.compile enabled (default mode)")
203
 
204
  self.compiled = True
205
  except Exception as e:
206
+ logger.warning(f"[WARNING] torch.compile not available or failed: {e}")
207
  self.compiled = True # Mark as tried
208
 
209
  logger.info(f"{self.name} loaded successfully.")
 
265
 
266
  return Image.fromarray(output_np)
267
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  class SpanStrategy(UpscalerStrategy):
269
  def __init__(self):
270
  super().__init__()
 
300
  try:
301
  if Config.DEVICE == 'cuda':
302
  self.model = torch.compile(self.model, mode='reduce-overhead')
303
+ logger.info("[INFO] torch.compile enabled (reduce-overhead mode)")
304
  elif os.name == 'nt' and Config.DEVICE == 'cpu':
305
+ logger.info("[INFO] Skipping torch.compile on Windows CPU.")
306
  elif (psutil.cpu_count(logical=False) or 0) < 4 and Config.DEVICE == 'cpu':
307
+ logger.info("[INFO] Skipping torch.compile on low-core CPU.")
308
  else:
309
  # SPAN architecture uses .data.clone() in forward pass which breaks torch.compile/inductor
310
+ logger.info("[INFO] Skipping torch.compile for SPAN (incompatible architecture).")
311
  # self.model = torch.compile(self.model)
312
  self.compiled = True
313
  except Exception:
 
402
  pass
403
  else:
404
  # HAT architecture also triggers "UntypedStorage" weakref errors with inductor on CPU
405
+ logger.info("[INFO] Skipping torch.compile for HAT-S (incompatible architecture).")
406
  # self.model = torch.compile(self.model)
407
  self.compiled = True
408
  except Exception:
 
459
  self.strategies: Dict[str, UpscalerStrategy] = {
460
  "SPAN (NomosUni) x2": SpanStrategy(),
461
  "RealESRGAN x2": RealESRGANStrategy(),
462
+ "HAT-S x4": HatsStrategy()
 
 
463
  }
464
  self.current_model_name: Optional[str] = None
465
 
 
487
  manager = UpscalerManager()
488
 
489
  # --- Gradio Interface Logic ---
490
+ def process_image(input_img: Image.Image, model_name: str, output_format: str) -> Tuple[Optional[str], str, str]:
491
  if input_img is None:
492
  return None, get_logs(), get_system_usage()
493
 
494
  try:
495
  strategy = manager.get_strategy(model_name)
496
 
497
+ output_img = strategy.upscale(input_img)
 
498
 
499
+ # Save to temp file with correct extension
500
+ output_path = f"output.{output_format.lower()}"
501
+
502
+ # Convert to RGB if saving as JPEG (doesn't support alpha)
503
+ if output_format.lower() in ['jpeg', 'jpg'] and output_img.mode == 'RGBA':
504
+ output_img = output_img.convert('RGB')
505
+
506
+ output_img.save(output_path, format=output_format)
507
 
508
  # Explicit GC after heavy operations
509
  gc.collect()
510
 
511
+ return output_path, get_logs(), get_system_usage()
512
  except Exception as e:
513
  error_msg = f"Critical Error: {str(e)}\n{traceback.format_exc()}"
514
  logger.error(error_msg)
 
515
  return None, get_logs() + "\n\n" + error_msg, get_system_usage()
516
 
517
  def unload_models():
 
520
 
521
  # --- UI Construction ---
522
  desc = """
523
+ # Universal Upscaler Pro (CPU Optimized)
524
+
525
+ This application provides state-of-the-art (SOTA) image upscaling running entirely on CPU, optimized for free-tier cloud environments.
526
+
527
+ ### Available Models
528
+
529
+ | Model | Scale | Best For | License |
530
+ | :--- | :--- | :--- | :--- |
531
+ | **SPAN (NomosUni)** | x2 | **Speed & General Use**. Extremely fast, parameter-free attention network. | Apache 2.0 |
532
+ | **RealESRGAN** | x2 | **Robustness**. Excellent at removing JPEG artifacts and noise. | BSD 3-Clause |
533
+ | **HAT-S** | x4 | **Texture Detail**. Hybrid Attention Transformer for high-fidelity restoration. | MIT |
534
+
535
+ ### Attributions & Credits
536
+
537
+ * **Real-ESRGAN**: [Wang et al., 2021](https://github.com/xinntao/Real-ESRGAN). *Real-ESRGAN: Training Real-World Blind Super-Resolution with Pure Synthetic Data*.
538
+ * **SPAN**: [Zhang et al., 2023](https://github.com/hongyuanyu/SPAN). *Swift Parameter-free Attention Network for Efficient Super-Resolution*.
539
+ * **HAT**: [Chen et al., 2023](https://github.com/XPixelGroup/HAT). *Activating Activation Functions for Image Restoration*.
540
+ * **NomosUni**: Custom SPAN training by [Phhofm](https://github.com/Phhofm).
541
  """
542
 
543
  with gr.Blocks(title="Universal Upscaler Pro") as iface:
544
  gr.Markdown(desc)
545
 
546
  with gr.Row():
547
+ with gr.Column(scale=1, min_width=300):
548
+ input_image = gr.Image(type="pil", label="Input Image", height=400)
549
 
550
+ with gr.Row():
551
  model_selector = gr.Dropdown(
552
  choices=list(manager.strategies.keys()),
553
  value="SPAN (NomosUni) x2",
554
+ label="Model Architecture",
555
+ scale=2
556
  )
557
+ output_format = gr.Dropdown(
558
+ choices=["PNG", "JPEG", "WEBP"],
559
+ value="PNG",
560
+ label="Output Format",
561
+ scale=1
562
  )
563
 
564
+ submit_btn = gr.Button("Upscale Image", variant="primary", size="lg")
565
+
566
  with gr.Accordion("Advanced Settings", open=False):
 
567
  unload_btn = gr.Button("Unload All Models (Free RAM)", variant="secondary")
568
+ system_info = gr.Label(value=get_system_usage(), label="System Status")
 
 
569
 
570
+ with gr.Column(scale=1, min_width=300):
571
+ output_image = gr.Image(type="filepath", label="Upscaled Result", height=400)
572
+ logs_output = gr.TextArea(label="Execution Logs", interactive=False, lines=8)
573
 
574
  # Event Wiring
575
  submit_btn.click(
576
  fn=process_image,
577
+ inputs=[input_image, model_selector, output_format],
578
  outputs=[output_image, logs_output, system_info]
579
  )
580
 
 
583
  inputs=[],
584
  outputs=[logs_output, system_info]
585
  )
 
 
 
586
 
587
+ iface.launch()
requirements.txt CHANGED
@@ -1,7 +1,4 @@
1
  torch
2
- diffusers
3
- transformers
4
- accelerate
5
  scipy
6
  pillow
7
  gradio
@@ -14,4 +11,4 @@ onnxruntime
14
  basicsr
15
  realesrgan
16
  openvino
17
- optimum
 
1
  torch
 
 
 
2
  scipy
3
  pillow
4
  gradio
 
11
  basicsr
12
  realesrgan
13
  openvino
14
+ optimum