Add a script I used to check that my tag spacing changes
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
This commit is contained in:
118
scripts/chatgpt-check-offset-diff.py
Normal file
118
scripts/chatgpt-check-offset-diff.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from PIL import Image, ImageChops
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
|
||||||
|
def load_rgb(path: Path) -> Image.Image:
|
||||||
|
return Image.open(path).convert("RGB")
|
||||||
|
|
||||||
|
def mse(img: Image.Image) -> float:
|
||||||
|
hist = img.histogram()
|
||||||
|
sq = sum(v * ((i % 256) ** 2) for i, v in enumerate(hist))
|
||||||
|
return sq / (img.width * img.height * 3)
|
||||||
|
|
||||||
|
def crop_pair(expected, actual, shift):
|
||||||
|
# shift > 0 means actual content is lower than expected.
|
||||||
|
w = min(expected.width, actual.width)
|
||||||
|
|
||||||
|
if shift >= 0:
|
||||||
|
e_box = (0, 0, w, min(expected.height, actual.height - shift))
|
||||||
|
a_box = (0, shift, w, shift + (e_box[3] - e_box[1]))
|
||||||
|
else:
|
||||||
|
s = -shift
|
||||||
|
a_box = (0, 0, w, min(actual.height, expected.height - s))
|
||||||
|
e_box = (0, s, w, s + (a_box[3] - a_box[1]))
|
||||||
|
|
||||||
|
return expected.crop(e_box), actual.crop(a_box)
|
||||||
|
|
||||||
|
def best_vertical_shift(expected, actual, max_shift=100):
|
||||||
|
best = None
|
||||||
|
|
||||||
|
for shift in range(18, 19):
|
||||||
|
e, a = crop_pair(expected, actual, shift)
|
||||||
|
if e.height <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
score = mse(ImageChops.difference(e, a))
|
||||||
|
if best is None or score < best[1]:
|
||||||
|
best = (shift, score)
|
||||||
|
|
||||||
|
return best
|
||||||
|
|
||||||
|
def changed_until_y(expected, actual, threshold=5, min_changed_pixels=10):
|
||||||
|
diff = ImageChops.difference(expected, actual)
|
||||||
|
last_changed = None
|
||||||
|
|
||||||
|
for y in range(diff.height):
|
||||||
|
row = diff.crop((0, y, diff.width, y + 1)).convert("L")
|
||||||
|
changed = sum(1 for px in row.getdata() if px > threshold)
|
||||||
|
|
||||||
|
if changed >= min_changed_pixels:
|
||||||
|
last_changed = y
|
||||||
|
|
||||||
|
return {
|
||||||
|
"last_changed_y": last_changed,
|
||||||
|
"clean_after_y": 0 if last_changed is None else last_changed + 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
def find_pairs(root: Path):
|
||||||
|
expected_files = sorted(root.rglob("*-expected.png"))
|
||||||
|
|
||||||
|
for expected in expected_files:
|
||||||
|
actual = expected.with_name(
|
||||||
|
expected.name.replace("-expected.png", "-actual.png")
|
||||||
|
)
|
||||||
|
if actual.exists():
|
||||||
|
yield expected, actual
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("root", type=Path, nargs="?", default=Path("test-results"))
|
||||||
|
ap.add_argument("--max-shift", type=int, default=100)
|
||||||
|
ap.add_argument("--make-diffs", action="store_true")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
failed = False
|
||||||
|
|
||||||
|
for expected_path, actual_path in find_pairs(args.root):
|
||||||
|
expected = load_rgb(expected_path)
|
||||||
|
actual = load_rgb(actual_path)
|
||||||
|
|
||||||
|
raw_score = mse(ImageChops.difference(
|
||||||
|
expected.crop((0, 0, min(expected.width, actual.width), min(expected.height, actual.height))),
|
||||||
|
actual.crop((0, 0, min(expected.width, actual.width), min(expected.height, actual.height))),
|
||||||
|
))
|
||||||
|
|
||||||
|
shift, shifted_score = best_vertical_shift(
|
||||||
|
expected, actual, max_shift=args.max_shift
|
||||||
|
)
|
||||||
|
|
||||||
|
status = "OK" if shifted_score < raw_score * 0.05 else "CHECK"
|
||||||
|
if status == "CHECK":
|
||||||
|
failed = True
|
||||||
|
|
||||||
|
rel = expected_path.relative_to(args.root)
|
||||||
|
print(f"{status} {rel}")
|
||||||
|
print(f" expected: {expected.size}, actual: {actual.size}")
|
||||||
|
print(f" best shift: {shift:+d}px")
|
||||||
|
print(f" raw mse: {raw_score:.3f}")
|
||||||
|
print(f" shifted mse: {shifted_score:.3f}")
|
||||||
|
|
||||||
|
if args.make_diffs:
|
||||||
|
e, a = crop_pair(expected, actual, shift)
|
||||||
|
change_info = changed_until_y(e, a, threshold=2)
|
||||||
|
print(f" last changed y after shift: {change_info['last_changed_y']}")
|
||||||
|
print(f" clean after y after shift: {change_info['clean_after_y']}")
|
||||||
|
diff = ImageChops.difference(e, a)
|
||||||
|
out = expected_path.with_name(
|
||||||
|
expected_path.name.replace("-expected.png", f"-shift-{shift:+d}-diff.png")
|
||||||
|
)
|
||||||
|
diff.save(out)
|
||||||
|
print(f" wrote: {out}")
|
||||||
|
|
||||||
|
raise SystemExit(1 if failed else 0)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user