Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ conda activate fmpose_3d
pip install fmpose3d
```

For the animal pipeline, install the optional DeepLabCut dependency:

```bash
pip install "fmpose3d[animals]"
```

## Demos

### Testing on in-the-wild images (humans)
Expand Down
10 changes: 9 additions & 1 deletion animals/demo/vis_animals.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,15 @@
from fmpose3d.models import get_model
CFM = get_model(args.model_type)

from deeplabcut.pose_estimation_pytorch.apis import superanimal_analyze_images
try:
from deeplabcut.pose_estimation_pytorch.apis import ( # pyright: ignore[reportMissingImports]
superanimal_analyze_images,
)
except ImportError:
raise ImportError(
"DeepLabCut is required for the animal demo. "
"Install it with: pip install \"fmpose3d[animals]\""
) from None

superanimal_name = "superanimal_quadruped"
model_name = "hrnet_w32"
Expand Down
9 changes: 9 additions & 0 deletions fmpose3d/inference_api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ result = api.predict("dog.jpg")
print(result.poses_3d.shape) # (1, 26, 3)
```

Before using the animal pipeline, install the optional DeepLabCut dependency:

```bash
pip install "fmpose3d[animals]"
```


## API Documentation

Expand Down Expand Up @@ -221,6 +227,9 @@ Default 2D estimator for the human pipeline. Wraps HRNet + YOLO with a COCO →

2D estimator for the animal pipeline. Uses DeepLabCut SuperAnimal and maps quadruped80K keypoints to the 26-joint Animal3D layout.

If DeepLabCut is not installed, calling this estimator raises a clear `ImportError`
with the recommended install command: `pip install "fmpose3d[animals]"`.

- `setup_runtime()` — No-op (DLC loads lazily).
- `predict(frames: ndarray)` → `(keypoints, scores, valid_frames_mask)` — Returns Animal3D-format 2D keypoints plus a frame-level validity mask.

Expand Down
18 changes: 15 additions & 3 deletions fmpose3d/inference_api/fmpose3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,20 @@ def _compute_valid_frames_mask(
}


def _require_superanimal_analyze_images() -> Callable[..., object]:
"""Return DeepLabCut's SuperAnimal API or raise a clear ImportError."""
try:
from deeplabcut.pose_estimation_pytorch.apis import ( # pyright: ignore[reportMissingImports]
superanimal_analyze_images,
)
except ImportError:
raise ImportError(
"DeepLabCut is required for the animal 2D estimator. "
"Install it with: pip install \"fmpose3d[animals]\""
) from None
return superanimal_analyze_images


class SuperAnimalEstimator:
"""2D pose estimator for animals: DeepLabCut SuperAnimal.

Expand Down Expand Up @@ -236,9 +250,7 @@ def predict(
"""
import cv2
import tempfile
from deeplabcut.pose_estimation_pytorch.apis import (
superanimal_analyze_images,
)
superanimal_analyze_images = _require_superanimal_analyze_images()

cfg = self.cfg
num_frames = frames.shape[0]
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ dependencies = [
"scikit-image>=0.19.0",
"filterpy>=1.4.5",
"pandas>=1.0.1",
"deeplabcut==3.0.0rc13",
"huggingface_hub>=0.20.0",
]

[project.optional-dependencies]
dev = ["pytest>=7.0.0", "black>=22.0.0", "flake8>=4.0.0", "isort>=5.10.0"]
wandb = ["wandb>=0.12.0"]
viz = ["matplotlib>=3.5.0", "opencv-python>=4.5.0"]
animals = ["deeplabcut>=3.0.0rc13"]

[tool.setuptools]
include-package-data = true
Expand Down
8 changes: 2 additions & 6 deletions tests/fmpose3d_api/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import os
import socket
from importlib.util import find_spec

import pytest

Expand Down Expand Up @@ -72,12 +73,7 @@ def weights_ready(filename: str) -> bool:
HUMAN_WEIGHTS_READY: bool = weights_ready(HUMAN_WEIGHTS_FILENAME)
ANIMAL_WEIGHTS_READY: bool = weights_ready(ANIMAL_WEIGHTS_FILENAME)

try:
import deeplabcut # noqa: F401

DLC_AVAILABLE: bool = True
except ImportError:
DLC_AVAILABLE = False
DLC_AVAILABLE: bool = find_spec("deeplabcut") is not None

# ---------------------------------------------------------------------------
# Reusable skip markers
Expand Down
12 changes: 12 additions & 0 deletions tests/fmpose3d_api/test_fmpose3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,18 @@ def test_pose3d_result(self):


class TestSuperAnimalPrediction:
def test_predict_raises_clear_error_without_deeplabcut(self):
"""Missing DLC should raise a clear installation hint."""
estimator = SuperAnimalEstimator()
frames = np.random.randint(0, 255, (1, 64, 64, 3), dtype=np.uint8)

with patch(
"fmpose3d.inference_api.fmpose3d.importlib.util.find_spec",
return_value=None,
):
with pytest.raises(ImportError, match=r"fmpose3d\[animals\]"):
estimator.predict(frames)

def test_predict_returns_zeros_when_no_bodyparts(self):
"""When DLC detects nothing, keypoints are zero-filled."""
pytest.importorskip("deeplabcut")
Expand Down
Loading