标定指纹改读生效值并按测量/读数分组 - What: calibration.py 新增 FILTER_MEASUREMENT_KEYS(flow_ema_alpha)与 FILTER_READOUT_KEYS(中值窗口、低通截止/阶数/采样率/rate_auto)、filters_fingerprint()(把 Sensor.filters_state() 归一化成 {measurement, readout, preset, source}:非法值丢弃、来源没写的键不出现而不是补默认、空映射返回 {} 表示"无记录")、filter_mismatches()(按组判读差异)与 _filters_from_config / _flat_filters_from(扁平旧格式与 preset 展开的兼容解析,显式键优先);pipeline_from_sensor_json() 新增 filters_state= 关键字——传了生效值就用生效值,否则读文件的 filters 段并标注 source="sensor.json.filters",返回结构化 filters 键,删除原先会凭空补出 0.5/3/10 的扁平键与 enabled 覆盖逻辑;Calibration.to_dict() 写 meta.filters(同时保留扁平键供旧读者按名取用);parse() 把 filters 读回 pipeline(此前 to_dict 写进 meta 的滤波键在读回时被整段丢弃,指纹实际是"只写不读");check() 改用 filter_mismatches 分开报告——measurement 组差异进 warnings 且措辞含 not comparable,readout 组差异进新增字段 CheckResult.notes(只记录、不拦截);build(extra_meta=…) 把 filters 与扁平滤波键折进结构化组。tests/test_calibration.py 新增 11 项测试(分组、preset 展开与显式键优先、enabled:false 全关、非法值为空而非默认、写入-读回往返、旧扁平文档读回、生效值压过文件、四个预设逐档核对)。docs/filters.md 新增第 7 节。 - Why: 用户决策 B 要求"Sensor.filters_state() 作为唯一生效值出口,calibration.py 指纹与 session.json 都读它、不读配置文件,指纹分 measurement/readout 两组";动手时确认旧实现三个问题叠加:来源错(读文件、被 filters.enabled 覆盖、文件没写就补默认 0.5/3/10),写进 meta 的指纹在读回时被丢弃,check() 因此永远比不出滤波差异——审计链是断的,不是不精确。 - Verification: SDK 全量 pytest 335 passed / 15 skipped,唯一失败 test_vnormal_longrun_no_outlier_jumps 需要 1200 帧而 data/sample/s1_480p.mp4 只有 577 帧(环境/数据,与本改动无关);external-frame 模式逐预设构造真实 Sensor,filters_fingerprint(filters_state()) 得 off(1.0,1,0.0) / low_latency(1.0,3,0.0) / balanced(0.5,3,0.0) / low_noise(0.3,3,10.0);写入→读回往返后 measurement/readout/preset 完整保留且 to_dict 幂等;旧扁平 meta 文档仍读回同一指纹;.repro/verify_cal_filters.py 50 项全绿。 - Not in this change: 未动 app(app 的 meta_from_sensor_json 只取 4 个键,不受影响);未升级 venv 内 SDK;examples/filters 的四个机器可读预设片段与 app 侧 wrapper/齿轮/CSV 17 列未动。

This commit is contained in:
ykcheungab
2026-09-14 10:43:58 +08:00
parent b956d071ea
commit 35190562e5
3 changed files with 483 additions and 48 deletions
+41
View File
@@ -72,3 +72,44 @@ t["unfiltered"]["raw"], t["unfiltered"]["newton"] # 中值/低通之前;同
3. 记录里主列 = 滤波后(与界面一致),未滤波列另存;列结构与格式版本可区分。
4. 标定/可比性指纹用 `measurement` 组;`readout` 组变化只记录、不拦截。
5. 双份数据在同一帧、同一份标定系数下产生,差异可完全归因于滤波链。
## 7. 指纹:标定文件里怎么记(已实现)
`orisys.calibration` 负责把滤波设置写进标定文件的 `meta`,并在读回时还原:
```python
from orisys.calibration import filters_fingerprint, filter_mismatches, pipeline_from_sensor_json
pipeline = pipeline_from_sensor_json(config_path, filters_state=sensor.filters_state())
# {"stitch_resolution": …, "dis_mode": …, "hhd_downsample": …, "config_ref": …,
# "filters": {"measurement": {"flow_ema_alpha": 0.5},
# "readout": {"force_median_window": 3, "force_lowpass_cutoff_hz": 0.0},
# "preset": "balanced", "source": "sensor.filters_state"}}
```
写入的文件长这样(`meta` 里同时保留扁平键供旧读者按名取用):
```json
"meta": {
"filters": {
"measurement": {"flow_ema_alpha": 0.5},
"readout": {"force_median_window": 3, "force_lowpass_cutoff_hz": 0.0},
"preset": "balanced",
"source": "sensor.filters_state"
},
"flow_ema_alpha": 0.5,
"force_median_window": 3
}
```
规则:
- **生效值优先**:传了 `filters_state=` 就用它;没传才读文件的 `filters` 段,并标成
`"source": "sensor.json.filters"`(那是"意图",不一定等于设备正在做的事)。
- **不猜**:来源没写的键**不出现**在指纹里。`{}` 表示"没有任何记录",绝不表示"大概是关的"。
旧文档写的扁平键(`meta.flow_ema_alpha` …)在读回时会被折进对应的组,因此仍可比。
- **`enabled: false`** 只作为"全部关"的废弃别名保留(`flow_ema_alpha=1`、中值 `1`、低通 `0`)。
- **分组判读**:`calibration.check(doc, pipeline=…)` 把两组的差异分开报告 —— measurement 组的
差异进 `warnings`(措辞含 `not comparable`,即"两份记录不可比"),readout 组的差异进
`notes`(只记录,不拦截)。`CheckResult.notes` 是为此新增的字段。