diff options
| author | Christian Kolset <christian.kolset@gmail.com> | 2026-06-09 18:47:49 -0600 |
|---|---|---|
| committer | Christian Kolset <christian.kolset@gmail.com> | 2026-06-09 18:47:49 -0600 |
| commit | 898fde4dc286517cb27590b2f9cd7406d9922bd4 (patch) | |
| tree | 02abc27a4c7e3434a171a8250a4728265b693860 /plugins/motion_capture/camera_panel.py | |
| parent | 17b2f07545b89487c2d1bb936dc52a612d38d470 (diff) | |
Upgrade motion capture: template matching, frame-freeze selection, resolution
Tracking:
- Add "template" mode (default) using snapshot + shape-masked matchTemplate
- Add "csrt" mode as alternative for texture-rich targets
- set_roi_rect(x, y, w, h, frame_rgb) initialises from exact selection rect
- set_roi() delegates to set_roi_rect (centred square, backwards-compat)
- start() accepts resolution=(w, h) to set camera capture resolution
Selection UX:
- Replace QLabel feed with FrameSelector widget (custom QWidget)
- "Click to Track" freezes frame; user scrolls to zoom (anchored to cursor),
right-drags to pan, left-drags to draw rubber-band selection
- Shape preview: rectangle or inscribed ellipse depending on mode
- "Confirm Selection" extracts template from frozen frame and starts tracking
- "Clear" cancels selection or clears active tracking
Configuration:
- CameraPanel adds Resolution combo (Default / 640×480 / 720p / 1080p / 1440p)
- CameraDevice stores and forwards resolution to tracker.start()
- Template Match listed first in Mode combo; shape combo hidden for CSRT
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'plugins/motion_capture/camera_panel.py')
| -rw-r--r-- | plugins/motion_capture/camera_panel.py | 117 |
1 files changed, 42 insertions, 75 deletions
diff --git a/plugins/motion_capture/camera_panel.py b/plugins/motion_capture/camera_panel.py index 611c942..5e25f7f 100644 --- a/plugins/motion_capture/camera_panel.py +++ b/plugins/motion_capture/camera_panel.py @@ -1,17 +1,25 @@ """ motion_capture/camera_panel.py -CameraPanel for AddDeviceDialog — scans for OpenCV cameras and builds -a CameraDevice when the user clicks "Add Device". +CameraPanel for AddDeviceDialog. +Camera discovery and simulation toggle are handled by AddDeviceDialog. +CameraScanThread is kept here and used by the unified scanner. """ from __future__ import annotations +from typing import Optional, Tuple -from PyQt6.QtCore import Qt, QThread, pyqtSignal -from PyQt6.QtWidgets import ( - QCheckBox, QGroupBox, QHBoxLayout, QLabel, QListWidget, - QListWidgetItem, QPushButton, QVBoxLayout, QWidget, -) +from PyQt6.QtCore import QThread, pyqtSignal +from PyQt6.QtWidgets import QComboBox, QFormLayout, QLabel, QVBoxLayout, QWidget + + +_RESOLUTIONS = { + "Default": None, + "640 × 480": (640, 480), + "1280 × 720": (1280, 720), + "1920 × 1080": (1920, 1080), + "2560 × 1440": (2560, 1440), +} class CameraScanThread(QThread): @@ -40,44 +48,24 @@ class CameraPanel(QWidget): def __init__(self): super().__init__() - self._cameras: list = [] - self._selected_index: int = 0 - self._scanner = None + self._selected_index: int = 0 + self._simulate: bool = False lay = QVBoxLayout(self) lay.setContentsMargins(0, 4, 0, 4) lay.setSpacing(8) - self.sim_chk = QCheckBox("Simulation mode (Lissajous pattern, no camera needed)") - self.sim_chk.setChecked(False) - lay.addWidget(self.sim_chk) - - scan_grp = QGroupBox("Available Cameras") - sg = QVBoxLayout(scan_grp) - sg.setSpacing(4) - - top = QHBoxLayout() - self._scan_btn = QPushButton("🔍 Scan Cameras") - self._scan_btn.setObjectName("addTraceBtn") - self._scan_btn.clicked.connect(self._scan) - self._status = QLabel("Click Scan to detect cameras") - self._status.setObjectName("traceSource") - top.addWidget(self._scan_btn) - top.addWidget(self._status, 1) - sg.addLayout(top) - - self._list = QListWidget() - self._list.setMaximumHeight(120) - self._list.setObjectName("portList") - self._list.itemClicked.connect(self._on_select) - self._list.setToolTip("Click a camera to select it") - sg.addWidget(self._list) - - hint = QLabel("↑ Click a camera above to select it") - hint.setObjectName("traceSource") - sg.addWidget(hint) - - lay.addWidget(scan_grp) + self._selected_lbl = QLabel("No camera selected — scan above and click a camera") + self._selected_lbl.setObjectName("traceSource") + self._selected_lbl.setWordWrap(True) + lay.addWidget(self._selected_lbl) + + form = QFormLayout() + form.setContentsMargins(0, 0, 0, 0) + self.res_cb = QComboBox() + self.res_cb.addItems(list(_RESOLUTIONS.keys())) + form.addRow("Resolution:", self.res_cb) + lay.addLayout(form) note = QLabel( "Requires OpenCV: pip install opencv-python\n" @@ -88,45 +76,24 @@ class CameraPanel(QWidget): lay.addWidget(note) lay.addStretch() - # ── Scan ───────────────────────────────────────────────────────────── - - def _scan(self): - self._scan_btn.setEnabled(False) - self._status.setText("Scanning…") - self._list.clear() - self._scanner = CameraScanThread() - self._scanner.cameras_found.connect(self._on_found) - self._scanner.start() - - def _on_found(self, cameras): - self._scan_btn.setEnabled(True) - self._cameras = cameras - self._list.clear() - if not cameras: - self._status.setText("No cameras found") - item = QListWidgetItem(" No cameras detected") - item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsSelectable) - self._list.addItem(item) - else: - self._status.setText(f"{len(cameras)} camera(s) found") - for idx, label in cameras: - item = QListWidgetItem(f" {label}") - item.setData(Qt.ItemDataRole.UserRole, idx) - self._list.addItem(item) - self._list.setCurrentRow(0) - self._selected_index = cameras[0][0] - - def _on_select(self, item: QListWidgetItem): - idx = item.data(Qt.ItemDataRole.UserRole) - if idx is not None: - self._selected_index = idx - - # ── Build ───────────────────────────────────────────────────────────── + def is_valid(self) -> bool: + return True + + def set_simulate(self, simulate: bool): + self._simulate = simulate + + def set_camera_index(self, index: int): + self._selected_index = index + self._selected_lbl.setText(f"Selected: Camera {index}") def build_device(self, device_id: str): from device import CameraDevice + resolution: Optional[Tuple[int, int]] = _RESOLUTIONS.get( + self.res_cb.currentText() + ) return CameraDevice( device_id=device_id, camera_index=self._selected_index, - simulate=self.sim_chk.isChecked(), + simulate=self._simulate, + resolution=resolution, ) |
