Metadata-Version: 2.4
Name: swing-analyzer
Version: 0.1.1
Summary: Golf swing pose analysis: overlays, phase detection, swing comparisons, and written reports from single-camera video (MediaPipe).
Author-email: Minke19104 <minke19104@gmail.com>
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: <3.13,>=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mediapipe==0.10.14
Requires-Dist: opencv-python
Requires-Dist: numpy
Requires-Dist: scipy
Dynamic: license-file

# Golf Swing Analyzer (v1)

Analyzes a golf swing video using pose estimation (MediaPipe) and overlays:
- **Dots** on body landmarks (shoulders, elbows, wrists, hips, knees, ankles)
- **Lines** connecting them into a skeleton
- **Angle triangles** (e.g. spine tilt vs. vertical), color-coded green/orange/red
  depending on how close to instructional target ranges
- **Wrist trail** showing the swing arc/path over recent frames (smoothed — the
  tracked point is EMA-damped and the drawn line is moving-averaged, so the tracer
  reads as a clean curve rather than jaggy/shaky segments; tune with `TRACER_EMA`
  and `TRACER_SMOOTH_WINDOW` in `src/swing_analyzer/analyze.py`)
- **Swing plane line** (down-the-line view) extrapolated through shoulders/hands

It also does lightweight swing-phase detection (address / top of backswing /
impact / finish) and outputs a metrics report.

## Setup

Install as a CLI tool (editable install — picks up code changes immediately):

```bash
pip install -e .
```

This installs a `swing` command with three subcommands: `analyze`, `compare`,
`full`. Run `swing --help`, or `swing <subcommand> --help`, for the full flag
list at any time.

Note: pin `mediapipe==0.10.14` — newer 0.10.3x releases dropped the legacy
`mediapipe.solutions` API in favor of a Tasks API that requires a separately
downloaded model file. 0.10.14 keeps the simple bundled-model API this tool
uses.

Outputs always land in `./output/` **relative to wherever you run `swing`**,
not the install location — so `cd` to your videos first.

## Usage

Single view:
```bash
swing analyze --face-on my_swing.mp4
```

Down-the-line view:
```bash
swing analyze --dtl my_swing_dtl.mp4
```

Both angles at once (produces a synced side-by-side video too):
```bash
swing analyze --face-on face.mp4 --dtl dtl.mp4
```

## Compare mode

Diff two swings against each other — e.g. same swing with different club/shaft
builds, or before/after a change:

```bash
swing compare --a old_shaft.mp4 --b new_shaft.mp4 \
    --label-a "Stock shaft" --label-b "Ventus Blue 7X"
```

Add `--view dtl` if both clips are down-the-line rather than face-on.

Outputs in `./output/`: `annotated_<a>.mp4`, `annotated_<b>.mp4`,
`compare_side_by_side.mp4`, and `compare_report.md` (metrics table + per-swing
assessment).

## Face blur (anonymize)

Pass `--blur-face` to pixelate the golfer's face in the output (uses the tracked
face landmarks, so the mosaic follows the head through the swing):

```bash
# compare mode — blurs the first swing (--a) only; the reference is left as-is
swing compare --a my_swing.mp4 --b reference.mp4 --blur-face
swing compare --a my_swing.mp4 --b other.mp4 --blur-face --blur-face-b  # blur both

# standalone
swing analyze --face-on my_swing.mp4 --blur-face
```

In compare mode `--blur-face` anonymizes only your clip (`--a`); add
`--blur-face-b` if you also want the reference blurred.

The blur box is **temporally smoothed** — EMA-averaged frame to frame to reduce
jitter, and held through short tracking gaps (up to `FACE_BLUR_HOLD_FRAMES`, ~0.4s)
so it doesn't flicker off on a blurry frame where pose tracking briefly drops.
Both `FACE_BLUR_SMOOTHING` and `FACE_BLUR_HOLD_FRAMES` are constants in
`src/swing_analyzer/analyze.py`.

## Auto-zoom (framing match)

