fixed display problem
This commit is contained in:
@@ -30,6 +30,7 @@ from orisys_app_shared.acquisition.image_backend import (
|
||||
SOURCE_LIVE,
|
||||
SOURCE_PREVIEW,
|
||||
SOURCE_RECOMPUTE,
|
||||
DomainMaskLatch,
|
||||
ImageFrame,
|
||||
plan_image,
|
||||
render_image,
|
||||
@@ -222,6 +223,11 @@ class StudioMainWindow(QMainWindow):
|
||||
self._last_calibration_export = ""
|
||||
self._depth_range_value: tuple[float, float] | None = None
|
||||
self._last_depth_frame = None
|
||||
#: The 2D domain boundary, latched for the session the way the depth range is:
|
||||
#: the island is the device's own geometry and is rebuilt from the same remap
|
||||
#: every frame, so re-deriving the edge per frame redrew a boundary that had not
|
||||
#: moved (see ``image_backend.DomainMaskLatch``).
|
||||
self._domain_mask = DomainMaskLatch()
|
||||
|
||||
icon = QIcon(str(logo_path()))
|
||||
if not icon.isNull():
|
||||
@@ -2028,6 +2034,10 @@ class StudioMainWindow(QMainWindow):
|
||||
self._last_display_available = None
|
||||
self._depth_range_value = None
|
||||
self._last_depth_frame = 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).
|
||||
self._domain_mask.reset()
|
||||
# The session's own bookkeeping (its counter and telemetry) is the
|
||||
# controller's: telling it a run starts is what drops the previous run's
|
||||
# numbers, so none of them can be read as this run's.
|
||||
@@ -2461,6 +2471,19 @@ class StudioMainWindow(QMainWindow):
|
||||
return ""
|
||||
return t("panels.depth_scale", low=f"{span[0]:.3f}", high=f"{span[1]:.3f}")
|
||||
|
||||
def _latched_domain_mask(self, frame: "ImageFrame"):
|
||||
"""The 2D domain boundary to draw, latched for the session.
|
||||
|
||||
Exactly the treatment ``_depth_range`` gives the colour scale, and for the same
|
||||
reason: the boundary is not a per-frame measurement. It is the device's own UV
|
||||
island — geometry, rebuilt from the same remap on every frame — and the depth
|
||||
view paints it as its "no depth" grey, so rebuilding it per frame redrew an edge
|
||||
that had not moved while any difference in ``frame.valid_mask`` repainted the
|
||||
whole boundary, which reads as lag. The latch holds the boundary until a frame
|
||||
no longer describes the same island (see ``DomainMaskLatch``).
|
||||
"""
|
||||
return self._domain_mask.update(frame.valid_mask)
|
||||
|
||||
def _replay_owns_image(self) -> bool:
|
||||
"""True while the 2D panel shows the replay, not the live camera.
|
||||
|
||||
@@ -2518,7 +2541,10 @@ class StudioMainWindow(QMainWindow):
|
||||
draw_style = self._draw_style()
|
||||
# The legend is decided by the same inputs as the picture: the arrow
|
||||
# magnification of this sensor's draw style, and whether this frame
|
||||
# carries a valid-area mask.
|
||||
# carries a valid-area mask. It stays the *frame's* statement rather than the
|
||||
# latched boundary's, because that is what the chip claims: the arrows are
|
||||
# gated by this frame's own mask on every path, latched boundary or not.
|
||||
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:
|
||||
@@ -2534,6 +2560,7 @@ class StudioMainWindow(QMainWindow):
|
||||
draw_style=draw_style,
|
||||
depth_range=self._depth_range(),
|
||||
canvas_bgr=hex_to_bgr(color(self._theme, "arrow_canvas", "#0B1830")),
|
||||
domain_mask=boundary,
|
||||
)
|
||||
if rendered is None:
|
||||
# Never substitute another picture here: a missing depth map that showed
|
||||
|
||||
@@ -925,6 +925,59 @@ def test_depth_view_reports_missing_data_and_fixes_its_scale(make_window):
|
||||
_finish(window, stuck)
|
||||
|
||||
|
||||
def test_the_domain_boundary_is_latched_for_the_session(make_window):
|
||||
"""The 2D boundary is the device's island: rebuilt per frame, decided once.
|
||||
|
||||
Same treatment as the depth colour scale above, and for the same reason — a mask
|
||||
that is re-derived every frame redraws an edge that has not moved, and any pixel of
|
||||
difference repaints the whole boundary (measured: a 0.73% area wobble repaints 69%
|
||||
of the boundary band).
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
window, stuck = _running_window(make_window)
|
||||
window.arrow_panel.set_view_mode("arrows_pure")
|
||||
island = np.zeros((8, 8), dtype=bool)
|
||||
island[1:7, 1:7] = True
|
||||
island_wobble = island.copy()
|
||||
island_wobble[1, 1] = False # one pixel of rim, i.e. 1.6% of a 36-pixel island
|
||||
|
||||
window._last_rendered_view_mode = None
|
||||
stuck.emit(ui_check._fake_packet(0, valid_mask=island))
|
||||
window._on_run_tick()
|
||||
assert np.array_equal(window._domain_mask.mask, island), (
|
||||
"the session never established the device's island"
|
||||
)
|
||||
|
||||
window._last_rendered_view_mode = None
|
||||
stuck.emit(ui_check._fake_packet(1, valid_mask=island_wobble))
|
||||
window._on_run_tick()
|
||||
assert np.array_equal(window._domain_mask.mask, island), (
|
||||
"a rebuilt mask moved the session's boundary"
|
||||
)
|
||||
assert (window._domain_mask.adoptions, window._domain_mask.held) == (1, 1)
|
||||
|
||||
# An island that really changed is adopted rather than frozen out.
|
||||
moved = np.zeros((8, 8), dtype=bool)
|
||||
moved[2:8, 2:8] = True
|
||||
window._last_rendered_view_mode = None
|
||||
stuck.emit(ui_check._fake_packet(2, valid_mask=moved))
|
||||
window._on_run_tick()
|
||||
assert np.array_equal(window._domain_mask.mask, moved), (
|
||||
"a moved island kept the old boundary on screen"
|
||||
)
|
||||
assert window._domain_mask.adoptions == 2
|
||||
|
||||
# A new run is a new session: the previous island must not survive into it.
|
||||
_finish(window, stuck)
|
||||
window._start_run()
|
||||
assert window._domain_mask.mask is None, (
|
||||
"the previous session's island survived into a new run"
|
||||
)
|
||||
window._run_timer.stop()
|
||||
window._stop_run(reason="user_stop")
|
||||
|
||||
|
||||
# ------------------------------------------------------------- experiment record
|
||||
def test_recording_writes_the_documented_experiment_layout(make_window, record_root):
|
||||
import csv
|
||||
|
||||
@@ -177,6 +177,7 @@ def _fake_packet(
|
||||
mono_t: float | None = None,
|
||||
shear_n: float | None = None,
|
||||
frame=None,
|
||||
valid_mask=None,
|
||||
):
|
||||
"""A packet shaped like the real worker's, for the record-channel checks.
|
||||
|
||||
@@ -213,6 +214,9 @@ def _fake_packet(
|
||||
invalid_reason=reason,
|
||||
raw_available=True,
|
||||
depth=depth,
|
||||
# The device's own UV-island mask, which the real workers pass through from the
|
||||
# runtime (``runtime.valid_mask``); the 2D boundary is drawn from it.
|
||||
valid_mask=valid_mask,
|
||||
# Per-field provenance: a producer has to say whether the arrays it hands over
|
||||
# are this frame's own (the 3D sink refuses an unstated field rather than
|
||||
# colouring something it cannot claim). A failed frame is stale here, exactly
|
||||
|
||||
Reference in New Issue
Block a user