update dd06 display

This commit is contained in:
2026-07-26 18:14:47 +08:00
parent b154a3acbb
commit 044fe02107
6 changed files with 169 additions and 7 deletions
+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))