接入 Autocal 实时相机及帧时间显示
视频和相机共用 Finger worker,并保留采集时间、源帧编号及媒体时间。通过界面、采集链和真实视频验证。
This commit is contained in:
@@ -211,6 +211,8 @@ def default_preferences() -> dict[str, Any]:
|
||||
"finger_bundle_path": DEFAULT_FINGER_BUNDLE,
|
||||
"finger_video_path": "",
|
||||
"finger_config_ref": "",
|
||||
"finger_source_kind": "video",
|
||||
"finger_camera_index": 0,
|
||||
#: The UV-space texture for the flat picker. Preferred over the derived
|
||||
#: path when it exists; empty falls back to the layout map.
|
||||
"finger_texture_path": DEFAULT_FINGER_TEXTURE,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""Finger video input and preview for the AutoCal sensor workspace."""
|
||||
"""Finger video or live camera input for the AutoCal sensor workspace."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
@@ -11,18 +12,20 @@ from PySide6.QtCore import Qt, QTimer, Signal
|
||||
from PySide6.QtGui import QImage, QPixmap
|
||||
from PySide6.QtWidgets import (
|
||||
QFileDialog,
|
||||
QComboBox,
|
||||
QGridLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QSpinBox,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
|
||||
class FingerSourcePanel(QWidget):
|
||||
"""Run the shared finger processing path from a video file.
|
||||
"""Run the shared finger processing path from a video or camera.
|
||||
|
||||
Worker callbacks originate on its acquisition thread; the GUI timer alone
|
||||
reads packets and changes widgets. The same worker also accepts a camera ID.
|
||||
@@ -35,6 +38,9 @@ class FingerSourcePanel(QWidget):
|
||||
video_path: str = "",
|
||||
config_ref: str = "",
|
||||
parent: QWidget | None = None,
|
||||
*,
|
||||
source_kind: str = "video",
|
||||
camera_index: int = 0,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self.worker = FingerAcquisitionWorker(on_error=self.errorReported.emit)
|
||||
@@ -46,20 +52,41 @@ class FingerSourcePanel(QWidget):
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
fields = QGridLayout()
|
||||
self.source_label = QLabel(self)
|
||||
self.source_kind = QComboBox(self)
|
||||
self.source_kind.setObjectName("fingerSourceKind")
|
||||
self.source_kind.setMaximumWidth(220)
|
||||
self.source_kind.addItem("", "video")
|
||||
self.source_kind.addItem("", "camera")
|
||||
self.source_kind.setCurrentIndex(1 if source_kind == "camera" else 0)
|
||||
self.video_label = QLabel(self)
|
||||
self.video_path = QLineEdit(video_path, self)
|
||||
self.video_path.setObjectName("fingerVideoPath")
|
||||
self.video_browse = QPushButton(self)
|
||||
self.camera_label = QLabel(self)
|
||||
self.camera_index = QSpinBox(self)
|
||||
self.camera_index.setObjectName("fingerCameraIndex")
|
||||
self.camera_index.setRange(0, 31)
|
||||
try:
|
||||
remembered_camera = int(camera_index)
|
||||
except (TypeError, ValueError):
|
||||
remembered_camera = 0
|
||||
self.camera_index.setValue(max(0, min(31, remembered_camera)))
|
||||
self.camera_index.setMaximumWidth(100)
|
||||
self.config_label = QLabel(self)
|
||||
self.config_ref = QLineEdit(config_ref, self)
|
||||
self.config_ref.setObjectName("fingerConfigRef")
|
||||
self.config_browse = QPushButton(self)
|
||||
fields.addWidget(self.video_label, 0, 0)
|
||||
fields.addWidget(self.video_path, 0, 1)
|
||||
fields.addWidget(self.video_browse, 0, 2)
|
||||
fields.addWidget(self.config_label, 1, 0)
|
||||
fields.addWidget(self.config_ref, 1, 1)
|
||||
fields.addWidget(self.config_browse, 1, 2)
|
||||
fields.addWidget(self.source_label, 0, 0)
|
||||
fields.addWidget(self.source_kind, 0, 1)
|
||||
fields.addWidget(self.video_label, 1, 0)
|
||||
fields.addWidget(self.video_path, 1, 1)
|
||||
fields.addWidget(self.video_browse, 1, 2)
|
||||
fields.addWidget(self.camera_label, 2, 0)
|
||||
fields.addWidget(self.camera_index, 2, 1)
|
||||
fields.addWidget(self.config_label, 3, 0)
|
||||
fields.addWidget(self.config_ref, 3, 1)
|
||||
fields.addWidget(self.config_browse, 3, 2)
|
||||
layout.addLayout(fields)
|
||||
|
||||
controls = QHBoxLayout()
|
||||
@@ -74,6 +101,9 @@ class FingerSourcePanel(QWidget):
|
||||
controls.addWidget(self.stop_button)
|
||||
controls.addWidget(self.status_label, 1)
|
||||
layout.addLayout(controls)
|
||||
self.preview_hint = QLabel(self)
|
||||
self.preview_hint.setWordWrap(True)
|
||||
layout.addWidget(self.preview_hint)
|
||||
|
||||
self.preview = QLabel(self)
|
||||
self.preview.setObjectName("fingerVideoPreview")
|
||||
@@ -91,15 +121,22 @@ class FingerSourcePanel(QWidget):
|
||||
self.config_browse.clicked.connect(self._choose_config)
|
||||
self.start_button.clicked.connect(self.start)
|
||||
self.stop_button.clicked.connect(self.stop)
|
||||
self.source_kind.currentIndexChanged.connect(self._on_source_changed)
|
||||
self._on_source_changed()
|
||||
self.retranslate()
|
||||
|
||||
def retranslate(self) -> None:
|
||||
self.source_label.setText(t("source.kind"))
|
||||
self.source_kind.setItemText(0, t("source.kind_video"))
|
||||
self.source_kind.setItemText(1, t("source.kind_camera"))
|
||||
self.video_label.setText(t("source.video"))
|
||||
self.camera_label.setText(t("source.camera_index"))
|
||||
self.config_label.setText(t("source.config"))
|
||||
self.video_browse.setText(t("source.browse"))
|
||||
self.config_browse.setText(t("source.browse"))
|
||||
self.start_button.setText(t("source.start"))
|
||||
self.stop_button.setText(t("source.stop"))
|
||||
self.preview_hint.setText(t("source.preview_only"))
|
||||
self._set_status(self._status_key)
|
||||
if self._last_image is None:
|
||||
self.preview.setText(t("source.no_frame"))
|
||||
@@ -121,12 +158,21 @@ class FingerSourcePanel(QWidget):
|
||||
if path:
|
||||
self.config_ref.setText(path)
|
||||
|
||||
def _on_source_changed(self) -> None:
|
||||
video = self.source_kind.currentData() == "video"
|
||||
for widget in (self.video_label, self.video_path, self.video_browse):
|
||||
widget.setVisible(video)
|
||||
self.camera_label.setVisible(not video)
|
||||
self.camera_index.setVisible(not video)
|
||||
|
||||
def start(self) -> None:
|
||||
video = Path(self.video_path.text().strip())
|
||||
config = Path(self.config_ref.text().strip())
|
||||
if not video.is_file() or not config.is_file():
|
||||
is_video = self.source_kind.currentData() == "video"
|
||||
if not config.is_file() or (is_video and not video.is_file()):
|
||||
self._show_error(t("source.missing_input"))
|
||||
return
|
||||
source = str(video) if is_video else self.camera_index.value()
|
||||
self._error = ""
|
||||
self._last_frame_id = None
|
||||
self._last_image = None
|
||||
@@ -134,7 +180,7 @@ class FingerSourcePanel(QWidget):
|
||||
self.preview.setText(t("source.no_frame"))
|
||||
self.frame_label.clear()
|
||||
try:
|
||||
self.worker.start(str(video), config_ref=str(config))
|
||||
self.worker.start(source, config_ref=str(config))
|
||||
except Exception as exc: # noqa: BLE001 - show source/config errors to operator
|
||||
self._show_error(str(exc))
|
||||
return
|
||||
@@ -155,6 +201,8 @@ class FingerSourcePanel(QWidget):
|
||||
def _set_running_controls(self, running: bool) -> None:
|
||||
self.start_button.setEnabled(not running)
|
||||
self.stop_button.setEnabled(running)
|
||||
self.source_kind.setEnabled(not running)
|
||||
self.camera_index.setEnabled(not running)
|
||||
for widget in (
|
||||
self.video_path,
|
||||
self.video_browse,
|
||||
@@ -181,9 +229,21 @@ class FingerSourcePanel(QWidget):
|
||||
if identity != self._last_frame_id:
|
||||
self._last_frame_id = identity
|
||||
self._show_frame(packet.processed_frame)
|
||||
acquired = datetime.fromtimestamp(packet.timestamp).strftime(
|
||||
"%H:%M:%S.%f"
|
||||
)[:-3]
|
||||
basis_key = {
|
||||
"host_replay": "source.basis_replay",
|
||||
"host_camera": "source.basis_camera",
|
||||
}.get(getattr(packet, "time_basis", ""), "source.basis_unknown")
|
||||
basis = t(basis_key)
|
||||
self.frame_label.setText(
|
||||
t("source.frame").format(
|
||||
frame=packet.frame_id, time=packet.timestamp
|
||||
frame=packet.frame_id,
|
||||
source_frame=getattr(packet, "source_frame_id", None) or "–",
|
||||
time=acquired,
|
||||
basis=basis,
|
||||
dropped=getattr(packet, "capture_dropped", 0),
|
||||
)
|
||||
)
|
||||
if not self._error:
|
||||
@@ -194,9 +254,12 @@ class FingerSourcePanel(QWidget):
|
||||
self._active = False
|
||||
self._set_running_controls(False)
|
||||
if not self._error:
|
||||
self._set_status(
|
||||
"source.finished" if reason == "ended" else "source.stopped"
|
||||
)
|
||||
key = {
|
||||
"ended": "source.finished",
|
||||
"disconnected": "source.disconnected",
|
||||
"error": "source.failed",
|
||||
}.get(reason, "source.stopped")
|
||||
self._set_status(key)
|
||||
|
||||
def _show_frame(self, frame: np.ndarray) -> None:
|
||||
if frame.ndim != 3 or frame.shape[2] != 3:
|
||||
|
||||
@@ -292,6 +292,8 @@ class AutoCalMainWindow(QMainWindow):
|
||||
video_path=str(self.prefs.get("finger_video_path") or ""),
|
||||
config_ref=str(self.prefs.get("finger_config_ref") or ""),
|
||||
parent=body,
|
||||
source_kind=str(self.prefs.get("finger_source_kind") or "video"),
|
||||
camera_index=self.prefs.get("finger_camera_index") or 0,
|
||||
)
|
||||
self.workspace = WorkspaceTabs(
|
||||
self.axis_panel,
|
||||
@@ -1370,6 +1372,8 @@ class AutoCalMainWindow(QMainWindow):
|
||||
self.prefs["finger_config_ref"] = (
|
||||
self.finger_source_panel.config_ref.text().strip()
|
||||
)
|
||||
self.prefs["finger_source_kind"] = self.finger_source_panel.source_kind.currentData()
|
||||
self.prefs["finger_camera_index"] = self.finger_source_panel.camera_index.value()
|
||||
try:
|
||||
save_preferences(self.prefs)
|
||||
except OSError:
|
||||
|
||||
Reference in New Issue
Block a user