LARP-Scaler / app.py
Anonumous's picture
Update app.py
add8812 verified
Raw
History Blame Contribute Delete
12.7 kB
import os
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
import spaces
import random
from math import sqrt
from pathlib import Path
import gradio as gr
import torch
from PIL import Image, ImageOps
from larpscaler import LarpScaler
MODEL_ID = "VladimirM388/larpscaler-v2-bf16"
MAX_OUTPUT_PIXELS = 4_194_304
QUALITY_MODE = "Quality"
FAST_MODE = "Fast"
ASSETS = Path(__file__).parent / "assets" / "examples"
PRESETS = {
QUALITY_MODE: {"steps": 1, "noise_level": 1.0, "guidance_scale": 4.5},
FAST_MODE: {"steps": 1, "noise_level": 0.35, "guidance_scale": 1.0},
}
EXAMPLES = [
[str(ASSETS / "mountain.jpg")],
[str(ASSETS / "architecture.jpg")],
[str(ASSETS / "portrait.jpg")],
]
CSS = """
:root {
--page: #0c0e13;
--panel: #151821;
--surface: #10131a;
--text: #f1f4fa;
--muted: #8d95a6;
--border: #292e3a;
--accent: #a89bff;
}
body,
.gradio-container {
background: radial-gradient(900px 480px at 50% -180px, rgba(120, 105, 234, 0.16), transparent 72%), var(--page) !important;
color: var(--text) !important;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
}
main,
.gradio-container,
.contain { max-width: none !important; }
#larp-shell {
max-width: 1260px;
margin: 0 auto;
padding: 28px 20px 36px;
}
#workspace {
align-items: stretch !important;
gap: 18px !important;
}
#source-panel,
#result-panel {
min-height: 0;
padding: 16px !important;
border: 1px solid var(--border) !important;
border-radius: 16px !important;
background: var(--panel) !important;
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.22);
}
.panel-title {
margin: 1px 2px 14px;
color: #aeb6c7;
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
}
#source-image,
#result-image {
overflow: hidden;
border: 1px solid var(--border);
border-radius: 12px !important;
background: var(--surface);
}
#source-image button,
#result-image button { border-radius: 8px !important; }
.gradio-container [data-testid="block-info"] {
color: var(--muted) !important;
font-size: 0.69rem !important;
font-weight: 700 !important;
letter-spacing: 0.07em;
text-transform: uppercase;
}
#control-dock {
align-items: end !important;
gap: 14px !important;
margin-top: 18px !important;
padding: 14px 16px 16px !important;
border: 1px solid var(--border) !important;
border-radius: 16px !important;
background: rgba(21, 24, 33, 0.96) !important;
}
#control-dock .form {
background: transparent !important;
}
#scale-control,
#mode-control,
#sample-strip { min-height: 52px; }
#scale-control,
#mode-control {
padding: 0 !important;
border: 0 !important;
background: transparent !important;
}
#scale-control .wrap,
#mode-control .wrap {
display: flex !important;
gap: 6px !important;
padding: 0 !important;
background: transparent !important;
}
#scale-control label,
#mode-control label {
display: flex !important;
flex: 1 1 0;
min-width: 0;
min-height: 40px;
align-items: center;
justify-content: center;
margin: 0 !important;
padding: 0 8px !important;
border: 1px solid var(--border) !important;
border-radius: 9px !important;
background: var(--surface) !important;
color: #aeb6c7 !important;
font-size: 0.78rem !important;
font-weight: 650 !important;
}
#scale-control label span,
#mode-control label span {
overflow: visible !important;
white-space: nowrap !important;
}
#scale-control input,
#mode-control input {
flex: 0 0 auto;
margin-right: 6px !important;
}
#scale-control label.selected,
#mode-control label.selected {
border-color: var(--accent) !important;
background: rgba(168, 155, 255, 0.15) !important;
color: #f1efff !important;
}
#scale-control input,
#mode-control input { accent-color: var(--accent) !important; }
#sample-strip .examples { margin: 0 !important; }
#sample-strip .gallery { gap: 6px !important; }
#sample-strip .thumbnail-item {
overflow: hidden;
border: 1px solid var(--border) !important;
border-radius: 8px !important;
background: var(--surface) !important;
}
.control-label {
height: 17px;
margin: 0 0 5px 1px;
color: var(--muted);
font-size: 0.69rem;
font-weight: 700;
letter-spacing: 0.07em;
text-transform: uppercase;
}
button#upscale-button,
button#clear-button {
min-height: 40px;
border-radius: 10px !important;
font-size: 0.84rem !important;
font-weight: 750 !important;
}
button#upscale-button {
border: 0 !important;
background: var(--accent) !important;
box-shadow: 0 8px 22px rgba(168, 155, 255, 0.2);
color: #12131a !important;
}
button#clear-button {
border: 1px solid var(--border) !important;
background: var(--surface) !important;
color: #c9cfdb !important;
}
button#upscale-button:hover { background: #beb5ff !important; }
button#clear-button:hover { border-color: #474e60 !important; background: #191d27 !important; }
#status-card {
min-height: 18px;
margin: 10px 2px 0;
color: var(--muted);
text-align: center;
}
#status-card p { margin: 0; font-size: 0.76rem; }
footer[aria-label="Gradio footer navigation"] { display: none !important; }
@media (max-width: 760px) {
#larp-shell { padding: 14px 12px 24px; }
#workspace, #control-dock { gap: 12px !important; }
}
"""
def _open_image(path: str | None) -> Image.Image:
if not path:
raise gr.Error("Upload an image before starting the upscale.")
with Image.open(path) as loaded:
return ImageOps.exif_transpose(loaded).convert("RGB")
def _fit_input_to_output_budget(image: Image.Image, scale: int) -> tuple[Image.Image, bool]:
output_pixels = image.width * image.height * scale * scale
if output_pixels <= MAX_OUTPUT_PIXELS:
return image, False
ratio = sqrt(MAX_OUTPUT_PIXELS / output_pixels)
width = max(1, int(image.width * ratio))
height = max(1, int(image.height * ratio))
return image.resize((width, height), Image.Resampling.LANCZOS), True
def _run_upscale(
image_path: str | None,
scale: str | int,
mode: str,
seed: float,
randomize_seed: bool,
adapter_path: str | None,
use_image_adapter: bool,
) -> tuple[Image.Image, int, str]:
image = _open_image(image_path)
image, _ = _fit_input_to_output_budget(image, int(scale))
adapter_image = _open_image(adapter_path) if adapter_path and use_image_adapter else None
used_seed = random.randint(0, 2**31 - 1) if randomize_seed else int(seed)
preset = PRESETS[mode]
result = UPSCALER.upscale(
image,
scale=int(scale),
steps=preset["steps"],
noise_level=preset["noise_level"],
guidance_scale=preset["guidance_scale"],
seed=used_seed,
adapter_image=adapter_image,
use_image_adapter=bool(use_image_adapter),
tile_size=1024,
tile_overlap=256,
tile_batch_size="auto",
)
status = f"{image.width} × {image.height}{result.width} × {result.height}"
return result, used_seed, status
UPSCALER = LarpScaler.from_pretrained(
MODEL_ID,
device="cuda",
dtype=torch.bfloat16,
)
@spaces.GPU(duration=60)
def upscale_image(
image_path: str | None,
scale: str,
mode: str,
seed: float,
randomize_seed: bool,
adapter_path: str | None,
use_image_adapter: bool,
) -> tuple[Image.Image, int, str]:
"""Upscale an image with LARP-Scaler on ZeroGPU.
Args:
image_path: Uploaded source image.
scale: Enlargement factor, one of 2, 4, or 8.
mode: Fast or quality inference preset.
seed: Seed used for deterministic refinement.
randomize_seed: Choose a new seed for this request.
adapter_path: Optional reference image for image guidance.
use_image_adapter: Whether to enable LARP-Scaler's guidance adapter.
"""
return _run_upscale(
image_path,
scale,
mode,
seed,
randomize_seed,
adapter_path,
use_image_adapter,
)
def reset_editor() -> tuple[None, str, str, float, bool, None, bool, None, str]:
return (
None,
"4",
QUALITY_MODE,
1234,
False,
None,
True,
None,
"",
)
def clear_result() -> tuple[None, str]:
"""Clear the generated image and its status."""
return None, ""
with gr.Blocks(title="LARP-Scaler") as demo:
with gr.Column(elem_id="larp-shell"):
with gr.Row(equal_height=True, elem_id="workspace"):
with gr.Column(scale=1, min_width=360, elem_id="source-panel"):
gr.HTML('<div class="panel-title">Source</div>')
source_image = gr.Image(
show_label=False,
type="filepath",
sources=["upload", "clipboard"],
image_mode="RGB",
height=500,
elem_id="source-image",
)
with gr.Column(scale=1, min_width=360, elem_id="result-panel"):
gr.HTML('<div class="panel-title">Result</div>')
result_image = gr.Image(
show_label=False,
type="pil",
format="png",
height=500,
buttons=["download", "fullscreen"],
elem_id="result-image",
placeholder="",
)
with gr.Row(equal_height=True, elem_id="control-dock"):
with gr.Column(scale=2, min_width=210):
scale = gr.Radio(
choices=[("2×", "2"), ("4×", "4"), ("8×", "8")],
value="4",
label="Scale",
elem_id="scale-control",
)
with gr.Column(scale=2, min_width=210):
mode = gr.Radio(
choices=[QUALITY_MODE, FAST_MODE],
value=QUALITY_MODE,
label="Mode",
elem_id="mode-control",
)
with gr.Column(scale=2, min_width=210, elem_id="sample-strip"):
gr.Examples(
examples=EXAMPLES,
inputs=[source_image],
label="Samples",
cache_examples=False,
)
with gr.Column(scale=2, min_width=210):
gr.HTML('<div class="control-label">Action</div>')
with gr.Row():
run_button = gr.Button(
"Upscale",
variant="primary",
size="md",
elem_id="upscale-button",
scale=3,
)
reset_button = gr.Button(
"Clear",
variant="secondary",
size="md",
elem_id="clear-button",
scale=1,
)
status = gr.Markdown(
"",
elem_id="status-card",
)
seed = gr.Number(value=1234, precision=0, visible=False)
randomize_seed = gr.Checkbox(value=False, visible=False)
use_image_adapter = gr.Checkbox(value=False, visible=False)
adapter_image = gr.Image(type="filepath", visible=False)
run_button.click(
fn=upscale_image,
inputs=[
source_image,
scale,
mode,
seed,
randomize_seed,
adapter_image,
use_image_adapter,
],
outputs=[result_image, seed, status],
api_name="upscale",
concurrency_limit=1,
concurrency_id="larp_gpu",
time_limit=60,
scroll_to_output=True,
show_progress="full",
)
reset_button.click(
fn=reset_editor,
outputs=[
source_image,
scale,
mode,
seed,
randomize_seed,
adapter_image,
use_image_adapter,
result_image,
status,
],
queue=False,
api_visibility="undocumented",
)
source_image.clear(
fn=clear_result,
outputs=[result_image, status],
queue=False,
api_visibility="undocumented",
)
demo.queue(default_concurrency_limit=1).launch(
mcp_server=True,
theme=gr.themes.Base(),
css=CSS,
)