dasd
This commit is contained in:
parent
0afaec558f
commit
973c02a043
|
|
@ -4,6 +4,7 @@ __pycache__/
|
|||
.venv/
|
||||
.build-venv/
|
||||
venv/
|
||||
client/
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ python main.py static/test.mp4
|
|||
1. **文件 → 打开视频**
|
||||
2. 在首帧依次点击 **O**(圆心)、**M**(初始尾端参考)
|
||||
3. 拖动滑块到目标帧,点击 **M2**(当前尾端),右侧显示角度
|
||||
4. 在画面上按 **A** / **D** 可按“跳帧数”向前 / 向后跳转
|
||||
|
||||
### 测量模式
|
||||
|
||||
|
|
@ -34,6 +35,7 @@ python main.py static/test.mp4
|
|||
3. 逐帧点击 M2,每点完一帧自动进下一帧
|
||||
4. 点击 **「停止测量」** 或跑完全部帧后自动停止
|
||||
5. **「导出测量数据 (CSV)」** 保存角度 + 每帧标注截图
|
||||
6. 测量中按 **A** / **D** 按“跳帧数”跳转,按 **→** 跳过当前帧并记录 0
|
||||
|
||||
### 方向矫正
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
# Actuator Agent Guide
|
||||
|
||||
## Project
|
||||
|
||||
- Python/PyQt6 desktop tool for reading actuator angles from video frames.
|
||||
- Application entry point: `main.py`.
|
||||
- Core modules: `actuator/angle_calc.py`, `actuator/video_reader.py`, and `actuator/automation.py`.
|
||||
|
||||
## Environment
|
||||
|
||||
- Use the project-local `uv` virtual environment: `client/`.
|
||||
- Python version: 3.10.
|
||||
- Install or refresh dependencies:
|
||||
|
||||
```bash
|
||||
uv pip install --python client/bin/python -r requirements.txt
|
||||
```
|
||||
|
||||
## Run And Verify
|
||||
|
||||
```bash
|
||||
client/bin/python main.py
|
||||
```
|
||||
|
||||
For Wayland:
|
||||
|
||||
```bash
|
||||
QT_QPA_PLATFORM=xcb client/bin/python main.py
|
||||
```
|
||||
|
||||
Smoke-test GUI initialization without a display:
|
||||
|
||||
```bash
|
||||
QT_QPA_PLATFORM=offscreen timeout 5s client/bin/python main.py
|
||||
```
|
||||
|
||||
An exit status caused by `timeout` is expected when the GUI initializes and remains open.
|
||||
|
||||
## Frame Navigation
|
||||
|
||||
- Set the "跳帧数" control to choose a frame-jump step (1-10000).
|
||||
- `A`: move backward by the selected step.
|
||||
- `D`: move forward by the selected step.
|
||||
- During measurement, `A`/`D` only navigate and do not create measurements for skipped frames.
|
||||
- During measurement, `Right` skips the current frame and records angle `0`; if M2 is selected, it records the measurement and advances one frame.
|
||||
|
||||
## Change Guidelines
|
||||
|
||||
- Keep GUI text in Chinese to match the existing application.
|
||||
- Run `client/bin/python -m py_compile main.py actuator/*.py` after Python changes.
|
||||
- Do not commit the local `client/` environment; it is ignored by Git.
|
||||
46
main.py
46
main.py
|
|
@ -24,7 +24,7 @@ from PyQt6.QtWidgets import (
|
|||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QLabel, QPushButton, QSlider, QFileDialog, QGroupBox,
|
||||
QDoubleSpinBox, QFormLayout, QMessageBox, QSplitter, QCheckBox,
|
||||
QSizePolicy,
|
||||
QSizePolicy, QSpinBox,
|
||||
)
|
||||
|
||||
from actuator import VideoReader, signed_angle, curvature_from_three_points
|
||||
|
|
@ -55,6 +55,7 @@ class FrameView(QLabel):
|
|||
self.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.setStyleSheet("background: #1a1a1a; border: 1px solid #333;")
|
||||
self.setMouseTracking(True)
|
||||
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
||||
self.setAcceptDrops(True)
|
||||
|
||||
self._frame: Optional[np.ndarray] = None
|
||||
|
|
@ -375,10 +376,19 @@ class MainWindow(QMainWindow):
|
|||
self.slider.setMaximum(0)
|
||||
self.slider.valueChanged.connect(self._on_slider)
|
||||
nl.addWidget(self.slider)
|
||||
step_row = QHBoxLayout()
|
||||
step_row.addWidget(QLabel("跳帧数:"))
|
||||
self.spin_frame_step = QSpinBox()
|
||||
self.spin_frame_step.setRange(1, 10000)
|
||||
self.spin_frame_step.setValue(1)
|
||||
self.spin_frame_step.setSuffix(" 帧")
|
||||
self.spin_frame_step.setToolTip("A/D 快捷键和导航按钮的跳转步长")
|
||||
step_row.addWidget(self.spin_frame_step)
|
||||
nl.addLayout(step_row)
|
||||
bn = QHBoxLayout()
|
||||
self.btn_prev = QPushButton("◀")
|
||||
self.btn_prev = QPushButton("A ◀")
|
||||
self.btn_prev.clicked.connect(self._prev_frame)
|
||||
self.btn_next = QPushButton("▶")
|
||||
self.btn_next = QPushButton("D ▶")
|
||||
self.btn_next.clicked.connect(self._next_frame)
|
||||
bn.addWidget(self.btn_prev)
|
||||
bn.addWidget(self.btn_next)
|
||||
|
|
@ -486,13 +496,23 @@ class MainWindow(QMainWindow):
|
|||
self._show_frame(value)
|
||||
|
||||
def _prev_frame(self) -> None:
|
||||
if self._video and not self._measuring:
|
||||
self._show_frame(max(0, self._current_frame_idx - 1))
|
||||
self._jump_frames(-self.spin_frame_step.value())
|
||||
|
||||
def _next_frame(self) -> None:
|
||||
if self._video and not self._measuring:
|
||||
self._show_frame(min(self._video.frame_count - 1,
|
||||
self._current_frame_idx + 1))
|
||||
self._jump_frames(self.spin_frame_step.value())
|
||||
|
||||
def _jump_frames(self, offset: int) -> None:
|
||||
"""按偏移量跳转,测量时不为未测帧创建记录。"""
|
||||
if self._video is None:
|
||||
return
|
||||
target = max(0, min(self._video.frame_count - 1,
|
||||
self._current_frame_idx + offset))
|
||||
if target == self._current_frame_idx:
|
||||
return
|
||||
if self._measuring:
|
||||
self._go_to_frame(target)
|
||||
else:
|
||||
self._show_frame(target)
|
||||
|
||||
# ══════════════ 测量模式 ═════════════════════════════
|
||||
|
||||
|
|
@ -527,6 +547,12 @@ class MainWindow(QMainWindow):
|
|||
self._update_display()
|
||||
|
||||
def _on_key_pressed(self, key: int) -> None:
|
||||
if key == Qt.Key.Key_A:
|
||||
self._prev_frame()
|
||||
return
|
||||
if key == Qt.Key.Key_D:
|
||||
self._next_frame()
|
||||
return
|
||||
if not self._measuring:
|
||||
return
|
||||
if key == Qt.Key.Key_Right:
|
||||
|
|
@ -535,9 +561,7 @@ class MainWindow(QMainWindow):
|
|||
else:
|
||||
self._record_skip()
|
||||
elif key == Qt.Key.Key_Left:
|
||||
if self._current_frame_idx > 0:
|
||||
self._current_frame_idx -= 1
|
||||
self._show_frame(self._current_frame_idx)
|
||||
self._jump_frames(-1)
|
||||
|
||||
def _record_skip(self) -> None:
|
||||
"""跳过当前帧,记录 0"""
|
||||
|
|
|
|||
Loading…
Reference in New Issue