"""Парное сравнение двух прогонов полигона на одних и тех же вставках. Полигон шумит сильнее, чем меняет его большинство правок: две реализации случайности при одинаковых настройках расходятся на P@150 на 0.07, на P@100 у ящика — на 0.11 (EXPERIMENTS п. 16.1). Поэтому сравнивать итоговые таблицы двух прогонов нельзя. Но у каждого сценария свой генератор (`make_benchmark.scenario_rng`), и от решений конвейера он не зависит: при другой настройке на вход идут те же кадры с теми же вставками, до луча. Значит, разница двух прогонов — только от настройки, и её видно по отдельным наблюдениям: сколько было 0 и стало 1 и наоборот. Перевороты в одну сторону — настоящий эффект, даже когда он меньше межзернового шума; поровну в обе стороны — шум. python tools/compare_benchmark.py artifacts/benchmark.json new.json python tools/compare_benchmark.py a.json b.json --objects человек_лежа,ящик """ from __future__ import annotations import argparse import json import numpy as np MIN_VISIBLE_RAYS = 2 # как в plot_benchmark: меньше — предмет не освещён def load(path: str) -> dict: with open(path, encoding="utf-8") as f: return {(r["bag"], r["obj"], r["lateral"]): r for r in json.load(f)} def main() -> None: ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("before", help="прогон «было» (json из make_benchmark)") ap.add_argument("after", help="прогон «стало»") ap.add_argument("--objects", default="", help="через запятую; пусто — все предметы") ap.add_argument("--edges", default="0,15,40,90,130,170,230", help="границы полос дальности, м") args = ap.parse_args() a, b = load(args.before), load(args.after) if set(a) != set(b): raise SystemExit("разный состав сценариев — прогоны не парные") edges = [float(x) for x in args.edges.split(",")] objs = ([o for o in args.objects.split(",") if o] if args.objects else sorted({k[1] for k in a})) print(f"{'предмет':14s}{'полоса':>12}{'набл.':>7}{'было':>7}{'стало':>7}" f"{'0→1':>6}{'1→0':>6}") total_up = total_down = 0 for obj in objs: obj_up = obj_down = 0 for lo, hi in zip(edges[:-1], edges[1:]): n = ha = hb = up = down = 0 for k, ra in a.items(): if k[1] != obj: continue rb = b[k] da = np.asarray(ra["d"]) if da.shape != np.shape(rb["d"]) or not np.allclose(da, rb["d"]): raise SystemExit(f"вход разошёлся в {k}: прогоны не парные") m = (da >= lo) & (da < hi) & (np.asarray(ra["rays"]) >= MIN_VISIBLE_RAYS) x, y = np.asarray(ra["hit"])[m], np.asarray(rb["hit"])[m] n += int(m.sum()) ha += int(x.sum()) hb += int(y.sum()) up += int(((x == 0) & (y == 1)).sum()) down += int(((x == 1) & (y == 0)).sum()) obj_up += up obj_down += down if n >= 20: print(f"{obj:14s}{f'{lo:.0f}–{hi:.0f} м':>12}{n:7d}{ha / n:7.2f}" f"{hb / n:7.2f}{up:6d}{down:6d}") print(f"{obj:14s}{'всего':>12}{'':21s}{obj_up:6d}{obj_down:6d}") total_up += obj_up total_down += obj_down print(f"\nвсе предметы: 0→1 {total_up}, 1→0 {total_down}") if __name__ == "__main__": main()