修复 Studio 失败测试并新增合并门禁

把丢失的 box 录像放回 apps/studio/tests/data/,修掉第 3 节 3–10 号与两处过时测试桩,
注册 timing 标记并让根目录收集零错误;新增 tools/gate.ps1 与可选的 pre-push 钩子。
验证:studio 524 通过 19 跳过、shared 467 通过 18 跳过、ui_check 632 项 0 失败、gate.ps1 退出码 0。
细节见 docs/test-repair-plan.zh.md。
This commit is contained in:
ykcheungab
2026-10-18 19:24:31 +08:00
parent dcabd547fa
commit 433b76e43c
38 changed files with 1036 additions and 171 deletions
@@ -403,7 +403,31 @@ class AnalysisSession:
if self._overlay is None:
self._overlay_failed = True
return None
return self._overlay.project(video_bgr, frame_id=frame_id)
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.
self._overlay_failed = True
return projected
@property
def overlay_unavailable(self) -> bool:
"""Whether this result has been asked for an overlay base and could not give one.
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.
"""
return bool(self._overlay_failed)
def close(self) -> None:
"""Release the overlay projector; the reader is closed by :class:`ResultLibrary`."""
@@ -29,6 +29,7 @@ from typing import Any
import numpy as np
from orisys_app_shared.acquisition.image_backend import (
ARROW_PURE,
ARROW_STITCHED,
DERIVED_MODES,
RESULT_FAILED,
@@ -1032,14 +1033,29 @@ class ReplayController(QObject):
The first processed view is arrows over the unrolled / stitched base: the same
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.
"""
current = str(getattr(self._image_panel, "view_mode", lambda: "")() or "")
if current in DERIVED_MODES:
return False
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.
self._image_panel.set_view_mode(ARROW_STITCHED)
overlay_failed = bool(getattr(session, "overlay_unavailable", False))
self._image_panel.set_view_mode(ARROW_PURE if overlay_failed else ARROW_STITCHED)
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 _display_policy(self) -> Any: