Files
orisys-app/apps/autocal/docs/capture-workflow-plan.zh.md
ykcheungab 06e06126ca 重构 Autocal 采集页:单一队列与控制器驱动的主按钮
选点、对准、加载与录像分散在两个面板,状态靠隐藏相位判断。现改由不依赖 Qt 的采集控制器驱动,并加入录像选项、汇总平铺副本、保持到时自动结束、会话结束回到空闲,以及无设备时的模拟采集。已运行 Autocal 非 Qt 测试并通过范围内门禁;界面测试待 Windows 复核,未做实机验收。
2026-10-18 16:04:32 +08:00

146 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Autocal 标定采集页重构实施计划
状态:2026-10-18 计划,P0–P6 已实施并通过独立验收复核。**仅软件验证**:V10 实机验收未执行,需用户指定设备、运动与载荷范围及停机条件;各项检查的实际口径见文末[实施结果](#实施结果2026-10-18)。目标是把“选点 → 对准 → 逐档加载 → 记录”收成一条流程:一个测点队列、一个不依赖 Qt 的状态控制器、一个随步骤变化的主按钮;同时加入录像选项、打开文件夹和平铺汇总。执行分工见[任务分工](capture-workflow-tasks.zh.md)。本计划不改变运动安全规则、不自动加载、不写回 SDK 系数。
## 基线与问题
调查基线:app `main`、HEAD `7607653`(实施时实际 HEAD 为 `7b5ffa7`,以 `.scratch/autocal-capture-workflow/baseline.md` 为准);工作树已有 `motion/service.py`、`record_diagnostics.py` 改动,实施时不得覆盖。结论来自源码、界面截图和 `D:/orisys_ws/data/dd02-gl`,未运行程序或硬件。
| 位置(相对 `apps/autocal/src/orisys_autocal/`) | 问题 |
|---|---|
| `ui/calibration_panel.py: _confirm/_sync` | “到达目标”按钮与参考力输入框按隐藏的 `campaign.phase` 在单点与多点之间切换 |
| `ui/multipoint_panel.py: _levels/_hold` | 多点读取右栏载荷档位,又自带 Min/Max force,存在两套载荷设置 |
| `ui/calibration_panel.py` 子页 | UV 图与实时图互斥;输入源配置藏在实时图子页;开始试验会自动切页 |
| `ui/multipoint_panel.py: bind_finger` | 求解器借用 Finger debug 的 `stack`;该页另有测点表、导出和执行,测点有两份 |
| 两个面板共 10 条状态文字 | 报错落点取决于代码路径,禁用按钮没有原因 |
| `trial.py:157`、`multipoint.py:1476` | `RecordConfig` 默认 `mp4v`,实测约 27 Mbit/s;同画面 H.264 样例约 5 Mbit/s |
| `capture_guard.capture_verdict`、`summary.py:264` | 无视频必判 `video_missing`,多点汇总还要求帧在视频内,因此无法安全提供“不录视频” |
| `summary_export.publish_summary` | 汇总藏在 `derived/summary_<UTC>_<id>/`;用户手工拼出 dd02-gl,已出现 site_id 重名 |
## 需先确认的决定
| 编号 | 决定 | 建议默认 | 阻塞阶段 |
|---|---|---|---|
| D1 | 新采集的单点是否改为“一个点的多点会话”(`sites/` 格式) | 是;旧 `trials/` 会话继续可读,`ManualTrial` 暂留不删 | P3 |
| D2 | 默认视频编码及等效门槛 | 保持 `mp4v` 直到 P2b 证明 H.264 重放差异可接受;门槛由用户给出 | P2 默认值 |
| D3 | Test sequence 页与测点队列的关系 | 本计划不改该页 | 无 |
| D4 | 只录保持窗口前后 | 不做:违背“每 trial 连续录像”契约 | 无 |
未确认的决定由主代理暂停相应阶段,不得自行选择。
## 阶段
每阶段的入口条件不满足就不开始;出口条件全部有证据才算完成。
### P0 基线(主代理)
- 入口:本计划及 D1、D2 已确认。
- 内容:记录 app 与工作区根的分支、HEAD、index、工作树;建立 `.scratch/autocal-capture-workflow/` 和 `.active`;跑基线全量测试与 `panel_check.py`,记录已知失败(finger 页 `QDoubleSpinBox` 高度 2 项)。
- 出口:基线命令与结果写入 scratch;之后任何失败须与基线对比后才能归因。
### P1 打开文件夹与平铺汇总
- 入口:P0。
- 内容:
1. 将 `ui/window.py: _reveal` 提取为 `ui/reveal.py: reveal(path, select=None)`,Windows 用 `explorer /select,<文件>`;菜单与采集页共用。
2. 采集页会话行加“打开会话文件夹”“打开汇总”“复制路径”;无目录时禁用并写明原因。
3. `publish_summary` 在原子替换 `summary-latest.json` 之后,把同一份四件文件写到会话根 `summary/`:先写临时目录再替换。副本失败只报告,不回滚指针、不改采集结果。
- 出口:`summary/` 文件哈希与指针所指版本一致;副本失败时旧 `summary/` 和指针保持不变;`reveal` 参数由测试模拟 `subprocess` 断言;两份格式文档更新目录说明。
### P2 录像选项
- 入口:P0;D2 已确认。
- 内容:
1. 新增 `recording_options.py`:`video: bool`、`codec`、`lossless_frames: bool`;默认值进 `preferences.py` 和 `config/preferences.default.json`。
2. 用一个工厂 `make_record_config(options, fps)` 替换 `trial.py` 与 `multipoint.py` 两处 `RecordConfig(...)`;不改 shared 默认值(Studio 也在用)。
3. 选项写入根 `session.json` 的 `recording` 块,会话建立后锁定。
4. `capture_verdict` 增加 `video_expected`;为 False 时跳过视频相关判定。`summary.py` 和 `session_review.py` 从 `recording` 块读取;缺该块的旧会话按 `video_expected=True` 处理。
5. 启动时用 cv2 向 scratch 写 3 帧探测编码器;不可用的编码在界面上禁用并说明,绝不静默回退。
6. 界面显示按码率与计划时长估算的体积和磁盘剩余空间(`shutil.disk_usage`),不足两倍估算时警告。
- 出口:关视频时 trial 为 complete、汇总行 eligible、复核显示“按设置未录视频”;旧会话结果不变;编码不可用在开始前拒绝。
### P2b 编码等效验证(只出报告,不改代码)
- 入口:P0。
- 内容:同一视频文件作为输入,分别用 `mp4v` 与 H.264 录制,各自经 SDK 重放,按 `frame_id` 比较 `FN_raw`/`FX_raw`/`FY_raw` 的逐帧差和各保持窗口均值差。脚本与结果只放 scratch。
- 出口:报告给出差值相对 1 N 档阶跃响应的比例,交用户按 D2 决定是否切换默认编码。
### P3 采集状态控制器
- 入口:P0;D1 已确认。
- 内容:新增 `capture_controller.py`,不导入 Qt 或运动服务,内部包一个 `Campaign`。
1. 队列即 `campaign.sites`;单点就是只有一行的队列。
2. `readiness()` 返回检查项:标定包、输入出帧、总线已初始化且轴映射无问题、输出目录可写、SN、最大力。
3. `primary_action()` 返回 `(key, label_key, enabled, blocked_reason)`;`perform(key, **inputs)` 执行;`secondary_actions()` 返回重测、跳过、停止;`poll()` 推进保持与收尾。
4. 对准只发出请求,仍走 `window._on_align_multipoint → plan_pose_move`。
5. 求解器由构造参数注入 `PlatformStack`,不再取 Finger 面板。
- 出口:表驱动测试覆盖完整加载阶梯、确认离开、换点、重测、复采、任意状态停止;每个禁用的主动作都有非空原因;录制中拒绝对准;直接驱动 `Campaign` 与经由控制器写出的 `session.json`、`reference.csv` 结构一致;`test_binding_purity.py` 通过。
### P4 新采集页界面
- 入口:P1、P3 完成(P2 可并行合入)。
- 内容:按设计稿重排 `CalibrationPanel`:
1. 会话行和 Ready 标签行;输入源与标定包放入“更改输入/标定包…”对话框。
2. 左侧 UV 图与实时图并排,下方是基于控制器模型的队列表。
3. 右侧当前点卡片(步骤标签、提示、参考力或六维力输入、主按钮、次要按钮)和力曲线;底部一行消息。
4. 控件只渲染控制器给出的内容,不读 `campaign` 内部字段;中英文 locale 同步。
- 出口:`panel_check.py` 在 1440×900、1920×1080 下无新增失败;在模拟模式下用界面测试走完两个点;用 grep 证明 `ui/` 中没有 `campaign.phase` 判断;离屏截图存 scratch 供人工查看。
### P5 合并 Finger debug
- 入口:P4。
- 内容:Finger debug 改名为“几何与安装”,保留安装偏置、工具 XY、行程限制、3D 预览和单点试选;删除其测点表,“导出测点操作表”迁到队列的导入/导出;`stackChanged` 触发控制器重新求解,不可达行标出;删除未使用的 `multipoint.import_points` 文案或实现它。
- 出口:修改安装偏置后队列位姿更新、不可达行禁止对准;导出的操作表字段与[数据格式](calibration-data-format.zh.md)一致;`test_finger_panel.py` 更新后通过。
### P6 收尾与可选的跨会话导出
- 入口:P5。
- 内容:更新[数据格式](calibration-data-format.zh.md)、[多点格式](multipoint-data-format.zh.md)、[汇总契约](recording-summary-contract.zh.md)和 README 的操作流程。可选:Records 页“导出批次…”,把多个会话合并成一个 dd02-gl 式文件夹,每行带 `session_id`、`trial_id`,不按 site_id 合并。
- 出口:文档门禁通过;如做批次导出,用两会话同名 site_id 夹具证明不混行。
## 统一验证
所有命令在 `D:\orisys_ws\orisys-app` 下执行,`$pythonExe` 指 `.venv-build\Scripts\python.exe`,basetemp 每次新建。
| 编号 | 检查 | 何时 | 通过条件 |
|---|---|---|---|
| V1 | 各阶段新增测试 | 每个任务交付 | 先在旧行为上失败、修复后通过(可行时) |
| V2 | `pytest apps/autocal/tests` | 每阶段出口 | 0 失败;跳过项须解释 |
| V3 | `tools/lint_gate.py` | 每阶段出口 | `status: clean` 或仅既有抑制 |
| V4 | `apps/autocal/tools/panel_check.py` | P1、P4、P5 | 与 P0 基线相比无新增失败 |
| V5 | `test_binding_purity.py`、`test_locales_autocal.py` | P3、P4 | 通过 |
| V6 | shared 测试 | 仅当改到 `shared/` | 记录器、生命周期、回放测试通过 |
| V7 | `D:\orisys_ws\scripts\doc_lint.py` | 改文档时 | 无新错误 |
| V8 | 离屏截图人工检查 | P4、P5 | 用户或验收代理确认 |
| V9 | P2b 编码报告 | 切换默认编码前 | 用户按 D2 接受 |
| V10 | 实机验收 | 全部完成后 | 需指定设备、运动与载荷范围、停机条件;代理不执行 |
V1–V9 是软件证据,不代表实机可用;V10 未执行前状态只能写“仅软件验证”。
## 实施结果(2026-10-18)
证据都在 `D:\orisys_ws\orisys-app\.scratch\autocal-capture-workflow\`:
| 阶段 | 结果 | 证据 |
|---|---|---|
| P0 | 基线 572 项测试通过;`panel_check` 2/260 既有失败 | `baseline.md` |
| P1、P2 | 打开文件夹、会话根 `summary/` 副本、录像选项与 `recording` 块 | `a/handoff-patches.zh.md`、`a/logs/` |
| P2b | 逐帧口径三轴均超 5% 门槛;保持窗口口径 FN、FX 基本在门槛内、FY 不在;本机 `cv2` 写不了 H.264 | `d/p2b-codec-equivalence.zh.md` |
| P3 | 控制器:队列、六项 Ready、单一主动作带原因、重测/跳过/停止 | `b/notes.md` |
| P4 | 新采集页只经控制器渲染,`ui/` 中 `campaign.phase` 零命中 | `c/verification.md`、`c/screens/` |
| P5 | 「几何与安装」页去掉测点表,操作表迁到队列;`panel_check` 由 2/260 失败变为 260 全过 | `c/verification-p5.md` |
| P6 | 三份格式/契约文档、`workflow.md`、README | 本目录各文档 |
| 验收 | 无阻断;2 项重要 + 1 项次要,修复后复核全部 PASS | `v/acceptance-report.zh.md` |
当前 `pytest apps/autocal/tests` 668 项全过,`panel_check.py` 260 项全过,被改文档的 `doc_lint` 为 clean。
各检查的实际口径:V2、V4、V5、V7 通过;V3 只在本任务范围内 clean,全仓门禁另有并行外部会话引入的 findings;
V6 不适用(未改 `shared/`);V8 截图已备、待人工确认;V9 报告已交,是否切换默认编码待用户按 D2 决定。
## 验收后修正(2026-10-18,审查 F1、F3、F5)
- 会话结束(完成或停止)后,主按钮“结束会话”把页面带回空闲:队列可编辑、可开始新会话,已记录的文件不变。
- 保持时间到点由控制器 `poll()` 自动结束,参考窗口为计划时长(`t_end - t_start = 保持秒数`),与点击或计时器迟到多久无关。
- 勾选“模拟”后不需要平台、总线、相机、标定包,也不需要先对准;没有标定包时,U、V 直接作为点的 X、Y,位姿标为 `simulated`。有平台时仍须先对准。
- 仅软件验证:控制器与 Campaign 的新增测试已运行;界面测试(含新增的离线模拟用例)在本机因缺 libEGL 未能运行,须在 Windows 上补跑。