summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/motion_capture/camera_panel.py18
-rw-r--r--plugins/motion_capture/device.py17
-rw-r--r--plugins/motion_capture/plugin.py33
-rw-r--r--plugins/motion_capture/tracker.py8
-rw-r--r--plugins/motion_capture/window.py23
5 files changed, 89 insertions, 10 deletions
diff --git a/plugins/motion_capture/camera_panel.py b/plugins/motion_capture/camera_panel.py
index 5e25f7f..279945e 100644
--- a/plugins/motion_capture/camera_panel.py
+++ b/plugins/motion_capture/camera_panel.py
@@ -21,6 +21,15 @@ _RESOLUTIONS = {
"2560 × 1440": (2560, 1440),
}
+_FPS = {
+ "Default": None,
+ "15 fps": 15,
+ "24 fps": 24,
+ "30 fps": 30,
+ "60 fps": 60,
+ "120 fps": 120,
+}
+
class CameraScanThread(QThread):
cameras_found = pyqtSignal(list) # list of (index: int, label: str)
@@ -64,7 +73,14 @@ class CameraPanel(QWidget):
form.setContentsMargins(0, 0, 0, 0)
self.res_cb = QComboBox()
self.res_cb.addItems(list(_RESOLUTIONS.keys()))
+ self.res_cb.setCurrentText("1280 × 720")
form.addRow("Resolution:", self.res_cb)
+
+ self.fps_cb = QComboBox()
+ self.fps_cb.addItems(list(_FPS.keys()))
+ self.fps_cb.setCurrentText("30 fps")
+ form.addRow("Frame Rate:", self.fps_cb)
+
lay.addLayout(form)
note = QLabel(
@@ -91,9 +107,11 @@ class CameraPanel(QWidget):
resolution: Optional[Tuple[int, int]] = _RESOLUTIONS.get(
self.res_cb.currentText()
)
+ fps: Optional[int] = _FPS.get(self.fps_cb.currentText())
return CameraDevice(
device_id=device_id,
camera_index=self._selected_index,
simulate=self._simulate,
resolution=resolution,
+ fps=fps,
)
diff --git a/plugins/motion_capture/device.py b/plugins/motion_capture/device.py
index 8eb1591..58f89ce 100644
--- a/plugins/motion_capture/device.py
+++ b/plugins/motion_capture/device.py
@@ -24,10 +24,13 @@ from devices.base_device import (
class CameraDevice(BaseDevice):
+ DEVICE_TYPE = "camera"
+
def __init__(self, device_id: str = "cam_0",
camera_index: int = 0,
simulate: bool = False,
- resolution=None):
+ resolution=None,
+ fps=None):
name = "Camera (Sim)" if simulate else f"Camera {camera_index}"
super().__init__(DeviceInfo(
device_id = device_id,
@@ -57,6 +60,7 @@ class CameraDevice(BaseDevice):
self._camera_index = camera_index
self._simulate = simulate
self._resolution = resolution
+ self._fps = fps
self._tracker: Optional[Any] = None
self._tracking_win = None
@@ -68,6 +72,7 @@ class CameraDevice(BaseDevice):
self._tracker.start(
-1 if self._simulate else self._camera_index,
resolution=self._resolution,
+ fps=self._fps,
)
if self._tracker.simulated:
self.status = DeviceStatus.SIMULATED
@@ -93,6 +98,16 @@ class CameraDevice(BaseDevice):
x, y = pos
return {"x_pos": float(x), "y_pos": float(y)}
+ def get_save_config(self) -> dict:
+ return {
+ "device_type": self.DEVICE_TYPE,
+ "device_id": self.info.device_id,
+ "camera_index": self._camera_index,
+ "simulate": self._simulate,
+ "resolution": list(self._resolution) if self._resolution else None,
+ "fps": self._fps,
+ }
+
def write_channel(self, channel_id: str, value: Any) -> bool:
return False
diff --git a/plugins/motion_capture/plugin.py b/plugins/motion_capture/plugin.py
index 0b44cb4..9cf9557 100644
--- a/plugins/motion_capture/plugin.py
+++ b/plugins/motion_capture/plugin.py
@@ -49,12 +49,16 @@ class MotionCapturePlugin(LabPlugin):
# ── Lifecycle ─────────────────────────────────────────────────────────
def on_load(self, context: PluginContext) -> None:
- self._ctx = context
- self._btn = None # toolbar button reference
- self._windows: dict = {} # device_id → MotionCaptureWindow
+ self._ctx = context
+ self._btn = None # toolbar button reference
+ self._windows: dict = {} # device_id → MotionCaptureWindow
+ self._window_state: dict = {} # device_id → {px_per_mm, camera_idx}
from ui.add_device_dialog import _PANELS
from camera_panel import CameraPanel
_PANELS["Camera"] = (CameraPanel, "cam")
+ from core.profile import ProfileManager
+ from device import CameraDevice
+ ProfileManager.register_device_factory(CameraDevice.DEVICE_TYPE, CameraDevice)
def on_unload(self) -> None:
for win in list(self._windows.values()):
@@ -62,6 +66,9 @@ class MotionCapturePlugin(LabPlugin):
self._windows.clear()
from ui.add_device_dialog import _PANELS
_PANELS.pop("Camera", None)
+ from core.profile import ProfileManager
+ from device import CameraDevice
+ ProfileManager.unregister_device_factory(CameraDevice.DEVICE_TYPE)
# ── Integration hooks ─────────────────────────────────────────────────
@@ -133,11 +140,27 @@ class MotionCapturePlugin(LabPlugin):
self._btn.setChecked(False)
return
win = MotionCaptureWindow(device._tracker)
- win.closed.connect(lambda: self._close_window(dev_id))
+
+ # Restore mode/shape from live tracker state
+ win.restore_ui_state(
+ mode=device._tracker.track_mode,
+ shape=device._tracker.template_shape,
+ )
+ # Restore px/mm and camera index from last session
+ if dev_id in self._window_state:
+ state = self._window_state[dev_id]
+ win.set_px_per_mm(state["px_per_mm"])
+ win.set_camera_index(state["camera_idx"])
+
+ win.closed.connect(lambda d=dev_id, w=win: self._on_window_closed(d, w))
self._windows[dev_id] = win
win.show()
- def _close_window(self, dev_id: str) -> None:
+ def _on_window_closed(self, dev_id: str, win) -> None:
+ self._window_state[dev_id] = {
+ "px_per_mm": win.get_px_per_mm(),
+ "camera_idx": win.get_camera_index(),
+ }
self._windows.pop(dev_id, None)
if not self._windows and self._btn is not None:
self._btn.setChecked(False)
diff --git a/plugins/motion_capture/tracker.py b/plugins/motion_capture/tracker.py
index ffc9f25..8dc89f1 100644
--- a/plugins/motion_capture/tracker.py
+++ b/plugins/motion_capture/tracker.py
@@ -128,7 +128,8 @@ class CameraTracker:
# ── Start / stop ──────────────────────────────────────────────────────
def start(self, camera_index: int = 0,
- resolution: Optional[Tuple[int, int]] = None) -> bool:
+ resolution: Optional[Tuple[int, int]] = None,
+ fps: Optional[int] = None) -> bool:
self.stop()
self._running = True
@@ -145,6 +146,8 @@ class CameraTracker:
if resolution:
cap.set(cv2.CAP_PROP_FRAME_WIDTH, resolution[0])
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, resolution[1])
+ if fps:
+ cap.set(cv2.CAP_PROP_FPS, fps)
if cap.isOpened():
with self._lock:
self._cap = cap
@@ -271,6 +274,9 @@ class CameraTracker:
with self._lock:
return self._lost
+ def is_running(self) -> bool:
+ return self._running and self._thread is not None and self._thread.is_alive()
+
@property
def fps(self) -> float:
return self._fps
diff --git a/plugins/motion_capture/window.py b/plugins/motion_capture/window.py
index bc9a0de..46b8850 100644
--- a/plugins/motion_capture/window.py
+++ b/plugins/motion_capture/window.py
@@ -245,10 +245,9 @@ class MotionCaptureWindow(QWidget):
closed = pyqtSignal()
def __init__(self, tracker, parent=None):
- super().__init__(parent, Qt.WindowType.Window | Qt.WindowType.Tool)
+ super().__init__(parent, Qt.WindowType.Window)
self._tracker = tracker
- self._connected = False
- self._selecting = False # True while frame is frozen for selection
+ self._selecting = False
self.setWindowTitle("Motion Capture")
self.setMinimumSize(680, 560)
@@ -256,6 +255,11 @@ class MotionCaptureWindow(QWidget):
self._build()
+ # Reflect tracker's actual state — it may already be running
+ self._connected = tracker.is_running()
+ self._sim_badge.setVisible(self._connected and tracker.simulated)
+ self._sync_controls()
+
self._refresh = QTimer(self)
self._refresh.timeout.connect(self._update)
self._refresh.start(33)
@@ -500,6 +504,19 @@ class MotionCaptureWindow(QWidget):
# ── State accessors ────────────────────────────────────────────────────
+ def restore_ui_state(self, mode: str, shape: str):
+ """Sync mode/shape UI to tracker state without triggering tracker resets."""
+ self._mode_cb.blockSignals(True)
+ self._shape_cb.blockSignals(True)
+ self._mode_cb.setCurrentIndex(1 if mode == "csrt" else 0)
+ self._shape_cb.setCurrentIndex(1 if shape == "circle" else 0)
+ is_template = mode == "template"
+ self._shape_lbl.setVisible(is_template)
+ self._shape_cb.setVisible(is_template)
+ self._mode_cb.blockSignals(False)
+ self._shape_cb.blockSignals(False)
+ self._feed.set_shape(shape)
+
def get_camera_index(self) -> int:
i = self._cam_combo.currentIndex()
cameras = getattr(self, "_cameras", [])