File size: 4,769 Bytes
0ac7455 c302e80 0ac7455 c302e80 3280fc1 0ac7455 3280fc1 c302e80 0ac7455 c302e80 0ac7455 22069f1 0ac7455 22069f1 c302e80 0ac7455 c302e80 3280fc1 c302e80 0ac7455 c302e80 3280fc1 c302e80 3280fc1 c302e80 0ac7455 c302e80 0ac7455 c302e80 | 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 | """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)
|