Skip to content

InternetRamen/fort-stability

Repository files navigation

FORT Stability Analysis

IMU-based stability classification for incline dumbbell bench press. Uses Apple Watch wrist motion data to detect reps and score each set as CLEAN, BORDERLINE, or SHAKY.

Quick Start

from stability_analysis import classify

result = classify("path/to/WristMotion.csv", expected_reps=8)

result["verdict"]     # CLEAN | BORDERLINE | SHAKY
result["score"]       # 0.0–1.0 instability score
result["confidence"]  # 0.0–1.0
result["n_reps"]      # reps detected
result["reasons"]     # human-readable explanations
result["metrics"]     # all computed metrics

Batch mode (auto-discovers W<weight>_..._R<reps> folders, exports to output/):

python stability_analysis.py

Project Structure

stability_analysis.py     # Main module (importable + batch CLI)
weak_point_report.py      # Per-phase weak point analysis
api.py                    # FastAPI REST API
convert_dataset.py        # Convert dataset/ CSVs to WristMotion format
example.py                # Single-CSV example
stability_analysis.ipynb  # Notebook with visualizations
data/
  labeled-self/           # W<weight>_..._R<reps>-<timestamp>/ folders
  dataset/                # Flat CSVs (different column naming)
output/
  rep_features.csv        # Per-rep metrics
  set_summary.csv         # Set-level verdicts

How Scoring Works

Pipeline

  1. Window detection — longest contiguous region with pitch > -0.3 rad (filters unrack/rack), trimmed 1s each end
  2. Rep segmentation — Savitzky-Golay smoothed pitch, find_peaks on troughs/peaks, trough-to-trough = 1 rep
  3. Parameter derivationdist from duration/reps/sample_rate, prom from pitch range (floor 0.03)
  4. Tiered scoring — weighted sum across metric tiers (below)

Scoring Tiers

Tier Weight Metrics Signal
T1 60% RotY variance, trough pitch std Direct instability
T2 25% ROM std, peak pitch std Fatigue-confounded
T3 15% Rep duration, concentric ratio std Correlated
T4 0% Acceleration tremor, path jerk Tracked only

Thresholds: >0.40 = SHAKY, >0.22 = BORDERLINE, else CLEAN

Confidence factors: tier agreement, rotY clarity (away from 0.065–0.075 ambiguous zone), rep count, tempo.

Key Metrics

Metric Range Description
instability_score 0–1 Set-level instability (tiered model)
score (per-rep) 0–10 Rep stability (10 = best). 50% rotY, 25% tremor, 25% jerk
rotY_var 0+ Lateral gyro variance — primary instability signal
acc_tremor 0+ High-freq (>3 Hz) acceleration RMS
path_jerk 0+ Pitch 2nd derivative RMS
confidence 0–1 Verdict decisiveness

REST API

uvicorn api:app --reload --port 8000
# Swagger docs: http://localhost:8000/docs
Method Path Description
GET /sets List discovered sets
GET /sets/{index}/report Weak-point report for one set
GET /reports All weak-point reports
GET /analyze?file=...&reps=... Analyze CSV by path
POST /upload?reps=... Upload and analyze a CSV
# Upload example
curl -X POST "http://localhost:8000/upload?reps=8" -F "file=@WristMotion.csv"
JSON response structure
{
  "set_info": { "index", "folder", "label", "arm", "weight", "expected_reps" },

  "n_reps": 8,
  "verdict": {
    "label": "SHAKY",               // CLEAN | BORDERLINE | SHAKY
    "instability_score": 0.524,
    "confidence": 0.71,
    "reasons": ["high lateral wobble (rotY_var=0.1014)", "..."]
  },

  "set_metrics": {
    "avg_rotY_var", "trough_pitch_std",    // T1
    "rom_std", "peak_pitch_std",           // T2
    "avg_duration", "con_ratio_std",       // T3
    "avg_acc_tremor", "avg_path_jerk",     // T4
    "avg_rom"
  },

  "avg_score": 5.4,                       // mean per-rep score (0–10)
  "reps": [{
    "rep": 1, "score": 6.6, "duration": 3.077, "rom": 0.338,
    "con_ratio": 0.848, "worst_phase": "lockout",
    "phases": {
      "concentric|lockout|eccentric|bottom": {
        "duration", "rotY_var", "acc_tremor", "path_jerk",
        "pitch_start", "pitch_end", "pitch_range"
      }
    }
  }],

  "phase_summary": {
    "concentric|lockout|eccentric|bottom": {
      "mean_duration", "std_duration", "mean_rotY_var", "std_rotY_var",
      "mean_acc_tremor", "std_acc_tremor", "mean_path_jerk", "std_path_jerk"
    }
  },

  "n_flags": 0, "n_warnings": 4,
  "findings": [{
    "severity": "flag|warning", "phase": "concentric",
    "message": "...", "evidence": { ... }, "affected_reps": [1, 5]
  }],
  "summary": "Minor concerns in concentric, eccentric."
}

Data Format

WristMotion.csv columns (100 Hz sample rate):

Column Description
time Timestamp (ns)
seconds_elapsed Time since start
rotationRateX/Y/Z Gyroscope (rad/s)
gravityX/Y/Z Gravity vector
accelerationX/Y/Z User acceleration
quaternionW/X/Y/Z Orientation quaternion
pitch, roll, yaw Euler angles (Apple convention)

Alternative format (data/dataset/ CSVs) — convert first:

python convert_dataset.py data/dataset/input.csv data/converted.csv

Dependencies

numpy pandas scipy -- core fastapi uvicorn python-multipart -- API matplotlib seaborn -- notebook only

Limitations

  • Rep count must be provided — unsupervised counting is unreliable (single-arm sub-movements overlap rep frequencies)
  • Incline DB bench only — thresholds and window detection are exercise-specific
  • Apple Watch convention — pitch/roll/yaw differ from standard ZYX Euler angles

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors