Predict clinical and sports scores from human movement video using kinematics.
Powered by Pose-JEPA — self-supervised masked embedding pretraining for skeleton sequences.
kinescope is an open-source Python package for extracting kinematic features from pose estimation data and predicting scores (clinical assessments, sports performance, etc.) using those features. For small labeled datasets (N=20–100), a Vision Transformer pretrained via Pose-JEPA (a skeleton adaptation of V-JEPA) provides strong representations before any labels are seen.
Published paper (GigaScience 2025): see the gigascience-2025 branch for the exact code used in the paper.
Video
↓ (sam3d-video-pose or any COCO-17 tool)
COCO-17 pose CSVs (frame, x, y, [z], part_idx, [confidence])
↓ kinescope process
Kinematic feature matrix (position, velocity, acceleration, joint angles, L/R correlations)
↓ kinescope predict
Score predictions (regression or classification; logreg / xgboost / random_forest / vit)
Two prediction pathways are supported:
- Kinematic features — hand-crafted features (velocity, angles, coordination) fed to XGBoost / logistic regression / random forest. Works with N as small as ~20 subjects, but performance follows power law scaling.
- Pose ViT — small vision transformer operating directly on COCO-17 timeseries, pretrained via Pose-JEPA (self-supervised masked embedding prediction) on large unlabeled motion datasets, then fine-tuned on labeled data.
Requires Python ≥ 3.10 and uv.
git clone https://github.com/quietscientist/score_prediction_from_video
cd score_prediction_from_video
# Create venv
uv venv .venv
source .venv/bin/activate
uv pip install -e .
# Optional: ViT timeseries model
uv pip install -e ".[vit]"
# Optional: pretraining on large unlabeled datasets
uv pip install -e ".[pretrain]"Use sam3d-video-pose or any other tool that outputs COCO-17 keypoints:
# sam3d-video-pose produces: frame, x, y, z, part_idx
python process_video.py --video my_video.mp4 --prompt "a person"Or convert from another format:
kinescope convert --input openpose_output.json --format openpose --output my_video_coco.csvPrepare a video info CSV (video_info.csv) with columns: video, fps, width, height.
kinescope process \
--input ./pose_csvs \
--output ./pipeline_output \
--dataset my_study \
--vid-info ./video_info.csv
# → pipeline_output/my_study_features/features_total_consolidated.csvPrepare a scores CSV with columns: video (or subject/id) and score.
kinescope predict \
--features ./pipeline_output/my_study_features/features_total_consolidated.csv \
--scores ./my_scores.csv \
--output ./results \
--model xgboostSelf-supervised pretraining via Pose-JEPA before fine-tuning on small labeled datasets:
export KINESCOPE_DATA_DIR=/path/to/pretraining/datasets
kinescope pretrain \
--datasets amass ntu120 humoto \
--output ./pretrain_ckpt \
--epochs 200 \
--embed-dim 256 \
--tpc-weight 0.1 \
--invariant-weight 0.1 \
--long-horizon-weight 0.05 \
--long-horizon-segments 4 \
--artifacts-dir ./artifactsThen use the pretrained weights for fine-tuning:
kinescope predict \
--features ./pose_csvs \
--scores ./my_scores.csv \
--model vit \
--pretrained-weights ./pretrain_ckpt/best.pt \
--output ./resultsimport kinescope
# Read any COCO-17 pose CSV (sam3d output)
df = kinescope.read_coco_csv("my_video_pose.csv", video_name="subj001", fps=30)
# Convert from other formats
df = kinescope.convert_to_coco("openpose_output.json", fmt="openpose")
df = kinescope.convert_to_coco("mediapipe_output.csv", fmt="mediapipe")
# Run the full feature extraction pipeline
pipeline = kinescope.PoseProcessingPipeline(
dataset="my_study",
pose_dir="./pose_csvs",
vid_info_csv="./video_info.csv",
output_path="./output",
)
pipeline.run()
# Train and evaluate a prediction model (kinematic features)
from kinescope.prediction import train_and_evaluate, load_features_and_labels
X, y = load_features_and_labels("features.csv", "scores.csv")
results = train_and_evaluate(X, y, model_name="xgboost", output_dir="./results")
print(results["metrics"])
# Load a labeled clinical dataset (e.g. UDysRS)
from kinescope.linearprobe.udysrs_loader import load_udysrs
data = load_udysrs("/path/to/UDysRS_UPDRS_Export")
# data["arrays"] — list of (T, 17, 2) COCO-17 clips
# data["scores"] — z-scored UDysRS totals per task
# data["task"] — "drinking" | "communication" | "la"The UDysRS dataset (Parkinson's vision-based pose estimation) can be downloaded from Kaggle: limi44/parkinsons-visionbased-pose-estimation-dataset (CC BY 4.0 — Li et al., J NeuroEng Rehabil 2018)
kinescope convert accepts any of:
| Format | Source |
|---|---|
coco |
sam3d-video-pose, MMPose, any COCO-17 CSV |
openpose |
OpenPose JSON (body_18) |
mediapipe |
MediaPipe Pose (BlazePose 33) CSV |
kinect_v2 |
KinectV2 25-joint CSV |
smpl |
SMPL 24-joint position CSV |
All formats are converted to the canonical COCO-17 DataFrame: frame, bp, x, y, [z], confidence.
Features are extracted for wrists, ankles, elbows, and knees:
- Position: median (x, y), IQR (x, y)
- Velocity: median |velocity|, IQR velocity (x, y)
- Acceleration: IQR acceleration (x, y)
- Complexity: positional entropy
- Joint angles: mean, std, entropy, median angular velocity, IQR angular velocity, IQR angular acceleration
- Left-right coordination: Pearson correlation between left and right limb trajectories
Both total (whole-video) and windowed (rolling) features are computed.
| Model | Flag | Notes |
|---|---|---|
| Logistic Regression | logreg |
Fast baseline; L2 regularization |
| XGBoost | xgboost |
Default; tabular features |
| Random Forest | random_forest |
Built-in feature importance |
| Pose ViT | vit |
Transformer on COCO-17 timeseries; requires [vit] extra |
All models use GridSearchCV + StratifiedKFold. Outputs: ROC curve, PR curve, confusion matrix, feature importance, pickled best model.
The ViT uses self-supervised pretraining via Pose-JEPA (adapted from V-JEPA): an EMA target encoder predicts latent embeddings of spatiotemporally masked joint tokens. A motion-gated temporal predictive coding (TPC) auxiliary loss provides additional signal during dynamic clips. Optional clip-level kinematic invariant supervision can be enabled with --invariant-weight to nudge representations toward symmetry/smoothness/coordination/entropy structure. A coarse long-horizon segment objective (--long-horizon-weight) can be enabled to bias representations toward slower-timescale temporal structure.
Supported pretraining datasets:
| Dataset | Format | CLI flag |
|---|---|---|
| AMASS | SMPL params | amass |
| NTU RGB+D 120 | KinectV2 skeleton | ntu120 |
| HUMOTO | Mixamo GLB + YAML | humoto |
| Own COCO-17 CSVs | native | coco |
src/kinescope/
├── pose/ # COCO-17 I/O and format converters
├── kinematics/ # Dynamics, joint angles, feature extraction
├── processing/ # Smoothing, interpolation, skeleton normalization
├── pipeline/ # PoseProcessingPipeline (end-to-end feature extraction)
├── prediction/ # Models, GridSearchCV training, evaluation plots
│ └── _vit.py # PoseViT, PoseJEPA, PoseViTClassifier
├── pretrain/ # Pose-JEPA pretraining: data loaders, training loop, visualizations
├── linearprobe/ # Labeled clinical dataset loaders (UDysRS, ...)
└── skeleton.py # COCO-17 constants and joint definitions
Feature computation adapted from Chambers et al. 2020.
If you use this code, please cite:
DOI: 10.5281/zenodo.14042732