import sys import torchvision.transforms.functional as F # Fix for basicsr error: redirect the old path to the new one sys.modules['torchvision.transforms.functional_tensor'] = F import os import gradio as gr # ... your other imports like from realesrgan import ... import os import gradio as gr import numpy as np import torch from PIL import Image from realesrgan import RealESRGANer from basicsr.archs.rrdbnet_arch import RRDBNet # ── Load model ───────────────────────────────────────────────────────────────── def load_upsampler(): model = RRDBNet( num_in_ch=3, num_out_ch=3, num_feat=64, num_block=6, num_grow_ch=32, scale=4 ) upsampler = RealESRGANer( scale=4, model_path="models/RealESRGAN_x4plus_anime_6B.pth", model=model, tile=256, tile_pad=10, pre_pad=0, half=False ) return upsampler print("Loading model...") upsampler = load_upsampler() print("Model ready ✅") def enhance(image, scale): if image is None: return None, "⚠️ No image uploaded." # Ensure scale is valid (2 or 4) scale = int(scale) if scale in [2, 4] else 4 # Resize large images for speed — cap at 256px on shortest side max_side = 256 w, h = image.size if max(w, h) > max_side * 2: ratio = (max_side * 2) / max(w, h) image = image.resize((int(w * ratio), int(h * ratio)), Image.LANCZOS) upsampler.scale = scale img_np = np.array(image) try: output, _ = upsampler.enhance(img_np, outscale=scale) except RuntimeError as e: return None, f"Error: {str(e)}" result = Image.fromarray(output) orig_w, orig_h = image.size new_w, new_h = result.size info = f"✅ Original: {orig_w}×{orig_h}px → Enhanced: {new_w}×{new_h}px ({scale}× upscale)" return result, info # ── CSS ───────────────────────────────────────────────────────────────────────── css = """ @import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;600;800&family=DM+Mono:wght@300;400;500&display=swap'); :root { --cream: #f5f0e8; --ink: #0f0e0c; --accent: #c8502a; --muted: #8a8070; --border: #d8d0c0; --card: #faf7f2; } *, *::before, *::after { box-sizing: border-box; } body, .gradio-container { background-color: var(--cream) !important; font-family: 'DM Mono', monospace !important; color: var(--ink) !important; } .gradio-container { max-width: 1100px !important; margin: 0 auto !important; padding: 0 1.5rem 3rem !important; } /* ── HEADER ── */ .site-header { padding: 2rem 0 1.5rem 0; border-bottom: 2px solid var(--ink); margin-bottom: 0; display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; } .header-left h1 { font-family: 'Syne', sans-serif; font-size: 2.8rem; font-weight: 800; color: var(--ink); letter-spacing: -2px; -webkit-text-fill-color: var(--ink); line-height: 1; margin: 0; } .header-left h1 span { color: var(--accent); -webkit-text-fill-color: var(--accent); } .header-left p { font-size: 0.68rem; letter-spacing: 2px; text-transform: uppercase; color: var(--muted); -webkit-text-fill-color: var(--muted); margin: 0.4rem 0 0 0; } .header-right { display: flex; flex-direction: column; align-items: flex-end; gap: 0.4rem; } .header-right img { height: 64px; width: auto; object-fit: contain; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.1)); display: none !important; } .header-right .inst-name { font-size: 0.6rem; letter-spacing: 1px; text-transform: uppercase; color: var(--muted); text-align: right; line-height: 1.5; display: none !important; } .badge-row { display: flex; gap: 0.4rem; margin-top: 0.75rem; flex-wrap: wrap; } .badge { background: var(--ink); color: var(--cream); -webkit-text-fill-color: var(--cream); font-size: 0.6rem; letter-spacing: 2px; text-transform: uppercase; padding: 3px 9px; border-radius: 1px; font-family: 'DM Mono', monospace; } .badge.red { background: var(--accent); } /* ── TABS ── */ .tab-nav button { font-family: 'DM Mono', monospace !important; font-size: 0.7rem !important; letter-spacing: 2px !important; text-transform: uppercase !important; color: var(--muted) !important; border: none !important; border-bottom: 2px solid transparent !important; background: transparent !important; padding: 0.9rem 1.2rem !important; border-radius: 0 !important; } .tab-nav button.selected { color: var(--ink) !important; border-bottom: 2px solid var(--accent) !important; } /* ── PROCESSING INDICATOR ── */ .processing-banner { display: none; background: var(--ink); color: var(--cream); padding: 0.75rem 1rem; border-radius: 2px; font-size: 0.75rem; letter-spacing: 2px; text-align: center; margin-bottom: 0.75rem; animation: pulse 1.5s ease-in-out infinite; } @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.5} } /* ── INPUTS ── */ textarea, input[type="text"] { background: var(--card) !important; border: 1px solid var(--border) !important; color: var(--ink) !important; font-family: 'DM Mono', monospace !important; font-size: 0.82rem !important; border-radius: 2px !important; } label, label span, .label-wrap span { font-family: 'DM Mono', monospace !important; font-size: 0.66rem !important; letter-spacing: 2px !important; text-transform: uppercase !important; color: var(--muted) !important; } input[type="range"] { accent-color: var(--accent) !important; } /* ── BUTTONS ── */ button.primary { background: var(--ink) !important; color: var(--cream) !important; font-family: 'DM Mono', monospace !important; font-size: 0.72rem !important; letter-spacing: 2px !important; text-transform: uppercase !important; border: none !important; border-radius: 2px !important; padding: 0.75rem 1.5rem !important; transition: background 0.2s !important; width: 100% !important; } button.primary:hover { background: var(--accent) !important; } button.secondary { background: transparent !important; color: var(--ink) !important; font-family: 'DM Mono', monospace !important; font-size: 0.72rem !important; letter-spacing: 2px !important; text-transform: uppercase !important; border: 1px solid var(--border) !important; border-radius: 2px !important; width: 100% !important; } button.secondary:hover { border-color: var(--ink) !important; } /* ── IMAGE PANELS ── */ .gr-image img { border-radius: 2px !important; border: 1px solid var(--border) !important; width: 100% !important; height: auto !important; } .image-wrap { border: 1px solid var(--border); border-radius: 2px; overflow: hidden; background: #1a1a1a; min-height: 220px; display: flex; align-items: center; justify-content: center; position: relative; } .image-placeholder { color: var(--cream); font-size: 0.7rem; letter-spacing: 2px; text-transform: uppercase; text-align: center; padding: 3rem; } .image-placeholder .icon { font-size: 2rem; margin-bottom: 0.75rem; } /* ── TIPS BOX ── */ .tips-box { padding: 1rem; border: 1px solid var(--border); border-radius: 2px; margin-top: 0.5rem; background: var(--card) !important; } .tips-title { font-size: 0.64rem; letter-spacing: 2px; text-transform: uppercase; color: var(--muted) !important; margin-bottom: 0.5rem; } .tips-body { font-size: 0.73rem; color: var(--ink) !important; line-height: 2.1; background: var(--card) !important; } .tip-chip { display: inline-block; background: var(--ink); color: var(--cream); font-size: 0.6rem; letter-spacing: 1px; padding: 2px 8px; border-radius: 1px; margin: 2px 4px 2px 0; } /* ── TEAM ── */ .team-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1rem; margin-top: 1.25rem; } .team-card { background: var(--card); border: 1px solid var(--border); border-radius: 2px; padding: 1.1rem 1.25rem; transition: border-color 0.2s, box-shadow 0.2s; } .team-card:hover { border-color: var(--ink); box-shadow: 4px 4px 0 var(--ink); } .team-card.leader { border-color: var(--accent); background: #fff8f5; } .team-card.leader:hover { box-shadow: 4px 4px 0 var(--accent); } .card-role { font-size: 0.6rem; letter-spacing: 3px; text-transform: uppercase; color: var(--muted) !important; margin-bottom: 0.3rem; } .card-name { font-family: 'Syne', sans-serif; font-size: 1.05rem; font-weight: 700; color: var(--ink) !important; margin-bottom: 0.15rem; } .card-roll { font-size: 0.68rem; color: var(--muted) !important; margin-bottom: 0.6rem; } .card-desc { font-size: 0.72rem; color: var(--ink) !important; line-height: 1.7; border-top: 1px solid var(--border); padding-top: 0.6rem; } .course-strip { background: var(--ink); color: var(--cream); padding: 1rem 1.5rem; border-radius: 2px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1.25rem; } .course-strip span { font-size: 0.64rem; letter-spacing: 2px; text-transform: uppercase; opacity: 0.6; color: var(--cream) !important; } .course-strip strong { font-family: 'Syne', sans-serif; font-size: 0.92rem; font-weight: 700; display: block; margin-top: 2px; color: var(--cream) !important; } /* ── MODEL TAB ── */ .model-hero { background: var(--card); color: var(--ink); border: 1px solid var(--border); padding: 2rem 1.75rem; border-radius: 2px; margin-bottom: 1.25rem; position: relative; overflow: hidden; } .model-hero::before { content: 'GAN'; position: absolute; right: -10px; top: 50%; transform: translateY(-50%); font-family: 'Syne', sans-serif; font-size: 7rem; font-weight: 800; color: rgba(15,14,12,0.05); pointer-events: none; } .model-hero h2 { font-family: 'Syne', sans-serif; font-size: 1.9rem; font-weight: 800; margin: 0 0 0.5rem 0; color: var(--ink); } .model-hero h2 span { color: #e8a87c; } .model-hero p { font-size: 0.8rem; line-height: 1.8; color: var(--muted); max-width: 600px; margin: 0; } .stat-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.75rem; margin-bottom: 1.25rem; } .stat-card { background: var(--card); border: 1px solid var(--border); border-radius: 2px; padding: 1rem; text-align: center; } .stat-val { font-family: 'Syne', sans-serif; font-size: 1.8rem; font-weight: 800; color: var(--accent); line-height: 1; } .stat-lbl { font-size: 0.62rem; letter-spacing: 2px; text-transform: uppercase; color: var(--muted); margin-top: 0.3rem; } .section-title { font-size: 0.66rem; letter-spacing: 3px; text-transform: uppercase; color: var(--muted); margin: 1.5rem 0 1rem 0; padding-bottom: 0.5rem; border-bottom: 1px solid var(--border); } .explainer-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem; } .explainer-card { background: var(--card); border: 1px solid var(--border); border-radius: 2px; padding: 1.25rem; } .explainer-icon { font-size: 1.4rem; margin-bottom: 0.5rem; } .explainer-title { font-family: 'Syne', sans-serif; font-size: 0.95rem; font-weight: 700; color: var(--ink) !important; margin-bottom: 0.4rem; } .explainer-desc { font-size: 0.73rem; color: var(--muted) !important; line-height: 1.75; } .dark-note { background: var(--card); color: var(--muted); border: 1px solid var(--border); padding: 1.25rem 1.5rem; border-radius: 2px; font-size: 0.75rem; line-height: 1.8; margin-top: 1rem; } .dark-note strong { color: var(--ink) !important; font-family: 'Syne', sans-serif; } .dark-note em { color: var(--ink) !important; -webkit-text-fill-color: var(--ink) !important; opacity: 1 !important; background: transparent !important; } /* render stability: avoid washed-out text during browser repaint/translation */ .team-card, .team-card *, .explainer-card, .explainer-card *, .dark-note, .dark-note * { text-shadow: none !important; } /* ── HF SPACES AGGRESSIVE TEXT COLOR FIX ── */ /* HF Spaces forces light text (#fff or #fafafa), which disappears on light backgrounds */ /* This section uses maximum specificity and !important to force dark text everywhere */ body, html, .gradio-container { color: var(--ink) !important; } /* Team cards: explicitly force all text dark */ .team-card { color: var(--ink) !important; } .team-card h3, .team-card h4, .team-card p, .team-card span, .team-card div { color: var(--ink) !important; } .card-name, .card-role, .card-roll, .card-desc { color: var(--ink) !important; } /* Model/explainer cards */ .explainer-card, .explainer-card h3, .explainer-card h4, .explainer-card p, .explainer-card div, .explainer-card span { color: var(--ink) !important; } .explainer-title { color: var(--ink) !important; } /* Header section: force ink color on everything */ .site-header { color: var(--ink) !important; } .header-left h1, .header-left p { color: var(--ink) !important; -webkit-text-fill-color: var(--ink) !important; opacity: 1 !important; } .header-left h1 span { color: var(--accent) !important; -webkit-text-fill-color: var(--accent) !important; } .header-left p { color: var(--muted) !important; -webkit-text-fill-color: var(--muted) !important; } .badge, .badge * { color: var(--cream) !important; -webkit-text-fill-color: var(--cream) !important; opacity: 1 !important; } .badge.red, .badge.red * { color: #fff7ef !important; -webkit-text-fill-color: #fff7ef !important; } /* Keep key warning/info banners dark and readable */ .dark-banner, .dark-banner * { background: var(--ink) !important; color: var(--cream) !important; border-color: #2b2a27 !important; } .dark-banner strong { color: #e8a87c !important; } .dark-chip { display: inline-block; background: #1a1a1a !important; color: var(--cream) !important; border: 1px solid #343331 !important; padding: 2px 6px; border-radius: 1px; margin: 0 3px; font-size: 0.68rem; } /* Course strip: this should stay cream on dark background */ .course-strip, .course-strip span, .course-strip strong { color: var(--cream) !important; } /* Dark note section: force ink for strong, muted for normal */ .dark-note { color: var(--muted) !important; } .dark-note strong, .dark-note h3, .dark-note h4 { color: var(--ink) !important; } /* Stat cards and model hero */ .stat-card, .stat-card *, .model-hero, .model-hero * { color: var(--ink) !important; } .stat-val { color: var(--accent) !important; } .model-hero h2 { color: var(--ink) !important; } .model-hero h2 span { color: #e8a87c !important; } /* ── GRADIO FORM ELEMENTS: FORCE DARK TEXT ── */ /* HF Spaces applies light blue (#4a9eff or similar) to all Gradio form components */ /* This section targets ALL form elements on the Enhance tab */ .label-wrap, .label-wrap *, .label-wrap span, .label-wrap label { color: var(--ink) !important; background: var(--card) !important; } .gr-image, .gr-image *, .gr-image label, .gr-image span, .gr-image > div, .gr-image > div > div, .gr-image [data-testid="image"], .gr-image [data-testid="image"] * { color: var(--ink) !important; background: var(--card) !important; } .gr-textbox, .gr-textbox *, .gr-textbox label { color: var(--ink) !important; background: var(--card) !important; } .gr-button, .gr-button *, .gr-button label { color: var(--ink) !important; } /* Tips and instructions boxes */ .tips-title, .tips-body, .tips-box, .tips-box * { color: var(--ink) !important; background: var(--card) !important; } .tip-chip { background: var(--ink) !important; color: var(--cream) !important; } /* Processing indicator and info text */ .processing-banner, .processing-banner *, .info-text, .info-text * { color: var(--ink) !important; } /* Form wrapper containers: ensure light backgrounds */ .gradio-container > div, .gradio-container .form-box, .gradio-container form { background: var(--card) !important; } /* ── SCOPED HF COLOR FIXES (avoid black-on-black) ── */ .gradio-container, .gradio-container label, .gradio-container input, .gradio-container textarea { color: var(--ink) !important; } /* Keep form controls readable without overriding themed dark content blocks */ .gr-textbox, .gr-number, .gradio-container input[type="text"], .gradio-container input[type="number"], .gradio-container textarea { background: var(--card) !important; border: 1px solid var(--border) !important; color: var(--ink) !important; } input::placeholder, textarea::placeholder { color: var(--muted) !important; } footer { display: none !important; } ::-webkit-scrollbar { width: 4px; } ::-webkit-scrollbar-track { background: var(--cream); } ::-webkit-scrollbar-thumb { background: var(--muted); border-radius: 2px; } """ # ── SVG: ESRGAN Pipeline ──────────────────────────────────────────────────────── esrgan_diagram = """ """ # ── SVG: GAN Diagram ──────────────────────────────────────────────────────────── gan_diagram = """ """ # ── SVG: Loss Function Diagram ────────────────────────────────────────────────── loss_diagram = """ """ # ── UI ────────────────────────────────────────────────────────────────────────── with gr.Blocks(title="ArtUpscale — AI Super Resolution") as demo: gr.HTML("""
AI-Powered Super Resolution · CS360 Artificial Intelligence · Semester 4
Real-Enhanced Super-Resolution Generative Adversarial Network — a deep learning model that doesn't just stretch pixels, it invents the fine detail that was lost in compression or downsizing. Think of it as an AI that has studied millions of high-resolution images and learned exactly what sharp edges and crisp textures look like — then applies that knowledge to reconstruct your blurry image from scratch.