fig(family): 六型号总览图 + PD132T↔PD136HA 档位对齐图;待实测清单 ⑧

- docs/img/family-overview.{svg,png}(tools/plot_family_overview.py)
  面板A 窗长/τ 条形;面板B 检测延迟 vs Δf/f(六型号按各自档0 判据);
  归一化延迟表(@1.1× / @2× / @10× 判据)+ 进入确认与口径脚注
  关键:@10×(强信号) 全部=1 窗 ⇒ 8.2/8.7/10.0/10.9/21.9/27.3ms;@1.1×(近阈值) 7~9 窗 ⇒ M1H 57ms ~ PD136HA 197ms
- docs/img/sens-alignment-pd132t-pd136ha.{svg,png}(tools/plot_sens_alignment.py)
  四档 进入/离开 双段条形 + 同档偏差(−46%~+69%) + 最近档映射 + 换型 4 步 + 两端档无对应警示
- 待实测清单新增 ⑧ 弱信号复测(0.10% 车:PD132T 档1 ≈76ms 应检出 / PD136HA 档1 应检不到)
- 文档接入:cross-model-comparison.md 新增「六型号总览图」节;comparison §5.4 嵌入对齐图;measurement doc §10 加指引
- README tools 段补 3 个绘图脚本;表格校验 0 异常(16 文件)
This commit is contained in:
wangfq
2026-09-11 11:27:58 +08:00
parent 9c88b1d7b0
commit db5e108620
10 changed files with 720 additions and 1 deletions
+223
View File
@@ -0,0 +1,223 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""六型号总览图(窗长 / τ / 检测延迟)—— 纯 stdlib 手写 SVG
面板 A:测量窗长 与 IIR 时间常数 τ(横向条形,对数刻度)
面板 B:检测延迟 vs Δf/f(各型号按【本型号档0 判据】计算,对数-对数)
转 PNGchromium --headless=new --disable-gpu --no-sandbox \
--screenshot=docs/img/family-overview.png --window-size=1520,1000 \
file://$PWD/docs/img/family-overview.svg
"""
import math
W, H = 1520, 1100
FONT = "'Noto Serif CJK SC','Noto Sans CJK SC',sans-serif"
MONO = "'DejaVu Sans Mono',monospace"
out = []
def esc(s):
return (s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;'))
def txt(x, y, s, size=14, fill='#222', anchor='start', weight='normal', family=None):
out.append('<text x="%.1f" y="%.1f" font-size="%s" fill="%s" text-anchor="%s" font-weight="%s" font-family="%s">%s</text>'
% (x, y, size, fill, anchor, weight, family or FONT, esc(s)))
def line(x1, y1, x2, y2, stroke='#999', w=1.0, dash=None):
d = ' stroke-dasharray="%s"' % dash if dash else ''
out.append('<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="%s" stroke-width="%s"%s/>'
% (x1, y1, x2, y2, stroke, w, d))
def rect(x, y, w, h, fill='none', stroke='none', rx=3, sw=1.0, op=1.0, dash=None):
d = ' stroke-dasharray="%s"' % dash if dash else ''
out.append('<rect x="%.1f" y="%.1f" width="%.1f" height="%.1f" rx="%s" fill="%s" fill-opacity="%s" stroke="%s" stroke-width="%s"%s/>'
% (x, y, w, h, rx, fill, op, stroke, sw, d))
def poly(pts, stroke='#1565c0', w=1.8, fill='none', dash=None):
d = ' stroke-dasharray="%s"' % dash if dash else ''
s = ' '.join('%.1f,%.1f' % p for p in pts)
out.append('<polyline points="%s" fill="%s" stroke="%s" stroke-width="%s" stroke-linejoin="round"%s/>'
% (s, fill, stroke, w, d))
def dot(x, y, r=4.0, fill='#c1121f'):
out.append('<circle cx="%.1f" cy="%.1f" r="%.1f" fill="%s"/>' % (x, y, r, fill))
def note(x, y, w, h, title, lines_, color='#0b6'):
rect(x, y, w, h, '#fbfdff', color, rx=6, sw=1.2, op=0.95)
txt(x + 12, y + 24, title, 14.5, color, weight='bold')
for i, s in enumerate(lines_):
txt(x + 12, y + 48 + i * 21, s, 13, '#333')
# ---- 型号数据(全部取证过;数值见 docs/cross-model-comparison.md----
# name, 测量窗(ms), 滤波更新节拍(ms), α, 档0 判据(Δf/f %), 进入确认说明, 颜色
M = [
('DLD154Pro', 8.738, 8.74, 79, 0.1650, '3 tick 确认(5ms tick', '#6a1b9a'),
('DLD154V4B', 1.092, 10.00, 79, 0.5140, '3~5 tick 确认(10ms tick', '#2e7d32'),
('PD136HA', 21.845, 21.85, 64, 0.2472, '无(单次越线)', '#e65100'),
('PD132T', 10.923, 10.92, 79, 0.4578, '无(单次越线)', '#1565c0'),
('PD132L', 27.307, 27.31, 79, 0.4578, '无(单次越线)', '#00838f'),
('M1H', 8.192, 8.19, 79, 0.3296, '10 tick 确认(≈0.6s', '#5d4037'),
]
def tau_line(tick, alfa):
return tick * 256.0 / alfa
def delay_ms(m, d_pct):
"""判据越线延迟:d_pct 以 % 传入;返回 (窗数, ms) 或 None(检不到)"""
tick, alfa, thr = m[2], m[3], m[4]
if d_pct < thr:
return None
y = 0.0
for n in range(1, 4001):
y = d_pct * (1.0 - (1.0 - alfa / 256.0) ** n)
if y >= thr:
return (n, n * tick)
return (4000, 4000 * tick)
# ================== 画布 ==================
out.append('<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" viewBox="0 0 %d %d">' % (W, H, W, H))
out.append('<rect width="%d" height="%d" fill="#ffffff"/>' % (W, H))
txt(30, 44, '车检器六型号总览:测量窗长 · IIR 时间常数 · 检测延迟', 25, '#111', weight='bold')
txt(30, 72, '窗长 = ⌊BASE/Xn⌋·Xn/f_cap(标称值);τ = 节拍×256/α(产品线口径);检测延迟 = 判据越线时刻(不含输出级 50ms 节拍与进入确认)', 13, '#555')
line(30, 86, W - 30, 86, '#ddd', 1.2)
# ================== 面板 A:窗长 / τ 条形 ==================
txt(40, 118, '面板 A:测量窗长与 IIR τ(对数刻度,单位 ms)', 16, '#111', weight='bold')
LA0, LA1 = 250.0, 700.0
ALO, AHI = 1.0, 200.0
lax = lambda v: LA0 + (math.log10(max(v, ALO)) / math.log10(AHI / ALO)) * (LA1 - LA0)
# 网格与刻度
for g in (1, 2, 5, 10, 20, 50, 100, 200):
x = lax(g)
line(x, 140, x, 560, '#e8e8e8', 1.0)
txt(x, 578, ('%g' % g), 12.5, '#777', anchor='middle')
txt(LA1 + 42, 578, 'ms', 12.5, '#777', anchor='middle')
ROW0, ROWH = 152.0, 68.0
for i, m in enumerate(M):
nm, win, tick, alfa, thr, conf, col = m
y0 = ROW0 + i * ROWH
txt(240, y0 + 20, nm, 15, '#111', anchor='end', weight='bold')
# 窗长
w1 = max(lax(win) - LA0, 2.0)
rect(LA0, y0 + 2, w1, 15, col, rx=2, op=0.95)
txt(lax(win) + 6, y0 + 14, '%.2f' % win, 12.5, col)
# τ
tau = tau_line(tick, alfa)
w2 = max(lax(tau) - LA0, 2.0)
rect(LA0, y0 + 24, w2, 15, col, rx=2, op=0.38)
txt(lax(tau) + 6, y0 + 36, 'τ %.1f' % tau, 12.5, '#555')
# 节拍标注(V4B 窗≠节拍)
if abs(win - tick) > 0.05:
txt(LA0 - 6, y0 + 36, '节拍 %.0f' % tick, 11.5, '#c1121f', anchor='end')
line(LA0, 140, LA0, 560, '#bbb', 1.4)
txt(250, 596, '深色 = 测量窗长;浅色 = IIR τ;红字 = 滤波更新节拍(与窗长不同者)', 12.5, '#666')
# ================== 面板 B:检测延迟 vs Δf/f ==================
txt(800, 118, '面板 B:检测延迟 vs 车辆信号 Δf/f(各型号按本型号档0 判据,对数-对数)', 16, '#111', weight='bold')
BX0, BX1 = 880.0, 1470.0
BYB, BYT = 545.0, 160.0
RLO, RHI = 0.02, 5.0
DLO, DHI = 4.0, 2000.0
bx = lambda v: BX0 + (math.log10(v / RLO) / math.log10(RHI / RLO)) * (BX1 - BX0)
by = lambda d: BYB - (math.log10(max(d, DLO) / DLO) / math.log10(DHI / DLO)) * (BYB - BYT)
for g in (0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5):
x = bx(g)
line(x, BYT, x, BYB, '#e8e8e8', 1.0)
txt(x, BYB + 20, ('%g' % g), 12.5, '#777', anchor='middle')
txt((BX0 + BX1) / 2, BYB + 42, '车辆引起的相对频偏 Δf/f%,对数)', 13, '#555', anchor='middle')
for g in (4, 10, 30, 100, 300, 1000, 2000):
y = by(g)
line(BX0, y, BX1, y, '#e8e8e8', 1.0)
txt(BX0 - 8, y + 4, ('%g' % g), 12.5, '#777', anchor='end')
txt(BX0 - 8, BYT - 14, '延迟 ms', 13, '#555', anchor='end')
# 典型车信号带
rect(bx(0.1), BYT, bx(1.0) - bx(0.1), BYB - BYT, '#fff8e1', rx=0, op=0.9)
txt((bx(0.1) + bx(1.0)) / 2, BYT + 18, '典型车信号带 0.1%~1%', 12.5, '#b8860b', anchor='middle')
# 各型号曲线
for m in M:
nm, win, tick, alfa, thr, conf, col = m
pts = []
v = thr * 1.002
while v <= RHI:
r = delay_ms(m, v)
if r is None:
v *= 1.12
continue
d = r[1]
if d > DHI:
pts = [] # 越过上限 ⇒ 断开(避免画出界)
v *= 1.10
continue
pts.append((bx(v), by(d)))
v = v * 1.06 + 0.0015
if len(pts) > 1:
poly(pts, col, 2.2)
# 1 窗下限虚线(标签统一放到图例,避免互相压字)
line(bx(thr), by(tick), bx(min(thr * 3.2, RHI)), by(tick), col, 1.0, '3,3')
# 阈值竖线
for m in M:
x = bx(m[4])
line(x, BYT, x, BYB, m[6], 1.0, '2,3')
txt(BX0 + 6, BYB - 8, '竖虚线 = 各型号档0 判据(左端=永不检出);横虚线 = 该型号 1 窗下限', 12, '#666')
# 图例(放在面板 B 左上空白区)
rect(890, 176, 236, 166, '#ffffff', '#ccc', rx=5, sw=1.0, op=0.94)
txt(902, 196, '型号 / 窗长', 13, '#555', weight='bold')
for i, m in enumerate(M):
yy = 220 + i * 20
rect(902, yy - 9, 14, 11, m[6], rx=2, op=0.95)
txt(924, yy, '%s %.2f ms' % (m[0], m[1]), 12, m[6])
# ================== 表格:精确数值 ==================
TY = 646.0
line(40, TY - 10, W - 40, TY - 10, '#ddd', 1.2)
txt(40, TY + 14, '精确数值(判据越线延迟;不含输出级与进入确认)', 16, '#111', weight='bold')
COLS = [(60, '型号'), (190, '测量窗'), (285, '滤波节拍'), (370, 'α'), (412, 'τ(口径)'), (500, '档0 判据'),
(620, '延迟 @1.1×判据'), (775, '延迟 @2×判据'), (925, '延迟 @10×判据'), (1060, '进入确认')]
for x, s in COLS:
txt(x, TY + 46, s, 13.5, '#555', weight='bold')
line(40, TY + 56, W - 40, TY + 56, '#ccc', 1.0)
for i, m in enumerate(M):
nm, win, tick, alfa, thr, conf, col = m
y = TY + 84 + i * 40
txt(60, y, nm, 14, col, weight='bold')
txt(190, y, '%.2f' % win, 13, '#333')
txt(285, y, '%.2f' % tick, 13, '#333')
txt(370, y, str(alfa), 13, '#333')
txt(412, y, '%.1f' % tau_line(tick, alfa), 13, '#333')
txt(500, y, '%.4f %%' % thr, 13, '#333')
for dx, mul in ((620, 1.1), (775, 2.0), (925, 10.0)):
r = delay_ms(m, thr * mul)
if r is None:
txt(dx, y, '', 13, '#c1121f')
else:
txt(dx, y, '%.1f ms%d 窗)' % (r[1], r[0]), 13, ('#1565c0' if mul < 1.5 else '#333'))
txt(1060, y, conf, 12.5, '#666')
line(40, TY + 84 + len(M) * 40 - 22, W - 40, TY + 84 + len(M) * 40 - 22, '#ccc', 1.0)
# ================== 脚注 ==================
note(40, TY + 84 + len(M) * 40 - 6, 700, 118, '口径与注意', [
'τ 为产品线口径(节拍×256/α);精确 τ = −节拍/ln(1−α/256),约小 13%',
'V4B 测量窗 1.09ms 与滤波节拍 10ms 不同(Value 每 1.09ms、CAPVD 每 10ms',
'延迟仅含「判据越线」;输出级 50ms 节拍与进入确认未计(见右侧)',
'「档0」各型号判据不同(0.165%~0.514%),仅作同档位号横向对比',
], '#c1121f')
note(760, TY + 84 + len(M) * 40 - 6, 720, 118, '进入确认(须另加)', [
'M1HIN_DELAY=10 tick(约 0.6s);V4B3~5 tick30~50ms',
'154Pro3 次确认;PD132L / PD132T / PD136HA:无(单次越线判定)',
'⇒ 整机响应 = 判据越线 + 进入确认 + ≤50ms 输出节拍',
], '#0b6')
OPEN = 'docs/img/family-overview.svg'
out.append('</svg>')
open(OPEN, 'w', encoding='utf-8').write('\n'.join(out))
print('已写出 %s%d 元素)' % (OPEN, len(out)))
for m in M:
nm = m[0]
f = lambda r: ('%.1f ms%d 窗)' % (r[1], r[0])) if r else ''
print(' %-11s%6.2f | 节拍 %6.2f | τ %6.1f | 档0 %6.4f%% | @1.1x: %-16s @2x: %-16s @10x: %s'
% (nm, m[1], m[2], tau_line(m[2], m[3]), m[4],
f(delay_ms(m, m[4] * 1.1)), f(delay_ms(m, m[4] * 2.0)), f(delay_ms(m, m[4] * 10.0))))