Add background stripping and cleaned badge fixture

This commit is contained in:
Meta Vens 2026-04-22 06:55:59 +01:00
commit 4b791e511c
14 changed files with 771 additions and 60 deletions

View file

@ -0,0 +1,14 @@
{
"id": "ai-badge-cleaned",
"kind": "real-ai-generated-emblem",
"description": "Manually cleaned transparent version of the AI badge emblem used as a real-image regression fixture.",
"source_image": "C:\\Users\\Meta\\.codex\\generated_images\\019db22f-5487-7a31-8a67-45f0c801dd7c\\ig_0b9862296469b4740169e7fbc657ac81919d4d2267673f6bae.png",
"revision": 3,
"notes": [
"Use this version when evaluating lattice inference and thin-feature preservation without the baked checkerboard background contaminating results.",
"Primary failure region is the sword tip and nearby outline taper, plus other sharp single-pixel and near-single-pixel features at mixed orientations.",
"Keep the raw checkerboard-background version around separately when testing the automatic background stripper.",
"Revision 2 reapplies transparency as a raster mask and thresholds that mask to suppress subtle dark-background glow left by the earlier cleanup.",
"Revision 3 binarizes the repo fixture copy to strict 0/255 alpha so the regression asset stays unambiguous."
]
}

BIN
tests/fixtures/real/ai-badge-cleaned.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 955 KiB

View file

@ -16,3 +16,14 @@ def test_default_command_runs_without_explicit_run_subcommand(tmp_path: Path) ->
exit_code = main([str(input_path), "--out", str(output_path), "--steps", "8"])
assert exit_code == 0
assert output_path.exists()
def test_default_command_accepts_strip_background_flag(tmp_path: Path) -> None:
source = make_emblem(16, 16)
fake = fake_pixelize(source, upscale=8, phase_x=0.2, phase_y=0.25, blur_radius=0.4)
input_path = tmp_path / "input.png"
output_path = tmp_path / "output.png"
save_rgba(input_path, fake)
exit_code = main([str(input_path), "--out", str(output_path), "--steps", "0", "--strip-background"])
assert exit_code == 0
assert output_path.exists()

View file

