update arrow rotate control
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
"finger_3d": "Finger 3D view",
|
"finger_3d": "Finger 3D view",
|
||||||
"show_fps": "Show FPS",
|
"show_fps": "Show FPS",
|
||||||
"plot_settings": "Plot settings…",
|
"plot_settings": "Plot settings…",
|
||||||
|
"rotate_arrows": "Rotate arrows 90°",
|
||||||
"help": "Help",
|
"help": "Help",
|
||||||
"about": "About"
|
"about": "About"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"finger_3d": "手指 3D 视图",
|
"finger_3d": "手指 3D 视图",
|
||||||
"show_fps": "显示 FPS",
|
"show_fps": "显示 FPS",
|
||||||
"plot_settings": "绘图设置…",
|
"plot_settings": "绘图设置…",
|
||||||
|
"rotate_arrows": "旋转箭头图 90°",
|
||||||
"help": "帮助",
|
"help": "帮助",
|
||||||
"about": "关于"
|
"about": "关于"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,5 +13,6 @@
|
|||||||
"plot_window_sec": 4.0,
|
"plot_window_sec": 4.0,
|
||||||
"plot_fn_max": 20.0,
|
"plot_fn_max": 20.0,
|
||||||
"plot_shear_max": 20.0,
|
"plot_shear_max": 20.0,
|
||||||
"image_view_mode": "arrows_pure"
|
"image_view_mode": "arrows_pure",
|
||||||
|
"arrow_rotation_deg": 90
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"schema_version": 2,
|
"schema_version": 1,
|
||||||
"unit": "N",
|
"unit": "N",
|
||||||
"channels": {
|
"channels": {
|
||||||
"normal": {
|
"normal": {
|
||||||
@@ -8,105 +8,22 @@
|
|||||||
"extrapolate": true,
|
"extrapolate": true,
|
||||||
"clamp_output_min": 0.0,
|
"clamp_output_min": 0.0,
|
||||||
"points": [
|
"points": [
|
||||||
[
|
[1000.0, 0.0],
|
||||||
3167.3289065950753,
|
[10000.0, 0.377],
|
||||||
0.0
|
[30000.0, 1.377],
|
||||||
],
|
[62000.0, 2.377]
|
||||||
[
|
|
||||||
3617.3242262492,
|
|
||||||
1.0
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"shear_x": {
|
"shear": {
|
||||||
"method": "piecewise_linear",
|
"method": "piecewise_linear",
|
||||||
"raw_key": "fshearx",
|
"input": "components",
|
||||||
|
"signed": true,
|
||||||
"extrapolate": true,
|
"extrapolate": true,
|
||||||
"clamp_output_min": 0.0,
|
"clamp_output_min": 0.0,
|
||||||
"points": [
|
"points": [
|
||||||
[
|
[1000.0, 0.0],
|
||||||
1471.613952418736,
|
[36000.0, 1.19],
|
||||||
0.0
|
[68000.0, 2.06]
|
||||||
],
|
|
||||||
[
|
|
||||||
1743.8539805266817,
|
|
||||||
1.0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"signed": true
|
|
||||||
},
|
|
||||||
"shear_y": {
|
|
||||||
"method": "piecewise_linear",
|
|
||||||
"raw_key": "fsheary",
|
|
||||||
"extrapolate": true,
|
|
||||||
"clamp_output_min": 0.0,
|
|
||||||
"points": [
|
|
||||||
[
|
|
||||||
3845.5798720537605,
|
|
||||||
1.0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
3881.793802339737,
|
|
||||||
0.0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"signed": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta": {
|
|
||||||
"date": "2026-09-06T21:54:09+08:00",
|
|
||||||
"sensor_type": "box",
|
|
||||||
"sn": "a",
|
|
||||||
"stitch_resolution": [
|
|
||||||
365,
|
|
||||||
365
|
|
||||||
],
|
|
||||||
"dis_mode": 3,
|
|
||||||
"hhd_downsample": 3,
|
|
||||||
"record_duration_s": 7.0,
|
|
||||||
"config_ref": "D:\\repo\\orisys-app\\apps\\customer\\config\\sensors\\box\\sensor.json",
|
|
||||||
"samples": {
|
|
||||||
"fn": [
|
|
||||||
{
|
|
||||||
"weight_n": 0.0,
|
|
||||||
"raw_mean": 3167.3289065950753,
|
|
||||||
"n": 283,
|
|
||||||
"std": 283.516893458341
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"weight_n": 1.0,
|
|
||||||
"raw_mean": 3617.3242262492,
|
|
||||||
"n": 293,
|
|
||||||
"std": 271.21386400281835
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fx": [
|
|
||||||
{
|
|
||||||
"weight_n": 0.0,
|
|
||||||
"raw_mean": -1471.613952418736,
|
|
||||||
"n": 280,
|
|
||||||
"std": 311.9173147283949
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"weight_n": 1.0,
|
|
||||||
"raw_mean": -1743.8539805266817,
|
|
||||||
"n": 295,
|
|
||||||
"std": 324.2175747527586
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fy": [
|
|
||||||
{
|
|
||||||
"weight_n": 0.0,
|
|
||||||
"raw_mean": 3881.793802339737,
|
|
||||||
"n": 292,
|
|
||||||
"std": 362.16000171157486
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"weight_n": 1.0,
|
|
||||||
"raw_mean": 3845.5798720537605,
|
|
||||||
"n": 295,
|
|
||||||
"std": 390.42421990805127
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ from orisys_customer.ui.toolbar import ToolbarWidget
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
RUNNABLE_TYPES = frozenset({"box", "mini", "finger"})
|
RUNNABLE_TYPES = frozenset({"box", "mini", "finger"})
|
||||||
|
ARROW_ROTATE_TYPES = frozenset({"box", "mini"})
|
||||||
|
|
||||||
|
|
||||||
def _bgr_to_qimage(frame: np.ndarray) -> QImage:
|
def _bgr_to_qimage(frame: np.ndarray) -> QImage:
|
||||||
@@ -196,6 +197,7 @@ class CustomerMainWindow(QMainWindow):
|
|||||||
self.toolbar.set_units(str(self.prefs.get("units") or "newton"))
|
self.toolbar.set_units(str(self.prefs.get("units") or "newton"))
|
||||||
self.arrow_panel.set_view_mode(str(self.prefs.get("image_view_mode") or "arrows_pure"))
|
self.arrow_panel.set_view_mode(str(self.prefs.get("image_view_mode") or "arrows_pure"))
|
||||||
self.arrow_panel.set_fps_visible(self._show_fps)
|
self.arrow_panel.set_fps_visible(self._show_fps)
|
||||||
|
self.arrow_panel.set_rotation_deg(self.prefs.get("arrow_rotation_deg", 90))
|
||||||
self._apply_plot_prefs_to_panel()
|
self._apply_plot_prefs_to_panel()
|
||||||
|
|
||||||
sensor_type = str(self.prefs.get("sensor_type") or "box")
|
sensor_type = str(self.prefs.get("sensor_type") or "box")
|
||||||
@@ -205,6 +207,7 @@ class CustomerMainWindow(QMainWindow):
|
|||||||
self.sensor_picker.set_config_name(config_name)
|
self.sensor_picker.set_config_name(config_name)
|
||||||
else:
|
else:
|
||||||
self.prefs["config_name"] = self.sensor_picker.config_name()
|
self.prefs["config_name"] = self.sensor_picker.config_name()
|
||||||
|
self._apply_arrow_rotation_for_sensor()
|
||||||
|
|
||||||
def _build_menus(self) -> None:
|
def _build_menus(self) -> None:
|
||||||
menu_bar = self.menuBar()
|
menu_bar = self.menuBar()
|
||||||
@@ -239,6 +242,10 @@ class CustomerMainWindow(QMainWindow):
|
|||||||
self.act_plot_settings = QAction(self)
|
self.act_plot_settings = QAction(self)
|
||||||
self.act_plot_settings.triggered.connect(self._on_plot_settings)
|
self.act_plot_settings.triggered.connect(self._on_plot_settings)
|
||||||
self.view_menu.addAction(self.act_plot_settings)
|
self.view_menu.addAction(self.act_plot_settings)
|
||||||
|
|
||||||
|
self.act_rotate_arrows = QAction(self)
|
||||||
|
self.act_rotate_arrows.triggered.connect(self._on_rotate_arrows)
|
||||||
|
self.view_menu.addAction(self.act_rotate_arrows)
|
||||||
self.view_menu.addSeparator()
|
self.view_menu.addSeparator()
|
||||||
|
|
||||||
self.theme_menu = self.view_menu.addMenu("")
|
self.theme_menu = self.view_menu.addMenu("")
|
||||||
@@ -289,6 +296,7 @@ class CustomerMainWindow(QMainWindow):
|
|||||||
self.act_finger_3d.blockSignals(True)
|
self.act_finger_3d.blockSignals(True)
|
||||||
self.act_finger_3d.setChecked(bool(self._show_finger_3d) if is_finger else False)
|
self.act_finger_3d.setChecked(bool(self._show_finger_3d) if is_finger else False)
|
||||||
self.act_finger_3d.blockSignals(False)
|
self.act_finger_3d.blockSignals(False)
|
||||||
|
self._apply_arrow_rotation_for_sensor()
|
||||||
|
|
||||||
def _apply_panel_visibility(self) -> None:
|
def _apply_panel_visibility(self) -> None:
|
||||||
is_finger = self.sensor_picker.sensor_type() == "finger"
|
is_finger = self.sensor_picker.sensor_type() == "finger"
|
||||||
@@ -329,6 +337,7 @@ class CustomerMainWindow(QMainWindow):
|
|||||||
self.act_finger_3d.setText(t("menu.finger_3d"))
|
self.act_finger_3d.setText(t("menu.finger_3d"))
|
||||||
self.act_show_fps.setText(t("menu.show_fps"))
|
self.act_show_fps.setText(t("menu.show_fps"))
|
||||||
self.act_plot_settings.setText(t("menu.plot_settings"))
|
self.act_plot_settings.setText(t("menu.plot_settings"))
|
||||||
|
self.act_rotate_arrows.setText(t("menu.rotate_arrows"))
|
||||||
self.theme_menu.setTitle(t("menu.theme"))
|
self.theme_menu.setTitle(t("menu.theme"))
|
||||||
self.act_theme_dark.setText(t("menu.theme_dark"))
|
self.act_theme_dark.setText(t("menu.theme_dark"))
|
||||||
self.act_theme_light.setText(t("menu.theme_light"))
|
self.act_theme_light.setText(t("menu.theme_light"))
|
||||||
@@ -449,9 +458,22 @@ class CustomerMainWindow(QMainWindow):
|
|||||||
config_ref=self._resolve_config_ref(),
|
config_ref=self._resolve_config_ref(),
|
||||||
)
|
)
|
||||||
self._apply_panel_visibility()
|
self._apply_panel_visibility()
|
||||||
|
self._apply_arrow_rotation_for_sensor()
|
||||||
self._update_run_enabled()
|
self._update_run_enabled()
|
||||||
self._persist_prefs()
|
self._persist_prefs()
|
||||||
|
|
||||||
|
def _apply_arrow_rotation_for_sensor(self) -> None:
|
||||||
|
enabled = self.sensor_picker.sensor_type() in ARROW_ROTATE_TYPES
|
||||||
|
self.arrow_panel.set_rotation_enabled(enabled)
|
||||||
|
self.act_rotate_arrows.setEnabled(enabled)
|
||||||
|
|
||||||
|
def _on_rotate_arrows(self) -> None:
|
||||||
|
if self.sensor_picker.sensor_type() not in ARROW_ROTATE_TYPES:
|
||||||
|
return
|
||||||
|
deg = self.arrow_panel.rotate_90_ccw()
|
||||||
|
self.prefs["arrow_rotation_deg"] = deg
|
||||||
|
self._persist_prefs()
|
||||||
|
|
||||||
def _on_config_changed(self, config_name: str) -> None:
|
def _on_config_changed(self, config_name: str) -> None:
|
||||||
self.prefs["config_name"] = str(config_name)
|
self.prefs["config_name"] = str(config_name)
|
||||||
self._persist_prefs()
|
self._persist_prefs()
|
||||||
@@ -824,6 +846,7 @@ class CustomerMainWindow(QMainWindow):
|
|||||||
self.prefs["video_path"] = self.toolbar.video_path()
|
self.prefs["video_path"] = self.toolbar.video_path()
|
||||||
self.prefs["units"] = self.toolbar.units()
|
self.prefs["units"] = self.toolbar.units()
|
||||||
self.prefs["image_view_mode"] = self.arrow_panel.view_mode()
|
self.prefs["image_view_mode"] = self.arrow_panel.view_mode()
|
||||||
|
self.prefs["arrow_rotation_deg"] = self.arrow_panel.rotation_deg()
|
||||||
self.prefs.setdefault("plot_window_sec", DEFAULT_PLOT_WINDOW_SEC)
|
self.prefs.setdefault("plot_window_sec", DEFAULT_PLOT_WINDOW_SEC)
|
||||||
self.prefs.setdefault("plot_fn_max", DEFAULT_PLOT_FN_MAX)
|
self.prefs.setdefault("plot_fn_max", DEFAULT_PLOT_FN_MAX)
|
||||||
self.prefs.setdefault("plot_shear_max", DEFAULT_PLOT_SHEAR_MAX)
|
self.prefs.setdefault("plot_shear_max", DEFAULT_PLOT_SHEAR_MAX)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, pyqtSignal
|
from PyQt5.QtCore import Qt, pyqtSignal
|
||||||
from PyQt5.QtGui import QImage, QPixmap
|
from PyQt5.QtGui import QImage, QPixmap, QTransform
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QComboBox,
|
QComboBox,
|
||||||
QFrame,
|
QFrame,
|
||||||
@@ -16,6 +16,17 @@ from PyQt5.QtWidgets import (
|
|||||||
from orisys_app_shared.i18n import t
|
from orisys_app_shared.i18n import t
|
||||||
|
|
||||||
VIEW_MODES = ("arrows_pure", "arrows_stitched", "depth")
|
VIEW_MODES = ("arrows_pure", "arrows_stitched", "depth")
|
||||||
|
_ROTATION_STEPS = (0, 90, 180, 270)
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_rotation_deg(value: object, default: int = 90) -> int:
|
||||||
|
try:
|
||||||
|
deg = int(value) % 360 # type: ignore[arg-type]
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
if deg not in _ROTATION_STEPS:
|
||||||
|
deg = int(round(deg / 90.0) * 90) % 360
|
||||||
|
return deg
|
||||||
|
|
||||||
|
|
||||||
class ArrowPanel(QFrame):
|
class ArrowPanel(QFrame):
|
||||||
@@ -34,6 +45,9 @@ class ArrowPanel(QFrame):
|
|||||||
self.fps_label.setVisible(False)
|
self.fps_label.setVisible(False)
|
||||||
self._fps_visible = False
|
self._fps_visible = False
|
||||||
self._fps_value: float | None = None
|
self._fps_value: float | None = None
|
||||||
|
self._rotation_deg = 90
|
||||||
|
self._rotation_enabled = True
|
||||||
|
self._source_image: QImage | None = None
|
||||||
self.view_combo = QComboBox()
|
self.view_combo = QComboBox()
|
||||||
self.view_combo.setMinimumWidth(140)
|
self.view_combo.setMinimumWidth(140)
|
||||||
self.view_combo.currentIndexChanged.connect(self._on_view_index_changed)
|
self.view_combo.currentIndexChanged.connect(self._on_view_index_changed)
|
||||||
@@ -68,6 +82,26 @@ class ArrowPanel(QFrame):
|
|||||||
self.view_combo.setCurrentIndex(idx)
|
self.view_combo.setCurrentIndex(idx)
|
||||||
self.view_combo.blockSignals(False)
|
self.view_combo.blockSignals(False)
|
||||||
|
|
||||||
|
def rotation_deg(self) -> int:
|
||||||
|
return self._rotation_deg
|
||||||
|
|
||||||
|
def set_rotation_deg(self, deg: object) -> None:
|
||||||
|
self._rotation_deg = _normalize_rotation_deg(deg)
|
||||||
|
self._repaint_source()
|
||||||
|
|
||||||
|
def set_rotation_enabled(self, enabled: bool) -> None:
|
||||||
|
wanted = bool(enabled)
|
||||||
|
if wanted == self._rotation_enabled:
|
||||||
|
return
|
||||||
|
self._rotation_enabled = wanted
|
||||||
|
self._repaint_source()
|
||||||
|
|
||||||
|
def rotate_90_ccw(self) -> int:
|
||||||
|
if not self._rotation_enabled:
|
||||||
|
return self._rotation_deg
|
||||||
|
self.set_rotation_deg(self._rotation_deg + 90)
|
||||||
|
return self._rotation_deg
|
||||||
|
|
||||||
def set_fps_visible(self, visible: bool) -> None:
|
def set_fps_visible(self, visible: bool) -> None:
|
||||||
self._fps_visible = bool(visible)
|
self._fps_visible = bool(visible)
|
||||||
self._refresh_fps_label()
|
self._refresh_fps_label()
|
||||||
@@ -113,10 +147,22 @@ class ArrowPanel(QFrame):
|
|||||||
self._placeholder = t("app.scaffold_hint")
|
self._placeholder = t("app.scaffold_hint")
|
||||||
self.image_label.setText(self._placeholder)
|
self.image_label.setText(self._placeholder)
|
||||||
|
|
||||||
def show_frame(self, image: QImage) -> None:
|
def _effective_rotation_deg(self) -> int:
|
||||||
if image.isNull():
|
if not self._rotation_enabled:
|
||||||
|
return 0
|
||||||
|
return self._rotation_deg
|
||||||
|
|
||||||
|
def _repaint_source(self) -> None:
|
||||||
|
if self._source_image is None or self._source_image.isNull():
|
||||||
return
|
return
|
||||||
|
self._paint_image(self._source_image)
|
||||||
|
|
||||||
|
def _paint_image(self, image: QImage) -> None:
|
||||||
pix = QPixmap.fromImage(image)
|
pix = QPixmap.fromImage(image)
|
||||||
|
deg = self._effective_rotation_deg()
|
||||||
|
if deg:
|
||||||
|
# QTransform.rotate is clockwise on screen pixmaps; negate for CCW.
|
||||||
|
pix = pix.transformed(QTransform().rotate(-deg), Qt.FastTransformation)
|
||||||
scaled = pix.scaled(
|
scaled = pix.scaled(
|
||||||
self.image_label.size(),
|
self.image_label.size(),
|
||||||
Qt.KeepAspectRatio,
|
Qt.KeepAspectRatio,
|
||||||
@@ -125,7 +171,14 @@ class ArrowPanel(QFrame):
|
|||||||
self.image_label.setPixmap(scaled)
|
self.image_label.setPixmap(scaled)
|
||||||
self.image_label.setText("")
|
self.image_label.setText("")
|
||||||
|
|
||||||
|
def show_frame(self, image: QImage) -> None:
|
||||||
|
if image.isNull():
|
||||||
|
return
|
||||||
|
self._source_image = image
|
||||||
|
self._paint_image(image)
|
||||||
|
|
||||||
def clear_preview(self, message: str | None = None) -> None:
|
def clear_preview(self, message: str | None = None) -> None:
|
||||||
|
self._source_image = None
|
||||||
self.image_label.clear()
|
self.image_label.clear()
|
||||||
self.image_label.setPixmap(QPixmap())
|
self.image_label.setPixmap(QPixmap())
|
||||||
text = message if message is not None else t("app.scaffold_hint")
|
text = message if message is not None else t("app.scaffold_hint")
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ _DEFAULTS: dict[str, Any] = {
|
|||||||
"plot_fn_max": 20.0,
|
"plot_fn_max": 20.0,
|
||||||
"plot_shear_max": 20.0,
|
"plot_shear_max": 20.0,
|
||||||
"image_view_mode": "arrows_pure",
|
"image_view_mode": "arrows_pure",
|
||||||
|
"arrow_rotation_deg": 90,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user