# ──────────────────────────────────────────────────────── # TorchVision compat shim (MUST be before importing basicsr) # Fixes: ModuleNotFoundError: torchvision.transforms.functional_tensor # ──────────────────────────────────────────────────────── import sys, types try: import torchvision.transforms.functional_tensor as _ft # noqa: F401 except Exception: from torchvision.transforms import functional as _F _mod = types.ModuleType("torchvision.transforms.functional_tensor") _mod.rgb_to_grayscale = _F.rgb_to_grayscale sys.modules["torchvision.transforms.functional_tensor"] = _mod # ──────────────────────────────────────────────────────── # Spaces ZeroGPU decorator (safe no-op locally) # ──────────────────────────────────────────────────────── try: import spaces GPU = spaces.GPU except Exception: def GPU(*args, **kwargs): def _wrap(f): return f return _wrap # ──────────────────────────────────────────────────────── # Standard imports # ──────────────────────────────────────────────────────── import gradio as gr import cv2 import numpy import os import random import inspect from pathlib import Path import zipfile import tempfile from basicsr.archs.rrdbnet_arch import RRDBNet as _RRDBNet from basicsr.utils.download_util import load_file_from_url from realesrgan import RealESRGANer from realesrgan.archs.srvgg_arch import SRVGGNetCompact # ──────────────────────────────────────────────────────── # Globals # ──────────────────────────────────────────────────────── last_file = None img_mode = "RGBA" # ──────────────────────────────────────────────────────── # Utilities # ──────────────────────────────────────────────────────── def rnd_string(x: int) -> str: characters = "abcdefghijklmnopqrstuvwxyz_0123456789" return "".join((random.choice(characters)) for _ in range(x)) def reset(): global last_file if last_file: try: print(f"Deleting {last_file} ...") os.remove(last_file) except Exception as e: print("Delete error:", e) last_file = None return gr.update(value=None), gr.update(value=None) def has_transparency(img): if img.info.get("transparency", None) is not None: return True if img.mode == "P": transparent = img.info.get("transparency", -1) for _, index in img.getcolors(): if index == transparent: return True elif img.mode == "RGBA": extrema = img.getextrema() if extrema[3][0] < 255: return True return False def image_properties(img): global img_mode if img: img_mode = "RGBA" if has_transparency(img) else "RGB" return f"Resolution: Width: {img.size[0]}, Height: {img.size[1]} | Color Mode: {img_mode}" def model_tip_text(model_name: str) -> str: tips = { "RealESRGAN_x4plus": ( "**RealESRGAN_x4plus (4×)** — Best for photoreal images (portraits, landscapes). " "Balanced detail recovery. Good default for Flux realism." ), "RealESRNet_x4plus": ( "**RealESRNet_x4plus (4×)** — Softer but great on noisy/compressed sources " "(old JPEGs, screenshots)." ), "RealESRGAN_x4plus_anime_6B": ( "**RealESRGAN_x4plus_anime_6B (4×)** — For anime/illustrations/line art only. " "Not recommended for real-life photos." ), "RealESRGAN_x2plus": ( "**RealESRGAN_x2plus (2×)** — Faster, lighter 2× cleanup when you don't need 4×." ), "realesr-general-x4v3": ( "**realesr-general-x4v3 (4×)** — Versatile mixed-content model with adjustable denoise. " "**Denoise Strength** slider only affects this model (blends with the WDN variant). " "Try 0.3–0.5 for slightly cleaner, sharper results." ), } return tips.get(model_name, "") # ──────────────────────────────────────────────────────── # RRDBNet builder that tolerates different Basicsr signatures # ──────────────────────────────────────────────────────── def build_rrdb(scale: int, num_block: int): """ Creates an RRDBNet across several possible constructor signatures used by basicsr/realesrgan. Tries, in order: 1) keyword style (num_in_ch/num_out_ch/num_feat/num_block/num_grow_ch/scale) 2) alt keyword style (in_nc/out_nc/nf/nb/gc/sf) 3) positional with gc before scale 4) positional with scale before gc """ # Try keyword: "num_*" + "scale" try: return _RRDBNet( num_in_ch=3, num_out_ch=3, num_feat=64, num_block=num_block, num_grow_ch=32, scale=scale ) except TypeError: pass # Try keyword: "in_nc/out_nc" + "sf" try: return _RRDBNet( in_nc=3, out_nc=3, nf=64, nb=num_block, gc=32, sf=scale ) except TypeError: pass # Inspect parameters to guess positional order params = list(inspect.signature(_RRDBNet).parameters.keys()) # Common positional (gc, scale) order try: return _RRDBNet(3, 3, 64, num_block, 32, scale) except TypeError: pass # Alternate positional (scale, gc) order try: return _RRDBNet(3, 3, 64, num_block, scale, 32) except TypeError as e: raise TypeError(f"RRDBNet signature not recognized: {e}") #Factor an upsampler builder def get_upsampler(model_name: str, outscale: int, tile: int = 256): # Build the same backbone/weights as in realesrgan(), but return a ready RealESRGANer if model_name == 'RealESRGAN_x4plus': model = build_rrdb(scale=4, num_block=23); netscale = 4 file_url = ['https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth'] elif model_name == 'RealESRNet_x4plus': model = build_rrdb(scale=4, num_block=23); netscale = 4 file_url = ['https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.1/RealESRNet_x4plus.pth'] elif model_name == 'RealESRGAN_x4plus_anime_6B': model = build_rrdb(scale=4, num_block=6); netscale = 4 file_url = ['https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth'] elif model_name == 'RealESRGAN_x2plus': model = build_rrdb(scale=2, num_block=23); netscale = 2 file_url = ['https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth'] elif model_name == 'realesr-general-x4v3': model = SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=32, upscale=4, act_type='prelu'); netscale = 4 file_url = [ 'https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-x4v3.pth', 'https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-wdn-x4v3.pth' ] else: raise ValueError(f"Unknown model: {model_name}") ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) weights_dir = os.path.join(ROOT_DIR, 'weights') os.makedirs(weights_dir, exist_ok=True) for url in file_url: fname = os.path.basename(url) local_path = os.path.join(weights_dir, fname) if not os.path.isfile(local_path): load_file_from_url(url=url, model_dir=weights_dir, progress=True) if model_name == 'realesr-general-x4v3': model_path = [ os.path.join(weights_dir, 'realesr-general-x4v3.pth'), os.path.join(weights_dir, 'realesr-general-wdn-x4v3.pth'), ] dni_weight = None # supplied at call site if using denoise blend else: model_path = os.path.join(weights_dir, f"{model_name}.pth") dni_weight = None use_cuda = False try: use_cuda = hasattr(cv2, "cuda") and cv2.cuda.getCudaEnabledDeviceCount() > 0 except Exception: use_cuda = False gpu_id = 0 if use_cuda else None upsampler = RealESRGANer( scale=netscale, model_path=model_path, dni_weight=dni_weight, model=model, tile=tile or 256, tile_pad=10, pre_pad=10, half=bool(use_cuda), gpu_id=gpu_id ) return upsampler, netscale, use_cuda, model_path # ──────────────────────────────────────────────────────── # Core upscaling # Decorated for Hugging Face Spaces ZeroGPU # ──────────────────────────────────────────────────────── @GPU() # lets Spaces know this function uses GPU; safe no-op locally def realesrgan(img, model_name, denoise_strength, face_enhance, outscale): if img is None: return # ----- Select backbone + weights ----- if model_name == 'RealESRGAN_x4plus': model = build_rrdb(scale=4, num_block=23); netscale = 4 file_url = ['https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth'] elif model_name == 'RealESRNet_x4plus': model = build_rrdb(scale=4, num_block=23); netscale = 4 file_url = ['https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.1/RealESRNet_x4plus.pth'] elif model_name == 'RealESRGAN_x4plus_anime_6B': model = build_rrdb(scale=4, num_block=6); netscale = 4 file_url = ['https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth'] elif model_name == 'RealESRGAN_x2plus': model = build_rrdb(scale=2, num_block=23); netscale = 2 file_url = ['https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth'] elif model_name == 'realesr-general-x4v3': model = SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=32, upscale=4, act_type='prelu'); netscale = 4 file_url = [ 'https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-x4v3.pth', 'https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-wdn-x4v3.pth' ] else: raise ValueError(f"Unknown model: {model_name}") # ----- Ensure weights on disk ----- ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) weights_dir = os.path.join(ROOT_DIR, 'weights') os.makedirs(weights_dir, exist_ok=True) for url in file_url: fname = os.path.basename(url) local_path = os.path.join(weights_dir, fname) if not os.path.isfile(local_path): load_file_from_url(url=url, model_dir=weights_dir, progress=True) if model_name == 'realesr-general-x4v3': base_path = os.path.join(weights_dir, 'realesr-general-x4v3.pth') wdn_path = os.path.join(weights_dir, 'realesr-general-wdn-x4v3.pth') model_path = [base_path, wdn_path] denoise_strength = float(denoise_strength) dni_weight = [1.0 - denoise_strength, denoise_strength] # base, WDN else: model_path = os.path.join(weights_dir, f"{model_name}.pth") dni_weight = None # ----- CUDA / precision / tiling ----- use_cuda = False try: use_cuda = hasattr(cv2, "cuda") and cv2.cuda.getCudaEnabledDeviceCount() > 0 except Exception: use_cuda = False gpu_id = 0 if use_cuda else None upsampler = RealESRGANer( scale=netscale, model_path=model_path, dni_weight=dni_weight, model=model, tile=256, # VRAM-safe default; lower to 128 if OOM tile_pad=10, pre_pad=10, half=bool(use_cuda), gpu_id=gpu_id ) # ----- Optional face enhancement ----- face_enhancer = None if face_enhance: from gfpgan import GFPGANer face_enhancer = GFPGANer( model_path='https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth', upscale=outscale, arch='clean', channel_multiplier=2, bg_upsampler=upsampler ) # ----- PIL -> cv2 ----- cv_img = numpy.array(img) if cv_img.ndim == 3 and cv_img.shape[2] == 4: cv_img = cv2.cvtColor(cv_img, cv2.COLOR_RGBA2BGRA) else: cv_img = cv2.cvtColor(cv_img, cv2.COLOR_RGB2BGR) # ----- Enhance ----- try: if face_enhancer: _, _, output = face_enhancer.enhance(cv_img, has_aligned=False, only_center_face=False, paste_back=True) else: output, _ = upsampler.enhance(cv_img, outscale=int(outscale)) except RuntimeError as error: print('Error', error) print('Tip: If you hit CUDA OOM, try a smaller tile size (e.g., 128).') return None # ----- cv2 -> display ndarray, also save ----- if output.ndim == 3 and output.shape[2] == 4: display_img = cv2.cvtColor(output, cv2.COLOR_BGRA2RGBA) extension = 'png' else: display_img = cv2.cvtColor(output, cv2.COLOR_BGR2RGB) extension = 'jpg' out_filename = f"output_{rnd_string(8)}.{extension}" try: cv2.imwrite(out_filename, output) global last_file last_file = out_filename except Exception as e: print("Save error:", e) return display_img #Add a batch upscaler that preserves filenames def render_progress(pct: float, text: str = "") -> str: pct = max(0.0, min(100.0, float(pct))) bar = f"
" label = f"