三维设置加箭头阈值,静默处理网格冲突
对话框写回 flow_on_px(二维三维共用),去掉 analysis_grid_mismatch 告警,缩短 UV/形变等工程腔文案并认 source=analysis。已用 pane UI 与 controller 单测验证。
This commit is contained in:
@@ -225,7 +225,7 @@
|
|||||||
"depth_unavailable": "No depth data for this frame",
|
"depth_unavailable": "No depth data for this frame",
|
||||||
"depth_scale": "Depth scale {low} – {high}",
|
"depth_scale": "Depth scale {low} – {high}",
|
||||||
"arrow_scale": "Arrows: displacement ×{factor}",
|
"arrow_scale": "Arrows: displacement ×{factor}",
|
||||||
"arrow_scale_tip": "Display magnification of the relative displacement: the arrows are drawn from the algorithm output of the captured image, with the measured displacement multiplied by this factor and capped at 0.8 of the grid spacing (a capped arrow no longer shows its true factor).",
|
"arrow_scale_tip": "Arrow length is magnified by displacement.",
|
||||||
"arrow_pipeline": "Captured image → algorithm output",
|
"arrow_pipeline": "Captured image → algorithm output",
|
||||||
"arrow_pipeline_tip": "Arrows are drawn over the algorithm output, not over the raw camera frame: points below the threshold get no arrow",
|
"arrow_pipeline_tip": "Arrows are drawn over the algorithm output, not over the raw camera frame: points below the threshold get no arrow",
|
||||||
"arrow_area_masked": "Valid area",
|
"arrow_area_masked": "Valid area",
|
||||||
@@ -315,7 +315,6 @@
|
|||||||
"analysis_cancelled": "the analysis was cancelled: no complete result was written, so no playback is served",
|
"analysis_cancelled": "the analysis was cancelled: no complete result was written, so no playback is served",
|
||||||
"analysis_no_source": "this recording has no video segment to analyse",
|
"analysis_no_source": "this recording has no video segment to analyse",
|
||||||
"analysis_busy": "an analysis is already running: wait for it, or cancel it",
|
"analysis_busy": "an analysis is already running: wait for it, or cancel it",
|
||||||
"analysis_grid_mismatch": "the current display settings moved the sample grid ({keys}); the stored candidate locations cannot serve it, so the replay keeps drawing the result's own grid",
|
|
||||||
"analysis_no_chain": "this recording does not say which sensor and configuration produced it ({error}), so no result can be read and no pass can be run",
|
"analysis_no_chain": "this recording does not say which sensor and configuration produced it ({error}), so no result can be read and no pass can be run",
|
||||||
"analysis_setup_declined": "no analysis was set up for this video: the setup prompt was dismissed, so there is nothing to draw from. Use Analysis ▸ Analysis setup… or open the video again"
|
"analysis_setup_declined": "no analysis was set up for this video: the setup prompt was dismissed, so there is nothing to draw from. Use Analysis ▸ Analysis setup… or open the video again"
|
||||||
},
|
},
|
||||||
@@ -504,7 +503,7 @@
|
|||||||
"housing": "Housing",
|
"housing": "Housing",
|
||||||
"deform": "Exaggerate",
|
"deform": "Exaggerate",
|
||||||
"deform_tip": "Display-only magnification of the measured deformation. It changes the picture and nothing else: the recorded data, the calibration and the camera are not touched.",
|
"deform_tip": "Display-only magnification of the measured deformation. It changes the picture and nothing else: the recorded data, the calibration and the camera are not touched.",
|
||||||
"deform_unavailable": "This data source provides no 3D displacement: the pane can only colour the surface, so exaggeration is unavailable.",
|
"deform_unavailable": "This data has no 3D displacement; exaggeration is unavailable.",
|
||||||
"field": "Colour by",
|
"field": "Colour by",
|
||||||
"field_tip": "Colour the surface by depth or optical flow.",
|
"field_tip": "Colour the surface by depth or optical flow.",
|
||||||
"field_depth": "Depth response",
|
"field_depth": "Depth response",
|
||||||
@@ -512,10 +511,12 @@
|
|||||||
"field_substituted": "This frame shows {shown}, not the chosen {chosen}.",
|
"field_substituted": "This frame shows {shown}, not the chosen {chosen}.",
|
||||||
"flow_arrows": "Surface flow arrows",
|
"flow_arrows": "Surface flow arrows",
|
||||||
"flow_arrows_tip": "Draw optical-flow arrows on the sensing surface using the same sample points as the 2D view.",
|
"flow_arrows_tip": "Draw optical-flow arrows on the sensing surface using the same sample points as the 2D view.",
|
||||||
"flow_arrows_unavailable": "This scene package has no UV→scene surface mapping, so flow arrows cannot be drawn.",
|
"flow_arrows_unavailable": "This scene cannot draw flow arrows.",
|
||||||
"contact_arrow": "Contact normal arrow",
|
"contact_arrow": "Contact normal arrow",
|
||||||
"contact_arrow_tip": "Draw a yellow local-normal arrow at the contact centre (length from the normal response, capped — not a calibrated force vector).",
|
"contact_arrow_tip": "Draw a yellow local-normal arrow at the contact centre (length from the normal response, capped — not a calibrated force vector).",
|
||||||
"contact_arrow_unavailable": "This scene package has no UV→scene surface mapping, so the contact arrow cannot be drawn.",
|
"contact_arrow_unavailable": "This scene cannot draw the contact arrow.",
|
||||||
|
"flow_threshold": "Arrow threshold",
|
||||||
|
"flow_threshold_tip": "Arrows below this displacement (px) are not drawn; shared by 2D and 3D.",
|
||||||
"quantity_flow_magnitude": "Optical flow magnitude",
|
"quantity_flow_magnitude": "Optical flow magnitude",
|
||||||
"quantity_depth_response": "Depth response",
|
"quantity_depth_response": "Depth response",
|
||||||
"preset_default": "Default view",
|
"preset_default": "Default view",
|
||||||
@@ -556,9 +557,9 @@
|
|||||||
"severity_info": "Note",
|
"severity_info": "Note",
|
||||||
"severity_warning": "Warning",
|
"severity_warning": "Warning",
|
||||||
"severity_error": "Error",
|
"severity_error": "Error",
|
||||||
"stats": "shown {displayed} · display drops {dropped} · compose {compose} ms · paint {paint} ms",
|
"stats": "shown {displayed} · frames not shown in time {dropped} · compose {compose} ms · paint {paint} ms",
|
||||||
"source_analysis": "Analysis result: one finished pass, read by frame",
|
"source_analysis": "Analysis result: read by frame",
|
||||||
"source_analysis_tip": "The picture comes from a stored analysis result: the chain has already run and its output is on disk, so scrubbing and playing are file reads. Changing a threshold or a colour does not recompute."
|
"source_analysis_tip": "The picture comes from a stored analysis result. Changing a threshold or colour does not recompute."
|
||||||
},
|
},
|
||||||
"analysis": {
|
"analysis": {
|
||||||
"title": "Analysing the recording",
|
"title": "Analysing the recording",
|
||||||
|
|||||||
@@ -225,7 +225,7 @@
|
|||||||
"depth_unavailable": "本帧没有深度数据",
|
"depth_unavailable": "本帧没有深度数据",
|
||||||
"depth_scale": "深度色标 {low} – {high}",
|
"depth_scale": "深度色标 {low} – {high}",
|
||||||
"arrow_scale": "箭头按位移放大 ×{factor}",
|
"arrow_scale": "箭头按位移放大 ×{factor}",
|
||||||
"arrow_scale_tip": "相对位移的显示放大倍率:箭头由原始采集图像经算法输出,长度按测量位移放大后绘制,上限为网格间距的 0.8 倍;触顶的箭头不再呈现真实倍率。",
|
"arrow_scale_tip": "箭头长度按位移放大显示。",
|
||||||
"arrow_pipeline": "原始图像 → 算法输出",
|
"arrow_pipeline": "原始图像 → 算法输出",
|
||||||
"arrow_pipeline_tip": "箭头画在算法输出上,不是相机原始帧:阈值以下的区域不画箭头",
|
"arrow_pipeline_tip": "箭头画在算法输出上,不是相机原始帧:阈值以下的区域不画箭头",
|
||||||
"arrow_area_masked": "有效区域",
|
"arrow_area_masked": "有效区域",
|
||||||
@@ -315,7 +315,6 @@
|
|||||||
"analysis_cancelled": "分析已取消:未写出完整结果,因此不提供回放",
|
"analysis_cancelled": "分析已取消:未写出完整结果,因此不提供回放",
|
||||||
"analysis_no_source": "这段录制没有可用于分析的视频分段",
|
"analysis_no_source": "这段录制没有可用于分析的视频分段",
|
||||||
"analysis_busy": "分析进行中:先等待或取消当前这一遍",
|
"analysis_busy": "分析进行中:先等待或取消当前这一遍",
|
||||||
"analysis_grid_mismatch": "当前显示设置改了采样网格({keys}),存下来的候选位置无法服务它:回放仍按结果自己的网格绘制",
|
|
||||||
"analysis_no_chain": "这条录像没有说明用哪套传感器与配置录下来({error}),因此既无法读取结果,也无法重算",
|
"analysis_no_chain": "这条录像没有说明用哪套传感器与配置录下来({error}),因此既无法读取结果,也无法重算",
|
||||||
"analysis_setup_declined": "这段视频没有建立分析:设置提示被关掉了,因此没有可画的东西。用「分析 ▸ 分析设置…」或重新打开视频即可分析"
|
"analysis_setup_declined": "这段视频没有建立分析:设置提示被关掉了,因此没有可画的东西。用「分析 ▸ 分析设置…」或重新打开视频即可分析"
|
||||||
},
|
},
|
||||||
@@ -504,7 +503,7 @@
|
|||||||
"housing": "显示外壳",
|
"housing": "显示外壳",
|
||||||
"deform": "放大形变",
|
"deform": "放大形变",
|
||||||
"deform_tip": "只放大画面上的形变显示。它只改图像:记录的数据、标定与算法相机都不受影响。",
|
"deform_tip": "只放大画面上的形变显示。它只改图像:记录的数据、标定与算法相机都不受影响。",
|
||||||
"deform_unavailable": "当前数据源未提供三维位移:本面板只有光流幅值的曲面着色,形变放大不可用。",
|
"deform_unavailable": "当前数据没有三维位移,形变放大不可用。",
|
||||||
"field": "颜色来源",
|
"field": "颜色来源",
|
||||||
"field_tip": "用深度或光流给表面着色。",
|
"field_tip": "用深度或光流给表面着色。",
|
||||||
"field_depth": "深度响应",
|
"field_depth": "深度响应",
|
||||||
@@ -512,10 +511,12 @@
|
|||||||
"field_substituted": "这一帧画的是 {shown},不是所选的 {chosen}。",
|
"field_substituted": "这一帧画的是 {shown},不是所选的 {chosen}。",
|
||||||
"flow_arrows": "表面光流箭头",
|
"flow_arrows": "表面光流箭头",
|
||||||
"flow_arrows_tip": "在传感表面上按与二维相同的采样点绘制光流箭头。",
|
"flow_arrows_tip": "在传感表面上按与二维相同的采样点绘制光流箭头。",
|
||||||
"flow_arrows_unavailable": "当前场景包没有 UV→三维 表面映射,无法绘制光流箭头。",
|
"flow_arrows_unavailable": "当前场景画不了光流箭头。",
|
||||||
"contact_arrow": "接触法向箭头",
|
"contact_arrow": "接触法向箭头",
|
||||||
"contact_arrow_tip": "在接触中心沿局部法向绘制黄色示意箭头(长度由法向响应限幅,不是标定合力)。",
|
"contact_arrow_tip": "在接触中心沿局部法向绘制黄色示意箭头(长度由法向响应限幅,不是标定合力)。",
|
||||||
"contact_arrow_unavailable": "当前场景包没有 UV→三维 表面映射,无法绘制接触法向箭头。",
|
"contact_arrow_unavailable": "当前场景画不了接触箭头。",
|
||||||
|
"flow_threshold": "箭头阈值",
|
||||||
|
"flow_threshold_tip": "低于此位移(像素)不画箭头;二维与三维共用。",
|
||||||
"quantity_flow_magnitude": "光流幅值",
|
"quantity_flow_magnitude": "光流幅值",
|
||||||
"quantity_depth_response": "深度响应",
|
"quantity_depth_response": "深度响应",
|
||||||
"preset_default": "默认视角",
|
"preset_default": "默认视角",
|
||||||
@@ -556,9 +557,9 @@
|
|||||||
"severity_info": "提示",
|
"severity_info": "提示",
|
||||||
"severity_warning": "警告",
|
"severity_warning": "警告",
|
||||||
"severity_error": "错误",
|
"severity_error": "错误",
|
||||||
"stats": "已显示 {displayed} · 显示跳帧 {dropped} · 合成 {compose} ms · 绘制 {paint} ms",
|
"stats": "已显示 {displayed} · 未赶上显示的帧 {dropped} · 合成 {compose} ms · 绘制 {paint} ms",
|
||||||
"source_analysis": "分析结果:已跑完的一遍,按帧读取",
|
"source_analysis": "分析结果:按帧读取",
|
||||||
"source_analysis_tip": "画面来自存下来的分析结果:算法链路已经跑完并写入缓存,拖动时间轴与播放都只是读文件。换阈值或配色不会重算。"
|
"source_analysis_tip": "画面来自已存的分析结果。改阈值或配色不会重算。"
|
||||||
},
|
},
|
||||||
"analysis": {
|
"analysis": {
|
||||||
"title": "正在分析录像",
|
"title": "正在分析录像",
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ from orisys_studio.replay_analysis import (
|
|||||||
AnalysisSession,
|
AnalysisSession,
|
||||||
ResultLibrary,
|
ResultLibrary,
|
||||||
ResultPolicyError,
|
ResultPolicyError,
|
||||||
policy_divergence,
|
|
||||||
)
|
)
|
||||||
from orisys_studio.shell_ports import ReplayShell
|
from orisys_studio.shell_ports import ReplayShell
|
||||||
|
|
||||||
@@ -776,27 +775,19 @@ class ReplayController(QObject):
|
|||||||
state this view is served in.
|
state this view is served in.
|
||||||
|
|
||||||
A policy that moves the sample *grid* cannot be served by the stored candidate
|
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
|
locations; :meth:`AnalysisSession.serve_policy` refuses it and the frame is
|
||||||
keys, and the frame is still redrawn from the result's own grid, because
|
still redrawn from the result's own grid — without a status-bar warning —
|
||||||
re-gridding it would draw samples nothing measured.
|
because re-gridding it would draw samples nothing measured.
|
||||||
"""
|
"""
|
||||||
session = self.available_result()
|
session = self.available_result()
|
||||||
if session is None:
|
if session is None:
|
||||||
return
|
return
|
||||||
from orisys_app_shared.scene3d.visual_policy import policy_for_settings
|
from orisys_app_shared.scene3d.visual_policy import policy_for_settings
|
||||||
|
|
||||||
policy = policy_for_settings(settings)
|
# A policy that moves the sample grid is refused inside ``serve_policy``;
|
||||||
if not session.serve_policy(policy):
|
# the frame is still redrawn from the result's own grid. No status-bar
|
||||||
# A box result's grid is the live painter's, not a display preference.
|
# lecture: the operator cannot change the stored locations from here.
|
||||||
# The display policy is the shared selector's, so the two always differ,
|
session.serve_policy(policy_for_settings(settings))
|
||||||
# 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))
|
|
||||||
)
|
|
||||||
self._engine.redraw()
|
self._engine.redraw()
|
||||||
|
|
||||||
def on_analysis_requested(self) -> None:
|
def on_analysis_requested(self) -> None:
|
||||||
|
|||||||
@@ -155,6 +155,10 @@ class ThreeDPanel(QFrame):
|
|||||||
#: Whether the pane explains the pipeline around the picture (the window's
|
#: Whether the pane explains the pipeline around the picture (the window's
|
||||||
#: 「诊断信息」 switch). Off by default: the picture and what it *is* stay.
|
#: 「诊断信息」 switch). Off by default: the picture and what it *is* stay.
|
||||||
self._show_diagnostics = False
|
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 = QVBoxLayout(self)
|
||||||
root.setContentsMargins(8, 6, 8, 6)
|
root.setContentsMargins(8, 6, 8, 6)
|
||||||
@@ -244,6 +248,20 @@ class ThreeDPanel(QFrame):
|
|||||||
self.contact_arrow_check.toggled.connect(self._emit_settings)
|
self.contact_arrow_check.toggled.connect(self._emit_settings)
|
||||||
form.addRow(self.contact_arrow_check)
|
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_label = QLabel()
|
||||||
self.scale_combo = QComboBox()
|
self.scale_combo = QComboBox()
|
||||||
self.scale_combo.addItem("fixed", "fixed")
|
self.scale_combo.addItem("fixed", "fixed")
|
||||||
@@ -539,20 +557,15 @@ class ThreeDPanel(QFrame):
|
|||||||
self.state_label.setVisible(self._show_diagnostics and state != "live")
|
self.state_label.setVisible(self._show_diagnostics and state != "live")
|
||||||
|
|
||||||
def set_source_kind(self, kind: str) -> None:
|
def set_source_kind(self, kind: str) -> None:
|
||||||
"""Say where the field comes from: ``live``, ``replay`` or ``recompute``.
|
"""Say where the field comes from: live, replay, recompute or analysis.
|
||||||
|
|
||||||
Three states, and the difference between the last two is the point of the
|
|
||||||
whole replay story:
|
|
||||||
|
|
||||||
* ``live`` — acquired from the device now;
|
* ``live`` — acquired from the device now;
|
||||||
* ``replay`` — a recording is open and **no** 3D field is stored in it, so the
|
* ``replay`` — a recording is open and **no** 3D field is stored in it;
|
||||||
pane is showing the surface model only;
|
* ``recompute`` — a recompute pass is running over the recording's video;
|
||||||
* ``recompute`` — a recompute pass is running over the recording's video, so
|
* ``analysis`` — a finished stored result is being read frame by frame.
|
||||||
what is on screen is what *this* algorithm version says about that footage.
|
|
||||||
It is not the recorded measurement, and the label says so.
|
|
||||||
"""
|
"""
|
||||||
kind = str(kind)
|
kind = str(kind)
|
||||||
if kind not in ("live", "replay", "recompute"):
|
if kind not in ("live", "replay", "recompute", "analysis"):
|
||||||
kind = "live"
|
kind = "live"
|
||||||
self._source_kind = kind
|
self._source_kind = kind
|
||||||
self.source_label.setText(t(f"scene3d.source_{kind}"))
|
self.source_label.setText(t(f"scene3d.source_{kind}"))
|
||||||
@@ -661,6 +674,7 @@ class ThreeDPanel(QFrame):
|
|||||||
self.gain_spin,
|
self.gain_spin,
|
||||||
self.flow_arrows_check,
|
self.flow_arrows_check,
|
||||||
self.contact_arrow_check,
|
self.contact_arrow_check,
|
||||||
|
self.flow_threshold_spin,
|
||||||
self.scale_combo,
|
self.scale_combo,
|
||||||
self.fps_combo,
|
self.fps_combo,
|
||||||
self.reset_button,
|
self.reset_button,
|
||||||
@@ -694,6 +708,9 @@ class ThreeDPanel(QFrame):
|
|||||||
self.field_combo.setCurrentIndex(index)
|
self.field_combo.setCurrentIndex(index)
|
||||||
self.flow_arrows_check.setChecked(bool(settings.flow_arrows_enabled))
|
self.flow_arrows_check.setChecked(bool(settings.flow_arrows_enabled))
|
||||||
self.contact_arrow_check.setChecked(bool(settings.contact_arrow_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))
|
self.depth_adaptive_check.setChecked(bool(settings.depth_scale_adaptive))
|
||||||
scale = settings.color_scale
|
scale = settings.color_scale
|
||||||
if scale is not None:
|
if scale is not None:
|
||||||
@@ -725,6 +742,9 @@ class ThreeDPanel(QFrame):
|
|||||||
deform = bool(self.deform_check.isChecked()) and bool(
|
deform = bool(self.deform_check.isChecked()) and bool(
|
||||||
self._capabilities and self._capabilities.displacement_available
|
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(
|
return DisplaySettings(
|
||||||
display_gain=float(self.gain_spin.value()),
|
display_gain=float(self.gain_spin.value()),
|
||||||
deform_enabled=deform,
|
deform_enabled=deform,
|
||||||
@@ -740,6 +760,10 @@ class ThreeDPanel(QFrame):
|
|||||||
field=self.field(),
|
field=self.field(),
|
||||||
flow_arrows_enabled=bool(self.flow_arrows_check.isChecked()),
|
flow_arrows_enabled=bool(self.flow_arrows_check.isChecked()),
|
||||||
contact_arrow_enabled=bool(self.contact_arrow_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()),
|
depth_scale_adaptive=bool(self.depth_adaptive_check.isChecked()),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -810,6 +834,9 @@ class ThreeDPanel(QFrame):
|
|||||||
self._render_field_note()
|
self._render_field_note()
|
||||||
self.flow_arrows_check.setText(t("scene3d.flow_arrows"))
|
self.flow_arrows_check.setText(t("scene3d.flow_arrows"))
|
||||||
self.contact_arrow_check.setText(t("scene3d.contact_arrow"))
|
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.scale_label.setText(t("scene3d.scale"))
|
||||||
self.range_label.setText(t("scene3d.scale_range"))
|
self.range_label.setText(t("scene3d.scale_range"))
|
||||||
self.range_label.setToolTip(t("scene3d.scale_range_tip"))
|
self.range_label.setToolTip(t("scene3d.scale_range_tip"))
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ class FingerOrientationDialog(QDialog):
|
|||||||
self.orientation_note.setWordWrap(True)
|
self.orientation_note.setWordWrap(True)
|
||||||
|
|
||||||
form.addRow(self.orientation_label, self.orientation_combo)
|
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)
|
root.addLayout(form)
|
||||||
|
|
||||||
self.button_box = QDialogButtonBox(
|
self.button_box = QDialogButtonBox(
|
||||||
|
|||||||
@@ -978,10 +978,45 @@ def test_the_controller_redraws_on_a_display_change_and_runs_no_pass(pass_root):
|
|||||||
assert session.threshold() == pytest.approx(0.5)
|
assert session.threshold() == pytest.approx(0.5)
|
||||||
# The frame is rebuilt from the same stored record, not from a new one.
|
# The frame is rebuilt from the same stored record, not from a new one.
|
||||||
assert session.frame(int(session.frame_ids[0])).frame_id == FIRST_FRAME_ID
|
assert session.frame(int(session.frame_ids[0])).frame_id == FIRST_FRAME_ID
|
||||||
|
assert host.notes == [], "a threshold change must not lecture about the grid"
|
||||||
finally:
|
finally:
|
||||||
session.reader.close()
|
session.reader.close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_refused_grid_change_redraws_without_a_status_warning():
|
||||||
|
"""Moving max_arrows cannot be served; the frame is redrawn and nothing is said.
|
||||||
|
|
||||||
|
No evidence recording: the session is a stub whose ``serve_policy`` refuses, which
|
||||||
|
is exactly the path that used to push ``playback.analysis_grid_mismatch``.
|
||||||
|
"""
|
||||||
|
class _Reader:
|
||||||
|
def close(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class _Session:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.calls: list = []
|
||||||
|
self.reader = _Reader()
|
||||||
|
|
||||||
|
def serve_policy(self, policy) -> bool:
|
||||||
|
self.calls.append(policy)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
controller, host, engine, parts = _controller()
|
||||||
|
session = _Session()
|
||||||
|
controller.results.open_session(session) # type: ignore[arg-type]
|
||||||
|
engine.use_analysis(session)
|
||||||
|
engine.redraws = 0
|
||||||
|
controller.on_display_settings(DisplaySettings(max_arrows=50))
|
||||||
|
assert engine.redraws == 1, "the refused policy still redraws the stored frame"
|
||||||
|
assert len(session.calls) == 1
|
||||||
|
assert host.notes == [], "a grid mismatch must not reach the status bar"
|
||||||
|
controller.results.close()
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------ analysis on open
|
# ------------------------------------------------------------ analysis on open
|
||||||
def test_a_running_session_does_not_hide_a_stored_result(pass_root):
|
def test_a_running_session_does_not_hide_a_stored_result(pass_root):
|
||||||
"""A result on disk is served while a session is open: reading it opens no chain.
|
"""A result on disk is served while a session is open: reading it opens no chain.
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ def test_the_pane_holds_a_legend_and_no_settings(panel):
|
|||||||
panel.housing_check,
|
panel.housing_check,
|
||||||
panel.deform_check,
|
panel.deform_check,
|
||||||
panel.gain_spin,
|
panel.gain_spin,
|
||||||
|
panel.flow_threshold_spin,
|
||||||
panel.scale_combo,
|
panel.scale_combo,
|
||||||
panel.depth_adaptive_check,
|
panel.depth_adaptive_check,
|
||||||
panel.fps_combo,
|
panel.fps_combo,
|
||||||
@@ -106,6 +107,36 @@ def test_adaptive_depth_scale_round_trips_through_the_dialog(panel):
|
|||||||
assert panel.settings().depth_scale_adaptive is False
|
assert panel.settings().depth_scale_adaptive is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_arrow_threshold_round_trips_and_feeds_the_shared_policy(panel):
|
||||||
|
"""The threshold the dialog writes is the one both views sample with."""
|
||||||
|
from orisys_app_shared.scene3d.contracts import DisplaySettings
|
||||||
|
from orisys_app_shared.scene3d.visual_policy import policy_for_settings
|
||||||
|
|
||||||
|
panel.apply_settings(
|
||||||
|
DisplaySettings(flow_on_px=0.9, flow_grid_spacing=12, max_arrows=200)
|
||||||
|
)
|
||||||
|
assert panel.flow_threshold_spin.value() == pytest.approx(0.9)
|
||||||
|
settings = panel.settings()
|
||||||
|
assert settings.flow_on_px == pytest.approx(0.9)
|
||||||
|
assert settings.flow_off_px == pytest.approx(0.8)
|
||||||
|
assert settings.flow_grid_spacing == 12
|
||||||
|
assert settings.max_arrows == 200
|
||||||
|
# A colour-scale click must not wipe the held grid or the typed threshold.
|
||||||
|
panel.housing_check.setChecked(True)
|
||||||
|
again = panel.settings()
|
||||||
|
assert again.flow_on_px == pytest.approx(0.9)
|
||||||
|
assert again.flow_grid_spacing == 12
|
||||||
|
assert policy_for_settings(again).flow_on_px == pytest.approx(0.9)
|
||||||
|
|
||||||
|
|
||||||
|
def test_source_kind_analysis_uses_its_own_label(panel):
|
||||||
|
"""A stored result is not labelled as live acquisition."""
|
||||||
|
panel.set_diagnostics_visible(True)
|
||||||
|
panel.set_source_kind("analysis")
|
||||||
|
assert panel.source_label.text() == t("scene3d.source_analysis")
|
||||||
|
assert panel.source_label.toolTip() == t("scene3d.source_analysis_tip")
|
||||||
|
|
||||||
|
|
||||||
def test_the_pane_header_offers_the_two_actions(qt_app):
|
def test_the_pane_header_offers_the_two_actions(qt_app):
|
||||||
"""A gear and a view reset: the two things that belong to *looking*."""
|
"""A gear and a view reset: the two things that belong to *looking*."""
|
||||||
from orisys_studio.ui.mesh3d_panel import Mesh3dPanel
|
from orisys_studio.ui.mesh3d_panel import Mesh3dPanel
|
||||||
|
|||||||
Reference in New Issue
Block a user