Lint the commits being pushed and scope overlay failures per sensor

The pre-push gate linted only the dirty tree, so the commits about to be pushed went
unchecked; and every failed overlay projection was sticky, which cost a finger result its
overlay over one bad frame while box results were retried nowhere. gate.ps1 now takes
-LintBase (default origin/main, exit 2 on an unknown ref), the projector declares whether
its own failures are permanent, and serve_stored_view decides before it sets the view.
Verified with gate.ps1 (PASS: 569 studio + 485 shared tests, 632 ui_check checks) and the
two round-2 red samples kept in scratch.
This commit is contained in:
ykcheungab
2026-10-18 21:17:03 +08:00
parent 0998009ec3
commit c37efd7a44
9 changed files with 648 additions and 260 deletions
@@ -389,8 +389,13 @@ class AnalysisSession:
"""The live-shaped processed image for this camera frame, or ``None``.
Opens a session-scoped projector from the result's provenance on first use.
Failure is sticky for the session: overlay views then report capture
unavailable rather than falling back to a resized fisheye.
**How long a failure lasts is the projector's answer, not this method's guess:**
a projector that declares its projection failures permanent (a box stitch that
cannot rebuild the lenslet grid on this footage) is recorded as unavailable and
not asked again; one that declares them transient (a finger unroll that lost a
single frame) skips that frame and is tried again on the next. The alternative —
making every failure sticky — cost a finger result its whole overlay view over
one bad frame (reported as R2 in docs/test-repair-rework.zh.md).
"""
if self._overlay_failed:
return None
@@ -401,16 +406,11 @@ class AnalysisSession:
self.reader.provenance
)
if self._overlay is None:
# Nothing can be opened for this result at all: permanent either way.
self._overlay_failed = True
return None
projected = self._overlay.project(video_bgr, frame_id=frame_id)
if projected is None:
# Sticky, as the docstring above says. A projector that cannot map this
# result's footage — measured: every frame of a real box recording, where the
# stitch rebuild raises inside the SDK — will not start working mid-session,
# and retrying per cursor move would rebuild the same failing stitch on every
# scrub. The session records the answer so a caller can pick a view this
# result *can* draw instead of leaving a refusal on screen.
if projected is None and self._overlay.projection_failures_are_permanent:
self._overlay_failed = True
return projected
@@ -420,12 +420,12 @@ class AnalysisSession:
The overlay views (``arrows_stitched``, ``depth_arrows``) paint on the same
UV-unrolled or stitched picture live publishes, which a stored result does not
contain: it is rebuilt from the result's provenance on first use. A **box**
result has no stored unroll map, and the box stitch rebuild can fail on real
footage, so the answer is only known after one attempt — and it is then sticky
for the session (see :meth:`overlay_base`). The caller that *picks* the first
view for an adopted result reads this, so the operator is not left on a view
that cannot be drawn.
contain: it is rebuilt from the result's provenance on first use. The answer is
only known after an attempt, and it is sticky **only for a failure the projector
declares permanent** — a box result whose stitch cannot rebuild, or a projector
that would not open at all. A finger unroll that loses one frame is not sticky
(see :meth:`overlay_base`). The caller that *picks* the first view for an adopted
result reads this, so the operator is not left on a view that cannot be drawn.
"""
return bool(self._overlay_failed)
@@ -1034,13 +1034,21 @@ class ReplayController(QObject):
texture live uses. A derived selection the operator already chose is left
alone — that mode is not this method's to override.
**But only a view this result can actually draw.** The base is rebuilt from the
result's provenance, which a box result cannot always do (no stored unroll map,
and the box stitch rebuild can fail on real footage: measured, every frame of the
restored 33-frame box recording). Offering the overlay there would leave the
operator on a blank panel reading "capture unavailable" while the pure-colour
arrows sit one step away, so the fallback is the drawable view — the overlay is
still in the selector, and choosing it still says why it cannot be drawn.
**But only a view this result can actually draw, decided before it is set.** The
base is rebuilt from the result's provenance, which a box result cannot always do
(no stored unroll map, and the box stitch rebuild can fail on real footage:
measured, every frame of the restored 33-frame box recording). Offering the
overlay there would leave the operator on a blank panel reading "capture
unavailable" while the pure-colour arrows sit one step away, so the fallback is
the drawable view — the overlay is still in the selector, and choosing it still
says why it cannot be drawn.
The probe runs *before* the view is set (see :meth:`_overlay_base_ready`) so the
panel is set once and drawn once: setting the overlay view first and switching
after the first render flashed the refusal on screen during adoption (R3 of
docs/test-repair-rework.zh.md). The base it asks for is the one the render that
follows will ask for, and the projector caches by frame id, so deciding costs no
second projection.
"""
current = str(getattr(self._image_panel, "view_mode", lambda: "")() or "")
if current in DERIVED_MODES:
@@ -1048,16 +1056,29 @@ class ReplayController(QObject):
session = self.available_result()
# The plan for the frame just drawn offers the derived modes (the frame carries
# ``result_available``), so this selection is one the panel can honour.
overlay_failed = bool(getattr(session, "overlay_unavailable", False))
self._image_panel.set_view_mode(ARROW_PURE if overlay_failed else ARROW_STITCHED)
drawable = True if session is None else self._overlay_base_ready(session)
self._image_panel.set_view_mode(ARROW_STITCHED if drawable else ARROW_PURE)
self.render_frame()
if not overlay_failed and getattr(session, "overlay_unavailable", False):
# The first attempt is what tells us: the base could not be rebuilt, so draw
# the view that needs no base instead of leaving a refusal on screen.
self._image_panel.set_view_mode(ARROW_PURE)
self.render_frame()
return True
def _overlay_base_ready(self, session) -> bool:
"""Whether ``render_frame`` can paint this result's overlay base, without drawing.
Asks the session for the same base, for the same frame, that the render will ask
for. ``True`` when there is no frame to judge: an unknown answer must not invent a
fallback view the operator did not ask for. A session that has already declared the
overlay unavailable is answered without another projection attempt.
"""
if bool(getattr(session, "overlay_unavailable", False)):
return False
if self.analysis_active():
frame = self._engine.analysed_frame()
else:
frame = session.frame_at(self._engine.position())
if frame is None:
return True
return self._processed_base_for(frame, session) is not None
def _display_policy(self) -> Any:
from orisys_app_shared.scene3d.visual_policy import policy_for_settings