Spaces:
Running on Zero
Running on Zero
| """Tests for the movement-analysis engine β no GPU, no model downloads.""" | |
| import math | |
| import numpy as np | |
| from formscout.types import Pose2DResult | |
| def _pose_from(traj_by_joint, n, base_conf=0.9): | |
| """Build a Pose2DResult; traj_by_joint maps joint->callable(i)->(x,y).""" | |
| kps = [] | |
| for i in range(n): | |
| frame = {} | |
| for j in range(17): | |
| if j in traj_by_joint: | |
| x, y = traj_by_joint[j](i) | |
| else: | |
| x, y = float(100 + j * 5), float(100 + j * 5) | |
| frame[j] = {"x": float(x), "y": float(y), "conf": base_conf} | |
| kps.append(frame) | |
| return Pose2DResult(keypoints=kps, fps=30.0, confidence=0.9) | |
| # ββ relevant_joints ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_relevant_joints_and_primary_consistent(): | |
| from formscout.analysis import relevant_joints as RJ | |
| for test in RJ.RELEVANT: | |
| joints = RJ.relevant_joints(test) | |
| angles = RJ.relevant_angles(test) | |
| prim = RJ.primary_angle(test) | |
| assert joints, f"{test} has no relevant joints" | |
| assert prim in angles, f"{test} primary angle not in angles" | |
| def test_openness_label_monotonic(): | |
| from formscout.analysis.relevant_joints import openness_label | |
| assert "open" in openness_label(175) | |
| assert openness_label(30).endswith("closed") | |
| # ββ timeseries ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_angle_series_length_matches_frames(): | |
| from formscout.analysis.timeseries import angle_series | |
| pose = _pose_from({}, n=8) | |
| series = angle_series(pose, "deep_squat") | |
| assert "left_knee_flexion" in series | |
| for name, vals in series.items(): | |
| assert len(vals) == 8 | |
| def test_relevant_flexion_reports_degrees_and_openness(): | |
| from formscout.analysis.timeseries import relevant_flexion_at | |
| # Straight leg: hip, knee, ankle collinear vertical β ~180Β° | |
| straight = { | |
| 11: lambda i: (200, 100), 13: lambda i: (200, 200), 15: lambda i: (200, 300), | |
| 12: lambda i: (260, 100), 14: lambda i: (260, 200), 16: lambda i: (260, 300), | |
| 5: lambda i: (200, 40), 6: lambda i: (260, 40), | |
| } | |
| pose = _pose_from(straight, n=5) | |
| flex = relevant_flexion_at(pose, "deep_squat", 2) | |
| assert "left_knee_flexion" in flex | |
| assert flex["left_knee_flexion"]["deg"] > 160 | |
| assert "open" in flex["left_knee_flexion"]["openness"] | |
| # ββ laban βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_laban_factors_in_unit_range(): | |
| from formscout.analysis.laban import compute_laban | |
| pose = _pose_from({13: lambda i: (100 + i * 8, 200)}, n=20) | |
| res = compute_laban(pose, "deep_squat", fps=30.0) | |
| for k, v in res["effort"].items(): | |
| assert 0.0 <= v <= 1.0, f"{k}={v} out of range" | |
| assert set(res["labels"]) == {"space", "weight", "time", "flow"} | |
| def test_laban_straight_line_is_direct(): | |
| from formscout.analysis.laban import compute_laban | |
| # Knee travels in a straight horizontal line β high directness (Space) | |
| pose = _pose_from({13: lambda i: (100 + i * 10, 200)}, n=20) | |
| res = compute_laban(pose, "deep_squat", fps=30.0) | |
| assert res["effort"]["space"] > 0.8 | |
| def test_laban_static_clip_low_energy(): | |
| from formscout.analysis.laban import compute_laban | |
| pose = _pose_from({13: lambda i: (200, 200)}, n=20) # no motion | |
| res = compute_laban(pose, "deep_squat", fps=30.0) | |
| assert res["effort"]["weight"] < 0.2 | |
| # ββ charts ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_angle_over_time_chart(tmp_path): | |
| from formscout.analysis import charts | |
| out = str(tmp_path / "angle.png") | |
| series = {"left_knee_flexion": [90, 100, 110, 95], "right_knee_flexion": [88, 99, 108, 94]} | |
| p = charts.angle_over_time(series, "left_knee_flexion", 2, out) | |
| assert p == out | |
| import os | |
| assert os.path.getsize(out) > 0 | |
| def test_velocity_profile_chart(tmp_path): | |
| from formscout.analysis import charts | |
| out = str(tmp_path / "vel.png") | |
| kps = [{j: {"x": 100 + j + i * 3, "y": 100 + j, "conf": 0.9} for j in range(17)} for i in range(10)] | |
| p = charts.velocity_profile(kps, 30.0, [13, 14, 11, 12], out) | |
| import os | |
| assert p == out and os.path.getsize(out) > 0 | |
| def test_laban_radar_chart(tmp_path): | |
| from formscout.analysis import charts | |
| out = str(tmp_path / "radar.png") | |
| p = charts.laban_radar({"space": 0.8, "weight": 0.4, "time": 0.6, "flow": 0.3}, out) | |
| import os | |
| assert p == out and os.path.getsize(out) > 0 | |
| def test_flexion_bars_chart(tmp_path): | |
| from formscout.analysis import charts | |
| out = str(tmp_path / "flex.png") | |
| flex = {"left_knee_flexion": {"deg": 95.0, "openness": "flexed"}, | |
| "left_hip_flexion": {"deg": 120.0, "openness": "mid-range"}} | |
| p = charts.flexion_bars(flex, out) | |
| import os | |
| assert p == out and os.path.getsize(out) > 0 | |
| def test_symmetry_bars_chart(tmp_path): | |
| from formscout.analysis import charts | |
| out = str(tmp_path / "sym.png") | |
| asym = [{"test": "hurdle_step", "left_score": 2, "right_score": 3, "delta": 1}] | |
| p = charts.symmetry_bars(asym, out) | |
| import os | |
| assert p == out and os.path.getsize(out) > 0 | |
| def test_charts_return_none_on_empty(tmp_path): | |
| from formscout.analysis import charts | |
| assert charts.angle_over_time({}, None, None, str(tmp_path / "a.png")) is None | |
| assert charts.flexion_bars({}, str(tmp_path / "f.png")) is None | |
| assert charts.symmetry_bars([], str(tmp_path / "s.png")) is None | |