帧率读数改为"采集时刻"的到达率,并在旁边显示每帧耗时 - What: sdk_worker 的采集循环里把帧率与时间戳的取样点从"算完之后"(t1) 改成"取到这一帧的时刻"(t0)——frame_times、SIGPkt.mono_t(实时分支)与 last_frame_mono(断线判定)都用 t0,所以 16.7 FPS 现在明确表示"帧是以这个速率到达的",而不是把流水线耗时当成帧率;process_ms 的终点从 process_chain 之后改到 read_forces_from_sensor 之后(t_done),因此它终于涵盖这一帧的**全部**开销(此前漏掉力值读取);last_frame_at 仍取 t1(compute chain 结束)作为节拍相位参考,行为不变(帧率测试仍然通过)。app_window 在 视图▸显示帧率 打开时把每帧耗时一起显示在轨道摘要里:"16.7 FPS · 58 ms",并新增 _latest_process_ms(随会话/回放清空)。tests/test_ui_smoke.py 增加一项断言:轨道同时给出速率与耗时,且没有耗时数据时仍只显示速率。 - Why: 用户看到 Studio 上方显示 16.67 fps 而不是 30,问是不是显示错了。查证结论:这个数字来自 packet.fps,是采集循环真实的"到达率",记录里的行数也是它——不是界面 bug;但它有两个缺陷让这个数字**无法单独判读**:(1) 取样点在算完之后,于是它把本机流水线耗时混进了"设备帧率";(2) 旁边没有每帧耗时,而"16.7 FPS"既可能是摄像头只给 16.7,也可能是流水线跟不上 30(实测:每帧 ≤30 ms 时循环仍能跑满 29.3 fps;40/50/60/70 ms 分别掉到 24.7/19.8/16.5/14.1 fps——16.67 恰好对应每帧约 60 ms)。本机 app 配置(365x365 拼接、dis_mode=3)在 480p 素材上 process_chain+力值读取只要 ~10 ms(上限约 100 fps),因此本次报的 16.67 不可能是这台机器上这个配置的流水线瓶颈;两种可能必须靠耗时数据区分。 - Verification: pytest shared/tests/test_worker_frame_rate.py 4 项全过(30 fps 设备仍被消费在 29.3 fps);studio 全量 90 passed(新增 1 项);ui_check 554 项检查 0 失败;真实 SDK 复测:文件源 55.4 fps 到达率、每帧 18.2 ms(含力值读取)。 - Not in this change: frames.csv 仍未记录 process_ms(下次改记录格式、加未滤波 17 列时一并加,避免连做两次版本升级);未动节拍逻辑本身。

This commit is contained in:
ykcheungab
2026-09-14 11:22:44 +08:00
parent a9f75a868d
commit 5ef4d955fb
3 changed files with 55 additions and 5 deletions
+23
View File
@@ -2105,6 +2105,29 @@ def test_the_status_bar_is_a_notice_line_not_a_state_line(make_window):
_finish(window, stuck)
def test_the_rail_reads_the_rate_and_what_it_cost(qt_app, make_window):
"""A frame rate alone cannot say whether we or the camera are the slow part.
``packet.process_ms`` is the whole per-frame cost, so the rail shows it next
to the rate: "16.7 FPS · 58 ms" means the loop cannot keep up, while
"16.7 FPS · 6 ms" means the camera is only delivering 16.7.
"""
window = make_window()
window._show_fps = True
window._latest_fps = 16.67
window._latest_process_ms = 58.4
window._refresh_status_rail()
text = window.toolbar.source_summary.text()
assert "16.7 FPS" in text, text
assert "58 ms" in text, text
# Without a measured cost the rate is still shown, on its own.
window._latest_process_ms = None
window._refresh_status_rail()
assert window.toolbar.source_summary.text().strip().endswith("FPS")
window.close()
# ------------------------------------------------------- plot settings dialog
def test_the_chart_gear_opens_the_settings_dialog(qt_app, make_window):
"""The gear is the one place the filters can be changed from the UI."""