三维设置加箭头阈值,静默处理网格冲突

对话框写回 flow_on_px(二维三维共用),去掉 analysis_grid_mismatch 告警,缩短 UV/形变等工程腔文案并认 source=analysis。已用 pane UI 与 controller 单测验证。
This commit is contained in:
ykcheungab
2026-10-08 16:01:15 +08:00
parent f2bbab76a6
commit f2d7b3e83c
7 changed files with 130 additions and 43 deletions
@@ -51,7 +51,6 @@ from orisys_studio.replay_analysis import (
AnalysisSession,
ResultLibrary,
ResultPolicyError,
policy_divergence,
)
from orisys_studio.shell_ports import ReplayShell
@@ -776,27 +775,19 @@ class ReplayController(QObject):
state this view is served in.
A policy that moves the sample *grid* cannot be served by the stored candidate
locations; :meth:`AnalysisSession.serve_policy` refuses it, the message names the
keys, and the frame is still redrawn from the result's own grid, because
re-gridding it would draw samples nothing measured.
locations; :meth:`AnalysisSession.serve_policy` refuses it and the frame is
still redrawn from the result's own grid — without a status-bar warning —
because re-gridding it would draw samples nothing measured.
"""
session = self.available_result()
if session is None:
return
from orisys_app_shared.scene3d.visual_policy import policy_for_settings
policy = policy_for_settings(settings)
if not session.serve_policy(policy):
# A box result's grid is the live painter's, not a display preference.
# The display policy is the shared selector's, so the two always differ,
# and saying so would scold the operator for a grid they cannot change.
from orisys_app_shared.acquisition.arrows import uses_sdk_box_grid
if not uses_sdk_box_grid(session.stored_policy()):
moved = policy_divergence(session.stored_policy(), policy)
self._host.notify(
t("playback.analysis_grid_mismatch", keys="·".join(moved))
)
# A policy that moves the sample grid is refused inside ``serve_policy``;
# the frame is still redrawn from the result's own grid. No status-bar
# lecture: the operator cannot change the stored locations from here.
session.serve_policy(policy_for_settings(settings))
self._engine.redraw()
def on_analysis_requested(self) -> None:
+37 -10
View File
@@ -155,6 +155,10 @@ class ThreeDPanel(QFrame):
#: Whether the pane explains the pipeline around the picture (the window's
#: 「诊断信息」 switch). Off by default: the picture and what it *is* stay.
self._show_diagnostics = False
#: Sample-grid fields have no controls: they stay at the values restored from
#: prefs (or the dataclass defaults) so a colour-scale click cannot reset them.
self._flow_grid_spacing = 10
self._max_arrows = 400
root = QVBoxLayout(self)
root.setContentsMargins(8, 6, 8, 6)
@@ -244,6 +248,20 @@ class ThreeDPanel(QFrame):
self.contact_arrow_check.toggled.connect(self._emit_settings)
form.addRow(self.contact_arrow_check)
# One threshold for both the 2D canvas and the 3D overlays. ``flow_off_px``
# is derived (on − 0.1): neither view applies hysteresis today, and a band
# only one side honoured was the 2D/3D arrow-count mismatch.
self.flow_threshold_label = QLabel()
self.flow_threshold_spin = QDoubleSpinBox()
self.flow_threshold_spin.setRange(0.0, 50.0)
self.flow_threshold_spin.setDecimals(2)
self.flow_threshold_spin.setSingleStep(0.1)
self.flow_threshold_spin.setSuffix(" px")
self.flow_threshold_spin.setValue(0.4)
self.flow_threshold_spin.setMaximumWidth(SPIN_WIDTH)
self.flow_threshold_spin.valueChanged.connect(self._emit_settings)
form.addRow(self.flow_threshold_label, self.flow_threshold_spin)
self.scale_label = QLabel()
self.scale_combo = QComboBox()
self.scale_combo.addItem("fixed", "fixed")
@@ -539,20 +557,15 @@ class ThreeDPanel(QFrame):
self.state_label.setVisible(self._show_diagnostics and state != "live")
def set_source_kind(self, kind: str) -> None:
"""Say where the field comes from: ``live``, ``replay`` or ``recompute``.
Three states, and the difference between the last two is the point of the
whole replay story:
"""Say where the field comes from: live, replay, recompute or analysis.
* ``live`` — acquired from the device now;
* ``replay`` — a recording is open and **no** 3D field is stored in it, so the
pane is showing the surface model only;
* ``recompute`` — a recompute pass is running over the recording's video, so
what is on screen is what *this* algorithm version says about that footage.
It is not the recorded measurement, and the label says so.
* ``replay`` — a recording is open and **no** 3D field is stored in it;
* ``recompute`` — a recompute pass is running over the recording's video;
* ``analysis`` — a finished stored result is being read frame by frame.
"""
kind = str(kind)
if kind not in ("live", "replay", "recompute"):
if kind not in ("live", "replay", "recompute", "analysis"):
kind = "live"
self._source_kind = kind
self.source_label.setText(t(f"scene3d.source_{kind}"))
@@ -661,6 +674,7 @@ class ThreeDPanel(QFrame):
self.gain_spin,
self.flow_arrows_check,
self.contact_arrow_check,
self.flow_threshold_spin,
self.scale_combo,
self.fps_combo,
self.reset_button,
@@ -694,6 +708,9 @@ class ThreeDPanel(QFrame):
self.field_combo.setCurrentIndex(index)
self.flow_arrows_check.setChecked(bool(settings.flow_arrows_enabled))
self.contact_arrow_check.setChecked(bool(settings.contact_arrow_enabled))
self.flow_threshold_spin.setValue(float(settings.flow_on_px))
self._flow_grid_spacing = max(1, int(settings.flow_grid_spacing))
self._max_arrows = max(1, int(settings.max_arrows))
self.depth_adaptive_check.setChecked(bool(settings.depth_scale_adaptive))
scale = settings.color_scale
if scale is not None:
@@ -725,6 +742,9 @@ class ThreeDPanel(QFrame):
deform = bool(self.deform_check.isChecked()) and bool(
self._capabilities and self._capabilities.displacement_available
)
flow_on = max(0.0, float(self.flow_threshold_spin.value()))
# Derived off band: kept for prefs / VisualPolicy, not applied as hysteresis.
flow_off = max(0.0, flow_on - 0.1)
return DisplaySettings(
display_gain=float(self.gain_spin.value()),
deform_enabled=deform,
@@ -740,6 +760,10 @@ class ThreeDPanel(QFrame):
field=self.field(),
flow_arrows_enabled=bool(self.flow_arrows_check.isChecked()),
contact_arrow_enabled=bool(self.contact_arrow_check.isChecked()),
flow_on_px=flow_on,
flow_off_px=flow_off,
flow_grid_spacing=int(self._flow_grid_spacing),
max_arrows=int(self._max_arrows),
depth_scale_adaptive=bool(self.depth_adaptive_check.isChecked()),
)
@@ -810,6 +834,9 @@ class ThreeDPanel(QFrame):
self._render_field_note()
self.flow_arrows_check.setText(t("scene3d.flow_arrows"))
self.contact_arrow_check.setText(t("scene3d.contact_arrow"))
self.flow_threshold_label.setText(t("scene3d.flow_threshold"))
self.flow_threshold_label.setToolTip(t("scene3d.flow_threshold_tip"))
self.flow_threshold_spin.setToolTip(t("scene3d.flow_threshold_tip"))
self.scale_label.setText(t("scene3d.scale"))
self.range_label.setText(t("scene3d.scale_range"))
self.range_label.setToolTip(t("scene3d.scale_range_tip"))
@@ -49,7 +49,8 @@ class FingerOrientationDialog(QDialog):
self.orientation_note.setWordWrap(True)
form.addRow(self.orientation_label, self.orientation_combo)
form.addRow(self.orientation_note)
# Tip lives on the combo; the note repeated the same sentence.
self.orientation_note.setVisible(False)
root.addLayout(form)
self.button_box = QDialogButtonBox(