Metadata-Version: 2.4
Name: myogait
Version: 0.9.1
Summary: Markerless video-based gait analysis toolkit
Author-email: Frederic Fer <f.fer@institut-myologie.org>
License-Expression: MIT
Project-URL: Homepage, https://github.com/IDMDataHub/myogait
Project-URL: Repository, https://github.com/IDMDataHub/myogait
Project-URL: Bug Tracker, https://github.com/IDMDataHub/myogait/issues
Keywords: gait,pose-estimation,biomechanics,opensim,markerless
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Requires-Dist: pandas>=2.0
Requires-Dist: scipy>=1.11
Requires-Dist: opencv-python-headless>=4.8
Requires-Dist: matplotlib>=3.7
Requires-Dist: gaitkit>=1.4.8
Requires-Dist: openpyxl>=3.1
Provides-Extra: mediapipe
Requires-Dist: mediapipe>=0.10; extra == "mediapipe"
Provides-Extra: yolo
Requires-Dist: ultralytics>=8.0; extra == "yolo"
Provides-Extra: sapiens
Requires-Dist: torch; extra == "sapiens"
Requires-Dist: huggingface-hub; extra == "sapiens"
Provides-Extra: sapiens2
Requires-Dist: torch>=2.7; extra == "sapiens2"
Requires-Dist: safetensors>=0.4; extra == "sapiens2"
Requires-Dist: huggingface-hub; extra == "sapiens2"
Provides-Extra: vitpose
Requires-Dist: transformers>=4.45; extra == "vitpose"
Requires-Dist: torch; extra == "vitpose"
Requires-Dist: accelerate; extra == "vitpose"
Provides-Extra: intel
Requires-Dist: intel-extension-for-pytorch; extra == "intel"
Provides-Extra: rtmw
Requires-Dist: rtmlib; extra == "rtmw"
Requires-Dist: onnxruntime; extra == "rtmw"
Provides-Extra: mmpose
Requires-Dist: mmpose; extra == "mmpose"
Requires-Dist: mmdet; extra == "mmpose"
Requires-Dist: openmim; extra == "mmpose"
Provides-Extra: openpose
Provides-Extra: detectron2
Requires-Dist: torch; extra == "detectron2"
Provides-Extra: alphapose
Requires-Dist: torch; extra == "alphapose"
Requires-Dist: torchvision; extra == "alphapose"
Requires-Dist: ultralytics>=8.0; extra == "alphapose"
Provides-Extra: c3d
Requires-Dist: c3d>=0.5; extra == "c3d"
Requires-Dist: ezc3d>=2.0; extra == "c3d"
Provides-Extra: excel
Requires-Dist: openpyxl>=3.1; extra == "excel"
Provides-Extra: yaml
Requires-Dist: pyyaml>=6.0; extra == "yaml"
Provides-Extra: loess
Requires-Dist: statsmodels>=0.14; extra == "loess"
Provides-Extra: wavelet
Requires-Dist: PyWavelets>=1.5; extra == "wavelet"
Provides-Extra: all
Requires-Dist: myogait[alphapose,c3d,detectron2,excel,loess,mediapipe,openpose,rtmw,sapiens,sapiens2,vitpose,wavelet,yaml,yolo]; extra == "all"
Provides-Extra: full
Requires-Dist: myogait[all,intel]; extra == "full"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: openpyxl>=3.1; extra == "dev"
Requires-Dist: pyyaml>=6.0; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: statsmodels>=0.14; extra == "dev"
Requires-Dist: PyWavelets>=1.5; extra == "dev"
Dynamic: license-file

# myogait

Markerless video-based gait analysis toolkit.

