# 力读数工作流(`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)` 在声明区间执行同一操作。 三级流水线已有同帧测量时,用 `process_measurement(row, stamp, tracker_reset_seq=...)` 传入快照自带的参考版本,不读流水线的实时计数。外部归零先调用 `target = pipeline.request_reset()`,再调用 `request_zero_external_reset(reference_id=..., declared_unloaded=True, target_reference_version=target)`。 `target` 为正整数,帧版本达到它才开始采样;较早复位后已在途的帧不能确认本次请求。 未被 flow 领取的请求合并为同一目标,领取后的新请求另分配目标;共用目标的复位与归零只确认一次。 该接缝尚未发布,本轮将原 `tracker_reset_seq` 请求参数改为明确的目标版本参数; 逐帧输入仍用 `tracker_reset_seq`。已发布 `Sensor.reset()` / `StreamPipeline.reset()` 用法不变。 实现见 `pipeline.py`、`force_session.py`,回归见 `tests/test_pipeline.py`、`tests/test_force_session.py`。 ## 后处理与有效设置 `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` 不同是唯一允许的管线身份差异,其余差异仍拒绝并报告。