Spaces:
Running on Zero
Running on Zero
File size: 11,532 Bytes
5d4511a 9a5065c 5d4511a 99302bc 0cf8ffc 5d4511a 99302bc 5d4511a af844a1 5d4511a 9a5065c 99302bc 5d4511a 99302bc 5d4511a 76862de 5d4511a 9a5065c 5d4511a 296faa9 76862de 296faa9 76862de af844a1 6d862f4 af844a1 6d862f4 af844a1 5d4511a 9a5065c 213bf15 5d4511a aa2a834 af844a1 aa2a834 af844a1 5d4511a 9a5065c 5d4511a 9a5065c 5d4511a 76862de 5d4511a aa2a834 af844a1 aa2a834 af844a1 5d4511a 9a5065c 5d4511a 76862de 5d4511a aa2a834 5d4511a 76862de 5d4511a af844a1 0e7a9cf 5d4511a af844a1 5d4511a 3801f4d 5d4511a 0e7a9cf 5d4511a 3801f4d 5d4511a 9a5065c af844a1 9a5065c af844a1 9a5065c 5d4511a 296faa9 af844a1 76862de af844a1 296faa9 76862de af844a1 5d4511a 9a5065c af844a1 9a5065c 5d4511a af844a1 5d4511a 9a5065c 5d4511a | 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 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 | """z-image-studio — Gradio entrypoint.
On HF Spaces, ``_bootstrap`` runs once on import to mirror the read-only preload
cache into a writable tree.
"""
from __future__ import annotations
import os
import random
from pathlib import Path
# DiffSynth defaults to ModelScope; force HF so preload_from_hub + HF cache work.
# Must be set before any diffsynth import path is taken (backend imports it lazily).
os.environ.setdefault("DIFFSYNTH_DOWNLOAD_SOURCE", "huggingface")
# Apple Silicon: let PyTorch fall back to CPU for the small set of ops MPS doesn't
# implement (some scaled-dot-product flavors, certain index ops). Without this,
# DiffSynth crashes mid-pipeline on the first unsupported op rather than degrading.
os.environ.setdefault("PYTORCH_ENABLE_MPS_FALLBACK", "1")
import gradio as gr
import backend
import lora as lora_mod
import models
import preprocessors
import theme
import ui
# ----- HF Spaces bootstrap ---------------------------------------------------
_REPO_ROOT = Path(__file__).resolve().parent
_DIFFSYNTH_MODELS_DIR = _REPO_ROOT / "models"
def _bootstrap() -> None:
"""Mirror the preload_from_hub cache, then symlink snapshots into DiffSynth's
expected ``./models/<repo>/`` layout so the pipeline reuses preloaded weights
instead of re-downloading on first call.
On Spaces: cache is read-only owned by the build user → mirror to ~/hf-cache-rw
first, then point HF env there, then symlink into ./models.
Locally: skip the mirror (we own the dirs); just symlink from the user's HF
cache to ./models so DiffSynth finds the snapshots.
"""
if models.on_spaces():
src = Path(os.environ.get("HF_HOME", str(Path.home() / ".cache" / "huggingface")))
dst = Path.home() / "hf-cache-rw"
models.mirror_preload_hf_cache(src, dst)
os.environ["HF_HOME"] = str(dst)
os.environ["HF_HUB_CACHE"] = str(dst / "hub")
cache_hub = dst / "hub"
else:
cache_hub = Path(os.environ.get("HF_HUB_CACHE", str(Path.home() / ".cache" / "huggingface" / "hub")))
# Point DiffSynth at our project-local models dir + symlink every cached
# snapshot so DiffSynth's ModelConfig finds them without re-downloading.
os.environ.setdefault("DIFFSYNTH_MODEL_BASE_PATH", str(_DIFFSYNTH_MODELS_DIR))
_DIFFSYNTH_MODELS_DIR.mkdir(exist_ok=True)
models.symlink_hf_cache_to_diffsynth_layout(cache_hub, _DIFFSYNTH_MODELS_DIR)
_bootstrap()
# ----- Lazy backend singleton ------------------------------------------------
_BACKEND: backend.ZImageStudioBackend | None = None
def get_backend() -> backend.ZImageStudioBackend:
global _BACKEND
if _BACKEND is None:
_BACKEND = backend.ZImageStudioBackend()
return _BACKEND
# ----- Generation event handlers --------------------------------------------
def _maybe_random_seed(seed: int) -> int:
return seed if seed and seed > 0 else random.randint(1, 2_147_483_647)
def _coerce_lora(lora_path: str | None) -> Path | None:
if not lora_path:
return None
p = Path(lora_path)
lora_mod.sniff(p) # validate cheaply; raises LoRAValidationError if bad
return p
def _on_model_change(model_name: str):
"""When the user picks Base / Turbo in the radio, update steps + cfg defaults
and the LoRA-compatibility hint on the toggle label."""
if model_name == "Base":
return 25, 4.0, gr.update(label="Use a LoRA (compatible with Z-Image)")
return 8, 1.0, gr.update(label="Use a LoRA (compatible with Z-Image-Turbo)")
def _preview_cn(image, mode):
"""Render the live preprocessor preview next to the input on the ControlNet tab.
Wrapped in ``try/except`` so that a missing optional dep (controlnet_aux for
Depth / Pose) never breaks the form — it just falls back to the raw input.
We DO log the exception to stderr so the next failure surfaces in the logs
rather than silently showing the user the original image (the previous
behavior hid a typo'd processor name for weeks).
"""
if image is None:
return None
try:
return preprocessors.run(mode, image)
except Exception as e:
import sys
import traceback
print(f"[preview_cn] {mode!r} failed: {e}", file=sys.stderr, flush=True)
traceback.print_exc(file=sys.stderr)
return image
def _esrgan_path() -> str:
"""Locate the preloaded RealESRGAN_x4plus.pth."""
from huggingface_hub import hf_hub_download
return hf_hub_download("lllyasviel/Annotators", "RealESRGAN_x4plus.pth")
def on_t2i_generate(
prompt,
negative_prompt,
model,
steps,
cfg,
width,
height,
seed,
lora_enabled,
lora_path,
lora_strength,
progress=gr.Progress(track_tqdm=True), # noqa: B008
):
if not lora_enabled:
lora_path = None
try:
lora_p = _coerce_lora(lora_path)
except lora_mod.LoRAValidationError as e:
raise gr.Error(str(e)) from e
params = dict(
prompt=prompt,
negative_prompt=negative_prompt or "",
model=model,
steps=int(steps),
cfg=float(cfg),
width=int(width),
height=int(height),
seed=_maybe_random_seed(int(seed)),
lora_path=lora_p,
lora_strength=float(lora_strength),
)
image, meta = backend.generate_with_retry(get_backend(), mode="t2i", params=params)
return image, meta
def on_controlnet_generate(
prompt,
input_image,
preprocessor,
controlnet_scale,
steps,
seed,
lora_enabled,
lora_path,
lora_strength,
progress=gr.Progress(track_tqdm=True), # noqa: B008
):
if not lora_enabled:
lora_path = None
try:
lora_p = _coerce_lora(lora_path)
except lora_mod.LoRAValidationError as e:
raise gr.Error(str(e)) from e
params = dict(
prompt=prompt,
input_image=input_image,
preprocessor=preprocessor,
controlnet_scale=float(controlnet_scale),
steps=int(steps),
seed=_maybe_random_seed(int(seed)),
lora_path=lora_p,
lora_strength=float(lora_strength),
)
image, meta = backend.generate_with_retry(get_backend(), mode="controlnet", params=params)
return image, meta
def on_upscale_generate(
prompt,
input_image,
refine_steps,
refine_denoise,
seed,
progress=gr.Progress(track_tqdm=True), # noqa: B008
):
params = dict(
prompt=prompt or "masterpiece, 8k",
input_image=input_image,
refine_steps=int(refine_steps),
refine_denoise=float(refine_denoise),
seed=_maybe_random_seed(int(seed)),
esrgan_model_path=_esrgan_path(),
)
image, meta = backend.generate_with_retry(get_backend(), mode="upscale", params=params)
return image, meta
# ----- Blocks ----------------------------------------------------------------
HEADER_HTML = """
<div style="display:flex;justify-content:space-between;align-items:baseline;padding:8px 0 4px 0;">
<div style="font-size:16px;font-weight:600;letter-spacing:-0.01em;">
Z-Image Studio<span class="zis-brand-period">.</span>
</div>
<div class="zis-status-dot" style="font-size:11px;color:#988B7C;letter-spacing:0.02em;">ready</div>
</div>
""".strip()
CTA_HTML = """
<div class="zis-cta">
Built with care.
<strong>Drop a <span class="zis-cta-heart">♥</span> at the top</strong> to support it
<span class="zis-cta-sep">·</span>
Follow <a href="https://huggingface.co/techfreakworm" target="_blank" rel="noopener noreferrer">@techfreakworm</a>
for what's next.
</div>
""".strip()
def build_app() -> gr.Blocks:
with gr.Blocks(theme=theme.build_theme(), css=theme.CSS, title="Z-Image Studio") as demo:
gr.HTML(HEADER_HTML)
gr.HTML(CTA_HTML)
with gr.Tabs():
with gr.Tab("Text → Image"):
t = ui.build_t2i_tab()
t["generate_btn"].click(
fn=on_t2i_generate,
inputs=[
t["prompt"],
t["negative_prompt"],
t["model"],
t["steps"],
t["cfg"],
t["width"],
t["height"],
t["seed"],
t["lora_enabled"],
t["lora_path"],
t["lora_strength"],
],
outputs=[t["output_image"], t["output_meta"]],
)
# Radio change → update step / cfg defaults + LoRA-compatibility hint
# on the toggle label + reveal Base-only fields.
t["model"].change(
fn=_on_model_change,
inputs=[t["model"]],
outputs=[t["steps"], t["cfg"], t["lora_enabled"]],
)
t["model"].change(
fn=lambda m: gr.Group(visible=(m == "Base")),
inputs=[t["model"]],
outputs=[t["base_group"]],
)
# LoRA checkbox → reveal file + strength.
t["lora_enabled"].change(
fn=lambda v: gr.Group(visible=v),
inputs=[t["lora_enabled"]],
outputs=[t["lora_group"]],
)
with gr.Tab("ControlNet"):
c = ui.build_controlnet_tab()
c["generate_btn"].click(
fn=on_controlnet_generate,
inputs=[
c["prompt"],
c["input_image"],
c["preprocessor"],
c["controlnet_scale"],
c["steps"],
c["seed"],
c["lora_enabled"],
c["lora_path"],
c["lora_strength"],
],
outputs=[c["output_image"], c["output_meta"]],
)
# Live preprocessor preview — fires on input change or mode change.
c["input_image"].change(
fn=_preview_cn,
inputs=[c["input_image"], c["preprocessor"]],
outputs=[c["preview_image"]],
)
c["preprocessor"].change(
fn=_preview_cn,
inputs=[c["input_image"], c["preprocessor"]],
outputs=[c["preview_image"]],
)
# LoRA checkbox → reveal file + strength.
c["lora_enabled"].change(
fn=lambda v: gr.Group(visible=v),
inputs=[c["lora_enabled"]],
outputs=[c["lora_group"]],
)
with gr.Tab("Upscale"):
u = ui.build_upscale_tab()
u["generate_btn"].click(
fn=on_upscale_generate,
inputs=[
u["prompt"],
u["input_image"],
u["refine_steps"],
u["refine_denoise"],
u["seed"],
],
outputs=[u["output_image"], u["output_meta"]],
)
return demo
if __name__ == "__main__":
demo = build_app()
demo.queue(default_concurrency_limit=1)
demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))
|