Daphne commited on
Commit ·
5d25a8e
1
Parent(s): dab45ec
Surface rep metrics in the app
Browse files- README.md +5 -5
- app.py +39 -4
- src/pozify/pipeline.py +0 -2
README.md
CHANGED
|
@@ -168,18 +168,18 @@ Each step lives in `src/pozify/steps/` and exposes a `run(...)` function.
|
|
| 168 |
|
| 169 |
## Adding Exercises
|
| 170 |
|
| 171 |
-
Supported exercise metadata is centralized in `src/pozify/exercise_catalog.py`. To add a new
|
| 172 |
exercise, add one `ExerciseSpec` with:
|
| 173 |
|
| 174 |
- `key` and `display_name`
|
| 175 |
- `default_variation` and confidence
|
| 176 |
-
- `metric_factory` for mock
|
| 177 |
- optional `variation_hints`, default `not_issues`, and `mock_issue`
|
| 178 |
|
| 179 |
The Gradio dropdown, profile validation, variation detector, rep analysis, and issue marker read from
|
| 180 |
this catalog, so new exercises do not require changing each step just to become selectable. Real
|
| 181 |
-
exercise-specific
|
| 182 |
-
|
| 183 |
|
| 184 |
Recommended replacement order:
|
| 185 |
|
|
@@ -187,7 +187,7 @@ Recommended replacement order:
|
|
| 187 |
2. `pose_backends/`: add or refine pose model adapters such as MediaPipe or MMPose.
|
| 188 |
3. `exercise_classifier.py`: load the exercise router model.
|
| 189 |
4. `rep_counter.py`: implement exercise-specific state machines.
|
| 190 |
-
5. `
|
| 191 |
6. `annotated_renderer.py`: render skeleton overlays and issue highlights.
|
| 192 |
7. `coach_summary.py`: call the selected small language model with retrieved knowledge cards.
|
| 193 |
|
|
|
|
| 168 |
|
| 169 |
## Adding Exercises
|
| 170 |
|
| 171 |
+
Supported exercise metadata is centralized in `src/pozify/exercise_catalog.py`. To add a new
|
| 172 |
exercise, add one `ExerciseSpec` with:
|
| 173 |
|
| 174 |
- `key` and `display_name`
|
| 175 |
- `default_variation` and confidence
|
| 176 |
+
- `metric_factory` for legacy/mock fallback metadata
|
| 177 |
- optional `variation_hints`, default `not_issues`, and `mock_issue`
|
| 178 |
|
| 179 |
The Gradio dropdown, profile validation, variation detector, rep analysis, and issue marker read from
|
| 180 |
this catalog, so new exercises do not require changing each step just to become selectable. Real
|
| 181 |
+
exercise-specific rep metrics live in `src/pozify/steps/rep_analysis.py`; variation rules live in
|
| 182 |
+
`src/pozify/steps/variation_detector.py`.
|
| 183 |
|
| 184 |
Recommended replacement order:
|
| 185 |
|
|
|
|
| 187 |
2. `pose_backends/`: add or refine pose model adapters such as MediaPipe or MMPose.
|
| 188 |
3. `exercise_classifier.py`: load the exercise router model.
|
| 189 |
4. `rep_counter.py`: implement exercise-specific state machines.
|
| 190 |
+
5. `issue_marker.py`: compute real issue scores and intervals from rep metrics.
|
| 191 |
6. `annotated_renderer.py`: render skeleton overlays and issue highlights.
|
| 192 |
7. `coach_summary.py`: call the selected small language model with retrieved knowledge cards.
|
| 193 |
|
app.py
CHANGED
|
@@ -54,11 +54,33 @@ def _mock_status_markdown(report: dict[str, Any]) -> str:
|
|
| 54 |
steps = ", ".join(f"`{step}`" for step in mock_steps)
|
| 55 |
return (
|
| 56 |
"## Pipeline Status\n\n"
|
| 57 |
-
"The current run uses real video QC, pose extraction, rep segmentation,
|
|
|
|
| 58 |
f"but these steps still use placeholders: {steps}."
|
| 59 |
)
|
| 60 |
|
| 61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
def analyze_video(
|
| 63 |
video_path: str | None,
|
| 64 |
goal: str,
|
|
@@ -83,7 +105,18 @@ def analyze_video(
|
|
| 83 |
report = result["final_report"]
|
| 84 |
video_quality = _quality_markdown(report["video_manifest"])
|
| 85 |
mock_status = _mock_status_markdown(report)
|
|
|
|
| 86 |
summary = report["coach_summary"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
if not report["video_manifest"]["analysis_allowed"]:
|
| 88 |
markdown = f"""{video_quality}
|
| 89 |
|
|
@@ -104,14 +137,16 @@ def analyze_video(
|
|
| 104 |
|
| 105 |
markdown = f"""## Scan Summary
|
| 106 |
|
| 107 |
-
- **Exercise router output:** {
|
| 108 |
-
- **Variation label:** {
|
| 109 |
- **Reps:** {len(report["reps"]["reps"])}
|
| 110 |
- **Analysis mode:** {report["artifacts"].get("analysis_mode", "unknown")}
|
| 111 |
- **Pose source:** {report["artifacts"].get("pose_source", "unknown")}
|
| 112 |
-
- **Mock finding:** {
|
| 113 |
- **Run ID:** `{report["run_id"]}`
|
| 114 |
|
|
|
|
|
|
|
| 115 |
{mock_status}
|
| 116 |
|
| 117 |
## Coach Notes
|
|
|
|
| 54 |
steps = ", ".join(f"`{step}`" for step in mock_steps)
|
| 55 |
return (
|
| 56 |
"## Pipeline Status\n\n"
|
| 57 |
+
"The current run uses real video QC, pose extraction, rep segmentation, "
|
| 58 |
+
"rep analysis, variation detection, and annotated video rendering, "
|
| 59 |
f"but these steps still use placeholders: {steps}."
|
| 60 |
)
|
| 61 |
|
| 62 |
|
| 63 |
+
def _metrics_markdown(report: dict[str, Any]) -> str:
|
| 64 |
+
metrics = report["rep_analysis"]["aggregate_metrics"]
|
| 65 |
+
lines = [
|
| 66 |
+
"## Movement Metrics",
|
| 67 |
+
"",
|
| 68 |
+
f"- **Average ROM score:** {metrics.get('avg_rom_score', 0):.0%}",
|
| 69 |
+
f"- **Average stability score:** {metrics.get('avg_stability_score', 0):.0%}",
|
| 70 |
+
f"- **Average symmetry score:** {metrics.get('avg_symmetry_score', 0):.0%}",
|
| 71 |
+
f"- **Average rep duration:** {metrics.get('avg_rep_duration_sec', 0)}s",
|
| 72 |
+
f"- **Tempo consistency:** {metrics.get('avg_tempo_consistency_score', 0):.0%}",
|
| 73 |
+
f"- **ROM fatigue trend:** {metrics.get('fatigue_trend_rom_delta', 0):+.2f}",
|
| 74 |
+
]
|
| 75 |
+
if "avg_hand_width_ratio" in metrics:
|
| 76 |
+
lines.append(f"- **Hand width ratio:** {metrics['avg_hand_width_ratio']:.2f}")
|
| 77 |
+
if "avg_stance_width_ratio" in metrics:
|
| 78 |
+
lines.append(f"- **Stance width ratio:** {metrics['avg_stance_width_ratio']:.2f}")
|
| 79 |
+
if "avg_lockout_quality" in metrics:
|
| 80 |
+
lines.append(f"- **Lockout quality:** {metrics['avg_lockout_quality']:.0%}")
|
| 81 |
+
return "\n".join(lines)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
def analyze_video(
|
| 85 |
video_path: str | None,
|
| 86 |
goal: str,
|
|
|
|
| 105 |
report = result["final_report"]
|
| 106 |
video_quality = _quality_markdown(report["video_manifest"])
|
| 107 |
mock_status = _mock_status_markdown(report)
|
| 108 |
+
movement_metrics = _metrics_markdown(report)
|
| 109 |
summary = report["coach_summary"]
|
| 110 |
+
exercise = report["exercise"]
|
| 111 |
+
variation = report["variation"]
|
| 112 |
+
exercise_line = (
|
| 113 |
+
f'{exercise["exercise"]} (mock confidence placeholder: {exercise["confidence"]:.0%})'
|
| 114 |
+
)
|
| 115 |
+
variation_line = (
|
| 116 |
+
f'{variation["detected_variation"]} '
|
| 117 |
+
f'(confidence: {variation["variation_confidence"]:.0%})'
|
| 118 |
+
)
|
| 119 |
+
finding = summary["main_findings"][0] if summary["main_findings"] else "No mock finding emitted"
|
| 120 |
if not report["video_manifest"]["analysis_allowed"]:
|
| 121 |
markdown = f"""{video_quality}
|
| 122 |
|
|
|
|
| 137 |
|
| 138 |
markdown = f"""## Scan Summary
|
| 139 |
|
| 140 |
+
- **Exercise router output:** {exercise_line}
|
| 141 |
+
- **Variation label:** {variation_line}
|
| 142 |
- **Reps:** {len(report["reps"]["reps"])}
|
| 143 |
- **Analysis mode:** {report["artifacts"].get("analysis_mode", "unknown")}
|
| 144 |
- **Pose source:** {report["artifacts"].get("pose_source", "unknown")}
|
| 145 |
+
- **Mock finding:** {finding}
|
| 146 |
- **Run ID:** `{report["run_id"]}`
|
| 147 |
|
| 148 |
+
{movement_metrics}
|
| 149 |
+
|
| 150 |
{mock_status}
|
| 151 |
|
| 152 |
## Coach Notes
|
src/pozify/pipeline.py
CHANGED
|
@@ -96,8 +96,6 @@ def run_pipeline(
|
|
| 96 |
analysis_mode = "mock" if mock_mode else "real"
|
| 97 |
mock_steps = [
|
| 98 |
"exercise_classifier",
|
| 99 |
-
"rep_analysis",
|
| 100 |
-
"variation_detector",
|
| 101 |
"issue_marker",
|
| 102 |
"coach_summary",
|
| 103 |
"verifier",
|
|
|
|
| 96 |
analysis_mode = "mock" if mock_mode else "real"
|
| 97 |
mock_steps = [
|
| 98 |
"exercise_classifier",
|
|
|
|
|
|
|
| 99 |
"issue_marker",
|
| 100 |
"coach_summary",
|
| 101 |
"verifier",
|