Ретина, ламина, медулла, лобула, грибовидное тело, веерное тело, центральный комплекс, нисходящие нейроны. Обучение памяти тоннеля и считывания MBON, оценка leave-one-bag-out, полигон дальности, 24 теста. Реальный объект на 55 м — 98.9 % кадров, ложных 7.5 трека на км, кадр обрабатывается за 33 мс на CPU. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
81 lines
3.2 KiB
Python
81 lines
3.2 KiB
Python
"""Characterise the Pandar128 scan geometry inside the metro tunnel bags."""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
|
|
import numpy as np
|
|
|
|
sys.path.insert(0, r"C:\Games\Study\AI_Lidar\tools")
|
|
from probe_bag import frames # noqa: E402
|
|
|
|
W, H = 7200, 128
|
|
|
|
|
|
def as_image(p):
|
|
"""Return (H,W) arrays: ordering is col-major with ring cycling fastest."""
|
|
x = p["x"].reshape(W, H).T.astype(np.float32)
|
|
y = p["y"].reshape(W, H).T.astype(np.float32)
|
|
z = p["z"].reshape(W, H).T.astype(np.float32)
|
|
i = p["intensity"].reshape(W, H).T.astype(np.float32)
|
|
return x, y, z, i
|
|
|
|
|
|
def main(db, idx=100):
|
|
for ts, m in frames(db, limit=1, start=idx):
|
|
x, y, z, inten = as_image(m["points"])
|
|
valid = ~((x == 0) & (y == 0) & (z == 0))
|
|
r = np.sqrt(x * x + y * y + z * z)
|
|
|
|
# azimuth measured in the sensor XY plane; forward is -Y
|
|
az = np.degrees(np.arctan2(x, -y)) # 0 = forward, + = right
|
|
el = np.degrees(np.arcsin(np.clip(z / np.maximum(r, 1e-6), -1, 1)))
|
|
|
|
print(f"valid {valid.sum()}/{valid.size} = {valid.mean():.1%}")
|
|
|
|
print("\n--- azimuth per column (median over valid rings) ---")
|
|
azc = np.where(valid, az, np.nan)
|
|
with np.errstate(all="ignore"):
|
|
colaz = np.nanmedian(azc, axis=0)
|
|
good = np.isfinite(colaz)
|
|
print("columns with any return:", good.sum())
|
|
cols = np.arange(W)
|
|
for c in [0, 1, 2, 1800, 3599, 3600, 3601, 5400, 7198, 7199]:
|
|
print(f" col {c:5d}: az={colaz[c]:8.3f}")
|
|
d = np.diff(colaz[good])
|
|
d = d[np.abs(d) < 1.0]
|
|
print(f" median azimuth step: {np.median(d):.4f} deg")
|
|
|
|
print("\n--- elevation per ring (median over valid columns) ---")
|
|
elr = np.where(valid, el, np.nan)
|
|
with np.errstate(all="ignore"):
|
|
ringel = np.nanmedian(elr, axis=1)
|
|
print(" ring0..9 :", np.round(ringel[:10], 2))
|
|
print(" ring60..69:", np.round(ringel[60:70], 2))
|
|
print(" ring118..127:", np.round(ringel[118:], 2))
|
|
print(f" elevation span: {np.nanmin(ringel):.2f} .. {np.nanmax(ringel):.2f}")
|
|
|
|
print("\n--- valid-return fraction by azimuth sector ---")
|
|
for lo, hi in [(-180, -90), (-90, -30), (-30, -10), (-10, 10), (10, 30), (30, 90), (90, 180)]:
|
|
sel = (colaz >= lo) & (colaz < hi)
|
|
if sel.sum() == 0:
|
|
print(f" [{lo:4d},{hi:4d}) : no columns")
|
|
continue
|
|
v = valid[:, sel]
|
|
print(f" [{lo:4d},{hi:4d}) : {sel.sum():5d} cols, valid {v.mean():.1%}")
|
|
|
|
print("\n--- forward cone (|az|<3 deg) range distribution ---")
|
|
fwd = np.abs(colaz) < 3.0
|
|
rf = r[:, fwd][valid[:, fwd]]
|
|
print(f" columns {fwd.sum()}, valid pts {rf.size}")
|
|
print(" pct:", np.round(np.percentile(rf, [50, 90, 99, 99.9, 100]), 2))
|
|
|
|
print("\n--- points beyond 100 m, in train gauge (|x|<1.6, -1<z<1.4) ---")
|
|
far = valid & (r > 100)
|
|
gauge = far & (np.abs(x) < 1.6) & (z > -1.0) & (z < 1.4)
|
|
print(f" far {far.sum()}, of them in gauge {gauge.sum()}")
|
|
if gauge.sum():
|
|
print(" max distance in gauge:", np.round(r[gauge].max(), 1))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1], int(sys.argv[2]) if len(sys.argv) > 2 else 100)
|