Add AOI hotspot strict audit for 20nm compare
This commit is contained in:
@@ -5,7 +5,6 @@ import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -19,6 +18,7 @@ DEFAULT_BACKEND_AUDIT_JSON = Path(
|
||||
"/root/sourceserver/pbf/NavSea_Original_vs_Delivery_Render_Audit_Karatsu_20nm_2026-03-31.r7.json"
|
||||
)
|
||||
DEFAULT_OUTPUT_DIR = Path("/root/sourceserver/pbf/report/strict_audit")
|
||||
DEFAULT_HOTSPOTS_PATH = Path("/root/sourceserver/pbf/strict_audit_hotspots_20nm.json")
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -32,6 +32,15 @@ class CropBox:
|
||||
return (self.left, self.top, self.right, self.bottom)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class HotspotSpec:
|
||||
id: str
|
||||
label: str
|
||||
notes: str
|
||||
pane_box_norm: tuple[float, float, float, float]
|
||||
backend_focus: tuple[str, ...]
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run strict audit by combining browser-rendered visual diff with backend render audit summary."
|
||||
@@ -39,11 +48,12 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument("--compare-url", default=DEFAULT_COMPARE_URL)
|
||||
parser.add_argument("--backend-audit-json", type=Path, default=DEFAULT_BACKEND_AUDIT_JSON)
|
||||
parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT_DIR)
|
||||
parser.add_argument("--width", type=int, default=1600)
|
||||
parser.add_argument("--height", type=int, default=900)
|
||||
parser.add_argument("--width", type=int, default=1280)
|
||||
parser.add_argument("--height", type=int, default=720)
|
||||
parser.add_argument("--toolbar-height", type=int, default=150)
|
||||
parser.add_argument("--bottom-trim", type=int, default=170)
|
||||
parser.add_argument("--timeout-sec", type=int, default=90)
|
||||
parser.add_argument("--timeout-sec", type=int, default=120)
|
||||
parser.add_argument("--hotspots-json", type=Path, default=DEFAULT_HOTSPOTS_PATH)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@@ -65,7 +75,7 @@ def run_browser_screenshot(compare_url: str, output_png: Path, width: int, heigh
|
||||
"--disable-gpu",
|
||||
"--enable-unsafe-swiftshader",
|
||||
"--no-sandbox",
|
||||
"--virtual-time-budget=20000",
|
||||
"--virtual-time-budget=15000",
|
||||
f"--window-size={width},{height}",
|
||||
f"--screenshot={output_png}",
|
||||
compare_url,
|
||||
@@ -82,6 +92,30 @@ def build_crop_boxes(image_width: int, image_height: int, toolbar_height: int, b
|
||||
return left_box, right_box
|
||||
|
||||
|
||||
def load_hotspots(hotspots_json: Path) -> list[HotspotSpec]:
|
||||
raw = json.loads(hotspots_json.read_text())
|
||||
hotspots: list[HotspotSpec] = []
|
||||
for item in raw:
|
||||
hotspots.append(
|
||||
HotspotSpec(
|
||||
id=item["id"],
|
||||
label=item["label"],
|
||||
notes=item.get("notes", ""),
|
||||
pane_box_norm=tuple(item["pane_box_norm"]),
|
||||
backend_focus=tuple(item.get("backend_focus", [])),
|
||||
)
|
||||
)
|
||||
return hotspots
|
||||
|
||||
|
||||
def normalized_box_to_pixels(box_norm: tuple[float, float, float, float], pane_width: int, pane_height: int) -> CropBox:
|
||||
left = max(0, min(pane_width, round(box_norm[0] * pane_width)))
|
||||
top = max(0, min(pane_height, round(box_norm[1] * pane_height)))
|
||||
right = max(left + 1, min(pane_width, round(box_norm[2] * pane_width)))
|
||||
bottom = max(top + 1, min(pane_height, round(box_norm[3] * pane_height)))
|
||||
return CropBox(left, top, right, bottom)
|
||||
|
||||
|
||||
def compute_visual_metrics(left_img: Image.Image, right_img: Image.Image) -> dict[str, Any]:
|
||||
if left_img.size != right_img.size:
|
||||
raise ValueError(f"image sizes differ: {left_img.size} vs {right_img.size}")
|
||||
@@ -116,7 +150,16 @@ def compute_visual_metrics(left_img: Image.Image, right_img: Image.Image) -> dic
|
||||
}
|
||||
|
||||
|
||||
def save_visual_outputs(full_png: Path, left_png: Path, right_png: Path, diff_png: Path, toolbar_height: int, bottom_trim: int) -> dict[str, Any]:
|
||||
def save_visual_outputs(
|
||||
full_png: Path,
|
||||
left_png: Path,
|
||||
right_png: Path,
|
||||
diff_png: Path,
|
||||
toolbar_height: int,
|
||||
bottom_trim: int,
|
||||
hotspots: list[HotspotSpec],
|
||||
output_dir: Path,
|
||||
) -> dict[str, Any]:
|
||||
image = Image.open(full_png).convert("RGBA")
|
||||
left_box, right_box = build_crop_boxes(image.width, image.height, toolbar_height, bottom_trim)
|
||||
left = image.crop(left_box.as_tuple())
|
||||
@@ -131,9 +174,49 @@ def save_visual_outputs(full_png: Path, left_png: Path, right_png: Path, diff_pn
|
||||
|
||||
metrics["full_crop_left"] = list(left_box.as_tuple())
|
||||
metrics["full_crop_right"] = list(right_box.as_tuple())
|
||||
metrics["hotspots"] = save_hotspot_outputs(left, right, hotspots, output_dir)
|
||||
return metrics
|
||||
|
||||
|
||||
def save_hotspot_outputs(
|
||||
left_img: Image.Image,
|
||||
right_img: Image.Image,
|
||||
hotspots: list[HotspotSpec],
|
||||
output_dir: Path,
|
||||
) -> list[dict[str, Any]]:
|
||||
hotspot_dir = output_dir / "hotspots"
|
||||
hotspot_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
results: list[dict[str, Any]] = []
|
||||
for hotspot in hotspots:
|
||||
box = normalized_box_to_pixels(hotspot.pane_box_norm, left_img.width, left_img.height)
|
||||
left_crop = left_img.crop(box.as_tuple())
|
||||
right_crop = right_img.crop(box.as_tuple())
|
||||
metrics = compute_visual_metrics(left_crop, right_crop)
|
||||
diff_img = metrics.pop("diff_image")
|
||||
diff_boost = diff_img.convert("RGB").point(lambda value: min(255, value * 4))
|
||||
|
||||
left_path = hotspot_dir / f"{hotspot.id}_left.png"
|
||||
right_path = hotspot_dir / f"{hotspot.id}_right.png"
|
||||
diff_path = hotspot_dir / f"{hotspot.id}_diff.png"
|
||||
left_crop.save(left_path)
|
||||
right_crop.save(right_path)
|
||||
diff_boost.save(diff_path)
|
||||
|
||||
metrics["id"] = hotspot.id
|
||||
metrics["label"] = hotspot.label
|
||||
metrics["notes"] = hotspot.notes
|
||||
metrics["backend_focus"] = list(hotspot.backend_focus)
|
||||
metrics["pane_box_norm"] = list(hotspot.pane_box_norm)
|
||||
metrics["pane_box_pixels"] = list(box.as_tuple())
|
||||
metrics["left_image"] = str(left_path)
|
||||
metrics["right_image"] = str(right_path)
|
||||
metrics["diff_image"] = str(diff_path)
|
||||
results.append(metrics)
|
||||
|
||||
return sorted(results, key=lambda item: item["changed_ratio"], reverse=True)
|
||||
|
||||
|
||||
def load_backend_summary(report_json_path: Path) -> dict[str, Any]:
|
||||
data = json.loads(report_json_path.read_text())
|
||||
|
||||
@@ -200,6 +283,32 @@ def write_reports(
|
||||
f"- `{output_dir / 'compare_right.png'}`",
|
||||
f"- `{output_dir / 'compare_diff.png'}`",
|
||||
"",
|
||||
"## Hotspot AOI Audit",
|
||||
"",
|
||||
]
|
||||
|
||||
for hotspot in visual_metrics.get("hotspots", []):
|
||||
lines.extend(
|
||||
[
|
||||
f"### {hotspot['label']}",
|
||||
"",
|
||||
f"- id: `{hotspot['id']}`",
|
||||
f"- notes: `{hotspot['notes']}`",
|
||||
f"- backend focus: `{hotspot['backend_focus']}`",
|
||||
f"- changed pixels: `{hotspot['changed_pixels']}` / `{hotspot['total_pixels']}`",
|
||||
f"- changed ratio: `{hotspot['changed_ratio']}`",
|
||||
f"- mean abs rgb: `{hotspot['mean_abs_rgb']}`",
|
||||
f"- pane box norm: `{hotspot['pane_box_norm']}`",
|
||||
f"- pane box pixels: `{hotspot['pane_box_pixels']}`",
|
||||
f"- left image: `{hotspot['left_image']}`",
|
||||
f"- right image: `{hotspot['right_image']}`",
|
||||
f"- diff image: `{hotspot['diff_image']}`",
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
"## Backend Render Audit",
|
||||
"",
|
||||
f"- original feature instances: `{backend_summary['original_feature_instances']}`",
|
||||
@@ -208,7 +317,8 @@ def write_reports(
|
||||
"",
|
||||
"Status counts:",
|
||||
"",
|
||||
]
|
||||
]
|
||||
)
|
||||
for key, value in backend_summary["status_counts"].items():
|
||||
lines.append(f"- `{key}`: `{value}`")
|
||||
|
||||
@@ -238,6 +348,7 @@ def main() -> None:
|
||||
args.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
compare_version = fetch_compare_version(args.compare_url)
|
||||
hotspots = load_hotspots(args.hotspots_json)
|
||||
full_png = args.output_dir / "compare_full.png"
|
||||
left_png = args.output_dir / "compare_left.png"
|
||||
right_png = args.output_dir / "compare_right.png"
|
||||
@@ -251,6 +362,8 @@ def main() -> None:
|
||||
diff_png=diff_png,
|
||||
toolbar_height=args.toolbar_height,
|
||||
bottom_trim=args.bottom_trim,
|
||||
hotspots=hotspots,
|
||||
output_dir=args.output_dir,
|
||||
)
|
||||
backend_summary = load_backend_summary(args.backend_audit_json)
|
||||
report_md, report_json = write_reports(
|
||||
@@ -270,6 +383,13 @@ def main() -> None:
|
||||
"report_json": str(report_json),
|
||||
"changed_ratio": visual_metrics["changed_ratio"],
|
||||
"changed_pixels": visual_metrics["changed_pixels"],
|
||||
"hotspots": [
|
||||
{
|
||||
"id": item["id"],
|
||||
"changed_ratio": item["changed_ratio"],
|
||||
}
|
||||
for item in visual_metrics.get("hotspots", [])
|
||||
],
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
|
||||
Reference in New Issue
Block a user