Add background stripping and cleaned badge fixture
This commit is contained in:
parent
91fbbf1e7d
commit
4b791e511c
14 changed files with 771 additions and 60 deletions
14
tests/fixtures/real/ai-badge-cleaned.json
vendored
Normal file
14
tests/fixtures/real/ai-badge-cleaned.json
vendored
Normal 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
BIN
tests/fixtures/real/ai-badge-cleaned.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 955 KiB |
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
[
|
||||
|
|
|
|||
|
|
@ -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
42
tests/test_preprocess.py
Normal 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])
|
||||
Loading…
Reference in a new issue