Dev #19

Merged
zhangyijian merged 53 commits from dev into main 2026-09-02 21:39:04 +08:00
6 changed files with 169 additions and 7 deletions
Showing only changes of commit 044fe02107 - Show all commits
+3 -3
View File
@@ -9,8 +9,8 @@
],
"fps": 30,
"resolution_stitch": [
390,
550
195,
275
],
"source_scale": 0.4,
"old": [
@@ -55,7 +55,7 @@
"raft",
"dis"
],
"hhd_downsample": 1
"hhd_downsample": 3
},
"contact": {
"min_contour_area": 3000,
+1 -1
View File
@@ -1,5 +1,5 @@
{
"video_source": "D:/repo/orisys-sdk/data/dd02-0703/1.mp4",
"video_source": "D:/repo/orisys-sdk/data/dd02/0607_1.mp4",
"config_path": "./config/dd02-ov.json",
"finger_assets_dir": "D:/repo/orisys-sdk/assets/finger/30fps-0703",
"csv_save_dir": "./outputs/csv",
+6
View File
@@ -1,10 +1,13 @@
"""2D Qt sensor monitor (image + force curves; no finger 3D mesh)."""
from .backend import (
PURE_COLOR_BG_BGR,
ArrowBackground,
ForceHistory,
ForcePlotState,
QtViewerBackend,
ViewerFrame,
build_display_image,
image_shape_meta,
panel_width_for_aspect,
select_display_image,
@@ -12,11 +15,14 @@ from .backend import (
from .window import QtViewerWindow, run_viewer
__all__ = [
"PURE_COLOR_BG_BGR",
"ArrowBackground",
"ForceHistory",
"ForcePlotState",
"QtViewerBackend",
"QtViewerWindow",
"ViewerFrame",
"build_display_image",
"image_shape_meta",
"panel_width_for_aspect",
"run_viewer",
+90 -2
View File
@@ -8,10 +8,11 @@ Does not import camera, ROI, or `orisys.Sensor`.
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional, Sequence, Tuple
from typing import Literal, Optional, Sequence, Tuple
import cv2
import numpy as np
import orisys
from scipy.signal import butter, filtfilt
FORCE_SAMPLE_RATE_HZ = 30.0
@@ -19,6 +20,17 @@ PLOT_BUFFER_POINTS = 120
PLOT_WINDOW_SEC = PLOT_BUFFER_POINTS / FORCE_SAMPLE_RATE_HZ
Y_AXIS_MIN_SPAN = 20000.0
ArrowBackground = Literal["pure_color", "captured"]
# #0B1016 as BGR (matches demo_wuxi / promote_video theme)
PURE_COLOR_BG_BGR = (22, 16, 11)
_ARROW_DRAW_KW = dict(
threshold=2,
grid_spacing=20,
arrow_scale=0.5,
below_threshold_color=(160, 160, 160),
)
@dataclass
class ViewerFrame:
@@ -92,6 +104,14 @@ def image_shape_meta(img: np.ndarray) -> Tuple[int, int, float]:
return h, w, aspect
def _base_bgr(snapshot) -> Optional[np.ndarray]:
for attr in ("image", "processed_image", "raw_image"):
out = ensure_bgr(getattr(snapshot, attr, None))
if out is not None:
return out
return None
def select_display_image(snapshot) -> np.ndarray:
"""Pick flow overlay / processed / raw image for the left panel."""
arrows = getattr(snapshot, "flow_overlay", None)
@@ -109,6 +129,29 @@ def select_display_image(snapshot) -> np.ndarray:
return np.zeros((400, 400, 3), dtype=np.uint8)
def build_display_image(
snapshot,
background: ArrowBackground = "pure_color",
) -> np.ndarray:
"""Build left-panel image: arrows on pure color or on the captured frame."""
flow = getattr(snapshot, "flow", None)
base = _base_bgr(snapshot)
if flow is not None and base is not None:
if background == "pure_color":
h, w = base.shape[:2]
canvas = np.empty((h, w, 3), dtype=np.uint8)
canvas[:, :] = PURE_COLOR_BG_BGR
return orisys.util.draw_arrows(canvas, flow, **_ARROW_DRAW_KW)
overlay = ensure_bgr(getattr(snapshot, "flow_overlay", None))
if overlay is not None:
return overlay
return orisys.util.draw_arrows(base, flow, **_ARROW_DRAW_KW)
return select_display_image(snapshot)
def bgr_to_rgb_bytes(img_bgr: np.ndarray) -> Tuple[np.ndarray, int, int]:
"""Convert BGR uint8 image to contiguous RGB for QImage."""
rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
@@ -255,6 +298,7 @@ class QtViewerBackend:
npoints: int = PLOT_BUFFER_POINTS,
lowpass_cutoff_hz: float = 10.0,
y_axis_min_span: float = Y_AXIS_MIN_SPAN,
arrow_background: ArrowBackground = "pure_color",
):
self.force_history = ForceHistory(
npoints=npoints,
@@ -263,6 +307,50 @@ class QtViewerBackend:
)
self._start_time: Optional[float] = None
self.last_viewer_frame: Optional[ViewerFrame] = None
self.arrow_background: ArrowBackground = arrow_background
def set_arrow_background(self, background: ArrowBackground) -> None:
self.arrow_background = background
def rebuild_display(self, snapshot) -> ViewerFrame:
"""Re-render display image without advancing force history."""
display = build_display_image(snapshot, self.arrow_background)
h, w, aspect = image_shape_meta(display)
prev = self.last_viewer_frame
if prev is not None:
frame = ViewerFrame(
display_image=display,
image_height=h,
image_width=w,
aspect_ratio=aspect,
depth=prev.depth,
normal_force=prev.normal_force,
shear_x=prev.shear_x,
shear_y=prev.shear_y,
normal_force_calibrated=prev.normal_force_calibrated,
fps=prev.fps,
timestamp=prev.timestamp,
record_frame=prev.record_frame,
)
else:
frame = ViewerFrame(
display_image=display,
image_height=h,
image_width=w,
aspect_ratio=aspect,
depth=getattr(snapshot, "depth", None),
normal_force=float(getattr(snapshot, "normal_force", 0.0)),
shear_x=float(getattr(snapshot, "shear_x", 0.0)),
shear_y=float(getattr(snapshot, "shear_y", 0.0)),
normal_force_calibrated=float(
getattr(snapshot, "normal_force_calibrated", 0.0)
),
fps=float(getattr(snapshot, "fps", 0.0)),
timestamp=float(getattr(snapshot, "timestamp", 0.0) or 0.0),
record_frame=ensure_bgr(getattr(snapshot, "record_frame", None)),
)
self.last_viewer_frame = frame
return frame
def reset(self) -> None:
self.force_history.reset()
@@ -271,7 +359,7 @@ class QtViewerBackend:
def ingest(self, snapshot, *, now: Optional[float] = None) -> ViewerFrame:
"""Convert a FrameSnapshot into ViewerFrame and update force history."""
display = select_display_image(snapshot)
display = build_display_image(snapshot, self.arrow_background)
h, w, aspect = image_shape_meta(display)
fn = float(getattr(snapshot, "normal_force", 0.0))
+34
View File
@@ -66,6 +66,40 @@ QLineEdit {
font-size: 13px;
}
QLabel#toolbarCaption {
color: #777777;
font-family: "SimHei";
font-size: 13px;
padding-left: 4px;
}
QComboBox {
background-color: #0d0d0d;
color: #999999;
border: 1px solid #333333;
border-radius: 4px;
padding: 5px 10px;
font-family: "SimHei";
font-size: 13px;
min-width: 96px;
}
QComboBox:hover {
background-color: #1a1a1a;
color: #cccccc;
border: 1px solid #555555;
}
QComboBox::drop-down {
border: none;
width: 20px;
}
QComboBox QAbstractItemView {
background-color: #0d0d0d;
color: #999999;
border: 1px solid #333333;
selection-background-color: #1a3a4a;
selection-color: #cccccc;
}
/* ---- 力曲线容器 ---- */
GraphicsLayoutWidget {
border: 1px solid #262626;
+35 -1
View File
@@ -13,6 +13,7 @@ from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QFont, QIcon
from PyQt5.QtWidgets import (
QApplication,
QComboBox,
QFileDialog,
QHBoxLayout,
QLabel,
@@ -35,7 +36,12 @@ from gui.sensors import create_sensor_adapter
from gui.widgets.log_panel import TeeStream, WidgetLogHandler, make_log_widget
from gui.widgets.qt_pixmap import ndarray_to_pixmap, pixmap_keep_aspect
from .backend import PLOT_BUFFER_POINTS, PLOT_WINDOW_SEC, QtViewerBackend, panel_width_for_aspect
from .backend import (
PLOT_BUFFER_POINTS,
PLOT_WINDOW_SEC,
QtViewerBackend,
panel_width_for_aspect,
)
from .styles import CURVE_THEME_COLOR, STYLE
@@ -127,6 +133,17 @@ class QtViewerWindow(QMainWindow):
self.btn_stop.clicked.connect(self._on_stop)
bar.addWidget(self.btn_stop)
bg_caption = QLabel("背景")
bg_caption.setObjectName("toolbarCaption")
bar.addWidget(bg_caption)
self.bg_combo = QComboBox()
self.bg_combo.setObjectName("bgCombo")
self.bg_combo.addItem("纯色背景", "pure_color")
self.bg_combo.addItem("采集图像", "captured")
self.bg_combo.setCurrentIndex(0)
self.bg_combo.currentIndexChanged.connect(self._on_arrow_background_changed)
bar.addWidget(self.bg_combo)
root_layout.addLayout(bar)
v_splitter = QSplitter(Qt.Vertical)
@@ -391,6 +408,23 @@ class QtViewerWindow(QMainWindow):
self.sensor_adapter.reset()
print("追踪器已重置。")
def _on_arrow_background_changed(self, _index: int) -> None:
value = self.bg_combo.currentData()
if value not in ("pure_color", "captured"):
return
self.viewer_backend.set_arrow_background(value)
if self._last_snapshot is None:
return
viewer_frame = self.viewer_backend.rebuild_display(self._last_snapshot)
self._last_viewer_frame = viewer_frame
aspect_changed = abs(viewer_frame.aspect_ratio - self._display_aspect) > 1e-3
self._display_aspect = viewer_frame.aspect_ratio
self._source_pixmap = ndarray_to_pixmap(viewer_frame.display_image, bgr=True)
if aspect_changed:
self.adjust_image_panel()
else:
self._render_display_pixmap()
def keyPressEvent(self, event):
key = event.key()
if key == Qt.Key_Escape: