import numpy as np from paperpod.vision.motion import find_segments, motion_score, prepare_gray def series(pattern: list[tuple[float, int]], dt: float = 0.125): """Expand [(score, count), ...] into (timestamps, scores).""" scores = [s for s, n in pattern for _ in range(n)] timestamps = [i * dt for i in range(len(scores))] return timestamps, scores def test_basic_stable_and_moving_segments(): # stable 2s, moving 1s, stable 2s ts, sc = series([(0.5, 16), (10.0, 8), (0.5, 16)]) segs = find_segments(ts, sc, threshold=4.0, stable_min_duration_s=1.0) kinds = [s.kind for s in segs] assert kinds == ["stable", "moving", "stable"] assert segs[0].duration >= 1.0 assert segs[2].duration >= 1.0 def test_short_stable_blip_is_demoted_to_moving(): # moving, tiny stable pause (0.25s), moving -> should merge into one moving segment ts, sc = series([(10.0, 8), (0.5, 2), (10.0, 8)]) segs = find_segments(ts, sc, threshold=4.0, stable_min_duration_s=1.0) assert [s.kind for s in segs] == ["moving"] def test_empty_input(): assert find_segments([], [], threshold=4.0, stable_min_duration_s=1.0) == [] def test_motion_score_zero_for_identical_frames(): img = np.random.default_rng(0).integers(0, 255, (100, 100, 3), dtype=np.uint8) g = prepare_gray(img) assert motion_score(g, g) == 0.0 def test_motion_score_positive_for_changed_frames(): rng = np.random.default_rng(0) a = prepare_gray(rng.integers(0, 255, (100, 100, 3), dtype=np.uint8)) b = prepare_gray(np.full((100, 100, 3), 200, dtype=np.uint8)) assert motion_score(a, b) > 0