删除深度重算引擎;修好回放视频每帧重开文件导致的无法播放,运输控制改到状态轨主按钮 - What: 1) 彻底删除深度重算引擎:shared/.../record/depth.py(DepthRecomputer/DepthResult)与 depth_backends.py(BoxReplay/FingerReplay)、其专属测试 test_depth_replay.py,record/__init__ 不再导出,playback.py::replay_warnings 不再报 depth_recomputed;apps/studio/replay.py 由 515 行瘦身为只做读盘+游标+视频帧+提示,回放视图的 2D 面板只提供「原始图像」;session.json 的 depth.replay 由 recompute 改为 none;删除 31 个只剩深度使用的文案键(depth_*/warn_state_*/warn_reset_*),warn_config_changed 与 warn_sdk_changed 改为描述"由视频重算";ui_check 深度段、studio 两个深度用例、test_playback 三例、drive_shared 的 test_depth_replay 与 reproduce.py 的深度段一并清理(shared 套件 150→134 项)。2) 无法播放的根因是 RecordingReader.video_frame_at 每帧都 cv2.VideoCapture 重开并 seek:小片段已 ~10 ms/帧,1080p H.264 每次 seek 需从关键帧解码(上百 ms),而播放每 33 ms 要一帧且全在 UI 线程上,视频因此看着不动;改为按分段缓存解码器、顺序帧用 read() 步进(实测 10 ms → 0.5 ms/帧)、跳转才 seek,新增 RecordingReader.close() 释放。运输控制按用户建议上移到状态轨主按钮:toolbar.set_run_role("playback") 使回放视图的主按钮变成「▶ 回放」/「⏸ 暂停」(实时视图仍是「▶ 运行」),点按以 1× 推进游标(墙钟计时,漏拍不变慢)、到末尾自动停,拖动游标/框选/滚轮/拖总览条经 ForcePlotsPanel.timeline_taken_over 自动暂停,空格键同效;曲线表头不再放第二个播放按钮;按钮样式在回放角色下不再套用"停止"的红色语义。 - Why: 用户要求先清掉深度重算引擎、并反馈"现在无法播放视频",建议把上方播放按钮在历史回放形式下改为回放。 - Verification: ui_check.py 554 项 0 失败(新增 seek<250ms/步进<60ms 的解码预算、回放按钮文字随播放/暂停切换、手动拖游标即暂停、切回实时视图按钮恢复运行、按深度视图已移除);test_ui_smoke.py 71 项 0 失败;drive_shared.py 134 项 0 失败(深度引擎用例随代码删除);locale_studio.py 262 键齐全、zh==en;reproduce.py 退出 0;.repro/probe_play.py 实测按「回放」后 0.75 s 内游标推进 0.689 s、面板 12/12 帧都在更新;两张复核图(回放视图的「▶ 回放」、实时视图的「▶ 运行」)。 - Not in this change: 录制文件格式与 CSV 列序不变(raw_*.mp4 与 forces.csv 继续一起写);「由视频重算」复核模式保留(它复用实时管线,是有状态、只能顺序前进的一遍)。
This commit is contained in:
@@ -848,109 +848,6 @@ def test_a_failed_recording_is_consumed_even_when_it_is_not_running(make_window)
|
||||
_finish(window, stuck)
|
||||
|
||||
|
||||
def test_the_depth_request_belongs_to_the_recording_it_started_in(make_window, record_root):
|
||||
"""Opening another recording must invalidate the request still running."""
|
||||
import threading as _threading
|
||||
import time as _time
|
||||
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.record.depth import DepthResult
|
||||
from orisys_studio.replay import ReplayController
|
||||
|
||||
window = _show(make_window(), (1200, 700))
|
||||
replay = ReplayController()
|
||||
replay.open_recording(Path(record_root))
|
||||
first_serial = replay._record_serial
|
||||
|
||||
started = _threading.Event()
|
||||
release = _threading.Event()
|
||||
closed: list[str] = []
|
||||
|
||||
class _Blocking:
|
||||
def preflight(self) -> str:
|
||||
return ""
|
||||
|
||||
def depth_at(self, _position, **_kwargs):
|
||||
started.set()
|
||||
release.wait(10.0)
|
||||
return DepthResult(
|
||||
depth=np.ones((2, 2)), frame_id=0, t_rel_s=0.0, record_id="old"
|
||||
)
|
||||
|
||||
def close(self) -> None:
|
||||
closed.append("closed")
|
||||
|
||||
blocking = _Blocking()
|
||||
replay._recomputer = blocking
|
||||
replay.request_depth()
|
||||
assert started.wait(5.0), "the depth request never started"
|
||||
|
||||
# Another recording is opened while the request is in flight.
|
||||
replay.open_recording(Path(record_root))
|
||||
assert replay._record_serial == first_serial + 1
|
||||
assert replay._recomputer is not blocking, "the processor was not replaced"
|
||||
assert closed == [], "the UI thread closed a processor a worker was reading"
|
||||
|
||||
release.set()
|
||||
deadline = _time.monotonic() + 10.0
|
||||
while not closed and _time.monotonic() < deadline:
|
||||
QApplication.processEvents()
|
||||
_time.sleep(0.01)
|
||||
assert closed == ["closed"], "the retired processor was never closed"
|
||||
assert replay._depth_thread is not None and not replay._depth_thread.is_alive()
|
||||
|
||||
# The answer belongs to the previous recording, so it is dropped, not drawn.
|
||||
replay._poll_depth()
|
||||
QApplication.processEvents()
|
||||
assert replay._depth_ok is False
|
||||
assert replay.depth_image() is None, "an answer for another recording was drawn"
|
||||
replay.close()
|
||||
window.close()
|
||||
|
||||
|
||||
def test_a_busy_depth_processor_is_not_closed_by_the_ui_thread(make_window, record_root):
|
||||
import threading as _threading
|
||||
import time as _time
|
||||
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.record.depth import DepthResult
|
||||
from orisys_studio.replay import ReplayController
|
||||
|
||||
window = _show(make_window(), (1200, 700))
|
||||
replay = ReplayController()
|
||||
replay.open_recording(Path(record_root))
|
||||
started = _threading.Event()
|
||||
release = _threading.Event()
|
||||
closed: list[str] = []
|
||||
|
||||
class _Blocking:
|
||||
def preflight(self) -> str:
|
||||
return ""
|
||||
|
||||
def depth_at(self, _position, **_kwargs):
|
||||
started.set()
|
||||
release.wait(10.0)
|
||||
return DepthResult(depth=np.ones((2, 2)), frame_id=0, t_rel_s=0.0)
|
||||
|
||||
def close(self) -> None:
|
||||
closed.append("closed")
|
||||
|
||||
replay._recomputer = _Blocking()
|
||||
replay.request_depth()
|
||||
assert started.wait(5.0)
|
||||
replay.close() # closing must not free a processor in use
|
||||
QApplication.processEvents()
|
||||
assert closed == [], "the controller closed a processor that was still reading"
|
||||
release.set()
|
||||
deadline = _time.monotonic() + 10.0
|
||||
while not closed and _time.monotonic() < deadline:
|
||||
QApplication.processEvents()
|
||||
_time.sleep(0.01)
|
||||
assert closed == ["closed"], "the owning thread never closed the processor"
|
||||
window.close()
|
||||
|
||||
|
||||
def test_a_confirmed_calibration_is_snapshotted_into_the_recording(make_window, record_root):
|
||||
"""The recording keeps the coefficients themselves, not only their hash."""
|
||||
@@ -1691,8 +1588,10 @@ def test_the_replay_view_loads_a_recording_and_scrubs_it(make_window, replay_rec
|
||||
|
||||
assert window.arrow_panel.view_modes() == ("recorded",)
|
||||
assert window.replay.video_image() is not None, "no frame at the cursor"
|
||||
assert panel.btn_play.isEnabled(), "the replay has no transport"
|
||||
assert not panel.btn_play.isChecked(), "the replay starts out playing"
|
||||
assert window.toolbar.run_role() == "playback", "the rail lost the transport"
|
||||
assert window.toolbar.btn_run.isEnabled(), "the replay has no transport"
|
||||
assert not window.toolbar.btn_run.isChecked(), "the replay starts out playing"
|
||||
assert window.toolbar.btn_run.text() == t("toolbar.playback")
|
||||
|
||||
# The cursor is the playhead: setting it moves the video.
|
||||
window.replay.set_position(window.replay.duration_s() / 2)
|
||||
@@ -1701,19 +1600,21 @@ def test_the_replay_view_loads_a_recording_and_scrubs_it(make_window, replay_rec
|
||||
assert abs(panel.cursors[0].value() - window.replay.position()) < 0.05
|
||||
|
||||
# Dragging the chart cursor scrubs the recording too, and pauses it.
|
||||
panel.btn_play.setChecked(True)
|
||||
window.toolbar.btn_run.setChecked(True)
|
||||
QApplication.processEvents()
|
||||
assert window.toolbar.btn_run.text() == t("toolbar.playback_pause")
|
||||
panel.cursors[0].setValue(0.25)
|
||||
QApplication.processEvents()
|
||||
assert abs(window.replay.position() - 0.25) < 0.05
|
||||
assert not panel.btn_play.isChecked(), "dragging the cursor did not pause playback"
|
||||
assert not window.toolbar.btn_run.isChecked(), "dragging the cursor did not pause playback"
|
||||
|
||||
# Leaving the view returns the charts to the live session.
|
||||
window.brand_bar.btn_live.click()
|
||||
QApplication.processEvents()
|
||||
assert not panel.is_recording_source()
|
||||
assert panel.mode() == "follow"
|
||||
assert not panel.btn_play.isEnabled(), "the live view still offers the transport"
|
||||
assert window.toolbar.run_role() == "acquire", "the rail kept the transport"
|
||||
assert window.toolbar.btn_run.text() == t("toolbar.run_label")
|
||||
assert window.arrow_panel.view_modes() == ("arrows_pure", "arrows_stitched", "depth")
|
||||
assert "raw_0001" not in window.toolbar.source_summary.text(), (
|
||||
"the replay's video is still named in the live view"
|
||||
@@ -2027,8 +1928,8 @@ def test_the_replay_transport_plays_and_pauses(make_window, replay_recording):
|
||||
QApplication.processEvents()
|
||||
panel = window.force_plots
|
||||
|
||||
assert panel.btn_play.isEnabled() and not panel.btn_play.isChecked()
|
||||
panel.btn_play.setChecked(True)
|
||||
assert window.toolbar.btn_run.isEnabled() and not window.toolbar.btn_run.isChecked()
|
||||
window.toolbar.btn_run.setChecked(True)
|
||||
started = time.perf_counter()
|
||||
for _ in range(10):
|
||||
QApplication.processEvents()
|
||||
@@ -2045,7 +1946,7 @@ def test_the_replay_transport_plays_and_pauses(make_window, replay_recording):
|
||||
# Dragging the cursor is a manual gesture: playback pauses where it is.
|
||||
panel.cursors[0].setValue(0.2)
|
||||
QApplication.processEvents()
|
||||
assert not panel.btn_play.isChecked(), "dragging the cursor did not pause"
|
||||
assert not window.toolbar.btn_run.isChecked(), "dragging the cursor did not pause"
|
||||
assert abs(window.replay.position() - 0.2) < 0.05
|
||||
parked = window.replay.position()
|
||||
for _ in range(6):
|
||||
@@ -2082,13 +1983,15 @@ def test_the_recompute_mode_reruns_the_pipeline_over_the_video(
|
||||
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 panel.btn_play.isChecked(), "the transport does not show the pass as running"
|
||||
assert window.toolbar.btn_run.isChecked(), (
|
||||
"the transport does not show the pass as running"
|
||||
)
|
||||
|
||||
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 panel.btn_play.isChecked(), "the transport stayed checked"
|
||||
assert not window.toolbar.btn_run.isChecked(), "the transport stayed checked"
|
||||
|
||||
|
||||
# ------------------------------------------- selection readout and notices
|
||||
|
||||
Reference in New Issue
Block a user