Spaces:
Running on Zero
Running on Zero
Extract transparent assets with BiRefNet
Browse files- README.md +6 -3
- app.py +101 -12
- requirements.txt +5 -0
README.md
CHANGED
|
@@ -67,7 +67,7 @@ If a requested asset has no deterministic hook, images are still generated for r
|
|
| 67 |
|
| 68 |
## Contract-driven generation and validation
|
| 69 |
|
| 70 |
-
Filenames and IDs do not decide whether an image is a scene or sprite. Dimensions, transparency, composition, camera, quantity, subject count, and silhouette requirements come from the manifest. Transparent single-subject outputs
|
| 71 |
|
| 72 |
The image model can still violate these instructions, so every result requires visual review. Single-subject outputs that fail automated checks are regenerated with a fresh seed rather than cropped into a guessed subject. The review UI can regenerate one output or a complete multi-output asset set.
|
| 73 |
|
|
@@ -81,15 +81,16 @@ If the Space has an `HF_TOKEN` secret and `USE_HF_PROMPT_PROVIDER=1`, it first u
|
|
| 81 |
|
| 82 |
## Default neural image pipeline
|
| 83 |
|
| 84 |
-
The deployed Space is designed for Hugging Face ZeroGPU and uses
|
| 85 |
|
| 86 |
```text
|
| 87 |
segmind/SSD-1B + latent-consistency/lcm-lora-ssd-1b
|
|
|
|
| 88 |
```
|
| 89 |
|
| 90 |
SSD-1B is an Apache-2.0 distilled SDXL model. The LCM adapter enables low-step generation. The weights run inside the Space's ZeroGPU allocation, so the app does not consume Hugging Face Inference Provider credits and does not require a paid image API.
|
| 91 |
|
| 92 |
-
Sprites and backgrounds are generated directly from their written prompts. No procedurally drawn sprite, palette, silhouette, camera guide, or background layout is passed into the production
|
| 93 |
|
| 94 |
SSD-1B's CLIP text encoder has a 77-token context window. Production prompts therefore use a conservative word budget, place the camera and single-subject contract first, and compact the free-form description. This prevents those critical constraints from being silently truncated behind verbose style text.
|
| 95 |
|
|
@@ -106,6 +107,8 @@ USE_PRIMARY_IMAGE_MODEL=1
|
|
| 106 |
REQUIRE_PRIMARY_IMAGE_MODEL=1
|
| 107 |
PRIMARY_IMAGE_MODEL=segmind/SSD-1B
|
| 108 |
PRIMARY_LORA_MODEL=latent-consistency/lcm-lora-ssd-1b
|
|
|
|
|
|
|
| 109 |
PRIMARY_IMAGE_STEPS=4
|
| 110 |
PRIMARY_SPRITE_STEPS=6
|
| 111 |
PRIMARY_SPRITE_ATTEMPTS=3
|
|
|
|
| 67 |
|
| 68 |
## Contract-driven generation and validation
|
| 69 |
|
| 70 |
+
Filenames and IDs do not decide whether an image is a scene or sprite. Dimensions, transparency, composition, camera, quantity, subject count, and silhouette requirements come from the manifest. Transparent single-subject outputs receive neural alpha extraction from the pinned MIT-licensed [`ZhengPeng7/BiRefNet`](https://huggingface.co/ZhengPeng7/BiRefNet) model, occupancy normalization, subject-count checks, and a retained-scene check that rejects environmental foreground spread around an isolated subject. Opaque outputs are checked for unwanted transparency. Seamless requests receive an opposite-edge similarity check. Other compositions are not incorrectly rejected for containing multiple subjects.
|
| 71 |
|
| 72 |
The image model can still violate these instructions, so every result requires visual review. Single-subject outputs that fail automated checks are regenerated with a fresh seed rather than cropped into a guessed subject. The review UI can regenerate one output or a complete multi-output asset set.
|
| 73 |
|
|
|
|
| 81 |
|
| 82 |
## Default neural image pipeline
|
| 83 |
|
| 84 |
+
The deployed Space is designed for Hugging Face ZeroGPU and uses real neural models for generation and transparent cutout extraction:
|
| 85 |
|
| 86 |
```text
|
| 87 |
segmind/SSD-1B + latent-consistency/lcm-lora-ssd-1b
|
| 88 |
+
ZhengPeng7/BiRefNet (transparent-cutout alpha only)
|
| 89 |
```
|
| 90 |
|
| 91 |
SSD-1B is an Apache-2.0 distilled SDXL model. The LCM adapter enables low-step generation. The weights run inside the Space's ZeroGPU allocation, so the app does not consume Hugging Face Inference Provider credits and does not require a paid image API.
|
| 92 |
|
| 93 |
+
Sprites and backgrounds are generated directly from their written prompts. No procedurally drawn sprite, palette, silhouette, camera guide, or background layout is passed into the production image generator. Sprite prompts use a portrait canvas and positive single-subject composition language; the negative prompt separately rejects sheets, lineups, repeated subjects, multiple views, and surrounding environments. BiRefNet predicts only the alpha mask for transparent cutouts while preserving SSD-1B's generated RGB artwork. After neural transparency extraction, sprites with zero or multiple significant foreground components, retained scene-like alpha, implausible silhouettes, or inadequate scale are regenerated with a new seed up to `PRIMARY_SPRITE_ATTEMPTS` times. If every attempt fails, the deployed Space returns an explicit model-generation error rather than returning a contact sheet or environment-contaminated cutout. Background negative prompts reject characters and generic particle overlays.
|
| 94 |
|
| 95 |
SSD-1B's CLIP text encoder has a 77-token context window. Production prompts therefore use a conservative word budget, place the camera and single-subject contract first, and compact the free-form description. This prevents those critical constraints from being silently truncated behind verbose style text.
|
| 96 |
|
|
|
|
| 107 |
REQUIRE_PRIMARY_IMAGE_MODEL=1
|
| 108 |
PRIMARY_IMAGE_MODEL=segmind/SSD-1B
|
| 109 |
PRIMARY_LORA_MODEL=latent-consistency/lcm-lora-ssd-1b
|
| 110 |
+
FOREGROUND_MODEL_ID=ZhengPeng7/BiRefNet
|
| 111 |
+
FOREGROUND_MODEL_REVISION=e2bf8e4460fc8fa32bba5ea4d94b3233d367b0e4
|
| 112 |
PRIMARY_IMAGE_STEPS=4
|
| 113 |
PRIMARY_SPRITE_STEPS=6
|
| 114 |
PRIMARY_SPRITE_ATTEMPTS=3
|
app.py
CHANGED
|
@@ -167,6 +167,11 @@ class IntegrationReport:
|
|
| 167 |
HF_TOKEN = os.environ.get("HF_TOKEN", "")
|
| 168 |
PRIMARY_IMAGE_MODEL = os.environ.get("PRIMARY_IMAGE_MODEL", "segmind/SSD-1B")
|
| 169 |
PRIMARY_LORA_MODEL = os.environ.get("PRIMARY_LORA_MODEL", "latent-consistency/lcm-lora-ssd-1b")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
PRIMARY_IMAGE_STEPS = int(os.environ.get("PRIMARY_IMAGE_STEPS", "4"))
|
| 171 |
PRIMARY_SPRITE_STEPS = int(os.environ.get("PRIMARY_SPRITE_STEPS", "6"))
|
| 172 |
PRIMARY_SPRITE_ATTEMPTS = max(1, int(os.environ.get("PRIMARY_SPRITE_ATTEMPTS", "3")))
|
|
@@ -198,6 +203,8 @@ CONTROLNET_PIPE = None
|
|
| 198 |
CONTROLNET_ERROR = None
|
| 199 |
PRIMARY_TEXT_PIPE = None
|
| 200 |
PRIMARY_MODEL_ERROR = None
|
|
|
|
|
|
|
| 201 |
|
| 202 |
|
| 203 |
def gpu_task(duration):
|
|
@@ -1949,6 +1956,56 @@ def initialize_primary_image_model() -> None:
|
|
| 1949 |
PRIMARY_MODEL_ERROR = short_error(exc)
|
| 1950 |
|
| 1951 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1952 |
def primary_diffusion_prompt(spec: AssetSpec) -> str:
|
| 1953 |
camera = primary_camera_phrase(spec.camera, is_background_spec(spec))
|
| 1954 |
if spec.camera == "auto" and spec.camera_instruction and spec.camera_instruction.lower() != "auto":
|
|
@@ -1996,10 +2053,18 @@ def primary_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[byt
|
|
| 1996 |
if PRIMARY_TEXT_PIPE is None:
|
| 1997 |
return None, "primary ZeroGPU diffusion model is not initialized on this runtime"
|
| 1998 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1999 |
try:
|
| 2000 |
import torch
|
| 2001 |
|
| 2002 |
-
is_background = is_background_spec(spec)
|
| 2003 |
attempts = 1 if is_background else PRIMARY_SPRITE_ATTEMPTS
|
| 2004 |
width, height = primary_generation_dimensions(spec)
|
| 2005 |
last_failure_detail = "the last output did not contain one valid foreground subject"
|
|
@@ -2016,12 +2081,18 @@ def primary_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[byt
|
|
| 2016 |
width=width,
|
| 2017 |
height=height,
|
| 2018 |
).images[0]
|
| 2019 |
-
if not is_background and has_invalid_cutout_studio_field(image):
|
| 2020 |
-
last_failure_detail = "the last output did not use the required plain light studio field"
|
| 2021 |
-
continue
|
| 2022 |
-
content = polish_diffusion_asset(image, spec)
|
| 2023 |
if is_background:
|
|
|
|
| 2024 |
return content, None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2025 |
|
| 2026 |
subject_count = estimated_foreground_subject_count(content)
|
| 2027 |
if subject_count != 1:
|
|
@@ -2050,6 +2121,7 @@ def primary_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[byt
|
|
| 2050 |
# here keeps the weights resident between decorated requests instead of paying the
|
| 2051 |
# model-load cost on every generation.
|
| 2052 |
initialize_primary_image_model()
|
|
|
|
| 2053 |
|
| 2054 |
|
| 2055 |
def controlnet_background_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
|
|
@@ -2148,6 +2220,20 @@ def polish_diffusion_asset(image: Image.Image, spec: AssetSpec) -> bytes:
|
|
| 2148 |
return out.getvalue()
|
| 2149 |
|
| 2150 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2151 |
def has_invalid_cutout_studio_field(image: Image.Image) -> bool:
|
| 2152 |
"""Reject model scenes before alpha extraction when their outer field is not light and neutral.
|
| 2153 |
|
|
@@ -2535,11 +2621,13 @@ def generate_asset(spec: AssetSpec, index: int, run_id: int) -> tuple[str, str,
|
|
| 2535 |
if USE_PRIMARY_IMAGE_MODEL:
|
| 2536 |
png_content, error = primary_diffusion_png(spec, index, run_id)
|
| 2537 |
source = f"{PRIMARY_IMAGE_MODEL} + {PRIMARY_LORA_MODEL} on ZeroGPU"
|
|
|
|
|
|
|
| 2538 |
if error:
|
| 2539 |
errors.append(f"primary ZeroGPU diffusion: {error}")
|
| 2540 |
if REQUIRE_PRIMARY_IMAGE_MODEL:
|
| 2541 |
raise RuntimeError(
|
| 2542 |
-
"
|
| 2543 |
f"No procedural image was returned. Details: {error}"
|
| 2544 |
)
|
| 2545 |
|
|
@@ -2785,7 +2873,7 @@ def model_configuration_summary() -> str:
|
|
| 2785 |
sprite_chain = []
|
| 2786 |
primary_source = f"{PRIMARY_IMAGE_MODEL} + {PRIMARY_LORA_MODEL} on ZeroGPU"
|
| 2787 |
if USE_PRIMARY_IMAGE_MODEL:
|
| 2788 |
-
sprite_chain.append(f"direct text-to-image {primary_source}")
|
| 2789 |
if USE_DIFFUSION_FOR_SPRITES:
|
| 2790 |
sprite_chain.append(FREE_IMAGE_MODEL)
|
| 2791 |
if USE_HF_IMAGE_PROVIDER:
|
|
@@ -2816,6 +2904,7 @@ def model_configuration_summary() -> str:
|
|
| 2816 |
neural_models = []
|
| 2817 |
if USE_PRIMARY_IMAGE_MODEL:
|
| 2818 |
neural_models.append(f"{PRIMARY_IMAGE_MODEL} + {PRIMARY_LORA_MODEL}")
|
|
|
|
| 2819 |
if USE_DIFFUSION_FOR_SPRITES or USE_DIFFUSION_FOR_BACKGROUNDS:
|
| 2820 |
neural_models.append(FREE_IMAGE_MODEL)
|
| 2821 |
if USE_CONTROLNET_FOR_BACKGROUNDS:
|
|
@@ -2823,10 +2912,10 @@ def model_configuration_summary() -> str:
|
|
| 2823 |
neural_models.extend(remote_sources)
|
| 2824 |
neural_status = ", ".join(dict.fromkeys(neural_models)) if neural_models else "none active—procedural code only"
|
| 2825 |
enforcement = "required; generation stops on model failure" if REQUIRE_PRIMARY_IMAGE_MODEL else "development fallback allowed"
|
| 2826 |
-
if PRIMARY_MODEL_ERROR:
|
| 2827 |
-
readiness = f"startup error: {PRIMARY_MODEL_ERROR}"
|
| 2828 |
-
elif PRIMARY_TEXT_PIPE is not None:
|
| 2829 |
-
readiness = "loaded on ZeroGPU"
|
| 2830 |
else:
|
| 2831 |
readiness = "loads in the deployed ZeroGPU runtime"
|
| 2832 |
return (
|
|
@@ -2837,7 +2926,7 @@ def model_configuration_summary() -> str:
|
|
| 2837 |
f"neural enforcement: `{enforcement}` · primary readiness: `{readiness}`. "
|
| 2838 |
"Every production image is generated directly from its written prompt by the primary text-to-image model. "
|
| 2839 |
"The user-defined composition and camera contract is placed first inside a conservative CLIP prompt budget. "
|
| 2840 |
-
"Transparent single-subject attempts
|
| 2841 |
"retained-scene, silhouette, and scale validation; failures are regenerated with a new seed up to "
|
| 2842 |
f"{PRIMARY_SPRITE_ATTEMPTS} times. No procedural guide is supplied to the model. The procedural renderer is "
|
| 2843 |
"development-only and is blocked in the deployed Space when the primary model fails."
|
|
|
|
| 167 |
HF_TOKEN = os.environ.get("HF_TOKEN", "")
|
| 168 |
PRIMARY_IMAGE_MODEL = os.environ.get("PRIMARY_IMAGE_MODEL", "segmind/SSD-1B")
|
| 169 |
PRIMARY_LORA_MODEL = os.environ.get("PRIMARY_LORA_MODEL", "latent-consistency/lcm-lora-ssd-1b")
|
| 170 |
+
FOREGROUND_MODEL_ID = os.environ.get("FOREGROUND_MODEL_ID", "ZhengPeng7/BiRefNet")
|
| 171 |
+
FOREGROUND_MODEL_REVISION = os.environ.get(
|
| 172 |
+
"FOREGROUND_MODEL_REVISION",
|
| 173 |
+
"e2bf8e4460fc8fa32bba5ea4d94b3233d367b0e4",
|
| 174 |
+
)
|
| 175 |
PRIMARY_IMAGE_STEPS = int(os.environ.get("PRIMARY_IMAGE_STEPS", "4"))
|
| 176 |
PRIMARY_SPRITE_STEPS = int(os.environ.get("PRIMARY_SPRITE_STEPS", "6"))
|
| 177 |
PRIMARY_SPRITE_ATTEMPTS = max(1, int(os.environ.get("PRIMARY_SPRITE_ATTEMPTS", "3")))
|
|
|
|
| 203 |
CONTROLNET_ERROR = None
|
| 204 |
PRIMARY_TEXT_PIPE = None
|
| 205 |
PRIMARY_MODEL_ERROR = None
|
| 206 |
+
FOREGROUND_MODEL = None
|
| 207 |
+
FOREGROUND_MODEL_ERROR = None
|
| 208 |
|
| 209 |
|
| 210 |
def gpu_task(duration):
|
|
|
|
| 1956 |
PRIMARY_MODEL_ERROR = short_error(exc)
|
| 1957 |
|
| 1958 |
|
| 1959 |
+
def initialize_foreground_model() -> None:
|
| 1960 |
+
"""Load the pinned neural foreground extractor used by transparent cutouts."""
|
| 1961 |
+
global FOREGROUND_MODEL, FOREGROUND_MODEL_ERROR
|
| 1962 |
+
if FOREGROUND_MODEL is not None or FOREGROUND_MODEL_ERROR:
|
| 1963 |
+
return
|
| 1964 |
+
if not USE_PRIMARY_IMAGE_MODEL or not os.environ.get("SPACE_ID"):
|
| 1965 |
+
return
|
| 1966 |
+
|
| 1967 |
+
try:
|
| 1968 |
+
import torch
|
| 1969 |
+
from transformers import AutoModelForImageSegmentation
|
| 1970 |
+
|
| 1971 |
+
model = AutoModelForImageSegmentation.from_pretrained(
|
| 1972 |
+
FOREGROUND_MODEL_ID,
|
| 1973 |
+
revision=FOREGROUND_MODEL_REVISION,
|
| 1974 |
+
trust_remote_code=True,
|
| 1975 |
+
torch_dtype=torch.float16,
|
| 1976 |
+
)
|
| 1977 |
+
FOREGROUND_MODEL = model.eval().to("cuda")
|
| 1978 |
+
except Exception as exc:
|
| 1979 |
+
FOREGROUND_MODEL_ERROR = short_error(exc)
|
| 1980 |
+
|
| 1981 |
+
|
| 1982 |
+
def neural_foreground_cutout(image: Image.Image) -> Image.Image:
|
| 1983 |
+
"""Use BiRefNet to derive alpha while preserving the generator's RGB artwork."""
|
| 1984 |
+
if FOREGROUND_MODEL is None:
|
| 1985 |
+
raise RuntimeError("neural foreground model is not initialized")
|
| 1986 |
+
|
| 1987 |
+
import numpy as np
|
| 1988 |
+
import torch
|
| 1989 |
+
|
| 1990 |
+
source = image.convert("RGB")
|
| 1991 |
+
sample = source.resize((1024, 1024), Image.LANCZOS)
|
| 1992 |
+
array = np.asarray(sample, dtype=np.float32) / 255.0
|
| 1993 |
+
tensor = torch.from_numpy(array).permute(2, 0, 1).unsqueeze(0)
|
| 1994 |
+
mean = torch.tensor((0.485, 0.456, 0.406)).view(1, 3, 1, 1)
|
| 1995 |
+
std = torch.tensor((0.229, 0.224, 0.225)).view(1, 3, 1, 1)
|
| 1996 |
+
tensor = ((tensor - mean) / std).to(device="cuda", dtype=torch.float16)
|
| 1997 |
+
|
| 1998 |
+
with torch.no_grad():
|
| 1999 |
+
prediction = FOREGROUND_MODEL(tensor)[-1].sigmoid()[0].squeeze().float().cpu().numpy()
|
| 2000 |
+
mask = Image.fromarray(np.clip(prediction * 255.0, 0, 255).astype(np.uint8)).resize(
|
| 2001 |
+
source.size,
|
| 2002 |
+
Image.LANCZOS,
|
| 2003 |
+
)
|
| 2004 |
+
cutout = source.convert("RGBA")
|
| 2005 |
+
cutout.putalpha(mask)
|
| 2006 |
+
return cutout
|
| 2007 |
+
|
| 2008 |
+
|
| 2009 |
def primary_diffusion_prompt(spec: AssetSpec) -> str:
|
| 2010 |
camera = primary_camera_phrase(spec.camera, is_background_spec(spec))
|
| 2011 |
if spec.camera == "auto" and spec.camera_instruction and spec.camera_instruction.lower() != "auto":
|
|
|
|
| 2053 |
if PRIMARY_TEXT_PIPE is None:
|
| 2054 |
return None, "primary ZeroGPU diffusion model is not initialized on this runtime"
|
| 2055 |
|
| 2056 |
+
is_background = is_background_spec(spec)
|
| 2057 |
+
if not is_background:
|
| 2058 |
+
if FOREGROUND_MODEL is None and not FOREGROUND_MODEL_ERROR:
|
| 2059 |
+
initialize_foreground_model()
|
| 2060 |
+
if FOREGROUND_MODEL_ERROR:
|
| 2061 |
+
return None, f"neural foreground model failed: {FOREGROUND_MODEL_ERROR}"
|
| 2062 |
+
if FOREGROUND_MODEL is None and REQUIRE_PRIMARY_IMAGE_MODEL:
|
| 2063 |
+
return None, "neural foreground model is not initialized on this runtime"
|
| 2064 |
+
|
| 2065 |
try:
|
| 2066 |
import torch
|
| 2067 |
|
|
|
|
| 2068 |
attempts = 1 if is_background else PRIMARY_SPRITE_ATTEMPTS
|
| 2069 |
width, height = primary_generation_dimensions(spec)
|
| 2070 |
last_failure_detail = "the last output did not contain one valid foreground subject"
|
|
|
|
| 2081 |
width=width,
|
| 2082 |
height=height,
|
| 2083 |
).images[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2084 |
if is_background:
|
| 2085 |
+
content = polish_diffusion_asset(image, spec)
|
| 2086 |
return content, None
|
| 2087 |
+
if FOREGROUND_MODEL is not None:
|
| 2088 |
+
content = polish_neural_cutout(neural_foreground_cutout(image), spec)
|
| 2089 |
+
else:
|
| 2090 |
+
# Local-development compatibility only. The public fail-closed
|
| 2091 |
+
# runtime requires BiRefNet and cannot enter this branch.
|
| 2092 |
+
if has_invalid_cutout_studio_field(image):
|
| 2093 |
+
last_failure_detail = "the last output did not use the required plain light studio field"
|
| 2094 |
+
continue
|
| 2095 |
+
content = polish_diffusion_asset(image, spec)
|
| 2096 |
|
| 2097 |
subject_count = estimated_foreground_subject_count(content)
|
| 2098 |
if subject_count != 1:
|
|
|
|
| 2121 |
# here keeps the weights resident between decorated requests instead of paying the
|
| 2122 |
# model-load cost on every generation.
|
| 2123 |
initialize_primary_image_model()
|
| 2124 |
+
initialize_foreground_model()
|
| 2125 |
|
| 2126 |
|
| 2127 |
def controlnet_background_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
|
|
|
|
| 2220 |
return out.getvalue()
|
| 2221 |
|
| 2222 |
|
| 2223 |
+
def polish_neural_cutout(image: Image.Image, spec: AssetSpec) -> bytes:
|
| 2224 |
+
"""Resize and normalize a neural alpha cutout without changing its generated RGB art."""
|
| 2225 |
+
contained = image.convert("RGBA")
|
| 2226 |
+
contained.thumbnail((spec.width, spec.height), Image.LANCZOS)
|
| 2227 |
+
canvas = Image.new("RGBA", (spec.width, spec.height), (0, 0, 0, 0))
|
| 2228 |
+
offset = ((spec.width - contained.width) // 2, (spec.height - contained.height) // 2)
|
| 2229 |
+
canvas.alpha_composite(contained, dest=offset)
|
| 2230 |
+
if spec.composition in {"single_subject", "icon", "animation_frame"}:
|
| 2231 |
+
canvas = normalize_sprite_foreground(canvas, spec)
|
| 2232 |
+
out = io.BytesIO()
|
| 2233 |
+
canvas.save(out, format="PNG")
|
| 2234 |
+
return out.getvalue()
|
| 2235 |
+
|
| 2236 |
+
|
| 2237 |
def has_invalid_cutout_studio_field(image: Image.Image) -> bool:
|
| 2238 |
"""Reject model scenes before alpha extraction when their outer field is not light and neutral.
|
| 2239 |
|
|
|
|
| 2621 |
if USE_PRIMARY_IMAGE_MODEL:
|
| 2622 |
png_content, error = primary_diffusion_png(spec, index, run_id)
|
| 2623 |
source = f"{PRIMARY_IMAGE_MODEL} + {PRIMARY_LORA_MODEL} on ZeroGPU"
|
| 2624 |
+
if not is_background:
|
| 2625 |
+
source += f" + {FOREGROUND_MODEL_ID} neural alpha"
|
| 2626 |
if error:
|
| 2627 |
errors.append(f"primary ZeroGPU diffusion: {error}")
|
| 2628 |
if REQUIRE_PRIMARY_IMAGE_MODEL:
|
| 2629 |
raise RuntimeError(
|
| 2630 |
+
"The neural image pipeline is required, but diffusion generation or foreground extraction failed. "
|
| 2631 |
f"No procedural image was returned. Details: {error}"
|
| 2632 |
)
|
| 2633 |
|
|
|
|
| 2873 |
sprite_chain = []
|
| 2874 |
primary_source = f"{PRIMARY_IMAGE_MODEL} + {PRIMARY_LORA_MODEL} on ZeroGPU"
|
| 2875 |
if USE_PRIMARY_IMAGE_MODEL:
|
| 2876 |
+
sprite_chain.append(f"direct text-to-image {primary_source} → neural alpha {FOREGROUND_MODEL_ID}")
|
| 2877 |
if USE_DIFFUSION_FOR_SPRITES:
|
| 2878 |
sprite_chain.append(FREE_IMAGE_MODEL)
|
| 2879 |
if USE_HF_IMAGE_PROVIDER:
|
|
|
|
| 2904 |
neural_models = []
|
| 2905 |
if USE_PRIMARY_IMAGE_MODEL:
|
| 2906 |
neural_models.append(f"{PRIMARY_IMAGE_MODEL} + {PRIMARY_LORA_MODEL}")
|
| 2907 |
+
neural_models.append(FOREGROUND_MODEL_ID)
|
| 2908 |
if USE_DIFFUSION_FOR_SPRITES or USE_DIFFUSION_FOR_BACKGROUNDS:
|
| 2909 |
neural_models.append(FREE_IMAGE_MODEL)
|
| 2910 |
if USE_CONTROLNET_FOR_BACKGROUNDS:
|
|
|
|
| 2912 |
neural_models.extend(remote_sources)
|
| 2913 |
neural_status = ", ".join(dict.fromkeys(neural_models)) if neural_models else "none active—procedural code only"
|
| 2914 |
enforcement = "required; generation stops on model failure" if REQUIRE_PRIMARY_IMAGE_MODEL else "development fallback allowed"
|
| 2915 |
+
if PRIMARY_MODEL_ERROR or FOREGROUND_MODEL_ERROR:
|
| 2916 |
+
readiness = f"startup error: {PRIMARY_MODEL_ERROR or FOREGROUND_MODEL_ERROR}"
|
| 2917 |
+
elif PRIMARY_TEXT_PIPE is not None and FOREGROUND_MODEL is not None:
|
| 2918 |
+
readiness = "diffusion and neural foreground models loaded on ZeroGPU"
|
| 2919 |
else:
|
| 2920 |
readiness = "loads in the deployed ZeroGPU runtime"
|
| 2921 |
return (
|
|
|
|
| 2926 |
f"neural enforcement: `{enforcement}` · primary readiness: `{readiness}`. "
|
| 2927 |
"Every production image is generated directly from its written prompt by the primary text-to-image model. "
|
| 2928 |
"The user-defined composition and camera contract is placed first inside a conservative CLIP prompt budget. "
|
| 2929 |
+
f"Transparent single-subject attempts use `{FOREGROUND_MODEL_ID}` for neural alpha extraction, are normalized to the requested canvas, then pass configured subject-count, "
|
| 2930 |
"retained-scene, silhouette, and scale validation; failures are regenerated with a new seed up to "
|
| 2931 |
f"{PRIMARY_SPRITE_ATTEMPTS} times. No procedural guide is supplied to the model. The procedural renderer is "
|
| 2932 |
"development-only and is blocked in the deployed Space when the primary model fails."
|
requirements.txt
CHANGED
|
@@ -1,8 +1,13 @@
|
|
| 1 |
Pillow
|
| 2 |
huggingface_hub
|
| 3 |
torch==2.8.0
|
|
|
|
| 4 |
diffusers>=0.35.1,<0.38
|
| 5 |
transformers>=4.48,<6
|
| 6 |
accelerate>=1.2,<2
|
| 7 |
peft>=0.14,<1
|
| 8 |
safetensors>=0.4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
Pillow
|
| 2 |
huggingface_hub
|
| 3 |
torch==2.8.0
|
| 4 |
+
torchvision==0.23.0
|
| 5 |
diffusers>=0.35.1,<0.38
|
| 6 |
transformers>=4.48,<6
|
| 7 |
accelerate>=1.2,<2
|
| 8 |
peft>=0.14,<1
|
| 9 |
safetensors>=0.4
|
| 10 |
+
numpy<3
|
| 11 |
+
timm>=1.0,<2
|
| 12 |
+
kornia>=0.8,<1
|
| 13 |
+
einops>=0.8,<1
|