fix Studio reset, 3D base colour and arrow-trigger defects

The tracker reset was decided by which panel was on screen rather than by the
session, the automatic colour range moved the 3D surface's base colour on the first
press, and the 2D canvas and the 3D overlay applied different flow thresholds (622
arrows against 432 on a real recording). ui_check 600/0; apps/studio 380 items and
shared 390 items with no failures; real-GL 18 items, only the pre-existing
colour-scale failure; AutoCal 358 passed; both views now select identical arrows on
all 42 frames of a real video.
This commit is contained in:
ykcheungab
2026-10-01 14:40:33 +08:00
parent 33a3d8c9c2
commit e0156a14c6
14 changed files with 446 additions and 72 deletions
+56 -22
View File
@@ -3,7 +3,6 @@
from __future__ import annotations
import numpy as np
from orisys_app_shared.acquisition.arrows import _policy_from_style
from orisys_app_shared.scene3d.composer import SceneComposer
from orisys_app_shared.scene3d.contracts import (
DisplaySettings,
@@ -12,7 +11,10 @@ from orisys_app_shared.scene3d.contracts import (
freeze,
)
from orisys_app_shared.scene3d.synthetic import poc_identity, poc_scene
from orisys_app_shared.scene3d.visual_policy import VisualPolicy, select_flow_samples
from orisys_app_shared.scene3d.visual_policy import (
policy_for_settings,
select_flow_samples,
)
def _depth_frame(assets, *, flow=None, contact_xy=None, fn=0.0):
@@ -81,10 +83,20 @@ def test_composer_hides_overlays_when_disabled() -> None:
def test_2d_3d_flow_sample_points_match() -> None:
"""Both figures show the same arrows for the same frame — hysteresis included.
The regression: the composer kept a "was this sample on last frame" grid and the 2D
canvas did not, so a sample between ``flow_off_px`` and ``flow_on_px`` was drawn by
one view and not the other. Measured on a real recording before the fix: 622 arrows
in the 3D overlay against 432 in the canvas, differing on 34 of 41 frames.
Frame A is above ``flow_on_px`` (both views draw it); frame B is inside the band —
above the old hysteresis floor, below the threshold the canvas applies. Both views
must agree on frame B, which is what fails if the composer re-acquires state the
canvas does not have.
"""
assets = poc_scene()
h, w = assets.shape_hw
flow = np.zeros((h, w, 2), dtype=np.float64)
flow[1, 1] = (0.7, 0.2)
mask = np.ones((h, w), dtype=bool)
settings = DisplaySettings(
flow_on_px=0.4,
@@ -92,23 +104,45 @@ def test_2d_3d_flow_sample_points_match() -> None:
flow_grid_spacing=1,
max_arrows=50,
)
policy_3d = _policy_from_style(
"finger",
flow_on_px=settings.flow_on_px,
flow_off_px=settings.flow_off_px,
flow_grid_spacing=settings.flow_grid_spacing,
max_arrows=settings.max_arrows,
policy = policy_for_settings(settings)
composer = SceneComposer()
def both_views(flow_value: float) -> tuple[int, int]:
flow = np.zeros((h, w, 2), dtype=np.float64)
flow[1, 1] = (flow_value, 0.0)
composed = composer.compose(
assets, _depth_frame(assets, flow=flow), settings
)
arrows_3d = (
0 if composed.flow_arrows is None else int(composed.flow_arrows.origins.shape[0])
)
# Exactly what the 2D canvas draws from: ``draw_arrow_display`` calls
# ``select_flow_samples`` with the policy the window passes it.
samples_2d = select_flow_samples(flow, mask, policy)
return arrows_3d, int(samples_2d.rows.size)
above, above_2d = both_views(0.30) # |2*flow| = 0.6 px, clearly on
assert above == 1 and above_2d == 1, (above, above_2d)
inside_band, inside_band_2d = both_views(0.175) # |2*flow| = 0.35 px: 0.3 < 0.35 < 0.4
assert inside_band_2d == 0, inside_band_2d
assert inside_band == inside_band_2d, (
"the 3D overlay drew an arrow the 2D canvas did not: the two views are not "
f"triggering at the same strength ({inside_band} against {inside_band_2d})"
)
policy_shared = VisualPolicy(
flow_on_px=settings.flow_on_px,
flow_off_px=settings.flow_off_px,
flow_grid_spacing=settings.flow_grid_spacing,
max_arrows=settings.max_arrows,
flow_edge_margin=0,
).normalised()
samples_2d = select_flow_samples(flow, mask, policy_shared)
samples_3d = select_flow_samples(flow, mask, policy_shared)
assert list(zip(samples_2d.rows, samples_2d.cols)) == list(
zip(samples_3d.rows, samples_3d.cols)
def test_both_views_build_their_policy_from_the_same_settings() -> None:
"""One mapping, one policy: the thresholds are display settings, not per-view ones."""
settings = DisplaySettings(
flow_on_px=0.7,
flow_off_px=0.2,
flow_grid_spacing=6,
max_arrows=123,
)
assert policy_3d.flow_on_px == settings.flow_on_px
policy = policy_for_settings(settings)
assert (policy.flow_on_px, policy.flow_off_px) == (0.7, 0.2)
assert policy.flow_grid_spacing == 6
assert policy.max_arrows == 123
# Normalised, so a hand-edited preference cannot invert the hysteresis band.
inverted = policy_for_settings(DisplaySettings(flow_on_px=0.1, flow_off_px=0.9))
assert inverted.flow_on_px >= inverted.flow_off_px