"""Ложные тревоги на участке `new_data`, которого считывание не видело. Прокси незнакомой линии в `evaluate.py --no-memory` — пять коротких записей, вместе чуть больше километра пути, и все они сняты в один день. `new_data` — двадцать минут непрерывной езды другим днём. Если учить считывание на вставках в первую половину, а ложные тревоги мерить на второй, проверка остаётся честной при любом ответе на вопрос, та ли это линия: второй половины модель не видела ни в каком виде. Вторая половина идёт одним непрерывным потоком — состояние конвейера (поза, ось, треки) переносится между кусками, как в `stream_new_data.py`. Памяти тоннеля нет. Несколько вариантов считывания гоняются по одним и тем же кадрам, поэтому разница между ними — только от считывания. Вариант может нести и свои настройки конвейера через `@`: так сравниваются модели при одинаковой строгости (`@mbon_power=2`), а не только при умолчаниях. python tools/eval_new_data.py --shards 110: \\ --readout было=artifacts/mbon_readout.npz --readout стало=new.npz \\ --readout строже=artifacts/mbon_readout.npz@mbon_power=2 \\ --out nd_eval.json """ from __future__ import annotations import argparse import json import time import _bootstrap as B # noqa: F401 import _new_data as ND import _params as PS from flyguard.bag import Bag from flyguard.mbon_readout import MbonReadout from flyguard.pipeline import FlyGuard, Params def main() -> None: ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("--tar", default=ND.DEFAULT_TAR) ap.add_argument("--shards", default="110:", help="номера шардов срезом; по умолчанию вторая половина") ap.add_argument("--per-chunk", type=int, default=5, help="сколько шардов распаковывать за раз") ap.add_argument("--readout", action="append", default=[], metavar="ИМЯ=ПУТЬ[@ПОЛЕ=ЗНАЧЕНИЕ...]", help="вариант считывания, после @ — свои поля Params; можно несколько") ap.add_argument("--out", required=True) PS.add_argument(ap) args = ap.parse_args() if not args.readout: raise SystemExit("нужен хотя бы один --readout ИМЯ=ПУТЬ") members = ND.pick(ND.shards(args.tar), args.shards) pipes, stats = {}, {} for item in args.readout: name, _, rest = item.partition("=") path, *own = rest.split("@") params = Params(**PS.apply({}, args.set + own, Params)) pipes[name] = FlyGuard(params, memory=None, readout=MbonReadout.load(path)) stats[name] = dict(frames=0, path_m=0.0, back_m=0.0, alarm_frames=0, fp_objects=0, tracks=set()) print(f"шарды {members[0][0]}–{members[-1][0]} ({len(members)} шт.), " f"вариантов считывания {len(pipes)}") t0 = time.time() for part in ND.split(members, args.per_chunk): with ND.chunk(args.tar, part) as d: for _, pc in Bag(d).frames(): for name, fg in pipes.items(): res = fg.process(pc) if res is None: continue s = stats[name] s["frames"] += 1 ds = res.ego.ds if res.ego else 0.0 s["path_m"] += ds s["back_m"] += max(-ds, 0.0) objs = res.decision.objects if objs: s["alarm_frames"] += 1 s["fp_objects"] += len(objs) s["tracks"].update(o.track_id for o in objs) line = " | ".join(f"{n}: {len(s['tracks'])} треков на {s['path_m'] / 1000:.2f} км" for n, s in stats.items()) print(f" шарды до {part[-1][0]:3d}: {line} | {(time.time() - t0) / 60:5.1f} мин", flush=True) rows = [] for name, s in stats.items(): km = max(s["path_m"] / 1000.0, 1e-6) rows.append(dict(readout=name, frames=s["frames"], path_m=s["path_m"], back_m=s["back_m"], alarm_frames=s["alarm_frames"], alarm_rate=s["alarm_frames"] / max(s["frames"], 1), fp_objects=s["fp_objects"], fp_tracks=len(s["tracks"]), fp_per_km=len(s["tracks"]) / km)) print(f"{name:12s} кадров {s['frames']:6d}, путь {s['path_m'] / 1000:6.2f} км " f"(назад {s['back_m']:.0f} м) | кадров с тревогой {rows[-1]['alarm_rate']:6.1%} | " f"ложных треков {len(s['tracks']):4d} = {rows[-1]['fp_per_km']:5.1f} на км") with open(args.out, "w", encoding="utf-8") as f: json.dump(rows, f, ensure_ascii=False, indent=1) print("сохранено:", args.out) if __name__ == "__main__": main()