fix(studio): 统一 Box/Mini 箭头网格与强度着色
This commit is contained in:
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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])
|
||||
|
||||
Reference in New Issue
Block a user