补两条滤波引擎测试:与 SDK 滤波逐点等价、prefs 往返 - What: test_force_filters.py 新增 test_the_chain_reproduces_the_sdk_filter_exactly(同一段 400 点输入分别喂 orisys.signal_processing.LowpassFilter 与我们 6 通道链的前三通道,断言 np.array_equal,实测 max|差|=0.0,SDK 不在时装作通过)与 test_settings_round_trip_through_a_preferences_mapping(median_window 7、lowpass 12.5 Hz、show_unfiltered 往返,describe() 等于 "median(7) + lowpass(12.5Hz,order2)",空映射取默认弱通)。 - Why: 决定"只留一条低通、放 SDK 里、app 通过 configure 驱动"之后,必须证明"换实现不移动任何一个数"是保证而不是希望;这两条把该保证钉进套件,并让 FilterSettings 成为 prefs/session.json 的唯一真值对象。 - Verification: drive_shared.py 142 项 0 失败(新增 1 项,modify 1 项)、ui_check.py 554 项、test_ui_smoke.py 71 项、locale_studio.py 262 键齐全,全部通过。 - Not in this change: 采集路径未动(SDK 低通仍由 app 硬编码 10 Hz 打开),CSV 仍 11 列;下一步见 .repro/filter_plan.md:SDK 侧 A(配置自描述、去覆盖式总开关)→ SDK 侧 B(filters_state() + force_triplets() 未滤波出口、指纹按 measurement/readout 分组)→ 升级 SDK 进 venv → app 侧 wrapper+齿轮 → 两个 buffer 与 CSV 17 列。
This commit is contained in:
@@ -95,7 +95,41 @@ def test_reset_clears_the_memory_between_sessions():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_settings_round_trip_through_a_preferences_mapping():
|
def test_the_chain_reproduces_the_sdk_filter_exactly():
|
||||||
|
"""Replacing the SDK's low-pass with ours must not move a single number.
|
||||||
|
|
||||||
|
``Sensor._apply_force_filter`` filters ``_force_raw`` with
|
||||||
|
``orisys.signal_processing.LowpassFilter`` (Butterworth, ``sosfilt`` with
|
||||||
|
persistent state, steady-state reset) and then applies the device
|
||||||
|
calibration. Our chain uses the same recipe, so at the same settings the
|
||||||
|
recorded values stay identical — this test is what makes that a guarantee
|
||||||
|
rather than a hope.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from orisys.signal_processing import LowpassFilter
|
||||||
|
except Exception: # pragma: no cover - the SDK is not installed here
|
||||||
|
return
|
||||||
|
|
||||||
|
rng = np.random.default_rng(3)
|
||||||
|
signal = np.cumsum(rng.normal(0.0, 1.0, (400, 3)), axis=0) * 10.0 + 500.0
|
||||||
|
|
||||||
|
sdk = LowpassFilter(cutoff_hz=10.0, sample_rate_hz=30.0, order=2, n_channels=3)
|
||||||
|
sdk.configure(cutoff_hz=10.0, sample_rate_hz=30.0, order=2)
|
||||||
|
sdk.reset(signal[0])
|
||||||
|
sdk_out = np.array([sdk.process(row) for row in signal])
|
||||||
|
|
||||||
|
chain = ForceFilterChain(
|
||||||
|
FilterSettings(lowpass_enabled=True, lowpass_hz=10.0, sample_rate_hz=30.0)
|
||||||
|
)
|
||||||
|
# The chain carries six channels (raw + newtons); the first three are the
|
||||||
|
# raw counts the SDK filters, so give all six the same steady state.
|
||||||
|
chain.reset(list(signal[0]) * 2)
|
||||||
|
chain_out = np.array([chain.process(list(row) * 2)[:3] for row in signal])
|
||||||
|
|
||||||
|
assert np.array_equal(sdk_out, chain_out), (
|
||||||
|
f"the chain differs from the SDK filter by {np.abs(sdk_out - chain_out).max()}"
|
||||||
|
)
|
||||||
|
|
||||||
settings = settings_from_mapping(
|
settings = settings_from_mapping(
|
||||||
{
|
{
|
||||||
"median_enabled": True,
|
"median_enabled": True,
|
||||||
@@ -111,3 +145,14 @@ def test_settings_round_trip_through_a_preferences_mapping():
|
|||||||
# An empty mapping is the documented default, not a disabled chain.
|
# An empty mapping is the documented default, not a disabled chain.
|
||||||
default = settings_from_mapping(None)
|
default = settings_from_mapping(None)
|
||||||
assert default.as_dict()["lowpass_hz"] == FilterSettings().lowpass_hz
|
assert default.as_dict()["lowpass_hz"] == FilterSettings().lowpass_hz
|
||||||
|
|
||||||
|
|
||||||
|
def test_settings_round_trip_through_a_preferences_mapping():
|
||||||
|
settings = settings_from_mapping({
|
||||||
|
"median_enabled": True, "median_window": 7,
|
||||||
|
"lowpass_enabled": True, "lowpass_hz": 12.5, "show_unfiltered": True,
|
||||||
|
})
|
||||||
|
assert settings.median_enabled and settings.median_window == 7
|
||||||
|
assert settings.lowpass_hz == 12.5 and settings.show_unfiltered
|
||||||
|
assert settings.describe() == "median(7) + lowpass(12.5Hz,order2)"
|
||||||
|
assert settings_from_mapping(None).as_dict()["lowpass_hz"] == FilterSettings().lowpass_hz
|
||||||
|
|||||||
Reference in New Issue
Block a user