added ruff code reivew

This commit is contained in:
ykcheungab
2026-09-27 19:21:55 +08:00
parent 07da372204
commit e2ac36269c
110 changed files with 890 additions and 426 deletions
+10 -9
View File
@@ -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",
]
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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",
]
+14 -15
View File
@@ -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
+17 -16
View File
@@ -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()
+6 -6
View File
@@ -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",
]
+25 -4
View File
@@ -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:
+7 -10
View File
@@ -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,
+3 -2
View File
@@ -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"]
+1 -1
View File
@@ -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 -3
View File
@@ -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