新增统一力读数会话 ForceSession

把同帧测量、空载归零、模型推断与共用后处理收进一个 SDK 会话,Studio 显示与记录使用同一样本;旧 Sensor 读数接口保持兼容。以 force session 与受影响 SDK 套件、lint 与文档门禁验证,硬件与真实载荷精度未验证。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpCfJF6SvonMKmPZhzKZHb
This commit is contained in:
ykcheungab
2026-10-20 07:07:22 +00:00
parent dc8a630df3
commit ee830d8505
17 changed files with 4175 additions and 29 deletions
+163
View File
@@ -455,3 +455,166 @@ def test_finger_runtime_binds_field_identity_and_forwards_the_window(monkeypatch
runtime.declare_unloaded_reference("runtime-window")
assert sensor.calibration.begin_calls == ["runtime-window"]
# ------------------------------------------------- session measurement seam
def test_measurement_session_skips_old_inference_and_readout():
"""The new entry publishes same-frame raw only: no model call, no readout."""
sensor = make_sensor()
sensor.cuda = sensor.verbose = False
sensor.valid_mask = sensor.valid_mask_hhd = None
sensor._hhd_downsample = 1
sensor._hhd_grid_shape = sensor._hhd_ref_shape = (2, 2)
sensor._model_frame_id = 0
field = np.zeros((2, 2, 2), dtype=np.float32)
sensor._Sensor__hhd = SimpleNamespace(
get_force=lambda *args, **kwargs: (None, (10., 0., 0., 0.)),
get_normal=lambda: field,
get_shear=lambda: field,
)
sensor.enable_measurement_session()
assert sensor.measurement_session is True
sensor.compute_hhd(field)
assert sensor.calibration.calls == [], "the session owns the one model call"
assert sensor._force_readout.filtered_raw is None
assert sensor._force_raw == (10., 0., 0.)
assert np.isnan(sensor.fnormal)
assert sensor.calibration_status == {}
# Contact statistics stay available, still without a model inference.
sensor._Sensor__contact3d = SimpleNamespace(
compute_foe=lambda *args, **kwargs: {
"centroid": (0, 1), "centroid_found": True,
"divergence_map": np.zeros((2, 2)),
}
)
sensor.compute_contact()
assert sensor.calibration.calls == []
assert sensor.centroid_found is True
sensor.disable_measurement_session()
assert sensor.measurement_session is False
def test_reset_tracker_confirms_without_touching_the_calibration_reference():
sensor = make_sensor()
resets = []
sensor._Sensor__tracker = SimpleNamespace(reset=lambda: resets.append("tracker"))
sensor.set_calibration_reference({"fn": 10.}, reference_id="reference")
sensor._force_unfiltered = (1., 2., 3.)
sensor.compute_deformation_valid = True
sensor.reset_tracker()
assert resets == ["tracker"]
assert sensor._force_unfiltered is None
assert sensor._calibration_reference == {"fn": 10.}
assert sensor._calibration_reference_id == "reference"
assert sensor.compute_deformation_valid is False
sensor.enable_measurement_session()
sensor.reset_tracker()
assert sensor.measurement_session is True, "the seam survives a frame-boundary reset"
def test_measure_current_frame_returns_this_frames_features_once():
sensor = make_sensor()
sensor.cuda = sensor.verbose = False
sensor.valid_mask = np.ones((2, 2), dtype=bool)
sensor.valid_mask_hhd = np.ones((2, 2), dtype=bool)
sensor._hhd_mask_f32 = None
sensor._hhd_downsample = 1
sensor._hhd_grid_shape = sensor._hhd_ref_shape = (2, 2)
sensor._model_frame_id = 0
field = np.zeros((2, 2, 2), dtype=np.float32)
sensor._Sensor__hhd = SimpleNamespace(
get_force=lambda *args, **kwargs: (None, (10., 20., 30., 0.)),
get_normal=lambda: field,
get_shear=lambda: field,
get_field=lambda valid_mask=None: {
"d": field, "valid_mask": np.ones((2, 2), dtype=bool),
"area_scale": 4.0, "grid_shape": (2, 2), "reference_shape": (2, 2),
},
)
sensor._Sensor__contact3d = SimpleNamespace(
compute_foe=lambda *args, **kwargs: {
"centroid": (0.4, 1.2), "centroid_found": True,
"divergence_map": np.zeros((2, 2)),
}
)
flow_calls = []
sensor.compute_flow = lambda image=None: (
flow_calls.append(image), setattr(sensor, "flow", field)
)
sensor.enable_measurement_session()
sensor.img = np.zeros((2, 2), dtype=np.uint8)
measurement = sensor.measure_current_frame(
frame_id=5, want_field=True, want_contact=True
)
assert flow_calls == [sensor.img]
assert measurement.raw == (10., 20., 30.)
assert measurement.raw_valid == (True, True, True)
assert measurement.field["grid_shape"] == (2, 2)
assert measurement.output_valid_mask.shape == (2, 2)
assert measurement.contact_found is True
assert measurement.centroid == (0.4, 1.2)
assert measurement.single_contact is True
def test_read_measurement_frame_prepares_the_source_frame_once():
sensor = make_sensor()
sensor.cuda = sensor.verbose = False
sensor.valid_mask = sensor.valid_mask_hhd = None
sensor._hhd_downsample = 1
sensor._hhd_grid_shape = sensor._hhd_ref_shape = (2, 2)
sensor._model_frame_id = 0
field = np.zeros((2, 2, 2), dtype=np.float32)
sensor._Sensor__hhd = SimpleNamespace(
get_force=lambda *args, **kwargs: (None, (1., 2., 3., 0.)),
get_normal=lambda: field,
get_shear=lambda: field,
)
prepared = []
sensor.get_img = lambda frame=None: (prepared.append(frame), setattr(sensor, "img", frame))
sensor.compute_flow = lambda image=None: setattr(sensor, "flow", field)
sensor.enable_measurement_session()
frame = np.ones((2, 2, 3), dtype=np.uint8)
measurement = sensor.read_measurement_frame(frame, frame_id=7)
assert prepared == [frame]
assert measurement.raw == (1., 2., 3.)
assert measurement.field is None, "no field unless the model asked for it"
assert measurement.contact_found is None, "no contact unless it was requested"
def test_finger_runtime_session_seam_delegates_to_its_sensor():
from orisys.finger.runtime import FingerRuntime
events = []
class FakeSensor:
def enable_measurement_session(self):
events.append("enable")
def disable_measurement_session(self):
events.append("disable")
def reset_tracker(self):
events.append("reset")
def measure_current_frame(self, *, frame_id, want_field=False, want_contact=False):
events.append(("measure", frame_id, want_field, want_contact))
return "measurement"
runtime = object.__new__(FingerRuntime)
runtime.tactile = SimpleNamespace(raw_sensor=FakeSensor())
processed = []
runtime.process_frame = lambda frame, color=False: processed.append((frame, color))
frame = np.zeros((2, 2, 3), dtype=np.uint8)
result = runtime.read_measurement_frame(
frame, frame_id=9, want_field=True, want_contact=False
)
assert processed == [(frame, False)]
assert result == "measurement"
assert events[-1] == ("measure", 9, True, False)
runtime.enable_measurement_session()
runtime.reset_tracker()
runtime.disable_measurement_session()
assert events[1:] == ["enable", "reset", "disable"]