Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8d8795c
Route FMPose3D through modelzoo video inference
deruyter92 Feb 25, 2026
c03fb2f
Move fmpose 3d module to pose_estimation_pytorch/modelzoo
deruyter92 Feb 25, 2026
34909b9
add dedicated FMPose3D video inference function
deruyter92 Feb 25, 2026
6d2eb0d
skip 3d predictions for invalid/empty 2d poses
deruyter92 Feb 25, 2026
785b55f
update tests
deruyter92 Feb 25, 2026
75f160c
Include 3d outputs in return
deruyter92 Feb 25, 2026
afc64a7
Clamp and warn: set max_individuals to 1
deruyter92 Feb 25, 2026
1f4cd90
change 3d data to dataframe
deruyter92 Feb 25, 2026
79306d6
add tests
deruyter92 Feb 25, 2026
04518bd
Update documentation
deruyter92 Feb 25, 2026
de1c573
Update modelzoo documentation fmpose3d
deruyter92 Feb 26, 2026
45f91b2
add commment legenda to __init__ for keypoint refactoring
deruyter92 Feb 26, 2026
a541c84
Add future note for keypoint refactoring
deruyter92 Feb 26, 2026
a96b8ca
fmpose3d: Add pytest markers for fmpose3d, unittest and functional
deruyter92 Feb 26, 2026
dd042b4
Apply formatting
deruyter92 Feb 26, 2026
1b3d94c
fix parameter name change in docs
deruyter92 Feb 26, 2026
8412c73
adjust parameter name -> fmpose_return_3d
deruyter92 Feb 26, 2026
ae8af5a
Update parameter naming superanimal_name
deruyter92 Feb 26, 2026
c0de52e
change recommended superanimal name for fmpose_3d
deruyter92 Feb 28, 2026
b771f33
Merge branch 'main' into jaap/fmpose_modelzoo_integration
deruyter92 Mar 12, 2026
8923015
Merge branch 'main' into jaap/fmpose_modelzoo_integration
deruyter92 Mar 19, 2026
f2c14c5
bump required version extra fmpose3d >= 0.0.8
deruyter92 Mar 19, 2026
9c78ddc
Merge branch 'main' into jaap/fmpose_modelzoo_integration
MMathisLab Mar 19, 2026
18203b9
apply linting
deruyter92 Mar 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion _toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ parts:
- file: docs/recipes/OpenVINO
- file: docs/recipes/flip_and_rotate
- file: docs/recipes/pose_cfg_file_breakdown
- file: docs/recipes/fmpose3d
- file: docs/recipes/publishing_notebooks_into_the_DLC_main_cookbook

- caption: Hardware Tips
Expand Down
8 changes: 0 additions & 8 deletions deeplabcut/modelzoo/fmpose_3d/__init__.py

This file was deleted.

56 changes: 0 additions & 56 deletions deeplabcut/modelzoo/fmpose_3d/fmpose3d.py

This file was deleted.

4 changes: 3 additions & 1 deletion deeplabcut/modelzoo/models_to_framework.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
"hrnet_w32": "pytorch",
"resnet_50": "pytorch",
"rtmpose_s": "pytorch",
"rtmpose_x": "pytorch"
"rtmpose_x": "pytorch",
"fmpose3d_humans": "pytorch",
"fmpose3d_animals": "pytorch",
}
60 changes: 55 additions & 5 deletions deeplabcut/modelzoo/video_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
from __future__ import annotations

import json
import logging
import os
import warnings
from pathlib import Path
from typing import Optional, Union

Expand All @@ -34,6 +36,8 @@
video_to_frames,
)

logger = logging.getLogger(__name__)


