Spaces:
Running on Zero
Running on Zero
File size: 7,817 Bytes
e4338d2 1d03914 e4338d2 1d03914 e4338d2 1d03914 c5a7b2a b6b5ee7 9cc6ee4 1d03914 e4338d2 5123f5b b6b5ee7 5123f5b 1d03914 51709c9 1d03914 e4338d2 1d03914 e4338d2 5123f5b e4338d2 1d03914 e4338d2 5123f5b e4338d2 1d03914 e4338d2 1d03914 c5a7b2a 1d03914 9cc6ee4 1d03914 e4338d2 1d03914 c5a7b2a e4338d2 c5a7b2a 421f1be e4338d2 b6b5ee7 421f1be b55a1fc e4338d2 c5a7b2a b6b5ee7 c5a7b2a b6b5ee7 c5a7b2a 9cc6ee4 c5a7b2a 9cc6ee4 c5a7b2a 9cc6ee4 c5a7b2a e4338d2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | import spaces
import gradio as gr
import os
import sys
import time
import tempfile
import shutil
import torch
_root = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, _root)
sys.path.insert(0, os.path.join(_root, "common"))
from PIL import Image
REPO_LAYERDIFF = "layerdifforg/seethroughv0.0.2_layerdiff3d"
REPO_DEPTH = "24yearsold/seethroughv0.0.1_marigold"
def _log(msg):
print(f"[{time.strftime('%H:%M:%S')}] {msg}", flush=True)
# --------------- Preload models to CPU at startup ---------------
_log("Preloading LayerDiff pipeline to CPU...")
from modules.layerdiffuse.diffusers_kdiffusion_sdxl import KDiffusionStableDiffusionXLPipeline
from modules.layerdiffuse.layerdiff3d import UNetFrameConditionModel
from modules.layerdiffuse.vae import TransparentVAE, TransparentVAEDecoder, TransparentVAEEncoder
_trans_vae = TransparentVAE.from_pretrained(REPO_LAYERDIFF, subfolder="trans_vae")
_unet_ld = UNetFrameConditionModel.from_pretrained(REPO_LAYERDIFF, subfolder="unet")
_layerdiff_pipe = KDiffusionStableDiffusionXLPipeline.from_pretrained(
REPO_LAYERDIFF, trans_vae=_trans_vae, unet=_unet_ld, scheduler=None
)
_log("LayerDiff pipeline loaded to CPU.")
_log("Preloading Marigold pipeline to CPU...")
from modules.marigold import MarigoldDepthPipeline
_unet_mg = UNetFrameConditionModel.from_pretrained(REPO_DEPTH, subfolder="unet")
_marigold_pipe = MarigoldDepthPipeline.from_pretrained(REPO_DEPTH, unet=_unet_mg)
_log("Marigold pipeline loaded to CPU.")
_models_on_gpu = False
from utils.inference_utils import apply_layerdiff, apply_marigold, further_extr
from utils.torch_utils import seed_everything
import utils.inference_utils as _inf
def _move_to_gpu():
global _models_on_gpu
if _models_on_gpu:
_log("Models already on GPU, skipping transfer.")
return
t0 = time.time()
_log("Moving LayerDiff to CUDA bf16...")
_layerdiff_pipe.vae.to(dtype=torch.bfloat16, device="cuda")
_layerdiff_pipe.trans_vae.to(dtype=torch.bfloat16, device="cuda")
_layerdiff_pipe.unet.to(dtype=torch.bfloat16, device="cuda")
_layerdiff_pipe.text_encoder.to(dtype=torch.bfloat16, device="cuda")
_layerdiff_pipe.text_encoder_2.to(dtype=torch.bfloat16, device="cuda")
_log(f"LayerDiff on GPU ({time.time() - t0:.1f}s)")
t0 = time.time()
_log("Moving Marigold to CUDA bf16...")
_marigold_pipe.to(device="cuda", dtype=torch.bfloat16)
_log(f"Marigold on GPU ({time.time() - t0:.1f}s)")
# Inject into inference_utils globals so apply_* functions skip their own loading
_inf.layerdiff_pipeline = _layerdiff_pipe
_inf.marigold_pipeline = _marigold_pipe
_models_on_gpu = True
_SKIP_TAGS = {"src_img", "src_head", "reconstruction"}
def _collect_layer_gallery(saved_dir):
"""Collect layer PNGs as (image, label) tuples for the gallery."""
gallery = []
for f in sorted(os.listdir(saved_dir)):
if not f.endswith(".png"):
continue
tag = f[:-4]
if tag.endswith("_depth") or tag in _SKIP_TAGS:
continue
img = Image.open(os.path.join(saved_dir, f))
gallery.append((img, tag))
return gallery
@spaces.GPU(duration=120)
def inference(image: Image.Image, resolution: int = 768, seed: int = 42, tblr_split: bool = False):
t_start = time.time()
if image is None:
raise gr.Error("Please upload an image.")
# Snap to nearest multiple of 64 for clean latent dimensions
resolution = max(64, min(resolution, 1280))
resolution = round(resolution / 64) * 64
_log(f"Resolution: {resolution}, Seed: {seed}, Image: {image.size}")
_move_to_gpu()
seed_everything(seed)
tmpdir = tempfile.mkdtemp(prefix="seethrough_")
try:
input_path = os.path.join(tmpdir, "input.png")
image.save(input_path)
t0 = time.time()
_log("Running LayerDiff...")
apply_layerdiff(
input_path, REPO_LAYERDIFF,
save_dir=tmpdir, seed=seed, resolution=resolution,
)
_log(f"LayerDiff done ({time.time() - t0:.1f}s)")
t0 = time.time()
_log("Running Marigold depth...")
apply_marigold(
input_path, REPO_DEPTH,
save_dir=tmpdir, seed=seed, resolution=resolution,
)
_log(f"Marigold done ({time.time() - t0:.1f}s)")
saved = os.path.join(tmpdir, "input")
# Collect gallery before PSD assembly (further_extr may modify files)
gallery = _collect_layer_gallery(saved)
t0 = time.time()
_log("Running PSD assembly...")
further_extr(saved, rotate=False, save_to_psd=True, tblr_split=tblr_split)
_log(f"PSD assembly done ({time.time() - t0:.1f}s)")
psd_path = saved + ".psd"
if os.path.exists(psd_path):
output_path = os.path.join(
tempfile.gettempdir(), "seethrough_output.psd"
)
shutil.copy2(psd_path, output_path)
_log(f"Total inference time: {time.time() - t_start:.1f}s")
return output_path, gallery
raise gr.Error("PSD generation failed — no output file produced.")
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
with gr.Blocks(title="See-through: Layer Decomposition") as demo:
gr.Markdown(
"# See-through: Single-image Layer Decomposition for Anime Characters\n\n"
'<a href="https://github.com/shitagaki-lab/see-through" target="_blank">GitHub</a> | '
'<a href="https://arxiv.org/abs/2602.03749" target="_blank">Paper (arXiv:2602.03749)</a>\n\n'
"Upload an anime character illustration to decompose it into "
"fully-inpainted semantic layers with depth ordering, "
"exported as a layered PSD file.\n\n"
"**Note:** 768 resolution is recommended for ZeroGPU free tier. "
"Higher resolutions may timeout or exhaust your daily quota. "
'For best quality, clone the <a href="https://github.com/shitagaki-lab/see-through" target="_blank">full repo</a> '
"and run `inference_psd.py` locally. "
'We also have a <a href="https://github.com/jtydhr88/ComfyUI-See-through" target="_blank">ComfyUI Node Extension</a> '
"and other community extensions — check our repo for more details.\n\n"
"**Disclaimer:** This demo uses a newer model checkpoint and "
"may not fully reproduce identical results reported in the paper."
)
with gr.Row():
with gr.Column(scale=1):
input_image = gr.Image(type="pil", label="Upload image (non-square images will be padded)")
resolution = gr.Slider(
minimum=768, maximum=1280, value=768, step=64,
label="Resolution",
info="768 recommended for ZeroGPU free tier. Higher resolutions may timeout or use up your daily quota quickly.",
)
seed = gr.Slider(minimum=0, maximum=9999, value=42, step=1, label="Seed")
tblr_split = gr.Checkbox(
value=False,
label="Split left/right arms & legs",
info="Separate left and right limbs into individual layers. Useful if the default output glues them together.",
)
run_btn = gr.Button("Run", variant="primary")
with gr.Column(scale=2):
psd_output = gr.File(label="Download layered PSD")
gallery_output = gr.Gallery(label="Separated layers", columns=4, height="auto")
run_btn.click(
fn=inference,
inputs=[input_image, resolution, seed, tblr_split],
outputs=[psd_output, gallery_output],
)
gr.Examples(
examples=[["common/assets/test_image.png", 768, 42, False]],
inputs=[input_image, resolution, seed, tblr_split],
)
if __name__ == "__main__":
demo.launch()
|