product-studio-birefnet / multiview_runtime.py
Dbmaxwell's picture
Switch Studio10 to Qwen multi-angle generation
0ac7455 verified
Raw
History Blame Contribute Delete
4.77 kB
"""Hosted Qwen studio generation through Hugging Face Inference Providers."""
from __future__ import annotations
import os
import time
import uuid
import zipfile
from pathlib import Path
import numpy as np
from PIL import Image
BASE_MODEL_ID = "Qwen/Qwen-Image-Edit-2511"
ANGLE_MODEL_ID = "fal/Qwen-Image-Edit-2511-Multiple-Angles-LoRA"
OUTPUT_ROOT = Path("/tmp/studio10-outputs")
STUDIO_SHOTS = (
("01 路 Front-left", None),
("02 路 Front-right", "<sks> front-right quarter view elevated shot medium shot"),
("03 路 Front", "<sks> front view eye-level shot medium shot"),
("04 路 Left profile", "<sks> left side view eye-level shot medium shot"),
("05 路 Right profile", "<sks> right side view eye-level shot medium shot"),
("06 路 Rear-right", "<sks> back-right quarter view elevated shot medium shot"),
("07 路 Rear-left", "<sks> back-left quarter view elevated shot medium shot"),
("08 路 Low angle", "<sks> front-left quarter view low-angle shot medium shot"),
("09 路 High angle", "<sks> front-right quarter view high-angle shot medium shot"),
("10 路 Rear", "<sks> back view eye-level shot medium shot"),
)
def _client():
from huggingface_hub import InferenceClient
token = os.getenv("HF_TOKEN")
if not token:
raise RuntimeError("HF_TOKEN is not configured in the Space secrets.")
return InferenceClient(provider="fal-ai", token=token, timeout=300)
def _identity_card(image: Image.Image, size: int = 1024) -> Image.Image:
product = image.convert("RGBA")
bbox = product.getchannel("A").getbbox()
if bbox:
product = product.crop(bbox)
product.thumbnail((size - 180, size - 180), Image.Resampling.LANCZOS)
card = Image.new("RGBA", (size, size), "white")
x = (size - product.width) // 2
y = size - product.height - 90
card.alpha_composite(product, (x, y))
return card.convert("RGB")
def _pure_white_finish(image: Image.Image) -> Image.Image:
array = np.asarray(image.convert("RGB")).copy()
low = array.min(axis=2)
spread = array.max(axis=2) - low
array[(low >= 247) & (spread <= 7)] = 255
return Image.fromarray(array, mode="RGB")
def generate_studio_photos(
isolated_reference: Image.Image,
seed: int,
) -> list[tuple[str, Image.Image]]:
client = _client()
reference = _identity_card(isolated_reference)
master_prompt = (
"Create a photorealistic ecommerce studio photo of this exact product from a front-left "
"three-quarter elevated camera angle. Preserve its precise shape, proportions, color, material, "
"stitching, seams, hardware, logos and labels. Rebuild the whole photograph; do not paste the "
"cutout. Place the product naturally on a seamless pure white studio floor with its real base "
"fully touching the floor. Add softbox lighting and a short attached contact shadow. Never float "
"or levitate the product. One product only, centered, fully visible, no props, no text, no border."
)
master = client.image_to_image(
image=reference,
prompt=master_prompt,
model=BASE_MODEL_ID,
num_inference_steps=40,
guidance_scale=1.0,
seed=int(seed),
)
master = _pure_white_finish(master)
results: list[tuple[str, Image.Image]] = [(STUDIO_SHOTS[0][0], master)]
for index, (title, pose_prompt) in enumerate(STUDIO_SHOTS[1:], start=1):
output = client.image_to_image(
image=master,
prompt=pose_prompt,
model=ANGLE_MODEL_ID,
num_inference_steps=40,
guidance_scale=1.0,
seed=int(seed) + index * 997,
)
results.append((title, _pure_white_finish(output)))
return results
def save_studio_outputs(results: list[tuple[str, Image.Image]]) -> tuple[list[str], str]:
OUTPUT_ROOT.mkdir(parents=True, exist_ok=True)
now = time.time()
for directory in OUTPUT_ROOT.iterdir():
try:
if directory.is_dir() and now - directory.stat().st_mtime > 6 * 3600:
for child in directory.iterdir():
child.unlink(missing_ok=True)
directory.rmdir()
except OSError:
pass
run_dir = OUTPUT_ROOT / uuid.uuid4().hex
run_dir.mkdir()
paths: list[str] = []
for index, (_, image) in enumerate(results, start=1):
path = run_dir / f"{index:02d}-studio-shot.png"
image.save(path, optimize=True)
paths.append(str(path))
archive_path = run_dir / "studio10.zip"
with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive:
for path in paths:
archive.write(path, arcname=Path(path).name)
return paths, str(archive_path)