added ruff code reivew
This commit is contained in:
@@ -9,12 +9,16 @@ ORY-BOX:单传感器基础流程
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
|
||||
import cv2
|
||||
import orisys
|
||||
import numpy as np
|
||||
import argparse
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
import orisys
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--video","-v", type=str, default="0", help="视频源:摄像头编号或视频文件路径")
|
||||
|
||||
@@ -18,6 +18,7 @@ import sys
|
||||
os.environ.setdefault("OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS", "0")
|
||||
|
||||
import cv2
|
||||
|
||||
import orisys
|
||||
from orisys.finger import FingerRuntime
|
||||
|
||||
|
||||
@@ -9,9 +9,10 @@ Acquisition adapters (no widgets) live in ``gui.sensors``.
|
||||
Shared bootstrap: ``gui.app``, ``gui.app_paths``, ``gui.log_config``, ``gui.widgets``.
|
||||
"""
|
||||
|
||||
from viz.contact_arrow import ContactArrowStyle
|
||||
|
||||
from .log_config import LOG_CONFIG, LogConfig
|
||||
from .viewer3d import (
|
||||
FingerDashboardWindow,
|
||||
LAYOUT,
|
||||
LOG_PANEL,
|
||||
MESH_CONTACT_ARROW,
|
||||
@@ -19,33 +20,33 @@ from .viewer3d import (
|
||||
PANEL_SETUP,
|
||||
TOP_ROW,
|
||||
DashboardLayout,
|
||||
FingerDashboardWindow,
|
||||
MeshLighting,
|
||||
PanelRow,
|
||||
PanelSetup,
|
||||
PanelSlot,
|
||||
run_dashboard,
|
||||
top_row_splitter_sizes,
|
||||
top_row_slot_index,
|
||||
top_row_splitter_sizes,
|
||||
)
|
||||
from viz.contact_arrow import ContactArrowStyle
|
||||
|
||||
__all__ = [
|
||||
"DashboardLayout",
|
||||
"FingerDashboardWindow",
|
||||
"LAYOUT",
|
||||
"LOG_CONFIG",
|
||||
"LOG_PANEL",
|
||||
"LogConfig",
|
||||
"MESH_CONTACT_ARROW",
|
||||
"MESH_LIGHTING",
|
||||
"ContactArrowStyle",
|
||||
"MeshLighting",
|
||||
"PANEL_SETUP",
|
||||
"TOP_ROW",
|
||||
"ContactArrowStyle",
|
||||
"DashboardLayout",
|
||||
"FingerDashboardWindow",
|
||||
"LogConfig",
|
||||
"MeshLighting",
|
||||
"PanelRow",
|
||||
"PanelSetup",
|
||||
"PanelSlot",
|
||||
"run_dashboard",
|
||||
"top_row_splitter_sizes",
|
||||
"top_row_slot_index",
|
||||
"top_row_splitter_sizes",
|
||||
]
|
||||
|
||||
@@ -34,8 +34,8 @@ def default_config_path() -> str:
|
||||
def require_gui_extras() -> None:
|
||||
try:
|
||||
import open3d # noqa: F401
|
||||
from PyQt5 import QtWidgets # noqa: F401
|
||||
import pyqtgraph # noqa: F401
|
||||
from PyQt5 import QtWidgets # noqa: F401
|
||||
except ImportError as exc:
|
||||
raise SystemExit('GUI example requires: pip install "orisys[gui]"') from exc
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
"""Sensor acquisition adapters for Qt / multi-sensor viewers (no Qt widgets)."""
|
||||
|
||||
from . import roi_geometry
|
||||
from .adapter import (
|
||||
Dd06RoiAdapter,
|
||||
FrameSnapshot,
|
||||
OrisysSensorAdapter,
|
||||
SensorAdapter,
|
||||
_snapshot_from_sensor,
|
||||
calibrate_normal_force,
|
||||
create_sensor_adapter,
|
||||
_snapshot_from_sensor,
|
||||
)
|
||||
from . import roi_geometry
|
||||
|
||||
__all__ = [
|
||||
"Dd06RoiAdapter",
|
||||
"FrameSnapshot",
|
||||
"OrisysSensorAdapter",
|
||||
"SensorAdapter",
|
||||
"_snapshot_from_sensor",
|
||||
"calibrate_normal_force",
|
||||
"create_sensor_adapter",
|
||||
"roi_geometry",
|
||||
"_snapshot_from_sensor",
|
||||
]
|
||||
|
||||
@@ -5,11 +5,11 @@ from __future__ import annotations
|
||||
import time
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
import orisys
|
||||
|
||||
from .roi_geometry import (
|
||||
@@ -21,7 +21,6 @@ from .roi_geometry import (
|
||||
warp_roi_to_rectangle,
|
||||
)
|
||||
|
||||
|
||||
NORMAL_FORCE_CAL_SLOPE = 3.92685554668795e-05
|
||||
NORMAL_FORCE_CAL_OFFSET = 0.0217596967278535
|
||||
|
||||
@@ -30,10 +29,10 @@ NORMAL_FORCE_CAL_OFFSET = 0.0217596967278535
|
||||
class FrameSnapshot:
|
||||
"""Normalized per-frame payload consumed by the Qt viewer."""
|
||||
|
||||
image: Optional[np.ndarray] = None
|
||||
flow: Optional[np.ndarray] = None
|
||||
depth: Optional[np.ndarray] = None
|
||||
contour: Optional[np.ndarray] = None
|
||||
image: np.ndarray | None = None
|
||||
flow: np.ndarray | None = None
|
||||
depth: np.ndarray | None = None
|
||||
contour: np.ndarray | None = None
|
||||
centroid: Any = None
|
||||
# Raw SDK force channels used for curves (same units for FN/FX/FY).
|
||||
normal_force: float = 0.0
|
||||
@@ -43,10 +42,10 @@ class FrameSnapshot:
|
||||
normal_force_calibrated: float = 0.0
|
||||
fps: float = 0.0
|
||||
timestamp: float = 0.0
|
||||
raw_image: Optional[np.ndarray] = None
|
||||
processed_image: Optional[np.ndarray] = None
|
||||
flow_overlay: Optional[np.ndarray] = None
|
||||
record_frame: Optional[np.ndarray] = None
|
||||
raw_image: np.ndarray | None = None
|
||||
processed_image: np.ndarray | None = None
|
||||
flow_overlay: np.ndarray | None = None
|
||||
record_frame: np.ndarray | None = None
|
||||
|
||||
|
||||
def calibrate_normal_force(raw_value: float) -> float:
|
||||
@@ -62,7 +61,7 @@ class SensorAdapter(ABC):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def read(self) -> Optional[FrameSnapshot]:
|
||||
def read(self) -> FrameSnapshot | None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
@@ -89,7 +88,7 @@ def _snapshot_from_sensor(
|
||||
*,
|
||||
raw_image=None,
|
||||
processed_image=None,
|
||||
) -> Optional[FrameSnapshot]:
|
||||
) -> FrameSnapshot | None:
|
||||
"""Build a FrameSnapshot with HHD/forces refreshed every frame."""
|
||||
if sensor is None or sensor.img is None:
|
||||
return None
|
||||
@@ -186,7 +185,7 @@ class OrisysSensorAdapter(SensorAdapter):
|
||||
verbose=self.verbose,
|
||||
)
|
||||
|
||||
def read(self) -> Optional[FrameSnapshot]:
|
||||
def read(self) -> FrameSnapshot | None:
|
||||
if self.sensor is None:
|
||||
return None
|
||||
img = self.sensor.get_img()
|
||||
@@ -255,7 +254,7 @@ class Dd06RoiAdapter(SensorAdapter):
|
||||
f"video={self._source}"
|
||||
)
|
||||
|
||||
def read(self) -> Optional[FrameSnapshot]:
|
||||
def read(self) -> FrameSnapshot | None:
|
||||
if self.sensor is None or self.cap is None:
|
||||
return None
|
||||
|
||||
|
||||
@@ -8,13 +8,14 @@ Does not import camera, ROI, or `orisys.Sensor`.
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal, Optional, Sequence, Tuple
|
||||
from typing import Literal
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import orisys
|
||||
from scipy.signal import butter, filtfilt
|
||||
|
||||
import orisys
|
||||
|
||||
FORCE_SAMPLE_RATE_HZ = 30.0
|
||||
PLOT_BUFFER_POINTS = 120
|
||||
PLOT_WINDOW_SEC = PLOT_BUFFER_POINTS / FORCE_SAMPLE_RATE_HZ
|
||||
@@ -40,14 +41,14 @@ class ViewerFrame:
|
||||
image_height: int
|
||||
image_width: int
|
||||
aspect_ratio: float # width / height
|
||||
depth: Optional[np.ndarray]
|
||||
depth: np.ndarray | None
|
||||
normal_force: float
|
||||
shear_x: float
|
||||
shear_y: float
|
||||
normal_force_calibrated: float
|
||||
fps: float
|
||||
timestamp: float
|
||||
record_frame: Optional[np.ndarray]
|
||||
record_frame: np.ndarray | None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -57,8 +58,8 @@ class ForcePlotState:
|
||||
times: np.ndarray # shape (n,)
|
||||
values: np.ndarray # shape (n, 3) FN, FX, FY
|
||||
sample_count: int
|
||||
x_range: Tuple[float, float]
|
||||
y_ranges: Tuple[Tuple[float, float], Tuple[float, float], Tuple[float, float]]
|
||||
x_range: tuple[float, float]
|
||||
y_ranges: tuple[tuple[float, float], tuple[float, float], tuple[float, float]]
|
||||
|
||||
|
||||
def lowpass_filter(
|
||||
@@ -87,7 +88,7 @@ def lowpass_filter(
|
||||
return filtfilt(b, a, y)
|
||||
|
||||
|
||||
def ensure_bgr(img: Optional[np.ndarray]) -> Optional[np.ndarray]:
|
||||
def ensure_bgr(img: np.ndarray | None) -> np.ndarray | None:
|
||||
if img is None:
|
||||
return None
|
||||
if img.ndim == 2:
|
||||
@@ -97,14 +98,14 @@ def ensure_bgr(img: Optional[np.ndarray]) -> Optional[np.ndarray]:
|
||||
return img
|
||||
|
||||
|
||||
def image_shape_meta(img: np.ndarray) -> Tuple[int, int, float]:
|
||||
def image_shape_meta(img: np.ndarray) -> tuple[int, int, float]:
|
||||
"""Return (height, width, width/height) from a display image."""
|
||||
h, w = int(img.shape[0]), int(img.shape[1])
|
||||
aspect = float(w) / float(h) if h > 0 else 1.0
|
||||
return h, w, aspect
|
||||
|
||||
|
||||
def _base_bgr(snapshot) -> Optional[np.ndarray]:
|
||||
def _base_bgr(snapshot) -> np.ndarray | None:
|
||||
for attr in ("image", "processed_image", "raw_image"):
|
||||
out = ensure_bgr(getattr(snapshot, attr, None))
|
||||
if out is not None:
|
||||
@@ -152,7 +153,7 @@ def build_display_image(
|
||||
return select_display_image(snapshot)
|
||||
|
||||
|
||||
def bgr_to_rgb_bytes(img_bgr: np.ndarray) -> Tuple[np.ndarray, int, int]:
|
||||
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)
|
||||
if not rgb.flags["C_CONTIGUOUS"]:
|
||||
@@ -231,7 +232,7 @@ class ForceHistory:
|
||||
self.data[-1, 0, :] = t
|
||||
self.data[-1, 1, :] = values
|
||||
|
||||
def _smooth_y_range(self, channel: int, ymin: float, ymax: float) -> Tuple[float, float]:
|
||||
def _smooth_y_range(self, channel: int, ymin: float, ymax: float) -> tuple[float, float]:
|
||||
if self._y_range_smooth[channel] is None:
|
||||
smoothed = (ymin, ymax)
|
||||
else:
|
||||
@@ -251,7 +252,7 @@ class ForceHistory:
|
||||
pad = max((smoothed[1] - smoothed[0]) * 0.12, 0.01)
|
||||
return smoothed[0] - pad, smoothed[1] + pad
|
||||
|
||||
def plot_state(self) -> Optional[ForcePlotState]:
|
||||
def plot_state(self) -> ForcePlotState | None:
|
||||
n = self.sample_count
|
||||
if n == 0:
|
||||
return None
|
||||
@@ -305,8 +306,8 @@ class QtViewerBackend:
|
||||
lowpass_cutoff_hz=lowpass_cutoff_hz,
|
||||
y_axis_min_span=y_axis_min_span,
|
||||
)
|
||||
self._start_time: Optional[float] = None
|
||||
self.last_viewer_frame: Optional[ViewerFrame] = None
|
||||
self._start_time: float | None = None
|
||||
self.last_viewer_frame: ViewerFrame | None = None
|
||||
self.arrow_background: ArrowBackground = arrow_background
|
||||
|
||||
def set_arrow_background(self, background: ArrowBackground) -> None:
|
||||
@@ -357,7 +358,7 @@ class QtViewerBackend:
|
||||
self._start_time = None
|
||||
self.last_viewer_frame = None
|
||||
|
||||
def ingest(self, snapshot, *, now: Optional[float] = None) -> ViewerFrame:
|
||||
def ingest(self, snapshot, *, now: float | None = None) -> ViewerFrame:
|
||||
"""Convert a FrameSnapshot into ViewerFrame and update force history."""
|
||||
display = build_display_image(snapshot, self.arrow_background)
|
||||
h, w, aspect = image_shape_meta(display)
|
||||
@@ -396,5 +397,5 @@ class QtViewerBackend:
|
||||
self.last_viewer_frame = frame
|
||||
return frame
|
||||
|
||||
def plot_state(self) -> Optional[ForcePlotState]:
|
||||
def plot_state(self) -> ForcePlotState | None:
|
||||
return self.force_history.plot_state()
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
from .dashboard import FingerDashboardWindow, run_dashboard
|
||||
from .layout import (
|
||||
LOG_PANEL,
|
||||
LAYOUT,
|
||||
LOG_PANEL,
|
||||
MESH_CONTACT_ARROW,
|
||||
MESH_LIGHTING,
|
||||
PANEL_SETUP,
|
||||
@@ -13,24 +13,24 @@ from .layout import (
|
||||
PanelRow,
|
||||
PanelSetup,
|
||||
PanelSlot,
|
||||
top_row_splitter_sizes,
|
||||
top_row_slot_index,
|
||||
top_row_splitter_sizes,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"DashboardLayout",
|
||||
"FingerDashboardWindow",
|
||||
"LAYOUT",
|
||||
"LOG_PANEL",
|
||||
"MESH_CONTACT_ARROW",
|
||||
"MESH_LIGHTING",
|
||||
"MeshLighting",
|
||||
"PANEL_SETUP",
|
||||
"TOP_ROW",
|
||||
"DashboardLayout",
|
||||
"FingerDashboardWindow",
|
||||
"MeshLighting",
|
||||
"PanelRow",
|
||||
"PanelSetup",
|
||||
"PanelSlot",
|
||||
"run_dashboard",
|
||||
"top_row_splitter_sizes",
|
||||
"top_row_slot_index",
|
||||
"top_row_splitter_sizes",
|
||||
]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
@@ -11,6 +12,8 @@ import cv2
|
||||
|
||||
DEFAULT_MAX_CAMERA_INDEX = 10
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CameraInfo:
|
||||
@@ -62,8 +65,14 @@ def _suppress_opencv_noise():
|
||||
if set_level is not None and prev_level is not None:
|
||||
try:
|
||||
set_level(prev_level)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
# Best-effort: a failure only leaves OpenCV probe noise suppressed,
|
||||
# but report it so a stuck log level is diagnosable.
|
||||
logger.warning(
|
||||
"camera discovery: could not restore OpenCV log level %r: %s",
|
||||
prev_level,
|
||||
exc,
|
||||
)
|
||||
|
||||
|
||||
def _backend_candidates() -> list[tuple[str, int]]:
|
||||
@@ -115,9 +124,21 @@ def list_cameras(max_index: int = DEFAULT_MAX_CAMERA_INDEX) -> list[CameraInfo]:
|
||||
backends = _backend_candidates()
|
||||
|
||||
with _suppress_opencv_noise():
|
||||
for index in range(0, max_index + 1):
|
||||
for index in range(max_index + 1):
|
||||
for backend_name, backend_api in backends:
|
||||
info = _try_open(index, backend_name, backend_api)
|
||||
try:
|
||||
info = _try_open(index, backend_name, backend_api)
|
||||
except Exception as exc:
|
||||
# Same hazard as the SDK enumerator: an escaping error for one
|
||||
# backend would abort the whole probe and hide every other
|
||||
# camera. Skip that candidate and keep going.
|
||||
logger.warning(
|
||||
"camera discovery: probing index %d via %s failed: %s",
|
||||
index,
|
||||
backend_name,
|
||||
exc,
|
||||
)
|
||||
continue
|
||||
if info is None:
|
||||
continue
|
||||
if index in seen_indices:
|
||||
|
||||
@@ -6,6 +6,9 @@ import math
|
||||
import sys
|
||||
import time
|
||||
|
||||
from pipeline.config import PipelineConfig
|
||||
from pipeline.csv_recorder import CsvRecorder
|
||||
from pipeline.service import FingerPipeline
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import (
|
||||
@@ -17,23 +20,17 @@ from PyQt5.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from pipeline.config import PipelineConfig
|
||||
from pipeline.csv_recorder import CsvRecorder
|
||||
from pipeline.service import FingerPipeline
|
||||
|
||||
from gui.app_paths import resource_path
|
||||
from gui.widgets import TeeStream, WidgetLogHandler, make_log_widget
|
||||
|
||||
from .camera_discovery import list_cameras, pick_default_camera
|
||||
from .preferences import get_preferred_video_source, load_preferences, save_preferences
|
||||
from .layout import (
|
||||
DashboardLayout,
|
||||
LAYOUT,
|
||||
PANEL_SETUP,
|
||||
PanelSetup,
|
||||
top_row_splitter_sizes,
|
||||
DashboardLayout,
|
||||
)
|
||||
from .mesh_view_state import MeshViewState
|
||||
from .preferences import get_preferred_video_source, load_preferences, save_preferences
|
||||
from .styles import STYLE
|
||||
from gui.widgets import TeeStream, WidgetLogHandler, make_log_widget
|
||||
from .widgets import (
|
||||
ArrowPanelWidget,
|
||||
ForceChartWidget,
|
||||
|
||||
@@ -47,11 +47,12 @@ import math
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
from gui.log_config import LOG_CONFIG, LogConfig
|
||||
from viz.contact_arrow import ContactArrowStyle, DEFAULT_CONTACT_ARROW_STYLE
|
||||
from viz.contact_arrow import ContactArrowStyle
|
||||
from viz.flow_arrow import FlowArrowStyle
|
||||
from viz.render_2d import ArrowBackground, ArrowCanvasColors
|
||||
|
||||
from gui.log_config import LOG_CONFIG, LogConfig
|
||||
|
||||
PanelId = Literal["mesh", "arrows", "charts", "log", "settings"]
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal
|
||||
|
||||
from .layout import DashboardLayout, LAYOUT
|
||||
from .layout import LAYOUT, DashboardLayout
|
||||
|
||||
|
||||
class MeshViewState(QObject):
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import cv2
|
||||
|
||||
from pipeline.frame import FingerFrameView
|
||||
from viz.render_2d import ArrowBackground, ArrowCanvasColors, render_uv_arrows
|
||||
|
||||
from ..layout import DashboardLayout, LAYOUT
|
||||
from .image_panel import ImagePanel
|
||||
from gui.widgets.qt_pixmap import ndarray_to_pixmap
|
||||
|
||||
from ..layout import LAYOUT, DashboardLayout
|
||||
from .image_panel import ImagePanel
|
||||
|
||||
|
||||
class ArrowPanelWidget(ImagePanel):
|
||||
"""UV unrolled flow-arrow panel."""
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
from pipeline.frame import FingerFrameView
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWidgets import QSizePolicy
|
||||
|
||||
from ..layout import DashboardLayout, LAYOUT
|
||||
from ..styles import LOG_FONT_PRIMARY, UI_FONT_PRIMARY
|
||||
from pipeline.frame import FingerFrameView
|
||||
from viz.signal_filters import lowpass_filter
|
||||
|
||||
from ..layout import LAYOUT, DashboardLayout
|
||||
from ..styles import LOG_FONT_PRIMARY, UI_FONT_PRIMARY
|
||||
|
||||
|
||||
def _require_pyqtgraph():
|
||||
try:
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pipeline.frame import FingerFrameView
|
||||
from PyQt5.QtCore import QEvent, Qt
|
||||
from PyQt5.QtGui import QMouseEvent
|
||||
|
||||
from pipeline.frame import FingerFrameView
|
||||
from viz.offscreen_mesh import OffscreenFingerMeshRenderer
|
||||
|
||||
from ..layout import DashboardLayout, LAYOUT
|
||||
from ..mesh_view_state import MeshViewState
|
||||
from ..mesh_render_worker import MeshRenderWorker
|
||||
from gui.widgets.qt_pixmap import ndarray_to_pixmap
|
||||
|
||||
from ..layout import LAYOUT, DashboardLayout
|
||||
from ..mesh_render_worker import MeshRenderWorker
|
||||
from ..mesh_view_state import MeshViewState
|
||||
from .image_panel import ImagePanel
|
||||
|
||||
_VIEW_ROTATE_SENSITIVITY = 0.35
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from PyQt5.QtCore import QSize, Qt, pyqtSignal
|
||||
from PyQt5.QtCore import QSize, pyqtSignal
|
||||
from PyQt5.QtWidgets import (
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
@@ -14,11 +14,11 @@ from PyQt5.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from orisys.finger import DEFAULT_FINGER_EXPORT_DIR
|
||||
from viz.render_2d import ArrowBackground
|
||||
|
||||
from ..layout import DashboardLayout, LAYOUT
|
||||
from orisys.finger import DEFAULT_FINGER_EXPORT_DIR
|
||||
|
||||
from ..layout import LAYOUT, DashboardLayout
|
||||
from ..mesh_view_state import MeshViewState
|
||||
from ..transport_icons import reset_icon, transport_icon
|
||||
|
||||
|
||||
@@ -9,12 +9,16 @@ ORY-MINI:单传感器基础流程
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
|
||||
import cv2
|
||||
import orisys
|
||||
import numpy as np
|
||||
import argparse
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
import orisys
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--video","-v", type=str, default="0", help="视频源:摄像头编号或视频文件路径")
|
||||
@@ -29,11 +33,13 @@ def main():
|
||||
# 输入既可以是摄像头编号,也可以是视频文件路径
|
||||
try:
|
||||
input = int(args.video)
|
||||
is_camera = True
|
||||
sensor = orisys.Sensor(input, config_name=args.config, verbose=args.verbose)
|
||||
except:
|
||||
is_camera = False
|
||||
except ValueError:
|
||||
# Not a camera index: treat the argument as a video-file path. Falling back
|
||||
# only on the conversion (not on Sensor construction) keeps a real
|
||||
# constructor failure visible instead of retrying it as a file path.
|
||||
sensor = orisys.Sensor(args.video, config_name=args.config, verbose=args.verbose)
|
||||
else:
|
||||
sensor = orisys.Sensor(input, config_name=args.config, verbose=args.verbose)
|
||||
|
||||
print("\n按键说明:'q' 退出程序,'r' 重置追踪器。")
|
||||
|
||||
|
||||
@@ -245,7 +245,10 @@ def main():
|
||||
try:
|
||||
with open(path) as fh:
|
||||
rows.append(json.load(fh))
|
||||
except Exception:
|
||||
except Exception: # noqa: S110 - a stream that wrote no result is reported below
|
||||
# A stream that crashed or produced no JSON output is expected here;
|
||||
# the empty-``rows`` check below reports that case explicitly, so this
|
||||
# is not a hidden failure.
|
||||
pass
|
||||
if not rows:
|
||||
print("no stream produced results", file=sys.stderr)
|
||||
|
||||
@@ -4,9 +4,10 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from orisys.finger import DEFAULT_FINGER_EXPORT_DIR
|
||||
from viz.mesh_camera import DEFAULT_FINGER_VIZ_POSE
|
||||
|
||||
from orisys.finger import DEFAULT_FINGER_EXPORT_DIR
|
||||
|
||||
|
||||
def mesh_threshold(mesh_map: str, threshold: float | None) -> float:
|
||||
if threshold is not None:
|
||||
@@ -33,7 +34,7 @@ class PipelineConfig:
|
||||
profile_every: int = 30
|
||||
|
||||
@classmethod
|
||||
def from_args(cls, args) -> "PipelineConfig":
|
||||
def from_args(cls, args) -> PipelineConfig:
|
||||
return cls(
|
||||
video=args.video,
|
||||
config=args.config,
|
||||
|
||||
@@ -7,7 +7,6 @@ from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CSV_COLUMNS = (
|
||||
"frame_index",
|
||||
"timestamp_s",
|
||||
|
||||
@@ -6,10 +6,10 @@ import time
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
from orisys.finger import FingerRuntime, compute_uv_surface_frames
|
||||
|
||||
from viz import FrameRecorder, build_uv_surface_mesh, resolve_viz_pose_path
|
||||
|
||||
from orisys.finger import FingerRuntime, compute_uv_surface_frames
|
||||
|
||||
from .config import PipelineConfig
|
||||
from .csv_recorder import CsvMeasurement
|
||||
from .frame import FingerFrameView
|
||||
|
||||
@@ -16,10 +16,10 @@ os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import orisys
|
||||
|
||||
from viz import ForceChartWindow, FrameRecorder
|
||||
|
||||
import orisys
|
||||
|
||||
|
||||
def open_sensor(video: str, config: str, cal: str, verbose: bool):
|
||||
try:
|
||||
|
||||
@@ -20,14 +20,17 @@
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
|
||||
import cv2
|
||||
import orisys
|
||||
import numpy as np
|
||||
import multiprocessing as mp
|
||||
from multiprocessing import Queue, Process
|
||||
import time
|
||||
import argparse
|
||||
import multiprocessing as mp
|
||||
import queue
|
||||
import time
|
||||
from multiprocessing import Process, Queue
|
||||
|
||||
import cv2
|
||||
|
||||
import orisys
|
||||
|
||||
|
||||
def sensor_worker(sensor_id, vid_src, config_name, result_queue, stop_event, target_fps=None):
|
||||
@@ -55,6 +58,7 @@ def sensor_worker(sensor_id, vid_src, config_name, result_queue, stop_event, tar
|
||||
print(f"传感器 {sensor_id} 已启动" + (f"(目标帧率:{target_fps} FPS)" if target_fps else ""))
|
||||
|
||||
frame_interval = 1.0 / target_fps if target_fps and target_fps > 0 else 0.0
|
||||
dropped_frames = 0
|
||||
|
||||
while not stop_event.is_set():
|
||||
t_start = time.perf_counter()
|
||||
@@ -95,9 +99,10 @@ def sensor_worker(sensor_id, vid_src, config_name, result_queue, stop_event, tar
|
||||
# 步骤 6:将结果发送到主进程(非阻塞)
|
||||
try:
|
||||
result_queue.put_nowait(data)
|
||||
except Exception:
|
||||
# 如果队列已满,则丢弃当前帧,避免阻塞工作进程
|
||||
pass
|
||||
except queue.Full:
|
||||
# 队列已满:丢弃当前帧,避免阻塞工作进程。这是有意的背压策略,
|
||||
# 但丢帧必须可观测,否则主进程看到的低帧率无从解释。
|
||||
dropped_frames += 1
|
||||
|
||||
except Exception as e:
|
||||
print(f"传感器 {sensor_id} 处理失败:{e}")
|
||||
@@ -112,7 +117,13 @@ def sensor_worker(sensor_id, vid_src, config_name, result_queue, stop_event, tar
|
||||
|
||||
# 释放资源
|
||||
sensor.disconnect()
|
||||
print(f"传感器 {sensor_id} 已停止。")
|
||||
if dropped_frames:
|
||||
print(
|
||||
f"传感器 {sensor_id} 已停止(因结果队列已满丢弃 {dropped_frames} 帧,"
|
||||
"主进程处理慢于采集)。"
|
||||
)
|
||||
else:
|
||||
print(f"传感器 {sensor_id} 已停止。")
|
||||
|
||||
except Exception as e:
|
||||
print(f"传感器 {sensor_id} 初始化失败:{e}")
|
||||
@@ -193,7 +204,8 @@ def main():
|
||||
while True:
|
||||
try:
|
||||
data = queue.get_nowait()
|
||||
except:
|
||||
except queue.Empty:
|
||||
# 队列已排空:正常结束内层循环,不是错误。
|
||||
break
|
||||
|
||||
if data is not None:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Shared visualization helpers for Orisys example scripts."""
|
||||
|
||||
from .finger_mesh_viewer import FingerMeshViewer
|
||||
from .flow_arrow import DEFAULT_FLOW_ARROW_STYLE, FlowArrowOverlay, FlowArrowStyle
|
||||
from .force_charts import ForceChartWindow
|
||||
from .mesh_camera import (
|
||||
DEFAULT_FINGER_VIZ_POSE,
|
||||
@@ -8,33 +10,32 @@ from .mesh_camera import (
|
||||
camera_look_at_from_world_to_cam,
|
||||
resolve_viz_pose_path,
|
||||
)
|
||||
from .mesh_viewer import UVMeshViewer, build_uv_surface_mesh
|
||||
from .offscreen_mesh import OffscreenFingerMeshRenderer
|
||||
from .recording import FrameRecorder
|
||||
from .render_2d import (
|
||||
DEFAULT_ARROW_CANVAS_COLORS,
|
||||
ArrowBackground,
|
||||
ArrowCanvasColors,
|
||||
DEFAULT_ARROW_CANVAS_COLORS,
|
||||
depth_colormap,
|
||||
frame_to_bgr,
|
||||
render_contact_panel,
|
||||
render_depth_panel,
|
||||
render_uv_arrows,
|
||||
)
|
||||
from .flow_arrow import DEFAULT_FLOW_ARROW_STYLE, FlowArrowOverlay, FlowArrowStyle
|
||||
from .finger_mesh_viewer import FingerMeshViewer
|
||||
from .mesh_viewer import UVMeshViewer, build_uv_surface_mesh
|
||||
from .recording import FrameRecorder
|
||||
from .signal_filters import lowpass_filter
|
||||
|
||||
__all__ = [
|
||||
"ArrowBackground",
|
||||
"ArrowCanvasColors",
|
||||
"DEFAULT_ARROW_CANVAS_COLORS",
|
||||
"DEFAULT_FLOW_ARROW_STYLE",
|
||||
"ArrowBackground",
|
||||
"ArrowCanvasColors",
|
||||
"FingerMeshViewer",
|
||||
"FlowArrowOverlay",
|
||||
"FlowArrowStyle",
|
||||
"FingerMeshViewer",
|
||||
"ForceChartWindow",
|
||||
"FrameRecorder",
|
||||
"OffscreenFingerMeshRenderer",
|
||||
"UVMeshViewer",
|
||||
"apply_mesh_lighting",
|
||||
"apply_view_from_world_to_cam",
|
||||
@@ -43,7 +44,6 @@ __all__ = [
|
||||
"depth_colormap",
|
||||
"frame_to_bgr",
|
||||
"lowpass_filter",
|
||||
"OffscreenFingerMeshRenderer",
|
||||
"render_contact_panel",
|
||||
"render_depth_panel",
|
||||
"render_uv_arrows",
|
||||
|
||||
@@ -8,7 +8,11 @@ import numpy as np
|
||||
|
||||
from orisys.finger.viewer import UVMeshViewer
|
||||
|
||||
from .contact_arrow import ContactArrowOverlay, ContactArrowStyle, DEFAULT_CONTACT_ARROW_STYLE
|
||||
from .contact_arrow import (
|
||||
DEFAULT_CONTACT_ARROW_STYLE,
|
||||
ContactArrowOverlay,
|
||||
ContactArrowStyle,
|
||||
)
|
||||
from .flow_arrow import DEFAULT_FLOW_ARROW_STYLE, FlowArrowOverlay, FlowArrowStyle
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, Literal
|
||||
from typing import Literal
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
"""Re-exports for finger 3D mesh examples; core viewer lives in orisys.finger."""
|
||||
|
||||
from orisys.finger.mesh import * # noqa: F403
|
||||
from orisys.finger.viewer import UVMeshViewer, init_uv_mesh_viewer, update_uv_mesh_viewer # noqa: F401
|
||||
from orisys.finger.viewer import ( # noqa: F401
|
||||
UVMeshViewer,
|
||||
init_uv_mesh_viewer,
|
||||
update_uv_mesh_viewer,
|
||||
)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Literal
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
import orisys
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
import numpy as np
|
||||
from scipy.signal import butter, filtfilt
|
||||
|
||||
|
||||
def lowpass_filter(data, cutoff_hz=5, sample_rate_hz=30, order=2) -> np.ndarray:
|
||||
"""Zero-phase Butterworth low-pass filter for uniformly sampled 1-D data."""
|
||||
y = np.asarray(data, dtype=np.float64)
|
||||
|
||||
Reference in New Issue
Block a user