Skip to content

Commit 184fa8c

Browse files
Merge pull request #10 from felipe-parodi/main
feat: PrimateFace Python package, CLI, tutorials, and docs
2 parents 8a39608 + 2171fa5 commit 184fa8c

58 files changed

Lines changed: 23985 additions & 5357 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ demos/mmdet_checkpoint.pth
5858
demos/mmpose_checkpoint.pth
5959
demos/test_output/
6060
demos/results/
61+
demos/notebooks/*_output/
62+
demos/notebooks/*_demo/
63+
demos/notebooks/*_results/
64+
demos/notebooks/embeddings*.npz
65+
demos/notebooks/figures_*
66+
67+
# Temp demo images
68+
*_demo.jpg
6169

6270
# Downloaded YOLO models
6371
yolov*.pt
@@ -127,3 +135,5 @@ docs/gradio/primateface_server.py
127135
AGENTS.md
128136

129137

138+
results/
139+
analysis/

README.md

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
<a href="https://huggingface.co/datasets/fparodi/PrimateFace">
1919
<img src="https://img.shields.io/badge/🤗%20Hugging%20Face-Dataset-blue" alt="Hugging Face Dataset">
2020
</a>
21+
&nbsp;&nbsp;
22+
<a href="https://huggingface.co/fparodi/primateface-models">
23+
<img src="https://img.shields.io/badge/🤗%20Hugging%20Face-Models-blue" alt="Hugging Face Models">
24+
</a>
2125
</p>
2226

2327
PrimateFace contains data, models, and tutorials for analyzing facial behavior across primates ([Parodi et al., 2025](https://www.biorxiv.org/content/10.1101/2025.08.12.669927)).
@@ -37,24 +41,25 @@ Most of the PrimateFace modules require GPU access. If you don't have access to
3741

3842
2. Run through the [Google Colab Notebook tutorials](https://docs.primateface.studio/tutorials/) to explore several applications of PrimateFace.
3943

40-
3. Clone this repository, install the dependencies, and run through the different modules (e.g., DINOv2, image and video demos, pseudo-labeling GUI, etc.) to fully utilize PrimateFace.
44+
3. Clone this repository, install the dependencies, download the pretrained models (`python demos/download_models.py`), and run through the different modules (e.g., DINOv2, image and video demos, pseudo-labeling GUI, etc.) to fully utilize PrimateFace.
4145

4246

4347
#### Structure
4448
This repository contains the code for PrimateFace, an ecosystem for facilitating cross-species primate face analysis.
4549

4650
```
47-
|--- dataset # Explore PrimateFace data
48-
|--- demos # Test models on your own data
49-
|--- notebooks # Google Colab notebooks for tutorials
50-
|--- dinov2 # Run and visualize DINOv2 features
51-
|--- docs # Documentation for PrimateFace
52-
|--- evals # Evaluate models across frameworks & datasets
53-
|--- gui # Run pseudo-labeling GUI on your own data
54-
|--- landmark-converter # Train & apply keypoint landmark converters (68 -> 48 kpts)
55-
|--- pyproject.toml
56-
|--- README.md
57-
|--- environment.yml # Unified conda environment for modules
51+
|--- primateface # Core library (pip install primateface)
52+
|--- analysis # Facial analysis (kinematics, symmetry, head pose, quality)
53+
|--- io.py # Export to CSV, COCO, DLC, SLEAP, NWB
54+
|--- cli.py # CLI: primateface analyze, primateface models
55+
|--- demos # Demo scripts and tutorial notebooks
56+
|--- notebooks # 7 Jupyter notebooks (Quick Start, face rec, etc.)
57+
|--- dinov2 # DINOv2 feature extraction and visualization
58+
|--- docs # Documentation
59+
|--- evals # Model evaluation across frameworks & datasets
60+
|--- gui # Pseudo-labeling GUI
61+
|--- landmark-converter # Keypoint landmark converters (68 <-> 48 kpts)
62+
|--- tests # Unit tests (121 passing)
5863
```
5964

6065

@@ -120,14 +125,15 @@ Note: You may see a harmless `RequestsDependencyWarning` about urllib3 versions
120125
- [Documentation Homepage](https://docs.primateface.studio)
121126
- [Notebook Tutorials](https://docs.primateface.studio/tutorials/)
122127

123-
| Tutorial | Open in Colab |
124-
|---------|----------------|
125-
| **1. Lemur Face Visibility Time-Stamping** | [![Open](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/KordingLab/PrimateFace/blob/main/demos/notebooks/App1_Lemur_time_stamping.ipynb) |
126-
| **2. Rapid Macaque Face Recognition** | [![Open](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/KordingLab/PrimateFace/blob/main/demos/notebooks/App2_Macaque_Face_Recognition.ipynb) |
127-
| **4. Human Infant Social Gaze Tracking** | [![Open](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/KordingLab/PrimateFace/blob/main/demos/notebooks/App4_Gaze_following.ipynb) |
128-
| **3. Howler Vocal-Motor Coupling** | Coming soon |
129-
| **5. Data-Driven Discovery of Facial Actions** | Coming soon |
130-
| **6. Cross-Subject Neural Decoding of Facial Actions** | Coming soon |
128+
| Tutorial | Description |
129+
|----------|-------------|
130+
| [`quickstart`](demos/notebooks/quickstart.ipynb) | **Start here** — 3-line API, Face object, visualization, export |
131+
| [`lemur_video_timestamping`](demos/notebooks/lemur_video_timestamping.ipynb) | Detect and timestamp primate faces in video |
132+
| [`macaque_face_recognition`](demos/notebooks/macaque_face_recognition.ipynb) | Face recognition: ArcFace vs MegaDescriptor vs DINOv2 |
133+
| [`howler_vocal_motor_coupling`](demos/notebooks/howler_vocal_motor_coupling.ipynb) | Correlate facial kinematics with vocalizations |
134+
| [`macaque_gaze_following`](demos/notebooks/macaque_gaze_following.ipynb) | Gaze-following heuristic with Gazelle |
135+
| [`landmark_demographics`](demos/notebooks/landmark_demographics.ipynb) | Predict age and sex from facial landmarks |
136+
| [`facial_action_discovery`](demos/notebooks/facial_action_discovery.ipynb) | Unsupervised facial action discovery (MotionMapper-inspired) |
131137

132138

133139
#### References

demos/README.md

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,20 @@ mim install "mmpose==1.3.2" --trusted-host download.openmmlab.com --trusted-host
2525

2626
### 2. Download pretrained models
2727

28+
Models are hosted on [HuggingFace](https://huggingface.co/fparodi/primateface-models) and download automatically in notebooks. To download manually:
29+
2830
```bash
2931
cd demos
3032
python download_models.py # Downloads to current directory
31-
# Or specify output directory:
32-
python download_models.py ./models
33+
python download_models.py --force # Re-download existing files
34+
```
35+
36+
Or from Python:
37+
38+
```python
39+
from demos.notebooks.notebook_utils import download_models_hf
40+
from pathlib import Path
41+
download_models_hf(Path("demos/"))
3342
```
3443

3544
### 3. Run face detection and face landmark estimation examples
@@ -145,18 +154,19 @@ pytest test_demos.py
145154

146155
```
147156
demos/
148-
├── primateface_demo.py # Main CLI interface
149-
├── process.py # Core processing pipeline
150-
├── classify_genus.py # Species classification
151-
├── viz_utils.py # Visualization utilities
152-
├── smooth_utils.py # Temporal smoothing
153-
├── demo_docs.md # Technical documentation
154-
└── notebooks/ # Interactive tutorials
157+
├── primateface_demo.py # Demo CLI (low-level, manual config paths)
158+
├── download_models.py # Model download from HuggingFace
159+
├── classify_genus.py # Species classification via VLM
160+
├── test_demos.py # Unit tests
161+
└── notebooks/ # 7 interactive tutorial notebooks
162+
├── quickstart.ipynb
163+
├── lemur_video_timestamping.ipynb
164+
├── macaque_face_recognition.ipynb
165+
├── howler_vocal_motor_coupling.ipynb
166+
├── infant_gaze_following.ipynb
167+
├── landmark_demographics.ipynb
168+
└── facial_action_discovery.ipynb
155169
```
156170

157-
## Next Steps
158-
159-
- Explore [notebooks/](notebooks/) for hands-on tutorials
160-
- Check [evals/](../evals/) for model evaluation
161-
- Use [gui/](../gui/) for interactive annotation
162-
- Try [dinov2/](../dinov2/) for feature extraction
171+
**Note:** Core modules (processor, model registry, constants, viz, smoothing) have moved
172+
to the `primateface/` package. Backward-compatible imports from `demos` still work.

demos/__init__.py

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,14 @@
1-
"""PrimateFace demonstration and example scripts.
1+
"""PrimateFace demonstration scripts and notebooks.
22
3-
This package contains demo scripts and utilities for PrimateFace:
4-
- Unified processing for videos and images
5-
- Primate genus classification
6-
- COCO annotation visualization
7-
- Utility modules for smoothing and visualization
8-
"""
3+
Core functionality has moved to the ``primateface`` package::
94
10-
from .constants import (
11-
DEFAULT_BBOX_THR,
12-
DEFAULT_KPT_THR,
13-
DEFAULT_NMS_THR,
14-
IMAGE_EXTENSIONS,
15-
PRIMATE_GENERA,
16-
VIDEO_EXTENSIONS,
17-
)
18-
from .process import PrimateFaceProcessor
19-
from .smooth_utils import MedianSavgolSmoother
20-
from .viz_utils import FastPoseVisualizer
5+
import primateface
6+
pf = primateface.PrimateFace()
7+
faces = pf.analyze("image.jpg")
218
22-
__all__ = [
23-
# Main processor
24-
'PrimateFaceProcessor',
25-
# Utilities
26-
'MedianSavgolSmoother',
27-
'FastPoseVisualizer',
28-
# Constants
29-
'DEFAULT_BBOX_THR',
30-
'DEFAULT_KPT_THR',
31-
'DEFAULT_NMS_THR',
32-
'IMAGE_EXTENSIONS',
33-
'VIDEO_EXTENSIONS',
34-
'PRIMATE_GENERA',
35-
]
9+
For low-level access to the processor, smoother, or visualizer::
3610
37-
__version__ = '0.1.0'
11+
from primateface._processor import PrimateFaceProcessor
12+
from primateface._smooth import MedianSavgolSmoother
13+
from primateface._viz import FastPoseVisualizer
14+
"""

demos/download_models.py

Lines changed: 86 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,116 @@
11
#!/usr/bin/env python
2-
"""
3-
Download PrimateFace models from Google Drive.
4-
Models include detection (MMDetection) and pose estimation (MMPose) checkpoints.
2+
"""Download PrimateFace models from Hugging Face Hub.
3+
4+
Models include detection (MMDetection) and pose estimation (MMPose) checkpoints
5+
hosted at https://huggingface.co/fparodi/primateface-models.
56
"""
67

7-
import os
8+
import shutil
89
import sys
910
import argparse
1011
from pathlib import Path
1112

1213
try:
13-
import gdown
14+
from primateface._model_registry import (
15+
HF_REPO_ID, HF_REPO_URL, MODELS, LIBRARY_NAME, LIBRARY_VERSION,
16+
)
1417
except ImportError:
15-
print("Installing gdown...")
16-
import subprocess
17-
subprocess.check_call([sys.executable, "-m", "pip", "install", "gdown"])
18-
import gdown
19-
20-
21-
def download_models(output_dir="."):
22-
"""Download all PrimateFace models to the specified directory."""
23-
24-
# Model IDs from Google Drive
25-
models = {
26-
"mmdet_config.py": "1Y_YFdIDRcWQLI-gRiCnOrDxCptzCiiNp",
27-
"mmdet_checkpoint.pth": "1zZ8S31zPHX5BWYKbnHxI1QOqP-fPnVFO",
28-
"mmpose_config.py": "1sG2lLybRkLwmC0IkEqtEGuT1OxwXomju",
29-
"mmpose_checkpoint.pth": "1Oa18Ty90bNE8fud0cuK3gZmPY_LAQo3Y",
30-
}
31-
32-
# Create output directory if needed
18+
# Fallback for standalone script execution
19+
from model_registry import ( # type: ignore[no-redef]
20+
HF_REPO_ID, HF_REPO_URL, MODELS, LIBRARY_NAME, LIBRARY_VERSION,
21+
)
22+
23+
24+
def download_models(output_dir: str = ".", force: bool = False) -> bool:
25+
"""Download all PrimateFace models to the specified directory.
26+
27+
Args:
28+
output_dir: Directory to save model files.
29+
force: If True, re-download even if files already exist.
30+
31+
Returns:
32+
True if all downloads succeeded.
33+
"""
34+
try:
35+
from huggingface_hub import hf_hub_download
36+
except ImportError:
37+
print(
38+
"ERROR: huggingface_hub is required to download models.\n"
39+
"Install with: uv pip install huggingface-hub\n"
40+
" or: pip install huggingface-hub",
41+
file=sys.stderr,
42+
)
43+
return False
44+
3345
output_path = Path(output_dir)
3446
output_path.mkdir(parents=True, exist_ok=True)
35-
36-
print(f"Downloading models to: {output_path.absolute()}")
47+
48+
print(f"Downloading PrimateFace models to: {output_path.absolute()}")
49+
print(f"Source: {HF_REPO_URL}")
50+
print()
51+
52+
for local_name, (subfolder, hf_filename) in MODELS.items():
53+
output_file = output_path / local_name
54+
model_type = "MMDetection" if "mmdet" in local_name else "MMPose"
55+
file_type = "config" if local_name.endswith(".py") else "checkpoint"
56+
57+
if output_file.exists() and not force:
58+
size_mb = output_file.stat().st_size / (1024 * 1024)
59+
print(f" Already exists: {local_name} ({size_mb:.1f} MB)")
60+
continue
61+
62+
print(f" Downloading {model_type} {file_type}: {local_name} ...")
63+
cached_path = hf_hub_download(
64+
repo_id=HF_REPO_ID,
65+
filename=hf_filename,
66+
subfolder=subfolder,
67+
library_name=LIBRARY_NAME,
68+
library_version=LIBRARY_VERSION,
69+
)
70+
71+
# Copy from HF cache to the requested output directory
72+
shutil.copy2(cached_path, str(output_file))
73+
74+
size_mb = output_file.stat().st_size / (1024 * 1024)
75+
print(f" Saved: {local_name} ({size_mb:.1f} MB)")
76+
3777
print()
38-
39-
# Download each model
40-
for filename, file_id in models.items():
41-
output_file = output_path / filename
42-
model_type = "MMDetection" if "mmdet" in filename else "MMPose"
43-
file_type = "config" if filename.endswith(".py") else "checkpoint"
44-
45-
print(f"Downloading {model_type} {file_type}...")
46-
url = f"https://drive.google.com/uc?id={file_id}"
47-
gdown.download(url, str(output_file), quiet=False)
48-
print()
49-
50-
# List downloaded files
51-
print("✓ All models downloaded successfully!")
52-
print("\nFiles downloaded:")
53-
for filename in models.keys():
54-
file_path = output_path / filename
78+
print("All models downloaded successfully!")
79+
print("\nFiles:")
80+
for local_name in MODELS:
81+
file_path = output_path / local_name
5582
if file_path.exists():
5683
size_mb = file_path.stat().st_size / (1024 * 1024)
57-
print(f" - {filename} ({size_mb:.1f} MB)")
58-
84+
print(f" - {local_name} ({size_mb:.1f} MB)")
85+
5986
return True
6087

6188

62-
def main():
63-
parser = argparse.ArgumentParser(description="Download PrimateFace models from Google Drive")
89+
def main() -> int:
90+
parser = argparse.ArgumentParser(
91+
description="Download PrimateFace models from Hugging Face Hub"
92+
)
6493
parser.add_argument(
6594
"output_dir",
6695
nargs="?",
6796
default=".",
68-
help="Directory to save models (default: current directory)"
97+
help="Directory to save models (default: current directory)",
98+
)
99+
parser.add_argument(
100+
"--force",
101+
action="store_true",
102+
help="Re-download even if files already exist",
69103
)
70-
104+
71105
args = parser.parse_args()
72-
106+
73107
try:
74-
download_models(args.output_dir)
108+
success = download_models(args.output_dir, force=args.force)
109+
return 0 if success else 1
75110
except Exception as e:
76111
print(f"Error downloading models: {e}", file=sys.stderr)
77112
return 1
78-
79-
return 0
80113

81114

82115
if __name__ == "__main__":
83-
sys.exit(main())
116+
sys.exit(main())

demos/model_registry.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"""Backward compatibility."""
2+
from primateface._model_registry import * # noqa: F401,F403

0 commit comments

Comments
 (0)