26382b359f
新增 FixedRateOutputFilter:设计帧率在会话创建时固定,实测帧率漂移和长间隔只报告、不重新设计也不清历史;request_output_settings 可跨线程排队,在力线程帧边界切换,只重建滤波链、清空历史,不改零点和参考版本,并发出 output_filter_changed StreamEvent(设置、指纹、生效帧、预热)。结果行带滤波设置身份与预热状态。已发布 ForceReadout 和旧 ForceSession/ForceOutputFilter 默认行为不变;设计帧率下与旧会话逐位一致。回归 391 项、lint 通过。
7.0 KiB
7.0 KiB
新力侧会话(T06a/T06b、T07 输出滤波)
本文件是单路 SensorStream 计划 T06a/T06b/T07 的 SDK 合同:新的力侧会话直接消费 T05 的
ImageMeasurement(T03 MeasurementFrame 头 + 同帧 raw/field),参考版本取帧上的
reference_generation。旧 ForceSession 仍为 App 保留到 T09/T10 迁移,本会话不提供
process_measurement、request_zero_external_reset、tracker_reset_seq 等旧外部复位接口。
接口
ForceStreamSession(sensor_family=..., calibration=None, settings=None):构造。T06a 支持 无标定与 schema2 标量曲线;T06b 补充 schema3 field 模型(hhd_normal_l1_field/1,需要context与同帧 field)和 FN-only 全局增益模型(outputs=["fn"]);settings为OutputSettings(None 用旧默认)。measurement_requirements:(want_field, want_contact),告诉 T05 调用方是否需要把fieldinclude 进ImageMeasurement(field 模型为(True, False),标量/FN-only 为(False, False))。request_zero(reference_id=..., declared_unloaded=True, expected_reference_generation=版本, max_gap_s=None, max_window_s=None): 为指定参考版本确认无载归零;可选窗口上限。process(measurement) -> ForceStreamOutput:消费一个 T05ImageMeasurement,返回逐帧结果 (frame携带 T03 帧头与reference_generation,以及raw/newton/status/quality/zero)。
规则
- 版本/会话变化即废旧零点(K8.3)。
reference_generation或session_id一旦变化, resetting、collecting、ready 任一阶段的旧零点立即失败(zero.phase == "failed"), 必须对新的版本显式调用request_zero(expected_reference_generation=新版本)才能恢复有效 N。 正常顺序(会话在处理 v0 → 复位得到目标 v1 → v1 到达)中,等于请求目标的新版本会被采用 并开始/继续同版本窗口,不会作废这次请求;只有未请求的版本变化或版本超过目标才失败。 旧的generation在采样开始后不再检查的缺陷(阶段 0 P3-2)由本会话修复。 - 基线只取请求版本的帧(K8.3/K8.5)。 归零请求绑定一个版本。目标到达前、仍属当前版本的
在途帧记为
zero_pending(不是迟到,也绝不进入窗口);比当前版本更旧的帧才是late; 超过目标的版本使请求失败,而不是静默改绑。基线固定 30 帧无载输入; 可选max_gap_s/max_window_s超限时失败并把原因写进zero.failure_reason。 - 重复、迟到、同时间戳(K4.2)。 以
sensor_id + session_id + source_frame_id(无源编号时用frame_id)去重,凡是送入引擎的帧都会登记身份。重复帧只产生诊断结果; 旧版本和同时间戳/乱序帧只标记late/zero_pending,不推进零点或滤波,也不作废旧零点。 不同源帧时间戳相同不算重复。 - 逐帧结果流(K6.1)。 每个输入帧都返回一个冻结
ForceStreamOutput,携带 T03 帧头与reference_generation;quality为ok/uncalibrated/zero_pending/duplicate/late/session_changed/version_changed/window_failed/invalid。 迟到、无效和带质量标记的结果不会被丢弃;拒绝帧仍保留 raw 特征。 - 数值等价与滤波。 零点/标定换算/读数滤波复用已发布算法;同一输入、无复位、同一标定时
与旧
ForceSession逐帧相等(T06a 覆盖标量曲线、无标定、median+lowpass 滤波器;T06b 覆盖 field 模型与 FN-only 全局增益模型,三类标定都在同一平台同一次运行中对照旧会话)。 - 三类标定与不匹配。 标量曲线、field、FN-only 各有有效测试;无标定会话保持 raw 与
uncalibrated状态;field 模型缺context时构造报CalibrationError,基线帧缺 field 或 模型拒绝帧时零点失败并给出invalid_input/原因;FN-only 的 FX/FY 永远保持 NaN +uncalibrated,不冒充三轴标定。 - 输出滤波(T07)。 设计帧率在构造时固定(
design_sample_rate_hz),实测帧率漂移和长 间隔只作为measured_sample_rate_hz/sample_rate_irregular诊断,不重新设计链路、不清空 历史。request_output_settings(settings)可从任意线程发出,只在力线程下一帧边界生效:只 重建滤波链并清空其历史,图像参考与已完成的零点不变;相同设置是空操作。每次实际切换发出 一个output_filter_changedStreamEvent(规范化设置、指纹、算法版本、生效帧、设计帧率、 预热信息,可 JSON 往返),由drain_events()取出。预热帧数为max(1, median_window) + 2×lowpass_order(低通开启时);结果行提供当前设置身份与预热计数。 全阶段关闭时输出与输入完全相同(含 NaN 与带符号的零)。已发布ForceOutputFilter/旧会话 的自适应调频默认行为不变。 - 线程与只读。 一个实例只属于一个力侧线程;输入数组只读,输出只含标量/状态/不可变进度。
输出滤波示例
from orisys.force_readout import OutputSettings
from orisys.force_stream_session import ForceStreamSession
session = ForceStreamSession(
sensor_family="box",
settings=OutputSettings(sample_rate_hz=30.0, median_window=3, lowpass_hz=5.0),
design_sample_rate_hz=30.0,
)
new_settings = OutputSettings(sample_rate_hz=30.0, median_window=5, lowpass_hz=10.0)
session.request_output_settings(new_settings)
assert session.output_settings.median_window == 3, "switch waits for a frame boundary"
assert session.design_sample_rate_hz == 30.0
示例
from orisys.force_stream_session import ForceStreamSession
from orisys.image_pipeline import ImageMeasurement
from orisys.stream_contract import FrameStamp, MeasurementFrame
session = ForceStreamSession(sensor_family="box")
session.request_zero(reference_id="run-1", declared_unloaded=True,
expected_reference_generation=0)
frame = MeasurementFrame(
stamp=FrameStamp(sensor_id="BOX-001", session_id="run-1", frame_id=1,
source_frame_id=1, capture_time_ns=33_333_333,
clock_id="host", capture_time_basis="capture_monotonic"),
source_dt_s=None, reference_generation=0,
)
row = session.process(ImageMeasurement(frame=frame, force_raw=(0.0, 0.0, 0.0),
torque=0.0, flow=None))
assert row.frame.reference_generation == 0
assert row.zero.phase in ("resetting", "collecting")
后续
T06b 已交付标量/field/FN-only 三类标定与旧 ForceSession 的完整数值等价;T07 已交付固定
设计帧率、显式切换事件与历史/直通合同。spatial_gain/接触映射模型不在 T06/T07 验收范围,
需要时另立任务。
T08 接单路流入口;T09/T10 迁移 App 后删除旧 ForceSession。
另一项后续任务:删除旧 ForceSession 之前,先把零点、标定、读数引擎从旧类中抽出来,
避免新会话依赖待删除的类型(T09 之前)。