把分析视频和分析设置挪到独立的「分析」菜单。

菜单栏为文件 | 采集 | 分析 | 视图 | 帮助。提示里指向这两项的路径已改成「分析」。
This commit is contained in:
ykcheungab
2026-10-07 21:36:13 +08:00
parent 154a84ddae
commit 3e45ecdc3c
5 changed files with 38 additions and 31 deletions
+7 -6
View File
@@ -21,6 +21,7 @@
"playback": "Open recording replay…", "playback": "Open recording replay…",
"playback_latest": "Replay most recent recording", "playback_latest": "Replay most recent recording",
"session": "Session", "session": "Session",
"analysis": "Analysis",
"view": "View", "view": "View",
"theme": "Theme", "theme": "Theme",
"theme_dark": "Dark", "theme_dark": "Dark",
@@ -37,7 +38,7 @@
"plot_settings": "Plot settings…", "plot_settings": "Plot settings…",
"rotate_arrows": "Rotate arrows 90°", "rotate_arrows": "Rotate arrows 90°",
"recompute": "Analyze video", "recompute": "Analyze video",
"recompute_tip": "On: serve a stored analysis result for this video, or run the pass if there is none. Off: leave analysis and read the recording’s own forces.csv when it has one. To change profile, calibration or reference frame use View ▸ Analysis setup…", "recompute_tip": "On: serve a stored analysis result for this video, or run the pass if there is none. Off: leave analysis and read the recording’s own forces.csv when it has one. To change profile, calibration or reference frame use Analysis ▸ Analysis setup…",
"analysis_setup": "Analysis setup…", "analysis_setup": "Analysis setup…",
"analysis_setup_tip": "Choose the profile, calibration and reference frame for this video, then run (or re-run) the analysis pass.", "analysis_setup_tip": "Choose the profile, calibration and reference frame for this video, then run (or re-run) the analysis pass.",
"run": "Run / Stop", "run": "Run / Stop",
@@ -115,7 +116,7 @@
"choose_video": "Choose a recorded video…", "choose_video": "Choose a recorded video…",
"choose_video_tip": "Open a recorded video: the 2D panel plays it and the charts read the forces.csv beside it", "choose_video_tip": "Open a recorded video: the 2D panel plays it and the charts read the forces.csv beside it",
"recompute": "Analyze video…", "recompute": "Analyze video…",
"recompute_tip": "Serve a stored analysis result for this video, or run the pass if there is none. The 2D panel can then draw arrows and depth; the charts may show force from the result. Stop cancels a pass that is still running. To change profile, calibration or reference frame use View ▸ Analysis setup…", "recompute_tip": "Serve a stored analysis result for this video, or run the pass if there is none. The 2D panel can then draw arrows and depth; the charts may show force from the result. Stop cancels a pass that is still running. To change profile, calibration or reference frame use Analysis ▸ Analysis setup…",
"replay_view_only": "Open a recording from the History replay view", "replay_view_only": "Open a recording from the History replay view",
"preview": "Preview", "preview": "Preview",
"preview_stop": "Stop preview", "preview_stop": "Stop preview",
@@ -134,7 +135,7 @@
"playback_tip": "Play this recording at 1× (space bar does the same); dragging the cursor pauses", "playback_tip": "Play this recording at 1× (space bar does the same); dragging the cursor pauses",
"playback_pause_tip": "Pause playback (dragging the cursor, box zoom or the wheel also pauses)", "playback_pause_tip": "Pause playback (dragging the cursor, box zoom or the wheel also pauses)",
"playback_disabled": "Open a recorded video first (“Choose a recorded video…” at the top left)", "playback_disabled": "Open a recorded video first (“Choose a recorded video…” at the top left)",
"playback_needs_recording": "This video has no recording beside it (no forces.csv): replay needs a recording; to just watch the video open it as a video, or use View ▸ Analyze video", "playback_needs_recording": "This video has no recording beside it (no forces.csv): replay needs a recording; to just watch the video open it as a video, or use Analysis ▸ Analyze video",
"playback_video_only": "Play this video (no recording beside it: the picture plays, the force charts are empty)", "playback_video_only": "Play this video (no recording beside it: the picture plays, the force charts are empty)",
"reset_sensor": "Reset sensor", "reset_sensor": "Reset sensor",
"reset_sensor_tip": "Reset the sensor tracker and its reference frame. Force thresholds remain as configured; chart history is kept.", "reset_sensor_tip": "Reset the sensor tracker and its reference frame. Force thresholds remain as configured; chart history is kept.",
@@ -297,10 +298,10 @@
"video_filter": "Recorded video (*.mp4 *.avi *.mov *.mkv);;All files (*)", "video_filter": "Recorded video (*.mp4 *.avi *.mov *.mkv);;All files (*)",
"no_video": "This recording has no usable video segment", "no_video": "This recording has no usable video segment",
"open_failed": "Could not open that recording: {error}", "open_failed": "Could not open that recording: {error}",
"not_a_recording": "“{name}” is not a recording: its folder has no forces.csv (a recording is the video plus forces.csv and session.json). Replay needs a recording; to just watch this video, open it as a video or use View ▸ Analyze video.", "not_a_recording": "“{name}” is not a recording: its folder has no forces.csv (a recording is the video plus forces.csv and session.json). Replay needs a recording; to just watch this video, open it as a video or use Analysis ▸ Analyze video.",
"video_only_opened": "Opened “{name}”; no analysis result yet. Choose Analyze video… above to generate one.", "video_only_opened": "Opened “{name}”; no analysis result yet. Choose Analyze video… above to generate one.",
"video_only_notice": "This video has no recording beside it (no forces.csv): it plays, but the force charts and the recording details are empty.", "video_only_notice": "This video has no recording beside it (no forces.csv): it plays, but the force charts and the recording details are empty.",
"video_only_panel": "This is not a recording, and this video cannot be decoded (no forces.csv beside it, and it does not read as playable video)\nYou can still try View ▸ Analyze video", "video_only_panel": "This is not a recording, and this video cannot be decoded (no forces.csv beside it, and it does not read as playable video)\nYou can still try Analysis ▸ Analyze video",
"empty_recording": "This recording has no playable picture (no raw_*.mp4 segment), so it cannot be replayed", "empty_recording": "This recording has no playable picture (no raw_*.mp4 segment), so it cannot be replayed",
"none_found": "No recording found under outputs/recordings", "none_found": "No recording found under outputs/recordings",
"frame_dropped": "This frame never reached the video (the record queue overflowed)", "frame_dropped": "This frame never reached the video (the record queue overflowed)",
@@ -320,7 +321,7 @@
"analysis_busy": "an analysis is already running: wait for it, or cancel it", "analysis_busy": "an analysis is already running: wait for it, or cancel it",
"analysis_grid_mismatch": "the current display settings moved the sample grid ({keys}); the stored candidate locations cannot serve it, so the replay keeps drawing the result's own grid", "analysis_grid_mismatch": "the current display settings moved the sample grid ({keys}); the stored candidate locations cannot serve it, so the replay keeps drawing the result's own grid",
"analysis_no_chain": "this recording does not say which sensor and configuration produced it ({error}), so no result can be read and no pass can be run", "analysis_no_chain": "this recording does not say which sensor and configuration produced it ({error}), so no result can be read and no pass can be run",
"analysis_setup_declined": "no analysis was set up for this video: the setup prompt was dismissed, so there is nothing to draw from. Use View ▸ Analysis setup… or open the video again" "analysis_setup_declined": "no analysis was set up for this video: the setup prompt was dismissed, so there is nothing to draw from. Use Analysis ▸ Analysis setup… or open the video again"
}, },
"video_analysis": { "video_analysis": {
"title": "Analyse this video", "title": "Analyse this video",
+6 -5
View File
@@ -21,6 +21,7 @@
"playback": "打开录制回放…", "playback": "打开录制回放…",
"playback_latest": "回放最近一次录制", "playback_latest": "回放最近一次录制",
"session": "采集", "session": "采集",
"analysis": "分析",
"view": "视图", "view": "视图",
"theme": "主题", "theme": "主题",
"theme_dark": "深色", "theme_dark": "深色",
@@ -37,7 +38,7 @@
"plot_settings": "绘图设置…", "plot_settings": "绘图设置…",
"rotate_arrows": "旋转箭头图 90°", "rotate_arrows": "旋转箭头图 90°",
"recompute": "分析视频", "recompute": "分析视频",
"recompute_tip": "打开:有兼容的分析结果就直接用,没有就跑一遍分析。关闭:离开分析,有 forces.csv 时曲线读录制值。要改配置、标定或参考帧请用「视图 ▸ 分析设置…」。", "recompute_tip": "打开:有兼容的分析结果就直接用,没有就跑一遍分析。关闭:离开分析,有 forces.csv 时曲线读录制值。要改配置、标定或参考帧请用「分析 ▸ 分析设置…」。",
"analysis_setup": "分析设置…", "analysis_setup": "分析设置…",
"analysis_setup_tip": "为这段视频选择配置、标定与参考帧,然后跑(或重跑)分析。", "analysis_setup_tip": "为这段视频选择配置、标定与参考帧,然后跑(或重跑)分析。",
"run": "运行 / 停止", "run": "运行 / 停止",
@@ -115,7 +116,7 @@
"choose_video": "选择录制视频…", "choose_video": "选择录制视频…",
"choose_video_tip": "打开一段录制的视频:2D 面板放视频,曲线读同目录的 forces.csv(原始记录值)", "choose_video_tip": "打开一段录制的视频:2D 面板放视频,曲线读同目录的 forces.csv(原始记录值)",
"recompute": "分析视频…", "recompute": "分析视频…",
"recompute_tip": "有兼容的分析结果就直接用,没有就跑一遍分析:2D 面板可画箭头与深度,曲线可读结果中的力。分析进行中可随时「停止」。要改配置、标定或参考帧请用「视图 ▸ 分析设置…」。", "recompute_tip": "有兼容的分析结果就直接用,没有就跑一遍分析:2D 面板可画箭头与深度,曲线可读结果中的力。分析进行中可随时「停止」。要改配置、标定或参考帧请用「分析 ▸ 分析设置…」。",
"replay_view_only": "请在「历史回放」视图里打开录像", "replay_view_only": "请在「历史回放」视图里打开录像",
"preview": "预览", "preview": "预览",
"preview_stop": "停止预览", "preview_stop": "停止预览",
@@ -134,7 +135,7 @@
"playback_tip": "按 1× 播放这段录制(空格键同效);拖动游标即暂停", "playback_tip": "按 1× 播放这段录制(空格键同效);拖动游标即暂停",
"playback_pause_tip": "暂停播放(拖动游标、框选或滚轮也会暂停)", "playback_pause_tip": "暂停播放(拖动游标、框选或滚轮也会暂停)",
"playback_disabled": "先打开一段录制视频(左上「选择录制视频…」)", "playback_disabled": "先打开一段录制视频(左上「选择录制视频…」)",
"playback_needs_recording": "这段视频旁边没有记录(缺 forces.csv):回放需要一条记录;只想看视频请直接打开视频,或用 视图 ▸ 分析视频", "playback_needs_recording": "这段视频旁边没有记录(缺 forces.csv):回放需要一条记录;只想看视频请直接打开视频,或用 分析 ▸ 分析视频",
"playback_video_only": "播放这段视频(没有配套记录:可以看画面,力曲线为空)", "playback_video_only": "播放这段视频(没有配套记录:可以看画面,力曲线为空)",
"reset_sensor": "重置传感器", "reset_sensor": "重置传感器",
"reset_sensor_tip": "重置传感器追踪器及参考帧;力值阈值保持设定值,曲线历史保留。", "reset_sensor_tip": "重置传感器追踪器及参考帧;力值阈值保持设定值,曲线历史保留。",
@@ -300,7 +301,7 @@
"not_a_recording": "「{name}」不是一条记录:同目录没有 forces.csv(一条记录 = 录像 + forces.csv + session.json)。回放需要记录;只想看这段视频请直接打开视频,或用「分析视频」。", "not_a_recording": "「{name}」不是一条记录:同目录没有 forces.csv(一条记录 = 录像 + forces.csv + session.json)。回放需要记录;只想看这段视频请直接打开视频,或用「分析视频」。",
"video_only_opened": "已打开「{name}」;尚无分析结果。点顶部「分析视频…」生成。", "video_only_opened": "已打开「{name}」;尚无分析结果。点顶部「分析视频…」生成。",
"video_only_notice": "这段视频没有配套记录(同目录无 forces.csv):可以播放,但力曲线与记录信息为空。", "video_only_notice": "这段视频没有配套记录(同目录无 forces.csv):可以播放,但力曲线与记录信息为空。",
"video_only_panel": "这不是一条记录,而且无法解码这段视频(没有 forces.csv,也不像可播放的视频文件)\n可以试试 视图 ▸ 分析视频", "video_only_panel": "这不是一条记录,而且无法解码这段视频(没有 forces.csv,也不像可播放的视频文件)\n可以试试 分析 ▸ 分析视频",
"empty_recording": "这条记录没有可播放的画面(没有 raw_*.mp4 视频分段),因此无法回放", "empty_recording": "这条记录没有可播放的画面(没有 raw_*.mp4 视频分段),因此无法回放",
"none_found": "outputs/recordings 下没有找到录制", "none_found": "outputs/recordings 下没有找到录制",
"frame_dropped": "该帧未被写入视频(记录队列曾溢出)", "frame_dropped": "该帧未被写入视频(记录队列曾溢出)",
@@ -320,7 +321,7 @@
"analysis_busy": "分析进行中:先等待或取消当前这一遍", "analysis_busy": "分析进行中:先等待或取消当前这一遍",
"analysis_grid_mismatch": "当前显示设置改了采样网格({keys}),存下来的候选位置无法服务它:回放仍按结果自己的网格绘制", "analysis_grid_mismatch": "当前显示设置改了采样网格({keys}),存下来的候选位置无法服务它:回放仍按结果自己的网格绘制",
"analysis_no_chain": "这条录像没有说明用哪套传感器与配置录下来({error}),因此既无法读取结果,也无法重算", "analysis_no_chain": "这条录像没有说明用哪套传感器与配置录下来({error}),因此既无法读取结果,也无法重算",
"analysis_setup_declined": "这段视频没有建立分析:设置提示被关掉了,因此没有可画的东西。用「视图 ▸ 分析设置…」或重新打开视频即可分析" "analysis_setup_declined": "这段视频没有建立分析:设置提示被关掉了,因此没有可画的东西。用「分析 ▸ 分析设置…」或重新打开视频即可分析"
}, },
"video_analysis": { "video_analysis": {
"title": "分析这段视频", "title": "分析这段视频",
+4 -2
View File
@@ -156,6 +156,8 @@
"purpose": "open a video, reveal the sensor config, open a recording for playback" }, "purpose": "open a video, reveal the sensor config, open a recording for playback" },
{ "id": "session_menu", "path": "session_menu", "objectName": null, "class": "QMenu", { "id": "session_menu", "path": "session_menu", "objectName": null, "class": "QMenu",
"purpose": "acquisition session controls: run, cancel, preview, tracker reset, pause" }, "purpose": "acquisition session controls: run, cancel, preview, tracker reset, pause" },
{ "id": "analysis_menu", "path": "analysis_menu", "objectName": null, "class": "QMenu",
"purpose": "offline analysis of a recording: serve or run a pass, and reopen analysis setup" },
{ "id": "view_menu", "path": "view_menu", "objectName": null, "class": "QMenu", { "id": "view_menu", "path": "view_menu", "objectName": null, "class": "QMenu",
"purpose": "what is on screen: panels, 3D, display aids, reset views, theme and language" }, "purpose": "what is on screen: panels, 3D, display aids, reset views, theme and language" },
{ "id": "theme_menu", "path": "theme_menu", "objectName": null, "class": "QMenu", { "id": "theme_menu", "path": "theme_menu", "objectName": null, "class": "QMenu",
@@ -201,9 +203,9 @@
{ "id": "act_rotate_arrows", "path": "act_rotate_arrows", "objectName": null, "class": "QAction", { "id": "act_rotate_arrows", "path": "act_rotate_arrows", "objectName": null, "class": "QAction",
"purpose": "view menu: rotate the 2D arrow field by 90 degrees for the mounted sensors" }, "purpose": "view menu: rotate the 2D arrow field by 90 degrees for the mounted sensors" },
{ "id": "act_recompute", "path": "act_recompute", "objectName": null, "class": "QAction", { "id": "act_recompute", "path": "act_recompute", "objectName": null, "class": "QAction",
"purpose": "view menu: analyze video — serve a stored result or run the pass; same state as the rail's button" }, "purpose": "analysis menu: analyze video — serve a stored result or run the pass; same state as the rail's button" },
{ "id": "act_analysis_setup", "path": "act_analysis_setup", "objectName": null, "class": "QAction", { "id": "act_analysis_setup", "path": "act_analysis_setup", "objectName": null, "class": "QAction",
"purpose": "view menu: reopen the analysis setup dialog (profile, calibration, reference frame)" }, "purpose": "analysis menu: reopen the analysis setup dialog (profile, calibration, reference frame)" },
{ "id": "act_reset_view", "path": "act_reset_view", "objectName": null, "class": "QAction", { "id": "act_reset_view", "path": "act_reset_view", "objectName": null, "class": "QAction",
"purpose": "view menu / F8: reset the views (curves, window, cursor, 3D camera) without touching the device" }, "purpose": "view menu / F8: reset the views (curves, window, cursor, 3D camera) without touching the device" },
{ "id": "act_theme_dark", "path": "act_theme_dark", "objectName": null, "class": "QAction", { "id": "act_theme_dark", "path": "act_theme_dark", "objectName": null, "class": "QAction",
+20 -17
View File
@@ -171,7 +171,7 @@ class StudioMainWindow(QMainWindow):
self._theme = str(self.prefs.get("theme") or "dark") self._theme = str(self.prefs.get("theme") or "dark")
self._language = str(self.prefs.get("language") or "zh_CN") self._language = str(self.prefs.get("language") or "zh_CN")
#: Reference-frame override for the next analysis pass on a real recording #: Reference-frame override for the next analysis pass on a real recording
#: (from View ▸ Analysis setup…). Bare-video workspaces store this in session.json. #: (from Analysis ▸ Analysis setup…). Bare-video workspaces store this in session.json.
self._analysis_reference_override: int | None = None self._analysis_reference_override: int | None = None
# The view preset, the calibration panel, the 3D pane's switch and the fps # The view preset, the calibration panel, the 3D pane's switch and the fps
# readout are the controllers' state; the window reads and writes them through # readout are the controllers' state; the window reads and writes them through
@@ -1064,7 +1064,7 @@ class StudioMainWindow(QMainWindow):
self.addAction(self.act_play) self.addAction(self.act_play)
# Acquisition session controls (run / preview / pause). Order on the bar: # Acquisition session controls (run / preview / pause). Order on the bar:
# File | Session | View | Help. # File | Session | Analysis | View | Help.
self.session_menu = menu_bar.addMenu("") self.session_menu = menu_bar.addMenu("")
self.act_run = QAction(self) self.act_run = QAction(self)
@@ -1095,6 +1095,19 @@ class StudioMainWindow(QMainWindow):
self.act_pause.toggled.connect(self._on_pause_action_toggled) self.act_pause.toggled.connect(self._on_pause_action_toggled)
self.session_menu.addAction(self.act_pause) self.session_menu.addAction(self.act_pause)
# Offline analysis of a recording. Separate from Session: that menu starts
# and stops a live device session.
self.analysis_menu = menu_bar.addMenu("")
# Serve / leave a stored analysis result (or run the pass when there is none).
self.act_recompute = QAction(self)
self.act_recompute.setCheckable(True)
self.act_recompute.setChecked(False)
self.analysis_menu.addAction(self.act_recompute)
# Reopen the setup dialog after the first pass (profile / calibration / reference).
self.act_analysis_setup = QAction(self)
self.act_analysis_setup.triggered.connect(self.replay_ctl.on_analysis_setup)
self.analysis_menu.addAction(self.act_analysis_setup)
self.view_menu = menu_bar.addMenu("") self.view_menu = menu_bar.addMenu("")
self.act_cal_panel = QAction(self) self.act_cal_panel = QAction(self)
self.act_cal_panel.setCheckable(True) self.act_cal_panel.setCheckable(True)
@@ -1142,17 +1155,6 @@ class StudioMainWindow(QMainWindow):
self.act_rotate_arrows = QAction(self) self.act_rotate_arrows = QAction(self)
self.act_rotate_arrows.triggered.connect(self.display_ctl.on_rotate_arrows) self.act_rotate_arrows.triggered.connect(self.display_ctl.on_rotate_arrows)
self.view_menu.addAction(self.act_rotate_arrows) self.view_menu.addAction(self.act_rotate_arrows)
# Serve / leave a stored analysis result (or run the pass when there is none).
self.act_recompute = QAction(self)
self.act_recompute.setCheckable(True)
self.act_recompute.setChecked(False)
self.view_menu.addAction(self.act_recompute)
# Reopen the setup dialog after the first pass (profile / calibration / reference).
self.act_analysis_setup = QAction(self)
self.act_analysis_setup.triggered.connect(self.replay_ctl.on_analysis_setup)
self.view_menu.addAction(self.act_analysis_setup)
self.view_menu.addSeparator() self.view_menu.addSeparator()
# The graphics half of the old 重置: curves, window, cursor and the 3D camera. # The graphics half of the old 重置: curves, window, cursor and the 3D camera.
@@ -1392,6 +1394,11 @@ class StudioMainWindow(QMainWindow):
self._sync_reset_action() self._sync_reset_action()
self.act_pause.setText(t("menu.pause")) self.act_pause.setText(t("menu.pause"))
self._sync_pause_action() self._sync_pause_action()
self.analysis_menu.setTitle(t("menu.analysis"))
self.act_recompute.setText(t("menu.recompute"))
self.act_recompute.setToolTip(t("menu.recompute_tip"))
self.act_analysis_setup.setText(t("menu.analysis_setup"))
self.act_analysis_setup.setToolTip(t("menu.analysis_setup_tip"))
self.view_menu.setTitle(t("menu.view")) self.view_menu.setTitle(t("menu.view"))
self.act_cal_panel.setText(t("menu.calibration_panel")) self.act_cal_panel.setText(t("menu.calibration_panel"))
self.act_finger_3d.setText(t("menu.finger_3d")) self.act_finger_3d.setText(t("menu.finger_3d"))
@@ -1401,10 +1408,6 @@ class StudioMainWindow(QMainWindow):
self.act_settings.setText(t("menu.settings")) self.act_settings.setText(t("menu.settings"))
self.act_plot_settings.setText(t("menu.plot_settings")) self.act_plot_settings.setText(t("menu.plot_settings"))
self.act_rotate_arrows.setText(t("menu.rotate_arrows")) self.act_rotate_arrows.setText(t("menu.rotate_arrows"))
self.act_recompute.setText(t("menu.recompute"))
self.act_recompute.setToolTip(t("menu.recompute_tip"))
self.act_analysis_setup.setText(t("menu.analysis_setup"))
self.act_analysis_setup.setToolTip(t("menu.analysis_setup_tip"))
self.act_reset_view.setText(t("menu.reset_view")) self.act_reset_view.setText(t("menu.reset_view"))
self.act_reset_view.setToolTip(t("menu.reset_view_tip")) self.act_reset_view.setToolTip(t("menu.reset_view_tip"))
self.theme_menu.setTitle(t("menu.theme")) self.theme_menu.setTitle(t("menu.theme"))
@@ -715,7 +715,7 @@ def test_the_prompt_restores_a_prior_reference_and_no_calibration(
def test_analysis_setup_reopens_the_prompt_after_the_first_pass( def test_analysis_setup_reopens_the_prompt_after_the_first_pass(
bare_clip, window, monkeypatch bare_clip, window, monkeypatch
): ):
"""View ▸ Analysis setup… asks again; a new reference frame starts a new pass.""" """Analysis ▸ Analysis setup… asks again; a new reference frame starts a new pass."""
open_bare_video(window, bare_clip, monkeypatch, reference=None) open_bare_video(window, bare_clip, monkeypatch, reference=None)
settle(window) settle(window)
first = window.replay_ctl.recording_directory() first = window.replay_ctl.recording_directory()