Fix replay 2D black canvas and journey UX clarity
Stored analysis overlays now use the decoded frame; latch resets and draw failures surface. Verified with bare-video, replay-wiring, visual_policy, and analysis-progress tests.
This commit is contained in:
@@ -280,6 +280,18 @@ def magnitude_to_ramp_index(
|
||||
return np.clip(scaled * 255.0, 0.0, 255.0).astype(np.uint8)
|
||||
|
||||
|
||||
def carry_frame_diagnostic(image: np.ndarray, diagnostic=None) -> np.ndarray:
|
||||
"""``image`` as a draw result that still carries ``frame_diagnostic``.
|
||||
|
||||
Used by :func:`draw_arrow_display` and by callers that must re-wrap after a
|
||||
transform (orientation) that would otherwise drop the attribute. ``None`` means
|
||||
the draw succeeded.
|
||||
"""
|
||||
out = np.asarray(image).view(_DrawResult)
|
||||
out.frame_diagnostic = diagnostic
|
||||
return out
|
||||
|
||||
|
||||
def _flow_result(image: np.ndarray, diagnostic=None) -> np.ndarray:
|
||||
"""``image`` as the result of :func:`draw_arrow_display`, carrying ``diagnostic``.
|
||||
|
||||
@@ -295,9 +307,7 @@ def _flow_result(image: np.ndarray, diagnostic=None) -> np.ndarray:
|
||||
panel's job is ``code -> localized text``; this module deliberately carries no locale
|
||||
string (see :data:`ARROW_RENDER_FAILED_MESSAGE`).
|
||||
"""
|
||||
out = image.view(_DrawResult)
|
||||
out.frame_diagnostic = diagnostic
|
||||
return out
|
||||
return carry_frame_diagnostic(image, diagnostic)
|
||||
|
||||
|
||||
class _DrawResult(np.ndarray):
|
||||
|
||||
@@ -29,6 +29,7 @@ from typing import Any
|
||||
import numpy as np
|
||||
|
||||
from orisys_app_shared.acquisition.arrows import (
|
||||
carry_frame_diagnostic,
|
||||
depth_map_to_bgr,
|
||||
draw_arrow_display,
|
||||
flow_colormap,
|
||||
@@ -451,21 +452,25 @@ def render_image(
|
||||
flow = frame.flow
|
||||
if sdk_result:
|
||||
flow = np.nan_to_num(np.asarray(flow, dtype=np.float64), nan=0.0)
|
||||
return apply_orientation(
|
||||
draw_arrow_display(
|
||||
frame.image_bgr,
|
||||
flow,
|
||||
background="captured" if wanted == ARROW_STITCHED else "pure_color",
|
||||
canvas_bgr=canvas_bgr,
|
||||
draw_style=draw_style,
|
||||
shared_painter=str(frame.source or "") in RESULT_SOURCES and not sdk_result,
|
||||
valid_mask=frame.valid_mask,
|
||||
domain_mask=domain_mask,
|
||||
colormap=flow_colormap(theme),
|
||||
policy=arrow_policy,
|
||||
),
|
||||
turn,
|
||||
drawn = draw_arrow_display(
|
||||
frame.image_bgr,
|
||||
flow,
|
||||
background="captured" if wanted == ARROW_STITCHED else "pure_color",
|
||||
canvas_bgr=canvas_bgr,
|
||||
draw_style=draw_style,
|
||||
shared_painter=str(frame.source or "") in RESULT_SOURCES and not sdk_result,
|
||||
valid_mask=frame.valid_mask,
|
||||
domain_mask=domain_mask,
|
||||
colormap=flow_colormap(theme),
|
||||
policy=arrow_policy,
|
||||
)
|
||||
# Orientation may rebuild the array; keep the draw failure on the result so the
|
||||
# panel can say so instead of showing a silent black canvas.
|
||||
diagnostic = getattr(drawn, "frame_diagnostic", None)
|
||||
oriented = apply_orientation(drawn, turn)
|
||||
if diagnostic is None:
|
||||
return oriented
|
||||
return carry_frame_diagnostic(oriented, diagnostic)
|
||||
# ``recorded``: the frame is the picture, with nothing drawn over it — and the
|
||||
# camera's own orientation, which no domain convention applies to.
|
||||
return ensure_bgr(np.asarray(frame.image_bgr))
|
||||
|
||||
@@ -248,6 +248,11 @@ def select_flow_samples(
|
||||
) * margin
|
||||
dx = dx * scale
|
||||
dy = dy * scale
|
||||
# Non-finite tips are discarded by ``valid`` below; zero them before the integer
|
||||
# cast so numpy does not warn on every sparse recovered frame during replay.
|
||||
finite_tip = np.isfinite(dx) & np.isfinite(dy)
|
||||
dx = np.where(finite_tip, dx, 0.0)
|
||||
dy = np.where(finite_tip, dy, 0.0)
|
||||
end_x = np.clip(np.round(cc + dx).astype(np.int32), 0, w - 1)
|
||||
end_y = np.clip(np.round(rr + dy).astype(np.int32), 0, h - 1)
|
||||
|
||||
|
||||
@@ -145,3 +145,16 @@ def test_paint_domain_background_colours() -> None:
|
||||
)
|
||||
assert tuple(canvas[0, 0]) == (1, 2, 3)
|
||||
assert tuple(canvas[1, 1]) == (10, 20, 30)
|
||||
|
||||
|
||||
def test_select_flow_samples_screens_non_finite_tips() -> None:
|
||||
"""Sparse recovered fields put NaN off the recorded cells; cast must stay quiet."""
|
||||
h, w = 30, 30
|
||||
flow = np.full((h, w, 2), np.nan, dtype=np.float64)
|
||||
flow[15, 15] = (0.5, 0.0)
|
||||
policy = VisualPolicy(flow_on_px=0.4, flow_grid_spacing=10, flow_edge_margin=0)
|
||||
with np.errstate(invalid="raise"):
|
||||
samples = select_flow_samples(flow, None, policy)
|
||||
pairs = set(zip(samples.rows.tolist(), samples.cols.tolist()))
|
||||
assert (15, 15) in pairs
|
||||
assert samples.active_count >= 1
|
||||
|
||||
Reference in New Issue
Block a user