Skip to content

Commit 1a7439f

Browse files
authored
Merge pull request #28 from AdaptiveMotorControlLab/dockerfile
Add Docker support and update test/demo scripts to use automatic Hugging Face downloads
2 parents 2c7cef5 + 8b402c3 commit 1a7439f

23 files changed

Lines changed: 251 additions & 121 deletions

Dockerfile

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
FROM pytorch/pytorch:2.4.1-cuda12.4-cudnn9-runtime
2+
3+
ARG USERNAME=fmpose3d
4+
ARG USER_UID=1000
5+
ARG USER_GID=1000
6+
RUN groupadd ${USERNAME} --gid ${USER_GID}
7+
RUN useradd -m -s /bin/bash -g ${USERNAME} -u ${USER_UID} ${USERNAME}
8+
RUN mkdir /app /logs /data
9+
10+
ENV DEBIAN_FRONTEND=noninteractive
11+
SHELL ["/bin/bash", "-c"]
12+
RUN apt-get update -y && apt-get install -yy --no-install-recommends \
13+
vim zsh tmux wget curl htop git sudo ssh git-lfs \
14+
python3 python3-pip \
15+
libgl1-mesa-glx libglib2.0-0 \
16+
ffmpeg \
17+
&& apt-get -y autoclean \
18+
&& apt-get -y autoremove \
19+
&& rm -rf /var/lib/apt/lists/* \
20+
&& apt-get clean
21+
22+
ENV PATH="/opt/conda/bin:${PATH}"
23+
24+
# Initialize conda for root just in case and fix symlinks
25+
RUN ln -fs /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh
26+
27+
# --- Install FMPose3D from GitHub ---
28+
RUN python -m pip install --no-cache-dir --upgrade pip && \
29+
git clone --depth 1 https://github.com/AdaptiveMotorControlLab/FMPose3D.git /tmp/fmpose3d && \
30+
python -m pip install --no-cache-dir "/tmp/fmpose3d[animals,viz]" gdown && \
31+
rm -rf /tmp/fmpose3d
32+
33+
# Allow non-root user to download DLC model weights at runtime
34+
RUN DLC_MODELZOO_DIR="$(python -c "import site, pathlib; print(pathlib.Path(site.getsitepackages()[0]) / 'deeplabcut' / 'modelzoo')")" \
35+
&& mkdir -p "${DLC_MODELZOO_DIR}/checkpoints" \
36+
&& chown -R "${USERNAME}:${USERNAME}" "${DLC_MODELZOO_DIR}"
37+
38+
# Set your user as owner of the home directory before switching
39+
RUN chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}
40+
41+
ENV NVIDIA_DRIVER_CAPABILITIES=all
42+
43+
# --- SWITCH TO USER ---
44+
USER ${USERNAME}
45+
ENV HOME=/home/${USERNAME}
46+
WORKDIR ${HOME}
47+
48+
# Ensure dotfiles exist
49+
RUN touch ${HOME}/.bashrc ${HOME}/.zshrc && \
50+
chmod 644 ${HOME}/.bashrc ${HOME}/.zshrc
51+
52+
# Install Oh My Zsh and plugins (MUST come before conda init, since OMZ overwrites .zshrc)
53+
RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended \
54+
&& git clone https://github.com/zsh-users/zsh-autosuggestions ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions \
55+
&& git clone https://github.com/zsh-users/zsh-syntax-highlighting ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting \
56+
&& sed -i 's/^plugins=(.*)$/plugins=(git zsh-autosuggestions zsh-syntax-highlighting)/' ~/.zshrc \
57+
&& echo "export PATH=\$PATH:${HOME}/.local/bin" >> ~/.zshrc
58+
59+
# Initialize conda AFTER Oh My Zsh (so conda init block is not overwritten)
60+
RUN /opt/conda/bin/conda init bash && /opt/conda/bin/conda init zsh
61+
62+
# Add conda activation to bashrc and zshrc
63+
RUN echo "conda activate base" >> ${HOME}/.bashrc && \
64+
echo "conda activate base" >> ${HOME}/.zshrc
65+
66+
SHELL ["/bin/zsh", "-c"]
67+
CMD ["zsh"]

Makefile

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# [USER: ADJUST PATH]
2+
PROJECT_NAME := fmpose3d
3+
#######
4+
# RUN #
5+
#######
6+
7+
# for local server
8+
IMG_NAME := fmpose3d
9+
IMG_TAG := v0.1
10+
DOCKERFILE := Dockerfile
11+
# Linux/macOS: use host UID/GID; Windows fallback to 1000
12+
HOST_UID ?= $(shell sh -c 'id -u 2>/dev/null || echo 1000')
13+
HOST_GID ?= $(shell sh -c 'id -g 2>/dev/null || echo 1000')
14+
BUILD_ARGS := \
15+
--build-arg USERNAME=fmpose3d \
16+
--build-arg USER_GID=$(HOST_GID) \
17+
--build-arg USER_UID=$(HOST_UID)
18+
19+
build:
20+
docker build $(BUILD_ARGS) \
21+
-t $(IMG_NAME):$(IMG_TAG) -f $(DOCKERFILE) .
22+
23+
build-clean:
24+
docker build --no-cache $(BUILD_ARGS) \
25+
-t $(IMG_NAME):$(IMG_TAG) -f $(DOCKERFILE) .
26+
27+
28+
CONTAINER_NAME := fmpose3d_dev1
29+
# [USER: ADJUST] Mount the project root into the container
30+
HOST_SRC := $(shell pwd)
31+
DOCKER_SRC := /fmpose3d
32+
VOLUMES := \
33+
--volume "$(HOST_SRC):$(DOCKER_SRC)"
34+
35+
36+
run:
37+
docker run -it --gpus all --shm-size=64g --name $(CONTAINER_NAME) -w $(DOCKER_SRC) $(VOLUMES) $(IMG_NAME):$(IMG_TAG)
38+
39+
exec:
40+
docker exec -it -w $(DOCKER_SRC) $(CONTAINER_NAME) /bin/zsh
41+
42+
exec_bash:
43+
docker exec -it -w $(DOCKER_SRC) $(CONTAINER_NAME) /bin/bash
44+
45+
stop:
46+
docker stop $(CONTAINER_NAME)
47+
48+
rm:
49+
docker rm $(CONTAINER_NAME)
50+
51+
# Help message
52+
help:
53+
@echo "Available targets:"
54+
@echo " build - Build Docker image for local server."
55+
@echo " run - Run a Docker container with GPU."
56+
@echo " exec - Attach to running container (zsh)."
57+
@echo " exec_bash - Attach to running container (bash)."
58+
@echo " stop - Stop the running container."
59+
@echo " rm - Remove the stopped container."
60+
@echo " help - Show this help message."
61+
@echo ""
62+
@echo "Usage:"
63+
@echo " 1. make build"
64+
@echo " 2. make run"
65+
@echo " 3. Inside container: pip install -e '.[animals,viz]'"
66+
@echo " 4. Inside container: sh scripts/FMPose3D_train.sh"

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ pip install "fmpose3d[animals]"
5151

5252
This visualization script is designed for single-frame based model, allowing you to easily run 3D human pose estimation on any single image.
5353

54-
Before testing, make sure you have the pre-trained model ready.
55-
You may either use the model trained by your own or download ours from [here](https://drive.google.com/drive/folders/1235_UgUQXYZtjprBOv2ZJJHY2KOAS_6p?usp=sharing) and place it in the `./pre_trained_models` directory.
54+
Pre-trained weights are downloaded automatically from [Hugging Face](https://huggingface.co/MLAdaptiveIntelligence/FMPose3D) the first time you run inference, so no manual setup is needed.
55+
56+
Alternatively, you can use your own trained weights or download ours from [Google Drive](https://drive.google.com/drive/folders/1aRZ6t_6IxSfM1nCTFOUXcYVaOk-5koGA?usp=sharing), place them in the `./pre_trained_models` directory, and set `model_weights_path` in the shell script (e.g. `demo/vis_in_the_wild.sh`).
5657

5758
Next, put your test images into folder `demo/images`. Then run the visualization script:
5859
```bash
@@ -93,7 +94,7 @@ sh ./scripts/FMPose3D_train.sh
9394

9495
### Inference
9596

96-
First, download the folder with pre-trained model from [here](https://drive.google.com/drive/folders/1235_UgUQXYZtjprBOv2ZJJHY2KOAS_6p?usp=sharing) and place it in the './pre_trained_models' directory.
97+
Pre-trained weights are fetched automatically from Hugging Face on the first run. You can also use local weights by setting `model_weights_path` in the shell script (see [Demos](#testing-on-in-the-wild-images-humans) above for details).
9798

9899
To run inference on Human3.6M:
99100

demo/vis_in_the_wild.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from fmpose3d.lib.checkpoint.download_checkpoints import ensure_checkpoints
2020
ensure_checkpoints()
2121

22+
from fmpose3d.utils.weights import resolve_weights_path
2223
from fmpose3d.lib.preprocess import h36m_coco_format, revise_kpts
2324
from fmpose3d.lib.hrnet.gen_kpts import gen_video_kpts as hrnet_pose
2425
from fmpose3d.common.arguments import opts as parse_args
@@ -113,7 +114,7 @@ def show3Dpose(vals, ax):
113114
def get_pose2D(path, output_dir, type):
114115

115116
print('\nGenerating 2D pose...')
116-
keypoints, scores = hrnet_pose(path, det_dim=416, num_peroson=1, gen_output=True, type=type)
117+
keypoints, scores = hrnet_pose(path, det_dim=416, num_person=1, gen_output=True, type=type)
117118
keypoints, scores, valid_frames = h36m_coco_format(keypoints, scores)
118119
re_kpts = revise_kpts(keypoints, scores, valid_frames)
119120
print('Generating 2D pose successful!')
@@ -278,8 +279,9 @@ def get_pose3D(path, output_dir, type='image'):
278279

279280
# if args.reload:
280281
model_dict = model['CFM'].state_dict()
281-
model_path = args.model_weights_path
282-
print(model_path)
282+
model_path = resolve_weights_path(args.model_weights_path, args.model_type)
283+
284+
print(f"Loading weights from: {model_path}")
283285
pre_dict = torch.load(model_path, map_location=device, weights_only=True)
284286
for name, key in model_dict.items():
285287
model_dict[name] = pre_dict[name]

demo/vis_in_the_wild.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ batch_size=1
66
sh_file='vis_in_the_wild.sh'
77

88
model_type='fmpose3d_humans'
9-
model_weights_path='../pre_trained_models/fmpose3d_h36m/FMpose3D_pretrained_weights.pth'
9+
10+
# By default, weights are automatically downloaded from Hugging Face Hub.
11+
# To use local weights instead, uncomment the line below:
12+
# model_weights_path='../pre_trained_models/fmpose3d_h36m/FMpose3D_pretrained_weights.pth'
13+
model_weights_path=''
1014

1115
target_path='./images/' # folder containing multiple images
1216
# target_path='./images/xx.png' # single image

fmpose3d/animals/common/animal3d_dataset.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ def __getitem__(self, item):
3838
else:
3939
keypoint_2d = np.array(data.get("keypoint_2d", []), dtype=np.float32)
4040
# normalize 2D keypoints
41-
hight = np.array(data["height"])
41+
height = np.array(data["height"])
4242
width = np.array(data["width"])
43-
keypoint_2d = normalize_screen_coordinates(keypoint_2d[..., :2], width, hight)
43+
keypoint_2d = normalize_screen_coordinates(keypoint_2d[..., :2], width, height)
4444

4545
# build 3D keypoints; append ones; fallback to zeros if missing
4646
if "keypoint_3d" in data and data["keypoint_3d"] is not None:

fmpose3d/animals/common/lifter3d.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ def triangulate_3d_batch(points_2d_batch, cameras):
306306
num_cams, num_frames, num_joints, _ = points_2d_batch.shape # (6, num_frames, 23, 2)
307307

308308
print("num_cams,num_frames,num_joinits", points_2d_batch.shape)
309-
# **1. compute projection matrics (6, 3, 4)**
309+
# **1. compute projection matrices (6, 3, 4)**
310310
proj_matrices = np.array(
311311
[cam["intrinsic_matrix"] @ np.hstack((cam["R"], cam["T"])) for cam in cameras]
312312
) # numpy array (6, 3, 4)
@@ -361,7 +361,7 @@ def triangulate_3d(points_2d, cameras):
361361
for i, cam in enumerate(cameras):
362362
K, dist, R, T = cam["intrinsic_matrix"], cam["distortion_coeffs"], cam["R"], cam["T"]
363363

364-
P = K @ np.hstack((R, T)) # projectio matrix
364+
P = K @ np.hstack((R, T)) # projection matrix
365365
# print("Projection Matrix P:\n", P)
366366

367367
proj_matrices.append(P)
@@ -628,7 +628,7 @@ def main():
628628
# reprojected_2d = project_3d_to_2d(points_3d, cameras[i])
629629
# # visualize_2d_on_video(video_files[i], frame_number, points_2d_frame[i], reprojected_2d, output_path)
630630

631-
# # test on trangulate 3D batch
631+
# # test on triangulate 3D batch
632632
# left_frame_id = 10
633633
# right_frame_id = 60
634634

fmpose3d/animals/common/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ def save_top_N_models(
354354

355355
def back_to_ori_uv(cropped_uv, bb_box):
356356
"""
357-
for cropped uv, back to origial uv to help do the uvd->xyz operation
357+
for cropped uv, back to original uv to help do the uvd->xyz operation
358358
:return:
359359
"""
360360
N, T, V, _ = cropped_uv.size()
@@ -423,7 +423,7 @@ def project_to_2d(X, camera_params):
423423
424424
Arguments:
425425
X -- 3D points in *camera space* to transform (N, *, 3)
426-
camera_params -- intrinsic parameteres (N, 2+2+3+2=9)
426+
camera_params -- intrinsic parameters (N, 2+2+3+2=9)
427427
"""
428428
assert X.shape[-1] == 3 # B,J,3
429429
assert len(camera_params.shape) == 2 # camera_params:[B,1,9]

fmpose3d/common/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ def save_top_N_models(
378378

379379
def back_to_ori_uv(cropped_uv, bb_box):
380380
"""
381-
for cropped uv, back to origial uv to help do the uvd->xyz operation
381+
for cropped uv, back to original uv to help do the uvd->xyz operation
382382
:return:
383383
"""
384384
N, T, V, _ = cropped_uv.size()
@@ -453,7 +453,7 @@ def project_to_2d(X, camera_params):
453453
454454
Arguments:
455455
X -- 3D points in *camera space* to transform (N, *, 3)
456-
camera_params -- intrinsic parameteres (N, 2+2+3+2=9)
456+
camera_params -- intrinsic parameters (N, 2+2+3+2=9)
457457
"""
458458
assert X.shape[-1] == 3 # B,J,3
459459
assert len(camera_params.shape) == 2 # camera_params:[B,1,9]

fmpose3d/inference_api/fmpose3d.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@
3434
ProgressCallback = Callable[[int, int], None]
3535

3636

37-
#: HuggingFace repository hosting the official FMPose3D checkpoints.
38-
_HF_REPO_ID: str = "deruyter92/fmpose_temp"
37+
from fmpose3d.utils.weights import HF_REPO_ID as _HF_REPO_ID
3938

4039
# Default camera-to-world rotation quaternion (from the demo script).
4140
_DEFAULT_CAM_ROTATION = np.array(
@@ -759,8 +758,6 @@ class _IngestedInput:
759758
# ---------------------------------------------------------------------------
760759

761760

762-
# FIXME @deruyter92: THIS IS TEMPORARY UNTIL WE DOWNLOAD THE WEIGHTS FROM HUGGINGFACE
763-
SKIP_WEIGHTS_VALIDATION = object() # sentinel value to indicate that the weights should not be validated
764761

765762
class FMPose3DInference:
766763
"""High-level, two-step inference API for FMPose3D.

0 commit comments

Comments
 (0)