Spaces:
Running on Zero
Running on Zero
Reject miniature duplicate sprites
Browse files
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 |
-
|
| 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 |
-
|
| 1778 |
-
if
|
| 1779 |
-
|
| 1780 |
-
|
| 1781 |
-
|
| 1782 |
-
|
| 1783 |
-
|
| 1784 |
-
|
| 1785 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 1890 |
-
"""Return
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1923 |
|
| 1924 |
-
if not
|
| 1925 |
return []
|
| 1926 |
-
largest =
|
| 1927 |
-
|
| 1928 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
|