feat: ForceStreamOutput 新增同幀未濾波量測欄位
App 需要濾波前的 raw 與 N 供錄製、分析與標定取樣;診斷幀保留 raw 特徵、N 為 NaN。已以帶標定、中值加低通的會話逐幀對照直通結果驗證。
This commit is contained in:
@@ -669,3 +669,84 @@ def test_fn_only_model_uncalibrated_session_stays_explicit() -> None:
|
||||
assert all(np.isnan(value) for value in results[-1].newton)
|
||||
assert set(results[-1].newton_status) == {"uncalibrated"}
|
||||
assert results[-1].quality == "uncalibrated"
|
||||
|
||||
|
||||
def test_unfiltered_measurement_fields_survive_the_output_filter() -> None:
|
||||
inputs = [100.0, 100.0, 100.0, 900.0, 100.0, 120.0, 140.0, 160.0]
|
||||
filtered = _new_chain(settings=helpers.legacy_settings(median_window=5))
|
||||
passthrough = _new_chain(settings=helpers.legacy_settings(median_window=1))
|
||||
for frame_id, value in enumerate(inputs, start=1):
|
||||
one = filtered.process(_measurement(frame_id, (value, 0.0, 0.0)))
|
||||
two = passthrough.process(_measurement(frame_id, (value, 0.0, 0.0)))
|
||||
assert one.measurement_raw == (value, 0.0, 0.0)
|
||||
assert two.raw == (value, 0.0, 0.0)
|
||||
assert two.measurement_raw == one.measurement_raw
|
||||
assert one.measurement_newton == two.measurement_newton
|
||||
assert filtered.process(_measurement(9, (900.0, 0.0, 0.0))).raw[0] != 900.0
|
||||
assert passthrough.process(_measurement(9, (900.0, 0.0, 0.0))).raw[0] == 900.0
|
||||
|
||||
|
||||
def test_diagnostic_rows_keep_the_unfiltered_measurement() -> None:
|
||||
session = _new_chain(settings=helpers.legacy_settings(median_window=5))
|
||||
session.process(_measurement(20, _raw_for(20)))
|
||||
duplicate = session.process(_measurement(20, _raw_for(20)))
|
||||
assert duplicate.quality == "duplicate"
|
||||
assert duplicate.measurement_raw == tuple(float(value) for value in _raw_for(20))
|
||||
assert duplicate.raw == duplicate.measurement_raw
|
||||
assert all(np.isnan(value) for value in duplicate.measurement_newton)
|
||||
assert all(np.isnan(value) for value in duplicate.newton)
|
||||
|
||||
|
||||
def test_calibrated_measurement_newton_is_unfiltered() -> None:
|
||||
filt = _new_chain(
|
||||
helpers.box_cal(),
|
||||
helpers.legacy_settings(
|
||||
median_window=5, lowpass_hz=3.0, lowpass_order=2, sample_rate_hz=30.0
|
||||
),
|
||||
)
|
||||
passthrough = _new_chain(
|
||||
helpers.box_cal(), helpers.legacy_settings(median_window=1)
|
||||
)
|
||||
for session in (filt, passthrough):
|
||||
session.request_zero(
|
||||
reference_id="r",
|
||||
declared_unloaded=True,
|
||||
expected_reference_generation=0,
|
||||
)
|
||||
rows = []
|
||||
pass_rows = []
|
||||
for frame_id in range(1, 80):
|
||||
raw = list(_raw_for(1))
|
||||
if frame_id == 60:
|
||||
raw[0] += 20000.0
|
||||
rows.append(filt.process(_measurement(frame_id, tuple(raw))))
|
||||
pass_rows.append(passthrough.process(_measurement(frame_id, tuple(raw))))
|
||||
for filtered, plain in zip(rows, pass_rows):
|
||||
for measured, reference in zip(
|
||||
filtered.measurement_newton, plain.newton
|
||||
):
|
||||
assert (np.isnan(measured) and np.isnan(reference)) or measured == reference
|
||||
assert filtered.measurement_raw == plain.raw
|
||||
spike = rows[59]
|
||||
assert spike.newton[0] != spike.measurement_newton[0]
|
||||
|
||||
|
||||
def test_late_frame_carries_unfiltered_measurement_fields() -> None:
|
||||
session = _new_chain(
|
||||
helpers.box_cal(),
|
||||
helpers.legacy_settings(
|
||||
median_window=5, lowpass_hz=3.0, lowpass_order=2, sample_rate_hz=30.0
|
||||
),
|
||||
)
|
||||
session.request_zero(
|
||||
reference_id="r", declared_unloaded=True, expected_reference_generation=0
|
||||
)
|
||||
for frame_id in range(1, 32):
|
||||
session.process(_measurement(frame_id, _raw_for(frame_id)))
|
||||
raw = _raw_for(32)
|
||||
late = session.process(
|
||||
_measurement(32, raw, capture_time_ns=31 * FRAME_NS)
|
||||
)
|
||||
assert late.late is True
|
||||
assert late.measurement_raw == tuple(float(value) for value in raw)
|
||||
assert all(np.isnan(value) for value in late.measurement_newton)
|
||||
|
||||
Reference in New Issue
Block a user