mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-12 19:10:55 +01:00
Adds the route-independent spine of the planned art-generation bake-off: a harness that decides whether generated art is well-formed, so "non-sloppy" becomes machine-checkable instead of a judgement call. Findings that shaped the design, all measured rather than assumed: - Integer-scale invariance separates true pixel art (impurity 0.0000) from interpolated fakes (1.0000) exactly -- but passes trivially at scale 1, so it must never be trusted alone on native-resolution output. - Gate ORDER is load-bearing. A soft-alpha image has no fully-opaque pixels, so an opaque-only colour count returns 0 and silently passes. ALPHA_NOT_BINARY now runs first and COLOR_COUNT fails closed on an empty opaque set. - Counting must be over opaque RGB only: transparent pixels retain arbitrary RGB and inflated a naive RGBA count, rejecting correct art at 18/16. - Declaring a gate was not enough to run it. hand_painted.toml shipped declaring SILHOUETTE_LOW_CONTRAST with no implementation, so it parsed, ran nothing, and reported a pass. load_spec now fails closed on unimplemented gates, result-only codes, and gates missing their config. Thresholds are calibrated against 250 genuine 32x32 CC0 icons, not guessed: p50=7 p90=13 p99=26 opaque colours; cap 32 rejects 0.0% of real art while raw generator output measured 34403 colours. An initial cap of 16 wrongly rejected a real DCSS icon -- re-derived from the corpus rather than nudged. Verified: 57 tests, 100% branch coverage, zero suppressions, all hooks pass. 58/60 (96.7%) of real CC0 icons pass the full stack; both failures confirmed true positives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T9rQ12yZ7sJxiPQTCS5dUF
90 lines
3.2 KiB
Python
90 lines
3.2 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
|
|
|
|
from python_pkg.artgate.codes import Code
|
|
from python_pkg.artgate.contrast import (
|
|
DEFAULT_MIN_DELTA,
|
|
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 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]
|