Merge branch 'main' of ssh://892887cvmk08.aicp.io:24928/orisys-tech/orisys-app

This commit is contained in:
ykcheungab
2026-10-10 14:16:03 +08:00
parent f5ba64e01a
commit cf86bd0702
14 changed files with 118 additions and 4 deletions
+3 -1
View File
@@ -527,6 +527,8 @@
"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.",
"depth_threshold": "Depth display threshold",
"depth_threshold_tip": "Values below this relative depth response use the base ramp colour; 0 disables it. Shared by 2D and 3D without changing measurements.",
"quantity_flow_magnitude": "Optical flow magnitude",
"quantity_depth_response": "Depth response",
"preset_default": "Default view",
@@ -544,7 +546,7 @@
"max_fps_tip": "How many frames per second the 3D view draws. A display budget only: the camera and the recording are unaffected, and a dropped frame here is never a dropped sample.",
"reset_view": "Reset view",
"settings_title": "3D display settings",
"settings_hint": "Per-frame adaptive applies to both 2D and 3D.",
"settings_hint": "Depth display threshold and per-frame adaptive apply to both 2D and 3D.",
"legend": "{quantity} [{unit}] · {quality}",
"legend_none": "No measurement on screen — the surface model is shown uncoloured",
"quality_calibrated": "calibrated",
+3 -1
View File
@@ -527,6 +527,8 @@
"contact_arrow_unavailable": "当前场景画不了接触箭头。",
"flow_threshold": "箭头阈值",
"flow_threshold_tip": "低于此位移(像素)不画箭头;二维与三维共用。",
"depth_threshold": "深度显示阈值",
"depth_threshold_tip": "低于此相对深度响应的区域显示为色标基色;0 表示关闭。二维深度图与三维表面共用,不改变测量数据。",
"quantity_flow_magnitude": "光流幅值",
"quantity_depth_response": "深度响应",
"preset_default": "默认视角",
@@ -544,7 +546,7 @@
"max_fps_tip": "三维视图每秒绘制多少帧。这只是显示预算:摄像头与记录不受影响,这里丢的帧永远不会变成丢的样本。",
"reset_view": "复位视角",
"settings_title": "三维显示设置",
"settings_hint": "每帧自适应同时作用于二维和三维。",
"settings_hint": "深度显示阈值与每帧自适应同时作用于二维深度图和三维表面。",
"legend": "{quantity} [{unit}] · {quality}",
"legend_none": "屏幕上没有测量值 — 仅显示不着色的表面模型",
"quality_calibrated": "已标定",
@@ -251,6 +251,9 @@ class FrameView:
theme=theme,
draw_style=draw_style,
depth_range=span,
depth_threshold=float(
getattr(self.depth_display_settings(), "depth_threshold", 0.0)
),
canvas_bgr=hex_to_bgr(color(theme, "arrow_canvas", "#0B1830")),
domain_mask=boundary,
# A frame read from a stored analysis result says which policy its *recovered*
@@ -164,6 +164,7 @@ uniform sampler2D u_valid;
uniform vec2 u_inv_domain_size;
uniform float u_lower;
uniform float u_upper;
uniform float u_depth_threshold;
uniform vec4 u_invalid_color;
out vec4 frag_color;
@@ -181,7 +182,11 @@ void main() {
return;
}
float span = max(u_upper - u_lower, 1e-12);
float t = clamp((texture(u_field, texel).r - u_lower) / span, 0.0, 1.0);
float value = texture(u_field, texel).r;
float t = clamp((value - u_lower) / span, 0.0, 1.0);
if (u_depth_threshold > 0.0 && value < u_depth_threshold) {
t = 0.0;
}
frag_color = vec4(texture(u_lut, vec2(t, 0.5)).rgb, 1.0);
}
"""
@@ -717,6 +722,7 @@ class GLRenderer:
#: Range of the valid samples of the field currently uploaded, for the
#: automatic colour scale. ``None`` until a frame carries valid data.
self._scalar_range: tuple[float, float] | None = None
self._field_quantity = ""
#: The 2D canvas's latched depth ``(lo, hi)``, when the window has one.
#: Depth automatic colouring uses this so both legends name one interval.
self._shared_depth_scale: tuple[float, float] | None = None
@@ -771,6 +777,7 @@ class GLRenderer:
# longer on screen: keeping it made the first automatic draw of the new scene use
# the previous scene's bounds.
self._scalar_range = None
self._field_quantity = ""
self._last_scale = None
self._last_scale_mode = ""
uvs = np.asarray(assets.binding.uvs, dtype=np.float32)
@@ -847,6 +854,7 @@ class GLRenderer:
previous=self._scalar_range,
shared_depth_scale=self._shared_depth_scale,
)
self._field_quantity = str(getattr(field.descriptor, "quantity", ""))
self._upload_overlays(frame)
def _upload_field_textures(self, field: Any) -> None:
@@ -1070,6 +1078,13 @@ class GLRenderer:
if part.role == "sensing":
_set_float(GL, locations.get("u_lower"), lower)
_set_float(GL, locations.get("u_upper"), upper)
_set_float(
GL,
locations.get("u_depth_threshold"),
settings.depth_threshold
if self._field_quantity == "depth_response"
else 0.0,
)
_set_vec4(GL, locations.get("u_invalid_color"), INVALID_COLOR)
self._bind_field_textures(GL, locations)
else:
@@ -1203,6 +1218,7 @@ class GLRenderer:
self._initialized = False
self._estimated_gpu_bytes = 0
self._scalar_range = None
self._field_quantity = ""
self._last_scale = None
self._last_scale_mode = ""
self._paint_calls = 0
@@ -1339,7 +1355,8 @@ def _compile_program(GL: Any, profile: Profile, name: str) -> int:
def _uniform_locations(GL: Any, program: int) -> dict[str, int]:
names = (
"u_view", "u_projection", "u_lower", "u_upper",
"u_invalid_color", "u_lut", "u_field", "u_valid", "u_inv_domain_size",
"u_invalid_color", "u_depth_threshold", "u_lut", "u_field",
"u_valid", "u_inv_domain_size",
"u_light_direction", "u_base_color",
)
out: dict[str, int] = {}
@@ -265,6 +265,16 @@ class ThreeDPanel(QFrame):
self.flow_threshold_spin.valueChanged.connect(self._emit_settings)
form.addRow(self.flow_threshold_label, self.flow_threshold_spin)
self.depth_threshold_label = QLabel()
self.depth_threshold_spin = QDoubleSpinBox()
self.depth_threshold_spin.setRange(0.0, 1000.0)
self.depth_threshold_spin.setDecimals(4)
self.depth_threshold_spin.setSingleStep(0.005)
self.depth_threshold_spin.setValue(0.0)
self.depth_threshold_spin.setMaximumWidth(SPIN_WIDTH)
self.depth_threshold_spin.valueChanged.connect(self._emit_settings)
form.addRow(self.depth_threshold_label, self.depth_threshold_spin)
self.scale_label = QLabel()
self.scale_combo = QComboBox()
self.scale_combo.addItem("fixed", "fixed")
@@ -701,6 +711,7 @@ class ThreeDPanel(QFrame):
self.flow_arrows_check,
self.contact_arrow_check,
self.flow_threshold_spin,
self.depth_threshold_spin,
self.scale_combo,
self.fps_combo,
self.reset_button,
@@ -735,6 +746,7 @@ class ThreeDPanel(QFrame):
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.depth_threshold_spin.setValue(float(settings.depth_threshold))
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))
@@ -791,6 +803,7 @@ class ThreeDPanel(QFrame):
flow_grid_spacing=int(self._flow_grid_spacing),
max_arrows=int(self._max_arrows),
depth_scale_adaptive=bool(self.depth_adaptive_check.isChecked()),
depth_threshold=float(self.depth_threshold_spin.value()),
)
# ------------------------------------------------------------------ signals
@@ -863,6 +876,9 @@ class ThreeDPanel(QFrame):
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.depth_threshold_label.setText(t("scene3d.depth_threshold"))
self.depth_threshold_label.setToolTip(t("scene3d.depth_threshold_tip"))
self.depth_threshold_spin.setToolTip(t("scene3d.depth_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"))
@@ -89,6 +89,7 @@ def settings_to_prefs(settings: DisplaySettings) -> dict[str, Any]:
"flow_grid_spacing": int(settings.flow_grid_spacing),
"max_arrows": int(settings.max_arrows),
"depth_scale_adaptive": bool(settings.depth_scale_adaptive),
"depth_threshold": float(settings.depth_threshold),
}
if scale is not None:
block["color_scale"] = {
@@ -171,4 +172,5 @@ def settings_from_prefs(
flow_grid_spacing=spacing,
max_arrows=max_arrows,
depth_scale_adaptive=_flag(raw.get("depth_scale_adaptive"), False),
depth_threshold=min(max(_number(raw.get("depth_threshold"), 0.0), 0.0), 1000.0),
)
@@ -280,6 +280,38 @@ def _plate_mode_rgb(pixels: np.ndarray) -> np.ndarray:
return values[int(np.argmax(counts))].astype(float)
def test_depth_threshold_only_changes_depth_colours(gl_widget, qt_app):
from orisys_studio.scene3d.gl_backend import lut_rgba
widget, _, _ = gl_widget
assets = synthetic.poc_scene()
values = np.full(assets.shape_hw, 0.05, dtype=np.float32)
depth = FieldDescriptor("depth_response", "1", "approximate", False)
flow = FieldDescriptor("flow_magnitude", "px", "approximate", False)
def settings(descriptor, threshold):
return DisplaySettings(
housing_visible=False,
depth_threshold=threshold,
color_scale=ColorScale(
descriptor=descriptor, lower=0.0, upper=1.0, lut_id="sequential"
),
)
widget.set_settings(settings(depth, 0.0))
widget.present(_frame(assets, values, descriptor=depth, frame_id=30))
unfiltered = _plate_mean_rgb(_render(widget, qt_app))
widget.set_settings(settings(depth, 0.1))
filtered = _plate_mean_rgb(_render(widget, qt_app))
base = lut_rgba("sequential")[0, 0, :3].astype(float)
assert np.abs(filtered - base).max() < 8.0
assert np.abs(unfiltered - filtered).max() > 10.0
widget.set_settings(settings(flow, 0.1))
widget.present(_frame(assets, values, descriptor=flow, frame_id=31))
assert np.abs(_plate_mean_rgb(_render(widget, qt_app)) - unfiltered).max() < 8.0
def test_a_press_does_not_move_the_surfaces_base_colour(gl_widget, qt_app):
"""The base colour follows the field, not the frame's current response.
@@ -57,6 +57,7 @@ def test_the_pane_holds_a_legend_and_no_settings(panel):
panel.deform_check,
panel.gain_spin,
panel.flow_threshold_spin,
panel.depth_threshold_spin,
panel.scale_combo,
panel.depth_adaptive_check,
panel.fps_combo,
@@ -107,6 +108,14 @@ def test_adaptive_depth_scale_round_trips_through_the_dialog(panel):
assert panel.settings().depth_scale_adaptive is False
def test_depth_threshold_round_trips_through_the_dialog(panel):
from orisys_app_shared.scene3d.contracts import DisplaySettings
panel.apply_settings(DisplaySettings(depth_threshold=0.025))
assert panel.depth_threshold_spin.value() == pytest.approx(0.025)
assert panel.settings().depth_threshold == pytest.approx(0.025)
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
+1
View File
@@ -261,6 +261,7 @@ def test_shader_sources_are_two_dialects_of_one_body():
len(shader_header("gles30")):
]
assert "texture(u_lut" in desktop_fragment and "texture(u_lut" in es_fragment
assert "value < u_depth_threshold" in desktop_fragment
# The surface reads *two textures* and decides validity from one of them. There is
# no per-vertex scalar or validity attribute, and no ``isnan`` rule: the NaN that
# rule used to catch was produced by the CPU gather this path replaced.
+3
View File
@@ -474,6 +474,7 @@ def test_the_display_settings_round_trip():
flow_grid_spacing=12,
max_arrows=250,
depth_scale_adaptive=True,
depth_threshold=0.025,
color_scale=ColorScale(
descriptor=FieldDescriptor("flow_magnitude", "px", "approximate", False),
lower=0.25,
@@ -497,7 +498,9 @@ def test_the_display_settings_round_trip():
assert (restored.color_scale.lower, restored.color_scale.upper) == (0.25, 4.5)
assert restored.color_scale.mode == "fixed"
assert restored.depth_scale_adaptive is True
assert restored.depth_threshold == 0.025
assert scene3d_prefs.settings_from_prefs({}).depth_scale_adaptive is False
assert scene3d_prefs.settings_from_prefs({"depth_threshold": -1}).depth_threshold == 0.0
def test_a_saved_descriptor_is_never_restored_as_a_measurement():