Brainrot_Muxa/tools/analyze_gauge.py
Данил Омелечко b8e95eccbe ML-ядро детектора: конвейер на схемах мозга дрозофилы
Ретина, ламина, медулла, лобула, грибовидное тело, веерное тело,
центральный комплекс, нисходящие нейроны. Обучение памяти тоннеля и
считывания MBON, оценка leave-one-bag-out, полигон дальности, 24 теста.

Реальный объект на 55 м — 98.9 % кадров, ложных 7.5 трека на км,
кадр обрабатывается за 33 мс на CPU.
2026-09-21 17:24:25 +03:00

111 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Что попадает внутрь габарита в нормальном тоннеле.
Накапливает по многим кадрам занятость плоскости (поперёк, высота) в разных
поясах дальности и устойчивость плоскости пути. Нужно, чтобы выбрать границы
габарита и пороги, а не угадывать их.
python tools/analyze_gauge.py --bag data/for_hackathon/roundT_doubleT --frames 60
"""
from __future__ import annotations
import argparse
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt # noqa: E402
import numpy as np # noqa: E402
import _bootstrap as B # noqa: F401,E402
from flyguard.bag import Bag # noqa: E402
from flyguard.geometry import RailPlane, TrackFrame, fit_rail_plane # noqa: E402
from flyguard.retina import ScanLayout # noqa: E402
BANDS = [(5, 20), (20, 50), (50, 90), (90, 150), (150, 250)]
U_EDGES = np.arange(-4.0, 4.01, 0.05)
H_EDGES = np.arange(-1.0, 4.01, 0.05)
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--bag", required=True)
ap.add_argument("--frames", type=int, default=60)
ap.add_argument("--stride", type=int, default=3)
ap.add_argument("--fov", type=float, default=25.0)
args = ap.parse_args()
bag = Bag(args.bag)
layout = ScanLayout.calibrate([pc for _, pc in bag.frames(start=2, stop=14)])
cols = layout.column_slice(args.fov)
lay = layout.sub(cols)
hist = [np.zeros((U_EDGES.size - 1, H_EDGES.size - 1), np.int64) for _ in BANDS]
planes: list[RailPlane] = []
deficits: list[np.ndarray] = []
prev = None
for _, pc in bag.frames(stop=args.frames * args.stride, stride=args.stride):
img = layout.project(pc).crop(cols)
plane = fit_rail_plane(img, lay, prev=prev)
prev = plane
planes.append(plane)
tf = TrackFrame(img, lay, plane)
for k, (lo, hi) in enumerate(BANDS):
m = tf.valid & (tf.d >= lo) & (tf.d < hi)
if m.any():
hh, _, _ = np.histogram2d(tf.u[m], tf.h[m], bins=(U_EDGES, H_EDGES))
hist[k] += hh.astype(np.int64)
# дефицит до пола: луч должен был дойти до полотна внутри коридора
fr = tf.floor_r
uf = fr * lay.dirs[..., 0]
hit_in = np.isfinite(fr) & (np.abs(uf) < 1.7) & (fr > 10) & (fr < 200)
m = hit_in & tf.valid
if m.any():
deficits.append((fr[m] - tf.r[m]).astype(np.float32))
a = np.array([p.a for p in planes]); b = np.array([p.b for p in planes])
hgt = np.array([p.height for p in planes]); rms = np.array([p.rms for p in planes])
print(f"=== {bag.path.name}: {len(planes)} кадров")
print(f" высота сенсора над путём: {hgt.mean():.3f} ± {hgt.std():.3f} м "
f"(разброс {hgt.min():.3f}…{hgt.max():.3f})")
print(f" тангаж: {np.degrees(np.arctan(a)).mean():+.3f}° ± {np.degrees(np.arctan(a)).std():.3f}°")
print(f" крен: {np.degrees(np.arctan(b)).mean():+.3f}° ± {np.degrees(np.arctan(b)).std():.3f}°")
print(f" невязка плоскости rms: {rms.mean():.3f} м, инлайеров {np.mean([p.inliers for p in planes]):.0f}")
if deficits:
dd = np.concatenate(deficits)
print(f" дефицит до пола внутри коридора, м: "
+ " ".join(f"p{q}={np.percentile(dd, q):+.2f}" for q in (1, 50, 90, 99, 99.9))
+ f" доля > 1 м: {np.mean(dd > 1.0):.3%}")
fig, axes = plt.subplots(2, len(BANDS), figsize=(4 * len(BANDS), 8), dpi=110)
fig.suptitle(f"{bag.path.name}: занятость (поперёк, высота) по поясам дальности; "
f"красное — габарит 3.4 × 2.2 м", fontsize=12)
for k, (lo, hi) in enumerate(BANDS):
hh = hist[k].T
tot = hh.sum()
for row, norm in enumerate(("log", "col")):
ax = axes[row, k]
if tot == 0:
ax.set_title(f"{lo}–{hi} м: пусто"); continue
show = np.log10(1 + hh) if norm == "log" else hh / np.maximum(hh.sum(0, keepdims=True), 1)
ax.imshow(show, origin="lower", aspect="auto", cmap="inferno",
extent=[U_EDGES[0], U_EDGES[-1], H_EDGES[0], H_EDGES[-1]])
ax.add_patch(plt.Rectangle((-1.7, 0.05), 3.4, 2.15, fill=False, ec="r", lw=1.2))
ax.axhline(0, color="c", lw=0.6)
ax.set_title(f"{lo}–{hi} м, n={tot/1e3:.0f}k" + ("" if row == 0 else " (норм. по столбцу)"),
fontsize=9)
ax.set_xlabel("поперёк, м")
if k == 0:
ax.set_ylabel("высота над рельсом, м")
B.FIGURES.mkdir(parents=True, exist_ok=True)
out = B.FIGURES / f"gauge_{bag.path.name}.png"
fig.tight_layout(rect=(0, 0, 1, 0.95))
fig.savefig(out)
print(" сохранено:", out)
if __name__ == "__main__":
main()