LeafCat79 commited on
Commit
0758bcb
·
verified ·
1 Parent(s): daff9c6

Reject miniature duplicate sprites

Browse files
Files changed (1) hide show
  1. app.py +60 -18
app.py CHANGED
@@ -1756,7 +1756,7 @@ def primary_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[byt
1756
  is_background = is_background_spec(spec)
1757
  attempts = 1 if is_background else PRIMARY_SPRITE_ATTEMPTS
1758
  width, height = (1344, 768) if is_background else (768, 1024)
1759
- last_component_count = 0
1760
  for attempt in range(attempts):
1761
  seed = abs(hash(f"primary|{spec.role}|{spec.prompt}|{index}|{run_id}|{attempt}")) % 2147483647
1762
  generator = torch.Generator(device="cuda").manual_seed(seed)
@@ -1774,15 +1774,19 @@ def primary_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[byt
1774
  if is_background:
1775
  return content, None
1776
 
1777
- last_component_count = estimated_foreground_subject_count(content)
1778
- if last_component_count == 1:
1779
- return content, None
1780
-
1781
- if last_component_count > 1:
1782
- detail = f"the last output contained {last_component_count} significant foreground subjects"
1783
- else:
1784
- detail = "the last output did not contain one significant foreground subject"
1785
- return None, f"single-subject validation failed after {attempts} model attempts; {detail}"
 
 
 
 
1786
  except Exception as exc:
1787
  return None, short_error(exc)
1788
 
@@ -1886,13 +1890,13 @@ def polish_diffusion_asset(image: Image.Image, spec: AssetSpec) -> bytes:
1886
  return out.getvalue()
1887
 
1888
 
1889
- def significant_foreground_component_areas(content: bytes) -> list[int]:
1890
- """Return material alpha components, ignoring small detached details and cleanup noise."""
1891
  image = Image.open(io.BytesIO(content)).convert("RGBA")
1892
  width, height = image.size
1893
  alpha = image.getchannel("A").tobytes()
1894
  foreground = bytearray(1 if value >= 64 else 0 for value in alpha)
1895
- areas: list[int] = []
1896
 
1897
  for start in range(width * height):
1898
  if not foreground[start]:
@@ -1900,11 +1904,17 @@ def significant_foreground_component_areas(content: bytes) -> list[int]:
1900
  foreground[start] = 0
1901
  stack = [start]
1902
  area = 0
 
 
1903
  while stack:
1904
  current = stack.pop()
1905
  area += 1
1906
  x = current % width
1907
  y = current // width
 
 
 
 
1908
  for dy in (-1, 0, 1):
1909
  ny = y + dy
1910
  if ny < 0 or ny >= height:
@@ -1919,13 +1929,25 @@ def significant_foreground_component_areas(content: bytes) -> list[int]:
1919
  if foreground[neighbor]:
1920
  foreground[neighbor] = 0
1921
  stack.append(neighbor)
1922
- areas.append(area)
 
 
 
 
 
 
 
 
 
1923
 
1924
- if not areas:
1925
  return []
1926
- largest = max(areas)
1927
- minimum = max(64, int(largest * 0.22), int(width * height * 0.005))
1928
- return sorted((area for area in areas if area >= minimum), reverse=True)
 
 
 
1929
 
1930
 
1931
  def has_deep_vertical_foreground_valley(content: bytes) -> bool:
@@ -1978,6 +2000,24 @@ def estimated_foreground_subject_count(content: bytes) -> int:
1978
  return 2 if has_deep_vertical_foreground_valley(content) else 1
1979
 
1980
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1981
  def free_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
1982
  global FREE_DIFFUSION_PIPE, FREE_DIFFUSION_ERROR
1983
  if FREE_DIFFUSION_ERROR:
@@ -2228,6 +2268,8 @@ def validate_asset_png(content: bytes, spec: AssetSpec) -> list[str]:
2228
  warnings.append("sprite has no significant foreground subject")
2229
  elif subject_count > 1:
2230
  warnings.append(f"sprite contains {subject_count} significant foreground subjects")
 
 
2231
  return warnings
2232
 
2233
 
 
1756
  is_background = is_background_spec(spec)
1757
  attempts = 1 if is_background else PRIMARY_SPRITE_ATTEMPTS
1758
  width, height = (1344, 768) if is_background else (768, 1024)
1759
+ last_failure_detail = "the last output did not contain one valid foreground subject"
1760
  for attempt in range(attempts):
1761
  seed = abs(hash(f"primary|{spec.role}|{spec.prompt}|{index}|{run_id}|{attempt}")) % 2147483647
1762
  generator = torch.Generator(device="cuda").manual_seed(seed)
 
1774
  if is_background:
1775
  return content, None
1776
 
1777
+ subject_count = estimated_foreground_subject_count(content)
1778
+ if subject_count != 1:
1779
+ if subject_count > 1:
1780
+ last_failure_detail = f"the last output contained {subject_count} significant foreground subjects"
1781
+ else:
1782
+ last_failure_detail = "the last output did not contain one significant foreground subject"
1783
+ continue
1784
+ if has_implausibly_thin_foreground_subject(content, spec):
1785
+ last_failure_detail = "the last output contained an implausibly thin foreground silhouette"
1786
+ continue
1787
+ return content, None
1788
+
1789
+ return None, f"sprite validation failed after {attempts} model attempts; {last_failure_detail}"
1790
  except Exception as exc:
1791
  return None, short_error(exc)
1792
 
 
1890
  return out.getvalue()
1891
 
1892
 
1893
+ def foreground_component_geometry(content: bytes) -> list[tuple[int, tuple[int, int, int, int]]]:
1894
+ """Return alpha-component areas and bounding boxes, largest first."""
1895
  image = Image.open(io.BytesIO(content)).convert("RGBA")
1896
  width, height = image.size
1897
  alpha = image.getchannel("A").tobytes()
1898
  foreground = bytearray(1 if value >= 64 else 0 for value in alpha)
1899
+ components: list[tuple[int, tuple[int, int, int, int]]] = []
1900
 
1901
  for start in range(width * height):
1902
  if not foreground[start]:
 
1904
  foreground[start] = 0
1905
  stack = [start]
1906
  area = 0
1907
+ min_x = max_x = start % width
1908
+ min_y = max_y = start // width
1909
  while stack:
1910
  current = stack.pop()
1911
  area += 1
1912
  x = current % width
1913
  y = current // width
1914
+ min_x = min(min_x, x)
1915
+ max_x = max(max_x, x)
1916
+ min_y = min(min_y, y)
1917
+ max_y = max(max_y, y)
1918
  for dy in (-1, 0, 1):
1919
  ny = y + dy
1920
  if ny < 0 or ny >= height:
 
1929
  if foreground[neighbor]:
1930
  foreground[neighbor] = 0
1931
  stack.append(neighbor)
1932
+ components.append((area, (min_x, min_y, max_x, max_y)))
1933
+
1934
+ return sorted(components, reverse=True)
1935
+
1936
+
1937
+ def significant_foreground_component_areas(content: bytes) -> list[int]:
1938
+ """Return material alpha components, ignoring only genuinely small detached details."""
1939
+ image = Image.open(io.BytesIO(content)).convert("RGBA")
1940
+ width, height = image.size
1941
+ components = foreground_component_geometry(content)
1942
 
1943
+ if not components:
1944
  return []
1945
+ largest = components[0][0]
1946
+ # A generated miniature duplicate can be much smaller than the primary view.
1947
+ # Keep a small absolute/noise floor, but do not discard material components
1948
+ # such as the reported 96px duplicate next to a 487px main silhouette.
1949
+ minimum = max(64, int(largest * 0.12), int(width * height * 0.004))
1950
+ return [area for area, _ in components if area >= minimum]
1951
 
1952
 
1953
  def has_deep_vertical_foreground_valley(content: bytes) -> bool:
 
2000
  return 2 if has_deep_vertical_foreground_valley(content) else 1
2001
 
2002
 
2003
+ def has_implausibly_thin_foreground_subject(content: bytes, spec: AssetSpec) -> bool:
2004
+ """Reject collapsed humanoid silhouettes without penalizing projectiles or narrow props."""
2005
+ if sprite_archetype(spec) not in ("humanoid", "enemy"):
2006
+ return False
2007
+ components = foreground_component_geometry(content)
2008
+ if not components:
2009
+ return False
2010
+ image = Image.open(io.BytesIO(content)).convert("RGBA")
2011
+ _, (x0, y0, x1, y1) = components[0]
2012
+ bbox_width = x1 - x0 + 1
2013
+ bbox_height = y1 - y0 + 1
2014
+ return (
2015
+ bbox_width < max(14, int(image.width * 0.12))
2016
+ and bbox_height >= int(image.height * 0.28)
2017
+ and bbox_height >= bbox_width * 3.5
2018
+ )
2019
+
2020
+
2021
  def free_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
2022
  global FREE_DIFFUSION_PIPE, FREE_DIFFUSION_ERROR
2023
  if FREE_DIFFUSION_ERROR:
 
2268
  warnings.append("sprite has no significant foreground subject")
2269
  elif subject_count > 1:
2270
  warnings.append(f"sprite contains {subject_count} significant foreground subjects")
2271
+ elif has_implausibly_thin_foreground_subject(content, spec):
2272
+ warnings.append("sprite foreground silhouette is implausibly thin")
2273
  return warnings
2274
 
2275