LeafCat79 commited on
Commit
7541208
·
verified ·
1 Parent(s): b1b939d

Reject scene-contaminated transparent assets

Browse files
Files changed (2) hide show
  1. README.md +2 -2
  2. app.py +52 -4
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 receive alpha extraction, occupancy normalization, and subject-count checks. 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
 
@@ -89,7 +89,7 @@ segmind/SSD-1B + latent-consistency/lcm-lora-ssd-1b
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 model. Sprite prompts use a portrait canvas and positive single-subject composition language; the negative prompt separately rejects sheets, lineups, repeated subjects, and multiple views. After transparency extraction, sprites with zero or multiple significant foreground components 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. Background negative prompts reject characters and generic particle overlays.
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
 
 
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 alpha extraction, 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
 
 
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 model. 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. After 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.
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
 
app.py CHANGED
@@ -1851,7 +1851,8 @@ def diffusion_negative_prompt(spec: AssetSpec) -> str:
1851
  return (
1852
  "multiple subjects, duplicate character, duplicate subject, repeated subject, character sheet, model sheet, turnaround, "
1853
  "lineup, alternate views, multiple poses, multiple views, pair, group, cropped subject, scenery, landscape, "
1854
- "room, ground, floor, background props, drop shadow, text, watermark"
 
1855
  + camera_negative
1856
  )
1857
  if spec.composition == "seamless":
@@ -1954,11 +1955,13 @@ def primary_diffusion_prompt(spec: AssetSpec) -> str:
1954
  camera = f"User-defined camera: {compact_prompt_words(spec.camera_instruction, 10)}."
1955
  variation = f"Distinct variation: {compact_prompt_words(spec.variation, 8)}. " if spec.variation else ""
1956
  if spec.composition == "single_subject":
1957
- description = compact_prompt_words(spec.prompt, 12 if variation else 18)
 
1958
  subject = (spec.group or spec.role).replace("_", " ")
1959
  return (
1960
  f"{camera} Single isolated {subject} subject. One complete body, one pose, centered and fully visible. "
1961
- f"Fill about eighty percent of the frame with modest even margins on a uniform white field. "
 
1962
  f"{variation}{description}"
1963
  )
1964
  description = compact_prompt_words(spec.prompt, 26 if variation else 34)
@@ -2024,6 +2027,9 @@ def primary_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[byt
2024
  else:
2025
  last_failure_detail = "the last output did not contain one significant foreground subject"
2026
  continue
 
 
 
2027
  if has_implausibly_thin_foreground_subject(content, spec):
2028
  last_failure_detail = "the last output contained an implausibly thin foreground silhouette"
2029
  continue
@@ -2339,6 +2345,46 @@ def has_undersized_foreground_subject(content: bytes) -> bool:
2339
  return max(width_ratio, height_ratio) < 0.68
2340
 
2341
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2342
  def free_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
2343
  global FREE_DIFFUSION_PIPE, FREE_DIFFUSION_ERROR
2344
  if FREE_DIFFUSION_ERROR:
@@ -2589,6 +2635,8 @@ def validate_asset_png(content: bytes, spec: AssetSpec) -> list[str]:
2589
  )
2590
  if any(value > 16 for value in corners):
2591
  warnings.append("transparent output has opaque corner pixels")
 
 
2592
  if spec.expected_subjects is not None:
2593
  subject_count = estimated_foreground_subject_count(content)
2594
  if subject_count != spec.expected_subjects:
@@ -2763,7 +2811,7 @@ def model_configuration_summary() -> str:
2763
  "Every production image is generated directly from its written prompt by the primary text-to-image model. "
2764
  "The user-defined composition and camera contract is placed first inside a conservative CLIP prompt budget. "
2765
  "Transparent single-subject foregrounds are normalized to the requested canvas, then pass configured subject-count, "
2766
- "silhouette, and scale validation; failures are regenerated with a new seed up to "
2767
  f"{PRIMARY_SPRITE_ATTEMPTS} times. No procedural guide is supplied to the model. The procedural renderer is "
2768
  "development-only and is blocked in the deployed Space when the primary model fails."
2769
  )
 
1851
  return (
1852
  "multiple subjects, duplicate character, duplicate subject, repeated subject, character sheet, model sheet, turnaround, "
1853
  "lineup, alternate views, multiple poses, multiple views, pair, group, cropped subject, scenery, landscape, "
1854
+ "environment, environmental framing, scenic background, surrounding landscape, ground plane, floor plane, "
1855
+ "background border, background props, drop shadow, text, watermark"
1856
  + camera_negative
1857
  )
1858
  if spec.composition == "seamless":
 
