From 044fe021079ee2e2a103660433f97870c733a7d0 Mon Sep 17 00:00:00 2001 From: zhangyijian Date: Sun, 26 Jul 2026 18:14:47 +0800 Subject: [PATCH] update dd06 display --- config/dd06-ov.json | 6 +- examples/gui/preference.json | 2 +- examples/gui/viewer2d/__init__.py | 6 ++ examples/gui/viewer2d/backend.py | 92 ++++++++++++++++++++++++++++++- examples/gui/viewer2d/styles.py | 34 ++++++++++++ examples/gui/viewer2d/window.py | 36 +++++++++++- 6 files changed, 169 insertions(+), 7 deletions(-) diff --git a/config/dd06-ov.json b/config/dd06-ov.json index 34e5974..4c966cd 100644 --- a/config/dd06-ov.json +++ b/config/dd06-ov.json @@ -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, diff --git a/examples/gui/preference.json b/examples/gui/preference.json index ef87142..3599411 100644 --- a/examples/gui/preference.json +++ b/examples/gui/preference.json @@ -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", diff --git a/examples/gui/viewer2d/__init__.py b/examples/gui/viewer2d/__init__.py index 5673c59..a55b3cf 100644 --- a/examples/gui/viewer2d/__init__.py +++ b/examples/gui/viewer2d/__init__.py @@ -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", diff --git a/examples/gui/viewer2d/backend.py b/examples/gui/viewer2d/backend.py index 8fc92f2..06d92c1 100644 --- a/examples/gui/viewer2d/backend.py +++ b/examples/gui/viewer2d/backend.py @@ -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)) diff --git a/examples/gui/viewer2d/styles.py b/examples/gui/viewer2d/styles.py index c1bded0..bc44ec4 100644 --- a/examples/gui/viewer2d/styles.py +++ b/examples/gui/viewer2d/styles.py @@ -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; diff --git a/examples/gui/viewer2d/window.py b/examples/gui/viewer2d/window.py index 9bec853..e67ed05 100644 --- a/examples/gui/viewer2d/window.py +++ b/examples/gui/viewer2d/window.py @@ -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: -- 2.52.0