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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user