added camera search and select, auto preference saving and version tag to 0.4.2

This commit is contained in:
ykcheungab
2026-07-15 15:08:59 +08:00
parent 7b12d17595
commit 11a747a771
9 changed files with 525 additions and 18 deletions
+129 -3
View File
@@ -22,6 +22,8 @@ from pipeline.csv_recorder import CsvRecorder
from pipeline.service import FingerPipeline
from .app_paths import resource_path
from .camera_discovery import list_cameras, pick_default_camera
from .preferences import get_preferred_video_source, load_preferences, save_preferences
from .layout import (
DashboardLayout,
LAYOUT,
@@ -102,26 +104,45 @@ class FingerDashboardWindow(QMainWindow):
mesh_state=self.mesh_state,
show_log=self._log_panel_visible,
)
# CLI / pipeline defaults first, then overlay saved user preferences.
self.settings_panel.set_video_source(str(self.config.video))
self.settings_panel.set_config_path(self.config.config)
self.settings_panel.set_calibration_path(self.config.calib or "")
self.settings_panel.set_finger_export_dir(self.config.export_dir)
self.settings_panel.set_csv_save_dir(self.config.csv_save_dir)
self.settings_panel.log_visibility_changed.connect(self._set_log_panel_visible)
self.settings_panel.free_rotation_changed.connect(self.mesh_viewport.set_free_rotation)
self._preferences = load_preferences()
self.settings_panel.apply_preference_state(self._preferences)
self._log_panel_visible = bool(self.settings_panel.show_log_check.isChecked())
self.mesh_viewport.set_free_rotation(bool(self.settings_panel.free_rotate_check.isChecked()))
if self.arrow_panel is not None:
self.settings_panel.arrow_background_changed.connect(self.arrow_panel.set_background)
self.arrow_panel.set_background(self.settings_panel.arrow_background())
# Sync pipeline config fields from restored panel values.
self.config.video = self.settings_panel.video_source() or self.config.video
self.config.config = self.settings_panel.config_path() or self.config.config
self.config.export_dir = self.settings_panel.finger_export_dir() or self.config.export_dir
self.config.csv_save_dir = self.settings_panel.csv_save_dir() or self.config.csv_save_dir
self.settings_panel.log_visibility_changed.connect(self._on_log_visibility_changed)
self.settings_panel.free_rotation_changed.connect(self._on_free_rotation_changed)
if self.arrow_panel is not None:
self.settings_panel.arrow_background_changed.connect(self._on_arrow_background_changed)
else:
self.settings_panel.arrow_background_changed.connect(lambda _v: self._save_preferences())
self.settings_panel.view_reset_requested.connect(self.mesh_viewport.reset_view)
self.settings_panel.browse_video_requested.connect(self._select_camera)
self.settings_panel.camera_refresh_requested.connect(self._refresh_cameras)
self.settings_panel.camera_selection_changed.connect(self._on_camera_selection_changed)
self.settings_panel.browse_config_requested.connect(self._select_config)
self.settings_panel.browse_finger_assets_requested.connect(self._select_finger_assets)
self.settings_panel.browse_csv_dir_requested.connect(self._select_csv_dir)
self.settings_panel.csv_record_toggled.connect(self._on_csv_record_toggled)
self.settings_panel.show_flow_arrows_check.toggled.connect(lambda _v: self._save_preferences())
self.settings_panel.btn_play_pause.clicked.connect(self._on_play_pause_clicked)
self.settings_panel.btn_reset_tracker.clicked.connect(self._on_reset)
self.settings_panel.set_play_pause_state("run")
self.settings_panel.set_csv_controls_for_pipeline(False)
self._refresh_cameras(announce=True)
self._panel_widgets: dict[str, QWidget] = {}
self._panel_widgets["mesh"] = self.mesh_viewport
@@ -162,6 +183,37 @@ class FingerDashboardWindow(QMainWindow):
self.log_widget.setVisible(self._log_panel_visible)
QTimer.singleShot(0, self._apply_panel_layout)
def _on_log_visibility_changed(self, visible: bool) -> None:
self._set_log_panel_visible(visible)
self._save_preferences()
def _on_free_rotation_changed(self, enabled: bool) -> None:
self.mesh_viewport.set_free_rotation(enabled)
self._save_preferences()
def _on_arrow_background_changed(self, value: str) -> None:
if self.arrow_panel is not None:
self.arrow_panel.set_background(value)
self._save_preferences()
def _save_preferences(self) -> None:
prefs = self.settings_panel.preference_state()
self._preferences = prefs
save_preferences(prefs)
# Keep in-memory pipeline config aligned with panel values.
video = prefs.get("video_source")
if isinstance(video, str) and video.strip():
self.config.video = video.strip()
config_path = prefs.get("config_path")
if isinstance(config_path, str) and config_path.strip():
self.config.config = config_path.strip()
assets = prefs.get("finger_assets_dir")
if isinstance(assets, str) and assets.strip():
self.config.export_dir = assets.strip()
csv_dir = prefs.get("csv_save_dir")
if isinstance(csv_dir, str) and csv_dir.strip():
self.config.csv_save_dir = csv_dir.strip()
def _apply_panel_layout(self) -> None:
setup = self.layout.panel_setup
scale = self._layout_scale_factor()
@@ -323,6 +375,14 @@ class FingerDashboardWindow(QMainWindow):
self.config.export_dir = self.settings_panel.finger_export_dir()
self.config.csv_save_dir = self.settings_panel.csv_save_dir() or "./outputs/csv"
if not self.config.video:
self._set_play_pause_button("run")
self.settings_panel.set_finger_export_dir_enabled(True)
print("请先选择摄像机或视频文件。")
return
self._save_preferences()
try:
self.pipeline = FingerPipeline(self.config)
self.pipeline.open()
@@ -342,6 +402,7 @@ class FingerDashboardWindow(QMainWindow):
self.timer.start(self.layout.timer_interval_ms)
self._set_play_pause_button("pause")
self.settings_panel.btn_reset_tracker.setEnabled(True)
self.settings_panel.set_camera_controls_enabled(False)
self.settings_panel.set_csv_controls_for_pipeline(True)
print(
f"▶ 启动 — 摄像机={self.config.video}, 配置={self.config.config}, "
@@ -364,7 +425,9 @@ class FingerDashboardWindow(QMainWindow):
self._set_play_pause_button("run")
self.settings_panel.btn_reset_tracker.setEnabled(False)
self.settings_panel.set_finger_export_dir_enabled(True)
self.settings_panel.set_camera_controls_enabled(True)
self.settings_panel.set_csv_controls_for_pipeline(False)
self._update_play_enabled_for_source()
def _on_reset(self) -> None:
if not self._running:
@@ -459,6 +522,60 @@ class FingerDashboardWindow(QMainWindow):
)
)
def _refresh_cameras(self, announce: bool = True) -> None:
"""Scan cameras and populate the settings dropdown."""
keep_file = self.settings_panel.is_file_source()
existing_file = self.settings_panel.video_source() if keep_file else ""
preferred = None
current = self.settings_panel.selected_camera_source()
saved = get_preferred_video_source(self._preferences)
cli_video = str(self.config.video).strip()
if current.isdigit():
preferred = current
elif saved and str(saved).isdigit():
preferred = str(saved)
elif cli_video.isdigit():
preferred = cli_video
cameras = list_cameras()
selected = pick_default_camera(cameras, preferred)
self.settings_panel.set_camera_options(cameras, selected)
if keep_file and existing_file:
self.settings_panel.set_video_source(existing_file)
elif selected:
self.settings_panel.set_video_source(selected)
self.config.video = selected
elif not keep_file:
self.config.video = ""
self._update_play_enabled_for_source()
self._save_preferences()
if announce:
if cameras:
names = ", ".join(cam.label() for cam in cameras)
print(f"已发现 {len(cameras)} 路摄像机:{names}")
if selected and not keep_file:
print(f"当前摄像机:{selected}")
else:
print("未发现可用摄像机。可点击 Refresh 重扫,或通过 … 选择视频文件。")
def _update_play_enabled_for_source(self) -> None:
if self._running:
return
usable = self.settings_panel.has_usable_source()
self.settings_panel.set_play_pause_state("run")
self.settings_panel.btn_play_pause.setEnabled(usable)
def _on_camera_selection_changed(self, camera_id: str) -> None:
camera_id = str(camera_id).strip()
if not camera_id:
return
self.config.video = camera_id
self._save_preferences()
self._update_play_enabled_for_source()
def _select_camera(self) -> None:
path, _ = QFileDialog.getOpenFileName(
self,
@@ -468,6 +585,9 @@ class FingerDashboardWindow(QMainWindow):
)
if path:
self.settings_panel.set_video_source(path)
self.config.video = path
self._save_preferences()
self._update_play_enabled_for_source()
def _select_config(self) -> None:
path, _ = QFileDialog.getOpenFileName(
@@ -478,6 +598,8 @@ class FingerDashboardWindow(QMainWindow):
)
if path:
self.settings_panel.set_config_path(path)
self.config.config = path
self._save_preferences()
def _select_finger_assets(self) -> None:
path = QFileDialog.getExistingDirectory(
@@ -487,6 +609,8 @@ class FingerDashboardWindow(QMainWindow):
)
if path:
self.settings_panel.set_finger_export_dir(path)
self.config.export_dir = path
self._save_preferences()
def _select_csv_dir(self) -> None:
path = QFileDialog.getExistingDirectory(
@@ -497,6 +621,7 @@ class FingerDashboardWindow(QMainWindow):
if path:
self.settings_panel.set_csv_save_dir(path)
self.config.csv_save_dir = path
self._save_preferences()
def _on_csv_record_toggled(self, start: bool) -> None:
if start:
@@ -556,6 +681,7 @@ class FingerDashboardWindow(QMainWindow):
def closeEvent(self, event):
self.timer.stop()
self._stop_csv_recording(announce=True)
self._save_preferences()
self.mesh_viewport.close_renderer()
self.pipeline.close()
event.accept()