新增统一力读数会话 ForceSession

把同帧测量、空载归零、模型推断与共用后处理收进一个 SDK 会话,Studio 显示与记录使用同一样本;旧 Sensor 读数接口保持兼容。以 force session 与受影响 SDK 套件、lint 与文档门禁验证,硬件与真实载荷精度未验证。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpCfJF6SvonMKmPZhzKZHb
This commit is contained in:
ykcheungab
2026-10-20 07:07:22 +00:00
parent dc8a630df3
commit ee830d8505
17 changed files with 4175 additions and 29 deletions
+101
View File
@@ -0,0 +1,101 @@
# 力读数工作流(`ForceSession`)
用途:SDK 显式力读数会话(`studio-force/1`)的公共接口参考。状态:已实现并接入 Studio,2026-10-20;
硬件、真实相机与真实载荷精度未验证。需求与验收矩阵见
[流程规格](../../docs/workflow/studio-force-workflow.zh.md),实施状态见
[重写计划](../../docs/workflow/studio-force-rewrite-plan.zh.md)。
测量、标定模型、空载基线与输出后处理全部由本会话拥有;调用方只提供源帧与帧身份,
发送归零/换模型请求,并分发返回的 `ForceOutput`。这样同一帧只测量、推断、后处理各一次,
显示与记录拿到同一个不可变输出。
## 入口与版本
```python
from orisys import ForceSession, WORKFLOW_VERSION
```
`WORKFLOW_VERSION = "studio-force/1"` 是处理顺序与语义的稳定身份,随每个输出返回,
记录、缓存键与诊断引用它;改变顺序或语义时必须提升该版本。
构造显式启用测量源(`Sensor` 或 `FingerRuntime`)的会话接缝:
```python
session = ForceSession(
source=runtime, # 已打开、由调用方负责 close
sensor_family="finger", # box | mini | finger | finger-mems
device_id="FINGER-001", # 可选,用于标定身份校验
context=context, # schema 3 模型需要的实际管线身份
calibration=model, # Calibration(schema 1/2)、ModelCalibration(schema 3)或 None
settings=output_settings, # 可选;默认 OutputSettings(sample_rate_hz=30.0)
)
```
构造失败不会留下“半切换”的会话;旧 `Sensor.read_info` / `ForceReadout.apply` 路径在
未构造 `ForceSession` 时保持原语义,本模块不与旧路径并行运行。
## 主要方法与属性
| 成员 | 说明 |
|---|---|
| `process_frame(source_frame, stamp) -> ForceOutput` | 唯一逐帧入口:测量 → 归零状态 → 标定 → 后处理。同一 `(session_id, frame_id)` 重复调用返回同一缓存对象且不推进状态;乱序帧显式失败并清除完成标志 |
| `read_sdk_frame(source_frame, stamp) -> SdkReading` | 只做同帧测量(图像准备、光流/HHD、可选接触/场),不推断、不读数滤波 |
| `request_zero(*, reference_id, declared_unloaded, replay_window=None) -> ZeroProgress` | 启动唯一空载归零操作;`declared_unloaded` 必须为空载确认;拒绝带载相对置零 |
| `update_zero(reading) -> ZeroProgress` | 推进归零状态机并执行本帧一次模型推断;迟到代次的结果被忽略 |
| `invalidate(*, reason) -> ZeroProgress` | 断流、取消、换模型或测量配置变化时作废基线/完成状态与滤波历史 |
| `apply_calibration(reading, zero) -> CalibratedReading` | 不复用旧结果、不重复推断,给出未做读数滤波的 raw/N 与逐轴状态 |
| `apply_filter(measurement) -> ForceOutput` | 唯一后处理:raw 与 N 各自独立地 median → low-pass → 可关闭逐轴阈值 |
| `set_calibration(calibration, *, context=None)` | 帧边界原子换模型;校验失败保留原模型、基线与完成状态 |
| `zero`、`calibration_fingerprint`、`output_settings`、`last_output` | 只读状态/身份;`output_settings` 是实际生效参数 |
## 帧身份
`FrameStamp(session_id, frame_id, source_frame_id, signal_time_s, time_basis)`;后处理低通的
采样率来自信号时间,不取离线处理耗时。实时与顺序录像只替换源适配器,其余流程相同。
## 归零(空载基线)
1. `request_zero(declared_unloaded=True, reference_id=...)` 只登记请求并递增参考代次;
2. 处理线程在下一个帧边界重置 tracker,然后才测量该帧;
3. 采集重置后连续 30 个有效处理帧,按模型类型得到基线:标量模型取 raw 中位数,
场模型委托 `ModelCalibration` 用同一增益图加权后的 30 帧中位数;
4. 第 30 帧仍为基线帧;第 31 帧起 `phase="ready"`、`completed=True` 并输出 N,
`effective_from_frame_id` 指向该帧。
基线未完成、无效帧、窗口不足或已取消时 N 为 `NaN`、状态显式;窗口不足不越界补帧。
录像重算用 `replay_window=(start, end)` 在声明区间执行同一操作。
## 后处理与有效设置
`OutputSettings(sample_rate_hz, median_window=1, lowpass_hz=0.0, lowpass_order=2,
raw_threshold=0.0, newton_threshold=0.0)`。每阶段由自身值关闭(median=1、lowpass=0、
threshold=0),全关时对有效值严格直通(NaN 保留);无效轴不进入滤波历史。
`ForceOutput.settings` 返回实际生效的参数(例如截止频率达到 Nyquist 时解析为 off)。
## 离线重滤波
```python
from orisys.force_readout import process_output_sequence
raw_out, newton_out, effective = process_output_sequence(
settings, raw_samples, newton_samples,
raw_valid=None, newton_valid=None, signal_times_s=times,
)
```
该函数只重放输出级(带完整 warm-up),用于从已存未滤波序列生成新参数下的最终输出;
不重新测量、不打开模型链。
## 状态与诊断
逐轴状态:`ok`、`out_of_range`、`zero_unavailable`、`no_contact`、`coordinate_unmapped`、
`outside_coverage`、`invalid_input`、`uncalibrated`、`measurement_failed`、`frame_order`、
`signal_time_order`。仅 `ok`/`out_of_range` 携带可用牛顿值;模型不输出的轴(例如 FN-only
模型的 FX/FY)恒为 `NaN` + `uncalibrated`。缺失值与有效零分开。
`CalibratedReading.diagnostics` 提供 `reference_id`、`baseline_N`、`baseline_raw_fn/fx/fy`、
`model_fingerprint`、`mapping`、`extrapolated_fraction`、`coverage_fraction`、`area_scale`、
`pending_reference_frames` 等;调用方存储或展示,不据此改写逐轴有效性。
场模型质量(外推、覆盖)与 downsample 差异属于诊断,不替代有效性判断;
`hhd_downsample` 不同是唯一允许的管线身份差异,其余差异仍拒绝并报告。