1955
  camera = f"User-defined camera: {compact_prompt_words(spec.camera_instruction, 10)}."
1956
  variation = f"Distinct variation: {compact_prompt_words(spec.variation, 8)}. " if spec.variation else ""
1957
  if spec.composition == "single_subject":
1958
+ description = compact_prompt_words(spec.prompt, 6 if variation else 10)
1959
+ variation = f"Variation: {compact_prompt_words(spec.variation, 4)}. " if spec.variation else ""
1960
  subject = (spec.group or spec.role).replace("_", " ")
1961
  return (
1962
  f"{camera} Single isolated {subject} subject. One complete body, one pose, centered and fully visible. "
1963
+ "Fill about eighty percent of the frame with modest even margins. "
1964
+ "Plain uniform white field only; no environment, scenery, ground plane, or surrounding border. "
1965
  f"{variation}{description}"
1966
  )
1967
  description = compact_prompt_words(spec.prompt, 26 if variation else 34)
 
2027
  else:
2028
  last_failure_detail = "the last output did not contain one significant foreground subject"
2029
  continue
2030
+ if has_scene_like_foreground_frame(content):
2031
+ last_failure_detail = "the last output retained a surrounding scene instead of an isolated subject"
2032
+ continue
2033
  if has_implausibly_thin_foreground_subject(content, spec):
2034
  last_failure_detail = "the last output contained an implausibly thin foreground silhouette"
2035
  continue
 
2345
  return max(width_ratio, height_ratio) < 0.68
2346
 
2347
 
2348
+ def has_scene_like_foreground_frame(content: bytes) -> bool:
2349
+ """Detect retained scenery spread around a nominally isolated transparent subject.
2350
+
2351
+ Alpha extraction can turn a model-generated environment into one connected
2352
+ foreground component, so subject counting alone cannot identify it. A clean
2353
+ cutout may reach one or two sides of its canvas, but retained scenery fills
2354
+ nearly every outer region around the center. The conservative 3x3 test avoids
2355
+ rejecting ordinary tall, wide, or circular silhouettes.
2356
+ """
2357
+ image = Image.open(io.BytesIO(content)).convert("RGBA")
2358
+ alpha = image.getchannel("A")
2359
+ width, height = image.size
2360
+ if width < 9 or height < 9:
2361
+ return False
2362
+
2363
+ occupied_regions = 0
2364
+ total_foreground = 0
2365
+ perimeter_regions = 0
2366
+ for row in range(3):
2367
+ y0 = round(row * height / 3)
2368
+ y1 = round((row + 1) * height / 3)
2369
+ for column in range(3):
2370
+ x0 = round(column * width / 3)
2371
+ x1 = round((column + 1) * width / 3)
2372
+ area = max(1, (x1 - x0) * (y1 - y0))
2373
+ foreground = sum(
2374
+ alpha.getpixel((x, y)) >= 64
2375
+ for y in range(y0, y1)
2376
+ for x in range(x0, x1)
2377
+ )
2378
+ total_foreground += foreground
2379
+ if row != 1 or column != 1:
2380
+ perimeter_regions += 1
2381
+ if foreground / area >= 0.28:
2382
+ occupied_regions += 1
2383
+
2384
+ foreground_ratio = total_foreground / max(1, width * height)
2385
+ return perimeter_regions == 8 and foreground_ratio >= 0.38 and occupied_regions >= 7
2386
+
2387
+
2388
  def free_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
2389
  global FREE_DIFFUSION_PIPE, FREE_DIFFUSION_ERROR
2390
  if FREE_DIFFUSION_ERROR:
 
2635
  )
2636
  if any(value > 16 for value in corners):
2637
  warnings.append("transparent output has opaque corner pixels")
2638
+ if spec.composition in {"single_subject", "icon", "animation_frame"} and has_scene_like_foreground_frame(content):
2639
+ warnings.append("transparent cutout retains a surrounding scene instead of an isolated subject")
2640
  if spec.expected_subjects is not None:
2641
  subject_count = estimated_foreground_subject_count(content)
2642
  if subject_count != spec.expected_subjects:
 
2811
  "Every production image is generated directly from its written prompt by the primary text-to-image model. "
2812
  "The user-defined composition and camera contract is placed first inside a conservative CLIP prompt budget. "
2813
  "Transparent single-subject foregrounds are normalized to the requested canvas, then pass configured subject-count, "
2814
+ "retained-scene, silhouette, and scale validation; failures are regenerated with a new seed up to "
2815
  f"{PRIMARY_SPRITE_ATTEMPTS} times. No procedural guide is supplied to the model. The procedural renderer is "
2816
  "development-only and is blocked in the deployed Space when the primary model fails."
2817
  )