统一 2D/3D 深度色条为同一量程。
将 2D 闩锁的深度色彩区间推给 3D 自动着色,固定量程仍跟旋钮。用 test_shared_depth_scale 与深度闩锁冒烟用例验证。
This commit is contained in:
@@ -2331,6 +2331,7 @@ class StudioMainWindow(QMainWindow):
|
||||
self._active_calibration_id = ""
|
||||
self._depth_range_value = None
|
||||
self._last_depth_frame = None
|
||||
self._sync_shared_depth_scale(None)
|
||||
# The new session has not established a domain boundary yet; the previous
|
||||
# session's island must not survive into it (a different sensor is a different
|
||||
# island, and the latch compares against what it holds).
|
||||
@@ -2652,7 +2653,8 @@ class StudioMainWindow(QMainWindow):
|
||||
Re-fitting the range on every frame (the old behaviour) made colours
|
||||
drift, so two frames could look identical while holding different
|
||||
depths. Latching keeps frames comparable for the whole session; the
|
||||
numeric range is shown in the panel header.
|
||||
numeric range is shown in the panel header. The same span is handed to
|
||||
the 3D pane so both legends name one interval.
|
||||
"""
|
||||
if self._depth_range_value is not None:
|
||||
return self._depth_range_value
|
||||
@@ -2668,8 +2670,15 @@ class StudioMainWindow(QMainWindow):
|
||||
if hi <= lo:
|
||||
hi = lo + 1.0
|
||||
self._depth_range_value = (lo, hi)
|
||||
self._sync_shared_depth_scale(self._depth_range_value)
|
||||
return self._depth_range_value
|
||||
|
||||
def _sync_shared_depth_scale(self, span: tuple[float, float] | None) -> None:
|
||||
"""Push the latched 2D depth colour span to the 3D surface (or clear it)."""
|
||||
panel = getattr(self, "mesh3d_panel", None)
|
||||
if panel is not None and hasattr(panel, "set_shared_depth_scale"):
|
||||
panel.set_shared_depth_scale(span)
|
||||
|
||||
def _depth_scale_text(self, frame: ImageFrame | None = None) -> str:
|
||||
"""The colour scale, plus — when the frame's depth is exactly zero — why it is flat.
|
||||
|
||||
@@ -2795,8 +2804,12 @@ class StudioMainWindow(QMainWindow):
|
||||
boundary = self._latched_domain_mask(frame)
|
||||
self.arrow_panel.set_arrow_scale(arrow_display_scale(draw_style))
|
||||
self.arrow_panel.set_valid_area(frame.valid_mask is not None)
|
||||
if mode == DEPTH:
|
||||
# Latch (and sync to 3D) from any depth-carrying frame, not only when the
|
||||
# 2D mode is Depth — the surface may show depth while the canvas shows arrows.
|
||||
if frame.depth is not None:
|
||||
self._last_depth_frame = frame.depth
|
||||
self._depth_range()
|
||||
if mode == DEPTH:
|
||||
self.arrow_panel.set_depth_scale(self._depth_scale_text(frame))
|
||||
else:
|
||||
self.arrow_panel.set_depth_scale(None)
|
||||
|
||||
@@ -506,6 +506,12 @@ class ThreeDCoordinator:
|
||||
# held field frame so the operator sees the change without waiting.
|
||||
self._settings_dirty = True
|
||||
|
||||
def set_shared_depth_scale(self, span: tuple[float, float] | None) -> None:
|
||||
"""Adopt the 2D canvas's latched depth colour span for automatic colouring."""
|
||||
setter = getattr(self._widget, "set_shared_depth_scale", None)
|
||||
if callable(setter):
|
||||
setter(span)
|
||||
|
||||
def set_ramp(self, ramp: str) -> None:
|
||||
"""Hand the theme's colour ramp to the composer.
|
||||
|
||||
|
||||
@@ -359,6 +359,33 @@ def _scale_bounds(scale, automatic: tuple[float, float] | None) -> tuple[float,
|
||||
return lower, upper
|
||||
|
||||
|
||||
def _field_scalar_range(
|
||||
field: Any,
|
||||
*,
|
||||
previous: tuple[float, float] | None,
|
||||
shared_depth_scale: tuple[float, float] | None,
|
||||
) -> tuple[float, float] | None:
|
||||
"""The automatic colour span for the uploaded field.
|
||||
|
||||
Depth shares the 2D canvas's latched ``(lo, hi)`` when the window has one, so the
|
||||
two legends name the same interval. Flow (and depth before that latch exists) keep
|
||||
the measured P5/P95 blend.
|
||||
"""
|
||||
descriptor = getattr(field, "descriptor", None)
|
||||
quantity = str(getattr(descriptor, "quantity", "") or "")
|
||||
if quantity == "depth_response" and shared_depth_scale is not None:
|
||||
lo = float(shared_depth_scale[0])
|
||||
hi = float(shared_depth_scale[1])
|
||||
if hi > lo and not (math.isnan(lo) or math.isnan(hi)):
|
||||
return lo, hi
|
||||
measured = _valid_scalar_range(
|
||||
field.scalar,
|
||||
field.valid,
|
||||
signed=bool(getattr(descriptor, "signed", False)),
|
||||
)
|
||||
return _blend_scale_range(previous, measured)
|
||||
|
||||
|
||||
def lut_rgba(lut_id: str = "sequential", ramp: str = "") -> np.ndarray:
|
||||
"""A 256x1 RGBA8 lookup table, built from the **one** ramp both views share.
|
||||
|
||||
@@ -667,6 +694,9 @@ 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
|
||||
#: 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
|
||||
#: The colour bounds of the last draw and how they were chosen.
|
||||
self._last_scale: tuple[float, float] | None = None
|
||||
self._last_scale_mode = ""
|
||||
@@ -789,10 +819,11 @@ class GLRenderer:
|
||||
GL.glBindBuffer(GL.GL_ARRAY_BUFFER, 0)
|
||||
self._upload_field_textures(field)
|
||||
self._last_upload_ms = (time.perf_counter() - started) * 1000.0
|
||||
measured = _valid_scalar_range(
|
||||
field.scalar, field.valid, signed=bool(field.descriptor.signed)
|
||||
self._scalar_range = _field_scalar_range(
|
||||
field,
|
||||
previous=self._scalar_range,
|
||||
shared_depth_scale=self._shared_depth_scale,
|
||||
)
|
||||
self._scalar_range = _blend_scale_range(self._scalar_range, measured)
|
||||
self._upload_overlays(frame)
|
||||
|
||||
def _upload_field_textures(self, field: Any) -> None:
|
||||
@@ -987,7 +1018,16 @@ class GLRenderer:
|
||||
view = np.asarray(camera.view, dtype=np.float32).reshape(4, 4)
|
||||
projection = np.asarray(camera.projection, dtype=np.float32).reshape(4, 4)
|
||||
scale = settings.color_scale
|
||||
lower, upper = _scale_bounds(scale, self._scalar_range)
|
||||
# Prefer the 2D latched span at draw time, not only at upload: the latch can
|
||||
# arrive after this field was uploaded, and a repaint must still equalise.
|
||||
automatic = self._scalar_range
|
||||
descriptor = getattr(scale, "descriptor", None) if scale is not None else None
|
||||
if (
|
||||
self._shared_depth_scale is not None
|
||||
and str(getattr(descriptor, "quantity", "") or "") == "depth_response"
|
||||
):
|
||||
automatic = self._shared_depth_scale
|
||||
lower, upper = _scale_bounds(scale, automatic)
|
||||
# What the shader was actually given, for the legend: in automatic mode the
|
||||
# operator's spin boxes are disabled and hold values nobody is using, so the
|
||||
# panel has to be able to ask what the picture means.
|
||||
@@ -1061,6 +1101,17 @@ class GLRenderer:
|
||||
"""The colour bounds the last draw actually used (``None`` before any draw)."""
|
||||
return self._last_scale
|
||||
|
||||
def set_shared_depth_scale(self, span: tuple[float, float] | None) -> None:
|
||||
"""Adopt the 2D canvas's latched depth colour span, or clear it."""
|
||||
if span is None:
|
||||
self._shared_depth_scale = None
|
||||
return
|
||||
lo, hi = float(span[0]), float(span[1])
|
||||
if hi <= lo or math.isnan(lo) or math.isnan(hi):
|
||||
self._shared_depth_scale = None
|
||||
return
|
||||
self._shared_depth_scale = (lo, hi)
|
||||
|
||||
def set_ramp(self, ramp: str) -> None:
|
||||
"""Adopt the theme's ramp. Rebuilds the LUT at the next draw, once.
|
||||
|
||||
|
||||
@@ -107,6 +107,8 @@ class Scene3DView(QWidget):
|
||||
#: they cannot drift apart. ``None`` means "no session": the adapter then uses
|
||||
#: each frame's own mask, which is what the tests and the PoC want.
|
||||
self._domain_latch: Any = None
|
||||
#: The 2D canvas's latched depth colour span, held until the coordinator exists.
|
||||
self._shared_depth_scale: tuple[float, float] | None = None
|
||||
self._expected: ExpectedGeometry | None = None
|
||||
self._snapshot: Callable[[Any, FrameIdentity], Any] | None = None
|
||||
#: The adapter behind ``_snapshot``: it owns the reason a packet was refused,
|
||||
@@ -148,6 +150,8 @@ class Scene3DView(QWidget):
|
||||
self._coordinator = ThreeDCoordinator(
|
||||
self._widget, self._composer, self._executor, max_fps=30
|
||||
)
|
||||
if self._shared_depth_scale is not None:
|
||||
self._coordinator.set_shared_depth_scale(self._shared_depth_scale)
|
||||
self._panel = ThreeDPanel(self._coordinator, self._widget)
|
||||
# The picture must not be squeezed out of existence by the controls above it:
|
||||
# a GL widget with zero height draws nothing and looks like a broken view.
|
||||
@@ -313,6 +317,19 @@ class Scene3DView(QWidget):
|
||||
if self._adapter is not None and hasattr(self._adapter, "domain_latch"):
|
||||
self._adapter.domain_latch = latch
|
||||
|
||||
def set_shared_depth_scale(self, span: tuple[float, float] | None) -> None:
|
||||
"""Share the 2D canvas's latched depth colour span with this pane.
|
||||
|
||||
Automatic depth colouring on the surface uses this interval so the 3D legend
|
||||
names the same ``(lo, hi)`` as the 2D depth scale. Fixed mode still follows the
|
||||
operator's spin boxes.
|
||||
"""
|
||||
self._shared_depth_scale = None if span is None else (float(span[0]), float(span[1]))
|
||||
if self._coordinator is not None and hasattr(
|
||||
self._coordinator, "set_shared_depth_scale"
|
||||
):
|
||||
self._coordinator.set_shared_depth_scale(self._shared_depth_scale)
|
||||
|
||||
def set_theme(self, theme: str) -> None:
|
||||
"""Adopt the application theme's colour ramp.
|
||||
|
||||
|
||||
@@ -94,6 +94,13 @@ class Sensor3DWidget(QOpenGLWidget):
|
||||
self._rejected_frames = 0
|
||||
|
||||
# ------------------------------------------------------------- CPU-side API
|
||||
def set_shared_depth_scale(self, span: tuple[float, float] | None) -> None:
|
||||
"""Hand the 2D canvas's latched depth colour span to the GL renderer."""
|
||||
setter = getattr(self._renderer, "set_shared_depth_scale", None)
|
||||
if callable(setter):
|
||||
setter(span)
|
||||
self.update()
|
||||
|
||||
def set_scene(self, assets: SceneAssets) -> None:
|
||||
"""Retain the asset and schedule its upload for the next current context."""
|
||||
self._assets = assets
|
||||
|
||||
@@ -114,6 +114,8 @@ class Mesh3dPanel(QFrame):
|
||||
#: The session's island latch, shared with the 2D canvas (see
|
||||
#: ``set_domain_latch``). Held until the view exists.
|
||||
self._domain_latch = None
|
||||
#: The 2D canvas's latched depth colour span (see ``set_shared_depth_scale``).
|
||||
self._shared_depth_scale: tuple[float, float] | None = None
|
||||
|
||||
# Reserve this top-level window's native handle *now*, before the window is
|
||||
# shown and while no OpenGL widget exists yet.
|
||||
@@ -227,6 +229,12 @@ class Mesh3dPanel(QFrame):
|
||||
if self._view is not None and hasattr(self._view, "set_theme"):
|
||||
self._view.set_theme(self._theme)
|
||||
|
||||
def set_shared_depth_scale(self, span: tuple[float, float] | None) -> None:
|
||||
"""Share the 2D depth colour span with the 3D surface (and hold it until built)."""
|
||||
self._shared_depth_scale = None if span is None else (float(span[0]), float(span[1]))
|
||||
if self._view is not None and hasattr(self._view, "set_shared_depth_scale"):
|
||||
self._view.set_shared_depth_scale(self._shared_depth_scale)
|
||||
|
||||
def set_domain_latch(self, latch) -> None:
|
||||
"""Share the 2D canvas's island latch with the 3D pane (see ``Scene3DView``).
|
||||
|
||||
@@ -321,6 +329,8 @@ class Mesh3dPanel(QFrame):
|
||||
view.set_theme(self._theme)
|
||||
if self._domain_latch is not None and hasattr(view, "set_domain_latch"):
|
||||
view.set_domain_latch(self._domain_latch)
|
||||
if hasattr(view, "set_shared_depth_scale"):
|
||||
view.set_shared_depth_scale(self._shared_depth_scale)
|
||||
if self._pending_settings is not None:
|
||||
view.restore_settings(self._pending_settings)
|
||||
# The kind was decided before the view existed (the window sets it when the
|
||||
|
||||
Reference in New Issue
Block a user