Files
orisys-app/shared/tests/test_display_units.py
T
2026-09-27 19:20:43 +08:00

139 lines
4.7 KiB
Python

"""Display unit selection and the per-axis drawing rule.
Two defects are covered here:
* the live charts chose their unit from the device-level ``force_calibrated``
flag, so a device with only its normal-force axis calibrated either showed
every axis in raw counts or showed raw counts inside newton panels;
* the drawing rule asked for **all three** axes to be finite, so a valid FN
disappeared from the chart whenever FX/FY had no calibrated value.
"""
from __future__ import annotations
import math
import numpy as np
import pytest
from orisys_app_shared.acquisition.sdk_worker import SdkPacket
from orisys_app_shared.force import select_display_forces
from orisys_app_shared.force.session import SessionSample, SessionStore
from orisys_app_shared.record.sample import to_record_sample
NAN = float("nan")
def _packet(**overrides) -> SdkPacket:
frame = np.zeros((4, 4, 3), np.uint8)
kwargs = dict(
raw_frame=frame,
processed_frame=frame,
timestamp=1000.0,
fps=30.0,
process_ms=1.0,
normal_force=1.5,
shear_x=0.25,
shear_y=-0.25,
normal_force_raw=123.0,
shear_x_raw=2.0,
shear_y_raw=-2.0,
force_calibrated=True,
calibrated_axes=("fn", "fx", "fy"),
frame_id=0,
)
kwargs.update(overrides)
return SdkPacket(**kwargs)
def test_newton_mode_marks_uncalibrated_axes_unavailable():
sample = to_record_sample(_packet(calibrated_axes=("fn",)))
display = select_display_forces(units_pref="newton", sample=sample)
assert display.unit == "N"
assert display.values[0] == pytest.approx(1.5)
assert math.isnan(display.values[1]) and math.isnan(display.values[2])
assert display.available == (True, False, False)
assert display.unavailable_axes() == ("fx", "fy")
assert not display.using_raw_fallback
def test_raw_mode_shows_the_raw_counts_for_every_axis():
sample = to_record_sample(_packet(calibrated_axes=("fn",)))
display = select_display_forces(units_pref="raw", sample=sample)
assert display.unit == "raw"
assert display.values == (123.0, 2.0, -2.0)
assert display.available == (True, True, True)
assert not display.using_raw_fallback
def test_an_uncalibrated_device_falls_back_to_raw_and_says_so():
sample = to_record_sample(
_packet(force_calibrated=False, calibrated_axes=())
)
display = select_display_forces(units_pref="newton", sample=sample)
assert display.unit == "raw"
assert display.using_raw_fallback, "the fallback must be announceable"
assert display.requested_unit == "N"
assert display.values == (123.0, 2.0, -2.0)
def test_a_failed_readout_has_no_available_axis():
sample = to_record_sample(
_packet(valid=False, invalid_reason="compute failed", stale_forces=True)
)
for unit in ("N", "raw"):
display = select_display_forces(units_pref=unit, sample=sample)
assert display.available == (False, False, False)
assert all(math.isnan(value) for value in display.values)
def _store_with_one_calibrated_axis() -> SessionStore:
store = SessionStore()
for index in range(5):
store.append(
SessionSample(
index,
index / 30.0,
fn_raw=100.0 + index,
fx_raw=2.0,
fy_raw=-2.0,
fn_n=1.0 + index * 0.1,
fx_n=NAN,
fy_n=NAN,
valid=True,
calibrated_axes=("fn",),
)
)
return store
def test_a_valid_axis_is_drawn_even_when_the_others_have_no_value():
"""The all-axes rule hid a good FN because FX/FY were uncalibrated."""
slice_ = _store_with_one_calibrated_axis().data()
times, values, mask = slice_.plottable("N")
assert mask.shape == (5, 3)
assert mask[:, 0].all(), "the calibrated axis was hidden"
assert not mask[:, 1].any()
assert not mask[:, 2].any()
fn_times, fn_values = slice_.plottable_axis("N", 0)
assert fn_times.size == 5 == fn_values.size
assert float(fn_values.max()) == pytest.approx(1.4)
fx_times, _fx_values = slice_.plottable_axis("N", 1)
assert fx_times.size == 0
def test_the_raw_triplet_is_still_complete_for_the_same_samples():
slice_ = _store_with_one_calibrated_axis().data()
for index in range(3):
times, values = slice_.plottable_axis("raw", index)
assert times.size == 5
assert np.isfinite(values).all()
def test_an_invalid_sample_is_excluded_from_every_axis():
store = _store_with_one_calibrated_axis()
store.append(SessionSample(9, 9.0, fn_n=NAN, valid=False))
slice_ = store.data()
_times, _values, mask = slice_.plottable("N")
assert not mask[-1].any(), "an invalid sample reached a drawing path"