[![CI](https://github.com/IDMDataHub/myogait/actions/workflows/ci.yml/badge.svg)](https://github.com/IDMDataHub/myogait/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/myogait)](https://pypi.org/project/myogait/)
[![Python versions](https://img.shields.io/pypi/pyversions/myogait)](https://pypi.org/project/myogait/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![Tests](https://img.shields.io/github/actions/workflow/status/IDMDataHub/myogait/ci.yml?label=tests)](https://github.com/IDMDataHub/myogait/actions/workflows/ci.yml)
[![Downloads](https://img.shields.io/pypi/dm/myogait)](https://pypi.org/project/myogait/)

**Author:** Frederic Fer, Institut de Myologie ([f.fer@institut-myologie.org](mailto:f.fer@institut-myologie.org))

myogait turns an ordinary video of a person walking into a quantitative gait
analysis — sagittal hip, knee and ankle angles, gait events, normalised gait
cycles, spatio-temporal and clinical parameters, normative comparison and a PDF
report — from a single camera, including a hand-held phone that follows the
subject. Marker-based recordings (C3D) go through the same algorithms, so
video can be benchmarked against a laboratory reference. It is a research
tool, not a certified medical device.

**No-code interface:** [myogait-app](https://github.com/IDMDataHub/myogait_app)
([PyPI](https://pypi.org/project/myogait-app/)) is a Streamlit application for
clinicians built on this library: trial explorer, patient follow-up, group
comparison and markerless-vs-marker comparison, without writing code.

---

## Quick Start

The validated end-to-end pipeline in one call — works on a video, a
pre-extracted `.myogait.json`, or a `.c3d` optical-capture file:

On a laptop without GPU, start with the lightweight MediaPipe backend
(`pip install "myogait[mediapipe]"`); Sapiens 2, the most accurate backend,
needs a recent PyTorch and ideally a GPU (see the Sapiens 2 section).

```python
import myogait as mg

result = mg.run_pipeline("walk.mp4", model="mediapipe")       # CPU
# result = mg.run_pipeline("walk.mp4", model="sapiens2-quick")  # GPU, most accurate

result["stats"]                  # cadence, stance %, ROM, symmetry, ...
result["cycles"]                 # normalized gait cycles (0-100%)
result["quality"]["warnings"]    # anything that should temper interpretation
```

`run_pipeline` applies the benchmarked defaults (Butterworth 4 Hz,
flexion-positive sign convention independent of walking direction, Zeni
events, physiological cycle window, direction-consistent cycle filter)
and reports quality diagnostics — tracking coverage, out-of-plane
distortion, insufficient cycle counts — instead of failing silently.
Every step remains available individually; see the
[tutorial](docs/tutorial.md).

Not sure which recipe fits your recording? `run_auto` inspects it (standing
start or not, there-and-back walkway, marker source) and picks the processing
recipe, falling back to the overground recipe if no gait cycle is found:

```python
result = mg.run_auto("walk.mp4", model="sapiens2-quick")
result["recipe"]["name"], result["reasons"]   # e.g. "overground", [...]
```

## Validation

myogait 0.8.9 (Sapiens2 backend) was compared with simultaneous marker-based
motion capture processed by the same myogait algorithms on the BioCV dataset
of the University of Bath (9 healthy adults, fixed cameras, Qualisys, 194
video–marker pairs).

![Joint-angle agreement with marker-based capture, left lateral view](validation/results/figures/BA_kinematics__Bath_lateral_left_cam01_.png)

| Lateral camera | Agreement with marker-based capture |
|---|---|
| Waveform *r*, hip / knee / ankle | 0.98 / 0.96 / 0.88 |
| Centred RMSE, hip / knee / ankle | 2.9° / 4.9° / 4.8° |
| Absolute RMSE, hip / knee / ankle | 8.6° / 7.8° / 6.6° |
| Cadence ICC(2,1) | 0.93 |
| Stride-length ICC(2,1) (bias) | 0.71 (−0.04 m) |
| Walking-speed ICC(2,1) | 0.83 |
| Initial contact / toe-off timing | −6 ± 26 / −3 ± 29 ms |

Waveform shape, timing and cadence are the validated outputs. Absolute angles
carry a hip offset (about −7°, trunk-based hip angle) and knee/ankle ranges of
motion are underestimated by about 9°. The camera on the far side of the
subject gives lower agreement, and frontal or rear views do not give usable
sagittal angles. A clinical validation in patients is ongoing. Full results,
limits, code and aggregate tables: [`validation/`](validation/README.md).

## Features

**Pose estimation**
- 11 interchangeable backends — MediaPipe, YOLO, Sapiens (3 sizes), Sapiens 2 (4 sizes), ViTPose, RTMW, HRNet, RTMPose, OpenPose, AlphaPose, Detectron2 — behind one API; `available_models()` reports what is installed without importing anything heavy
- Monocular depth and 28/29-class body-part segmentation (Sapiens / Sapiens 2)

**Gait analysis**
- Sagittal joint angles with a walking-direction-independent, flexion-positive convention
- Gait events (Zeni, crossing, velocity, O'Connor + gaitkit detectors), cycle segmentation with duration **and quality** gates (landmark confidence / frame coherence)
- Spatio-temporal parameters (cadence, stride time, stance %), symmetry and variability indices, harmonic ratio, step length and walking speed with configurable anthropometric scaling (`femur_mm`, `foot_mm`, `femur_ratio`)
- Pathology screening (Trendelenburg, spastic, steppage, crouch) and biomechanical range validation

**Quality & reference data**
- Per-frame biomechanical coherence scoring; built-in quality diagnostics in `run_pipeline`
- Empirical adult normative curves (optical motion capture) with per-phase SD
- C3D reference support: marker-convention autodetection, isotropic loading, 3-D ankle reference, CMC / RMSE / Bland-Altman benchmark metrics

**Output**
- Publication-quality plots, multi-page PDF clinical report
- Export to CSV, Excel, OpenSim (`.mot`/`.trc`), C3D
- CLI (`extract`, `run`, `analyze`, `batch`, `download`, `info`) and YAML/JSON pipeline configuration

## Installation

```bash
pip install myogait
```

Install with a specific pose estimation backend:

```bash
pip install myogait[mediapipe]   # MediaPipe (lightweight, CPU)
pip install myogait[yolo]        # YOLO via Ultralytics
pip install myogait[sapiens]     # Sapiens v1 (Meta AI) + Intel Arc GPU support
pip install myogait[sapiens2]    # Sapiens 2 (Meta AI, ICLR 2026) — see "Sapiens 2" section below for one-shot setup
pip install myogait[vitpose]     # ViTPose via HuggingFace Transformers
pip install myogait[rtmw]        # RTMW 133-keypoint whole-body
pip install myogait[mmpose]      # HRNet / RTMPose via MMPose
pip install myogait[alphapose]   # AlphaPose FastPose
pip install myogait[detectron2]  # Detectron2 Keypoint R-CNN
pip install myogait[all]         # All backends
```

## Supported Pose Estimation Backends

| Backend | Install | Notes |
|---------|---------|-------|
| MediaPipe | `pip install myogait[mediapipe]` | Fast, good for real-time. 33 landmarks |
| YOLOv8-Pose | `pip install myogait[yolo]` | Fast, robust. 17 COCO keypoints |
| ViTPose | `pip install myogait[vitpose]` | State-of-the-art accuracy |
| Sapiens | `pip install myogait[sapiens]` | Meta model, depth estimation |
| Sapiens 2 | `pip install myogait[sapiens2]` | Meta ICLR 2026, +4 mAP, 4K support |
| RTMPose/RTMLib | `pip install myogait[rtmw]` | Real-time, ONNX optimized |
| MMPose | `pip install myogait[mmpose]` | Academic reference, many models |
| OpenPose | Built-in (OpenCV DNN) | Historical baseline, bottom-up. 17 COCO keypoints |
| AlphaPose | `pip install myogait[alphapose]` | Top-down baseline, biomechanics standard |
| Detectron2 | `pip install myogait[detectron2]` | Meta academic baseline, Keypoint R-CNN |

## GPU Support

All models support **NVIDIA CUDA** GPUs.  Sapiens and ViTPose also support
**Intel Arc / Xe GPUs** (via `intel-extension-for-pytorch`).

| Model | CUDA | Intel Arc (XPU) | CPU |
|-------|------|-----------------|-----|
| MediaPipe | — | — | yes |
| YOLO | yes | — | yes |
| Sapiens (pose, depth, seg) | yes | yes | yes |
| Sapiens 2 (pose, depth, seg) | yes | yes | yes |
| ViTPose | yes | yes | yes |
| RTMW | yes (onnxruntime) | — | yes |
| HRNet / RTMPose | yes | — | yes |
| OpenPose | yes (OpenCV DNN) | — | yes |
| AlphaPose | yes | yes | yes |
| Detectron2 | yes | — | yes |

## Supported Pose Models

| Name | Keypoints | Format | Backend | Install |
|------|-----------|--------|---------|---------|
| `mediapipe` | 33 | MediaPipe | Google MediaPipe Tasks (heavy) | `pip install myogait[mediapipe]` |
| `yolo` | 17 | COCO | Ultralytics YOLOv8-Pose | `pip install myogait[yolo]` |
| `sapiens-quick` | 17 COCO + 308 Goliath | COCO | Meta Sapiens 0.3B (336M params) | `pip install myogait[sapiens]` |
| `sapiens-mid` | 17 COCO + 308 Goliath | COCO | Meta Sapiens 0.6B (664M params) | `pip install myogait[sapiens]` |
| `sapiens-top` | 17 COCO + 308 Goliath | COCO | Meta Sapiens 1B (1.1B params) | `pip install myogait[sapiens]` |
| `sapiens2-quick` | 17 COCO + 308 Goliath | COCO | Meta Sapiens 2 0.4B (ICLR 2026) | `pip install myogait[sapiens2]` |
| `sapiens2-mid` | 17 COCO + 308 Goliath | COCO | Meta Sapiens 2 0.8B | `pip install myogait[sapiens2]` |
| `sapiens2-top` | 17 COCO + 308 Goliath | COCO | Meta Sapiens 2 1B | `pip install myogait[sapiens2]` |
| `sapiens2-ultra` | 17 COCO + 308 Goliath | COCO | Meta Sapiens 2 5B | `pip install myogait[sapiens2]` |
| `vitpose` | 17 | COCO | ViTPose-base (HuggingFace) | `pip install myogait[vitpose]` |
| `vitpose-large` | 17 | COCO | ViTPose+-large (HuggingFace) | `pip install myogait[vitpose]` |
| `vitpose-huge` | 17 | COCO | ViTPose+-huge (HuggingFace) | `pip install myogait[vitpose]` |
| `rtmw` | 17 COCO + 133 whole-body | COCO | RTMW via rtmlib | `pip install myogait[rtmw]` |
| `hrnet` | 17 | COCO | HRNet-W48 via MMPose | `pip install myogait[mmpose]` |
| `mmpose` | 17 | COCO | RTMPose-m via MMPose | `pip install myogait[mmpose]` |
| `openpose` | 17 | COCO | CMU OpenPose via OpenCV DNN | Built-in |
| `alphapose` | 17 | COCO | AlphaPose FastPose (ResNet-50) | `pip install myogait[alphapose]` |
| `detectron2` | 17 | COCO | Keypoint R-CNN (R50-FPN, 3x) | `pip install myogait[detectron2]` |

## Sapiens Auxiliary Models

In addition to pose, Sapiens provides **depth estimation** and **body-part
segmentation**.  These run alongside any Sapiens pose model to enrich
per-landmark data.

### Depth Estimation

Monocular relative depth.  Per-landmark depth values (closer = higher)
are stored in each frame as `landmark_depths`.

| Size | HuggingFace repo |
|------|------------------|
| 0.3b | `facebook/sapiens-depth-0.3b-torchscript` |
| 0.6b | `facebook/sapiens-depth-0.6b-torchscript` |
| 1b | `facebook/sapiens-depth-1b-torchscript` |
| 2b | `facebook/sapiens-depth-2b-torchscript` |

### Body-Part Segmentation

28-class segmentation (face, torso, arms, legs, hands, feet, clothing...).
Per-landmark body-part labels are stored in each frame as `landmark_body_parts`.

| Size | mIoU | HuggingFace repo |
|------|------|------------------|
| 0.3b | 76.7 | `facebook/sapiens-seg-0.3b-torchscript` |
| 0.6b | 77.8 | `facebook/sapiens-seg-0.6b-torchscript` |
| 1b | 79.9 | `facebook/sapiens-seg-1b-torchscript` |

## Sapiens 2 (ICLR 2026)

Sapiens 2 improves over v1 with +4 mAP (pose), +24.3 mIoU (seg), and 45.6%
lower angular error (normals).  Same Goliath 308 keypoint layout as v1.
Requires `torch>=2.7` and `safetensors`.

| Size | HuggingFace pose repo | HuggingFace depth repo | HuggingFace seg repo |
|------|----------------------|------------------------|---------------------|
| 0.4b | `facebook/sapiens2-pose-0.4b` | `facebook/sapiens2-depth-0.4b` | `facebook/sapiens2-seg-0.4b` |
| 0.8b | `facebook/sapiens2-pose-0.8b` | `facebook/sapiens2-depth-0.8b` | `facebook/sapiens2-seg-0.8b` |
| 1b | `facebook/sapiens2-pose-1b` | `facebook/sapiens2-depth-1b` | `facebook/sapiens2-seg-1b` |
| 5b | `facebook/sapiens2-pose-5b` | `facebook/sapiens2-depth-5b` | `facebook/sapiens2-seg-5b` |

Sapiens 2 segmentation uses **29 classes** (adds `Eyeglass` at index 2).

### Plug-and-play setup

Meta ships Sapiens 2 weights as `.safetensors` and the Python package that
rebuilds the model architecture lives only on GitHub (not PyPI).
`myogait` ships a one-shot helper that handles the bridge:

```bash
# 1) myogait + Sapiens 2 runtime deps
pip install myogait[sapiens2]

# 2) one-shot setup: installs Meta `sapiens` from GitHub, downloads the
#    weights, traces a TorchScript .pt2 cached in ~/.myogait/models/.
#    From now on inference no longer needs the Meta package.
myogait setup-sapiens2 --size 0.4b --cleanup-safetensors
```

Once that command finishes, `mg.extract(..., model="sapiens2-quick")`
loads the cached `.pt2` directly — no Meta package required, no internet
on subsequent runs.  Repeat for `--size 0.8b`, `1b`, `5b` if you want
the larger variants.  The `.pt2` is **device-specific** (constants get
baked at trace time), so the helper traces on the device myogait will
use at inference (CUDA > XPU > CPU, in that order).

If you prefer to keep the Meta `sapiens` package around (e.g. for
research code that imports it directly), drop the `--uninstall-sapiens`
flag — it stays installed but is not on the inference path anymore.

### Usage

```bash
# Sapiens v1 — pose + depth + segmentation
myogait extract video.mp4 -m sapiens-quick --with-depth --with-seg

# Sapiens 2 — same API, better accuracy
myogait extract video.mp4 -m sapiens2-top --with-depth --with-seg

# Python
from myogait import extract
data = extract("video.mp4", model="sapiens2-top", with_depth=True, with_seg=True)
```

### End-to-end demo (extract + overlay + side-by-side + angle plots)

```bash
python examples/sapiens2_compare_pipeline.py path/to/video.mp4 --out-dir out
```

Produces in `out/`:

  * `<stem>_<model>.json` — pivot data per model (mediapipe, sapiens-quick,
    sapiens2-quick), reused on subsequent runs
  * `<stem>_<model>.mp4` — skeleton overlay per model
  * `compare_3way_slow.mp4` — three panels side-by-side at half-speed with
    legends and a darkened background so the skeleton stands out
  * `compare_angles.png` — per-cycle hip / knee / ankle curves on left and
    right side, every cycle thin + each model's mean thick

The skeleton-on-video alignment honours `data["frames"][i]["frame_idx"]`,
so the auto-cropped intro of long recordings is rendered correctly across
all models.

### Experimental Input Degradation (robustness studies)

`myogait` includes an **experimental** pre-extraction degradation layer for
controlled robustness studies (frame-rate, resolution, contrast, perspective).
By default, it is disabled and applies **no modification**.

Python API:

```python
from myogait import extract

data = extract(
    "video.mp4",
    model="mediapipe",
    experimental={
        "enabled": True,
        "target_fps": 15.0,    # frame-rate degradation
        "downscale": 0.6,      # spatial degradation
        "contrast": 0.7,       # contrast degradation
        "aspect_ratio": 1.2,   # non-square stretch
        "perspective_x": 0.2,  # side-like perspective skew
        "perspective_y": 0.1,  # forward/backward tilt skew
    },
)
```

CLI:

```bash
myogait extract video.mp4 \
  --exp-enable \
  --exp-target-fps 15 \
  --exp-downscale 0.6 \
  --exp-contrast 0.7 \
  --exp-aspect-ratio 1.2 \
  --exp-perspective-x 0.2 \
  --exp-perspective-y 0.1
```

### Experimental VICON Alignment (Single Video)

For validation workflows, you can align one myogait result with one VICON
trial and attach ground-truth comparison metrics to the JSON.
This is experimental and disabled by default in the standard pipeline.

```python
from myogait import run_single_trial_vicon_benchmark

data = run_single_trial_vicon_benchmark(
    data,                               # myogait result dict
    trial_dir="/path/to/trial_01_1",   # contains *.mat files
    vicon_fps=200.0,
    max_lag_seconds=10.0,
)

# Results in data["experimental"]["vicon_benchmark"]
```

### Experimental Single-Pair Benchmark Runner

You can run a full benchmark grid on one `(video, vicon_trial)` pair and
generate:
- one JSON per run (`<output_dir>/runs/*.json`)
- one CSV summary (`<output_dir>/benchmark_summary.csv`)
- one manifest (`<output_dir>/benchmark_manifest.json`)

```python
from myogait import run_single_pair_benchmark

manifest = run_single_pair_benchmark(
    video_path="video.mp4",
    vicon_trial_dir="/path/to/trial_01_1",
    output_dir="./benchmark_out",
    benchmark_config={
        "models": ["mediapipe", "yolo"],         # or "all"
        "event_methods": "all",                  # or ["zeni", "gk_zeni", ...]
        "normalization_variants": [
            {"name": "none", "enabled": False, "kwargs": {}},
            {"name": "butterworth", "enabled": True, "kwargs": {"filters": ["butterworth"]}},
        ],
        "degradation_variants": [
            {"name": "none", "experimental": {"enabled": False}},
            {"name": "lowres", "experimental": {"enabled": True, "downscale": 0.7, "target_fps": 15.0}},
        ],
        "continue_on_error": True,
    },
)

print(manifest["summary_csv"])
```

This runner is experimental and intended for validation studies against an optical reference.

### References

- **Sapiens v1:** Rawal et al., *Sapiens: Foundation for Human Vision Models*, ECCV 2024 — [arXiv:2408.12569](https://arxiv.org/abs/2408.12569) — [Code](https://github.com/facebookresearch/sapiens) — [Models](https://huggingface.co/collections/facebook/sapiens-66d22047daa6402d565cb2fc)
- **Sapiens 2:** Rawal et al., *Sapiens 2: A Human Foundation Model*, ICLR 2026 — [arXiv:2604.21681](https://arxiv.org/abs/2604.21681) — [Code](https://github.com/facebookresearch/sapiens2) — [Models](https://huggingface.co/facebook/sapiens2)

## ViTPose

Vision Transformer for pose estimation (NeurIPS 2022).  Top-down
architecture with RT-DETR person detector.  Fully pip-installable
via HuggingFace Transformers.

| Variant | Size | HuggingFace repo |
|---------|------|------------------|
| `vitpose` (base) | 90M | `usyd-community/vitpose-base-simple` |
| `vitpose-large` | 400M | `usyd-community/vitpose-plus-large` |
| `vitpose-huge` | 900M | `usyd-community/vitpose-plus-huge` |

- **Paper:** Xu et al., *ViTPose: Simple Vision Transformer Baselines for Human Pose Estimation*, NeurIPS 2022 — [arXiv:2204.12484](https://arxiv.org/abs/2204.12484)
- **Paper:** Xu et al., *ViTPose++: Vision Transformer for Generic Body Pose Estimation*, TPAMI 2024 — [arXiv:2212.04246](https://arxiv.org/abs/2212.04246)

## RTMW (Whole-Body 133 Keypoints)

Real-Time Multi-person Whole-body estimation: body (17) + feet (6) +
face (68) + hands (42) = 133 keypoints.  Uses rtmlib (lightweight
ONNX inference, no MMPose required).

| Mode | Pose Model | Speed |
|------|-----------|-------|
| `performance` | RTMW-x-l 384x288 | Slower, most accurate |
| `balanced` | RTMW-x-l 256x192 | Default |
| `lightweight` | RTMW-l-m 256x192 | Fastest |

- **Paper:** Jiang et al., *RTMPose: Real-Time Multi-Person Pose Estimation based on MMPose*, 2023
- **RTMW configs:** [MMPose cocktail14](https://github.com/open-mmlab/mmpose/tree/main/configs/wholebody_2d_keypoint/rtmpose/cocktail14)
- **rtmlib:** [github.com/Tau-J/rtmlib](https://github.com/Tau-J/rtmlib)

## Other Pose Models

### MediaPipe

Google's MediaPipe PoseLandmarker — 33 landmarks with full-body coverage.
Uses the **heavy** model variant (most accurate). Auto-downloaded on first use.

- **Docs:** [developers.google.com/mediapipe](https://developers.google.com/mediapipe/solutions/vision/pose_landmarker)

### YOLO Pose

Ultralytics YOLOv8-Pose — 17 COCO keypoints, fast single-shot detection.

- **Docs:** [docs.ultralytics.com](https://docs.ultralytics.com/tasks/pose/)

### HRNet / RTMPose (MMPose)

HRNet-W48 and RTMPose-m via the OpenMMLab MMPose framework — 17 COCO keypoints.

- **MMPose:** [github.com/open-mmlab/mmpose](https://github.com/open-mmlab/mmpose)
- **HRNet:** Sun et al., *Deep High-Resolution Representation Learning for Visual Recognition*, TPAMI 2019
- **RTMPose:** Jiang et al., *RTMPose: Real-Time Multi-Person Pose Estimation based on MMPose*, 2023

### OpenPose

CMU OpenPose — historical bottom-up baseline, 17 COCO keypoints via OpenCV DNN.
No extra dependency needed (uses OpenCV DNN, included in core install).
Model auto-downloaded on first use (~200 MB).

- **Paper:** Cao et al., *Realtime Multi-Person 2D Pose Estimation using Part Affinity Fields*, CVPR 2017
- **Code:** [github.com/CMU-Perceptual-Computing-Lab/openpose](https://github.com/CMU-Perceptual-Computing-Lab/openpose)

### AlphaPose

AlphaPose FastPose (ResNet-50) — top-down baseline widely used in biomechanics.
Uses YOLO person detection + heatmap-based pose estimation.
Supports both the official AlphaPose library and a fallback PyTorch reimplementation.

- **Paper:** Fang et al., *AlphaPose: Whole-Body Regional Multi-Person Pose Estimation and Tracking in Real-Time*, TPAMI 2022
- **Code:** [github.com/MVIG-SJTU/AlphaPose](https://github.com/MVIG-SJTU/AlphaPose)

### Detectron2 / Keypoint R-CNN

Meta's Detectron2 with Keypoint R-CNN (ResNet-50-FPN, 3x schedule) — academic baseline.
Built-in person detection and 17 COCO keypoint estimation in a single pass.

- **Paper:** Wu et al., *Detectron2*, 2019 ; He et al., *Mask R-CNN*, ICCV 2017
- **Code:** [github.com/facebookresearch/detectron2](https://github.com/facebookresearch/detectron2)

## Tutorials & Examples

### Basic Pipeline

The core workflow extracts pose landmarks from video, preprocesses the signal,
computes joint kinematics, detects gait events, and derives spatio-temporal
parameters.

```python
from myogait import extract, normalize, compute_angles, detect_events
from myogait import segment_cycles, analyze_gait

# 1. Extract landmarks from video
data = extract("walking_video.mp4", model="mediapipe")

# 2. Preprocess: filter noise, handle gaps
data = normalize(data, filters=["butterworth"])

# 3. Compute joint angles (sagittal + frontal if depth available)
data = compute_angles(data)

# 4. Detect gait events (heel strikes, toe offs)
data = detect_events(data, method="gk_bike")  # uses gaitkit Bayesian detector

# 5. Segment into gait cycles
cycles = segment_cycles(data)

# 6. Compute spatio-temporal parameters
stats = analyze_gait(data, cycles)
print(f"Cadence: {stats['spatiotemporal']['cadence_steps_per_min']:.1f} steps/min")
print(f"Walking speed: {stats['walking_speed']['speed_mean']:.2f} m/s")
```

### Clinical Scores

Compute standardized gait quality indices used in clinical gait analysis
laboratories worldwide.

```python
from myogait import gait_profile_score_2d, sagittal_deviation_index
from myogait import gait_variable_scores, movement_analysis_profile

# GPS-2D: overall gait quality score (RMS deviation from normative)
gps = gait_profile_score_2d(cycles)
print(f"GPS-2D: {gps['gps_2d_overall']:.1f}")

# SDI: Sagittal Deviation Index (0-120, 100 = normal)
# Note: This is a z-score based index, NOT the GDI (Schwartz & Rozumalski 2008).
sdi = sagittal_deviation_index(cycles)
print(f"SDI: {sdi['gdi_2d_overall']:.1f}")

# Per-joint deviation scores
gvs = gait_variable_scores(cycles)
for side in ("left", "right"):
    for joint, score in gvs[side].items():
        print(f"  {side} {joint}: {score:.1f}")
```

### Data Quality Assessment

Evaluate landmark detection confidence and flag outliers before running
the analysis pipeline.

```python
from myogait import confidence_filter, detect_outliers, data_quality_score
from myogait import frame_coherence_score

# Filter low-confidence landmarks
data = confidence_filter(data, threshold=0.3)

# Detect and interpolate outliers
data = detect_outliers(data, z_thresh=3.0)

# Quality report
quality = data_quality_score(data)
print(f"Quality score: {quality['score']}/100")

# Per-frame coherence scoring (z-score based, adapts to any FPS)
data = frame_coherence_score(data)
print(f"Mean coherence: {data['coherence_summary']['mean_score']:.3f}")
print(f"Low frames: {data['coherence_summary']['low_coherence_frames']}")
```

### Sagittal drift correction (opt-in)

Long recordings with a fixed camera show a slow angular drift (projection
artifact as the subject's distance to the camera changes): hip angles can
slide by 10–30° from the first to the last cycle. `apply_linear_detrend()`
(myogait.corrections) fits and removes that linear drift per joint while
preserving the anatomical mean and per-cycle ROM.

```python
from myogait.corrections import apply_linear_detrend
data = apply_linear_detrend(data)   # after compute_angles()
```

or via the CLI: `myogait analyze video.json --detrend`.

**Caveat:** the correction family in this module was calibrated on healthy
adults; on pathological gait a real kinematic drift can be part of the
clinical picture. Keep the flag off by default and compare with/without
before drawing clinical conclusions (see the warnings at the top of
`myogait/corrections.py`).

### Normative Comparison

Compare patient kinematics against normative reference bands. Adult hip,
knee, ankle and sagittal pelvis curves were computed from the BioCV dataset of
healthy adults (Evans et al. 2024, doi:10.15125/BATH-01258); trunk and
frontal-plane curves come from Perry & Burnfield (2010) and Winter (2009).

```python
from myogait import plot_normative_comparison, get_normative_band

# Plot patient vs normative bands
fig = plot_normative_comparison(data, cycles, plane="both")
fig.savefig("normative_comparison.png", dpi=150)

# Access raw normative data
band = get_normative_band("hip", stratum="adult")
mean, lower, upper = band["mean"], band["lower"], band["upper"]
```

### Event Detection with gaitkit

myogait integrates multiple gait event detection algorithms, including
Bayesian and ensemble methods from the gaitkit library.

```python
from myogait import detect_events, event_consensus, list_event_methods

# List all available methods
print(list_event_methods())
# ['zeni', 'velocity', 'crossing', 'oconnor',
#  'gk_bike', 'gk_zeni', 'gk_ensemble', ...]

# Single method (gaitkit Bayesian BIS -- best F1 score)
data = detect_events(data, method="gk_bike")

# Consensus: vote across multiple detectors
data = event_consensus(data, methods=["gk_bike", "gk_zeni", "gk_oconnor"])
```

The neural detectors `gk_intellevent` and `gk_deepevent` additionally
require **onnxruntime** (or `onnxruntime-directml` on Windows without
CUDA).  Install with `pip install onnxruntime` — the other detectors
work without it and out of the box.

### Metric calibration — measured anthropometrics preferred over height

`analyze_gait()`, `step_length()` and `walking_speed()` accept
`femur_mm` (femur length) and `foot_mm` (foot length, heel → longest
toe) in addition to `height_m`.  The scale hierarchy is:

1. **Femur + foot** together: the two independent scale estimates are
   averaged for the tightest calibration (recommended for research).
2. **Femur alone** (`femur_mm`): use the measured femur directly.
3. **Foot alone** (`foot_mm`): use the measured foot directly.
4. **Height** (`height_m`): fallback, derives femur as 24.5 % of
   height.
5. Nothing → output in image-normalised units.

```python
# Best: both measurements
stats = mg.analyze_gait(data, cycles, femur_mm=442, foot_mm=265)

# Quick-fix: femur only
stats = mg.analyze_gait(data, cycles, femur_mm=442)

# Fallback: height
stats = mg.analyze_gait(data, cycles, height_m=1.68)
```

### Per-cycle biomarker export (Excel)

`export_excel()` emits a `Biomarkers_per_cycle` sheet with one row per
detected cycle: ROM, min / max / mean per joint, peak angular velocity
(deg/s), peak angular acceleration (deg/s²), phase-restricted peaks
(stance vs swing separately), plus the SCI-relevant clinical markers
(foot drop = ankle at HS, stiff-knee = peak knee flex in swing,
toe-clearance = min ankle in swing) and the swing-peak knee timing.
Makes per-cycle variability directly visible without post-processing.

```python
mg.export_excel(data, "report.xlsx", cycles=cycles, stats=stats)
```

### C3D loading with automatic marker-convention detection

C3D input and output need the optional `c3d` extra
(`pip install "myogait[c3d]"`). `load_c3d()` reads any C3D file and returns a myogait-compatible pivot
dict.  When `marker_mapping` is omitted the function autodetects the
convention among the registered families (Plug-in Gait, ISB, Helen
Hayes, ...) by scoring how many lower-limb markers each convention can
resolve, and falls back to a regex-based fuzzy match on the raw labels
if no registered convention scores high enough.

```python
data = mg.load_c3d("trial.c3d")          # autodetect
data["extraction"]["c3d_convention"]     # → 'plug_in_gait' | 'iso_biomechanics' | ...
data["extraction"]["c3d_convention_scores"]   # per-convention resolution count

# List / inspect / extend
list(mg.C3D_MARKER_CONVENTIONS.keys())
mg.C3D_MARKER_CONVENTIONS["plug_in_gait"]["LEFT_HIP"]   # ['LASI', 'LPSI', 'LHJC']

# Autodetect just the convention (without loading the trajectories)
name, mapping, scores = mg.detect_c3d_convention(labels)
```

New conventions can be registered by editing
`myogait.experimental_vicon.C3D_MARKER_CONVENTIONS` (flat dict, keyed
by myogait landmark name).

### Export to OpenSim / Pose2Sim

Export landmarks and kinematics in formats compatible with OpenSim
musculoskeletal modeling and Pose2Sim multi-camera triangulation.

```python
from myogait import export_trc, export_mot, export_openpose_json
from myogait import export_opensim_scale_setup, export_ik_setup

# OpenSim .trc markers file (with height-based unit conversion)
export_trc(data, "markers.trc", opensim_model="gait2392")

# OpenSim .mot kinematics
export_mot(data, "kinematics.mot")

# OpenSim Scale Tool setup XML
export_opensim_scale_setup(data, "scale_setup.xml", model_file="gait2392.osim")

# OpenSim IK setup XML
export_ik_setup("markers.trc", "ik_setup.xml", model_file="scaled_model.osim")

# Pose2Sim: export OpenPose-format JSON for triangulation
export_openpose_json(data, "./openpose_output/", model="BODY_25")
```

### Video Overlay & Visualization

Generate annotated videos, anonymized stick-figure animations, and
publication-ready dashboards.

```python
from myogait import render_skeleton_video, render_stickfigure_animation
from myogait import plot_summary, plot_gvs_profile

# Skeleton overlay on original video
render_skeleton_video("video.mp4", data, "overlay.mp4", show_angles=True)

# Anonymized stick figure GIF
render_stickfigure_animation(data, "stickfigure.gif")

# Summary dashboard
fig = plot_summary(data, cycles, stats)
fig.savefig("dashboard.png", dpi=150)

# MAP barplot (Movement Analysis Profile)
fig = plot_gvs_profile(cycles)
fig.savefig("gvs_profile.png", dpi=150)
```

### PDF Report

Generate a multi-page clinical report with kinematic plots, spatio-temporal
tables, and normative comparisons.

```python
from myogait import generate_report

# Generate clinical PDF report (French or English)
generate_report(data, cycles, stats, "rapport_marche.pdf", language="fr")
```

### Multiple Export Formats

Export analysis results to a variety of tabular and structured formats for
downstream processing and archival.

```python
from myogait import export_csv, export_excel, to_dataframe, export_summary_json

# CSV files (one per data type)
export_csv(data, "./csv_output/", cycles, stats)

# Excel workbook
export_excel(data, "gait_analysis.xlsx", cycles, stats)

# Pandas DataFrame for custom analysis
df = to_dataframe(data, what="angles")
print(df.head())

# Compact summary JSON
export_summary_json(data, cycles, stats, "summary.json")
```

## CLI Usage

Run the full pipeline on a video:

```bash
myogait run video.mp4                                    # MediaPipe (default)
myogait run video.mp4 -m sapiens-quick                   # Sapiens 0.3B
myogait run video.mp4 -m sapiens-top --with-depth        # Sapiens 1B + depth
myogait run video.mp4 -m sapiens2-top --with-depth       # Sapiens 2 1B + depth
myogait run video.mp4 -m sapiens2-ultra                  # Sapiens 2 5B
myogait run video.mp4 -m vitpose                         # ViTPose
myogait run video.mp4 -m rtmw                            # RTMW 133 keypoints
```

Extract landmarks only:

```bash
myogait extract video.mp4 -m sapiens-top --with-depth --with-seg
```

Analyze previously extracted results:

```bash
myogait analyze result.json --csv --pdf
```

Optional lateral-label correction:

```python
from myogait import correct_lateral_labels

# Default: conservative global inversion correction
data = correct_lateral_labels(data)

# Opt-in: recover isolated ankle/knee/heel swaps during crossings
data = correct_lateral_labels(data, mode="partial")
```

Batch process multiple videos:

```bash
myogait batch *.mp4 -o results/
```

Download models:

```bash
myogait download --list                 # list all available models
myogait download sapiens-0.3b           # Sapiens v1 pose 0.3B
myogait download sapiens-depth-1b       # Sapiens v1 depth 1B
myogait download sapiens2-1b            # Sapiens 2 pose 1B
myogait download sapiens2-ultra         # Sapiens 2 pose 5B
```

Inspect a result file:

```bash
myogait info result.json
```

## API Reference

All functions operate on a single `data` dict that flows through the pipeline.

| Function | Description |
|---|---|
| **One-call pipelines** | |
| `run_pipeline(source, model)` | Validated end-to-end pipeline on a video, `.myogait.json` or `.c3d` |
| `run_auto(source, model)` | Same, with the recipe chosen from the recording (`detect_recipe`) |
| `filter_cycles_by_direction(data, cycles)` | Keep the dominant walking direction (there-and-back walkways) |
| **Core pipeline** | |
| `extract(video, model, experimental=None)` | Extract pose landmarks from video |
| `normalize(data, filters)` | Filter and normalize landmark trajectories |
| `compute_angles(data)` | Compute sagittal joint angles |
| `compute_frontal_angles(data)` | Compute frontal plane angles (requires depth) |
| `detect_events(data, method)` | Detect gait events (15 methods incl. gaitkit) |
| `event_consensus(data, methods)` | Multi-method voting for robust event detection |
| `segment_cycles(data)` | Segment into individual gait cycles |
| `analyze_gait(data, cycles)` | Compute spatio-temporal parameters |
| `run_single_trial_vicon_benchmark(data, trial_dir)` | Experimental VICON alignment + metrics (single trial) |
| `run_single_pair_benchmark(video, trial_dir, output_dir, benchmark_config=None)` | Experimental benchmark grid runner (single video + single VICON trial) |
| **Clinical scores** | |
| `gait_profile_score_2d(cycles)` | GPS-2D: overall gait deviation (sagittal + frontal) |
| `sagittal_deviation_index(cycles)` | SDI (Sagittal Deviation Index): z-score based 0-120 index (100 = normal). Not the GDI. |
| `gait_variable_scores(cycles)` | Per-joint deviation vs normative |
| `movement_analysis_profile(cycles)` | MAP barplot data |
| **Quality** | |
| `confidence_filter(data)` | Remove low-confidence landmarks |
| `detect_outliers(data)` | Detect and interpolate spikes |
| `data_quality_score(data)` | Composite quality score 0-100 |
| `frame_coherence_score(data)` | Per-frame biomechanical coherence [0,1] (z-score based) |
| `fill_gaps(data)` | Interpolate missing landmark gaps |
| **Analysis** | |
| `walking_speed(data, cycles)` | Estimated walking speed |
| `step_length(data, cycles)` | Step/stride length estimation |
| `stride_variability(data, cycles)` | CV of spatio-temporal parameters |
| `arm_swing_analysis(data, cycles)` | Arm swing amplitude and asymmetry |
| `segment_lengths(data)` | Anthropometric segment lengths |
| `detect_pathologies(data, cycles)` | Pattern detection (equinus, antalgic, etc.) |
| **Visualization** | |
| `plot_summary(data, cycles, stats)` | Summary dashboard |
| `plot_normative_comparison(data, cycles)` | Patient vs normative bands |
| `plot_gvs_profile(cycles)` | Movement Analysis Profile barplot |
| `render_skeleton_video(video, data, out)` | Skeleton overlay on video |
| `render_stickfigure_animation(data, out)` | Anonymized stick figure GIF |
| **Export** | |
| `export_csv(data, dir, cycles, stats)` | CSV files |
| `export_mot(data, path)` | OpenSim .mot kinematics |
| `export_trc(data, path)` | OpenSim .trc markers |
| `load_c3d(path)` | Load VICON C3D file into myogait data dict |
| `export_openpose_json(data, dir)` | OpenPose JSON for Pose2Sim |
| `export_opensim_scale_setup(data, path)` | OpenSim Scale Tool XML |
| `export_ik_setup(trc, path)` | OpenSim IK setup XML |
| `to_dataframe(data, what)` | Pandas DataFrame |
| `generate_report(data, cycles, stats, path)` | Multi-page clinical PDF report |
| `validate_biomechanical(data, cycles)` | Validate against physiological ranges |

## Configuration

myogait supports YAML and JSON pipeline configuration files:

```python
from myogait import load_config, save_config

config = load_config("pipeline.yaml")
config["filter"]["method"] = "butterworth"
config["filter"]["cutoff"] = 6.0
save_config(config, "pipeline_updated.yaml")
```

Experimental degradation can also be set in config (disabled by default):

```yaml
extract:
  model: mediapipe
  experimental:
    enabled: false
    target_fps: null
    downscale: 1.0
    contrast: 1.0
    aspect_ratio: 1.0
    perspective_x: 0.0
    perspective_y: 0.0
```

## JSON Output Format

When using Sapiens (v1 or v2) with depth and segmentation:

```json
{
  "extraction": {
    "model": "sapiens2-top",
    "depth_model": "sapiens2-depth-1b",
    "seg_model": "sapiens2-seg-1b",
    "auxiliary_format": "goliath308",
    "seg_classes": ["Background", "Apparel", "Eyeglass", "Face_Neck", "..."]
  },
  "frames": [
    {
      "frame_idx": 0,
      "landmarks": { "NOSE": {"x": 0.52, "y": 0.31, "visibility": 0.95} },
      "goliath308": [[0.52, 0.31, 0.95], "..."],
      "landmark_depths": { "NOSE": 0.73, "LEFT_HIP": 0.45, "..." : 0.0 },
      "landmark_body_parts": { "NOSE": "Face_Neck", "LEFT_HIP": "Left_Upper_Leg" }
    }
  ]
}
```

## Acknowledgments

myogait is developed at the [Institut de Myologie](https://www.institut-myologie.org/) (Paris, France) by the **PhysioEvalLab / IDMDataHub** team.

This work is funded by **Crédit Agricole CIB-LCL** and supported by:
- [AFM-Téléthon](https://www.afm-telethon.fr/) — French Muscular Dystrophy Association
- [Fondation Myologie](https://www.fondation-myologie.org/) — Research foundation for muscle diseases
- [Téléthon](https://www.telethon.fr/) — Annual fundraising event for rare disease research

<p align="center">
  <img src="https://img.shields.io/badge/Cr%C3%A9dit_Agricole_CIB--LCL-Funding-006A4E?style=for-the-badge" alt="Crédit Agricole CIB-LCL">
  &nbsp;
  <a href="https://www.institut-myologie.org/">
    <img src="https://img.shields.io/badge/Institut_de_Myologie-Paris-0055A4?style=for-the-badge" alt="Institut de Myologie">
  </a>
  &nbsp;
  <a href="https://www.afm-telethon.fr/">
    <img src="https://img.shields.io/badge/AFM--Téléthon-Supporter-E30613?style=for-the-badge" alt="AFM-Téléthon">
  </a>
  &nbsp;
  <a href="https://www.fondation-myologie.org/">
    <img src="https://img.shields.io/badge/Fondation_Myologie-Research-00A651?style=for-the-badge" alt="Fondation Myologie">
  </a>
  &nbsp;
  <a href="https://www.telethon.fr/">
    <img src="https://img.shields.io/badge/Téléthon-Funding-FFC107?style=for-the-badge" alt="Téléthon">
  </a>
</p>

## Citation

If you use myogait in your research, please cite it (see also
[`CITATION.cff`](CITATION.cff)):

```bibtex
@software{myogait,
  author    = {Fer, Frédéric},
  title     = {myogait: a markerless video-based gait analysis toolkit},
  year      = {2026},
  publisher = {Zenodo},
  doi       = {10.5281/zenodo.18726412},
  url       = {https://github.com/IDMDataHub/myogait}
}
```

The gait-event detectors are described in Fer F, Marques T, Hogrel J-Y,
Jacoupy M, Feigean R. *Event-specific generalisation of gait-event detectors:
a 16-dataset benchmark with real markerless-video validation across two
pose-estimation chains.* Research Square, 2026.
[doi:10.21203/rs.3.rs-10854607/v1](https://doi.org/10.21203/rs.3.rs-10854607/v1).

## Contributing

Bug reports, questions and pull requests are welcome — see
[CONTRIBUTING.md](CONTRIBUTING.md) and the
[Code of Conduct](CODE_OF_CONDUCT.md).

## License

This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
