配置没写的滤波键等于"关",不再回落到会滤波的默认值 - What: DEFAULT_FORCE_MEDIAN_WINDOW 3 -> 1、DEFAULT_FLOW_EMA_ALPHA 0.5 -> 1.0(DEFAULT_FORCE_LOWPASS_HZ 本就是 0.0),并把注释改成"配置文件没写的键就是关,而不是我们觉得合理的那一档"——原因写在注释里:否则设备会去滤一个没人要求滤的测量,而文件里没有任何线索;实测的延迟/降噪数据保留在注释中,作为 balanced/low_noise 预设的依据。docs/filters.md 第 2 节增加"缺省 = 关"这一条并说明没有 filters 段的配置什么都不滤,预设表里 balanced 的用途由"默认建议"改为"建议起步档"(它不再是默认值)。examples/filters/README.md 补一句"整段删掉等价于 off"。tests/test_filter_presets.py 新增 test_a_config_with_no_filters_block_filters_nothing:把 box bundle 的 filters 段整段删掉后构造真实 Sensor,断言 filters_state() 报 ema 1.0 / median 1 / cutoff 0.0 且 any_stage_applied 为 False。 - Why: 上一版只把"非法值"改成回落到关,但"键根本不写"仍会走 DEFAULT_* 从而把 EMA 0.5 + 中值 3 打开——这与"配置自描述、文件说什么就跑什么、出厂默认全关"矛盾,且现实里存在大量没有 filters 段的手写配置(例如 _perf/cfg_res_280x300.json),它们会在无人察觉的情况下继续滤波。缺省开是这条链上最后一个隐藏默认值。 - Verification: 删掉 filters 段后实测 filters_state() = (1.0, 1, 0.0) 且 any_stage_applied=False,出厂 box/mini/finger 三份同样为全关;pytest tests/test_filter_presets.py + tests/test_calibration.py → 33 passed;SDK 全量 336 passed / 15 skipped(唯一失败仍是需要 1200 帧而样例只有 577 帧的 longrun 测试);app 侧 studio 85 项与 shared 142 项测试、ui_check 554 项检查在升级后的 0.5.2 下全绿。 - Not in this change: 出厂 bundle 的三个显式值未动(本来就全关);_perf 的两个校验脚本不在仓库内,已就地补上显式 filters 段(balanced)以保持"开 vs 关"对照仍有意义;app 侧 wrapper/齿轮/CSV 17 列未动。
This commit is contained in:
+4
-1
@@ -21,6 +21,9 @@
|
||||
|
||||
- **每个级由自己的值开关**:`flow_ema_alpha >= 1`、`force_median_window <= 1`、
|
||||
`force_lowpass_cutoff_hz <= 0` 都是"关"。
|
||||
- **配置没写的键 = 关**:缺省不是"我们觉得合理的那档",而是"不滤波"。因此**没有 `filters`
|
||||
段的配置文件什么都不滤**,出厂 bundle 也把三个值显式写成全关 —— 想让哪一级跑起来,
|
||||
必须在文件里写出来(数值或 `preset`)。这样"文件说的"和"设备跑的"才总是一致。
|
||||
- **非法值 → 关**,绝不回落到"默认开"。
|
||||
- `filters.enabled` 是**废弃别名**,显式 `false` 表示"三级全关";没有任何开关能覆盖已写的值。
|
||||
- `filters.preset` 可引用下面的预设;**显式键优先于预设值**(可从预设出发只改一个旋钮)。
|
||||
@@ -32,7 +35,7 @@
|
||||
|---|---|---|---|---|---|
|
||||
| `off` | 1.0 | 1 | 0.0 | 0 帧 | 原始读数;文件说什么就跑什么 |
|
||||
| `low_latency` | 1.0 | 3 | 0.0 | ≈1 帧(33 ms) | 延迟敏感,只去尖峰 |
|
||||
| `balanced` | 0.5 | 3 | 0.0 | ≈2 帧(67 ms) | 默认建议:EMA 干掉力级滤波够不到的光流抖动 |
|
||||
| `balanced` | 0.5 | 3 | 0.0 | ≈2 帧(67 ms) | 建议起步档:EMA 干掉力级滤波够不到的光流抖动 |
|
||||
| `low_noise` | 0.3 | 3 | 10.0 | ≈3 帧(100 ms) | 底噪优先,读数再低通一道 |
|
||||
|
||||
延迟换算 @60fps 减半;数字来自 `_perf/`(对照表见 `orisys-sdk-perf-plan.md`)。
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
到 `sensors/<name>/sensor.json` 的 `"filters"` 段即可。原有的 `filters` 段请整段替换,
|
||||
不要只改其中一行(避免出现 `preset` 与数值混写的歧义——显式键优先于预设值,混写容易看错)。
|
||||
|
||||
配置里**没写**的滤波键等于"关":整段删掉就是什么都不滤,和 `off` 那段等价。
|
||||
|
||||
背景、代价与可追溯性约定见 `docs/filters.md`;延迟/噪声实测见 `_perf/` 与
|
||||
`orisys-sdk-perf-plan.md`。
|
||||
|
||||
|
||||
+17
-9
@@ -32,16 +32,24 @@ from .signal_processing import (
|
||||
)
|
||||
|
||||
# Default signal-conditioning settings (config: top-level "filters" block).
|
||||
# Measured on dd02-0703 at 280x300 (see the noise analysis in the perf notes):
|
||||
# flow EMA 0.5 + median 3 costs ~2 frames at 30 fps, cuts steady-press force
|
||||
# jitter by roughly half and removes ~85% of the heavy-tailed spikes, while also
|
||||
# stabilising vnormal/contact (which a force-level filter cannot do).
|
||||
# The force low-pass is OFF by default: on top of the EMA it added no measurable
|
||||
# noise benefit at the same latency, so it is kept as an opt-in stage for sites
|
||||
# with different noise characteristics.
|
||||
#
|
||||
# A key a config does not state is **off**, not "whatever we thought was
|
||||
# sensible": the file has to name what runs, or a device can be filtering a
|
||||
# measurement nobody asked it to filter and no reader of the file could tell.
|
||||
# The shipped bundles state all three values explicitly, so they are unaffected
|
||||
# by these defaults; a config with no "filters" block at all filters nothing.
|
||||
#
|
||||
# Which settings are worth asking for is measured, not guessed. On dd02-0703 at
|
||||
# 280x300 (see the noise analysis in the perf notes) flow EMA 0.5 + median 3
|
||||
# costs ~2 frames at 30 fps and cuts steady-press force jitter by roughly half
|
||||
# while also stabilising vnormal/contact (which a force-level filter cannot do);
|
||||
# that combination is the "balanced" preset. The force low-pass stays off there
|
||||
# too: on top of the EMA it added no measurable noise benefit at the same
|
||||
# latency, so it is an opt-in stage for sites with different noise
|
||||
# characteristics (the "low_noise" preset).
|
||||
DEFAULT_FORCE_LOWPASS_HZ = 0.0
|
||||
DEFAULT_FORCE_MEDIAN_WINDOW = 3
|
||||
DEFAULT_FLOW_EMA_ALPHA = 0.5
|
||||
DEFAULT_FORCE_MEDIAN_WINDOW = 1
|
||||
DEFAULT_FLOW_EMA_ALPHA = 1.0
|
||||
|
||||
#: Named filter presets, so a recommendation is *executable* instead of a
|
||||
#: comment in a JSON file (``json.load`` cannot carry comments). A config may
|
||||
|
||||
@@ -10,6 +10,7 @@ when a copy stops matching them.
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -88,3 +89,29 @@ def test_shipped_bundles_report_all_off(bundle: str):
|
||||
assert fp["measurement"] == {"flow_ema_alpha": 1.0}
|
||||
assert fp["readout"]["force_median_window"] == 1
|
||||
assert fp["readout"]["force_lowpass_cutoff_hz"] == 0.0
|
||||
|
||||
|
||||
def test_a_config_with_no_filters_block_filters_nothing(tmp_path: Path):
|
||||
"""Absence is off, not "the values we thought were sensible".
|
||||
|
||||
A key that is missing has to mean the stage is not running: otherwise a
|
||||
device filters a measurement nobody asked for, and nothing in the file says
|
||||
so. Callers with a hand-written config (an old customer file, a bench
|
||||
script) rely on this.
|
||||
"""
|
||||
copy = tmp_path / "box"
|
||||
shutil.copytree(REPO_ROOT / "sensors" / "box", copy)
|
||||
path = copy / "sensor.json"
|
||||
cfg = json.loads(path.read_text(encoding="utf-8"))
|
||||
cfg.pop("filters", None)
|
||||
path.write_text(json.dumps(cfg, indent=2), encoding="utf-8")
|
||||
|
||||
sensor = Sensor("external", config_name=str(path), verbose=False, backend="cpu")
|
||||
try:
|
||||
state = sensor.filters_state()
|
||||
finally:
|
||||
sensor.disconnect()
|
||||
assert state["measurement"]["flow_ema_alpha"] == 1.0
|
||||
assert state["readout"]["force_median_window"] == 1
|
||||
assert state["readout"]["force_lowpass_cutoff_hz"] == 0.0
|
||||
assert state["any_stage_applied"] is False
|
||||
|
||||
Reference in New Issue
Block a user