mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 22:07:03 +01:00
Closes the remaining bake-off cells. The phase's real result was not a route
score but a MISSING GATE.
All twelve SDXL icons measured 100% opaque coverage -- square tiles with no
sprite silhouette -- and passed every gate in the harness. Gate-pass was
measuring pipeline hygiene, not usability. MARGIN_TOO_SMALL inverts the table
from a meaningless 12/12/12 tie to:
diffusion 0/12 (every output full-bleed)
vector 10/12
procedural 11/12
The two non-diffusion failures were true positives in MY shapes, not gate
error: the procedural sword pommel ran off the bottom edge and the vector meat
bone clipped the top. Both fixed; both routes back to 12/12.
Diffusion recovers to 12/12 with flood-fill background removal, but visual
inspection shows the fill eats into the art (bone reduced to a fragment, sword
nearly erased). Reported as inadequate rather than shipped -- proper matting is
needed before route (b) is a usable pipeline.
Blender exits 0 even when its embedded script raises: BLENDER_EEVEE_NEXT does
not exist in 5.2, and the failure produced a clean exit and no file. render()
now asserts the output exists, or the route would report phantom successes.
Model gating verified at execution time, never recalled: SDXL base and
nerijs/pixel-art-xl are ungated, FLUX.1-dev is gated=auto, and
runwayml/stable-diffusion-v1-5 now 307-redirects.
The finishing stage now generalises across three continuous-tone sources
(SVG raster, Blender render, SDXL): each is 0/N raw and N/N finished.
Verified: 114 tests, 100% branch coverage on 13 modules, zero suppressions,
all hooks pass. Note: a local pytest run masked a missing branch that the
pre-commit hook caught -- the hook's run is authoritative.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9rQ12yZ7sJxiPQTCS5dUF
136 lines
4.9 KiB
Python
136 lines
4.9 KiB
Python
"""Silhouette-contrast tests, with a calibration control pair.
|
|
|
|
The threshold is justified by controls rather than taste: a near-white subject
|
|
scores 5.0 against white and must fail; a mid-grey subject scores ~128 against
|
|
both and must pass. A gate whose threshold has no controls is a bug waiting to
|
|
reject good art.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from python_pkg.artgate.codes import Code
|
|
from python_pkg.artgate.contrast import (
|
|
DEFAULT_MIN_DELTA,
|
|
check_margin,
|
|
check_silhouette,
|
|
silhouette_delta,
|
|
)
|
|
|
|
|
|
def _solid(value: int, alpha: int = 255, size: int = 8) -> np.ndarray:
|
|
"""Build a uniform RGBA square.
|
|
|
|
Args:
|
|
value: Grey level for all channels.
|
|
alpha: Alpha for every pixel.
|
|
size: Square edge length.
|
|
|
|
Returns:
|
|
An ``(size, size, 4)`` uint8 array.
|
|
"""
|
|
arr = np.zeros((size, size, 4), dtype=np.uint8)
|
|
arr[:, :, :3] = value
|
|
arr[:, :, 3] = alpha
|
|
return arr
|
|
|
|
|
|
class TestCalibrationControls:
|
|
"""Provably-failing and provably-passing inputs bracket the threshold."""
|
|
|
|
def test_near_white_fails_against_light(self) -> None:
|
|
"""A near-white subject is invisible on white and must fail."""
|
|
arr = _solid(250)
|
|
assert silhouette_delta(arr, 255) < DEFAULT_MIN_DELTA
|
|
assert check_silhouette(arr) == [Code.SILHOUETTE_LOW_CONTRAST]
|
|
|
|
def test_near_black_fails_against_dark(self) -> None:
|
|
"""A near-black subject is invisible on black and must fail."""
|
|
arr = _solid(4)
|
|
assert silhouette_delta(arr, 0) < DEFAULT_MIN_DELTA
|
|
assert check_silhouette(arr) == [Code.SILHOUETTE_LOW_CONTRAST]
|
|
|
|
def test_mid_grey_passes_both(self) -> None:
|
|
"""A mid-grey subject reads on both backgrounds and must pass."""
|
|
arr = _solid(128)
|
|
assert check_silhouette(arr) == []
|
|
|
|
def test_both_backgrounds_are_required(self) -> None:
|
|
"""Passing one background is not sufficient.
|
|
|
|
Near-white separates hugely from black (250) yet still fails, which is
|
|
exactly the case a single-background check would wave through.
|
|
"""
|
|
arr = _solid(250)
|
|
assert silhouette_delta(arr, 0) == 250.0
|
|
assert check_silhouette(arr) == [Code.SILHOUETTE_LOW_CONTRAST]
|
|
|
|
|
|
class TestMargin:
|
|
"""A sprite needs a clear border; a square tile is not an item icon.
|
|
|
|
Regression: twelve SDXL icons measured 100% opaque coverage and passed
|
|
every other gate, because nothing checked whether the asset had a
|
|
silhouette at all. Gate-pass measured hygiene, not usability.
|
|
"""
|
|
|
|
def test_full_bleed_is_rejected(self) -> None:
|
|
"""A fully-opaque canvas has no silhouette and must fail."""
|
|
assert check_margin(_solid(128), 1) == [Code.MARGIN_TOO_SMALL]
|
|
|
|
def test_clear_border_passes(self) -> None:
|
|
"""Content inset from every edge passes."""
|
|
arr = _solid(128, alpha=0)
|
|
arr[2:6, 2:6, 3] = 255
|
|
assert check_margin(arr, 1) == []
|
|
|
|
@pytest.mark.parametrize("edge", ["top", "bottom", "left", "right"])
|
|
def test_any_touched_edge_is_rejected(self, edge: str) -> None:
|
|
"""Touching a single edge is enough to fail."""
|
|
arr = _solid(128, alpha=0)
|
|
arr[2:6, 2:6, 3] = 255
|
|
index = {
|
|
"top": (0, 3),
|
|
"bottom": (-1, 3),
|
|
"left": (3, 0),
|
|
"right": (3, -1),
|
|
}[edge]
|
|
arr[index[0], index[1], 3] = 255
|
|
assert check_margin(arr, 1) == [Code.MARGIN_TOO_SMALL]
|
|
|
|
def test_zero_margin_disables_the_gate(self) -> None:
|
|
"""A style that permits full bleed opts out explicitly."""
|
|
assert check_margin(_solid(128), 0) == []
|
|
|
|
def test_wider_margin_is_stricter(self) -> None:
|
|
"""A 2px requirement rejects art that only clears 1px."""
|
|
arr = _solid(128, alpha=0)
|
|
arr[1:7, 1:7, 3] = 255
|
|
assert check_margin(arr, 1) == []
|
|
assert check_margin(arr, 2) == [Code.MARGIN_TOO_SMALL]
|
|
|
|
|
|
class TestEdgeCases:
|
|
"""Degenerate inputs fail closed rather than dividing by zero."""
|
|
|
|
def test_fully_transparent_is_empty_canvas(self) -> None:
|
|
"""An image that paints nothing reports EMPTY_CANVAS."""
|
|
assert check_silhouette(_solid(128, alpha=0)) == [Code.EMPTY_CANVAS]
|
|
|
|
def test_delta_is_zero_when_nothing_is_painted(self) -> None:
|
|
"""The alpha-weighted mean is defined for a blank image."""
|
|
assert silhouette_delta(_solid(128, alpha=0), 0) == 0.0
|
|
|
|
def test_alpha_weighting_ignores_transparent_margin(self) -> None:
|
|
"""A transparent margin does not drag the subject's luma."""
|
|
arr = _solid(250, size=8)
|
|
arr[4:, :, 3] = 0
|
|
assert silhouette_delta(arr, 255) == 5.0
|
|
|
|
def test_threshold_is_configurable(self) -> None:
|
|
"""A style may demand more separation than the default."""
|
|
arr = _solid(128)
|
|
assert check_silhouette(arr, min_delta=200.0) == [Code.SILHOUETTE_LOW_CONTRAST]
|