Clips shot from different distances make one golfer look much smaller than the
other. Before syncing, each clip's body size is measured (the nose→ankles box as
a fraction of frame height, median over the swing) and the clip is **zoomed in and
panned down** to a standard framing:

- **Standard zoom box** — the golfer's body is zoomed to fill ~50% of the frame
  height (`STANDARD_BODY_FRACTION`). In compare mode the tighter of the two clips
  raises this so both clips stay matched to each other (e.g. your range clip zooms
  in to match a tour-pro reference).
- **Standard ground line** — the clip is panned down so the feet (and the ball,
  which sits at the feet) land ~85% down the frame (`STANDARD_FEET_Y`), so every
  clip shares the same ground line and the ball/feet are always in view.

This runs in **both** compare and standalone (`swing analyze`) modes. It only
ever zooms in (never adds borders) and crops at the original aspect ratio, so the
body is never stretched. The two standards are constants at the top of
`src/swing_analyzer/analyze.py` — override them there (or pass `target_fraction` to
`match_framing()`); there is deliberately no command-line flag.

## Slow-motion & tempo sync

Every clip is checked on load: swing tempo is measured from the **downswing**
(top-of-backswing → impact, the fastest and most consistent part of a swing). A
real-time downswing is ~0.2–0.35s; anything that plays back faster than ~0.8s is
treated as real-time and **slowed down** so the downswing spans ~1.5s, while a
clip that's already slow-mo is left as-is. In compare mode both clips are slowed
to that same tempo so they play at matched speed.

The `compare_side_by_side.mp4` then does a piecewise time-warp — backswing,
downswing, and follow-through are each resampled to a shared length — so
**top-of-backswing and impact line up across the two clips**, not just the first
frame.

Guardrails keep pathological clips sane: slowdown is capped at 8×, a retimed clip
never plays longer than 20s, and the side-by-side trims excess
backswing/follow-through context. Tunables live at the top of `src/swing_analyzer/analyze.py`
(`SLOWMO_*`). **This all leans on the heuristic phase detector, so it works best
on a clean, tightly-trimmed single-swing clip** — feed it a 20s highlight reel
and top/impact detection (and therefore the tempo/sync) gets unreliable.

## Output

All in `./output/`:
- `annotated_<name>.mp4` — video with overlays
- `side_by_side.mp4` — if both views were provided
- `report.md` — phase frame numbers, key metrics, and flagged issues

## Tips for good input video

- Frame the whole swing (address to finish) in view, camera roughly waist-to-head height
- Face-on: camera directly facing you, perpendicular to target line
- Down-the-line: camera behind you, aligned with the target line
- Plain background helps pose detection; avoid other people/objects in frame
- 30fps+ is fine; higher fps (60/120) gives smoother phase detection around impact

## Known limitations (v1)

- 2D single-camera pose estimation — no true 3D depth, so some angles (especially
  swing plane in face-on view) are approximate.
- Phase detection is a simple heuristic based on wrist height/velocity, not a
  robust event detector — works best with a clear address position held briefly
  and a normal tempo swing.
- No clubhead or clubshaft tracking (MediaPipe tracks body only) — swing plane
  line is a proxy through hands, not the actual shaft.
- No ball flight / clubhead speed data — that needs radar/launch-monitor input.
- Target ranges in `TARGETS` (in `src/swing_analyzer/analyze.py`) are rough instructional
  benchmarks, not personalized — worth tuning to your own baseline over time
  rather than treating as absolute truth.

## Extending

- Compare mode (`src/swing_analyzer/compare.py`) is scaffolded — it's a thin wrapper that
  calls `analyze_video()` on both clips and diffs the `summary` dicts. Natural
  next steps: frame-aligned overlay (warp swing B's timeline onto A's phase
  markers so both hit "top of backswing" at the same frame), a ghost/overlay
  video (draw both skeletons on one frame, semi-transparent), or exporting the
  comparison table as CSV for tracking across many sessions.
- To track club-specific data (shaft lean, clubface angle) you'd need either
  a marker on the club visible to a second detector, or a dedicated launch
  monitor/radar unit — video-only pose estimation can't see the club reliably.
