Merge branch 'main' of ssh://892887cvmk08.aicp.io:24928/orisys-tech/orisys-app
This commit is contained in:
@@ -582,28 +582,25 @@ def test_the_idle_dot_switch_hides_the_dots_and_leaves_the_arrows_alone():
|
||||
|
||||
|
||||
def test_the_idle_marker_is_the_marker_a_box_sample_gets():
|
||||
"""One marker shape and one tint across sensor types.
|
||||
"""One marker shape and one tint across sensor styles.
|
||||
|
||||
Box and Mini draw through the SDK's ``orisys.util.draw_arrows``, which paints a
|
||||
below-threshold sample with ``cv2.circle(radius=1, thickness=-1)`` in the same
|
||||
``below_threshold_color`` this module passes it. The finger canvas has to put the same
|
||||
blob on the same kind of location, or a quiet sample would look like two different
|
||||
things depending on the sensor; this compares the two paths' pixels for one
|
||||
below-threshold sample.
|
||||
Box and finger both use the shared per-sample painter. With the same policy they
|
||||
must put the same plus-shaped idle blob on the same quiet sample.
|
||||
"""
|
||||
pytest.importorskip("orisys", reason="the SDK path needs the SDK")
|
||||
from orisys_app_shared.acquisition.arrows import FLOW_IDLE_HEX, hex_to_bgr
|
||||
from orisys_app_shared.scene3d.visual_policy import VisualPolicy
|
||||
|
||||
# One candidate location, shared by both paths: the box draw's own grid is 20 px with
|
||||
# a 15 px edge margin, so a 60x60 frame has exactly the (30, 30) sample.
|
||||
# One candidate location on a 20 px grid with a 15 px edge: a 60x60 frame has
|
||||
# exactly the (30, 30) sample inside the margin.
|
||||
size = 60
|
||||
flow = np.zeros((size, size, 2), dtype=np.float64)
|
||||
flow[30, 30] = (0.1, 0.0) # 0.2 px: under both paths' thresholds
|
||||
flow[30, 30] = (0.1, 0.0) # 0.2 px: under the threshold
|
||||
canvas = _flat_canvas(size, size)
|
||||
policy = VisualPolicy(flow_on_px=0.4, flow_grid_spacing=20, flow_edge_margin=15)
|
||||
|
||||
box = draw_arrow_display(canvas, flow, background="captured", draw_style="box")
|
||||
box = draw_arrow_display(
|
||||
canvas, flow, background="captured", draw_style="box", policy=policy
|
||||
)
|
||||
finger = draw_arrow_display(
|
||||
canvas, flow, background="captured", draw_style="finger", policy=policy
|
||||
)
|
||||
@@ -970,19 +967,17 @@ def _painted(image, canvas=(0x16, 0x13, 0x12)) -> int:
|
||||
|
||||
|
||||
def test_a_recovered_field_is_drawn_by_the_shared_painter_whatever_the_style_is():
|
||||
"""Where a frame came from decides the painter, not which sensor's style is selected.
|
||||
"""Live and analysis sources use the same painter on a sparse recovered field.
|
||||
|
||||
The SDK painter walks the dense sampling grid, so on a field that is finite only at a
|
||||
stored result's candidate cells it finds no sample at all and paints nothing: measured
|
||||
on a real box recording, **0 px** against the shared painter's **1,486 px** on the same
|
||||
frame. So the routing is by ``ImageFrame.source`` — the frame came from a result — and
|
||||
the box *style* still owns the magnification and the idle-dot tint.
|
||||
The field is finite only at the stored candidate cells. The shared selector walks
|
||||
those cells, so both sources paint the same picture under the same policy.
|
||||
"""
|
||||
from orisys_app_shared.scene3d.visual_policy import select_flow_samples
|
||||
|
||||
sparse, samples = _recovered_flow()
|
||||
base = np.zeros((60, 80, 3), dtype=np.uint8)
|
||||
base[:, :] = (0x16, 0x13, 0x12)
|
||||
policy = _policy()
|
||||
|
||||
result_frame = backend.ImageFrame(
|
||||
base, flow=sparse, source=backend.SOURCE_ANALYSIS, flow_valid=True
|
||||
@@ -991,7 +986,7 @@ def test_a_recovered_field_is_drawn_by_the_shared_painter_whatever_the_style_is(
|
||||
base, flow=sparse, source=backend.SOURCE_LIVE, flow_valid=True
|
||||
)
|
||||
drawn = backend.render_image(
|
||||
result_frame, mode=backend.ARROW_PURE, draw_style="box", arrow_policy=_policy()
|
||||
result_frame, mode=backend.ARROW_PURE, draw_style="box", arrow_policy=policy
|
||||
)
|
||||
expected = draw_arrow_display(
|
||||
base,
|
||||
@@ -1001,30 +996,27 @@ def test_a_recovered_field_is_drawn_by_the_shared_painter_whatever_the_style_is(
|
||||
# The backend hands the theme's ramp to the painter, so the reference call has to
|
||||
# as well; the default is ``cv2.COLORMAP_JET``, which is a different picture.
|
||||
colormap=flow_colormap("dark"),
|
||||
policy=_policy(),
|
||||
shared_painter=True,
|
||||
policy=policy,
|
||||
)
|
||||
assert np.array_equal(np.asarray(drawn), np.asarray(expected)), (
|
||||
"a result's canvas is not the shared painter's output"
|
||||
)
|
||||
assert _painted(drawn) > 0, "the recovered field painted nothing"
|
||||
|
||||
# The same array handed over as a *live* field keeps the SDK path — which is what the
|
||||
# sensor family's draw style has always meant, and which paints nothing here.
|
||||
sdk = backend.render_image(
|
||||
live_frame, mode=backend.ARROW_PURE, draw_style="box", arrow_policy=_policy()
|
||||
live = backend.render_image(
|
||||
live_frame, mode=backend.ARROW_PURE, draw_style="box", arrow_policy=policy
|
||||
)
|
||||
assert np.array_equal(np.asarray(drawn), np.asarray(live)), (
|
||||
"live and analysis disagree on the same sparse field"
|
||||
)
|
||||
assert _painted(sdk) == 0, "the SDK painter drew a recovered field"
|
||||
assert not np.array_equal(np.asarray(drawn), np.asarray(sdk))
|
||||
|
||||
# One marker per recorded cell at most: the recovered field carries no other value, so
|
||||
# nothing outside those cells can become a sample.
|
||||
assert select_flow_samples(sparse, None, _policy()).rows.size == samples.rows.size
|
||||
assert select_flow_samples(sparse, None, policy).rows.size == samples.rows.size
|
||||
|
||||
|
||||
def test_a_box_result_on_the_sdk_grid_matches_the_live_painter():
|
||||
"""A box pass stores the live painter's grid, so filling the gaps and calling it
|
||||
back draws the same picture as the dense field."""
|
||||
"""A box pass stores the box grid; the idempotent inverse redraws the dense field."""
|
||||
from orisys_app_shared.acquisition.arrows import sdk_box_policy
|
||||
from orisys_app_shared.scene3d.visual_policy import (
|
||||
DISPLACEMENT_PX_PER_FLOW,
|
||||
@@ -1039,13 +1031,13 @@ def test_a_box_result_on_the_sdk_grid_matches_the_live_painter():
|
||||
assert samples.rows.size > 0
|
||||
dx = np.asarray(samples.dx, np.float64)
|
||||
dy = np.asarray(samples.dy, np.float64)
|
||||
length = np.sqrt(dx * dx + dy * dy)
|
||||
flow_mag = np.asarray(samples.magnitude, np.float64) / DISPLACEMENT_PX_PER_FLOW
|
||||
safe = np.maximum(length, 1e-12)
|
||||
moving = length > 0.0
|
||||
scale = float(policy.flow_arrow_scale) or 1.0
|
||||
divisor = (
|
||||
DISPLACEMENT_PX_PER_FLOW * float(policy.flow_length_margin) / scale
|
||||
)
|
||||
sparse = np.full(dense.shape, np.nan, dtype=np.float64)
|
||||
sparse[samples.rows, samples.cols, 0] = np.where(moving, dx / safe * flow_mag, 0.0)
|
||||
sparse[samples.rows, samples.cols, 1] = np.where(moving, dy / safe * flow_mag, 0.0)
|
||||
sparse[samples.rows, samples.cols, 0] = dx / divisor
|
||||
sparse[samples.rows, samples.cols, 1] = dy / divisor
|
||||
base = np.zeros(dense.shape[:2] + (3,), np.uint8)
|
||||
base[:] = (0x16, 0x13, 0x12)
|
||||
cmap = flow_colormap("dark")
|
||||
@@ -1056,6 +1048,7 @@ def test_a_box_result_on_the_sdk_grid_matches_the_live_painter():
|
||||
background="pure_color",
|
||||
canvas_bgr=(0x16, 0x13, 0x12),
|
||||
colormap=cmap,
|
||||
policy=policy,
|
||||
)
|
||||
frame = backend.ImageFrame(
|
||||
base,
|
||||
|
||||
Reference in New Issue
Block a user