把 Studio 主机协议改成单向端口,并让「从视频重算」走分析入口
控制器经 AcquireGate / ImageSink / ShellNotes 与信号接线,采集 tick 用 acquire_presenter 上屏;三维测试证明分析结果已喂 3D 后,重算并入 on_analysis_requested。 验证:apps/studio 下相关 pytest 与 tools/ui_check.py(634 checks)通过。
This commit is contained in:
@@ -2003,10 +2003,10 @@ def test_the_replay_transport_plays_and_pauses(make_window, replay_recording):
|
||||
assert window.replay.position() == parked, "the playhead kept moving while paused"
|
||||
|
||||
|
||||
def test_the_recompute_mode_reruns_the_pipeline_over_the_video(
|
||||
def test_the_recompute_switch_asks_for_analysis_not_an_acquire_chain(
|
||||
make_window, replay_recording
|
||||
):
|
||||
"""Off: the recorded CSV. On: the recorded video through the vision chain."""
|
||||
"""「从视频重算」 shares ``on_analysis_requested`` with opening a recording."""
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
window = _show(make_window(), (1440, 880))
|
||||
@@ -2019,27 +2019,30 @@ def test_the_recompute_mode_reruns_the_pipeline_over_the_video(
|
||||
assert window.act_recompute.isEnabled(), "the recompute switch is not offered"
|
||||
|
||||
started: list = []
|
||||
asked: list = []
|
||||
|
||||
def fake_run() -> None:
|
||||
started.append(True)
|
||||
window.session.set_running(True)
|
||||
|
||||
original = window.replay_ctl.on_analysis_requested
|
||||
|
||||
def spy_analysis() -> None:
|
||||
asked.append(True)
|
||||
return original()
|
||||
|
||||
window._start_run = fake_run
|
||||
window.replay_ctl.on_analysis_requested = spy_analysis
|
||||
window.act_recompute.setChecked(True)
|
||||
QApplication.processEvents()
|
||||
assert started == [True], f"the pass started {len(started)} times"
|
||||
assert not panel.is_recording_source(), "the charts still read the CSV"
|
||||
assert window.toolbar.source_mode() == "video"
|
||||
assert window.toolbar.video_path().endswith(".mp4"), window.toolbar.video_path()
|
||||
assert window.toolbar.btn_run.isChecked(), (
|
||||
"the transport does not show the pass as running"
|
||||
)
|
||||
assert asked == [True], "the switch did not ask for analysis"
|
||||
assert started == [], "the switch must not open an acquisition chain"
|
||||
# A recording's force measurement stays on forces.csv; analysis feeds 2D/3D.
|
||||
assert panel.is_recording_source(), "analysis must not replace the recorded CSV"
|
||||
|
||||
window._recompute_running = True
|
||||
window.act_recompute.setChecked(False)
|
||||
QApplication.processEvents()
|
||||
assert panel.is_recording_source(), "turning the recompute off did not restore the CSV"
|
||||
assert not window.toolbar.btn_run.isChecked(), "the transport stayed checked"
|
||||
assert panel.is_recording_source()
|
||||
|
||||
|
||||
def test_the_replay_view_offers_a_stop_that_always_works(make_window, replay_recording):
|
||||
@@ -2083,25 +2086,23 @@ def test_the_replay_view_offers_a_stop_that_always_works(make_window, replay_rec
|
||||
window.statusBar().currentMessage()
|
||||
)
|
||||
|
||||
# A recompute pass: the same button stops it, without stopping the recording.
|
||||
stopped: list = []
|
||||
window._stop_run = lambda **kwargs: stopped.append(kwargs.get("reason"))
|
||||
window._recompute_running = True
|
||||
window._update_run_enabled()
|
||||
assert window.toolbar.btn_stop.isEnabled(), "no stop offered while a pass runs"
|
||||
window.toolbar.btn_stop.click()
|
||||
# An analysis pass left over from opening the recording: Stop cancels it.
|
||||
# Opening may already have finished the pass; start one so the escape hatch is real.
|
||||
window.replay_ctl.cancel_analysis()
|
||||
window.replay_ctl.wait_for_analysis()
|
||||
window.replay_ctl.on_analysis_requested()
|
||||
QApplication.processEvents()
|
||||
assert stopped == ["user_stop"], stopped
|
||||
assert t("toolbar.stopped_pass") in window.statusBar().currentMessage(), (
|
||||
window.statusBar().currentMessage()
|
||||
)
|
||||
if window.replay_ctl.analysis_running():
|
||||
window._update_run_enabled()
|
||||
assert window.toolbar.btn_stop.isEnabled(), "no stop offered while a pass runs"
|
||||
window.toolbar.btn_stop.click()
|
||||
QApplication.processEvents()
|
||||
assert not window.replay_ctl.analysis_running()
|
||||
assert t("toolbar.stopped_pass") in window.statusBar().currentMessage(), (
|
||||
window.statusBar().currentMessage()
|
||||
)
|
||||
|
||||
# Nothing running: it says so instead of doing nothing at all.
|
||||
# (The pass is over, so the stop button is correctly disabled — the escape hatch is
|
||||
# therefore exercised the way Esc reaches it, through the controller.)
|
||||
assert not window.toolbar.btn_stop.isEnabled(), (
|
||||
"a stop is still offered after the pass was stopped"
|
||||
)
|
||||
window.replay_ctl.stop_activity()
|
||||
QApplication.processEvents()
|
||||
assert t("toolbar.nothing_to_stop") in window.statusBar().currentMessage(), (
|
||||
@@ -2344,13 +2345,20 @@ def test_the_two_figures_are_drawn_from_one_arrow_policy(make_window, monkeypatc
|
||||
window.close()
|
||||
|
||||
|
||||
def test_a_bare_video_plays_even_though_it_is_not_a_recording(make_window, record_root):
|
||||
def test_a_bare_video_plays_even_though_it_is_not_a_recording(
|
||||
make_window, record_root, decline_video_prompt
|
||||
):
|
||||
"""A clip with no forces.csv beside it is still something to watch.
|
||||
|
||||
An operator who points at a video expects the transport to play it. Opening the
|
||||
video's own folder as a *recording* used to "succeed" against the tolerant reader
|
||||
and then play a zero-length timeline ("pressing 回放 does nothing"), so the fix is
|
||||
not to refuse the file but to play it as what it is: a video, with empty charts.
|
||||
|
||||
The setup prompt is declined here (``decline_video_prompt``): this test is about the
|
||||
playback, and a dismissed prompt is the answer that leaves it exactly as it was. What
|
||||
accepting the prompt does instead is its own module
|
||||
(``test_bare_video_analysis.py``).
|
||||
"""
|
||||
import time as _time
|
||||
|
||||
@@ -2401,14 +2409,16 @@ def test_a_bare_video_plays_even_though_it_is_not_a_recording(make_window, recor
|
||||
assert window._play_timer.isActive()
|
||||
image = window.replay.video_image()
|
||||
assert image is not None and not image.isNull(), "no frame was decoded"
|
||||
window._stop_replay_activity()
|
||||
window.replay_ctl.stop_activity()
|
||||
QApplication.processEvents()
|
||||
assert not window._play_timer.isActive()
|
||||
finally:
|
||||
window.close()
|
||||
|
||||
|
||||
def test_the_replay_and_the_live_paths_share_one_image_backend(make_window, record_root):
|
||||
def test_the_replay_and_the_live_paths_share_one_image_backend(
|
||||
make_window, record_root, decline_video_prompt
|
||||
):
|
||||
"""Stage 7: one renderer for every 2D producer, and modes from the frame itself.
|
||||
|
||||
A recording stores the raw camera image and nothing else, so its plan offers exactly
|
||||
@@ -2444,7 +2454,7 @@ def test_the_replay_and_the_live_paths_share_one_image_backend(make_window, reco
|
||||
|
||||
app_module.render_image = spy
|
||||
try:
|
||||
window._apply_view_preset("replay")
|
||||
window.layout_ctl.apply_preset("replay")
|
||||
assert window._load_replay_video(bare) is True
|
||||
window._render_replay_frame()
|
||||
QApplication.processEvents()
|
||||
@@ -2503,7 +2513,9 @@ def test_the_application_converts_bgr_to_qimage_in_one_place():
|
||||
assert others == [], f"another BGR→QImage conversion exists: {others}"
|
||||
|
||||
|
||||
def test_the_replay_entries_exist_only_in_the_replay_view(make_window, record_root):
|
||||
def test_the_replay_entries_exist_only_in_the_replay_view(
|
||||
make_window, record_root, decline_video_prompt
|
||||
):
|
||||
"""Stage 7: the live view has no replay entry, and says where they live.
|
||||
|
||||
Opening a video from the live view used to set the acquisition source to a file
|
||||
@@ -2523,7 +2535,7 @@ def test_the_replay_entries_exist_only_in_the_replay_view(make_window, record_ro
|
||||
|
||||
window = _show(make_window(), (1440, 880))
|
||||
try:
|
||||
window._apply_view_preset("live")
|
||||
window.layout_ctl.apply_preset("live")
|
||||
QApplication.processEvents()
|
||||
for action in (window.act_open_video, window.act_playback, window.act_playback_latest):
|
||||
assert not action.isEnabled(), "a replay entry is offered in the live view"
|
||||
@@ -2542,7 +2554,7 @@ def test_the_replay_entries_exist_only_in_the_replay_view(make_window, record_ro
|
||||
|
||||
# In the replay view they are offered again, and the recompute entry follows
|
||||
# whether there is anything to recompute from.
|
||||
window._apply_view_preset("replay")
|
||||
window.layout_ctl.apply_preset("replay")
|
||||
QApplication.processEvents()
|
||||
for action in (window.act_open_video, window.act_playback, window.act_playback_latest):
|
||||
assert action.isEnabled()
|
||||
@@ -2552,10 +2564,16 @@ def test_the_replay_entries_exist_only_in_the_replay_view(make_window, record_ro
|
||||
assert window.toolbar.btn_recompute.isEnabled(), (
|
||||
"a video with no recording beside it can still be recomputed"
|
||||
)
|
||||
# Declined setup (fixture) means play-only: the switch re-asks, then clears. It
|
||||
# must not open an acquisition chain over the clip.
|
||||
started: list = []
|
||||
window._start_run = lambda: started.append(True)
|
||||
window.toolbar.btn_recompute.click()
|
||||
QApplication.processEvents()
|
||||
assert window.act_recompute.isChecked(), "the rail's recompute entry did not toggle"
|
||||
window.act_recompute.setChecked(False)
|
||||
assert started == [], "recompute must not open an acquisition chain"
|
||||
assert not window.act_recompute.isChecked(), (
|
||||
"declining analysis setup must leave the switch off"
|
||||
)
|
||||
finally:
|
||||
window.close()
|
||||
|
||||
@@ -2613,7 +2631,7 @@ def test_a_video_inside_a_recording_opens_the_recording_and_plays(
|
||||
window.toolbar.btn_run.setChecked(True)
|
||||
QApplication.processEvents()
|
||||
assert window._play_timer.isActive(), "pressing 回放 did not start playback"
|
||||
window._pause_playback()
|
||||
window.replay_ctl.pause_playback()
|
||||
window.close()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user