"""Live operator feedback must preserve sample validity and UV orientation.""" from dataclasses import replace import numpy as np from orisys_app_shared.acquisition.models import SdkPacket class Worker: is_running = True def __init__(self): self.sinks = [] def add_sink(self, sink): self.sinks.append(sink) def remove_sink(self, sink): self.sinks.remove(sink) def packet(**values): image = np.full((64, 80, 3), (10, 20, 30), dtype=np.uint8) base = SdkPacket(raw_frame=image.copy(), processed_frame=image, timestamp=100, mono_t=1, fps=30, process_ms=1, session_id=1, frame_id=1, normal_force_raw=12, shear_x_raw=2, shear_y_raw=-3) return replace(base, **values) def test_force_feedback_consumes_every_sample_and_breaks_invalid_gaps(qt_app): from orisys_autocal.ui.live_force_view import LiveForceView worker = Worker() view = LiveForceView(worker) for item in (packet(), packet(frame_id=2, mono_t=1.1, stale_forces=True), packet(frame_id=3, mono_t=1.2, normal_force_raw=20)): worker.sinks[0](item) view.refresh() assert view.history.sample_count == 3 x, y = view.curves[0].getData() np.testing.assert_allclose(x, [0, .1, .2]) assert y[0] == 12 and np.isnan(y[1]) and y[2] == 20 assert "20 raw" in view.readings.text() assert "20.000 N" not in view.readings.text() view.on_packet(packet(session_id=2, normal_force=4, force_calibrated=True, calibrated_axes=("fn",))) view.refresh() assert view.history.sample_count == 1 assert "4.000 N" in view.readings.text() assert "2.000 N" not in view.readings.text() worker.is_running = False view.refresh() assert view.state.text() view.shutdown() assert not worker.sinks def test_display_toggles_redraw_same_frame_without_mutating_measurements(qt_app, monkeypatch): from orisys_autocal.ui import finger_source_panel panel = finger_source_panel.FingerSourcePanel() item = packet(flow=np.full((64, 80, 2), 2., dtype=float), depth=np.full((64, 80), .5), valid_mask=np.ones((64, 80), bool), flow_valid=True, flow_frame_id=1, depth_valid=True, depth_frame_id=1) original = item.processed_frame.copy() panel._last_packet = item frames = [] monkeypatch.setattr(panel, "_show_frame", lambda frame: frames.append(frame.copy())) panel._redraw() np.testing.assert_array_equal(frames[-1], original) panel.background_check.setChecked(False) assert not np.array_equal(frames[-1], original) solid = frames[-1] panel.depth_check.setChecked(True) assert not np.array_equal(frames[-1], solid) depth = frames[-1] panel.arrows_check.setChecked(True) assert not np.array_equal(frames[-1], depth) assert frames[-1].shape == original.shape np.testing.assert_array_equal(item.processed_frame, original) assert np.all(item.flow == 2) panel._last_packet = replace(item, flow_valid=False, depth_frame_id=0) panel._redraw() assert panel.display_note.text() np.testing.assert_array_equal(frames[-1], solid) panel.shutdown() def test_depth_range_adapts_per_frame_and_excludes_invalid_uv_pixels(qt_app, monkeypatch): from orisys_autocal.ui import finger_source_panel panel = finger_source_panel.FingerSourcePanel() panel.background_check.setChecked(False) panel.depth_check.setChecked(True) mask = np.zeros((64, 80), dtype=bool) mask[10:30, 20:40] = True depth = np.full((64, 80), 1e6) depth[mask] = np.linspace(2, 4, mask.sum()) depth[10, 20] = np.nan frames = [] monkeypatch.setattr(panel, "_show_frame", lambda frame: frames.append(frame.copy())) panel._last_packet = packet(depth=depth, valid_mask=mask, depth_valid=True, depth_frame_id=1) panel._redraw() first = frames[-1] assert "4" in panel.display_note.text() panel._last_packet = replace(panel._last_packet, depth=depth * 10 + 5) panel._redraw() # A different numerical range spans the same colour ramp in the next frame. np.testing.assert_array_equal(frames[-1], first) assert "45" in panel.display_note.text() panel._last_packet = replace(panel._last_packet, depth=np.full_like(depth, np.nan)) panel._redraw() assert panel.display_note.text() assert not np.array_equal(frames[-1], first) panel.shutdown()