Download validate_page_measures.py from codepawl/loupe: direct link, hf CLI and curl.
- Browser
- Download file 3.25 kB
-
https://huggingface.co/codepawl/loupe/resolve/main/validate_page_measures.py
- Command line
-
hf download hf://codepawl/loupe/validate_page_measures.py
-
curl -L -o validate_page_measures.py https://huggingface.co/codepawl/loupe/resolve/main/validate_page_measures.py
3.25 kB
| """Compare what Loupe reads from a page's pixels with what the browser computed, block by block. | |
| Pages are collected in truth mode (collect_page.cjs ... truth). The audit never reads those truth fields; | |
| this script does, to report how far font size, line height and contrast are from the browser's values. | |
| Usage: validate_page_measures.py <collected page directory> [more directories] | |
| """ | |
| import json | |
| import re | |
| import sys | |
| from pathlib import Path | |
| sys.stdout.reconfigure(encoding="utf-8", errors="replace") | |
| PACKAGE = Path(__file__).resolve().parent | |
| sys.path.insert(0, str(PACKAGE)) | |
| import numpy | |
| from PIL import Image | |
| import block_measure | |
| def colour(text, behind=None): | |
| """An rgb() or rgba() string as RGB; a translucent colour is composited over the colour behind it.""" | |
| numbers = [float(value) for value in re.findall(r"[\d.]+", text)] | |
| alpha = numbers[3] if len(numbers) > 3 and behind is not None else 1.0 | |
| return [alpha * numbers[channel] + (1 - alpha) * (behind[channel] if behind else 0.0) for channel in range(3)] | |
| def page_errors(directory, maps): | |
| layout = json.loads((directory / "layout.json").read_text(encoding="utf-8")) | |
| with Image.open(directory / "page.png") as image: | |
| pixels = numpy.asarray(image.convert("RGB")) | |
| errors = {"font_px": [], "line_height_px": [], "contrast": []} | |
| for text in layout["texts"]: | |
| truth = text["truth"] | |
| measured = block_measure.measure_block(pixels, text["box"], maps) | |
| errors["font_px"].append(abs(measured["font_px"] / truth["font_px"] - 1)) | |
| if measured["lines"] > 1 and truth["line_height_px"] is not None: | |
| errors["line_height_px"].append(abs(measured["line_height_px"] / truth["line_height_px"] - 1)) | |
| behind = colour(truth["background"]) | |
| declared = block_measure.contrast_ratio(behind, colour(truth["color"], behind)) | |
| errors["contrast"].append(abs(measured["contrast"] / declared - 1)) | |
| return errors | |
| def summary(values): | |
| values = numpy.asarray(values) | |
| return {"blocks": int(len(values)), "median_relative_error": float(numpy.median(values)), | |
| "within_10_percent": float((values <= 0.10).mean()), "within_20_percent": float((values <= 0.20).mean())} | |
| def main(): | |
| maps = block_measure.load_maps() | |
| pooled = {"font_px": [], "line_height_px": [], "contrast": []} | |
| report = {"pages": {}} | |
| for argument in sys.argv[1:]: | |
| directory = Path(argument) | |
| errors = page_errors(directory, maps) | |
| report["pages"][directory.name] = {kind: summary(values) for kind, values in errors.items() if values} | |
| for kind, values in errors.items(): | |
| pooled[kind].extend(values) | |
| report["pooled"] = {kind: summary(values) for kind, values in pooled.items() if values} | |
| (PACKAGE / "page_measure_validation.json").write_text(json.dumps(report, indent=1), encoding="utf-8") | |
| for name, entry in {**report["pages"], "POOLED": report["pooled"]}.items(): | |
| print(name, {kind: (value["blocks"], round(value["median_relative_error"], 3), round(value["within_10_percent"], 2)) | |
| for kind, value in entry.items()}) | |
| if __name__ == "__main__": | |
| main() | |