def get_checkpoint_epoch(checkpoint_path):
"""
Expand Down Expand Up @@ -80,6 +84,7 @@ def video_inference_superanimal(
customized_model_config: Optional[str] = None,
plot_bboxes: bool = True,
create_labeled_video: bool = True,
fmpose_return_3d: bool = False,
):
"""
This function performs inference on videos using a pretrained SuperAnimal model.
Expand Down Expand Up @@ -177,6 +182,13 @@ def video_inference_superanimal(
create_labeled_video (bool):
Specifies if a labeled video needs to be created, True by default.

fmpose_return_3d (bool):
Only used when ``model_name`` starts with ``"fmpose3d"``.
If True, include in-memory 3D poses in the return payload
(per video: ``{"df_2d": ..., "df_3d": ...}``).
If False (default), keep the legacy return payload with only
the 2D DataFrame per video.

Raises:
NotImplementedError:
If the model is not found in the modelzoo.
Expand Down Expand Up @@ -316,8 +328,10 @@ def video_inference_superanimal(
"""
if scale_list is None:
scale_list = []

print(f"Running video inference on {videos} with {superanimal_name}_{model_name}")
if not model_name.startswith("fmpose3d"):
print(
f"Running video inference on {videos} with {superanimal_name}_{model_name}"
)
dlc_root_path = get_deeplabcut_path()
modelzoo_path = os.path.join(dlc_root_path, "modelzoo")
available_architectures = json.load(
Expand Down Expand Up @@ -352,6 +366,42 @@ def video_inference_superanimal(
create_labeled_video=create_labeled_video,
)
elif framework == "pytorch":
if model_name.startswith("fmpose3d"):
logger.info("Running video inference on %s using %s", videos, model_name)

recommended_superanimal_name = {
"fmpose3d_animals": "quadruped",
"fmpose3d_humans": "human",
}.get(model_name)

Comment thread
deruyter92 marked this conversation as resolved.
provided_superanimal_name = superanimal_name or "<not provided>"
if superanimal_name != recommended_superanimal_name:
warnings.warn(
"For FMPose3D models, model selection is driven by 'model_name'. But for API "
"consistency, it is recommended to set 'superanimal_name' to the corresponding value."
f"Provided superanimal_name={provided_superanimal_name!r} differs from the "
f"recommended value for {model_name!r}: "
f"{recommended_superanimal_name!r}.",
stacklevel=2,
)
Comment thread
deruyter92 marked this conversation as resolved.

from deeplabcut.pose_estimation_pytorch.modelzoo.fmpose_3d.inference import (
_video_inference_fmpose3d,
)

return _video_inference_fmpose3d(
video_paths=videos,
model_name=model_name,
max_individuals=max_individuals,
pcutoff=pcutoff,
batch_size=batch_size,
dest_folder=dest_folder,
device=device,
create_labeled_video=create_labeled_video,
cropping=cropping,
include_3d_in_return=fmpose_return_3d,
)

torchvision_detector_name = None
if superanimal_name != "superanimal_humanbody" and detector_name is None:
raise ValueError(
Expand Down Expand Up @@ -483,9 +533,9 @@ def video_inference_superanimal(

if superanimal_name != "superanimal_humanbody":
detector_snapshot_prefix = f"snapshot-{detector_name}"
config["detector"]["runner"][
"snapshot_prefix"
] = detector_snapshot_prefix
config["detector"]["runner"]["snapshot_prefix"] = (
detector_snapshot_prefix
)

# the model config's parameters need to be updated for adaptation training
model_config_path = model_folder / "pytorch_config.yaml"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@

Model weights are hosted on HuggingFace Hub and are downloaded automatically when no local path is provided. The library is installable via `pip install fmpose3d` and requires Python >= 3.8.

For a full overview and documentation on the API, see https://github.com/AdaptiveMotorControlLab/FMPose3D.
For a full overview and documentation on the API, see https://github.com/AdaptiveMotorControlLab/FMPose3D.
15 changes: 15 additions & 0 deletions deeplabcut/pose_estimation_pytorch/modelzoo/fmpose_3d/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
DeepLabCut2.0-3.0 Toolbox (deeplabcut.org)
© A. & M. Mathis Labs
https://github.com/DeepLabCut/DeepLabCut
Please see AUTHORS for contributors.
https://github.com/DeepLabCut/DeepLabCut/blob/main/AUTHORS
Licensed under GNU Lesser General Public License v3.0
"""

# NOTE: this module may contain items that need refactoring during
# the keypoint migration.

# kpt_refactor - Needs attention when refactoring keypoints
# i_o - This module writes keypoints to disk
# pandas - This module relies on pandas (might be moved to polars)
137 changes: 137 additions & 0 deletions deeplabcut/pose_estimation_pytorch/modelzoo/fmpose_3d/fmpose3d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""
DeepLabCut2.0-3.0 Toolbox (deeplabcut.org)
© A. & M. Mathis Labs
https://github.com/DeepLabCut/DeepLabCut
Please see AUTHORS for contributors.
https://github.com/DeepLabCut/DeepLabCut/blob/main/AUTHORS
Licensed under GNU Lesser General Public License v3.0
"""

from dataclasses import dataclass

from fmpose3d import (
FMPose3DConfig,
FMPose3DInference,
SupportedModel,
)


@dataclass(frozen=True)
class FMPose3DModelMetadata:
"""Metadata for an FMPose3D model variant."""

superanimal_name: str
bodyparts: tuple[str, ...]

@property
def num_bodyparts(self) -> int:
return len(self.bodyparts)

def build_model_cfg(self, max_individuals: int) -> dict:
"""Build a DLC-compatible model_cfg dict for create_df_from_prediction."""
return {
"metadata": {
"bodyparts": list(self.bodyparts),
"unique_bodyparts": [],
"individuals": [f"individual{i + 1}" for i in range(max_individuals)],
},
}


FMPOSE3D_MODEL_METADATA: dict[str, FMPose3DModelMetadata] = {
"fmpose3d_humans": FMPose3DModelMetadata(
superanimal_name="superanimal_humanbody",
bodyparts=(
"pelvis",
"right_hip",
"right_knee",
"right_ankle",
"left_hip",
"left_knee",
"left_ankle",
"spine",
"thorax",
"neck",
"head",
"left_shoulder",
"left_elbow",
"left_wrist",
"right_shoulder",
"right_elbow",
"right_wrist",
),
),
"fmpose3d_animals": FMPose3DModelMetadata(
superanimal_name="superanimal_quadruped",
bodyparts=(
"left_eye",
"right_eye",
"nose",
"neck",
"root_of_tail",
"left_shoulder",
"left_elbow",
"left_front_paw",
"right_shoulder",
"right_elbow",
"right_front_paw",
"left_hip",
"left_knee",
"left_back_paw",
"right_hip",
"right_knee",
"right_back_paw",
"withers",
"throat",
"left_ear",
"right_ear",
"mouth",
"chin",
"left_hock",
"right_hock",
"tail_tip",
),
),
}


def get_fmpose3d_inference_api(
model_type: SupportedModel = "fmpose3d_humans",
snapshot_path: str | None = None,
device: str | None = None,
config_kwargs: dict = {},
) -> FMPose3DInference:
"""
Get a FMPose3DInference API for a given model type and snapshot path.

Args:
model_type: one of the supported model types: "fmpose3d_humans", "fmpose3d_animals",
snapshot_path: The path to the snapshot file. If None, FMPose3D will download the default snapshot.
device: The device to use. If None, the device will be inferred from the environment.
config_kwargs: Additional keyword arguments to pass to the FMPose3DConfig.
Returns:
FMPose3DInference: An FMPose3DInference API runner.

Example Usages
```python
# Initialize the API (downloads the default weights automatically from huggingface)
fmpose = get_fmpose3d_inference_api(
model_type="fmpose3d_animals",
device="cuda:0",
)

# Run inference on an image
predictions_3d = fmpose.predict(source="path/to/image.jpg") # or (H, W, 3) numpy array

# Lift 2d predictions to 3d
keypoints_2d = np.random.rand(num_frames, num_joints, 2)
predictions_3d = fmpose.pose_3d(keypoints_2d=keypoints_2d)
```
"""
model_config = FMPose3DConfig(model_type=model_type, **config_kwargs)
fmpose3d_api = FMPose3DInference(
model_config,
model_weights_path=snapshot_path,
device=device,
)
return fmpose3d_api
Loading
Loading