BladeSzaSza's picture
fix: define REPO_NAME in hf_upload.sh (ensure_blade_space referenced it)
4948993 verified
Raw
History Blame Contribute Delete
6.07 kB
"""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