fix(studio): 统一 Box/Mini 箭头网格与强度着色

This commit is contained in:
ykcheungab
2026-10-10 16:07:26 +08:00
parent 48de5b74e6
commit f94b51e2de
4 changed files with 47 additions and 12 deletions
+7 -8
View File
@@ -1734,16 +1734,15 @@ class StudioMainWindow(QMainWindow):
return wanted
def _arrow_policy(self):
"""The one flow-arrow policy the 2D canvas and the 3D surface both sample with.
"""Use the box recording grid or the finger's shared 2D/3D display policy.
The thresholds and the sample grid are display settings (the pane's), so the
policy is built from them here and handed to the 2D renderer; the 3D composer
builds the same value from the same settings object
(``scene3d.composer`` via ``visual_policy.policy_for_settings``). That is what
makes the two figures trigger at the same strength: previously each path built
its own policy *and* the 3D one applied a hysteresis the canvas did not, so the
same frame could carry arrows in one figure and none in the other.
Box and mini analysis store samples on the SDK box grid; live rendering must
use that same grid. Finger keeps one policy for its 2D and 3D views.
"""
if self._draw_style() != "finger":
from orisys_app_shared.acquisition.arrows import sdk_box_policy
return sdk_box_policy()
from orisys_app_shared.scene3d.visual_policy import policy_for_settings
return policy_for_settings(self.mesh3d_panel.display_settings())
+9
View File
@@ -2424,6 +2424,15 @@ def test_the_two_figures_are_drawn_from_one_arrow_policy(make_window, monkeypatc
window.close()
@pytest.mark.parametrize("sensor_type", ["box", "mini"])
def test_box_family_live_uses_the_recorded_sample_grid(make_window, sensor_type):
from orisys_app_shared.acquisition.arrows import sdk_box_policy
window = _show(make_window(sensor_type=sensor_type), (1440, 880))
assert window._arrow_policy() == sdk_box_policy()
window.close()
def test_a_bare_video_plays_even_though_it_is_not_a_recording(
make_window, record_root, decline_video_prompt
):
@@ -23,7 +23,7 @@ FLOW_IDLE_HEX = "#8A93A6"
#: BGR grey used for depth samples that carry no value.
NO_DEPTH_BGR = (0x8A, 0x8A, 0x8A)
#: The fixed flow-magnitude colour range, in **uv pixels of displacement**.
#: Finger's fixed flow-magnitude colour range, in **uv pixels of displacement**.
#:
#: The colour of an arrow is a statement about its magnitude, so it has to be the same
#: statement from one frame to the next: a per-frame range makes the same magnitude a
@@ -34,6 +34,8 @@ NO_DEPTH_BGR = (0x8A, 0x8A, 0x8A)
#: where the arrow itself does. A pane that offers the operator a colour range owns this
#: value and passes it as ``magnitude_range`` to :func:`draw_arrow_display`.
FLOW_MAGNITUDE_RANGE: tuple[float, float] = (0.0, 2.0)
#: Box/Mini use a 2 px activation floor, so their fixed range extends to 8 px.
BOX_FLOW_MAGNITUDE_RANGE: tuple[float, float] = (0.0, 8.0)
#: Whether an inactive sample is drawn as a quiet dot. **On**, which is the decided
#: display behaviour: a sample below the display threshold is a statement about the
@@ -498,8 +500,10 @@ def draw_arrow_display(
finger style falls back to the finger defaults and box style to :func:`sdk_box_policy`.
``magnitude_range`` fixes the colour scale, in the same unit as ``flow_on_px``
(uv pixels of displacement); the default is :data:`FLOW_MAGNITUDE_RANGE` and a
settings model that owns the range passes its own here. ``per_frame_colour`` opts
(uv pixels of displacement). The default is :data:`FLOW_MAGNITUDE_RANGE` for
finger and :data:`BOX_FLOW_MAGNITUDE_RANGE` for box/mini; their different
sampling floors need different fixed ranges to retain intensity contrast.
A settings model that owns the range passes its own here. ``per_frame_colour`` opts
back in to normalising every frame against its own maximum, which makes one
magnitude a different colour from one frame to the next.
@@ -570,7 +574,11 @@ def draw_arrow_display(
policy=selected,
colormap=colormap,
below_threshold_color=tuple(draw_kw["below_threshold_color"]),
magnitude_range=magnitude_range,
magnitude_range=(
magnitude_range
if magnitude_range is not None
else FLOW_MAGNITUDE_RANGE if finger else BOX_FLOW_MAGNITUDE_RANGE
),
per_frame_colour=per_frame_colour,
# Read here rather than as a parameter default: the module constant is
# the display decision, and a caller that flips it (a build, a test)
+19
View File
@@ -1080,3 +1080,22 @@ def test_a_box_result_on_the_sdk_grid_matches_the_live_painter():
)
assert np.array_equal(np.asarray(drawn), np.asarray(live))
assert _painted(drawn) > 0
@pytest.mark.parametrize("draw_style", ["box", "mini"])
def test_box_family_keeps_sparse_idle_dots_and_distinct_motion_colours(draw_style):
from orisys_app_shared.acquisition.arrows import FLOW_IDLE_HEX, hex_to_bgr, sdk_box_policy
policy = sdk_box_policy()
base = np.zeros((100, 120, 3), np.uint8)
flow = np.zeros((100, 120, 2), np.float32)
flow[50, 50, 0] = 1.25 # 2.5 px displacement, just over the box floor.
flow[50, 90, 0] = 3.0 # 6 px displacement on the same frame.
drawn = draw_arrow_display(
base, flow, draw_style=draw_style, policy=policy,
colormap=flow_colormap("dark"),
)
tint = np.array(hex_to_bgr(FLOW_IDLE_HEX), np.uint8)
assert _marker_is_there(drawn, 30, 30, tint)
assert not _marker_is_there(drawn, 50, 50, tint)
assert not np.array_equal(drawn[50, 50], drawn[50, 90])