From e4986d0c5da24b04e8180897a6eb8e58a517caff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=BB=20=D0=9E=D0=BC=D0=B5=D0=BB?= =?UTF-8?q?=D0=B5=D1=87=D0=BA=D0=BE?= Date: Mon, 21 Sep 2026 17:14:49 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D1=83=D1=81=D0=BA=20=D0=BA=D0=BE=D0=BD=D0=B2?= =?UTF-8?q?=D0=B5=D0=B9=D0=B5=D1=80=D0=B0=20=D0=BE=D1=84=D0=BB=D0=B0=D0=B9?= =?UTF-8?q?=D0=BD,=20=D1=83=D0=B1=D1=80=D0=B0=D0=BB=20=D1=85=D0=B2=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D1=8B=20=D0=BF=D1=80=D0=BE=20=D1=81=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 --- docs/CONNECTOME.md | 30 ---------------- docs/EXPERIMENTS.md | 2 +- tools/run_pipeline.py | 80 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 31 deletions(-) create mode 100644 tools/run_pipeline.py diff --git a/docs/CONNECTOME.md b/docs/CONNECTOME.md index 97db3a6..00ad01a 100644 --- a/docs/CONNECTOME.md +++ b/docs/CONNECTOME.md @@ -147,36 +147,6 @@ T4 получает вход из ON-пути, T5 — из OFF-пути; каж --- -## 3.5. Атлас: те же клетки, видимые глазом - -Проверить, что архитектура действительно собрана из заявленных схем, можно не на -словах. `tools/build_brain_atlas.py` берёт публичные выгрузки Codex (FAFB v783) и -сводит их в атлас: положение сомы каждого нейрона и номер стадии FlyGuard, которой -он соответствует. Привязка идёт по **имени типа клетки**, а не по догадке, и все -типы находятся поимённо: - -| Тип | В коннектоме | Где используется | -|---|---|---| -| LC11 | 127 | `lobula.py` — кандидаты как компактные группы лучей | -| LPLC2 | 210 | `medulla.py` — надвигание как дивергенция потока | -| HS (HSE/HSN/HSS) | 6 | `medulla.py` — широкопольный поток | -| VS1…VS8 | 16 | там же, вертикальный канал | -| T4a–d / T5a–d | 6243 / 6002 | `medulla.py` — коррелятор Хассенштайна–Райхардта | -| L1 / L2 | 3503 | `lamina.py` — каналы ON и OFF | -| Клетки Кеньона | 5177 | `mushroom_body.py` — разрежённый код | -| MBON | 96 | там же, выход памяти | -| APL | 2 | там же, глобальное торможение | -| EPG | 47 | `central_complex.py` — кольцевой аттрактор | -| DNp01 | 2 | `descending.py` — гигантское волокно | - -Совпадение с литературой полное: LC11 около 110–130 клеток, HS по три на сторону, -VS по восемь, APL и гигантское волокно — по одной на полушарие. - -Вид `brain_style: cloud` рисует эти 139 255 нейронов на их анатомических местах и -подсвечивает активностью соответствующей стадии. Симуляции по-прежнему нет: -коннектом даёт анатомию, конвейер даёт активность, вид накладывает одно на другое -за 8 мс на кадр. - ## 4. Источники * Dorkenwald S. и др. **Neuronal wiring diagram of an adult brain.** Nature, 2024 — diff --git a/docs/EXPERIMENTS.md b/docs/EXPERIMENTS.md index 2c60247..32efc3c 100644 --- a/docs/EXPERIMENTS.md +++ b/docs/EXPERIMENTS.md @@ -339,7 +339,7 @@ numba являются ламина и оценка движения — вме | Кадров с тревогой | **9.4 %** | 17.2 % | | Посторонних тревог на кадр (полигон) | **0.055** | 0.171 | | Реальный объект на 55 м | 98.9 % | 98.9 % | -| Задержка, медиана в контейнере | 32 мс | 33 мс | +| Задержка, медиана | 32 мс | 33 мс | **Включено по умолчанию.** Размен здесь принципиально лучше, чем у разделения фигуры и фона (п. 9.4): там было вчетверо больше ложных за +29 % дальности, здесь — в 1.75 diff --git a/tools/run_pipeline.py b/tools/run_pipeline.py new file mode 100644 index 0000000..a434efa --- /dev/null +++ b/tools/run_pipeline.py @@ -0,0 +1,80 @@ +"""Сквозной прогон конвейера по бэгу: решения, треки, тайминги. + + python tools/run_pipeline.py --bag data/for_hackathon/doubleT_obstacle --verbose + python tools/run_pipeline.py --all --memory artifacts/mushroom_body.npz +""" +from __future__ import annotations + +import argparse + +import numpy as np + +import _bootstrap as B # noqa: F401 +from flyguard.bag import Bag, find_bags +from flyguard.mushroom_body import MushroomBody +from flyguard.pipeline import FlyGuard, Params + + +def run(bag_path, params: Params, memory, limit: int | None, verbose: bool) -> dict: + bag = Bag(bag_path) + fg = FlyGuard(params, memory=memory) + stages: dict[str, list[float]] = {} + n_det = n_frames = 0 + dists, speeds, ncand = [], [], [] + + for _, pc in bag.frames(stop=limit): + res = fg.process(pc) + if res is None: + continue + n_frames += 1 + for k, v in res.timings.items(): + stages.setdefault(k, []).append(v) + ncand.append(len(res.candidates)) + speeds.append(res.ego.speed if res.ego else 0.0) + d = res.decision + if d.detected: + n_det += 1 + dists.append(d.distance) + if verbose and n_frames % 10 == 0: + obj = (f"{d.distance:6.1f} м conf={d.confidence:.2f} " + f"{'ЭКСТРЕННО' if d.emergency else 'предупр.'}" + if d.detected else "путь свободен") + print(f" кадр {n_frames:4d} v={res.ego.speed*3.6:5.1f} км/ч " + f"канд.={len(res.candidates):3d} треков={len(fg.cx.tracks):3d} | {obj} " + f"| {res.total_ms:5.1f} мс") + + tot = np.array([sum(v[i] for v in stages.values()) for i in range(n_frames)]) \ + if n_frames else np.zeros(1) + res = dict(name=bag.path.name, frames=n_frames, det_rate=n_det / max(n_frames, 1), + p50=float(np.median(tot)), p95=float(np.percentile(tot, 95)), + cands=float(np.mean(ncand)) if ncand else 0.0, + v=float(np.median(speeds) * 3.6) if speeds else 0.0, + d_med=float(np.median(dists)) if dists else float("nan")) + print(f"{res['name']:40s} кадров {res['frames']:4d} | тревога в {res['det_rate']:6.1%} " + f"кадров (медиана {res['d_med']:6.1f} м) | канд./кадр {res['cands']:5.1f} | " + f"v={res['v']:5.1f} км/ч | {res['p50']:5.1f}/{res['p95']:5.1f} мс (p50/p95)") + if verbose: + for k in sorted(stages, key=lambda k: -np.median(stages[k])): + print(f" {k:12s} {np.median(stages[k]):6.2f} мс") + return res + + +def main() -> None: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--bag") + ap.add_argument("--all", action="store_true") + ap.add_argument("--limit", type=int, default=150) + ap.add_argument("--memory", default=None) + ap.add_argument("--fov", type=float, default=30.0) + ap.add_argument("--verbose", action="store_true") + args = ap.parse_args() + + memory = MushroomBody.load(args.memory) if args.memory else None + params = Params(fov_deg=args.fov) + bags = find_bags(B.DATA / "for_hackathon") if args.all else [args.bag] + for b in bags: + run(b, params, memory, args.limit, args.verbose) + + +if __name__ == "__main__": + main()