Merge branch 'main' of ssh://892887cvmk08.aicp.io:24928/orisys-tech/orisys-app
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user