Commit Graph

  • 86996694b4 统一力值阈值处理并保留原始采样 ykcheungab 2026-10-07 12:25:12 +08:00
  • 6194778a6f 收起复用与完成态分析横幅。 ykcheungab 2026-10-07 09:37:08 +08:00
  • 97e80d3d38 为二维与三维深度色标增加每帧自适应选项。 ykcheungab 2026-10-07 09:34:57 +08:00
  • 425f0dc9e2 统一 2D/3D 深度色条为同一量程。 ykcheungab 2026-10-06 21:31:53 +08:00
  • 829d2c1b53 回放叠加改用展开底图,并统一 2D/3D 投递。 ykcheungab 2026-10-06 21:21:37 +08:00
  • 321e52f47a 改善 Studio 回放布局与传感器切换体验 ykcheungab 2026-10-06 20:18:50 +08:00
  • 425fa0d44d Fix replay 2D black canvas and journey UX clarity ykcheungab 2026-10-06 16:26:47 +08:00
  • a4313f300d 缩短两份超限文档,去掉重复内容 ykcheungab 2026-10-06 14:27:53 +08:00
  • d11441cd79 Show only the bundle's name in the sensor configuration row ykcheungab 2026-10-05 17:01:42 +08:00
  • 92b2f2fcd7 裸视频可分析:生成工作区、设置提示、逐帧力与按来源选画笔 ykcheungab 2026-10-05 16:57:02 +08:00
  • c0a10e6adb Present replay 3D with the cursor and open Analyze on a remembered video ykcheungab 2026-10-05 16:52:27 +08:00
  • 3433eb0fec Record the live box arrow grid so replay matches live paint ykcheungab 2026-10-05 15:01:48 +08:00
  • 28e955a817 把 Studio 主机协议改成单向端口,并让「从视频重算」走分析入口 ykcheungab 2026-10-05 14:00:24 +08:00
  • 25ccffd814 Discover sensor bundles from the SDK catalog, not a product list ykcheungab 2026-10-05 09:30:04 +08:00
  • a92a2ba237 Read the analysis cache before refusing a pass, and own the cache root ykcheungab 2026-10-04 16:53:50 +08:00
  • 1aa454311f Make the cached analysis views reachable over a recording ykcheungab 2026-10-04 16:04:02 +08:00
  • fbf32b5303 Serve replay playback from a cached analysis result ykcheungab 2026-10-04 14:13:05 +08:00
  • 9e20fd0277 Store the depth channel in an analysis result and serve it to the 2D depth view ykcheungab 2026-10-04 14:11:22 +08:00
  • cf49b0836e Serve replay playback from a cached analysis result ykcheungab 2026-10-04 13:38:03 +08:00
  • b6f6a06851 Add an offline analysis pass and result cache for replay ykcheungab 2026-10-04 13:37:58 +08:00
  • 6fb2f72c6e Make the rail reset zero the sensor in live and the display in replay ykcheungab 2026-10-04 11:09:22 +08:00
  • aa526c62a9 Mark every below-threshold finger sample with an idle dot ykcheungab 2026-10-04 11:09:19 +08:00
  • ca28d788c2 Return flow sample locations with an active flag and a fixed colour ramp ykcheungab 2026-10-04 10:35:56 +08:00
  • dda6c74fb7 fix the finger 2D canvas orientation and the 3D arrow size ykcheungab 2026-10-04 10:03:46 +08:00
  • 9b1a3fb922 state the finger 3D camera presets in the design export's frame ykcheungab 2026-10-01 16:56:36 +08:00
  • eb70ac7638 add the Studio element manifest and its window-matching test ykcheungab 2026-10-01 15:18:16 +08:00
  • e0156a14c6 fix Studio reset, 3D base colour and arrow-trigger defects ykcheungab 2026-10-01 14:40:33 +08:00
  • 33a3d8c9c2 add the Studio UI layout contract Studio had a colour contract with a mechanical audit but no layout equivalent, which is where the recurring visual defects live: window flashing, the label under the 3D view, the platform-not-moving report, and the 2D/3D sync work all passed a 3,408-assertion tool suite because that suite asserts widget state, not the visible result. apps/studio/docs/ui-layout.md states the contract: ViewLayoutController owns the top-level arrangement and each panel owns its internal layout; every path that changes the view must go through apply_preset(); each view's pane priority and degradation order; and which numeric geometry arguments are legitimate (a 1px separator is not the same defect as a magic width on a flexible panel). It also records the one transition that is broken today. on_cal_panel_toggled(False) sets view_preset = "live" directly instead of calling apply_preset("live"), so the replay -> calibration -> calibration-off sequence leaves the rail on the replay source, the run button in playback role, and the previous view's splitter sizes. The live branch does call apply_preset, which is what makes the asymmetry easy to miss. Three claimed enforcement gaps are stated so the contract is not mistaken for something already enforced: audit_palette reads theme JSON and never scans source for the hex literals ui-palette.md forbids; the containment test compares width and height without x/y, so a shifted or overlapping child passes; and the GL pixel suite skips invisibly without a real context. Verified: doc_lint default and --all both exit 0; the new document is registered in the workspace inventory (63 collected, 63 inventoried, 0 missing) and linked from README.md beside the palette reference. No source file changed. ykcheungab 2026-10-01 11:59:00 +08:00
  • d914b81242 migrated sensor processing to stream added agent doc and coding rules ykcheungab 2026-09-30 21:22:41 +08:00
  • f875378503 docs: state where this repository's documentation rules live ykcheungab 2026-09-30 16:12:34 +08:00
  • 3dae1b0117 tools: delegate documentation checks to the workspace doc gate ykcheungab 2026-09-30 16:12:30 +08:00
  • e556c131bf fixed axis 4 warning zhangyijian 2026-09-29 17:41:11 +08:00
  • 720bd81031 fixed window flashing bug ykcheungab 2026-09-28 21:17:26 +08:00
  • b8dc5e925b docs: repoint architecture docs at the workspace docs tree; ignore .scratch/ ykcheungab 2026-09-27 21:14:20 +08:00
  • fa2703968e added ruff test ykcheungab 2026-09-27 19:20:43 +08:00
  • bcab2790bf modify 3d view to sync / identical to 2d view ykcheungab 2026-09-27 10:42:38 +08:00
  • 3daee6d407 fixed display problem ykcheungab 2026-09-25 17:53:07 +08:00
  • 12781854ca fixed commetn from songxiao ykcheungab 2026-09-25 17:46:37 +08:00
  • 6effe74172 move controller connect to popup dialog ykcheungab 2026-09-24 21:36:24 +08:00
  • 4dad413d29 Merge branch 'main' of ssh://892887cvmk08.aicp.io:24928/orisys-tech/orisys-app ykcheungab 2026-09-23 10:00:13 +08:00
  • 3347d17a20 added motion trajectory simulation ykcheungab 2026-09-23 09:58:00 +08:00
  • b3279cc2a6 fixed platform not moving problem zhangyijian 2026-09-23 09:52:45 +08:00
  • f1bec62ca4 added motion simulator feature ykcheungab 2026-09-21 16:11:45 +08:00
  • ccedb9ddd9 migrate pyqt5 to pyside6 ykcheungab 2026-09-21 09:34:56 +08:00
  • e276b5de0f autocal layout first design ykcheungab 2026-09-20 21:15:39 +08:00
  • 07d7c3bad6 official 0.1.0 version ykcheungab 2026-09-16 09:43:57 +08:00
  • c538dbf074 fixed label under 3d view ykcheungab 2026-09-15 21:50:37 +08:00
  • c2293d4d41 界面:三维可选「深度响应 / 光流幅值」上色(默认深度),并加一个「诊断信息」开关收拾面板脚下的文字 ykcheungab 2026-09-15 21:23:18 +08:00
  • 0b87e3e36d 界面:三维显示设置对话框的关闭按钮说中文(Qt 自带翻译没有被安装) ykcheungab 2026-09-15 20:57:21 +08:00
  • 650578d738 界面:三维面板只留画面与图例,全部配置进「三维显示设置」对话框;三维占比提为最宽 ykcheungab 2026-09-15 20:55:33 +08:00
  • d539ce93ad 文档:阶段记录补上「阶段 10:app_window 按职责拆分」与遗留项 ykcheungab 2026-09-15 10:03:05 +08:00
  • b37c861254 文档:刷新架构报告的指标与结论(app_window 拆分已落地) ykcheungab 2026-09-15 10:02:37 +08:00
  • 81e39ca868 修复:设备的"清零已生效"确认从未被记录(worker 的 on_reset_applied 没有接线) ykcheungab 2026-09-15 09:58:45 +08:00
  • c62452ec4b 拆分收尾:标定归属规则移入 CalibrationService(app_window 2340→2304 行) ykcheungab 2026-09-15 09:53:47 +08:00
  • 0a42a452dd 拆分 C2:显示偏好移入 DisplayPrefsController(app_window 2347→2340 行 / 净减 110 行代码) ykcheungab 2026-09-15 09:50:25 +08:00
  • aecde67c73 拆分 C1:面板布局与视图预设移入 ViewLayoutController(app_window 2499→2347 行) ykcheungab 2026-09-15 09:45:05 +08:00
  • fbf808d878 拆分 B:回放策略移入 ReplayController(app_window 2837→2499 行) ykcheungab 2026-09-15 09:38:40 +08:00
  • a35d1877d6 拆分 A2:会话状态与生命周期移入 SessionController(app_window 2900→2837 行) ykcheungab 2026-09-15 09:23:52 +08:00
  • 384e24514b chore(studio): 去掉分拆后不再使用的 settings_from_mapping 导入 ykcheungab 2026-09-15 08:52:32 +08:00
  • c2671e5c2f refactor(studio): 分拆 app_window 第一步 —— 会话事实(记录元数据/滤波器/采集协商)搬进 session_metadata ykcheungab 2026-09-15 08:51:17 +08:00
  • 8d374e5850 refactor(studio): 回放引擎改名 ReplayEngine —— 把「引擎」与「窗口级控制器」的名字分开 ykcheungab 2026-09-15 08:41:44 +08:00
  • dd0ee2c71e docs(architecture): Studio 架构图与可维护性评估(按源码实际 import 关系生成) ykcheungab 2026-09-15 08:25:20 +08:00
  • cf45e654e1 docs(studio-3d): 阶段 7–9 交付说明 + 接口骨架同步 + 保护包的应用侧配置 ykcheungab 2026-09-14 22:02:43 +08:00
  • ef487098c9 feat(studio-3d): 限速、真实耗时统计、无位移复用资产几何,以及保护包密钥注入(阶段 9 上半) ykcheungab 2026-09-14 21:52:02 +08:00
  • e2570925e8 feat(studio-3d): 能力控件、真实色标与复位语义 —— 只提供数据源真正喂得出来的控制(阶段 8 第一批) ykcheungab 2026-09-14 21:35:47 +08:00
  • 7e415c4ae9 feat(studio-2d): 回放可一键重算;实时界面不再有任何回放入口 ykcheungab 2026-09-14 21:28:07 +08:00
  • 8e6f9658b4 refactor(studio-2d): 二维图像单一后端 —— 视图模式由帧内容决定,四条路径共用一个渲染入口 ykcheungab 2026-09-14 21:24:28 +08:00
  • bf643de93d docs(studio-3d): 阶段 6 交付说明(正确性与失效状态)+ 接口骨架同步 ykcheungab 2026-09-14 21:13:57 +08:00
  • 3c0424b90c feat(studio-3d): 设备几何身份接入 —— 同尺寸不同映射/不同表面被拒绝,资产加载只采用最新请求 ykcheungab 2026-09-14 21:10:53 +08:00
  • 7d1fca4c93 fix(studio-3d): 字段级来源与失效状态 —— 旧光流/算法失败不再冒充实时测量 ykcheungab 2026-09-14 21:03:07 +08:00
  • d06e116c15 fix(studio-3d): 取消 epoch —— 停止/隐藏/换会话之后,已在跑的后台合成不能再把旧测量放回屏幕 ykcheungab 2026-09-14 20:53:38 +08:00
  • 6de6b8b703 feat(studio): 回放可以直接播放「一条视频」——不必先按「从视频重算」 ykcheungab 2026-09-14 20:21:13 +08:00
  • c9a28e7c3a fix(studio-3d): 审计三维改动 —— 修「第一个会话之后画面永久冻结」,并把面板/视图接口显式化 ykcheungab 2026-09-14 19:58:33 +08:00
  • eb1dfbce8a fix(studio): 回放视图加一个无条件「停止」(Esc 同效),并让「文件一帧都不出」直接报错而不是一直等 ykcheungab 2026-09-14 17:40:49 +08:00
  • a26f77bb67 fix(studio): 回放视图里选了不是记录的视频时,明确说明原因并给出可行路径 ykcheungab 2026-09-14 17:21:29 +08:00
  • 981c250559 feat(studio-3d): 历史回放里的三维面板 —— 由「重算」通道喂它,并标注来源 ykcheungab 2026-09-14 17:13:37 +08:00
  • 2226bdde68 docs(studio-3d): 阶段 5 补全测试计数,并记录与本功能无关的 Windows 计时器粒度失败 ykcheungab 2026-09-14 16:49:30 +08:00
  • 8c8a97e8d6 docs(studio-3d): 阶段 5 兼容说明补齐「最小交付」与「客户包无私钥」的证据来源 ykcheungab 2026-09-14 16:49:07 +08:00
  • 5cd92254e1 feat(studio-3d): 阶段 5 —— 发布保护(签名/可选加密/平台锁定)与性能长时报告 ykcheungab 2026-09-14 16:44:33 +08:00
  • ac4dcd643f feat(studio): 三维视图接入产品(阶段 4:界面、生命周期与记录隔离) ykcheungab 2026-09-14 15:47:32 +08:00
  • ea1027e5a6 阶段 3:实时数据 —— 采样、合成、最新帧调度与适配器 - What: sampling.py 实现 SurfaceSampler(向量化四点双线性:无效 tap 不计入也不致无效、任一**正权** tap 无效即整顶点无效且值为 NaN、域/形状不符先拒;采样表只在加载时构建)与 remap_to_domain(按 unroll 映射 gather,空洞保持 NaN 且 valid=False,保留尾随轴以支持矢量场)。composer.py 实现 SceneComposer.compose(域校验 → 采样 → 位移 → **从形变后位置重算法线**;scalar 绝不成以 display_gain;approximate 位移产生 display_illustrative(info) 诊断、calibrated 不产生、无位移则着色并报 field_unavailable)与 build_rest_frame(参考帧:scalar 全 NaN、valid 全假、descriptor=unavailable/display_only),gl_backend.rest_surface 改为委托它,避免两处定义。coordinator.py 实现完整调度:容量 1 双 mailbox、generation/来源/会话三重过期拒绝并计数、**最多一次合成在飞行**、可见性挂起/恢复、设置变更(倍率变化重合成**屏幕上那一帧**)、close 幂等;submit 只做身份与形状的廉价校验,绝不等显示。adapters.py 实现 Finger/Box 适配器:|flow| 或 depth 响应、可选经 unroll 映射 remap、设备 valid_mask 优先、域不符时**发布空**并报出所见的两个形状;一律不造位移(未标定的毫米不如没有),并新增 display_illustrative 到诊断码表(info)。assets.py 从"桩类堆"改成门面:re-export 真正实现 + SceneCompiler.compile(外壳 GLB 与预算检查;加密请求明确拒绝并指向阶段 5)。测试:shared/tests/test_scene3d_live.py 25 项(采样/无效语义/倍率只影响位置/示意形变标注/法线重算/remap 空洞/参考帧/只读数组)、apps/studio/tests/test_scene3d_coordinator.py 13 项(状态机、过期、替换计数、单飞行、submit p99<1ms、切换场景丢弃在飞结果、隐藏挂起、倍率重合成、close 幂等)、test_scene3d_adapters.py 13 项;并把三处"桩仍未实现"的过时断言改成对已实现行为的断言。docs/architecture/studio-3d-stages.md 阶段 3 记为完成。 - Why: 阶段 3 决定"屏幕上的颜色和力值到底对应什么"。三条最容易错且肉眼看不出来的规则必须由代码而不是约定来保证:无效点不是 0(NaN 与 valid 分离,否则缺口会被画成一个测量值)、显示倍率只动位置不动测量(否则色标悄悄变了含义)、以及显示永远不能给采集施加背压(容量 1 mailbox + 单飞行合成 + 廉价 submit)。 - Verification: shared 全量通过(scene3d 相关 103 项:contracts 26、assets 33、package 19、live 25);studio 全量通过(scene3d 相关 54 项:stage1 24、skeleton 4、coordinator 13、adapters 13;GL 冒烟 10 项在真实 context 下另行通过);ui_check 554 项 0 失败。过程中修掉 3 个真实缺陷:倍率变化时用 incoming.peek() 取不到"屏幕上那一帧"(已被消费)导致改了倍率画面不动——改为保留 _last_field_frame;display_illustrative 不在码表里被默认成 error;assets.py 里与 package/legacy 同名的重复桩类(会误导集成者,已改为门面)。另修正两处我自己的错误断言(对称凸起顶点法线本就竖直;pytest.approx 不能直接 .all())。 - Not in this change: 手势与面板(阶段 4);示意位移的真实来源(需要 SDK lift + 已确认尺度,现阶段 adapter 一��� displacement=None);app_window 接线与记录隔离(阶段 4)。 ykcheungab 2026-09-14 15:21:39 +08:00
  • d0dcac743f 阶段 2:资产合同 —— 旧 NPZ 导入、坐标/单位、绑定、包容器与工具 - What: shared/scene3d 新增四个模块与两个工具的真实现。digest.py:规范化内容摘要(C 连续小端字节 + 排序 JSON;DIGEST_SCHEMA 版本化),geometry_id(只看 part 角色/顶点/三角形,故意不含法线材质与 LOD 名——改颜色不得让标定失效)、binding_id、field_domain_id(形状、flip_v、source_scale、原点/索引约定、映射内容)、asset_hash(条目摘要 + 去自引用字段的 manifest)。legacy.py:LegacyFingerImporter 按"先单位、后刚体"两步转换(meters_per_source_unit 必填且不得猜、validate_rigid_transform 拒绝含缩放/镜像/非仿射的 4x4);split_at_uv_seams 按 (源顶点, 量化 UV) 拆点,接缝处不平均 UV;vertex_normals 面积加权并给无面顶点有限回退;uv_to_pixel 采用实测约定 x=u*(W-1)、y=(1-v)*(H-1)(flip_v 时);build_binding 生成四点双线性采样表(域内裁剪、零权 tap 丢弃并重分配、未用项 -1/0);uv_correspondence_residual 用 Procrustes 残差把"UV 是否对得上 world_xyz"变成可断言的事实;estimate_meters_per_source_unit 只报告不应用。glb.py:glTF 2.0 子集读取(GLB 头/chunk 校验、accessor/bufferView/stride、节点 TRS 与矩阵烘焙进顶点、法线用逆转置再归一化、缺法线则计算),明确拒绝 Draco/meshopt/量化扩展、稀疏 accessor、外部 buffer、骨骼、形态目标、非 TRIANGLES、越界索引、非有限坐标与超预算三角形;with_housing 把外壳作为独立 part 加入且不动感知几何 id。package.py:.oryscene(zip:envelope.json + manifest.json + sensing/housing *.npy,无 pickle 无代码执行),写入原子(先写 .partial 再替换)、读取逐一校验 magic/envelope 版本/payload 状态/条目路径/manifest 自哈希/逐条目 sha256/dtype/shape/**从包内数组重算 geometry_id 与 binding_id**,加密封套明确报"本版本只开明文包"。tools/scene_assets/validate_scene.py 与 compile_scene.py 真实现(含 --json 报告与退出码:0 通过、2 检查失败、1 参数/文件问题;未给单位时停在 needs_units 并打印实测候选尺度)。测试:shared/tests/test_scene3d_assets.py 33 项(真实 1032 导出与 1005 对比资产的输入样例、接缝/法线/绑定/单位/变换/摘要的失败样例、GLB 往返与拒绝样例)、test_scene3d_package.py 24 项(往返、外壳条目、以及篡改条目/篡改 manifest 哈希/自洽但错误的几何/非有限坐标/多余条目/缺条目/路径穿越/dtype 与 shape 不符/未来版本/加密封套/key provider 的拒绝)。docs/architecture/studio-3d-stages.md 阶段 2 记为完成并附验收结果。 - Why: 阶段 2 要回答的是"这份几何到底是什么、单位是什么、它和哪套 UV 域配套、以及这份包有没有被动过",这些一旦错了会以"三维看起来有点怪"的形式出现且事后无法追溯。因此把 UV 约定做成可测量的事实(真实资产残差 0.0064 vs 错误翻转 0.59)、把单位做成必填参数而不是默认值、把 identity 做成内容摘要并**在读取时重算**,同时用真实资产当输入样例、用篡改后的包当失败样例。 - Verification: 真实资产导入:1032 顶点/1974 三角形/域 300×280/尺寸 15.81×15.0×7.5 mm,validate_assets 通过;UV 残差 0.0064(错翻转 0.59);两套资产混用被拒(geometry_mismatch);缩放/镜像/非仿射变换被拒;compile→validate 端到端 ok(包内条目哈希与 manifest 自哈希全部通过);shared 全量 244 passed;studio 118 passed;ui_check 554 项 0 失败;.repro/verify_assets.py 22 项 0 失败。过程中修掉 5 个真实缺陷:np.load 需要真实文件对象(手写内存 stub 抛空消息异常)、包可以"自洽却错误"(改数组+改哈希后无人重算几何 id,现已重算并比对)、binding 行数不匹配被误报为域错误、np.ptp 平铺比较无意义(换算/不变性断言改为按轴)、以及合成 PoC 场景自己用字符串当 geometry_id(所有人都会抄它,等于绕过检查)。 - Not in this change: 签名与 AES-GCM(阶段 5);LOD 简化;calibration.py 侧的资产哈希登记;三维视图仍未被应用导入(阶段 4 接线)。 ykcheungab 2026-09-14 15:08:29 +08:00
  • cad795c890 补阶段 1 的失败样例:软件渲染被拒、着色器编译失败只降级三维 - What: test_scene3d_gl_smoke.py 增加两项失败样例。其一:把 is_software_renderer 打桩为真后调用 GLDispatch.bind_current_context,断言抛 ThreeDUnsupported、诊断码为 gl_unsupported、且异常带回的 GLCapabilities 明确标着 software_renderer=True(板卡不得在软件光栅器上签收)。其二:把 shader_source 打桩成非法 GLSL,断言 GLRenderer.initialize 抛 diagnostic.code=shader_compile_failed 且 is_initialized 保持假;再用一个 initialize 必然失败的 renderer 构造真实 widget(走公开 API:show/repaint/processEvents),断言视图进入 unavailable、诊断已发出、第二次 repaint 是空操作而不是异常——即三维失败只降级自己,不影响应用。 - Why: 规格 §13 要求每阶段交付含失败样例;阶段 1 的两个失败路径(GL 不足、shader 编译失败)此前只有实现没有验收,而这两条恰恰是板上最可能遇到的情况。 - Verification: 真实 context 下 tests/test_scene3d_gl_smoke.py 10 项全过(原 8 + 新 2);offscreen 下该文件按设计 skip;shared 全量 187 passed、studio 全量 118 passed、ui_check 554 项 0 失败。 - Not in this change: 未改实现(这一版只是把已有失败路径钉进测试)。 ykcheungab 2026-09-14 14:57:56 +08:00
  • 5767b4bf11 阶段 1:平台 PoC —— 真实 GL 上跑通静态场景,并修掉它抓出的相机/上下文错误 - What: 按 docs/architecture/studio-3d-stages.md 阶段 1 落地图形栈。OrbitCamera 全部实现(target/distance/yaw/pitch 表示;fit 按包围球与较窄的那个视场轴定距离;orbit/pan/zoom 有界;set_preset 从 eye/target 反解角度使拖动可延续;matrices 输出只读 float32 view/projection;另加 depth_range_m() 便于断言)。gl_backend:GLDispatch.bind_current_context 真正绑定 PyOpenGL 并读 vendor/renderer/GL/GLSL/max texture、判软件渲染、profile 不达标时抛 ThreeDUnsupported 并带回能力;GLRenderer 实现 initialize(两个 program,desktop33/gles30 共用同一份着色器主体、只换头部)、upload_scene(每 part 一套 VAO/VBO/EBO,交错 layout position/normal/scalar/valid = 8 float)、update_surface(只对感知面 glBufferSubData,绝不重建索引)、draw(绑 Qt 的 defaultFramebufferObject、深度测试、按 role 选 program、housing 隐藏即跳过、scalar 经 LUT 纹理与 range uniform 上色,Python 不生成 RGB 数组)、release(幂等);纯函数 pack_part/pack_scene_part/estimate_gpu_bytes/rest_surface/shader_source/shader_header/lut_rgba/is_software_renderer/profile_from_renderable_type 都可在无 GPU 环境测试。Sensor3DWidget 实现 initializeGL/paintGL/resizeGL/release_resources/aboutToBeDestroyed/showEvent/hideEvent 与鼠标事件;加载场景时先采用资产自带的默认视角再 fit;一帧几何不匹配只"拒绝该帧"(计数 + 诊断 + 用 rest_surface 清掉上次测量)而不是把整个视图打成不可用。shared/scene3d/synthetic.py 提供 PoC 与测试共用的已知场景(20mm 感知板 + 外壳盒,米、sensor 坐标系、带默认三视角预设)。tools/scene_assets/probe_gl.py 真实现:真实窗口取 context 并输出 JSON 报告(平台/Qt/PyOpenGL/vendor/renderer/GL/GLSL/DPR/framebuffer/是否软件渲染),退出码即门禁(0 通过、3 软件渲染、4 无上下文);apps/studio/pyproject.toml 增加 [three-d] 可选依赖(仅 PyOpenGL;Open3D 不进运行时)。测试:apps/studio/tests/test_scene3d_stage1.py 24 项(相机数学、PoC 场景合规、打包布局、着色器两方言共用主体、LUT、软件渲染识别、renderableType 映射、GPU 预算、参考帧语义,全部无需 GPU)+ test_scene3d_gl_smoke.py 8 项(真实 context:能力、两个 part 都画出来、外壳可隐藏、参考帧中性 vs 色图有差异、动态更新不动 housing 缓冲、坏几何帧被拒且视图存活、resize 跟随、release 幂等且新 context 能重传场景、探针如实报告);offscreen 下后者自动 skip。docs/architecture/gl-report-windows-dev.json 为本机平台报告。 - Why: 用户要求"从骨架搭起→计划阶段→逐阶段实现与验收",并明确 ARM 暂缓。阶段 1 的产出本身就是一份能回答"这块板的图形栈能不能用"的报告,所以先把它做实、把 profile 冻结点定在真实 context 上,而不是先堆屏幕上的东西。 - Verification: 真实 context(QT_QPA_PLATFORM=windows)下 8 项 GL 验收全过;offscreen 下 24 项 CPU 项全过、GL 项按设计 skip;shared 全量 187 passed;studio 全量 116 passed(含新 32 项);ui_check 554 项 0 失败;probe_gl 输出 verdict=ok(NVIDIA RTX 4080 SUPER、GL 3.3 Core、GLSL 3.30、非软件渲染)。过程中这套验收抓出并修掉了 7 个真实缺陷:Qt renderableType 映射反了(NVIDIA 兼容 ES 着色器所以本机看不出,Intel/ARM 会直接失败)、观察相机是左手基导致整模型镜像(det=-1)、近平面过近使 NDC z 全挤在 0.99(深度精度归零、外壳与感知面会 z-fighting)、手动 makeCurrent 早于 Qt 首次绘制会把 GPU 对象建在被丢弃的 context 里(glBindVertexArray 报 INVALID_OPERATION)、context 重建后不重传场景、一帧几何不匹配把整个视图打死、以及 grabFramebuffer 是 BGRA+stride 导致颜色断言全部读错通道。 - Not in this change: 未接入 app_window/main.py(三维对现有应用仍完全惰性,故"开/关三维 CSV 行序一致"这条验收随阶段 4 做);阶段 2 的资产导入/坐标/包格式;ARM 平台实测(probe 已可在板上直接运行)。 ykcheungab 2026-09-14 14:55:45 +08:00
  • 0b309e7edf 三维显示骨架:可执行合同 + 模块签名 + 分阶段计划 - What: 按 docs/architecture/studio-3d-design.md 与 studio-3d-contracts.pyi 搭建骨架(阶段 0)。shared/src/orisys_app_shared/scene3d/:contracts.py 把 .pyi 的规格变成可执行代码——全部 DTO/枚举(FrameIdentity/FieldDescriptor/SurfaceFieldFrame/MeshPart/SurfaceBinding/CameraPreset/ExpectedGeometry/SceneAssets/ColorScale/DisplaySettings/VertexFields/RenderFrame/CameraMatrices/GLCapabilities/RenderStats/CompileOptions/CompileReport)、标准诊断码表(DIAGNOSTIC_SEVERITY,含 stale_frame=info、field_unavailable=warning 等)、ThreeDError、freeze()(拥有数据 + C 连续 + 只读)、五个校验器(validate_surface_frame/validate_binding/validate_mesh_part/validate_assets/validate_display_settings/validate_render_frame)、LatestFrameMailbox(容量 1、替换计数、锁内只换引用)、UNAVAILABLE_DESCRIPTOR/rest_descriptor() 与 summarize_parts(),以及全部 Protocol;该模块不 import Qt/OpenGL/SDK。assets.py/sampling.py/composer.py 为签名骨架(未实现处抛 NotImplementedError 并写明归属阶段)。apps/studio/src/orisys_studio/scene3d/:camera/adapters/coordinator/gl_backend/widget/panel 签名骨架,其中 configure_surface_format() 已实现(QApplication 之前调用,desktop33=GL3.3 Core / gles30=GLES3.0,深度 24、无 MSAA);模块级一律不 import GL 绑定,PyOpenGL 只在 GLDispatch.bind_current_context 里加载。tools/scene_assets/{probe_gl,validate_scene,compile_scene}.py 提供入口与参数,未实现时输出 not_implemented 并返回 2(避免被 CI 当成通过)。docs/architecture/studio-3d-stages.md:阶段 0-5 的产出、每阶段的验收矩阵、并行度与回归门。测试:shared/tests/test_scene3d_contracts.py 26 项(freeze 拥有数据、可写/非连续数组被拒、scalar 与 valid 形状一致、位移缺 quality 被拒、非有限 scalar 标为 valid 只报警不静默、绑定越界/-1 约定/权重和为 1、三角形索引越界、感知法线单位长度、恰好一个 sensing part、binding 必须属于 sensing part、设备几何/域/尺寸不符被拒、色标 lower<upper、邮箱替换计数与多线程不阻塞生产者、身份区分 source/generation、骨架可导入且不引入 PyOpenGL);apps/studio/tests/test_scene3d_skeleton.py 4 项(骨架不 import OpenGL、surface format 两个 profile、widget 构造不需要 context、各 stub 报出归属阶段)。 - Why: 用户要求"先从骨架搭起,然后计划功能实现顺序,最后逐阶段实现与验收"。骨架阶段的价值在于把接口变成可执行的(.pyi 本身无法被测试),并把"数组只读、单位与质量必须声明、近似位移不得当毫米、参考帧不得冒充测量、显示丢帧不得计入记录丢帧"这些容易在实现时被绕过的规则先固定下来;同时明确不 import Qt/OpenGL 的边界,使采集线程与 CI 能在无 GPU 环境导入它。 - Verification: shared 全量 187 passed(原 161 + 26);studio 95 passed(原 91 + 4);ui_check 554 项检查 0 失败;compileall 通过;probe_gl --profile desktop33 在未实现时返回 2 并说明将产出什么报告。 - Not in this change: 三维功能本身(阶段 1 起);app_window/main.py 未接线,骨架对现有应用完全惰性(无任何模块被应用导入)。 ykcheungab 2026-09-14 14:32:38 +08:00
  • c10e579371 update 3d scafold ykcheungab 2026-09-14 13:55:36 +08:00
  • 4a482e579f 按"硬件问题、配置不变"收尾:记录里写下实测帧率,探针加逐颗验收模式 - What: worker 把本会话实测到的采集速率记进 self._measured_capture_fps(box 与 finger 两条循环都算:finger 的帧率也从"1 秒窗口内计数"改成 (n-1)/span 的速率,两条视图对 fps 的含义必须一致),capture_status() 因此多出 measured_fps —— session.json 的 capture 块现在同时有 requested / negotiated / measured_fps / dropped_frames,一颗摄像头到底交付了多少 fps 由记录自证。status.capture_slow 的中英文案改写:删掉已被实测否证的"降低分辨率/换格式"建议,改成"这是采集端(摄像头/驱动)的问题、软件改不了,换一颗真能交付 30 fps 的(并先确认线材/端口没把它降速)再测"。tools/camera_probe.py 新增 --check 验收模式:只测 bundle 用的那一个格式(MJPG 1920x1080@30,120 帧),按**实测**速率打印一行 PASS/FAIL 并给出退出码,用于逐颗过检;无摄像头时报 "No camera answered on index N (is the app closed?)" 并返回 1。tests:假设备用上 capture_status() 的 measured_fps(0.05 s 粒度等它出现,断言 20–35 fps)、探针 --check 的无摄像头路径。 - Why: 用户判定这是驱动/硬件问题(同型号产品此前能跑 30 fps),决定**配置不变**、以后用能到 30 fps 的产品,并在之后逐颗检测。配置不变意味着应用继续按 30 fps 申请(这是对的:申请 15 只会把问题上移),但由此产生两件必须收尾的事:(1) 提示语里"降低分辨率/换格式"是错的建议——探针已经证明 720p MJPG 一样是 15 fps、YUYV 只有 1 fps;(2) 既然要逐颗检测,就必须有一个能回答"这一颗交付了多少"的权威数字,而驱动自报值不可信(DSHOW 回读),所以把实测值写进记录并给探针一个单行验收模式。 - Verification: shared 全量 161 passed;studio 91 passed;ui_check 554 项检查 0 失败;locale_check 中英键集一致;探针 --check 在无摄像头时打印指定文案并返回 1,--help 列出该选项。 - Not in this change: 未改任何采集配置(resolution_raw/fps/fourcc 都不动);未做自动降级;frames.csv 仍未记录 process_ms/capture_fps(留到加未滤波 17 列、记录格式升版时一起)。 ykcheungab 2026-09-14 13:48:08 +08:00
  • 30ef50814f 探针增加尺寸扫描:区分链路带宽不够与摄像头本身就是 15 fps - What: tools/camera_probe.py 新增 SWEEP_SIZES 与 size_sweep(),在帧率扫描之后对 MJPG@30 依次申请 1920x1080/1280x720/960x540/848x480/640x480 并打印实测速率,提示语写明判断方式:帧越小越快 ⇒ 链路带宽是瓶颈;每个尺寸都一样 ⇒ 是摄像头本身的速率上限。 - Why: 用户实测 MJPG 在 1080p 与 720p 都是 15 fps、YUYV 两档都是 1 fps,两种解释仍然都成立(链路只能扛约 3-4 MB/s,或这颗摄像头在这些格式上就是 15 fps 设备),而处置完全不同;帧率扫描只能区分"申请被砍半"与"固定上限",尺寸扫描才能区分"带宽"与"设备"。 - Verification: compileall 通过;无摄像头时仍返回 1 并给出解释;studio 全量 91 passed(探针的无摄像头用例覆盖 main() 的降级路径)。 - Not in this change: 未改 SDK 采集参数;未做自动降级。 ykcheungab 2026-09-14 13:40:04 +08:00
  • a44eb2d65d 达不到帧率的判据改用实测速率(DSHOW 回读值会说谎),探针加帧率扫描 - What: short_capture_fallacy() 改为以**实测**速率为准(新增 measured_fps 关键字参数),消息改成"it delivers about 15 fps while the config asks for 30 (1920x1080 reported 30, MJPG/DSHOW)"——把驱动自报值降级为"矛盾的一部分"而不是判据;app_window 的 _check_capture_negotiation() 相应改为用 packet 的 capture_fps(实测)判据、实测为 0 时不下结论(等窗口填满)。tools/camera_probe.py 重写:probe() 返回结构体、format_row() 把 reported 与 measured 分列并在达不到 90% 时标 "<-- misses the request";新增 --samples 与**帧率扫描**(同一格式依次申请 60/30/20/15/10 fps,逐行给实测值与"跟随申请 / 只有申请的 x 倍"的判断,最后给"本次看到的ceiling")。tests/test_worker_frame_rate.py 的归因测试同步更新,并显式覆盖"驱动自报 30 而实测 15 必须判为不达标"与"没有实测值时不说话"。 - Why: 用户跑探针的结果推翻了我上一轮的判据:MJPG 1920x1080@30 -> MJPG 1920x1080 @ 30.0 measured 15.0。DSHOW 后端下 CAP_PROP_FPS 只是回读你设的值,所以 SDK 的 status.fps 同样不可信 —— 我原来比较"申请 vs 协商"等于拿申请跟自己比,那个提示永远不会出现(用户实际看到的只有轨道上的实测 16.7)。这也解释了为什么这件事必须靠实测:真实速率只能测出来。另外探针显示 1280x720 MJPG 实测也是 15、YUYV 只有 1 fps,说明换分辨率不解决问题,需要区分"摄像头本身的上限"与"我们申请多少就被砍半"。 - Verification: shared 全量 161 passed(含新增/更新的 3 项断言);studio 91 passed;ui_check 554 项检查 0 失败;探针在本机(无摄像头)仍返回 1 并给出解释,format_row/_decode 单测通过("reported 30.0 measured 15.0 ... <-- misses the request")。 - Not in this change: 未改 SDK 采集申请顺序/后端;未做自动降级;frames.csv 仍未记录 process_ms/capture_fps。 ykcheungab 2026-09-14 13:38:57 +08:00
  • 3db55b2077 新增摄像头能力探针 tools/camera_probe.py,用来把 16.7 fps 归因到格式协商 - What: 新脚本对每个摄像头索引 × 两种后端(DSHOW/MSMF)× 五组请求(MJPG 1920x1080@30、MJPG 1920x1080@auto、MJPG 1280x720@30、YUYV 1920x1080@30、YUYV 1280x720@30)实际打开并连续抓 60 帧,逐行打印"申请 → 驱动协商到的 fourcc/尺寸/帧率 + 实测帧率",这样\"协商到 30\"与\"真的给到 30\"是两个分开的事实;没有摄像头时给出明确说明(含"要关掉 app,两个进程不能同时占用同一个摄像头")并返回 1;--help/--index/--max-index 可用。tests/test_ui_smoke.py 增加 test_the_camera_probe_is_usable_without_a_camera:无摄像头时返回 1 且输出解释、fourcc 编解码往返正确('MJPG' → 'MJPG'、0 → '-')。 - Why: 用户按上一轮读数确认"还是摄像头 16.7",即摄像头自身只发布 16.7。配置申请的是 resolution_raw=1920x1080、fps=30、fourcc 走 CaptureConfig 默认 MJPG、backend=auto;SDK 已在 open 里做了 width/height → fourcc → fps → fourcc 再设一次 + warmup 后校验并在 fourcc 不符时重试一次,但仍存在两种可能:这颗摄像头在 1080p 下就只有 16.67(UVC 常见),或者它在 MJPG 之外落到了别的格式/后端。这两种的处置完全不同(前者要改分辨率/换摄像头,而 1920x1080 是 5x3 瓦片网格的结构参数,改它要重做拼接标定;后者是申请顺序/格式的问题,可以在 SDK 侧修),而在应用里看不出来。因此需要一个不经过应用的独立探针,把驱动真正提供的东西列出来。 - Verification: 本机(无摄像头)运行 --index 9 → 输出 "No camera answered on indices 9..9." 与关闭 app 的提示、退出码 1;--help 正常;_decode(_fourcc_code('MJPG')) == 'MJPG'。studio 全量 91 passed(新增 1 项);ui_check 554 项检查 0 失败。 - Not in this change: 未改 SDK 的采集申请顺序或后端选择(等探针结果:若显示 MJPG+1080p 下驱动只给 16.67,则是硬件边界;若显示申请 MJPG 却协商成 YUYV,再改 SDK);未做分辨率自动降级。 ykcheungab 2026-09-14 13:30:21 +08:00
  • 50122d19a2 把摄像头协商结果记进 session.json,并在达不到配置帧率时明说 - What: sdk_worker 新增 capture_status_of(sensor)(把采集源的 config 与 status 合成 {requested:{width,height,fps,fourcc}, negotiated:{width,height,fps,fourcc,backend}, dropped_frames})与 short_capture_fallacy(status)(当协商帧率低于申请值 90% 时给出一句话描述,含分辨率/实际帧率/申请帧率/格式/后端;两者都齐全时才说话);两个 worker 都加 capture_status() 访问器(box 取当前 sensor,finger 取 runtime.tactile.raw_sensor)。app_window:session.json 新增顶层 capture 块(_capture_metadata(),走同一个访问器);新增 _check_capture_negotiation()——每个会话在驱动第一次报出速率后检查一次,达不到就通过 _notify 明确提示(不是每帧刷屏),新键 status.capture_slow(中英)写明"软件改不了、记录按实际帧率保存、请降分辨率/换格式或换摄像头"。tests/test_worker_frame_rate.py:假设备补上 CaptureConfig 与 CaptureStatus 的 width/height/fps/fourcc/backend_name 字段,新增 test_a_low_camera_rate_is_recorded_as_a_negotiation(1920x1080、申请 30、协商 16.67 时 capture_status() 两半都在、描述里含 1920x1080/16.67/30;协商到位时描述为空串)。 - Why: 用户按上一轮的读数报告"还是摄像头 16.7",即摄像头自身只发布 16.7 —— 这排除了流水线(每帧只 12 ms)与应用侧节拍,剩下的只能是采集格式协商:配置申请的是 resolution_raw 1920x1080 @ 30 fps(fourcc 走 CaptureConfig 默认 MJPG)。这类"驱动只提供这个格式"的情况软件无法改变,但记录里必须留下证据、操作员必须被告知,否则一个 16.7 的采集和一次正常的 30 fps 采集在文件里长得一样,事后再也无法解释。 - Verification: shared 全量 161 passed(新增 1 项);studio 90 passed;ui_check 554 项检查 0 失败;locale_check 287 字面键全部命中、中英键集一致(343+1=344? 见下方键数)。 - Not in this change: 未改 SDK 的采集参数(仍是 1920x1080/30/MJPG、后端 auto);frames.csv 仍未记录 process_ms/capture_fps(下次改记录格式时一并加);未做自动降级(例如检测到 16.7 就自动改用更低分辨率)——那会改变传感器几何,必须由人决定。 ykcheungab 2026-09-14 13:27:33 +08:00
  • a1314a02cb 界面显示每帧耗时与摄像头自身帧率,让低帧率可归因 - What: SdkPacket 新增 capture_fps(采集源自报的发布速率,与 fps 同一窗口)与 capture_dropped(SDK 后台读取线程累计的"发布了但没人取走"的帧数);sdk_worker 的循环记录 (t0, 已发布序号) 并在每帧算出摄像头自身的发布速率,同时读 dropped_frames;新增 _capture_source()/_capture_dropped() 两个取值助手(原 _capture_sequence 里的取值逻辑抽出来复用)。app_window 在 视图▸显示帧率 打开时把这一段一起显示在轨道摘要上:16.7 FPS · 12 ms · 摄像头 30.0 (-13)——速率是我们消费的、耗时是每帧全部开销、摄像头速率是采集源自己发布的;两个速率不一致时后面的 (-N) 就是"摄像头有、我们没接住"的帧数。新增 locale 键 status.rail_camera_fps(中英)。tests/test_worker_frame_rate.py:假设备的采集源现在也像 orisys.capture.source 一样统计"发布时上一帧还没被取走"的丢帧,并支持给 get_img 加一个可配置的每帧耗时(PROCESS_COST_S);新增 test_the_camera_s_own_rate_is_reported_next_to_ours:循环跟得上时摄像头速率≈30、丢帧 0;把流水线放到 60 ms/帧后我们的速率掉到 22 以下、**摄像头速率仍报 30**、丢帧 >5。 - Why: 用户报"上方显示 16.7 fps 而不是实际 30",并且给出了关键数据:16.7 fps 但每帧只耗 12 ms。12 ms 的工作量塞进 60 ms 的周期里,说明循环大部分时间在等——而"等"有两种完全不同的原因:(a) 摄像头/驱动只发布 16.7(USB 带宽、MJPG/YUYV、格式协商),(b) 摄像头发布 30 而我们只取到一半(节拍与轮询问题)。原来的读数只有"我们消费了多少",两种情形长得一模一样,无法判断,所以这不是显示 bug 而是**读数缺一半**。SDK 的 status.last_seq 与 dropped_frames 正好提供另一半(last_seq 每发布一帧就自增,与消费者无关;dropped_frames 在发布时若上一帧仍在缓存里就 +1,即消费者的亏欠),因此把它读出来即可判定。 - Verification: shared 全量 160 passed(新增 2 项,含上面的归因测试);studio 90 passed;ui_check 554 项检查 0 失败;locale_check 287 字面键全部命中、中英键集一致。过程中自查出一个我自己引入的问题:最初把 (t0, 序号) 的记录与按时间裁剪放在**每次轮询**里,而等待期间该分支每秒会进几万次,列表推导把 GIL 抢光,假摄像头的发布速率从 30 掉到 8(test_the_same_frame_is_not_counted_twice 立刻变红)——改为"仅在该序号发生变化时记录",即为每发布帧 O(1) 而非每轮询 O(n)。 - Not in this change: 未改节拍/等待逻辑(假设备上 30 fps 与 60 ms 慢流水线两种情形的消费速率都符合预期,未发现节拍 bug);frames.csv 仍未记录 process_ms/capture_fps(下次改记录格式时一并加)。 ykcheungab 2026-09-14 12:07:28 +08:00
  • 5ef4d955fb 帧率读数改为"采集时刻"的到达率,并在旁边显示每帧耗时 - 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 列时一并加,避免连做两次版本升级);未动节拍逻辑本身。 ykcheungab 2026-09-14 11:22:44 +08:00
  • a9f75a868d 界面不摆还没接线的开关:先撤掉“同时显示未滤波曲线” - What: plot_settings_dialog 去掉"同时显示未滤波曲线"复选框(连同 reset 里的清位、retranslate 的两行文案、filter_values() 里的读取),filter_values() 里写明 show_unfiltered 暂时固定 False 及原因;两个 locale 删掉 filter_show_unfiltered / filter_show_unfiltered_tip 两个键。FilterSettings.show_unfiltered 字段保留(session.json 的 filters 块仍在记它,等第二 buffer 接上后再把开关放回对话框)。 - Why: 上一版把这个复选框放进了对话框,但"未滤波曲线"还没有数据通路——力值卡片不画、CSV 也没有 *_unfiltered 列,等于界面上摆了一个承诺了却不做事的开关;这正是这一轮一直在清理的那类问题(文件说的/界面说的与实际做的不一致)。宁可先不给入口,也不要给一个假的。 - Verification: .repro/locale_check.py:287 个字面键全部命中、两个 locale 键集合相同(341/341);studio 全量 89 passed;ui_check 554 项检查 0 失败。 - Not in this change: CSV 的 17 列与未滤波第二 buffer 仍未接线(这是把开关放回来的前提)。 ykcheungab 2026-09-14 11:10:28 +08:00
  • 92e0b95ca2 曲线图表头加齿轮,绘图设置对话框里可以直接调滤波 - What: ui/icons.py 新增 settings(齿轮)图标(八个齿 + 圆环,颜色取自主题 token,与另外三个图标同一套画法);force_plots.py 表头新增 btn_settings 与 plot_settings_requested 信号(放在缩放按钮之后、单位芯片之前),换主题时一并重绘图标,retranslate 里补 tooltip/可访问名;plot_settings_dialog.py 增加"滤波"分区:预设下拉(off/low_latency/balanced/low_noise + 自定义,显示的就是写进 sensor.json 的那个名字)、光流 EMA α(0.05–1.00,1.00 = 关)、中值窗口(1–31,1 = 关)、低通截止(0–60 Hz,0 = 关)、低通阶数(1–6)、采样率("自动跟随帧率"复选框 + 固定值)、"同时显示未滤波曲线";选预设即写入该预设的数值,手改任一数值自动切到"自定义",选中的预设名会随 filter_values() 一起带出(preset 字段),"_reset_defaults" 回到"什么都不滤";对话框底部按当前数值给提示:光流 EMA 打开时提示"这是测量设置、两次录制不可比"、全关时提示"当前不滤波"、录制中显示"录制中不能更改滤波"并禁用整个分区;截止频率达到或超过奈奎斯特(帧率一半)时用 dialog.filter_nyquist 明确说出"设备不会滤",其中 cutoff/nyquist/rate 三个数字由 FilterSettings.nyquist_issue() 提供(problems() 仍返回英文串供日志与测试用,判断条件只有一处)。filters.py 新增 FALLBACK_PRESETS 与 available_presets()(SDK 能导入就用 SDK 的 FILTER_PRESETS,导入不了才用这份文档副本,测试保证两者逐值一致)。app_window.py:把 plot_settings_requested 接到 _on_plot_settings,打开对话框时传入当前 _filter_settings()、filters_locked(正在跑或正在录)、以及 _device_sample_rate()(实测帧率优先,否则 30 fps,用于奈奎斯特检查);接受后把 filter_values().as_dict() 逐键写成 plot_* 偏好(与 _filter_settings() 读回的是同一组键),未在采集时立刻 set_filter_settings() 交给两个 worker,并在状态栏回报"已保存:{describe}(下次采集生效)"。两个 locale 文件补齐 21+3 个新键,中英各 343 键一一对应。tests/test_ui_smoke.py 新增 4 项:齿轮确实触发设置信号、预设/自定义切换与数值一致、录制中分区被禁用且给出理由、对话框写的键与 _filter_settings 读回的键是同一组。 - Why: 用户要求"建议曲线图增加一个齿轮的配置按键,可以调节曲线图的上限和滤波等"。此前滤波只能靠手改 preferences.json 的 plot_* 键(上一轮留下的口子),而 app 默认已改成"什么都不滤"——没有这个入口,等于把默认变成了唯一选择。 - Verification: studio 全量 89 passed(原 85 + 新增 4);shared 全量 158 passed;ui_check 554 项检查 0 失败;.repro/locale_check.py 扫描 64 个源文件里 287 个 t("…") 字面键,两个 locale 全部命中且键集合完全相同(343/343);离屏实测对话框:预设 balanced→flowEMA(0.5)+median(3)、改中值→自定义、选 low_noise→带低通、30 Hz 采样率下填 30 Hz 截止→弹出奈奎斯特警告、恢复默认→off/不滤波。 - Not in this change: CSV 的 17 列与未滤波第二 buffer(SessionStore/记录格式版本)、回放界面显示滤波前曲线、标定面板显示 filters 块、发布说明里"默认不再滤波"的措辞。 ykcheungab 2026-09-14 11:08:25 +08:00
  • 148573934f 采集端统一由 SDK 滤波、显示端不再二次滤波,session.json 记录生效值 - What: sdk_worker/finger_worker 删除硬编码的 sensor.configure(lowpass_cutoff_hz=LOWPASS_CUTOFF_HZ, lowpass_sample_rate_hz=...) —— 它会让界面上的曲线与 CSV 走两条不同的滤波路径;改为在会话打开设备后调用 FilterSettings.apply_to(sensor),并把两件事分别记下来:self._filter_state = effective_filters(sensor)(设备报的**实际**生效值,供界面与记录使用)与 self._filters_unsupported(该 SDK 应用不了的键,老 SDK 必须被看见而不是被吞掉);新增 worker 的 set_filter_settings()/filter_settings()/filter_state()/filters_unsupported() 访问器,start() 时清空上一会话的 filter_state(与 _calibration_version 同样的理由:新会话不能继承旧会话的说法)。finger_worker 保留原有的"runtime 可能拒绝 configure"的容错,但改成记日志并把全部键标为未应用。app_window 新增 _filter_settings()(从 preferences 的 plot_* 键读,空/坏值即"什么都不滤")与 _filter_metadata()(向**正在跑的** worker 要设备实际状态,写成 session.json 的 filters 块:measurement/readout/describe/source,SDK 缺键时附 unsupported_by_sdk);_start_session 在 start() 之前把设置交给 worker。ui/force_plots.py 把 MetricHistory 的 lowpass_cutoff_hz 从 LOWPASS_CUTOFF_HZ 改成 0.0:SDK 已经滤过一遍,且这个 buffer 就是进记录的那个,显示端再滤会让曲线画出 CSV 里根本没有的数据(原来用的还是零相位 filtfilt,非因果)。acquisition/constants.py 删掉 LOWPASS_CUTOFF_HZ("app 在 10 Hz 滤波"这件事不存在了),FORCE_SAMPLE_RATE_HZ 保留。tests/test_force_filters.py 增 3 项:worker 未开设备时 filter_state() 为 {}("没问过"不等于"没在滤")、effective_filters() 对没有 filters_state() 的 SDK 返回 {}、以及对着真实 SDK 的端到端断言(external-frame 构造 box Sensor → apply_to → filters_state 回报 flowEMA(0.5)+median(3)+lowpass(10Hz,order2) → force_triplets 两个 buffer 都在 → 全关也可表达)。apps/studio/tools/ui_check.py 的 _StuckWorker 补上新合约方法(set_filter_settings/filter_settings/filter_state/filters_unsupported),否则窗口一启动就 AttributeError。 - Why: 用户的核心要求是"显示的和记录的必须一致,包括所有滤波",并决定整条链留在 SDK、app 只通过 Sensor.configure 驱动。此前 app 硬编码 10 Hz 低通(SDK 侧)**加上**显示端 filtfilt(app 侧),同一次采集的两份数据经过两条不同的滤波路径;session.json 里的 filter 块也只是照抄常量,说的不是设备在做的事。 - Verification: shared 全量 156 passed(含新增的真实 SDK 端到端断言);studio 85 项测试通过(修好 _StuckWorker 合约后);ui_check 554 项检查、4 个场景 0 失败;编译通过。 - Not in this change: 曲线表头的齿轮按钮与滤波设置对话框(因此现在还只能改 preferences.json 的 plot_* 键来换滤波)、CSV 的 17 列与未滤波第二 buffer、回放界面显示滤波前曲线、发布说明里"默认不再滤波"的措辞。 ykcheungab 2026-09-14 11:01:55 +08:00
  • 0d8b79b759 app 侧滤波接口改为驱动 SDK 并读回生效值 - What: shared/force/filters.py 从"自己滤一遍"改成 app 的滤波控制面,只做三件事:(1) FilterSettings 改用 SDK 的键与"按值开关"语义(flow_ema_alpha / median_window / lowpass_hz / lowpass_order / sample_rate_hz / show_unfiltered / preset;α≥1、窗口≤1、截止≤0 各自表示关),normalised() 把不可用值一律落成"关"而不是落成会滤波的默认值,并新增 problems(sample_rate_hz) 指出"截止 ≥ Nyquist、设备根本不会滤"这类请求;(2) apply_to(sensor) 就是一次 Sensor.configure(...),用 CONFIGURE_PARAMETERS 一处维护"状态键 → configure 形参"的映射(低通那一对在配置/状态里叫 force_lowpass_*、在调用里叫 lowpass_*,此前正是这里写错导致支持性判断把所有键都判成不支持),并返回该 SDK 支持不了的键而不是默默丢掉——老 SDK(0.5.2 之前)没有 flow_ema_alpha/中值,其光流 EMA 也关不掉,必须让人看见;(3) from_filters_state(state) 读回设备**实际**在跑的值(界面与记录都以此为准),metadata(sensor_state) 产出 session.json/CSV 头的 filters 块,measurement 组与 readout 组严格分开、带 describe 与 source(有设备时为 sensor.filters_state,只有请求时为 app.FilterSettings,两者不可混认)。新增 describe_state()/settings_from_mapping();ForceFilterChain 降级为"参考实现",只为 tests/test_force_filters.py 的等价性断言存在(app 不再调用它),并删掉随之无用的 _odd()。force/__init__.py 导出 describe_state。tests/test_force_filters.py 重写为 19 项:按值开关、不可用值=关、Nyquist 预警、一次 configure 调用且键名正确、老 SDK 缺键被上报而不是忽略、显示开关绝不发给设备、读回生效值与 sample_rate 往返、metadata 两组分离与来源标注、空的 preferences 映射等于"什么都不滤"、参考链仍与 SDK 滤波器逐位一致。 - Why: 用户要求"统一采集滤波和显示滤波、显示的和记录的必须一致、保留滤波前数据两个 buffer",并决定整条链留在 SDK、app 通过 Sensor.configure 驱动、filters.py 保留为 wrapper 与 CSV 元数据出口。上一版的 FilterSettings 用 median_enabled/lowpass_enabled 布尔 + app 自己实现滤波,既与 SDK 的"按值开关"两套词汇,也会让同一份数据在界面与 CSV 之间出现第二条滤波路径。 - Verification: pytest shared/tests/test_force_filters.py → 19 passed(含与 orisys.signal_processing.LowpassFilter 的逐位等价断言);shared 全量 153 passed;SDK 已升级到 0.5.2(editable),studio 85 项测试与 ui_check 554 项检查在其上全绿。 - Not in this change: sdk_worker/finger_worker 仍在硬编码 lowpass_cutoff_hz=10.0(下一步改为 apply_to)、曲线图仍在显示端做 filtfilt、齿轮对话框与 plot_* preferences、CSV 的 17 列与未滤波 buffer、session.json 写入 filters 块都还没接。 ykcheungab 2026-09-14 10:56:04 +08:00
  • 65dafdc84d 补两条滤波引擎测试:与 SDK 滤波逐点等价、prefs 往返 - What: test_force_filters.py 新增 test_the_chain_reproduces_the_sdk_filter_exactly(同一段 400 点输入分别喂 orisys.signal_processing.LowpassFilter 与我们 6 通道链的前三通道,断言 np.array_equal,实测 max|差|=0.0,SDK 不在时装作通过)与 test_settings_round_trip_through_a_preferences_mapping(median_window 7、lowpass 12.5 Hz、show_unfiltered 往返,describe() 等于 "median(7) + lowpass(12.5Hz,order2)",空映射取默认弱通)。 - Why: 决定"只留一条低通、放 SDK 里、app 通过 configure 驱动"之后,必须证明"换实现不移动任何一个数"是保证而不是希望;这两条把该保证钉进套件,并让 FilterSettings 成为 prefs/session.json 的唯一真值对象。 - Verification: drive_shared.py 142 项 0 失败(新增 1 项,modify 1 项)、ui_check.py 554 项、test_ui_smoke.py 71 项、locale_studio.py 262 键齐全,全部通过。 - Not in this change: 采集路径未动(SDK 低通仍由 app 硬编码 10 Hz 打开),CSV 仍 11 列;下一步见 .repro/filter_plan.md:SDK 侧 A(配置自描述、去覆盖式总开关)→ SDK 侧 B(filters_state() + force_triplets() 未滤波出口、指纹按 measurement/readout 分组)→ 升级 SDK 进 venv → app 侧 wrapper+齿轮 → 两个 buffer 与 CSV 17 列。 ykcheungab 2026-09-14 10:20:33 +08:00
  • 2fd2891a47 新增单链力滤波引擎(中值 + 因果低通),为"显示=记录"做准备 - What: shared/src/orisys_app_shared/force/filters.py 新增 FilterSettings(中值开关/窗口、低通开关/截止/阶数、采样率、show_unfiltered,窗口强制奇数、<3 自动关闭、空设置直通)与 ForceFilterChain:6 通道(3 原始计数 + 3 牛顿值)、先中值后低通、两级均因果且逐样本、每通道独立 IIR 状态,reset(first) 用稳态初值启动以避免会话开头的假瞬态;settings_from_mapping 支持从 prefs/metadata 构造,describe() 给出一行摘要写进录制元数据;force/__init__ 导出这三个名字。新增 shared/tests/test_force_filters.py(7 例:直通、中值去单点尖峰但不削持续变化、窗口奇数化与下限、阶跃无起始瞬态、通道状态独立、reset 清状态、prefs 往返),并加入 drive_shared 的模块表。 - Why: 用户问"滤波在哪里调",查清现状是两处固化 10 Hz 低通(SDK 侧影响记录、显示侧再用 filtfilt 二次滤波导致显示≠记录)且 median 完全不存在;用户要求把采集滤波与显示滤波统一成一条链、显示值必须等于记录值、并保留滤波前数据(另开一个 buffer,且写进 forces.csv)。本轮先落地这条链并覆盖测试。 - Verification: drive_shared.py 141 项 0 失败(含新增 7 项)、ui_check.py 554 项 0 失败、test_ui_smoke.py 71 项 0 失败、locale_studio.py 262 键齐全。 - Not in this change: 采集路径未改动(SDK 低通仍是 10 Hz、forces.csv 仍是 11 列)——把带状态的滤波器半途接进记录会让数据只滤一半,因此下一步一次性完成:关 SDK 侧滤波、worker 应用链并让 packet 同时携带滤波前后两套值、forces.csv 追加 6 列未滤波读数(11→17、RECORD_FORMAT_VERSION+1)、SessionStore 第二 buffer、去掉显示端 filtfilt、曲线表头新增齿轮按钮并扩展曲线设置对话框(中值/低通/显示未滤波,运行中禁用滤波控件)。完整清单见 .repro/filter_plan.md。 ykcheungab 2026-09-14 09:57:03 +08:00
  • da8165cf03 删除深度重算引擎;修好回放视频每帧重开文件导致的无法播放,运输控制改到状态轨主按钮 - What: 1) 彻底删除深度重算引擎:shared/.../record/depth.py(DepthRecomputer/DepthResult)与 depth_backends.py(BoxReplay/FingerReplay)、其专属测试 test_depth_replay.py,record/__init__ 不再导出,playback.py::replay_warnings 不再报 depth_recomputed;apps/studio/replay.py 由 515 行瘦身为只做读盘+游标+视频帧+提示,回放视图的 2D 面板只提供「原始图像」;session.json 的 depth.replay 由 recompute 改为 none;删除 31 个只剩深度使用的文案键(depth_*/warn_state_*/warn_reset_*),warn_config_changed 与 warn_sdk_changed 改为描述"由视频重算";ui_check 深度段、studio 两个深度用例、test_playback 三例、drive_shared 的 test_depth_replay 与 reproduce.py 的深度段一并清理(shared 套件 150→134 项)。2) 无法播放的根因是 RecordingReader.video_frame_at 每帧都 cv2.VideoCapture 重开并 seek:小片段已 ~10 ms/帧,1080p H.264 每次 seek 需从关键帧解码(上百 ms),而播放每 33 ms 要一帧且全在 UI 线程上,视频因此看着不动;改为按分段缓存解码器、顺序帧用 read() 步进(实测 10 ms → 0.5 ms/帧)、跳转才 seek,新增 RecordingReader.close() 释放。运输控制按用户建议上移到状态轨主按钮:toolbar.set_run_role("playback") 使回放视图的主按钮变成「▶ 回放」/「⏸ 暂停」(实时视图仍是「▶ 运行」),点按以 1× 推进游标(墙钟计时,漏拍不变慢)、到末尾自动停,拖动游标/框选/滚轮/拖总览条经 ForcePlotsPanel.timeline_taken_over 自动暂停,空格键同效;曲线表头不再放第二个播放按钮;按钮样式在回放角色下不再套用"停止"的红色语义。 - Why: 用户要求先清掉深度重算引擎、并反馈"现在无法播放视频",建议把上方播放按钮在历史回放形式下改为回放。 - Verification: ui_check.py 554 项 0 失败(新增 seek<250ms/步进<60ms 的解码预算、回放按钮文字随播放/暂停切换、手动拖游标即暂停、切回实时视图按钮恢复运行、按深度视图已移除);test_ui_smoke.py 71 项 0 失败;drive_shared.py 134 项 0 失败(深度引擎用例随代码删除);locale_studio.py 262 键齐全、zh==en;reproduce.py 退出 0;.repro/probe_play.py 实测按「回放」后 0.75 s 内游标推进 0.689 s、面板 12/12 帧都在更新;两张复核图(回放视图的「▶ 回放」、实时视图的「▶ 运行」)。 - Not in this change: 录制文件格式与 CSV 列序不变(raw_*.mp4 与 forces.csv 继续一起写);「由视频重算」复核模式保留(它复用实时管线,是有状态、只能顺序前进的一遍)。 ykcheungab 2026-09-14 09:31:21 +08:00