回放并入主窗口,并修掉开场尖峰 / 录制时长 / 录制帧率 / 回放被摄像头覆盖等 9 项评审问题 - What: 回放不再是独立窗口——删除 PlaybackDialog,拆成 replay.py::ReplayController(读盘、游标、深度重算,只发信号)与 ui/replay_bar.py(录制选择、可拖拽进度条、单位、重算深度、提示行),品牌轨「历史回放」直接换主窗口布局,文件菜单两条入口都切到该视图,曲线改读 RecordingCurveSource(区间按需读盘 + 缓存包络),2D 面板切「原始图像 / 深度图」;传感器与标定合并到 SensorDialog(类型/配置下拉 + 当前生效标定 + 最近导出系数),标定面板加纵向滚动区、九按钮两行、六列表头 ResizeToContents。评审 9 项:1) 开场尖峰两个根因——push() 遇单位变化只记 _unit 而保留旧单位样本(raw 计数被画在牛顿轴上),改为「切换即换单位」:清缓冲按新单位重建、触发切换的那一帧仍刷数值卡;_start_run 先 session_store.reset() 再 set_store(),新会话不再带上次的尾巴(复现脚本 .repro/probe_item1.py:旧 39 样本峰值 9.841 → 新 37 样本峰值 2.390)。2) 箭头页脚改为「箭头按位移放大 ×{factor}」。3) 表头单位芯片由只读 QLabel 改为可点按钮,一次点击同时改曲线、数值卡与设置区下拉。4-6) 全程总览/框选/返回最新 改为 ui/icons.py 按主题令牌运行时绘制的图标,tooltip 与 accessibleName 保留文字,全程总览区域名仍是文字。7) stats() 在录制进行中即返回 _last_t - _t0,记录条开始计时(此前只在 _finalise 写、整场停在 00:00.0)。8) 容器 fps 改用传感器配置的速率(packet.fps 是"每秒完成多少帧"的计数器,突发/文件源会读到 48),视频按 1/fps 节流并把超前的帧计入 resampled_video(frames.csv/forces.csv 一行不少,报告写明原因),session.json 同时记 fps 与 measured_fps。9) _replay_owns_image() 让回放视图独占 2D 面板,实时帧不再覆盖;push() 在录制源挂载时连单位都不切,曲线/数值卡/单位一并归录制。附带:数值卡改读游标处样本(_readout_at_cursor)、回放按录制实际有的单位打开(_replay_unit_for + ReplayController.set_unit,避免 raw-only 录制在 newton 偏好下画三张空图)、视频速率下限。 - Why: 用户评审 9 点,其中 1/7/8/9 是代码缺陷(开场尖峰、录制时长不更新、录制 48fps 与实际 30fps 不符、回放时 2D 面板仍被摄像头刷新),2-6 是可读性问题(×2 含义不明、单位与视图控制应可点/应符号化)。 - Verification: ui_check.py 524 项 0 失败(新增 chart header and unit chip / acquisition correctness / replay owns the 2D pane 三段)、test_ui_smoke.py 69 项 0 失败(新增 7 个回归用例:单位切换不混单位、单位芯片、图标语义、录制中时长、传感器帧率+节流、回放独占面板、回放游标读数与单位回退)、drive_shared.py 146 项 0 失败、locale_studio.py(268 键齐全、zh==en、prefs 对齐)、reproduce.py、ui_snapshot.py 四张复核图(深色实时/回放、浅色 1280x720 实时与标定)。 - Not in this change: 录制文件格式(forces.csv 列序、frames.csv、session.json 路径与分段规则)不变,节流只影响"哪些帧进视频",并显式计数上报;三维面板仍是占位符。
This commit is contained in:
@@ -35,15 +35,72 @@ if sys.platform == "win32" and "QT_QPA_FONTDIR" not in os.environ:
|
||||
os.chdir(APP_ROOT)
|
||||
|
||||
|
||||
def _replay_data(window, app) -> Path:
|
||||
"""Synthesize one short recording (with visible frames) and open it."""
|
||||
import numpy as np
|
||||
|
||||
from orisys_app_shared.record import ExperimentRecorder, RecordConfig
|
||||
|
||||
from ui_check import _fake_packet
|
||||
|
||||
root = APP_ROOT / "outputs" / "_snapshot_replay"
|
||||
if root.exists():
|
||||
import shutil
|
||||
|
||||
shutil.rmtree(root, ignore_errors=True)
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
recorder = ExperimentRecorder(RecordConfig(video=True, fps=30.0, segment_seconds=30.0))
|
||||
recording = Path(
|
||||
recorder.start(
|
||||
root=root,
|
||||
session_id=1,
|
||||
metadata={
|
||||
"device": {
|
||||
"sensor_type": "box",
|
||||
"config_ref": str(root / "sensor.json"),
|
||||
"config_sha256": "snapshot",
|
||||
},
|
||||
"versions": {"sdk": "snapshot"},
|
||||
"depth": {"stored": False, "replay": "recompute"},
|
||||
},
|
||||
)
|
||||
)
|
||||
# A recognisable frame: a horizontal gradient with a bright bar, so the
|
||||
# replay's "recorded image" view is reviewable instead of black.
|
||||
height, width = 108, 192
|
||||
frame = np.zeros((height, width, 3), np.uint8)
|
||||
frame[:, :, 1] = np.linspace(20, 200, width, dtype=np.uint8)[None, :]
|
||||
frame[height // 3 : 2 * height // 3, :, 2] = 180
|
||||
for index in range(150):
|
||||
recorder.on_packet(_fake_packet(index, frame=frame))
|
||||
recorder.stop()
|
||||
return recording
|
||||
|
||||
|
||||
def _run_scene(window, app, scene: str):
|
||||
"""Drive the window into a scene that would otherwise need hardware.
|
||||
|
||||
``recording`` mocks the record channel's stats through the same single seam
|
||||
the app reads them from (``_record_stats``), so nothing is written to disk
|
||||
and no device is opened: the render is of the real widgets, not of a mockup.
|
||||
``replay`` writes one short synthetic recording and opens it in the replay
|
||||
view, which is the only way to review the progress bar without a session.
|
||||
"""
|
||||
from ui_check import _fake_packet, _StuckWorker
|
||||
|
||||
if scene == "replay":
|
||||
window.brand_bar.btn_playback.click()
|
||||
for _ in range(4):
|
||||
app.processEvents()
|
||||
recording = _replay_data(window, app)
|
||||
window._load_recording(recording)
|
||||
# Park the playhead where the bar and the charts show a real position.
|
||||
window.replay.set_position(window.replay.duration_s() * 0.4)
|
||||
window.arrow_panel.set_view_mode("recorded")
|
||||
for _ in range(6):
|
||||
app.processEvents()
|
||||
return
|
||||
|
||||
stuck = _StuckWorker()
|
||||
window._ensure_workers = lambda: None
|
||||
window._box_worker = stuck
|
||||
@@ -160,12 +217,14 @@ def main() -> int:
|
||||
parser.add_argument("--demo", action="store_true", help="inject demo plot/table data")
|
||||
parser.add_argument(
|
||||
"--scene",
|
||||
choices=("idle", "connecting", "recording"),
|
||||
choices=("idle", "connecting", "recording", "replay"),
|
||||
default="idle",
|
||||
help=(
|
||||
"connecting renders the Stop/cancel state of a hung device open; "
|
||||
"recording renders the record strip of a running recording "
|
||||
"(mocked stats: no file is written, no device is opened)"
|
||||
"(mocked stats: no file is written, no device is opened); "
|
||||
"replay writes one short synthetic recording and opens it in the "
|
||||
"replay view so the progress bar can be reviewed"
|
||||
),
|
||||
)
|
||||
parser.add_argument("--out", required=True, help="output PNG path")
|
||||
@@ -203,6 +262,9 @@ def main() -> int:
|
||||
window.show()
|
||||
app.processEvents()
|
||||
|
||||
if args.demo and args.scene == "replay":
|
||||
pass # the recording is the demo data
|
||||
|
||||
if args.scene == "connecting":
|
||||
# A device open that never returns: the state the Stop button must
|
||||
# survive. ``ui_check._StuckWorker`` drives the same window state.
|
||||
@@ -211,7 +273,10 @@ def main() -> int:
|
||||
if args.scene == "recording":
|
||||
_run_scene(window, app, "recording")
|
||||
|
||||
if args.demo:
|
||||
if args.scene == "replay":
|
||||
_run_scene(window, app, "replay")
|
||||
|
||||
if args.demo and args.scene != "replay":
|
||||
_demo_data(window)
|
||||
for _ in range(4):
|
||||
app.processEvents()
|
||||
|
||||
Reference in New Issue
Block a user