| """ |
| Per-frame time series for the joints relevant to a screening test. |
| |
| Builds angle-over-time series (used by the angle graph) and a per-joint flexion |
| summary at a chosen frame (degrees + open/closed label). Reuses the biomechanics |
| geometry helpers so angle definitions never diverge. |
| """ |
| from __future__ import annotations |
|
|
| import math |
|
|
| from formscout.agents.biomechanics import _angle_between_points, _get_joint |
| from formscout.analysis.relevant_joints import openness_label, relevant_angles |
|
|
|
|
| def angle_series(pose2d, test_name: str) -> dict[str, list[float]]: |
| """Return {angle_name: [deg per frame]} for this test's relevant angles. |
| |
| Frames where the angle cannot be measured hold NaN. |
| """ |
| angles = relevant_angles(test_name) |
| series: dict[str, list[float]] = {name: [] for name in angles} |
| for kps in pose2d.keypoints: |
| for name, (a, b, c) in angles.items(): |
| pa, pb, pc = _get_joint(kps, a), _get_joint(kps, b), _get_joint(kps, c) |
| if pa and pb and pc: |
| series[name].append(_angle_between_points(pa, pb, pc)) |
| else: |
| series[name].append(float("nan")) |
| return series |
|
|
|
|
| def relevant_flexion_at(pose2d, test_name: str, frame_idx: int) -> dict[str, dict]: |
| """At one frame, the relevant joint angles with degree value + openness label. |
| |
| Returns {angle_name: {"deg": float, "openness": str}}; angles that cannot be |
| measured at that frame are omitted. |
| """ |
| out: dict[str, dict] = {} |
| if not (0 <= frame_idx < len(pose2d.keypoints)): |
| return out |
| kps = pose2d.keypoints[frame_idx] |
| for name, (a, b, c) in relevant_angles(test_name).items(): |
| pa, pb, pc = _get_joint(kps, a), _get_joint(kps, b), _get_joint(kps, c) |
| if pa and pb and pc: |
| deg = _angle_between_points(pa, pb, pc) |
| if not math.isnan(deg): |
| out[name] = {"deg": deg, "openness": openness_label(deg)} |
| return out |
|
|