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
@@ -22,11 +22,23 @@ import numpy as np
logger = logging.getLogger(__name__)
__all__ = ["OverlayBaseProjector"]
__all__ = ["OverlayBaseProjector", "projection_failures_are_permanent"]
_CWD_LOCK = threading.Lock()
def projection_failures_are_permanent(sensor_type: str) -> bool:
"""Whether a failed projection for this sensor is worth retrying on the next frame.
Only the finger unroll has a per-frame remap, so one miss is that frame's problem. A
box stitch failure is a property of the footage — the lenslet grid either converges
on these pixels or it does not — so retrying it per cursor move rebuilds the same
failure. This is the one place the policy is stated; both openers read it, and the
analysis session reads the projector's declaration instead of deciding again.
"""
return str(sensor_type) != "finger"
def _ensure_bgr(image: np.ndarray) -> np.ndarray:
if image.ndim == 2:
return cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
@@ -38,7 +50,15 @@ def _ensure_bgr(image: np.ndarray) -> np.ndarray:
class OverlayBaseProjector:
"""Maps one camera frame onto the measurement domain for overlay views."""
"""Maps one camera frame onto the measurement domain for overlay views.
``projection_failures_are_permanent`` says what a failed projection *means*, because
only the projector knows: a finger unroll can lose one frame (a transient remap
miss) and be fine on the next, while a box stitch that cannot rebuild the lenslet
grid will not start working mid-session. The consumer cannot tell from the returned
``None``, and must not try to infer it from an exception's text — it asks this
declaration instead (see ``AnalysisSession.overlay_base``).
"""
def __init__(
self,
@@ -47,11 +67,14 @@ class OverlayBaseProjector:
project,
close=None,
working: Path | None = None,
projection_failures_are_permanent: bool = True,
) -> None:
self.sensor_type = str(sensor_type)
self._project = project
self._close = close
self._working = None if working is None else Path(working)
#: Default True: a projector only gets to be "retry next frame" if it says so.
self.projection_failures_are_permanent = bool(projection_failures_are_permanent)
self._cache_id: int | None = None
self._cache: np.ndarray | None = None
@@ -124,7 +147,13 @@ class OverlayBaseProjector:
return None
return _ensure_bgr(unrolled)
return cls(sensor_type="finger", project=project)
# One frame the unroll cannot map is that frame's problem: the next camera frame
# has its own remap, so the session keeps offering the overlay (reported as R2).
return cls(
sensor_type="finger",
project=project,
projection_failures_are_permanent=projection_failures_are_permanent("finger"),
)
@classmethod
def _open_box(cls, *, config_ref: str | None) -> OverlayBaseProjector | None:
@@ -163,7 +192,15 @@ class OverlayBaseProjector:
logger.debug("box overlay sensor disconnect skipped", exc_info=True)
shutil.rmtree(working, ignore_errors=True)
return cls(sensor_type="box", project=project, close=close, working=working)
# A box stitch that fails once fails for this footage: the lenslet grid is a
# property of the recording, so retrying per scrub rebuilds the same failure.
return cls(
sensor_type="box",
project=project,
close=close,
working=working,
projection_failures_are_permanent=projection_failures_are_permanent("box"),
)
def project(
self, video_bgr: np.ndarray, *, frame_id: int | None = None