@ -5,6 +5,8 @@ import numpy as np
from repixelizer.metrics import (
exact_match_ratio,
foreground_adjacency_error,
foreground_edge_concentration,
foreground_edge_position_error,
foreground_exact_match_ratio,
foreground_motif_error,
foreground_reconstruction_error,
@ -66,6 +68,35 @@ def test_texture_metrics_track_adjacency_and_local_motifs() -> None:
assert foreground_motif_error(original, same_colors_wrong_structure) > 0.0
def test_edge_position_metric_prefers_crisp_shift_to_blurry_smear() -> None:
original = np.zeros((7, 7, 4), dtype=np.float32)
original[1:6, 3] = np.asarray([1.0, 1.0, 1.0, 1.0], dtype=np.float32)
shifted = np.zeros_like(original)
shifted[1:6, 4] = np.asarray([1.0, 1.0, 1.0, 1.0], dtype=np.float32)
blurred = np.zeros_like(original)
blurred[1:6, 2] = np.asarray([0.4, 0.4, 0.4, 1.0], dtype=np.float32)
blurred[1:6, 3] = np.asarray([0.7, 0.7, 0.7, 1.0], dtype=np.float32)
blurred[1:6, 4] = np.asarray([0.4, 0.4, 0.4, 1.0], dtype=np.float32)
assert foreground_edge_position_error(original, original) == 0.0
assert foreground_edge_position_error(original, shifted) > 0.0
assert foreground_edge_position_error(original, blurred) > foreground_edge_position_error(original, shifted)
def test_edge_concentration_prefers_crisp_edges_to_blurry_smear() -> None:
shifted = np.zeros((7, 7, 4), dtype=np.float32)
shifted[1:6, 4] = np.asarray([1.0, 1.0, 1.0, 1.0], dtype=np.float32)
blurred = np.zeros_like(shifted)
blurred[1:6, 2] = np.asarray([0.4, 0.4, 0.4, 1.0], dtype=np.float32)
blurred[1:6, 3] = np.asarray([0.7, 0.7, 0.7, 1.0], dtype=np.float32)
blurred[1:6, 4] = np.asarray([0.4, 0.4, 0.4, 1.0], dtype=np.float32)
assert foreground_edge_concentration(shifted) > foreground_edge_concentration(blurred)
def test_source_lattice_consistency_prefers_matching_cell_structure() -> None:
lowres = np.asarray(
[

View file

@ -74,6 +74,30 @@ def test_pipeline_preserves_transparency_for_sprite(tmp_path: Path) -> None:
assert partial.size == 0
def test_pipeline_can_strip_checkerboard_background(tmp_path: Path) -> None:
source = make_sprite(24, 24)
checker = np.ones_like(source)
checker[..., 3] = 1.0
for y in range(checker.shape[0]):
for x in range(checker.shape[1]):
tile = 0.97 if ((x // 4) + (y // 4)) % 2 == 0 else 0.91
checker[y, x, :3] = tile
alpha = source[..., 3:4]
opaque_input = checker.copy()
opaque_input[..., :3] = source[..., :3] * alpha + checker[..., :3] * (1.0 - alpha)
input_path = tmp_path / "input.png"
output_path = tmp_path / "output.png"
from repixelizer.io import save_rgba
save_rgba(input_path, opaque_input)
result = run_pipeline(input_path, output_path, target_size=24, steps=0, strip_background=True)
assert result.source_rgba[..., 3].min() < 0.05
assert result.output_rgba[..., 3].min() < 0.05
assert result.output_rgba[..., 3].max() > 0.9
def test_pipeline_can_emit_initialized_output_without_optimizer_steps(tmp_path: Path) -> None:
source = make_emblem(24, 24)
fake = fake_pixelize(source, upscale=10, phase_x=0.15, phase_y=0.2, blur_radius=0.6, warp_strength=0.2, warp_detail=5)

42
tests/test_preprocess.py Normal file
View file

@ -0,0 +1,42 @@
from __future__ import annotations
import numpy as np
from repixelizer.preprocess import strip_edge_background
def test_strip_edge_background_removes_light_checkerboard_and_preserves_subject() -> None:
rgba = np.ones((80, 80, 4), dtype=np.float32)
rgba[..., 3] = 1.0
for y in range(80):
for x in range(80):
tile = 0.97 if ((x // 4) + (y // 4)) % 2 == 0 else 0.91
rgba[y, x, :3] = tile
rgba[12:68, 10:70] = np.array([0.1, 0.7, 0.2, 1.0], dtype=np.float32)
for y in range(24, 56):
for x in range(26, 54):
tile = 0.97 if ((x // 4) + (y // 4)) % 2 == 0 else 0.91
rgba[y, x, :3] = tile
rgba[20:60, 16:20] = np.array([0.98, 0.97, 0.96, 1.0], dtype=np.float32)
stripped = strip_edge_background(rgba)
assert stripped[0, 0, 3] == 0.0
assert stripped[4, 4, 3] == 0.0
assert stripped[40, 40, 3] == 0.0
assert stripped[30, 18, 3] == 1.0
assert np.allclose(stripped[30, 18, :3], rgba[30, 18, :3])
assert stripped[16, 16, 3] == 1.0
def test_strip_edge_background_handles_trivial_monochrome_background() -> None:
rgba = np.zeros((32, 32, 4), dtype=np.float32)
rgba[..., :3] = np.array([0.2, 0.3, 0.8], dtype=np.float32)
rgba[..., 3] = 1.0
rgba[8:24, 8:24] = np.array([1.0, 0.6, 0.1, 1.0], dtype=np.float32)
stripped = strip_edge_background(rgba)
assert stripped[0, 0, 3] == 0.0
assert stripped[16, 16, 3] == 1.0
assert np.allclose(stripped[16, 16, :3], rgba[16, 16, :3])