Document Version: 1.0 Last Updated: 2025-01-13 Status: Planning Phase
This document outlines a practical 3-month validation roadmap for kinemotion that does not require laboratory equipment (force plates, motion capture, jump mats). Items are ranked by ROI (Return on Investment) and implementation difficulty to prioritize high-impact work.
Current Status: Pre-validation - technically sound implementation lacking empirical validation
Goal: Establish measurement reliability, compare against accessible benchmarks, and transparently document limitations
| ROI: ⭐⭐⭐⭐⭐ | Difficulty: ⭐☆☆☆☆ | Time: 2-4 hours |
Description: Add clear validation status warnings to README, API docs, and user-facing output.
Deliverables:
README.md with validation status sectiondocs/validation-status.mdContent to include:
## ⚠️ Validation Status
**Current Status:** Pre-validation (not validated against force plates)
**Suitable for:**
- ✅ Training monitoring (relative changes within athlete)
- ✅ Educational purposes
- ✅ Exploratory analysis
- ✅ Proof-of-concept research
**NOT suitable for:**
- ❌ Research publications (as measurement tool)
- ❌ Clinical decision-making
- ❌ Talent identification (absolute comparisons)
- ❌ Legal/insurance assessments
**Known Limitations:**
- No force plate validation
- MediaPipe accuracy affected by: lighting, clothing, occlusion
- Lower sampling rate (30-60fps) vs validated apps (120-240Hz)
- Indirect measurement (landmarks → CoM) introduces error
Files to modify:
README.mddocs/validation-status.md (new)docs/README.md (add link)Success Criteria:
| ROI: ⭐⭐⭐⭐⭐ | Difficulty: ⭐⭐☆☆☆ | Time: 4-8 hours |
Description: Automatically flag low-confidence detections based on tracking quality indicators.
Output Format:
{
"jump_height": 0.35,
"flight_time": 0.532,
"confidence": "high",
"quality_indicators": {
"avg_visibility": 0.89,
"tracking_stable": true,
"phase_detection_clear": true,
"outliers_detected": 2
},
"warnings": []
}
Confidence Rules:
Warning Triggers:
"Poor lighting or occlusion detected""Unstable landmark tracking""Unclear phase transitions""High measurement uncertainty"Files to modify:
src/kinemotion/dropjump/analysis.pysrc/kinemotion/countermovement_jump/analysis.pysrc/kinemotion/api.pysrc/kinemotion/core/quality.py (new module)Implementation:
# src/kinemotion/core/quality.py
from dataclasses import dataclass
from typing import Literal
@dataclass
class QualityAssessment:
confidence: Literal["high", "medium", "low"]
avg_visibility: float
tracking_stable: bool
phase_detection_clear: bool
outliers_detected: int
warnings: list[str]
def assess_quality(
visibilities: np.ndarray,
positions: np.ndarray,
outlier_mask: np.ndarray,
phases: list[tuple]
) -> QualityAssessment:
"""Calculate quality indicators and confidence score."""
...
Testing:
Success Criteria:
| ROI: ⭐⭐⭐⭐⭐ | Difficulty: ⭐☆☆☆☆ | Time: 1-2 hours |
Description: Verify algorithm produces identical results on repeated runs of same video (deterministic behavior).
Test Protocol:
# Run same video 100 times
for i in {1..100}; do
uv run kinemotion dropjump-analyze test.mp4 --output results_${i}.json
done
# Compare all outputs - should be IDENTICAL
python scripts/compare_results.py results_*.json
Expected Result:
If Results Vary:
Script to create:
# scripts/compare_results.py
import json
import sys
from pathlib import Path
def compare_results(result_files: list[Path]) -> bool:
"""Compare multiple result files for identity."""
baseline = json.loads(result_files[0].read_text())
for i, file in enumerate(result_files[1:], start=2):
current = json.loads(file.read_text())
if current != baseline:
print(f"Mismatch at run {i}")
print(f"Baseline: {baseline}")
print(f"Current: {current}")
return False
print(f"✅ All {len(result_files)} runs produced identical results")
return True
Success Criteria:
| ROI: ⭐⭐⭐⭐☆ | Difficulty: ⭐⭐☆☆☆ | Time: 2-4 hours |
Description: Validate timing accuracy using physics with dropped objects from measured heights.
Test Protocol:
Equipment:
- Basketball or medicine ball
- Measuring tape
- Camera at 60fps
- Tripod
Heights to test:
- 0.50m → expected flight_time = 0.319s
- 1.00m → expected flight_time = 0.452s
- 1.50m → expected flight_time = 0.553s
Formula: t = sqrt(2h/g) where g = 9.81 m/s²
Repetitions: 10 drops per height × 3 heights = 30 videos
Data Collection:
Analysis:
# scripts/validate_known_heights.py
import numpy as np
from kinemotion import process_dropjump_video
def validate_physics(video_path: str, true_height_m: float):
"""Compare measured vs theoretical flight time."""
result = process_dropjump_video(video_path)
measured_time = result['flight_time']
expected_time = np.sqrt(2 * true_height_m / 9.81)
error = measured_time - expected_time
percent_error = (error / expected_time) * 100
return {
'measured': measured_time,
'expected': expected_time,
'error': error,
'percent_error': percent_error
}
Validation Metrics:
Success Criteria:
Deliverable:
docs/validation/known-height-validation.md with results| ROI: ⭐⭐⭐⭐☆ | Difficulty: ⭐⭐☆☆☆ | Time: 4-8 hours |
Description: Systematically test how tunable parameters affect output metrics.
Parameters to Test:
test_matrix = {
'smoothing_window': [3, 5, 7, 9, 11],
'polyorder': [2, 3, 4],
'velocity_threshold': [0.01, 0.015, 0.02, 0.025, 0.03],
'model_complexity': [0, 1, 2],
'min_contact_frames': [2, 3, 4, 5],
'use_curvature': [True, False],
'use_ransac': [True, False],
}
Test Videos:
Analysis Script:
# scripts/parameter_sensitivity.py
import itertools
import pandas as pd
from kinemotion import process_video
def run_sensitivity_analysis(videos, param_grid):
"""Test all parameter combinations."""
results = []
for params in itertools.product(*param_grid.values()):
param_dict = dict(zip(param_grid.keys(), params))
for video in videos:
result = process_video(video, **param_dict)
results.append({
**param_dict,
'video': video,
'jump_height': result['jump_height'],
'flight_time': result['flight_time'],
# ... other metrics
})
return pd.DataFrame(results)
Visualization:
Success Criteria:
Deliverable:
docs/validation/parameter-sensitivity.mddocs/validation/sensitivity-plots/| ROI: ⭐⭐⭐⭐⭐ | Difficulty: ⭐⭐⭐☆☆ | Time: 8-16 hours |
Description: Compare kinemotion’s automated detection against manual frame selection (MyJump’s validated method).
Why This Matters:
Test Protocol:
Sample Size: 50 jumps (25 drop jump, 25 CMJ)
Athletes: 5 athletes × 10 jumps each
Video: 60fps, lateral view, 2-3m distance
For each jump:
1. Record video
2. Process with kinemotion (automated)
3. Manually identify takeoff/landing frames (blind to automated result)
4. Calculate flight time: (landing_frame - takeoff_frame) / fps
5. Calculate jump height: h = g × t² / 8
6. Compare automated vs manual
Manual Frame Selection Guidelines:
Takeoff Frame: Last frame where feet are in contact with ground
Landing Frame: First frame where feet contact ground after flight
Tips for consistency:
- Use frame-by-frame playback
- Look for foot-ground contact/separation
- Mark frame numbers in spreadsheet
- Have 2 independent raters for reliability
Statistical Analysis:
# scripts/validate_vs_manual.py
import numpy as np
from scipy.stats import pearsonr
from sklearn.metrics import mean_absolute_error
def calculate_icc(automated, manual):
"""Calculate ICC(2,1) - two-way random effects model."""
# Implementation following McGraw & Wong (1996)
...
def bland_altman_analysis(automated, manual):
"""Calculate bias and 95% limits of agreement."""
mean_diff = np.mean(automated - manual)
std_diff = np.std(automated - manual)
loa_upper = mean_diff + 1.96 * std_diff
loa_lower = mean_diff - 1.96 * std_diff
return {
'bias': mean_diff,
'loa_upper': loa_upper,
'loa_lower': loa_lower,
'std_diff': std_diff
}
Target Metrics:
Deliverables:
data/validation/manual_comparison.csvnotebooks/manual_comparison_analysis.ipynbdocs/validation/manual-comparison-study.mdSuccess Criteria:
| ROI: ⭐⭐⭐⭐☆ | Difficulty: ⭐⭐⭐☆☆ | Time: 4-8 hours |
Description: Test measurement consistency across different cameras/devices.
Test Protocol:
Devices to test:
1. iPhone 12 Pro (60fps, 1920×1080)
2. Samsung Galaxy S21 (30fps, 1920×1080)
3. GoPro Hero 10 (60fps, 2.7K)
4. Laptop webcam (30fps, 720p)
Setup:
- Record same jumps simultaneously with all devices
- Side-by-side camera positions (same view angle)
- 10 jumps per session × 3 sessions = 30 jumps
- Process each video independently with kinemotion
Analysis:
# Calculate ICC across devices
def inter_device_icc(devices: dict[str, list[float]]) -> float:
"""
ICC(2,k) - two-way random effects, consistency definition.
Tests if devices produce consistent relative measurements.
"""
...
# Mean differences between devices
def pairwise_device_comparison(device_a, device_b):
"""Compare each device pair."""
return {
'mean_diff': np.mean(device_a - device_b),
'std_diff': np.std(device_a - device_b),
'correlation': pearsonr(device_a, device_b)[0]
}
Expected Findings:
Deliverables:
docs/validation/inter-device-reliability.mdSuccess Criteria:
| ROI: ⭐⭐⭐⭐☆ | Difficulty: ⭐⭐☆☆☆ | Time: 4-6 hours |
Description: Quantify impact of video frame rate on measurement accuracy.
Critical Question: Can 30fps video match accuracy of 120-240Hz validated apps?
Test Protocol:
# Record at highest fps available (60-120fps)
# Downsample to test lower rates
for fps in 60 48 30 24 15; do
ffmpeg -i original_60fps.mp4 -r $fps downsampled_${fps}fps.mp4
kinemotion dropjump-analyze downsampled_${fps}fps.mp4 > result_${fps}.json
done
# Compare metrics across frame rates
python scripts/analyze_fps_sensitivity.py
Videos to Test:
Analysis:
def fps_sensitivity_analysis(results_by_fps):
"""Analyze how frame rate affects accuracy."""
baseline = results_by_fps[60] # 60fps as baseline
for fps, results in results_by_fps.items():
mae = mean_absolute_error(baseline, results)
correlation = pearsonr(baseline, results)[0]
print(f"{fps}fps: MAE={mae:.3f}m, r={correlation:.3f}")
Expected Results:
Deliverables:
docs/validation/frame-rate-sensitivity.mdSuccess Criteria:
| ROI: ⭐⭐⭐⭐☆ | Difficulty: ⭐⭐⭐☆☆ | Time: 8-12 hours |
Description: Create reusable validation utilities for systematic testing.
Module Structure:
# src/kinemotion/validation/__init__.py
from .reliability import calculate_icc, calculate_sem, calculate_mdl
from .agreement import bland_altman_analysis, calculate_bias
from .physics import check_energy_conservation, check_trajectory_validity
from .comparison import compare_to_manual, compare_devices
from .quality import assess_measurement_quality
# src/kinemotion/validation/reliability.py
def calculate_icc(
measurements: list[list[float]],
icc_type: str = "ICC(2,1)"
) -> tuple[float, tuple[float, float]]:
"""
Calculate Intraclass Correlation Coefficient.
Args:
measurements: List of measurement sets (e.g., different sessions)
icc_type: Type of ICC to calculate
- ICC(1,1): One-way random effects
- ICC(2,1): Two-way random effects, absolute agreement
- ICC(3,1): Two-way mixed effects, consistency
Returns:
Tuple of (ICC value, 95% confidence interval)
Reference:
McGraw & Wong (1996). Forming inferences about some
intraclass correlation coefficients.
"""
...
def calculate_sem(icc: float, sd: float) -> float:
"""
Calculate Standard Error of Measurement.
SEM = SD × sqrt(1 - ICC)
Represents measurement precision.
"""
return sd * np.sqrt(1 - icc)
def calculate_mdl(sem: float, confidence: float = 0.95) -> float:
"""
Calculate Minimal Detectable Change.
MDC = SEM × sqrt(2) × Z_score
Smallest change that exceeds measurement error.
"""
z_score = 1.96 if confidence == 0.95 else 2.576 # 99% CI
return sem * np.sqrt(2) * z_score
# src/kinemotion/validation/agreement.py
def bland_altman_analysis(
method_a: np.ndarray,
method_b: np.ndarray,
method_a_name: str = "Method A",
method_b_name: str = "Method B"
) -> dict:
"""
Perform Bland-Altman analysis for method comparison.
Returns bias, limits of agreement, and plot data.
Reference:
Bland & Altman (1986). Statistical methods for assessing
agreement between two methods of clinical measurement.
"""
differences = method_a - method_b
mean_values = (method_a + method_b) / 2
bias = np.mean(differences)
std_diff = np.std(differences, ddof=1)
loa_upper = bias + 1.96 * std_diff
loa_lower = bias - 1.96 * std_diff
return {
'bias': float(bias),
'std_diff': float(std_diff),
'loa_upper': float(loa_upper),
'loa_lower': float(loa_lower),
'differences': differences,
'mean_values': mean_values,
'n': len(differences)
}
# src/kinemotion/validation/physics.py
def check_energy_conservation(
takeoff_velocity: float,
peak_height: float,
tolerance: float = 0.05
) -> tuple[bool, float]:
"""
Verify energy conservation: KE at takeoff → PE at peak.
h_calculated = v² / (2g)
Returns (is_valid, percent_error)
"""
g = 9.81
h_calculated = (takeoff_velocity ** 2) / (2 * g)
error = abs(h_calculated - peak_height) / peak_height
return (error < tolerance, error * 100)
def check_trajectory_validity(
positions: np.ndarray,
velocities: np.ndarray,
accelerations: np.ndarray
) -> dict[str, bool]:
"""
Check physics consistency of trajectory.
Tests:
1. Acceleration near -g during flight
2. Velocity integrates to position
3. Symmetric flight trajectory
"""
...
Testing:
# tests/validation/test_reliability.py
def test_icc_perfect_agreement():
"""ICC should be 1.0 for identical measurements."""
measurements = [[1.0, 2.0, 3.0], [1.0, 2.0, 3.0]]
icc, ci = calculate_icc(measurements)
assert icc == 1.0
def test_icc_no_agreement():
"""ICC should be near 0 for random measurements."""
measurements = [[1.0, 2.0, 3.0], [3.0, 1.0, 2.0]]
icc, ci = calculate_icc(measurements)
assert icc < 0.5
def test_bland_altman():
"""Test Bland-Altman analysis calculation."""
a = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
b = np.array([1.1, 2.1, 2.9, 4.1, 4.9])
result = bland_altman_analysis(a, b)
assert 'bias' in result
assert 'loa_upper' in result
assert 'loa_lower' in result
Deliverables:
src/kinemotion/validation/docs/api/validation.mdnotebooks/validation-examples/Success Criteria:
| ROI: ⭐⭐⭐⭐⭐ | Difficulty: ⭐⭐⭐⭐☆ | Time: 16-24 hours |
Description: Derive and apply correction equations for systematic bias (like Bishop 2022 for TTTO).
Prerequisites:
Methodology:
# 1. Collect paired data (automated vs manual)
automated = [] # kinemotion measurements
manual = [] # manual frame selection (gold standard)
# 2. Check for systematic bias
bias = np.mean(automated - manual)
if abs(bias) > 0.01: # > 1cm systematic error
print("Systematic bias detected")
# 3. Linear regression to find correction
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(automated.reshape(-1, 1), manual)
# Correction equation: y = ax + b
a = model.coef_[0]
b = model.intercept_
print(f"Correction: manual = {a:.4f} × automated + {b:.4f}")
# 4. Validate correction
corrected = a * automated + b
new_bias = np.mean(corrected - manual)
assert abs(new_bias) < 0.001 # Should eliminate bias
Example from Bishop 2022:
# Time-to-take-off correction
# y = 0.8947x + 0.1507
# where y = force plate TTTO, x = manual TTTO
def apply_ttto_correction(raw_ttto: float) -> float:
"""Apply validated correction factor."""
return 0.8947 * raw_ttto + 0.1507
Implementation:
# src/kinemotion/correction.py
from dataclasses import dataclass
from typing import Literal
@dataclass
class CorrectionFactor:
metric: str
slope: float
intercept: float
r_squared: float
n_samples: int
validation_date: str
reference: str
# Correction factors (to be determined from validation)
CORRECTION_FACTORS = {
'jump_height': CorrectionFactor(
metric='jump_height',
slope=1.0, # TBD from validation
intercept=0.0, # TBD from validation
r_squared=0.0, # TBD from validation
n_samples=0,
validation_date='TBD',
reference='Internal validation study'
),
'time_to_takeoff': CorrectionFactor(
metric='time_to_takeoff',
slope=1.0, # TBD
intercept=0.0, # TBD
r_squared=0.0, # TBD
n_samples=0,
validation_date='TBD',
reference='Internal validation study'
)
}
def apply_correction(
value: float,
metric: str,
apply: bool = True
) -> float:
"""Apply validated correction factor to measurement."""
if not apply:
return value
factor = CORRECTION_FACTORS.get(metric)
if factor is None:
return value
return factor.slope * value + factor.intercept
Validation of Correction:
# Must validate on INDEPENDENT dataset
# - Training set: derive correction (n=30)
# - Test set: validate correction (n=20)
def validate_correction(test_automated, test_manual, correction_func):
"""Validate correction on independent dataset."""
corrected = correction_func(test_automated)
# Should eliminate bias
bias_before = np.mean(test_automated - test_manual)
bias_after = np.mean(corrected - test_manual)
# Should improve agreement
mae_before = mean_absolute_error(test_automated, test_manual)
mae_after = mean_absolute_error(corrected, test_manual)
return {
'bias_before': bias_before,
'bias_after': bias_after,
'mae_before': mae_before,
'mae_after': mae_after,
'improvement': (mae_before - mae_after) / mae_before * 100
}
Deliverables:
src/kinemotion/correction.pydocs/validation/correction-factors.mdSuccess Criteria:
| ROI: ⭐⭐⭐⭐☆ | Difficulty: ⭐⭐⭐⭐☆ | Time: 20-40 hours (data collection) |
Description: Establish test-retest reliability over multiple sessions (ecological validity).
Study Design:
Participants: 10 athletes (recreationally trained)
Sessions: 5 sessions per athlete (2 weeks, every 3-4 days)
Jumps per session: 3 CMJ + 3 drop jump = 6 jumps
Total: 10 athletes × 5 sessions × 6 jumps = 300 jumps
Standardization:
- Same time of day (±2 hours)
- No training 24h before testing
- Same camera setup (distance, height, angle)
- Same warm-up protocol (5 min)
- Same environment (gym, lighting)
Data Collection Protocol:
Session Protocol:
1. 5-minute standardized warm-up
- Light jogging
- Dynamic stretches
- 3 practice jumps (submaximal)
2. Rest 2 minutes
3. Testing (randomized order):
- 3 × CMJ (90s rest between)
- 3 × Drop jump from 40cm box (90s rest)
4. Video recording:
- Lateral view, 2.5m distance
- 60fps, 1920×1080
- Tripod-mounted, fixed position
- Ensure full body in frame
5. Record:
- Session date/time
- Athlete ID (anonymized)
- Jump order
- Video filename
- Any notes (fatigue, injury, etc.)
Statistical Analysis:
# Within-session reliability
def calculate_within_session_icc(session_data):
"""ICC for 3 jumps within each session."""
# ICC(3,1) - consistency within session
...
# Between-session reliability
def calculate_between_session_icc(athlete_data):
"""ICC across 5 sessions for each athlete."""
# ICC(2,1) - absolute agreement across sessions
...
# Coefficient of Variation
def calculate_cv(measurements):
"""CV = (SD / Mean) × 100"""
return (np.std(measurements) / np.mean(measurements)) * 100
# Minimal Detectable Change
def calculate_mdc(sem, confidence=0.95):
"""Smallest change exceeding measurement error."""
z = 1.96 if confidence == 0.95 else 2.576
return sem * np.sqrt(2) * z
Expected Results:
Deliverables:
data/reliability-study/notebooks/reliability-analysis.ipynbdocs/validation/test-retest-reliability.mdSuccess Criteria:
| ROI: ⭐⭐⭐⭐☆ | Difficulty: ⭐⭐⭐⭐⭐ | Time: 24-40 hours |
Description: Enable force-velocity profiling (Samozino 2014) with loaded jumps.
Theoretical Background:
API Design:
# Single loaded jump
result = process_cmj_video(
video_path="jump_50kg.mp4",
additional_load_kg=50,
athlete_mass_kg=75
)
# Returns: velocity, force (both normalized to body mass)
# Full F-v profile from multiple loads
fv_profile = calculate_fv_profile(
jumps=[
{"video": "jump_0kg.mp4", "load_kg": 0},
{"video": "jump_20kg.mp4", "load_kg": 20},
{"video": "jump_40kg.mp4", "load_kg": 40},
{"video": "jump_60kg.mp4", "load_kg": 60},
{"video": "jump_75kg.mp4", "load_kg": 75},
],
athlete_mass_kg=75
)
print(fv_profile)
# {
# 'F0': 35.2, # Theoretical max force (N/kg)
# 'v0': 3.1, # Theoretical max velocity (m/s)
# 'Pmax': 27.3, # Max power (W/kg)
# 'Sfv': -11.4, # F-v slope (N.s.kg⁻¹.m⁻¹)
# 'Sfv_opt': -13.5, # Optimal slope
# 'FvIMB': 15.6, # F-v imbalance (%)
# 'profile_type': 'velocity_oriented'
# }
Implementation:
# src/kinemotion/fv_profile.py
from dataclasses import dataclass
import numpy as np
@dataclass
class LoadedJumpResult:
load_kg: float
total_mass_kg: float
velocity: float # Mean velocity during push-off (m/s)
force: float # Mean force during push-off (N)
power: float # Mean power (W)
def calculate_loaded_jump_metrics(
video_path: str,
athlete_mass_kg: float,
additional_load_kg: float,
hpo: float # Push-off distance (m)
) -> LoadedJumpResult:
"""
Calculate force, velocity, power for loaded jump.
Following Samozino et al. (2008) methodology:
- Force = mg(h/hpo + 1) [normalized to body mass]
- Velocity = sqrt(2gh) [mean during push-off]
- Power = Force × Velocity
"""
# Get jump height from video
result = process_cmj_video(video_path)
h = result['jump_height']
# Total mass being moved
total_mass = athlete_mass_kg + additional_load_kg
# Calculate force (normalized to athlete body mass)
g = 9.81
F_abs = total_mass * g * (h / hpo + 1) # Absolute force (N)
F_norm = F_abs / athlete_mass_kg # Normalized (N/kg)
# Calculate velocity (mean during push-off)
v = np.sqrt(2 * g * h)
# Calculate power (normalized)
P_norm = F_norm * v # W/kg
return LoadedJumpResult(
load_kg=additional_load_kg,
total_mass_kg=total_mass,
velocity=v,
force=F_norm,
power=P_norm
)
def fit_fv_relationship(jumps: list[LoadedJumpResult]) -> dict:
"""
Fit linear F-v relationship and calculate profile.
F = F0 - (F0/v0) × v
where F0 = force intercept, v0 = velocity intercept
Pmax = F0 × v0 / 4
Sfv = -F0/v0 (slope)
"""
forces = np.array([j.force for j in jumps])
velocities = np.array([j.velocity for j in jumps])
# Linear regression
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(velocities.reshape(-1, 1), forces)
F0 = model.intercept_ # Force at zero velocity
slope = model.coef_[0]
v0 = -F0 / slope # Velocity at zero force
Sfv = slope # F-v slope
Pmax = F0 * v0 / 4 # Max power
# Calculate optimal Sfv (Samozino 2012 equation)
# Sfv_opt depends on Pmax and hpo
# Simplified: Sfv_opt ≈ -sqrt(Pmax / (hpo × g))
return {
'F0': float(F0),
'v0': float(v0),
'Pmax': float(Pmax),
'Sfv': float(Sfv),
'Sfv_opt': float(Sfv_opt), # TBD: implement Samozino equation
'FvIMB': float(abs((Sfv - Sfv_opt) / Sfv_opt) * 100),
'r_squared': float(model.score(velocities.reshape(-1, 1), forces))
}
Validation Requirements:
Deliverables:
src/kinemotion/fv_profile.py moduledocs/guides/force-velocity-profiling.mdnotebooks/fv-profiling-example.ipynbSuccess Criteria:
| ROI: ⭐⭐⭐☆☆ | Difficulty: ⭐⭐☆☆☆ | Time: 4-8 hours |
Description: Enhanced debug video output with on-screen indicators.
Features:
Example Output:
[Video Frame]
┌─────────────────────────────────┐
│ Confidence: HIGH (0.89) │
│ Phase: FLIGHT │
│ Jump Height: 0.35m │
│ Flight Time: 0.532s │
├─────────────────────────────────┤
│ ⚠ Warning: Low visibility 0.65 │
└─────────────────────────────────┘
[Velocity Graph Below]
▲
│ ╱╲
v │ ╱ ╲
│ ╱ ╲___
│ ╱
└──────────────► time
^ ^ ^
| | |
start| land
takeoff
Implementation:
# src/kinemotion/visualization/enhanced_debug.py
def create_enhanced_debug_overlay(
frame: np.ndarray,
metrics: dict,
quality: QualityAssessment,
current_phase: str,
velocity_history: list[float],
time_ms: int
) -> np.ndarray:
"""Create informative debug overlay."""
...
Success Criteria:
| ROI: ⭐⭐⭐☆☆ | Difficulty: ⭐⭐☆☆☆ | Time: 8-12 hours |
Description: HTML report for batch processing results.
Features:
<!DOCTYPE html>
<html>
<head>
<title>Kinemotion Batch Analysis</title>
</head>
<body>
<h1>Jump Analysis Report</h1>
<section id="summary">
<h2>Summary Statistics</h2>
<table>
<tr><td>Total Jumps:</td><td>50</td></tr>
<tr><td>Mean Height:</td><td>0.35 ± 0.08m</td></tr>
<tr><td>High Confidence:</td><td>45/50 (90%)</td></tr>
</table>
</section>
<section id="trends">
<h2>Trend Analysis</h2>
<canvas id="heightChart"></canvas>
</section>
<section id="outliers">
<h2>Flagged Jumps</h2>
<ul>
<li>jump_12.mp4: Low confidence (visibility)</li>
<li>jump_37.mp4: Unusually low height</li>
</ul>
</section>
</body>
</html>
Usage:
kinemotion cmj-analyze videos/*.mp4 --batch --report dashboard.html
open dashboard.html
Success Criteria:
| ROI: ⭐⭐⭐☆☆ | Difficulty: ⭐⭐☆☆☆ | Time: 4-8 hours |
Description: Test and document optimal recording conditions.
Conditions to Test:
Lighting:
Camera Distance:
Camera Angle:
Background:
Data Collection:
Deliverable:
docs/guides/recording-guidelines.mdSuccess Criteria:
| ROI: ⭐⭐⭐☆☆ | Difficulty: ⭐⭐⭐☆☆ | Time: 4-8 hours |
Description: Quantify impact of clothing on landmark detection accuracy.
Test Protocol:
Same athlete, 3 clothing conditions:
10 jumps per condition
Compare: visibility scores, tracking stability, metric consistency
Expected Result:
Deliverable:
| ROI: ⭐⭐⭐☆☆ | Difficulty: ⭐⭐⭐☆☆ | Time: 8-12 hours |
Description: Automatic validation using physics laws.
Checks to Implement:
# Energy conservation
def check_energy_conservation(v_takeoff, h_peak):
"""h_calculated = v²/(2g) should match h_measured"""
h_calc = v_takeoff**2 / (2 * 9.81)
error = abs(h_calc - h_peak) / h_peak
if error > 0.1: # >10% error
return False, f"Energy mismatch: {error*100:.1f}%"
return True, "OK"
# Biological plausibility
def check_plausibility(metrics):
"""Flag implausible values."""
warnings = []
if metrics['jump_height'] > 0.80:
warnings.append("Unusually high jump (world-class)")
if metrics['jump_height'] < 0.10:
warnings.append("Unusually low jump")
if metrics.get('gct', 1.0) < 0.10:
warnings.append("Impossibly short ground contact")
if metrics.get('gct', 0.0) > 2.0:
warnings.append("Unusually long ground contact")
return warnings
# Trajectory consistency
def check_trajectory(positions, velocities):
"""Velocity should integrate to position."""
# Numerical integration
integrated_pos = np.cumsum(velocities)
correlation = pearsonr(positions, integrated_pos)[0]
if correlation < 0.9:
return False, f"Trajectory inconsistent (r={correlation:.2f})"
return True, "OK"
Success Criteria:
| ROI: ⭐⭐⭐☆☆ | Difficulty: ⭐⭐⭐☆☆ | Time: 16-24 hours |
Description: Simple web UI for video analysis.
Stack:
Features:
Deployment:
Success Criteria:
| ROI: ⭐⭐☆☆☆ | Difficulty: ⭐⭐⭐⭐☆ | Time: 24-40 hours |
Live webcam analysis with immediate feedback.
Challenges:
| ROI: ⭐⭐☆☆☆ | Difficulty: ⭐⭐⭐⭐⭐ | Time: 100+ hours |
Native iOS/Android app.
Why Low ROI:
| ROI: ⭐⭐☆☆☆ | Difficulty: ⭐⭐⭐⭐⭐ | Time: 60+ hours |
Use MediaPipe 3D world coordinates.
Why Low ROI:
| ROI: ⭐⭐☆☆☆ | Difficulty: ⭐⭐⭐⭐☆ | Time: 40+ hours |
Export to TrainingPeaks, Strava, etc.
Why Low ROI:
| ROI: ⭐⭐☆☆☆ | Difficulty: ⭐⭐⭐⭐☆ | Time: 40+ hours |
Database for tracking progress over time.
Why Low ROI:
Weeks 1-2:
Weeks 3-4: 5. Parameter sensitivity report (8h) ✅ 6. Start manual comparison study (data collection) ✅
Deliverable:
Weeks 5-6: 7. Complete manual comparison study (16h) ✅ 8. Inter-device reliability study (8h) ✅
Weeks 7-8: 9. Frame rate sensitivity study (6h) ✅ 10. Add validation module to code (12h) ✅
Deliverable:
Weeks 9-10: 11. Physics-based sanity checks (12h) ✅ 12. Environmental condition testing (8h) ✅
Weeks 11-12: 13. Improved debug visualizations (8h) ✅ 14. Write technical validation report ✅ 15. Prepare preprint for publication ✅
Deliverable:
After 3 Months:
Quantitative:
Qualitative:
Claims We CAN Make:
Claims We CANNOT Make:
When Lab Equipment Becomes Available:
Advanced Features:
User-Facing:
Version 1.0 (2025-01-13):