keep sweeps in acquisition order through history replays

The processor appended sweeps the GUI fed back to its own history, so its next replay
published them twice and out of order, and the B-scan came out with rotated columns.
Fed-back sweeps now stay out of that history, results are kept in timestamp order,
picking the displayed combo only redraws and only new sweeps are recorded to disk.
This commit is contained in:
Ayzen
2026-10-02 16:05:32 +03:00
parent 45861f4047
commit d283ecfdf1
11 changed files with 421 additions and 91 deletions
@@ -23,6 +23,8 @@ os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PyQt6.QtWidgets import QApplication # noqa: E402
from python_app.gui.app_window import AppWindow # noqa: E402
from collections import deque # noqa: E402
from python_app.gui.runtime.history import record_result_history # noqa: E402
from python_app.models.dataset_model import ( # noqa: E402
ComboKey,
@@ -167,5 +169,61 @@ class BscanDisplayWindowTest(unittest.TestCase):
self.assertEqual(len(self.w._bscan_history_by_combo[(0, 0)]), 50)
class ResultHistoryOrderTest(unittest.TestCase):
"""The result history stays in acquisition order whatever order results arrive in.
The processor reprocessing its retained sweeps also delivers sweeps whose first
result the GUI missed (the results ring overwrites unread slots). Appended, such a
sweep showed up as the newest B-scan column, and was recorded to disk once more.
"""
@staticmethod
def _ids(history: deque[ResultCollection]) -> list[int]:
return [int(collection.collection_id) for collection in history]
def test_a_new_sweep_is_appended(self) -> None:
history = deque(_bscan_result(cid) for cid in (1, 2))
self.assertTrue(record_result_history(history, _bscan_result(3)))
self.assertEqual(self._ids(history), [1, 2, 3])
def test_a_late_result_takes_its_place_in_acquisition_order(self) -> None:
history = deque(_bscan_result(cid) for cid in (1, 2, 4, 5))
self.assertFalse(record_result_history(history, _bscan_result(3)))
self.assertEqual(self._ids(history), [1, 2, 3, 4, 5])
def test_a_reprocessed_result_replaces_its_entry(self) -> None:
history = deque(_bscan_result(cid) for cid in (1, 2, 3))
reprocessed = _bscan_result(2)
self.assertFalse(record_result_history(history, reprocessed))
self.assertEqual(self._ids(history), [1, 2, 3])
self.assertIs(history[1], reprocessed)
def test_a_full_history_drops_its_oldest_entry(self) -> None:
history = deque((_bscan_result(cid) for cid in (1, 2, 4, 5)), maxlen=4)
record_result_history(history, _bscan_result(3))
self.assertEqual(self._ids(history), [2, 3, 4, 5])
record_result_history(history, _bscan_result(1)) # older than all it keeps
self.assertEqual(self._ids(history), [2, 3, 4, 5])
record_result_history(history, _bscan_result(6))
self.assertEqual(self._ids(history), [3, 4, 5, 6])
def test_only_new_sweeps_are_recorded_to_disk(self) -> None:
window = _window
arrivals = [_bscan_result(cid) for cid in (2, 3, 2, 1)] # 2 reprocessed, 1 late
reader = type("Reader", (), {"pop_result_collection": lambda _self: arrivals.pop(0) if arrivals else None})()
recorded: list[int] = []
window._result_reader = reader
self.addCleanup(setattr, window, "_result_reader", None)
window._record_collection = lambda stage, collection: recorded.append(int(collection.collection_id))
self.addCleanup(delattr, window, "_record_collection")
window._result_history.clear()
self.addCleanup(window._result_history.clear)
window._read_all_results()
self.assertEqual(recorded, [2, 3])
self.assertEqual(self._ids(window._result_history), [1, 2, 3])
if __name__ == "__main__":
unittest.main()