Spaces:
Sleeping
Sleeping
Initial PixelBoost AI upscaler (Real-ESRGAN)
Browse files- .gitignore +5 -0
- README.md +28 -6
- app.py +133 -0
- requirements.txt +8 -0
.gitignore
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
weights/
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
.gradio/
|
| 5 |
+
flagged/
|
README.md
CHANGED
|
@@ -1,13 +1,35 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
colorTo: pink
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
python_version: '3.13'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: PixelBoost AI Upscaler
|
| 3 |
+
emoji: ✨
|
| 4 |
+
colorFrom: purple
|
| 5 |
colorTo: pink
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 4.44.1
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
+
short_description: Real-ESRGAN AI image upscaler for PixelBoost
|
| 12 |
---
|
| 13 |
|
| 14 |
+
# PixelBoost AI Upscaler
|
| 15 |
+
|
| 16 |
+
Real-ESRGAN (`realesr-general-x4v3`) behind a Gradio interface. This Space
|
| 17 |
+
powers the **AI Enhance** mode of [PixelBoost](https://pixelboost-upscaler.pages.dev/).
|
| 18 |
+
The Space exposes an `/upscale` API endpoint that the PixelBoost FastAPI
|
| 19 |
+
backend calls via `gradio_client`.
|
| 20 |
+
|
| 21 |
+
## Local dev
|
| 22 |
+
|
| 23 |
+
```bash
|
| 24 |
+
pip install -r requirements.txt
|
| 25 |
+
python app.py
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
## Notes
|
| 29 |
+
|
| 30 |
+
- Free CPU tier: expect 20-90s per image. Tiling (256px) keeps memory low.
|
| 31 |
+
- Model weights download from the official Real-ESRGAN GitHub release on
|
| 32 |
+
first boot and are cached in `./weights`.
|
| 33 |
+
- Source-of-truth lives in
|
| 34 |
+
[pixelboost-upscaler/hf-space](https://github.com/minhajulofficial/pixelboost-upscaler/tree/main/hf-space);
|
| 35 |
+
push changes from there.
|
app.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""PixelBoost AI upscaler — HuggingFace Space (Gradio).
|
| 2 |
+
|
| 3 |
+
Real-ESRGAN inference behind a Gradio interface. The PixelBoost backend
|
| 4 |
+
(FastAPI) calls this Space's ``/upscale`` API endpoint when the user picks
|
| 5 |
+
"AI Enhance" mode. The Gradio UI is also usable directly for manual testing.
|
| 6 |
+
|
| 7 |
+
Model: realesr-general-x4v3 (5MB, SRVGG-based) — chosen for fast CPU
|
| 8 |
+
inference on HF Spaces' free tier. Quality is excellent on photos with the
|
| 9 |
+
mild denoise strength applied here.
|
| 10 |
+
|
| 11 |
+
Scales:
|
| 12 |
+
- 4x: native single forward pass through the 4x model.
|
| 13 |
+
- 2x: 4x forward pass + LANCZOS downscale to 2x (cheaper than a separate 2x
|
| 14 |
+
model and produces sharper results than a pure 2x model on free CPU).
|
| 15 |
+
- 6x: 4x forward pass + LANCZOS upscale to 6x.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
from __future__ import annotations
|
| 19 |
+
|
| 20 |
+
import os
|
| 21 |
+
import sys
|
| 22 |
+
import types
|
| 23 |
+
|
| 24 |
+
import gradio as gr
|
| 25 |
+
import numpy as np
|
| 26 |
+
import torch
|
| 27 |
+
from PIL import Image
|
| 28 |
+
|
| 29 |
+
# --- torchvision compat shim ---------------------------------------------------
|
| 30 |
+
# basicsr (transitively via realesrgan) imports
|
| 31 |
+
# ``torchvision.transforms.functional_tensor`` which was removed in
|
| 32 |
+
# torchvision >= 0.17. Recreate the module pointing at ``functional`` before
|
| 33 |
+
# basicsr is imported.
|
| 34 |
+
try:
|
| 35 |
+
import torchvision.transforms.functional_tensor # type: ignore[import-not-found] # noqa: F401
|
| 36 |
+
except ImportError:
|
| 37 |
+
from torchvision.transforms import functional as _tv_functional
|
| 38 |
+
|
| 39 |
+
_shim = types.ModuleType("torchvision.transforms.functional_tensor")
|
| 40 |
+
_shim.rgb_to_grayscale = _tv_functional.rgb_to_grayscale # type: ignore[attr-defined]
|
| 41 |
+
sys.modules["torchvision.transforms.functional_tensor"] = _shim
|
| 42 |
+
|
| 43 |
+
# --- model setup --------------------------------------------------------------
|
| 44 |
+
from basicsr.archs.srvgg_arch import SRVGGNetCompact # noqa: E402
|
| 45 |
+
from basicsr.utils.download_util import load_file_from_url # noqa: E402
|
| 46 |
+
from realesrgan import RealESRGANer # noqa: E402
|
| 47 |
+
|
| 48 |
+
MODEL_NAME = "realesr-general-x4v3"
|
| 49 |
+
MODEL_URL = (
|
| 50 |
+
"https://github.com/xinntao/Real-ESRGAN/releases/download/"
|
| 51 |
+
"v0.2.5.0/realesr-general-x4v3.pth"
|
| 52 |
+
)
|
| 53 |
+
WEIGHTS_DIR = os.environ.get("PIXELBOOST_WEIGHTS_DIR", "weights")
|
| 54 |
+
TILE_SIZE = int(os.environ.get("PIXELBOOST_TILE_SIZE", "256"))
|
| 55 |
+
MAX_INPUT_PIXELS = int(os.environ.get("PIXELBOOST_MAX_INPUT_PIXELS", str(4_000_000)))
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _build_upscaler() -> RealESRGANer:
|
| 59 |
+
os.makedirs(WEIGHTS_DIR, exist_ok=True)
|
| 60 |
+
model_path = load_file_from_url(
|
| 61 |
+
url=MODEL_URL, model_dir=WEIGHTS_DIR, file_name=f"{MODEL_NAME}.pth"
|
| 62 |
+
)
|
| 63 |
+
arch = SRVGGNetCompact(
|
| 64 |
+
num_in_ch=3,
|
| 65 |
+
num_out_ch=3,
|
| 66 |
+
num_feat=64,
|
| 67 |
+
num_conv=32,
|
| 68 |
+
upscale=4,
|
| 69 |
+
act_type="prelu",
|
| 70 |
+
)
|
| 71 |
+
return RealESRGANer(
|
| 72 |
+
scale=4,
|
| 73 |
+
model_path=model_path,
|
| 74 |
+
model=arch,
|
| 75 |
+
tile=TILE_SIZE,
|
| 76 |
+
tile_pad=10,
|
| 77 |
+
pre_pad=0,
|
| 78 |
+
half=False, # CPU does not support fp16
|
| 79 |
+
device=torch.device("cpu"),
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
print(f"[pixelboost] loading {MODEL_NAME}...", flush=True)
|
| 84 |
+
UPSAMPLER = _build_upscaler()
|
| 85 |
+
print(f"[pixelboost] model loaded; tile={TILE_SIZE}", flush=True)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
# --- inference ----------------------------------------------------------------
|
| 89 |
+
def upscale(image: Image.Image | None, scale: int = 4) -> Image.Image:
|
| 90 |
+
"""Run Real-ESRGAN inference and resize to the requested scale."""
|
| 91 |
+
if image is None:
|
| 92 |
+
raise gr.Error("No image provided.")
|
| 93 |
+
if int(scale) not in {2, 4, 6}:
|
| 94 |
+
raise gr.Error("Scale must be 2, 4, or 6.")
|
| 95 |
+
|
| 96 |
+
image = image.convert("RGB")
|
| 97 |
+
if image.width * image.height > MAX_INPUT_PIXELS:
|
| 98 |
+
raise gr.Error(
|
| 99 |
+
f"Input too large ({image.width}x{image.height}). "
|
| 100 |
+
f"Max ~{MAX_INPUT_PIXELS // 1_000_000} megapixels in AI mode."
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
arr = np.array(image)
|
| 104 |
+
output, _ = UPSAMPLER.enhance(arr, outscale=4)
|
| 105 |
+
result = Image.fromarray(output)
|
| 106 |
+
|
| 107 |
+
target = (image.width * int(scale), image.height * int(scale))
|
| 108 |
+
if result.size != target:
|
| 109 |
+
result = result.resize(target, Image.Resampling.LANCZOS)
|
| 110 |
+
return result
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
demo = gr.Interface(
|
| 114 |
+
fn=upscale,
|
| 115 |
+
inputs=[
|
| 116 |
+
gr.Image(type="pil", label="Input image"),
|
| 117 |
+
gr.Radio([2, 4, 6], value=4, label="Scale", type="value"),
|
| 118 |
+
],
|
| 119 |
+
outputs=gr.Image(type="pil", label="Upscaled", format="png"),
|
| 120 |
+
title="PixelBoost AI Upscaler",
|
| 121 |
+
description=(
|
| 122 |
+
"Real-ESRGAN (realesr-general-x4v3) running on HuggingFace free CPU. "
|
| 123 |
+
"Expect 20-90s per image. Called from the PixelBoost backend when "
|
| 124 |
+
"users pick AI Enhance mode."
|
| 125 |
+
),
|
| 126 |
+
api_name="upscale",
|
| 127 |
+
allow_flagging="never",
|
| 128 |
+
concurrency_limit=1,
|
| 129 |
+
)
|
| 130 |
+
demo.queue(max_size=20)
|
| 131 |
+
|
| 132 |
+
if __name__ == "__main__":
|
| 133 |
+
demo.launch(show_api=True)
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch==2.2.2
|
| 2 |
+
torchvision==0.17.2
|
| 3 |
+
numpy<2.0
|
| 4 |
+
pillow>=10.0.0
|
| 5 |
+
opencv-python-headless>=4.8.0
|
| 6 |
+
basicsr==1.4.2
|
| 7 |
+
realesrgan==0.3.0
|
| 8 |
+
gradio>=4.44.0
|